Hide compliance status on none-Schengen trips.
This commit is contained in:
parent
7e3f9a9b1e
commit
5d5ce61da4
2 changed files with 59 additions and 45 deletions
|
|
@ -7,26 +7,37 @@ from datetime import date, timedelta
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
from . import get_country, trip
|
from . import get_country, trip
|
||||||
from .schengen import calculate_schengen_time, extract_schengen_stays_from_travel
|
from .schengen import (
|
||||||
|
SCHENGEN_COUNTRIES,
|
||||||
|
calculate_schengen_time,
|
||||||
|
extract_schengen_stays_from_travel,
|
||||||
|
)
|
||||||
from .types import SchengenCalculation, SchengenStay, StrDict, Trip
|
from .types import SchengenCalculation, SchengenStay, StrDict, Trip
|
||||||
|
|
||||||
|
|
||||||
def add_schengen_compliance_to_trip(trip_obj: Trip) -> Trip:
|
def trip_includes_schengen(trip: Trip) -> bool:
|
||||||
|
return bool({c.alpha_2.lower() for c in trip.countries} & SCHENGEN_COUNTRIES)
|
||||||
|
|
||||||
|
|
||||||
|
def add_schengen_compliance_to_trip(trip: Trip) -> Trip:
|
||||||
"""Add Schengen compliance information to a trip object."""
|
"""Add Schengen compliance information to a trip object."""
|
||||||
|
if not trip_includes_schengen(trip):
|
||||||
|
return trip
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Calculate Schengen compliance for the trip
|
# Calculate Schengen compliance for the trip
|
||||||
calculation = calculate_schengen_time(trip_obj.travel)
|
calculation = calculate_schengen_time(trip.travel)
|
||||||
|
|
||||||
# Add the calculation to the trip object
|
# Add the calculation to the trip object
|
||||||
trip_obj.schengen_compliance = calculation
|
trip.schengen_compliance = calculation
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log the error but don't fail the trip loading
|
# Log the error but don't fail the trip loading
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"Failed to calculate Schengen compliance for trip {trip_obj.start}: {e}"
|
f"Failed to calculate Schengen compliance for trip {trip.start}: {e}"
|
||||||
)
|
)
|
||||||
trip_obj.schengen_compliance = None
|
trip.schengen_compliance = None
|
||||||
|
|
||||||
return trip_obj
|
return trip
|
||||||
|
|
||||||
|
|
||||||
def get_schengen_compliance_for_all_trips(
|
def get_schengen_compliance_for_all_trips(
|
||||||
|
|
@ -127,7 +138,9 @@ def schengen_dashboard_data(data_dir: str | None = None) -> dict[str, typing.Any
|
||||||
data_dir = flask.current_app.config["PERSONAL_DATA"]
|
data_dir = flask.current_app.config["PERSONAL_DATA"]
|
||||||
|
|
||||||
# Load all trips
|
# Load all trips
|
||||||
trip_list = trip.build_trip_list(data_dir)
|
trip_list = [
|
||||||
|
trip for trip in trip.build_trip_list(data_dir) if trip_includes_schengen(trip)
|
||||||
|
]
|
||||||
|
|
||||||
# Calculate current compliance with trip information
|
# Calculate current compliance with trip information
|
||||||
all_travel_items = []
|
all_travel_items = []
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,43 @@ def airport_label(airport: StrDict) -> str:
|
||||||
return f"{name} ({airport['iata']})"
|
return f"{name} ({airport['iata']})"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SchengenStay:
|
||||||
|
"""Represents a stay in the Schengen area."""
|
||||||
|
|
||||||
|
entry_date: date
|
||||||
|
exit_date: date | None # None if currently in Schengen
|
||||||
|
country: str
|
||||||
|
days: int
|
||||||
|
trip_date: date | None = None # Trip start date for linking
|
||||||
|
trip_name: str | None = None # Trip name for display
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
"""Post init."""
|
||||||
|
if self.exit_date is None:
|
||||||
|
# Currently in Schengen, calculate days up to today
|
||||||
|
self.days = (date.today() - self.entry_date).days + 1
|
||||||
|
else:
|
||||||
|
self.days = (self.exit_date - self.entry_date).days + 1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SchengenCalculation:
|
||||||
|
"""Result of Schengen time calculation."""
|
||||||
|
|
||||||
|
total_days_used: int
|
||||||
|
days_remaining: int
|
||||||
|
is_compliant: bool
|
||||||
|
current_180_day_period: tuple[date, date] # (start, end)
|
||||||
|
stays_in_period: SchengenStay
|
||||||
|
next_reset_date: typing.Optional[date] # When the 180-day window resets
|
||||||
|
|
||||||
|
@property
|
||||||
|
def days_over_limit(self) -> int:
|
||||||
|
"""Days over the 90-day limit."""
|
||||||
|
return max(0, self.total_days_used - 90)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Trip:
|
class Trip:
|
||||||
"""Trip."""
|
"""Trip."""
|
||||||
|
|
@ -67,7 +104,7 @@ class Trip:
|
||||||
flight_bookings: list[StrDict] = field(default_factory=list)
|
flight_bookings: list[StrDict] = field(default_factory=list)
|
||||||
name: str | None = None
|
name: str | None = None
|
||||||
private: bool = False
|
private: bool = False
|
||||||
schengen_compliance: typing.Optional["SchengenCalculation"] = None
|
schengen_compliance: SchengenCalculation | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self) -> str:
|
def title(self) -> str:
|
||||||
|
|
@ -409,39 +446,3 @@ class Holiday:
|
||||||
if self.local_name and self.local_name != self.name
|
if self.local_name and self.local_name != self.name
|
||||||
else self.name
|
else self.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SchengenStay:
|
|
||||||
"""Represents a stay in the Schengen area."""
|
|
||||||
|
|
||||||
entry_date: date
|
|
||||||
exit_date: typing.Optional[date] # None if currently in Schengen
|
|
||||||
country: str
|
|
||||||
days: int
|
|
||||||
trip_date: typing.Optional[date] = None # Trip start date for linking
|
|
||||||
trip_name: typing.Optional[str] = None # Trip name for display
|
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
|
||||||
if self.exit_date is None:
|
|
||||||
# Currently in Schengen, calculate days up to today
|
|
||||||
self.days = (date.today() - self.entry_date).days + 1
|
|
||||||
else:
|
|
||||||
self.days = (self.exit_date - self.entry_date).days + 1
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class SchengenCalculation:
|
|
||||||
"""Result of Schengen time calculation."""
|
|
||||||
|
|
||||||
total_days_used: int
|
|
||||||
days_remaining: int
|
|
||||||
is_compliant: bool
|
|
||||||
current_180_day_period: tuple[date, date] # (start, end)
|
|
||||||
stays_in_period: list["SchengenStay"]
|
|
||||||
next_reset_date: typing.Optional[date] # When the 180-day window resets
|
|
||||||
|
|
||||||
@property
|
|
||||||
def days_over_limit(self) -> int:
|
|
||||||
"""Days over the 90-day limit."""
|
|
||||||
return max(0, self.total_days_used - 90)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue