fix
This commit is contained in:
+25
-16
@@ -15,6 +15,7 @@ from ..extensions import db
|
||||
from ..models import AppSetting, CompanySettings, FuelCard, FuelCardPolicy, FuelCardStationRule, FuelEntry, FuelStationCompany, FuelStationPoint, OrlenPrice, User, Vehicle
|
||||
from ..services import POLISH_REGIONS, aggregate_ure_companies, calculate_costs, fetch_orlen_price, fetch_orlen_range, fetch_ure_stations, invoice_period
|
||||
from ..station_catalog import sync_station_catalog
|
||||
from ..orlen_sync import sync_orlen_prices
|
||||
|
||||
FUEL_TYPES = ("PB95", "PB98", "DIESEL", "LPG")
|
||||
|
||||
@@ -28,23 +29,31 @@ def orlen_prices():
|
||||
@api.post('/orlen/sync')
|
||||
@role_required('boss','admin')
|
||||
def orlen_sync():
|
||||
d=payload(); fuels=d.get('fuels',[]); fuels=[fuels] if isinstance(fuels,str) else fuels; fuels=[f.upper() for f in fuels if f.upper() in FUEL_TYPES]; year=int(d.get('year',date.today().year))
|
||||
regions=d.get('regions',[]); regions=[regions] if isinstance(regions,str) else regions; regions=[str(r).strip().lower() for r in regions if str(r).strip().lower() in POLISH_REGIONS]
|
||||
if not fuels:return fail('Wybierz co najmniej jedno paliwo')
|
||||
settings=CompanySettings.query.first(); result={}; total=0
|
||||
d = payload()
|
||||
fuels = d.get('fuels', [])
|
||||
fuels = [fuels] if isinstance(fuels, str) else fuels
|
||||
fuels = [fuel.upper() for fuel in fuels if fuel.upper() in FUEL_TYPES]
|
||||
regions = d.get('regions', [])
|
||||
regions = [regions] if isinstance(regions, str) else regions
|
||||
regions = [str(region).strip().lower() for region in regions if str(region).strip().lower() in POLISH_REGIONS]
|
||||
if not fuels:
|
||||
return fail('Wybierz co najmniej jedno paliwo')
|
||||
|
||||
try:
|
||||
for fuel in fuels:
|
||||
added=updated=0
|
||||
for row in fetch_orlen_range(fuel,date(year,1,1),min(date(year,12,31),date.today()),'all' if fuel=='LPG' else settings.region):
|
||||
if fuel=='LPG' and regions and row.get('region') not in regions: continue
|
||||
x=OrlenPrice.query.filter_by(fuel_type=row['fuel_type'],effective_date=row['effective_date'],region=row['region']).first()
|
||||
if x:
|
||||
for k,v in row.items():setattr(x,k,v)
|
||||
x.fetched_at=datetime.utcnow();updated+=1
|
||||
else:db.session.add(OrlenPrice(**row));added+=1
|
||||
result[fuel]={'added':added,'updated':updated};total+=added+updated
|
||||
db.session.commit();return response(result,f'Przetworzono {total} rekordów')
|
||||
except Exception as exc:db.session.rollback();return fail(str(exc),502)
|
||||
year = int(d.get('year', date.today().year))
|
||||
settings = CompanySettings.query.first()
|
||||
result, total = sync_orlen_prices(
|
||||
fuels=fuels,
|
||||
year=year,
|
||||
regions=regions,
|
||||
default_region=settings.region,
|
||||
full_refresh=boolv(d, 'full_refresh'),
|
||||
)
|
||||
db.session.commit()
|
||||
return response(result, f'Przetworzono {total} rekordów')
|
||||
except Exception as exc:
|
||||
db.session.rollback()
|
||||
return fail(str(exc), 502)
|
||||
|
||||
@api.get('/orlen/preview')
|
||||
@login_required
|
||||
|
||||
+25
-18
@@ -13,6 +13,7 @@ from .station_catalog import sync_station_catalog
|
||||
from . import THEMES
|
||||
from .vat import entry_vat_rate, freeze_vat_action, global_vat_override, save_global_vat_override
|
||||
from .settlement import calculate as calculate_snapshot_settlement, freeze as freeze_settlement
|
||||
from .orlen_sync import sync_orlen_prices
|
||||
|
||||
main = Blueprint("main", __name__)
|
||||
FUEL_TYPES = ("PB95", "PB98", "DIESEL", "LPG")
|
||||
@@ -499,28 +500,34 @@ def orlen_data():
|
||||
@login_required
|
||||
@roles("boss", "admin")
|
||||
def orlen_sync():
|
||||
payload = request.get_json(silent=True) or request.form
|
||||
fuels = payload.get("fuels", [])
|
||||
if isinstance(fuels, str): fuels = [fuels]
|
||||
fuels = [f.upper() for f in fuels if f.upper() in FUEL_TYPES]
|
||||
year = int(payload.get("year", date.today().year))
|
||||
if not fuels: return jsonify({"ok": False, "error": "Wybierz co najmniej jedno paliwo."}), 400
|
||||
settings = CompanySettings.query.first(); start = date(year,1,1); end = min(date(year,12,31), date.today())
|
||||
result = {}; total = 0
|
||||
data = request.get_json(silent=True) or request.form
|
||||
fuels = data.get("fuels", [])
|
||||
if isinstance(fuels, str):
|
||||
fuels = [fuels]
|
||||
fuels = [fuel.upper() for fuel in fuels if fuel.upper() in FUEL_TYPES]
|
||||
if not fuels:
|
||||
return jsonify({"ok": False, "error": "Wybierz co najmniej jedno paliwo."}), 400
|
||||
|
||||
regions = data.get("regions", [])
|
||||
if isinstance(regions, str):
|
||||
regions = [regions]
|
||||
regions = [str(region).strip().lower() for region in regions if str(region).strip().lower() in POLISH_REGIONS]
|
||||
full_refresh = data.get("full_refresh") in (True, 1, "1", "true", "on", "yes")
|
||||
|
||||
try:
|
||||
for fuel in fuels:
|
||||
imported = 0; updated = 0
|
||||
for row in fetch_orlen_range(fuel, start, end, "all" if fuel == "LPG" else settings.region):
|
||||
existing = OrlenPrice.query.filter_by(fuel_type=row["fuel_type"], effective_date=row["effective_date"], region=row["region"]).first()
|
||||
if existing:
|
||||
existing.price_per_liter=row["price_per_liter"]; existing.raw_value=row["raw_value"]; existing.product_name=row["product_name"]; existing.source=row["source"]; existing.fetched_at=datetime.utcnow(); updated += 1
|
||||
else:
|
||||
db.session.add(OrlenPrice(**row)); imported += 1
|
||||
result[fuel] = {"added": imported, "updated": updated}; total += imported + updated
|
||||
settings = CompanySettings.query.first()
|
||||
result, total = sync_orlen_prices(
|
||||
fuels=fuels,
|
||||
year=int(data.get("year", date.today().year)),
|
||||
regions=regions,
|
||||
default_region=settings.region,
|
||||
full_refresh=full_refresh,
|
||||
)
|
||||
db.session.commit()
|
||||
return jsonify({"ok": True, "message": f"Przetworzono {total} rekordów.", "result": result})
|
||||
except Exception as exc:
|
||||
db.session.rollback(); return jsonify({"ok": False, "error": str(exc)}), 502
|
||||
db.session.rollback()
|
||||
return jsonify({"ok": False, "error": str(exc)}), 502
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import func
|
||||
|
||||
from .extensions import db
|
||||
from .models import OrlenPrice
|
||||
from .services import fetch_orlen_range
|
||||
|
||||
|
||||
def _year_bounds(year, today=None):
|
||||
today = today or date.today()
|
||||
start = date(year, 1, 1)
|
||||
end = min(date(year, 12, 31), today)
|
||||
if end < start:
|
||||
raise ValueError("Nie można synchronizować danych dla przyszłego roku")
|
||||
return start, end
|
||||
|
||||
|
||||
def _latest_date(fuel, start, end, region=""):
|
||||
query = db.session.query(func.max(OrlenPrice.effective_date)).filter(
|
||||
OrlenPrice.fuel_type == fuel,
|
||||
OrlenPrice.effective_date.between(start, end),
|
||||
)
|
||||
if fuel == "LPG":
|
||||
query = query.filter(OrlenPrice.region == region)
|
||||
else:
|
||||
query = query.filter(OrlenPrice.region == "")
|
||||
return query.scalar()
|
||||
|
||||
|
||||
def _sync_start(fuel, start, end, regions, full_refresh):
|
||||
if full_refresh:
|
||||
return start
|
||||
|
||||
if fuel != "LPG":
|
||||
return _latest_date(fuel, start, end) or start
|
||||
|
||||
latest_dates = [_latest_date("LPG", start, end, region) for region in regions]
|
||||
return min((value or start) for value in latest_dates)
|
||||
|
||||
|
||||
def sync_orlen_prices(fuels, year, regions, default_region, full_refresh=False, today=None):
|
||||
start, end = _year_bounds(year, today=today)
|
||||
target_regions = [str(region).strip().lower() for region in regions if str(region).strip()]
|
||||
if not target_regions:
|
||||
target_regions = [str(default_region).strip().lower()]
|
||||
|
||||
result = {}
|
||||
total = 0
|
||||
|
||||
for fuel in fuels:
|
||||
fuel_regions = target_regions if fuel == "LPG" else []
|
||||
date_from = _sync_start(fuel, start, end, fuel_regions, full_refresh)
|
||||
added = 0
|
||||
updated = 0
|
||||
|
||||
rows = fetch_orlen_range(
|
||||
fuel,
|
||||
date_from,
|
||||
end,
|
||||
"all" if fuel == "LPG" else default_region,
|
||||
)
|
||||
for row in rows:
|
||||
if fuel == "LPG" and row.get("region") not in fuel_regions:
|
||||
continue
|
||||
|
||||
existing = OrlenPrice.query.filter_by(
|
||||
fuel_type=row["fuel_type"],
|
||||
effective_date=row["effective_date"],
|
||||
region=row["region"],
|
||||
).first()
|
||||
if existing:
|
||||
for key, value in row.items():
|
||||
setattr(existing, key, value)
|
||||
existing.fetched_at = datetime.utcnow()
|
||||
updated += 1
|
||||
else:
|
||||
db.session.add(OrlenPrice(**row))
|
||||
added += 1
|
||||
|
||||
processed = added + updated
|
||||
result[fuel] = {
|
||||
"added": added,
|
||||
"updated": updated,
|
||||
"from": date_from.isoformat(),
|
||||
"to": end.isoformat(),
|
||||
"full_refresh": bool(full_refresh),
|
||||
}
|
||||
total += processed
|
||||
|
||||
return result, total
|
||||
@@ -1,2 +1,2 @@
|
||||
(()=>{const F=FuelTrack;F.bindLpgRegionPicker=()=>{const select=F.qs('#lpg-region-select');if(!select||select.dataset.choicesBound)return;select.dataset.choicesBound='1';const choices=new Choices(select,{removeItemButton:true,searchEnabled:true,shouldSort:false,position:'bottom',placeholder:true,placeholderValue:'Wybierz województwa',noResultsText:'Brak województwa',noChoicesText:'Brak opcji',itemSelectText:'Wybierz'});const modal=F.qs('#lpgRegionsModal');modal?.addEventListener('hidden.bs.modal',()=>{const count=F.qsa('option:checked',select).length;const badge=F.qs('#lpg-region-count');if(badge)badge.textContent=count?`${count} wybrano`:'Domyślne województwo'})};F.bindOrlenSync=()=>{const btn=F.qs('#orlen-sync-btn');if(!btn||btn.dataset.bound)return;btn.dataset.bound='1';btn.addEventListener('click',async()=>{const form=F.qs('#orlen-filter-form'),fuels=F.qsa('[name=fuel]:checked',form).map(x=>x.value),year=F.qs('[name=year]',form).value,status=F.qs('#orlen-sync-status');if(!fuels.length){status.textContent='Wybierz co najmniej jedno paliwo.';return}btn.disabled=true;const old=btn.innerHTML;btn.innerHTML='Pobieranie…';try{const regions=F.qsa('[name=region] option:checked',form).map(x=>x.value);const r=await fetch('/api/orlen/sync',{method:'POST',headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest'},body:JSON.stringify({fuels,year,regions})}),d=await r.json();if(!r.ok)throw new Error(d.error||'Błąd pobierania');status.textContent=d.message;await F.replaceMain(`/orlen?${new URLSearchParams(new FormData(form))}`,{push:true});F.notify(d.message)}catch(e){status.textContent=`Błąd: ${e.message}`;F.notify(e.message,'danger')}finally{btn.disabled=false;btn.innerHTML=old}})}})();
|
||||
(()=>{const F=FuelTrack;F.bindLpgRegionPicker=()=>{const select=F.qs('#lpg-region-select');if(!select||select.dataset.choicesBound)return;select.dataset.choicesBound='1';new Choices(select,{removeItemButton:true,searchEnabled:true,shouldSort:false,position:'bottom',placeholder:true,placeholderValue:'Wybierz województwa',noResultsText:'Brak województwa',noChoicesText:'Brak opcji',itemSelectText:'Wybierz'});const modal=F.qs('#lpgRegionsModal');modal?.addEventListener('hidden.bs.modal',()=>{const count=F.qsa('option:checked',select).length;const badge=F.qs('#lpg-region-count');if(badge)badge.textContent=count?`${count} wybrano`:'Domyślne województwo'})};F.bindOrlenSync=()=>{const incrementalBtn=F.qs('#orlen-sync-btn'),fullBtn=F.qs('#orlen-full-sync-btn');if(!incrementalBtn||incrementalBtn.dataset.bound)return;incrementalBtn.dataset.bound='1';if(fullBtn)fullBtn.dataset.bound='1';const sync=async fullRefresh=>{const form=F.qs('#orlen-filter-form'),fuels=F.qsa('[name=fuel]:checked',form).map(x=>x.value),year=F.qs('[name=year]',form).value,status=F.qs('#orlen-sync-status');if(!fuels.length){status.textContent='Wybierz co najmniej jedno paliwo.';return}if(fullRefresh&&!confirm(`Pobrać ponownie wszystkie dane dla ${year} roku? Operacja może potrwać dłużej.`))return;const regions=F.qsa('[name=region] option:checked',form).map(x=>x.value),old=incrementalBtn.innerHTML;incrementalBtn.disabled=true;if(fullBtn)fullBtn.disabled=true;incrementalBtn.innerHTML=fullRefresh?'Pełne pobieranie…':'Pobieranie…';try{const r=await fetch('/api/orlen/sync',{method:'POST',headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest'},body:JSON.stringify({fuels,year,regions,full_refresh:fullRefresh})}),d=await r.json();if(!r.ok||!d.ok)throw new Error(d.error||d.message||'Błąd pobierania');status.textContent=d.message;await F.replaceMain(`/orlen?${new URLSearchParams(new FormData(form))}`,{push:true});F.notify(d.message)}catch(e){status.textContent=`Błąd: ${e.message}`;F.notify(e.message,'danger')}finally{incrementalBtn.disabled=false;if(fullBtn)fullBtn.disabled=false;incrementalBtn.innerHTML=old}};incrementalBtn.addEventListener('click',()=>sync(false));fullBtn?.addEventListener('click',()=>sync(true))}})();
|
||||
;(()=>{const F=FuelTrack;F.bindOrlenComparison=()=>{const type=F.qs('#comparison-type'),fuel=F.qs('#compare-fuel');if(type&&!type.dataset.compareBound){type.dataset.compareBound='1';const update=()=>{F.qs('#year-comparison-fields')?.classList.toggle('d-none',type.value!=='years');F.qs('#period-comparison-fields')?.classList.toggle('d-none',type.value!=='periods')};type.addEventListener('change',update);update()}if(fuel&&!fuel.dataset.compareBound){fuel.dataset.compareBound='1';const update=()=>F.qs('#compare-region-wrap')?.classList.toggle('d-none',fuel.value!=='LPG');fuel.addEventListener('change',update);update()}}})();
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{% else %}<tr><td colspan="8" class="text-center text-body-secondary py-4">Brak tankowań spełniających kryteria.</td></tr>{% endfor %}</tbody></table></div>
|
||||
{% if entries_page.pages > 1 %}<nav><ul class="pagination mb-0">{% for p in entries_page.iter_pages() %}{% if p %}<li class="page-item {% if p==entries_page.page %}active{% endif %}"><a class="page-link ajax-nav-link" href="{{url_for('main.admin_fuel_entries',page=p,q=q,month=month,company_id=company_id)}}">{{p}}</a></li>{% else %}<li class="page-item disabled"><span class="page-link">…</span></li>{% endif %}{% endfor %}</ul></nav>{% endif %}
|
||||
</div></div>
|
||||
<div class="modal fade" id="fuelEntryEditModal" tabindex="-1"><div class="modal-dialog modal-lg modal-dialog-scrollable"><div class="modal-content"><form id="fuel-entry-edit-form" class="ajax-form" data-method="PUT"><div class="modal-header"><h2 class="modal-title fs-5">Edytuj tankowanie</h2><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="row g-3">
|
||||
<div class="modal fade" id="fuelEntryEditModal" tabindex="-1"><div class="modal-dialog modal-lg modal-dialog-scrollable"><form id="fuel-entry-edit-form" class="modal-content ajax-form" data-method="PUT"><div class="modal-header"><h2 class="modal-title fs-5">Edytuj tankowanie</h2><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="row g-3">
|
||||
<div class="col-md-6"><label class="form-label">Pojazd</label><select class="form-select" name="vehicle_id" required>{% for v in vehicles %}<option value="{{v.id}}">{{v.name}} · {{v.registration}}</option>{% endfor %}</select></div>
|
||||
<div class="col-md-6"><label class="form-label">Data i godzina</label><input class="form-control" type="datetime-local" name="fueled_at" required></div>
|
||||
<div class="col-md-4"><label class="form-label">Paliwo</label><select class="form-select" name="fuel_type">{% for f in ['PB95','PB98','DIESEL','LPG'] %}<option>{{f}}</option>{% endfor %}</select></div>
|
||||
@@ -36,5 +36,5 @@
|
||||
<div class="col-md-3"><label class="form-label">Dopłata netto / l</label><input class="form-control" type="number" step="0.0001" name="snapshot_surcharge_per_liter" required></div>
|
||||
<div class="col-md-6"><div class="form-check mt-4"><input class="form-check-input" type="checkbox" name="snapshot_uses_last_price" id="edit-snapshot-last-price"><label class="form-check-label" for="edit-snapshot-last-price">Użyj zapisanej ceny hurtowej jako Last Price</label></div></div>
|
||||
<div class="col-md-6"><label class="form-label">Źródło / opis reguły</label><input class="form-control" name="snapshot_rule_source" maxlength="160"></div>
|
||||
</div></div><div class="modal-footer"><button type="button" class="btn btn-danger me-auto" id="fuel-entry-delete-btn">Usuń tankowanie</button><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Anuluj</button><button class="btn btn-primary">Zapisz zmiany</button></div></form></div></div></div>
|
||||
</div></div><div class="modal-footer"><button type="button" class="btn btn-danger me-auto" id="fuel-entry-delete-btn">Usuń tankowanie</button><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Anuluj</button><button class="btn btn-primary">Zapisz zmiany</button></div></form></div></div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -20,7 +20,18 @@
|
||||
<div class="col-md-3"><div class="form-check mb-2"><input class="form-check-input" type="checkbox" name="gross" value="1" id="gross-price" {% if include_vat %}checked{% endif %}><label class="form-check-label" for="gross-price">Dolicz VAT</label></div></div>
|
||||
<div class="col-md-3"><button class="btn btn-secondary w-100">Pokaż dane</button></div>
|
||||
</form>
|
||||
{% if current_user.role in ['boss','admin'] %}<div class="d-flex align-items-center gap-3 mt-3"><button type="button" id="orlen-sync-btn" class="btn btn-primary">Pobierz z API</button><span id="orlen-sync-status" class="small text-body-secondary" aria-live="polite"></span></div>{% endif %}
|
||||
{% if current_user.role in ['boss','admin'] %}
|
||||
<div class="d-flex flex-wrap align-items-center gap-3 mt-3">
|
||||
<div class="btn-group">
|
||||
<button type="button" id="orlen-sync-btn" class="btn btn-primary">Pobierz nowe dane</button>
|
||||
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">Opcje pobierania</span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><button type="button" id="orlen-full-sync-btn" class="dropdown-item">Pobierz cały wybrany rok ponownie</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<span id="orlen-sync-status" class="small text-body-secondary" aria-live="polite"></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div></div>
|
||||
|
||||
<div class="modal fade lpg-region-modal" id="lpgRegionsModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog modal-lg modal-dialog-centered"><div class="modal-content"><div class="modal-header"><h2 class="modal-title fs-5">Województwa LPG</h2><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><label class="form-label" for="lpg-region-select">Wybierz jedno lub wiele województw</label><select class="form-select" name="region" id="lpg-region-select" form="orlen-filter-form" multiple>{% for r in regions %}<option value="{{r}}" {% if r in selected_regions %}selected{% endif %}>{{r}}</option>{% endfor %}</select></div><div class="modal-footer"><button type="button" class="btn btn-primary" data-bs-dismiss="modal">Gotowe</button></div></div></div></div>
|
||||
|
||||
Reference in New Issue
Block a user