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 ..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 ..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 ..station_catalog import sync_station_catalog
|
||||||
|
from ..orlen_sync import sync_orlen_prices
|
||||||
|
|
||||||
FUEL_TYPES = ("PB95", "PB98", "DIESEL", "LPG")
|
FUEL_TYPES = ("PB95", "PB98", "DIESEL", "LPG")
|
||||||
|
|
||||||
@@ -28,23 +29,31 @@ def orlen_prices():
|
|||||||
@api.post('/orlen/sync')
|
@api.post('/orlen/sync')
|
||||||
@role_required('boss','admin')
|
@role_required('boss','admin')
|
||||||
def orlen_sync():
|
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))
|
d = payload()
|
||||||
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]
|
fuels = d.get('fuels', [])
|
||||||
if not fuels:return fail('Wybierz co najmniej jedno paliwo')
|
fuels = [fuels] if isinstance(fuels, str) else fuels
|
||||||
settings=CompanySettings.query.first(); result={}; total=0
|
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:
|
try:
|
||||||
for fuel in fuels:
|
year = int(d.get('year', date.today().year))
|
||||||
added=updated=0
|
settings = CompanySettings.query.first()
|
||||||
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):
|
result, total = sync_orlen_prices(
|
||||||
if fuel=='LPG' and regions and row.get('region') not in regions: continue
|
fuels=fuels,
|
||||||
x=OrlenPrice.query.filter_by(fuel_type=row['fuel_type'],effective_date=row['effective_date'],region=row['region']).first()
|
year=year,
|
||||||
if x:
|
regions=regions,
|
||||||
for k,v in row.items():setattr(x,k,v)
|
default_region=settings.region,
|
||||||
x.fetched_at=datetime.utcnow();updated+=1
|
full_refresh=boolv(d, 'full_refresh'),
|
||||||
else:db.session.add(OrlenPrice(**row));added+=1
|
)
|
||||||
result[fuel]={'added':added,'updated':updated};total+=added+updated
|
db.session.commit()
|
||||||
db.session.commit();return response(result,f'Przetworzono {total} rekordów')
|
return response(result, f'Przetworzono {total} rekordów')
|
||||||
except Exception as exc:db.session.rollback();return fail(str(exc),502)
|
except Exception as exc:
|
||||||
|
db.session.rollback()
|
||||||
|
return fail(str(exc), 502)
|
||||||
|
|
||||||
@api.get('/orlen/preview')
|
@api.get('/orlen/preview')
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
+25
-18
@@ -13,6 +13,7 @@ from .station_catalog import sync_station_catalog
|
|||||||
from . import THEMES
|
from . import THEMES
|
||||||
from .vat import entry_vat_rate, freeze_vat_action, global_vat_override, save_global_vat_override
|
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 .settlement import calculate as calculate_snapshot_settlement, freeze as freeze_settlement
|
||||||
|
from .orlen_sync import sync_orlen_prices
|
||||||
|
|
||||||
main = Blueprint("main", __name__)
|
main = Blueprint("main", __name__)
|
||||||
FUEL_TYPES = ("PB95", "PB98", "DIESEL", "LPG")
|
FUEL_TYPES = ("PB95", "PB98", "DIESEL", "LPG")
|
||||||
@@ -499,28 +500,34 @@ def orlen_data():
|
|||||||
@login_required
|
@login_required
|
||||||
@roles("boss", "admin")
|
@roles("boss", "admin")
|
||||||
def orlen_sync():
|
def orlen_sync():
|
||||||
payload = request.get_json(silent=True) or request.form
|
data = request.get_json(silent=True) or request.form
|
||||||
fuels = payload.get("fuels", [])
|
fuels = data.get("fuels", [])
|
||||||
if isinstance(fuels, str): fuels = [fuels]
|
if isinstance(fuels, str):
|
||||||
fuels = [f.upper() for f in fuels if f.upper() in FUEL_TYPES]
|
fuels = [fuels]
|
||||||
year = int(payload.get("year", date.today().year))
|
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
|
if not fuels:
|
||||||
settings = CompanySettings.query.first(); start = date(year,1,1); end = min(date(year,12,31), date.today())
|
return jsonify({"ok": False, "error": "Wybierz co najmniej jedno paliwo."}), 400
|
||||||
result = {}; total = 0
|
|
||||||
|
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:
|
try:
|
||||||
for fuel in fuels:
|
settings = CompanySettings.query.first()
|
||||||
imported = 0; updated = 0
|
result, total = sync_orlen_prices(
|
||||||
for row in fetch_orlen_range(fuel, start, end, "all" if fuel == "LPG" else settings.region):
|
fuels=fuels,
|
||||||
existing = OrlenPrice.query.filter_by(fuel_type=row["fuel_type"], effective_date=row["effective_date"], region=row["region"]).first()
|
year=int(data.get("year", date.today().year)),
|
||||||
if existing:
|
regions=regions,
|
||||||
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
|
default_region=settings.region,
|
||||||
else:
|
full_refresh=full_refresh,
|
||||||
db.session.add(OrlenPrice(**row)); imported += 1
|
)
|
||||||
result[fuel] = {"added": imported, "updated": updated}; total += imported + updated
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify({"ok": True, "message": f"Przetworzono {total} rekordów.", "result": result})
|
return jsonify({"ok": True, "message": f"Przetworzono {total} rekordów.", "result": result})
|
||||||
except Exception as exc:
|
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()}}})();
|
;(()=>{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>
|
{% 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 %}
|
{% 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></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">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-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>
|
<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-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"><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 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 %}
|
{% 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"><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>
|
<div class="col-md-3"><button class="btn btn-secondary w-100">Pokaż dane</button></div>
|
||||||
</form>
|
</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></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>
|
<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>
|
||||||
|
|||||||
@@ -362,3 +362,146 @@ def test_last_price_zero_and_full_vat_deduction():
|
|||||||
from app.domain.costs import calculate_net_price_costs
|
from app.domain.costs import calculate_net_price_costs
|
||||||
assert round(float(calculate_net_price_costs(10, 23, 0)["effective_cost"]), 2) == 12.30
|
assert round(float(calculate_net_price_costs(10, 23, 0)["effective_cost"]), 2) == 12.30
|
||||||
assert round(float(calculate_net_price_costs(10, 23, 100)["effective_cost"]), 2) == 10.00
|
assert round(float(calculate_net_price_costs(10, 23, 100)["effective_cost"]), 2) == 10.00
|
||||||
|
|
||||||
|
|
||||||
|
def test_fuel_entry_edit_modal_uses_scrollable_bootstrap_structure(tmp_path):
|
||||||
|
app = make_app(tmp_path, "fuel-entry-modal.db")
|
||||||
|
client = app.test_client()
|
||||||
|
client.post('/login', data={'email': 'admin@example.com', 'password': 'admin123!'})
|
||||||
|
|
||||||
|
html = client.get('/admin/fuel-entries').get_data(as_text=True)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'modal-dialog modal-lg modal-dialog-scrollable">'
|
||||||
|
'<form id="fuel-entry-edit-form" class="modal-content ajax-form"'
|
||||||
|
in html
|
||||||
|
)
|
||||||
|
assert '<div class="modal-content"><form id="fuel-entry-edit-form"' not in html
|
||||||
|
|
||||||
|
|
||||||
|
def test_orlen_incremental_sync_starts_at_latest_saved_record(tmp_path, monkeypatch):
|
||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
from app.extensions import db
|
||||||
|
from app.models import OrlenPrice
|
||||||
|
from app.orlen_sync import sync_orlen_prices
|
||||||
|
import app.orlen_sync as orlen_sync_module
|
||||||
|
|
||||||
|
app = make_app(tmp_path, "orlen-incremental.db")
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def fake_fetch(fuel, date_from, date_to, region):
|
||||||
|
calls.append((fuel, date_from, date_to, region))
|
||||||
|
return []
|
||||||
|
|
||||||
|
monkeypatch.setattr(orlen_sync_module, 'fetch_orlen_range', fake_fetch)
|
||||||
|
with app.app_context():
|
||||||
|
db.session.add_all([
|
||||||
|
OrlenPrice(fuel_type='PB95', effective_date=date(2026, 1, 2), price_per_liter=Decimal('4.1000'), raw_value=Decimal('4100'), region='', product_name='PB95', source='test'),
|
||||||
|
OrlenPrice(fuel_type='PB95', effective_date=date(2026, 7, 15), price_per_liter=Decimal('4.5000'), raw_value=Decimal('4500'), region='', product_name='PB95', source='test'),
|
||||||
|
])
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
result, total = sync_orlen_prices(
|
||||||
|
fuels=['PB95'],
|
||||||
|
year=2026,
|
||||||
|
regions=[],
|
||||||
|
default_region='mazowieckie',
|
||||||
|
full_refresh=False,
|
||||||
|
today=date(2026, 8, 2),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert calls == [('PB95', date(2026, 7, 15), date(2026, 8, 2), 'mazowieckie')]
|
||||||
|
assert result['PB95']['from'] == '2026-07-15'
|
||||||
|
assert result['PB95']['full_refresh'] is False
|
||||||
|
assert total == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_orlen_full_refresh_starts_at_beginning_of_selected_year(tmp_path, monkeypatch):
|
||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
from app.extensions import db
|
||||||
|
from app.models import OrlenPrice
|
||||||
|
from app.orlen_sync import sync_orlen_prices
|
||||||
|
import app.orlen_sync as orlen_sync_module
|
||||||
|
|
||||||
|
app = make_app(tmp_path, "orlen-full-refresh.db")
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def fake_fetch(fuel, date_from, date_to, region):
|
||||||
|
calls.append((fuel, date_from, date_to, region))
|
||||||
|
return []
|
||||||
|
|
||||||
|
monkeypatch.setattr(orlen_sync_module, 'fetch_orlen_range', fake_fetch)
|
||||||
|
with app.app_context():
|
||||||
|
db.session.add(OrlenPrice(fuel_type='DIESEL', effective_date=date(2026, 7, 20), price_per_liter=Decimal('4.9000'), raw_value=Decimal('4900'), region='', product_name='DIESEL', source='test'))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
result, _ = sync_orlen_prices(
|
||||||
|
fuels=['DIESEL'],
|
||||||
|
year=2026,
|
||||||
|
regions=[],
|
||||||
|
default_region='mazowieckie',
|
||||||
|
full_refresh=True,
|
||||||
|
today=date(2026, 8, 2),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert calls == [('DIESEL', date(2026, 1, 1), date(2026, 8, 2), 'mazowieckie')]
|
||||||
|
assert result['DIESEL']['from'] == '2026-01-01'
|
||||||
|
assert result['DIESEL']['full_refresh'] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_orlen_lpg_incremental_sync_uses_oldest_latest_region_date(tmp_path, monkeypatch):
|
||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
from app.extensions import db
|
||||||
|
from app.models import OrlenPrice
|
||||||
|
from app.orlen_sync import sync_orlen_prices
|
||||||
|
import app.orlen_sync as orlen_sync_module
|
||||||
|
|
||||||
|
app = make_app(tmp_path, "orlen-lpg-incremental.db")
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def fake_fetch(fuel, date_from, date_to, region):
|
||||||
|
calls.append((fuel, date_from, date_to, region))
|
||||||
|
return [
|
||||||
|
{'fuel_type': 'LPG', 'effective_date': date(2026, 8, 1), 'price_per_liter': Decimal('2.1000'), 'raw_value': Decimal('2.1000'), 'region': 'mazowieckie', 'product_name': 'LPG', 'source': 'test'},
|
||||||
|
{'fuel_type': 'LPG', 'effective_date': date(2026, 8, 1), 'price_per_liter': Decimal('2.2000'), 'raw_value': Decimal('2.2000'), 'region': 'pomorskie', 'product_name': 'LPG', 'source': 'test'},
|
||||||
|
]
|
||||||
|
|
||||||
|
monkeypatch.setattr(orlen_sync_module, 'fetch_orlen_range', fake_fetch)
|
||||||
|
with app.app_context():
|
||||||
|
db.session.add_all([
|
||||||
|
OrlenPrice(fuel_type='LPG', effective_date=date(2026, 7, 20), price_per_liter=Decimal('2.0000'), raw_value=Decimal('2.0000'), region='mazowieckie', product_name='LPG', source='test'),
|
||||||
|
OrlenPrice(fuel_type='LPG', effective_date=date(2026, 7, 25), price_per_liter=Decimal('2.0000'), raw_value=Decimal('2.0000'), region='śląskie', product_name='LPG', source='test'),
|
||||||
|
])
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
result, total = sync_orlen_prices(
|
||||||
|
fuels=['LPG'],
|
||||||
|
year=2026,
|
||||||
|
regions=['mazowieckie', 'śląskie'],
|
||||||
|
default_region='mazowieckie',
|
||||||
|
full_refresh=False,
|
||||||
|
today=date(2026, 8, 2),
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
assert OrlenPrice.query.filter_by(fuel_type='LPG', region='pomorskie').count() == 0
|
||||||
|
|
||||||
|
assert calls == [('LPG', date(2026, 7, 20), date(2026, 8, 2), 'all')]
|
||||||
|
assert result['LPG']['from'] == '2026-07-20'
|
||||||
|
assert total == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_orlen_page_has_incremental_and_full_refresh_controls(tmp_path):
|
||||||
|
app = make_app(tmp_path, "orlen-sync-ui.db")
|
||||||
|
client = app.test_client()
|
||||||
|
client.post('/login', data={'email': 'admin@example.com', 'password': 'admin123!'})
|
||||||
|
|
||||||
|
html = client.get('/orlen?mode=history').get_data(as_text=True)
|
||||||
|
|
||||||
|
assert 'id="orlen-sync-btn"' in html
|
||||||
|
assert 'Pobierz nowe dane' in html
|
||||||
|
assert 'id="orlen-full-sync-btn"' in html
|
||||||
|
assert 'Pobierz cały wybrany rok ponownie' in html
|
||||||
|
|||||||
Reference in New Issue
Block a user