PageSourceSearch

https://trialog-8a95b.web.app/src/features/wasserschaden/data/WasserschadenFirestoreDataSource.js

js trialog-8a95b.web.app collected 2026-10-03 12:39:36 UTC 6,293 bytes, 173 lines download raw bytes

1/**
2 * Data Source: WasserschadenFirestoreDataSource
3 * Persistenz der Projektpipeline in Firestore (`ws_projekte`).
4 *
5 * EINE FLACHE SAMMLUNG, kein Brett-Dokument daneben - anders als im IPM mit
6 * seinen drei Sammlungen. Der Grund steht in `domain/pipeline.js`: Die sieben
7 * Schritte stehen im Quelltext, nicht in der Datenbank. Es gibt also nichts
8 * zu laden, bevor die erste Karte gelesen werden kann, und beim ersten
9 * Aufruf steht die Pipeline schon da, statt erst angelegt werden zu muessen.
10 *
11 * UNGEFILTERT GELESEN, und das ist hier richtig: Die Sammlung gehoert dem
12 * Bereich (siehe `darfWasserschadensanierungSehen`), und `firestore.rules`
13 * laesst nur dessen Konten heran. Ein Sichtbarkeitsfilter in der Abfrage wie
14 * im IPM haette hier nichts zu filtern - dort trennt er die Vorgaenge
15 * verschiedener Makler, hier arbeiten alle Berechtigten an denselben Projekten.
16 *
17 * ZWEI SCHREIBER, DESHALB GEZIELTE FELDER (seit 5.314, Befund L16-1): Seit
18 * 5.215.0 schreibt auch der Zugang der GL Trocknungstechnik. `save` ersetzt
19 * das ganze Dokument - fuer das Anlegen richtig, fuer jede spaetere Aenderung
20 * aus einem aelteren Bildschirmstand aber ein stiller Datenverlust: Wer morgens
21 * die Pipeline oeffnet und nachmittags eine Karte zieht, schrieb Versicherung
22 * und Schadennummer, die der andere tagsueber nachgetragen hatte, wieder leer.
23 * Aenderungen laufen deshalb ueber `aktualisiereFelder`.
24 */
25
26import { firebaseApp } from '../../../core/firebase/index.js';
27import { authService } from '../../../core/auth/index.js';
28import { FIRESTORE_COLLECTIONS } from '../../../core/config/firebase.config.js';
29import { DomainError, StorageError } from '../../../core/errors/index.js';
30
31const SAMMLUNG = FIRESTORE_COLLECTIONS.WS_PROJEKTE;
32
33export class WasserschadenFirestoreDataSource {
34  #firestore = null;
35
36  #getFirestore() {
37    if (!this.#firestore) {
38      this.#firestore = firebaseApp.firestore;
39    }
40    return this.#firestore;
41  }
42
43  async #helfer() {
44    return import('https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js');
45  }
46
47  /**
48   * Wer schreibt gerade? Wandert als `userId` in jedes Dokument - ohne die
49   * Angabe liesse sich spaeter nicht mehr sagen, wer ein Projekt angelegt hat.
50   */
51  #userId() {
52    const user = authService.getCurrentUser();
53    if (!user) throw new Error('User not authenticated');
54    return user.uid;
55  }
56
57  async findAll() {
58    try {
59      const { collection, getDocs } = await this.#helfer();
60      const schnappschuss = await getDocs(collection(this.#getFirestore(), SAMMLUNG));
61      return schnappschuss.docs.map((d) => d.data());
62    } catch (fehler) {
63      throw new StorageError(`Failed to load ${SAMMLUNG}: ${fehler.message}`);
64    }
65  }
66
67  async save(daten) {
68    try {
69      const { doc, setDoc, serverTimestamp } = await this.#helfer();
70      await setDoc(doc(this.#getFirestore(), SAMMLUNG, daten.id), {
71        ...daten,
72        userId: this.#userId(),
73        serverUpdatedAt: serverTimestamp(),
74      });
75      return daten;
76    } catch (fehler) {
77      throw new StorageError(`Failed to save to ${SAMMLUNG}: ${fehler.message}`);
78    }
79  }
80
81  /**
82   * Nur die genannten Felder schreiben (updateDoc) - alles andere bleibt so,
83   * wie es auf dem Server steht (seit 5.314, Befund L16-1).
84   *
85   * Mit `pruefe` laeuft der Schreibvorgang in einer Transaktion: Die Funktion
86   * bekommt den SERVERSTAND und wirft, wenn er nicht mehr zu dem passt, auf dem
87   * die Aenderung beruht. Gebraucht fuer Listen (Checkliste, Kommentare), die
88   * sich nur als Ganzes schreiben lassen - ein Feld-Update allein wuerde dort
89   * die Haken des anderen wieder entfernen.
90   *
91   * Ein fachlicher Einwand aus `pruefe` (DomainError) geht UNVERPACKT hinaus:
92   * Er traegt schon den Satz fuer den Anwender; eine StorageError darum machte
93   * daraus das allgemeine „Vorgang fehlgeschlagen".
94   *
95   * @param {string} id
96   * @param {object} felder
97   * @param {((serverDaten: object) => void)|null} [pruefe]
98   */
99  async aktualisiereFelder(id, felder, pruefe = null) {
100    try {
101      const {
102        doc, updateDoc, runTransaction, serverTimestamp,
103      } = await this.#helfer();
104      const firestore = this.#getFirestore();
105      const ziel = doc(firestore, SAMMLUNG, id);
106      const daten = {
107        ...felder,
108        userId: this.#userId(),
109        serverUpdatedAt: serverTimestamp(),
110      };
111
112      if (!pruefe) {
113        await updateDoc(ziel, daten);
114        return;
115      }
116
117      await runTransaction(firestore, async (tx) => {
118        const stand = await tx.get(ziel);
119        if (!stand.exists()) {
120          throw new DomainError(
121            'Das Projekt gibt es nicht mehr – vermutlich wurde es inzwischen gelöscht. Bitte die Seite neu laden.',
122            'NICHT_GEFUNDEN'
123          );
124        }
125        pruefe(stand.data());
126        tx.update(ziel, daten);
127      });
128    } catch (fehler) {
129      if (fehler?.name === 'DomainError') throw fehler;
130      throw new StorageError(`Failed to update ${SAMMLUNG}/${id}: ${fehler.message}`);
131    }
132  }
133
134  async remove(id) {
135    try {
136      const { doc, deleteDoc } = await this.#helfer();
137      await deleteDoc(doc(this.#getFirestore(), SAMMLUNG, id));
138    } catch (fehler) {
139      throw new StorageError(`Failed to delete ${SAMMLUNG}/${id}: ${fehler.message}`);
140    }
141  }
142
143  /**
144   * Mehrere Raenge in EINEM Schreibvorgang.
145   *
146   * Gebraucht nach einer Neuverteilung (siehe shared/kanban/rangfolge.js).
147   * Als einzelne Aufrufe waere der Schritt zwischendurch in einer Reihenfolge,
148   * die niemand gewollt hat - der Stapel ist entweder ganz oder gar nicht
149   * geschrieben.
150   *
151   * @param {Array<{id: string, rang: number}>} aenderungen
152   */
153  async saveRaenge(aenderungen) {
154    if (!aenderungen || aenderungen.length === 0) return;
155
156    try {
157      const { doc, writeBatch, serverTimestamp } = await this.#helfer();
158      const firestore = this.#getFirestore();
159      const stapel = writeBatch(firestore);
160
161      aenderungen.forEach(({ id, rang }) => {
162        stapel.update(doc(firestore, SAMMLUNG, id), {
163          rang,
164          serverUpdatedAt: serverTimestamp(),
165        });
166      });
167
168      await stapel.commit();
169    } catch (fehler) {
170      throw new StorageError(`Failed to reorder ${SAMMLUNG}: ${fehler.message}`);
171    }
172  }
173}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.