From 6cfbfa658c0e8c3579a539fbce530b081cb73f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Mon, 13 Jul 2026 15:55:37 +0200 Subject: [PATCH] uprawnienia do stacji i usuwanie firm --- app/api.py | 20 ++++++++++++++++-- app/database_migrations.py | 14 +++++++++++- app/main.py | 34 +++++++++++++++++++++++------- app/models.py | 1 + app/templates/admin_companies.html | 2 +- app/templates/fuel_form.html | 2 +- app/templates/stations.html | 2 +- 7 files changed, 61 insertions(+), 14 deletions(-) diff --git a/app/api.py b/app/api.py index 9539fb2..3b28d19 100644 --- a/app/api.py +++ b/app/api.py @@ -148,8 +148,11 @@ def create_fuel_entry(): try: odo=int(d['odometer']); fueled_at=datetime.fromisoformat(d['fueled_at']); station_company=db.session.get(FuelStationCompany,int(d['station_company_id'])) if d.get('station_company_id') else None settings=vehicle.company or current_user.company or CompanySettings.query.first() - if station_company and settings.allowed_stations and station_company not in settings.allowed_stations: + access_mode=settings.station_access_mode or 'all_prefer_favorites' + if station_company and access_mode=='allowed_only' and station_company not in settings.allowed_stations: return fail('Ta stacja nie jest dozwolona przez politykę firmy',403) + if station_company and access_mode=='favorites_allowed_only' and station_company not in settings.allowed_stations and station_company not in settings.favorite_stations: + return fail('Ta stacja nie jest ulubiona ani dozwolona przez politykę firmy',403) wholesale=source=None; region=(station_company.selected_region if station_company else None) or settings.region try: p=fetch_orlen_price(d['fuel_type'],fueled_at.date(),region);wholesale=p['price_per_liter'];source=p['source'] @@ -391,7 +394,7 @@ def companies_list(): def companies_create(): d=payload() try: - c=CompanySettings(name=(d.get('name') or '').strip(),entity_type=d.get('entity_type','JDG'),vat_rate=Decimal(d.get('vat_rate') or 23),vat_deduction_percent=Decimal(d.get('vat_deduction_percent') or 50),region=(d.get('region') or 'mazowieckie').lower(),invoice_split_day=int(d.get('invoice_split_day') or 15),active=True) + c=CompanySettings(name=(d.get('name') or '').strip(),entity_type=d.get('entity_type','JDG'),vat_rate=Decimal(d.get('vat_rate') or 23),vat_deduction_percent=Decimal(d.get('vat_deduction_percent') or 50),region=(d.get('region') or 'mazowieckie').lower(),invoice_split_day=int(d.get('invoice_split_day') or 15),active=True,station_access_mode='all_prefer_favorites') if not c.name: raise ValueError('Nazwa firmy jest wymagana') db.session.add(c);db.session.commit();return response({'id':c.id,'redirect':url_for('main.admin_companies',company_id=c.id)},'Dodano firmę',201) except Exception as exc:db.session.rollback();return fail(str(exc)) @@ -445,6 +448,19 @@ def company_favorites(company_id): c.favorite_stations=FuelStationCompany.query.filter(FuelStationCompany.id.in_(ids),FuelStationCompany.active.is_(True)).all() if ids else [] db.session.commit();socketio.emit('app_changed',{'resource':'company_favorites','company_id':c.id});return response(message='Zapisano ulubione stacje firmy') + +@api.put('/companies//station-access') +@role_required('boss','admin') +def company_station_access(company_id): + company=CompanySettings.query.get_or_404(company_id) + if current_user.role=='boss' and current_user.company_id!=company.id:return fail('Brak uprawnień',403) + d=payload();mode=(d.get('station_access_mode') or '').strip() + modes={'all_prefer_favorites','all','allowed_only','favorites_allowed_only'} + if mode not in modes:return fail('Nieprawidłowy tryb dostępu do stacji') + company.station_access_mode=mode + db.session.commit();socketio.emit('app_changed',{'resource':'station_access','company_id':company.id}) + return response({'station_access_mode':mode},'Zapisano politykę dostępu do stacji') + @api.put('/companies//allowed-stations') @role_required('boss','admin') def company_allowed_stations(company_id): diff --git a/app/database_migrations.py b/app/database_migrations.py index 7d035c5..30e3a5b 100644 --- a/app/database_migrations.py +++ b/app/database_migrations.py @@ -14,7 +14,19 @@ class Migration: upgrade: Callable[[], None] -MIGRATIONS: tuple[Migration, ...] = () +def add_station_access_mode() -> None: + inspector = inspect(db.engine) + columns = {column["name"] for column in inspector.get_columns("company_settings")} + if "station_access_mode" not in columns: + db.session.execute(text( + "ALTER TABLE company_settings ADD COLUMN station_access_mode VARCHAR(40) " + "NOT NULL DEFAULT 'all_prefer_favorites'" + )) + + +MIGRATIONS: tuple[Migration, ...] = ( + Migration("20260713_company_station_access_mode", add_station_access_mode), +) def ensure_migration_table() -> None: inspector = inspect(db.engine) diff --git a/app/main.py b/app/main.py index 994f339..528fd7e 100644 --- a/app/main.py +++ b/app/main.py @@ -156,7 +156,13 @@ def fuel(): fueled_at = datetime.fromisoformat(request.form["fueled_at"]); wholesale = None; source = None station_company_id = request.form.get("station_company_id", type=int) station_company = db.session.get(FuelStationCompany, station_company_id) if station_company_id else None - price_region = station_company.selected_region if station_company and station_company.selected_region else CompanySettings.query.first().region + settings = vehicle.company or current_user.company or CompanySettings.query.first() + access_mode = settings.station_access_mode or "all_prefer_favorites" + if station_company and access_mode == "allowed_only" and station_company not in settings.allowed_stations: + response = error_response("Ta stacja nie jest dozwolona przez politykę firmy", 403); return response or redirect(url_for("main.fuel")) + if station_company and access_mode == "favorites_allowed_only" and station_company not in settings.allowed_stations and station_company not in settings.favorite_stations: + response = error_response("Ta stacja nie jest ulubiona ani dozwolona przez politykę firmy", 403); return response or redirect(url_for("main.fuel")) + price_region = station_company.selected_region if station_company and station_company.selected_region else settings.region try: p = fetch_orlen_price(request.form["fuel_type"], fueled_at.date(), price_region); wholesale=p["price_per_liter"]; source=p["source"] except Exception: pass @@ -165,20 +171,32 @@ def fuel(): vehicle.current_odometer = odometer; db.session.add(e); db.session.commit(); socketio.emit("fuel_added", {"vehicle": vehicle.name, "gross": round(e.gross,2)}); response = ok_response("Zapisano tankowanie", redirect=url_for("main.dashboard")); return response or redirect(url_for("main.dashboard")) settings=current_user.company or CompanySettings.query.first() allowed=list(settings.allowed_stations) + company_favorites=list(settings.favorite_stations) + access_mode=settings.station_access_mode or "all_prefer_favorites" base_query=FuelStationCompany.query.filter_by(active=True) - stations=(base_query.filter(FuelStationCompany.id.in_([x.id for x in allowed])) if allowed else base_query).order_by(FuelStationCompany.station_count.desc(), FuelStationCompany.brand_name.asc()).all() - favorites=[x for x in current_user.favorite_stations if x in stations] + if access_mode == "allowed_only": + allowed_ids=[x.id for x in allowed] + stations=base_query.filter(FuelStationCompany.id.in_(allowed_ids)).all() if allowed_ids else [] + elif access_mode == "favorites_allowed_only": + scope_ids={x.id for x in allowed}|{x.id for x in company_favorites} + stations=base_query.filter(FuelStationCompany.id.in_(scope_ids)).all() if scope_ids else [] + else: + stations=base_query.all() + stations=sorted(stations,key=lambda x:(-x.station_count,x.display_name.lower())) + user_favorites=[x for x in current_user.favorite_stations if x in stations] + user_favorite_ids={x.id for x in current_user.favorite_stations} + favorites=list(user_favorites) favorite_scope="user" - if not favorites and settings.favorite_stations: - favorites=[x for x in settings.favorite_stations if x in stations];favorite_scope="company" - if not favorites: favorites=sorted(stations,key=lambda x:(-x.station_count,x.company_name.lower()))[:10];favorite_scope="default" + if not favorites and access_mode in ("all_prefer_favorites", "favorites_allowed_only") and company_favorites: + favorites=[x for x in company_favorites if x in stations];favorite_scope="company" + if not favorites and access_mode == "all_prefer_favorites": favorites=stations[:10];favorite_scope="default" favorites=sorted(favorites,key=lambda x:(-x.station_count,x.display_name.lower())) favorite_ids={x.id for x in favorites} top_stations=sorted((x for x in stations if x.id not in favorite_ids),key=lambda x:(-x.station_count,x.display_name.lower()))[:10] proposed_ids=favorite_ids|{x.id for x in top_stations} - favorite_catalog=sorted(stations,key=lambda x:(-x.station_count,x.display_name.lower())) + favorite_catalog=sorted(stations,key=lambda x:(0 if x.id in user_favorite_ids else 1,-x.station_count,x.display_name.lower())) other_stations=sorted((x for x in stations if x.id not in proposed_ids),key=lambda x:x.display_name.lower()) - return render_template("fuel_form.html", vehicles=vehicles, stations=stations, favorite_stations=favorites, top_stations=top_stations, favorite_catalog=favorite_catalog, other_stations=other_stations, now=datetime.now().strftime("%Y-%m-%dT%H:%M"), restricted=bool(allowed), favorite_scope=favorite_scope, company=settings, fuel_cards=FuelCard.query.filter_by(active=True,company_id=settings.id).order_by(FuelCard.name).all()) + return render_template("fuel_form.html", vehicles=vehicles, stations=stations, favorite_stations=favorites, top_stations=top_stations, favorite_catalog=favorite_catalog, other_stations=other_stations, now=datetime.now().strftime("%Y-%m-%dT%H:%M"), restricted=access_mode in ("allowed_only", "favorites_allowed_only"), access_mode=access_mode, user_favorite_ids=user_favorite_ids, favorite_scope=favorite_scope, company=settings, fuel_cards=FuelCard.query.filter_by(active=True,company_id=settings.id).order_by(FuelCard.name).all()) @main.route("/api/orlen-price") @login_required diff --git a/app/models.py b/app/models.py index 6c0bcdb..3b4f152 100644 --- a/app/models.py +++ b/app/models.py @@ -56,6 +56,7 @@ class CompanySettings(db.Model): region = db.Column(db.String(60), default="mazowieckie", nullable=False) invoice_split_day = db.Column(db.Integer, default=15, nullable=False) active = db.Column(db.Boolean, default=True, nullable=False) + station_access_mode = db.Column(db.String(40), default="all_prefer_favorites", nullable=False) users = db.relationship("User", back_populates="company", foreign_keys="User.company_id") class AppSetting(db.Model): diff --git a/app/templates/admin_companies.html b/app/templates/admin_companies.html index 6870f7f..0da0c2f 100644 --- a/app/templates/admin_companies.html +++ b/app/templates/admin_companies.html @@ -59,7 +59,7 @@
-

Dostęp do stacji

{% if selected_company.allowed_stations %}

Aktywna lista zamknięta: {{ selected_company.allowed_stations|length }} dozwolonych stacji.

{% else %}

Wszystkie aktywne stacje są dozwolone.

{% endif %}
+

Dostęp do stacji

{% if selected_company.station_access_mode == 'allowed_only' %}Tylko stacje z listy dozwolonych ({{ selected_company.allowed_stations|length }}).{% elif selected_company.station_access_mode == 'favorites_allowed_only' %}Tylko ulubione i stacje z listy dozwolonych.{% elif selected_company.station_access_mode == 'all' %}Wszystkie stacje, bez preferowania ulubionych.{% else %}Wszystkie stacje są dozwolone, ulubione są preferowane.{% endif %}

Konfiguruj dostęp
diff --git a/app/templates/fuel_form.html b/app/templates/fuel_form.html index 49a7123..ea55b5c 100644 --- a/app/templates/fuel_form.html +++ b/app/templates/fuel_form.html @@ -1 +1 @@ -{% extends 'base.html' %}{% block content %}

Nowe tankowanie

Najpierw pokazujemy ulubione oraz 10 największych pozostałych sieci. {% if restricted %} · firma ograniczyła listę do dozwolonych sieci{% endif %}.
Cena Orlen zostanie pobrana automatycznie.
{% endblock %}{% block scripts %}{% endblock %} +{% extends 'base.html' %}{% block content %}

Nowe tankowanie

Najpierw pokazujemy ulubione oraz 10 największych pozostałych sieci. {% if restricted %} · firma ograniczyła listę do dozwolonych sieci{% endif %}.
Cena Orlen zostanie pobrana automatycznie.
{% endblock %}{% block scripts %}{% endblock %} diff --git a/app/templates/stations.html b/app/templates/stations.html index eea3d43..63cd03a 100644 --- a/app/templates/stations.html +++ b/app/templates/stations.html @@ -9,7 +9,7 @@
{% endif %}
-

Dostęp do stacji

{% if allowed_ids %}

Aktywna jest lista zamknięta: dozwolonych stacji: {{ allowed_ids|length }}. Niezaznaczone stacje nie pojawią się w formularzu tankowania.

{% else %}

Wszystkie aktywne stacje są dozwolone. Ulubione wpływają tylko na kolejność propozycji.

{% endif %}
{% if allowed_ids %}
{% endif %}
+

Dostęp do stacji

Wybierz, czy stacje mają być tylko preferowane, czy także ograniczane.


Domyślnie: wszystkie stacje są dostępne, a ulubione pojawiają się na początku. Lista „dozwolone” jest używana wyłącznie w trybach ograniczających.
{% for s in station_page.items %}
Stacja / firmaPunktyPaliwaUstawieniaWarunki kart
{% if s.station_count > 1 %}{% else %}{{s.display_name}}{% endif %}{% if s.company_name != s.display_name %}
{{s.company_name}}
{% endif %}
NIP: {{s.nip or '—'}} · REGON: {{s.regon or '—'}}