M33: enforce booking readiness workflow
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
const BRUSSELS_TIME_ZONE = "Europe/Brussels";
|
||||
|
||||
const partsFormatter = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: BRUSSELS_TIME_ZONE,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: "h23",
|
||||
});
|
||||
|
||||
function partsAt(value: Date): Record<string, string> {
|
||||
return Object.fromEntries(
|
||||
partsFormatter.formatToParts(value)
|
||||
.filter((part) => part.type !== "literal")
|
||||
.map((part) => [part.type, part.value]),
|
||||
);
|
||||
}
|
||||
|
||||
export function toBrusselsDateTimeLocal(value: Date): string {
|
||||
const parts = partsAt(value);
|
||||
return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`;
|
||||
}
|
||||
|
||||
export function brusselsDateTimeFromNow(hours: number): string {
|
||||
const value = new Date(Date.now() + hours * 60 * 60 * 1000);
|
||||
value.setUTCMinutes(0, 0, 0);
|
||||
return toBrusselsDateTimeLocal(value);
|
||||
}
|
||||
|
||||
/** Convert a Fleet Ops Europe/Brussels wall-clock value to an unambiguous UTC instant. */
|
||||
export function brusselsLocalToIso(value: string): string {
|
||||
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/.exec(value);
|
||||
if (!match) throw new Error("Invalid Brussels date-time");
|
||||
const [, year, month, day, hour, minute] = match;
|
||||
const wallClockUtc = Date.UTC(+year, +month - 1, +day, +hour, +minute);
|
||||
let candidate = wallClockUtc;
|
||||
for (let iteration = 0; iteration < 3; iteration += 1) {
|
||||
const parts = partsAt(new Date(candidate));
|
||||
const represented = Date.UTC(+parts.year, +parts.month - 1, +parts.day, +parts.hour, +parts.minute);
|
||||
candidate += wallClockUtc - represented;
|
||||
}
|
||||
return new Date(candidate).toISOString();
|
||||
}
|
||||
@@ -53,6 +53,9 @@
|
||||
"searchFirst": "Search for a customer first",
|
||||
"chooseCustomer": "Select a customer",
|
||||
"vehicle": "Available vehicle",
|
||||
"vehicleSearch": "Search vehicle",
|
||||
"vehiclePlaceholder": "Reference, make, model, registration or location",
|
||||
"locationUnknown": "location unknown",
|
||||
"loadingVehicles": "Checking availability…",
|
||||
"chooseVehicle": "Select a vehicle",
|
||||
"noVehicles": "No vehicle available for this window",
|
||||
@@ -102,6 +105,13 @@
|
||||
"startOdometerPending": "Not yet recorded — trip hasn't started yet",
|
||||
"endOdometerPending": "Not yet recorded — not yet closed",
|
||||
"requirementsComplete": "Requirements complete",
|
||||
"requirementsAction": "Check requirements",
|
||||
"requirementsActionDetail": "The departure inspection remains locked until the driving licence and rental requirements are explicitly confirmed.",
|
||||
"requirementsConfirmation": "Check evidence",
|
||||
"requirementsConfirmationPlaceholder": "Record what was checked",
|
||||
"requirementsConfirm": "Confirm requirements",
|
||||
"requirementsSaving": "Confirming…",
|
||||
"requirementsFailed": "The requirements could not be confirmed.",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"cancelAction": "Cancel booking",
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
"searchFirst": "Recherchez d'abord un client",
|
||||
"chooseCustomer": "Sélectionnez un client",
|
||||
"vehicle": "Véhicule disponible",
|
||||
"vehicleSearch": "Rechercher un véhicule",
|
||||
"vehiclePlaceholder": "Référence, marque, modèle, plaque ou site",
|
||||
"locationUnknown": "site inconnu",
|
||||
"loadingVehicles": "Vérification des disponibilités…",
|
||||
"chooseVehicle": "Sélectionnez un véhicule",
|
||||
"noVehicles": "Aucun véhicule disponible pour cette période",
|
||||
@@ -102,6 +105,13 @@
|
||||
"startOdometerPending": "Pas encore enregistré — trajet pas encore commencé",
|
||||
"endOdometerPending": "Pas encore enregistré — pas encore clôturé",
|
||||
"requirementsComplete": "Exigences complètes",
|
||||
"requirementsAction": "Contrôler les exigences",
|
||||
"requirementsActionDetail": "L’inspection de départ reste verrouillée jusqu’à la confirmation explicite du permis et des exigences de location.",
|
||||
"requirementsConfirmation": "Preuve du contrôle",
|
||||
"requirementsConfirmationPlaceholder": "Notez ce qui a été contrôlé",
|
||||
"requirementsConfirm": "Confirmer les exigences",
|
||||
"requirementsSaving": "Confirmation…",
|
||||
"requirementsFailed": "Les exigences n’ont pas pu être confirmées.",
|
||||
"yes": "Oui",
|
||||
"no": "Non",
|
||||
"cancelAction": "Annuler la réservation",
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
"searchFirst": "Zoek eerst een klant",
|
||||
"chooseCustomer": "Selecteer een klant",
|
||||
"vehicle": "Beschikbaar voertuig",
|
||||
"vehicleSearch": "Voertuig zoeken",
|
||||
"vehiclePlaceholder": "Referentie, merk, model, nummerplaat of locatie",
|
||||
"locationUnknown": "locatie onbekend",
|
||||
"loadingVehicles": "Beschikbaarheid controleren…",
|
||||
"chooseVehicle": "Selecteer een voertuig",
|
||||
"noVehicles": "Geen beschikbaar voertuig in deze periode",
|
||||
@@ -102,6 +105,13 @@
|
||||
"startOdometerPending": "Nog niet vastgelegd — rit nog niet gestart",
|
||||
"endOdometerPending": "Nog niet vastgelegd — nog niet afgesloten",
|
||||
"requirementsComplete": "Vereisten volledig",
|
||||
"requirementsAction": "Vereisten controleren",
|
||||
"requirementsActionDetail": "De vertrekinspectie blijft vergrendeld totdat rijbewijs en huurvereisten expliciet zijn bevestigd.",
|
||||
"requirementsConfirmation": "Controlebewijs",
|
||||
"requirementsConfirmationPlaceholder": "Noteer wat gecontroleerd werd",
|
||||
"requirementsConfirm": "Vereisten bevestigen",
|
||||
"requirementsSaving": "Bevestigen…",
|
||||
"requirementsFailed": "De vereisten konden niet worden bevestigd.",
|
||||
"yes": "Ja",
|
||||
"no": "Nee",
|
||||
"cancelAction": "Boeking annuleren",
|
||||
|
||||
Reference in New Issue
Block a user