import { HttpClient } from '@angular/common/http'; import { ConfigService } from './config.service'; import { Hub } from './hub.service'; import './es5'; import { User } from './user.service'; import * as i0 from "@angular/core"; /** * Interface complète pour gérer le timing des commandes produit * Centralise toute la logique de countdown, validation et formatage */ export interface ProductOrderTiming { isOutOfTimeLimit: boolean; shouldShowCountdown: boolean; hoursLeft: number; formattedTimeLeft: string; formattedDeadline: string; } /** * # CalendarService - Gestion Centralisée des Dates de Livraison * * ## 🎯 RÔLE ET RESPONSABILITÉS * * Ce service centralise TOUTE la logique de calcul des dates de livraison. * Il est synchronisé avec l'API Calendar backend (calendar.js) testée. * * **Principe** : Logique pure de business rules, sans état utilisateur. * **Input** : Hub configuration + paramètres * **Output** : Dates calculées selon les règles métier * * ## 📋 USE CASES DÉTAILLÉS * * ### UC1 - Première Visite Utilisateur * **Contexte** : Nouvel utilisateur arrive sur le site * **Acteurs** : CartService.setContext() → CalendarService * **Flow** : * 1. ConfigService charge la config du hub * 2. CartService appelle CalendarService.nextShippingDay(hub) * 3. CalendarService calcule selon hub.weekdays + noshipping + timelimit * 4. CartService stocke le résultat comme cache.currentShippingDay * * ```typescript * // Dans CartService.setContext() * const nextShippingDay = this.$calendar.nextShippingDay(config.shared.hub); * this.cache.currentShippingDay = new Date(nextShippingDay); * ``` * * ### UC2 - Commande en Cours (Pending Order) * **Contexte** : Utilisateur a une commande authorized/prepaid existante * **Acteurs** : CartService.setContext() détecte l'ordre pending * **Flow** : * 1. CartService trouve order avec status 'authorized'|'prepaid' * 2. Extrait order.shipping.when comme date de référence * 3. Vérifie via CalendarService.potentialShippingWeek() si toujours valide * 4. Si valide : utilise cette date, sinon calcule nextShippingDay() * * ```typescript * // Dans CartService.setContext() * if (!this.currentPendingOrder && order) { * const day = new Date(order.shipping.when); * const validDays = this.$calendar.potentialShippingWeek(config.shared.hub); * if (validDays.some(d => d.equalsDate(day))) { * this.cache.currentShippingDay = day; // ✅ Garde la date de l'ordre * } else { * this.cache.currentShippingDay = this.$calendar.nextShippingDay(config.shared.hub); * } * } * ``` * * ### UC3 - Changement de Date par l'Utilisateur * **Contexte** : Utilisateur sélectionne une nouvelle date dans le calendrier * **Acteurs** : Component → CartService.setShippingDay() → save() * **Flow** : * 1. Composant kng-calendar valide la date via CalendarService.isShippingDayAvailable() * 2. Si valide : appelle CartService.setShippingDay(newDate, hours) * 3. CartService met à jour cache.currentShippingDay + save() automatique * 4. Nouvelle préférence persistée côté serveur * * ```typescript * // Dans kng-calendar.component.ts * doSetCurrentShippingDay(day: Date) { * if (this.$calendar.isShippingDayAvailable(this.currentHub, day)) { * const hours = this.config.getDefaultTimeByDay(day); * this.$cart.setShippingDay(day, hours); // => save() automatique * } * } * ``` * * ### UC4 - Validation Temps Restant (Product Page) * **Contexte** : Affichage du countdown avant fermeture des commandes * **Acteurs** : product.component.ts → CalendarService.timeleftBeforeCollect() * **Flow** : * 1. Composant récupère currentShippingDay depuis CartService * 2. Appelle CalendarService.timeleftBeforeCollect(hub, product.timelimit, shippingDay) * 3. CalendarService calcule : (collectTime - preparationHours) - now * 4. Composant affiche "Il reste X heures pour commander" * * ```typescript * // Dans product.component.ts * updateTimeLeft() { * const shippingDay = this.$cart.getCurrentShippingDay(); * const timeLeft = this.$calendar.timeleftBeforeCollect( * this.config.shared.hub, * this.product.attributes.timelimit, * shippingDay * ); * this.hoursLeftBeforeOrder = timeLeft; * } * ``` * * ### UC5 - Affichage Calendrier Semaine * **Contexte** : Composant kng-calendar affiche les jours disponibles * **Acteurs** : kng-calendar.component.ts → CalendarService.fullWeekShippingDays() * **Flow** : * 1. Composant appelle CalendarService.fullWeekShippingDays(hub) * 2. CalendarService calcule potentialShippingWeek() - noshipping dates * 3. Filtre selon uncapturedTimeLimit (6 jours par défaut) * 4. Composant affiche la liste des jours sélectionnables * * ```typescript * // Dans kng-calendar.component.ts * ngOnInit() { * this.availableDays = this.$calendar.fullWeekShippingDays(this.currentHub); * this.currentWeek = Array.from({length: 7}) * .map((id, idx) => (new Date(this.availableDays[0])).plusDays(idx)); * } * ``` * * ### UC6 - Multiple Commandes Même Date (Économie Shipping) * **Contexte** : Utilisateur a déjà une commande le même jour * **Acteurs** : CartService.hasShippingReductionMultipleOrder() + CalendarService * **Flow** : * 1. CartService détecte currentPendingOrder.shipping.when == currentShippingDay * 2. Vérifie si même adresse de livraison * 3. Si OUI : réduction shipping (économie pour le client) * 4. Interface affiche "Livraison groupée - Économie réalisée" * * ```typescript * // Dans CartService * hasShippingReductionMultipleOrder(address): boolean { * if (this.currentPendingOrder?.shipping) { * const whenDay = this.currentPendingOrder.shipping.when.getDate(); * const nextDay = this.cache.currentShippingDay?.getDate(); * const sameDay = (whenDay === nextDay); * const sameAddress = UserAddress.isEqual(address, this.currentPendingOrder.shipping); * return sameDay && sameAddress; * } * return false; * } * ``` * * ### UC7 - Cross-Market Shipping (Multi-Hub) * **Contexte** : Produits de différents hubs dans le même panier * **Acteurs** : CartService.getShippingDayForMultipleHUBs() + CalendarService * **Flow** : * 1. CartService détecte items de hubs différents * 2. Calcule intersection des jours de livraison via CalendarService * 3. Si intersection vide : force séparation des commandes * 4. Si intersection OK : même date de livraison pour tous * * ```typescript * // Dans CartService * getShippingDayForMultipleHUBs() { * const hubsDate = this.config.shared.hubs.map(hub => * this.$calendar.fullWeekShippingDays(hub) * ); * // Intersection des jours disponibles * return hubsDate[0].filter(date => * hubsDate[1].some(d2 => d2.equalsDate(date)) * ); * } * ``` * * ## 🔒 RÈGLES DE SÉPARATION DES RESPONSABILITÉS * * ### CalendarService (CE SERVICE) : * - ✅ Calculs purs des dates selon business rules * - ✅ Validation des jours disponibles * - ✅ Logique UTC avec correction timezone * - ✅ Synchronisation avec calendar.js backend * - ❌ PAS de gestion d'état utilisateur * - ❌ PAS de persistance * - ❌ PAS d'interface utilisateur * * ### CartService : * - ✅ Gestion de l'état currentShippingDay * - ✅ Persistance via save() automatique * - ✅ Détection des commandes pending * - ✅ Logique de réduction shipping * - ❌ PAS de calculs de dates business (délègue à CalendarService) * * ### Composants : * - ✅ Interface utilisateur et interactions * - ✅ Validation avant appels CartService * - ✅ Affichage des résultats CalendarService * - ❌ PAS de logique métier de dates * - ❌ PAS de gestion directe de l'état */ export declare class CalendarService { private $http; private $config; constructor($http: HttpClient, $config: ConfigService); /** * Vérifier que ConfigService est chargé avant d'utiliser CalendarService */ private ensureConfigLoaded; /** * Obtenir le hub par défaut depuis ConfigService */ private getDefaultHub; /** * ✅ LOGIQUE EXACTE de calendar.js (testé) * * Calcule le prochain jour potentiel de livraison selon : * - hub.timelimit : heures nécessaires de préparation * - hub.timelimitH : heure limite de commande (ex: 16h) * * @param hub Hub optionnel, utilise le hub par défaut si non fourni * @returns Date de livraison potentielle en timezone Hub */ potentialShippingDay(hub?: Hub): Date; /** * @deprecated Use getValidShippingDatesForHub(...)[0] instead - first valid date * ✅ DÉLÉGUER vers getValidShippingDatesForHub - fonction principale */ nextShippingDay(hub?: any, user?: any): Date | null; /** * ✅ fonction utilitaire pour avoir la date pour les collaborateurs, vendeurs et la logistique */ currentShippingDay(hub?: Hub): Date; /** * @deprecated Use getValidShippingDatesForHub with options instead * Backward compatibility wrapper for fullWeekShippingDays * ✅ SYNC AVEC BACKEND: fullWeekShippingDays est un wrapper deprecated * * @param hub Hub optionnel, utilise le hub par défaut si non fourni * @param limit Limite en jours (défaut: hub.uncapturedTimeLimit || 6) * @param user Utilisateur pour limites premium (optionnel) * @returns Tableau des dates de livraison disponibles */ fullWeekShippingDays(hub?: any, limit?: number, user?: any): Date[]; /** * ✅ CALCUL DU TEMPS RESTANT AVANT DEADLINE DE COMMANDE AVEC INTERFACE COMPLÈTE * * **⚠️ FRONTEND HUB-CENTRIC vs BACKEND UTC-FIRST :** * - **Frontend** : Travaille en timezone Hub (Europe/Zurich) pour cohérence utilisateur * - **Backend** : Travaille en UTC pur avec conversions Hub seulement pour calculs business * * --- LOGIQUE MÉTIER CORRECTE --- * 1. **`hub.timelimit`** = Heures de préparation nécessaires (ex: 24h) * 2. **`hub.timelimitH`** = Heure de collecte quotidienne chez les vendeurs (ex: 16h Swiss) * 3. **`product.attributes.timelimit`** = **HEURE LIMITE SPÉCIFIQUE** (remplace hub.timelimitH) * * --- EXEMPLE CONCRET --- * Configuration hub : * - `timelimit: 24` (24h de préparation nécessaire) * - `timelimitH: 16` (collecte quotidienne à 16h Swiss) * * **Produit normal (MARDI livraison)** : * - Collecte = MARDI 16h00 Swiss * - Deadline = MARDI 16h00 - 24h = LUNDI 16h00 Swiss * * **Produit strict (pain frais avec `product.attributes.timelimit = 12`)** : * - Collecte = MARDI 16h00 Swiss * - **Limite produit** = LUNDI 16h00 → remplace par 12h00 → **LUNDI 12h00 Swiss** * - Le vendeur impose une deadline plus restrictive (12h au lieu de 16h) * * @param hub Hub optionnel, utilise le hub par défaut si non fourni * @param productTimelimit Heure limite spécifique pour ce produit (remplace hub.timelimitH si plus restrictif) * @param when Date de livraison spécifique choisie par le client * @param options Options { includeInterface?: boolean } * @returns Temps restant (number) ou interface ProductOrderTiming si includeInterface=true */ timeleftBeforeCollect(hub?: Hub, productTimelimit?: number, when?: Date, options?: { includeInterface?: boolean; }): number | ProductOrderTiming; /** * Use getValidShippingDatesForHub with potentialShippingDay instead * ✅ DÉLÉGUER vers dayToDates - fonction utilitaire */ potentialShippingWeek(hub?: Hub): Date[]; /** * Vérifier si un jour spécifique est disponible pour la livraison * ✅ OPTION LASTMINUTE : Ajoute validation heure limite * * @param hub Hub de livraison * @param date Date à vérifier * @param options { lastMinute?: boolean, now?: Date } * @returns true si le jour est disponible */ isShippingDayAvailable(hub: Hub, date: Date, options?: { lastMinute?: boolean; now?: Date; }): boolean; /** * Mapper la semaine de livraison potentielle avec les raisons de fermeture * * @param hub Hub optionnel, utilise le hub par défaut si non fourni * @returns Dates avec messages de fermeture si applicable */ noShippingMessage(hub?: Hub): any[]; /** * Obtenir le timezone du hub (défaut: Europe/Zurich) * * @param hub Hub optionnel * @returns Timezone string (ex: 'Europe/Zurich') */ getHubTimezone(hub?: any): string; /** * Convertir UTC vers timezone du hub pour calculs business * PRINCIPE: UTC normalisé → Hub timezone pour comparaisons → retour UTC * * @param utcDate Date en UTC * @param hub Hub pour timezone (défaut: Europe/Zurich) * @returns Date dans timezone du hub */ toHubTime(utcDate: Date, hub?: any): Date; /** * Convertir timezone hub vers UTC pour stockage * PRINCIPE: Reconversion Hub → UTC pour normalisation * * @param hubDate Date dans timezone du hub * @param hub Hub pour timezone (défaut: Europe/Zurich) * @returns Date en UTC */ toUTC(hubDate: Date, hub?: any): Date; /** * Validation complète si un jour est disponible (availableDays + currentRanks + limites premium) * Prend en compte les contraintes de capacité par jour et les privilèges premium * * @param day Date UTC à vérifier * @param availableDays Liste des dates disponibles (timezone marché) * @param options Options { user?: User, hub?: Hub } - utilise config.shared par défaut * @returns true si le jour est disponible selon toutes les contraintes */ isDayAvailable(day: Date, availableDays: Date[], options?: { user?: User; hub?: Hub; }): boolean; /** * Validation simplifiée si un jour est dans la liste des jours disponibles (sans currentRanks) * Utilisée quand availableDays contient déjà les contraintes filtrées * * @param day Date UTC à vérifier * @param availableDays Liste des dates disponibles (timezone marché) * @returns true si le jour est dans la liste disponible */ isInAvailableDays(day: Date, availableDays: Date[]): boolean; /** * Retourne l'heure par défaut selon jour (16h normalement, 12h samedi) * day est en UTC, mais doit être comparé avec config hub Swiss * * @param day Date UTC * @param hub Hub pour configuration timezone * @returns Heure par défaut (ex: 16 pour 16h) */ getDefaultTimeByDay(day: Date, hub?: Hub): number; /** * Vérifie si la commande last-minute est disponible (version simplifiée) * ✅ SYNCHRONISÉ avec backend calendar.js * Délègue à isShippingDayAvailable avec option lastMinute * * @param hub Hub optionnel * @param options { now?: Date } * @returns {{ available: boolean, when?: Date, hours?: number }} */ isLastMinuteAvailable(hub?: Hub, options?: { now?: Date; }): { available: boolean; when?: Date; hours?: number; }; /** * ✅ FONCTION PRINCIPALE: API flexible avec contraintes de capacité et règles business * Synchronisée avec calendar.js backend (fonction source) * * @param hub Hub optionnel * @param options Options: { days, user, currentRanks, config, detailed, limitToWeekdays } * @returns Dates de livraison selon options et contraintes */ getValidShippingDatesForHub(hub?: any, options?: any): Date[] | any[]; /** * ✅ SOLUTION INTERNATIONALE : Convertit un instant UTC vers ISO avec offset timezone * Force le frontend à travailler dans le timezone du marché (Europe/Zurich) * * @param dateInput Date UTC ou string ISO * @param timeZone Timezone IANA (défaut: Europe/Zurich) * @param withMillis Inclure millisecondes (défaut: true) * @returns String ISO avec offset (ex: "2025-09-16T12:00:00.000+02:00") */ convertToTimezone(dateInput: string | number | Date, timeZone?: string, withMillis?: boolean): string; /** * ✅ STRATÉGIE HYBRIDE : Dates serveur + convertToTimezone frontend * Utilise les dates calculées côté serveur et les convertit pour le timezone du marché * * @param hub Hub pour timezone * @param options Options pour le serveur * @returns Dates au format ISO avec offset timezone marché */ getValidShippingDatesFromServer(hub?: any, options?: any): string[]; /** * Formate une date UTC pour affichage client dans timezone du hub * La date reste UTC, seul l'affichage change * * @param utcDate Date en UTC * @param hub Hub pour timezone (défaut: Europe/Zurich) * @returns String formaté en timezone Swiss */ formatForClient(utcDate: Date, hub?: any): string; /** * Use timeleftBeforeCollect with { includeInterface: true } instead * ✅ DÉLÉGUER vers timeleftBeforeCollect - fonction principale */ getProductOrderTiming(product: any, hub?: Hub, options?: any): ProductOrderTiming; /** * ✅ FONCTION UTILITAIRE : Construire l'interface ProductOrderTiming * Utilisée par timeleftBeforeCollect (fonction principale) */ private buildProductOrderTimingInterface; /** * Formate le temps restant en heures et minutes * * @param hoursLeft Heures restantes (peut être décimal) * @returns String formaté "2 h 30 minutes" ou "45 minutes" */ formatHoursAndMinutesLeft(hoursLeft: number): string; /** * Formate l'heure de deadline (utilisé quand le délai est dépassé) * Reconstruit l'heure exacte de la deadline à partir du temps restant négatif * * @param hoursLeft Heures restantes (négatif si deadline passée) * @returns String formaté "14h30" */ formatDeadlineTime(hoursLeft: number): string; /** * Retourne le nom court d'un jour selon l'index et la langue * * @param idx Index du jour (0=dimanche, 6=samedi) * @param lang Langue ('fr', 'de', 'en') * @returns Nom court du jour (ex: 'lun.', 'mar.') */ getWeekDay(idx: number, lang?: string): string; /** * Noms complets des jours de la semaine selon la langue * * @param lang Langue ('fr', 'de', 'en') * @returns Array des noms complets ['dimanche', 'lundi', ...] */ i18n_weekdays(lang?: string): string[]; /** * Noms courts des jours de la semaine selon la langue * * @param lang Langue ('fr', 'de', 'en') * @returns Array des noms courts ['dim.', 'lun.', ...] */ i18n_weekdaysShort(lang?: string): string[]; /** * Noms des mois selon la langue * * @param lang Langue ('fr', 'de', 'en') * @returns Array des noms des mois ['janvier', 'février', ...] */ i18n_months(lang?: string): string[]; /** * Noms des mois selon la langue (alias de i18n_months pour rétrocompatibilité) */ weekdaysNames(lang?: string): string[]; /** * Noms des mois selon la langue (alias de i18n_months pour rétrocompatibilité) */ monthsNames(lang?: string): string[]; /** * Obtient le message de fermeture pour un jour donné * Synchronisé avec la logique config.noShippingMessage() * * @param hub Hub pour les périodes de fermeture * @param currentShippingDay Jour à vérifier * @param locale Langue pour le message * @returns Message de fermeture ou null */ getNoShippingMessage(hub: any, currentShippingDay: Date, locale?: string): string | null; private formatDates; private dayToDates; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } //# sourceMappingURL=calendar.service.d.ts.map