AB-Kern: Entwürfe anlegen, ändern und löschen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ef646f12de
commit
0fc2eb4516
2 files changed
+238
-3
No files matched your search
@@ -5,10 +5,14 @@
|
||||
* über `deps` (L4/L5, Standard: dynamischer Import beim Aufruf). Jede Änderung an einer bestehenden AB läuft unter
|
||||
* sperreAuftrag(id) (konstanten.js), das Senden zusätzlich unter SPERRE_NUMMER; Mails gehen immer erst nach der Sperre.
|
||||
*/
|
||||
import {
|
||||
AUFTRAG_ARTEN, AUFTRAG_WEGE, KUNDENARTEN, LIMITS, STUNDENSATZ_STANDARD_CENTS, VIERTELSTUNDE, ZAHLUNGSPLAN_ARTEN,
|
||||
} from './konstanten.js'
|
||||
import { config } from '../../config.js'
|
||||
import { getDocument, listDocuments, Query, updateDocument } from '../appwriteAdmin.js'
|
||||
import { withLocks } from '../keyedLock.js'
|
||||
import { createAuftrag, deleteAuftrag, getAuftrag, updateAuftrag } from './auftragStore.js'
|
||||
import { AuftragError } from './fehler.js'
|
||||
import {
|
||||
AUFTRAG_ARTEN, AUFTRAG_WEGE, KUNDENARTEN, LIMITS, sperreAuftrag, STUNDENSATZ_STANDARD_CENTS, VIERTELSTUNDE, ZAHLUNGSPLAN_ARTEN,
|
||||
} from './konstanten.js'
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// Eingabeprüfung (rein, synchron)
|
||||
@@ -187,3 +191,115 @@ export function pruefeEingabe(eingabe, { teilweise = false } = {}) {
|
||||
}
|
||||
return ergebnis
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// Entwurf anlegen, ändern, löschen
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
const hatText = (wert) => typeof wert === 'string' && wert.trim() !== ''
|
||||
|
||||
export async function ladeAuftrag(id) {
|
||||
const auftrag = istDokumentId(id) ? await getAuftrag(id) : null
|
||||
if (!auftrag) throw new AuftragError(404, 'Auftragsbestätigung nicht gefunden.')
|
||||
return auftrag
|
||||
}
|
||||
|
||||
export async function ladeKunde(kundenId) {
|
||||
const nichtGefunden = () => new AuftragError(404, 'Kunde nicht gefunden.')
|
||||
if (!istDokumentId(kundenId)) throw nichtGefunden()
|
||||
try {
|
||||
return await getDocument(config.collections.customers, kundenId)
|
||||
} catch (err) {
|
||||
if (err?.status === 404) throw nichtGefunden()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/** Die Felder, die die Eingabeprüfung kennt, aus einem gespeicherten Auftrag (ohne kundenId und kundenart). */
|
||||
function eingabeAusAuftrag(auftrag) {
|
||||
const { projektId, titel, art, positionen, stundensatzCents, schaetzungMinuten, zahlungsplan, zahlungszielTage, auftragVom, auftragWeg, startAm, fertigBis, zeitquelle } = auftrag
|
||||
return { projektId, titel, art, positionen, stundensatzCents, schaetzungMinuten, zahlungsplan, zahlungszielTage, auftragVom, auftragWeg, startAm, fertigBis, zeitquelle }
|
||||
}
|
||||
|
||||
/** Das Ticket eines Projekts als Zeitquelle (nie ein Leereintrag). */
|
||||
function ticketsDesProjekts(projekt) {
|
||||
return hatText(projekt?.ticketId) ? [projekt.ticketId.trim()] : []
|
||||
}
|
||||
|
||||
async function ladeProjekt(projektId) {
|
||||
try {
|
||||
return await getDocument(config.collections.websiteProjects, projektId)
|
||||
} catch (err) {
|
||||
if (err?.status === 404) return null
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/** Projekt und Tickets müssen dem Kunden gehören; liefert die Felder mit aufgelöstem Titel und aufgelöster Zeitquelle. */
|
||||
async function pruefeBezuege(kundeDoc, felder) {
|
||||
let titel = felder.titel
|
||||
let zeitquelle = felder.zeitquelle
|
||||
let projekt = null
|
||||
if (felder.projektId) {
|
||||
projekt = await ladeProjekt(felder.projektId)
|
||||
if (!projekt || projekt.customerId !== kundeDoc.$id) throw new AuftragError(400, 'Projekt: Das Projekt gehört nicht zu diesem Kunden.')
|
||||
}
|
||||
if (!titel) titel = String(projekt?.projectName || '').trim().slice(0, LIMITS.maxTitel).trim()
|
||||
if (!titel) throw new AuftragError(400, 'Titel: Bitte einen Titel angeben oder ein Projekt wählen.')
|
||||
if (zeitquelle.length === 0 && projekt) zeitquelle = ticketsDesProjekts(projekt)
|
||||
if (zeitquelle.length > 0) {
|
||||
const tickets = await listDocuments(config.collections.workorders, [Query.equal('$id', zeitquelle), Query.limit(LIMITS.maxZeitquellen)])
|
||||
const gefunden = new Map(tickets.map((t) => [t.$id, t]))
|
||||
if (!zeitquelle.every((id) => gefunden.get(id)?.customerId === kundeDoc.$id)) {
|
||||
throw new AuftragError(400, 'Zeitquelle: Mindestens ein Ticket gehört nicht zu diesem Kunden.')
|
||||
}
|
||||
}
|
||||
return { ...felder, titel, zeitquelle }
|
||||
}
|
||||
|
||||
/** Setzt die Kundenart am Kunden, aber nur, wenn dort noch keine steht. */
|
||||
async function schreibeKundenart(kundeDoc, kundenart) {
|
||||
if (!kundenart || hatText(kundeDoc.kundenart)) return
|
||||
await updateDocument(config.collections.customers, kundeDoc.$id, { kundenart })
|
||||
}
|
||||
|
||||
/** Die Felder einer AB, wie createAuftrag und updateAuftrag sie bekommen. */
|
||||
const speicherFelder = (f) => ({
|
||||
projektId: f.projektId, titel: f.titel, zeitquelle: f.zeitquelle, art: f.art, positionen: f.positionen,
|
||||
stundensatzCents: f.stundensatzCents, schaetzungMinuten: f.schaetzungMinuten, summeCents: f.summeCents,
|
||||
zahlungsplan: f.zahlungsplan, zahlungszielTage: f.zahlungszielTage, auftragVom: f.auftragVom, auftragWeg: f.auftragWeg,
|
||||
startAm: f.startAm, fertigBis: f.fertigBis,
|
||||
})
|
||||
|
||||
export async function legeEntwurfAn(eingabe, { von } = {}) {
|
||||
const felder = pruefeEingabe(eingabe)
|
||||
if (!hatText(eingabe.kundenId)) throw new AuftragError(400, 'Kunde: Bitte einen Kunden angeben.')
|
||||
const kundeDoc = await ladeKunde(eingabe.kundenId)
|
||||
const fertig = await pruefeBezuege(kundeDoc, felder)
|
||||
await schreibeKundenart(kundeDoc, felder.kundenart)
|
||||
return createAuftrag({ kundenId: eingabe.kundenId, ...speicherFelder(fertig), erstelltVon: String(von || '') })
|
||||
}
|
||||
|
||||
export async function aendereEntwurf(id, eingabe) {
|
||||
return withLocks(sperreAuftrag(id), async () => {
|
||||
const auftrag = await ladeAuftrag(id)
|
||||
if (auftrag.status !== 'entwurf') throw new AuftragError(409, 'Nur ein Entwurf lässt sich ändern.')
|
||||
if (eingabe?.kundenId !== undefined && eingabe.kundenId !== auftrag.kundenId) {
|
||||
throw new AuftragError(400, 'Kunde: Der Kunde eines Auftrags lässt sich nicht ändern.')
|
||||
}
|
||||
const teil = pruefeEingabe(eingabe, { teilweise: true })
|
||||
const felder = pruefeEingabe({ ...eingabeAusAuftrag(auftrag), ...teil })
|
||||
const kundeDoc = await ladeKunde(auftrag.kundenId)
|
||||
const fertig = await pruefeBezuege(kundeDoc, felder)
|
||||
await schreibeKundenart(kundeDoc, felder.kundenart)
|
||||
return updateAuftrag(id, speicherFelder(fertig))
|
||||
})
|
||||
}
|
||||
|
||||
export async function loescheEntwurf(id) {
|
||||
return withLocks(sperreAuftrag(id), async () => {
|
||||
const auftrag = await ladeAuftrag(id)
|
||||
if (auftrag.status !== 'entwurf') throw new AuftragError(409, 'Nur ein Entwurf lässt sich löschen.')
|
||||
await deleteAuftrag(id)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user