Zum Inhalt

Lieferpausen - Technische Dokumentation für Mobile App

Übersicht

Lieferpausen sind kundenspezifische Zeiträume, in denen KEINE Lieferungen an einen Kunden erfolgen. Sie werden verwendet, um Urlaube, Betriebsferien oder andere Pausen in der regelmäßigen Lieferung zu verwalten. Lieferpausen werden bei der Berechnung des nächsten Liefertermins und bei Bestellerinnerungen berücksichtigt.

Datenmodell

DeliveryBreakItem

class DeliveryBreakItem {
  final String? id;              // Firestore Document ID
  final DateTime beginDate;      // Startdatum der Lieferpause (inklusiv)
  final DateTime endDate;        // Enddatum der Lieferpause (inklusiv)
  final String? reason;          // Optionaler Grund (z.B. "Urlaub", "Betriebsferien")
  final DateTime createdAt;      // Erstellungszeitpunkt
  final String createdBy;        // User ID des Erstellers
}

Eigenschaften

  • id: Wird automatisch von Firestore vergeben, null bei neuen Items
  • beginDate: Datum im Format DateTime, Uhrzeit wird ignoriert (nur Tag zählt)
  • endDate: Datum im Format DateTime, muss >= beginDate sein
  • reason: Optional, Standard ist "Lieferpause" wenn nicht angegeben
  • createdAt: Timestamp der Erstellung
  • createdBy: Firebase Auth UID des Benutzers

Serialisierung

toMap():

{
  'id': id,
  'beginDate': beginDate.toIso8601String(),
  'endDate': endDate.toIso8601String(),
  'reason': reason,
  'createdAt': createdAt.toIso8601String(),
  'createdBy': createdBy,
}

fromMap():

DeliveryBreakItem.fromMap(Map<String, dynamic> map) {
  return DeliveryBreakItem(
    id: map['id'],
    beginDate: DateTime.parse(map['beginDate']),
    endDate: DateTime.parse(map['endDate']),
    reason: map['reason'],
    createdAt: DateTime.parse(map['createdAt']),
    createdBy: map['createdBy'],
  );
}

Firebase-Struktur

Firestore Collection Path

/customers
  /{customerId}
    /deliveryBreaks
      /{deliveryBreakId}
        - beginDate: "2024-01-01T00:00:00.000Z"
        - endDate: "2024-01-07T00:00:00.000Z"
        - reason: "Urlaub"
        - createdAt: "2024-01-01T10:30:00.000Z"
        - createdBy: "userId123"

Collection Name Konstante

FirebaseCollectionNames.deliveryBreaks = 'deliveryBreaks'

Subcollection Struktur

Lieferpausen sind eine Subcollection unter dem jeweiligen Kunden. Pfad: customers/{customerId}/deliveryBreaks

Backend Service

DeliveryBreakFirebaseService

Der Service bietet folgende Methoden:

1. Lieferpausen laden (einmalig)

Future<List<DeliveryBreakItem>> getDeliveryBreaksByCustomerId(String customerId)
  • Lädt alle Lieferpausen für einen Kunden einmalig
  • Returns: Liste von DeliveryBreakItem
  • Bei Fehler: leere Liste []

2. Lieferpausen streamen (reactive)

Stream<List<DeliveryBreakItem>> streamDeliveryBreaks(String customerId)
  • Liefert einen Stream, der bei Änderungen automatisch aktualisiert wird
  • Ideal für Echtzeit-Updates in der UI
  • Returns: Stream<List<DeliveryBreakItem>>

3. Lieferpause erstellen

Future<void> createDeliveryBreakItem(String customerId, DeliveryBreakItem item)
  • Erstellt eine neue Lieferpause
  • createdAt und createdBy werden automatisch gesetzt
  • Item wird mit add() zu Firestore hinzugefügt (ID wird automatisch generiert)

4. Lieferpause aktualisieren

Future<void> updateDeliveryBreakItem(String customerId, DeliveryBreakItem item)
  • Aktualisiert eine bestehende Lieferpause
  • item.id muss gesetzt sein
  • Verwendet Firestore update()

5. Lieferpause löschen

Future<void> deleteDeliveryBreakItem(String customerId, String itemId)
  • Löscht eine Lieferpause
  • Firestore Document wird permanent gelöscht

State Management (BLoC Pattern)

DeliveryBreakBloc

Der BLoC verwaltet den State der Lieferpausen für einen Kunden.

Events

// Lieferpausen für einen Kunden laden
LoadDeliveryBreaks(String customerId)

// Neue Lieferpause hinzufügen
AddDeliveryBreak(String customerId, DeliveryBreakItem item)

// Lieferpause aktualisieren
UpdateDeliveryBreak(String customerId, DeliveryBreakItem item)

// Lieferpause löschen
DeleteDeliveryBreak(String customerId, String itemId)

// Internes Event für Stream-Updates
DeliveryBreaksUpdated(List<DeliveryBreakItem> items)

States

BlocInitial()                                    // Initial State
BlocLoading()                                    // Lädt gerade Daten
LoadedDeliveryBreaks(List<DeliveryBreakItem>)   // Erfolgreich geladen (extends BlocLoaded)
BlocLoadingFailed()                              // Fehler beim Laden
AddingDeliveryBreakFailed(String message)       // Fehler beim Erstellen
UpdatingDeliveryBreakFailed(String message)     // Fehler beim Aktualisieren
DeletingDeliveryBreakFailed(String message)     // Fehler beim Löschen

Wichtig: LoadedDeliveryBreaks muss von BlocLoaded erben, damit der EsBlocBuilder den State korrekt erkennt und die UI rendert.

Verwendung im Widget

// Im Widget
BlocConsumer<DeliveryBreakBloc, BaseBlocState>(
  listener: (context, state) {
    if (state is BlocLoadingFailed) {
      // Fehler anzeigen
    }
  },
  builder: (context, state) {
    if (state is LoadedDeliveryBreaks) {
      final breaks = state.deliveryBreaksList;
      // UI mit Lieferpausen rendern
    }
    return Container();
  },
)

// Event dispatchen
context.read<DeliveryBreakBloc>().add(LoadDeliveryBreaks(customerId));

Logik & Business Rules

1. Datumsvalidierung

  • beginDate muss vor oder gleich endDate sein
  • Beide Daten müssen in der Zukunft oder Gegenwart liegen
  • In der Desktop-App: maximal 730 Tage (2 Jahre) in die Zukunft

2. Überlappende Lieferpausen

  • Mehrere Lieferpausen können existieren
  • Überlappungen sind technisch erlaubt, sollten aber vermieden werden
  • Die UI sollte Überlappungen visuell kennzeichnen

3. Prüfung ob Datum in Lieferpause liegt

bool isInDeliveryBreak(DateTime date, List<DeliveryBreakItem> breaks) {
  return breaks.any((deliveryBreak) {
    final dateOnly = DateTime(date.year, date.month, date.day);
    final startOnly = DateTime(
      deliveryBreak.beginDate.year,
      deliveryBreak.beginDate.month,
      deliveryBreak.beginDate.day
    );
    final endOnly = DateTime(
      deliveryBreak.endDate.year,
      deliveryBreak.endDate.month,
      deliveryBreak.endDate.day
    );

    return (dateOnly.isAfter(startOnly) || dateOnly.isAtSameMomentAs(startOnly)) &&
           (dateOnly.isBefore(endOnly) || dateOnly.isAtSameMomentAs(endOnly));
  });
}

4. Integration mit Liefertag-Berechnung

Lieferpausen werden bei der Berechnung des nächsten Liefertages berücksichtigt:

// Backend Node.js Funktion (job_orderReminderNotifications.js)
function checkCustomerDeliveryBreaks(date, deliveryBreaks) {
  if (!deliveryBreaks || deliveryBreaks.length === 0) {
    return { isAllowed: true, reason: '' };
  }

  const deliveryBreak = deliveryBreaks.find(period => {
    const start = period.beginDate;
    const end = period.endDate;
    return date >= start && date <= end;
  });

  if (deliveryBreak) {
    const reason = deliveryBreak.reason || 'Lieferpause';
    return { isAllowed: false, reason };
  }

  return { isAllowed: true, reason: '' };
}

Der Job job_orderReminderNotifications lädt Lieferpausen für jeden Kunden und berücksichtigt sie bei der Berechnung:

// Lade Lieferpausen für diesen Kunden
const deliveryBreaksSnapshot = await db
  .collection('customers')
  .doc(customerId)
  .collection('deliveryBreaks')
  .get();

const customerDeliveryBreaks = deliveryBreaksSnapshot.docs.map(doc => {
  const data = doc.data();
  return {
    beginDate: data.beginDate.toDate ? data.beginDate.toDate() : new Date(data.beginDate),
    endDate: data.endDate.toDate ? data.endDate.toDate() : new Date(data.endDate),
    reason: data.reason || 'Lieferpause',
  };
});

// Berechne nächsten Liefertag UNTER BERÜCKSICHTIGUNG der Lieferpausen
const deliveryInfo = calculateNextDeliveryDay(
  deliverySchedule,
  deliveryDaysSettings,
  customerDeliveryBreaks,  // ← Lieferpausen werden übergeben!
  nowInCustomerTZ
);

UI-Komponenten (Desktop-App als Referenz)

1. DeliveryBreakEditorDialog

Dialog zum Erstellen/Bearbeiten von Lieferpausen.

Features: - Kalenderansicht zur Auswahl von Start- und Enddatum - Bereichsauswahl (von-bis) - Optionales Textfeld für Grund - Validation: Start muss <= Ende sein - Action Buttons: Abbrechen / Übernehmen

Verwendung:

final result = await DeliveryBreakEditorDialog.show(
  context,
  deliveryBreak: existingBreak, // null für neuen Eintrag
);

if (result != null) {
  // result ist DeliveryBreakItem
  // Service aufrufen zum Speichern
}

2. DeliveryDaysCalendarDialog

Kalenderansicht die Liefertage UND Lieferpausen visualisiert.

Features: - Monatsansicht mit Liefertagen (grün) - Lieferpausen werden visuell hervorgehoben (lila/purple) - Liste der anstehenden Lieferpausen - Direktes Bearbeiten/Löschen von Lieferpausen - Icons: CupertinoIcons.pause_circle_fill für Lieferpausen - Tap auf Lieferpause scrollt zum Datum - Long-Press öffnet Editor

Visuelle Kennzeichnung:

if (isDeliveryBreak) {
  Icon(
    CupertinoIcons.pause_circle_fill,
    size: 14,
    color: isDelivery ? Colors.white : Colors.purple.shade700,
  )
}

Lokalisierung (i18n)

Deutsche Texte (lib/l10n/intl_de.arb)

{
  "customerDetailPage_deliveryBreaks": "Lieferpausen",
  "customerDeliveryBreak_edit": "Lieferpause bearbeiten",
  "customerDeliveryBreak_add": "Lieferpause hinzufügen",
  "customerDeliveryBreak_start": "Beginn",
  "customerDeliveryBreak_end": "Ende",
  "customerDeliveryBreak_reason": "Grund"
}

Englische Texte (lib/l10n/intl_en.arb)

{
  "customerDetailPage_deliveryBreaks": "Delivery Breaks",
  "customerDeliveryBreak_edit": "Edit delivery break",
  "customerDeliveryBreak_add": "Add delivery break",
  "customerDeliveryBreak_start": "Start",
  "customerDeliveryBreak_end": "End",
  "customerDeliveryBreak_reason": "Reason"
}

Verwendung

S.of(context).customerDeliveryBreak_add
S.of(context).customerDeliveryBreak_edit

Mobile App - Implementierungshinweise

Empfohlene Funktionen für Mobile App

1. Anzeige der Lieferpausen

Listenansicht: - Liste aller aktiven/zukünftigen Lieferpausen - Sortiert nach Startdatum - Card-Design mit: - Datumsbereich (z.B. "01.01.2024 - 07.01.2024") - Grund (falls vorhanden) - Icon für Lieferpause - Edit/Delete Aktionen (wenn Berechtigungen vorhanden)

Kalenderansicht: - Monatlicher Kalender mit Markierungen - Liefertage in einer Farbe (grün) - Lieferpausen in anderer Farbe (lila/purple) - Tap auf Tag zeigt Details

2. Erstellen einer Lieferpause

Bottom Sheet oder Dialog: - Date Range Picker für Start- und Enddatum - Optional: Textfeld für Grund - Save Button

Validation:

if (startDate == null || endDate == null) {
  // Fehler: Beide Daten müssen ausgewählt sein
}
if (endDate.isBefore(startDate)) {
  // Fehler: Endedatum muss nach Startdatum liegen
}

3. Bearbeiten einer Lieferpause

  • Gleicher Dialog/Sheet wie beim Erstellen
  • Felder werden mit vorhandenen Werten gefüllt
  • Update statt Create beim Speichern

4. Löschen einer Lieferpause

  • Swipe-to-delete in der Liste
  • Oder: Icon-Button mit Bestätigungsdialog
  • Nach Löschen: Liste aktualisieren

5. Berechtigungen

Prüfen Sie: - Darf der aktuelle User Lieferpausen sehen? - Darf der aktuelle User Lieferpausen erstellen/bearbeiten/löschen?

Dies hängt von den Rollen und Berechtigungen ab (siehe ROLES_AND_PERMISSIONS.md).

Code-Beispiel für Mobile App

// 1. Service initialisieren
final deliveryBreakService = DeliveryBreakFirebaseService();

// 2. BLoC initialisieren (in BlocProvider)
BlocProvider(
  create: (context) => DeliveryBreakBloc(deliveryBreakService),
  child: DeliveryBreaksScreen(),
)

// 3. Lieferpausen laden
context.read<DeliveryBreakBloc>().add(LoadDeliveryBreaks(customerId));

// 4. Stream listen
StreamBuilder<List<DeliveryBreakItem>>(
  stream: deliveryBreakService.streamDeliveryBreaks(customerId),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      final breaks = snapshot.data!;
      return ListView.builder(
        itemCount: breaks.length,
        itemBuilder: (context, index) {
          final deliveryBreak = breaks[index];
          return DeliveryBreakCard(deliveryBreak: deliveryBreak);
        },
      );
    }
    return CircularProgressIndicator();
  },
)

// 5. Neue Lieferpause erstellen
Future<void> createDeliveryBreak(BuildContext context) async {
  // ... User Input holen ...

  final newBreak = DeliveryBreakItem(
    beginDate: startDate,
    endDate: endDate,
    reason: reasonController.text.isNotEmpty ? reasonController.text : null,
    createdAt: DateTime.now(),
    createdBy: '', // Wird vom Service gesetzt
  );

  context.read<DeliveryBreakBloc>().add(
    AddDeliveryBreak(customerId, newBreak)
  );
}

// 6. Lieferpause aktualisieren
context.read<DeliveryBreakBloc>().add(
  UpdateDeliveryBreak(customerId, updatedBreak)
);

// 7. Lieferpause löschen
context.read<DeliveryBreakBloc>().add(
  DeleteDeliveryBreak(customerId, breakId)
);

Testing

Unit Tests

Die Desktop-App enthält Unit Tests für den BLoC: test/unit/blocs/delivery_break_bloc/delivery_break_bloc_test.dart

Was wird getestet: - States haben korrekte Properties - Events dispatchen korrekt - BLoC lädt Lieferpausen - CRUD Operationen funktionieren

Beispiel:

test('LoadDeliveryBreaks should hold customerId', () {
  final event = LoadDeliveryBreaks('customer123');
  expect(event.customerId, 'customer123');
});

Integration Tests

Empfohlene Tests für Mobile App: - Lieferpausen laden und in Liste anzeigen - Neue Lieferpause erstellen und in Firestore speichern - Lieferpause bearbeiten - Lieferpause löschen - Validierung (Start < Ende) - Offline-Verhalten (Firestore Persistence)

Performance-Optimierung

1. Caching

Die Desktop-App verwendet keinen expliziten Cache, aber der BLoC hält deliveryBreaksList im Memory.

2. Pagination

Aktuell gibt es keine Pagination. Bei sehr vielen Lieferpausen sollte erwogen werden: - Nur aktive/zukünftige Lieferpausen laden - Limit auf die letzten N Monate

3. Indizierung

Empfohlene Firestore Indexes: - beginDate ASC - endDate DESC

Für Queries wie:

.where('endDate', isGreaterThan: DateTime.now())
.orderBy('beginDate', descending: false)

4. Offline Support

Firestore bietet automatisches Caching. Stellen Sie sicher, dass Persistence aktiviert ist:

await FirebaseFirestore.instance.enablePersistence();

Fehlerbehandlung

1. Fehlende Berechtigungen

try {
  await deliveryBreakService.createDeliveryBreakItem(customerId, item);
} catch (e) {
  if (e is FirebaseException && e.code == 'permission-denied') {
    // Keine Berechtigung
    showDialog('Sie haben keine Berechtigung...');
  }
}

2. Netzwerkfehler

if (state is BlocLoadingFailed) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Fehler beim Laden: ${state.message}')),
  );
}

3. Ungültige Daten

if (endDate.isBefore(startDate)) {
  return 'Enddatum muss nach Startdatum liegen';
}

Sicherheit & Berechtigungen

Firestore Security Rules

Stelle sicher, dass die Security Rules Lieferpausen schützen:

match /customers/{customerId}/deliveryBreaks/{breakId} {
  // Nur authentifizierte User mit Zugriff auf den Kunden
  allow read: if isAuthenticated() && hasAccessToCustomer(customerId);
  allow write: if isAuthenticated() && hasCustomerEditPermission(customerId);
}

Siehe deployment/PERMISSIONS_SYSTEM.md für Details zur Berechtigungsverwaltung.

Zusammenfassung

Key Points für Mobile App

  1. Datenstruktur: Einfaches Model mit Start, Ende, Grund
  2. Firestore: Subcollection unter customer
  3. CRUD: Vollständiger Service vorhanden
  4. State Management: BLoC Pattern mit Events und States
  5. UI: Kalender- und Listenansicht empfohlen
  6. Backend-Integration: Automatische Berücksichtigung bei Liefertermin-Berechnung
  7. Validation: Start <= Ende, beide Daten müssen gesetzt sein
  8. i18n: DE/EN Texte vorhanden
  9. Testing: Unit Tests als Referenz

Nächste Schritte

  1. UI-Design für Mobile App erstellen
  2. Screen-Flow definieren (Liste → Detail → Editor)
  3. Berechtigungen integrieren
  4. Offline-Support testen
  5. Push-Benachrichtigung bei neuen Lieferpausen erwägen

Version: 1.0
Datum: 17. Februar 2026
Autor: System Documentation