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.