Rechnungen: baueRechnung für freie Rechnung und Festbetrag, Testhilfe
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fd189b085d
commit
b78ca971af
3 files changed
+312
No files matched your search
@@ -0,0 +1,100 @@
|
||||
// Gemeinsame Testdaten für die Rechnungstests des Strangs L2. Erst nach useTestEnv() importieren (zieht config.js).
|
||||
import { config } from '../server/config.js'
|
||||
import { deleteDocument } from '../server/services/appwriteAdmin.js'
|
||||
import { createAuftrag, updateAuftrag } from '../server/services/auftraege/auftragStore.js'
|
||||
import { createProjektRechnung } from '../server/services/auftraege/projektRechnungenStore.js'
|
||||
|
||||
/** n Arbeitstage (Montag bis Freitag) ab start (YYYY-MM-DD, einschließlich). */
|
||||
export function werktage(start, n) {
|
||||
const tage = []
|
||||
for (let ms = Date.parse(`${start}T00:00:00Z`); tage.length < n; ms += 86_400_000) {
|
||||
const tag = new Date(ms)
|
||||
if (tag.getUTCDay() % 6 !== 0) tage.push(tag.toISOString().slice(0, 10))
|
||||
}
|
||||
return tage
|
||||
}
|
||||
|
||||
const deutsch = (tag) => tag.split('-').reverse().join('.')
|
||||
|
||||
/**
|
||||
* Kunden k1 (fest, mit Anschrift: Frau Wolf), k2 (fest), lead1 (Interessent); Projekte p1 (k1, Ticket t1), p2 (k2, Ticket t2),
|
||||
* p3 (k1, Ticket t4); Tickets t1 bis t4 (t2 gehört k2); Arbeitsblätter ws1 bis wsT3 (Oktober) und wl01 bis wl20 (Großprojekt).
|
||||
*/
|
||||
export function seedRechnungsDaten(aw) {
|
||||
const kunde = (id, over = {}) =>
|
||||
aw.seed('customers', { $id: id, email: `${id}@example.invalid`, name: `Kunde ${id}`, customerStatus: 'active', status: 'active', street: 'Hauptstr. 1', postalCode: '10115', city: 'Berlin', ...over })
|
||||
kunde('k1', { name: 'Anelia Wolf', email: 'wolf@example.invalid' })
|
||||
kunde('k2')
|
||||
kunde('lead1', { customerStatus: 'lead' })
|
||||
const projekt = (id, customerId, projectName, ticketId) => aw.seed('websiteProjects', { $id: id, customerId, projectName, ticketId })
|
||||
projekt('p1', 'k1', 'Website anelia-wolf.com', 't1')
|
||||
projekt('p2', 'k2', 'Fremdes Projekt', 't2')
|
||||
projekt('p3', 'k1', 'Großprojekt', 't4')
|
||||
for (const [id, customerId, woid] of [['t1', 'k1', 'WO-100'], ['t2', 'k2', 'WO-200'], ['t3', 'k1', 'WO-101'], ['t4', 'k1', 'WO-102']]) {
|
||||
aw.seed('workorders', { $id: id, customerId, woid, topic: `Ticket ${woid}`, status: 'occupied' })
|
||||
}
|
||||
const blatt = (id, workorderId, startDate, totalTime, details, over = {}) =>
|
||||
aw.seed('worksheets', { $id: id, workorderId, woid: 'WO', startDate, totalTime, details, serviceType: 'remote', countsToPlan: false, isComment: false, ...over })
|
||||
blatt('ws1', 't1', '06.10.2026', 90, 'Startseite')
|
||||
blatt('ws2', 't1', '06.10.2026', 45, 'Kontaktformular')
|
||||
blatt('ws3', 't1', '07.10.2026', 60, 'Impressum')
|
||||
blatt('wsAbo', 't1', '07.10.2026', 60, 'Wartung', { countsToPlan: true })
|
||||
blatt('wsKomm', 't1', '07.10.2026', 30, 'Notiz', { isComment: true })
|
||||
blatt('wsNull', 't1', '07.10.2026', 0, 'Leer')
|
||||
blatt('wsFremd', 't2', '07.10.2026', 60, 'Fremd')
|
||||
blatt('wsT3', 't3', '08.10.2026', 30, 'Zusatzticket')
|
||||
// Großprojekt: 20 Arbeitstage (vier Wochen, KW 45 bis 48) zu je 60 Minuten
|
||||
werktage('2026-11-02', 20).forEach((tag, i) => blatt(`wl${String(i + 1).padStart(2, '0')}`, 't4', deutsch(tag), 60, `Etappe ${i + 1}`))
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine versendete Festpreis-AB des Kunden k1 zu p1 (80.000 Cent: Website 600 €, Texte 2 × 100 €; Anzahlung 50 %; AB-2026-0002,
|
||||
* gesendet am 06.10.2026) über die L0-Speicher anlegen. over gilt für createAuftrag, patch für das anschließende updateAuftrag
|
||||
* (Status, Nummer, bestaetigtAm, …).
|
||||
*/
|
||||
export async function legeAuftragAn(over = {}, patch = {}) {
|
||||
const roh = await createAuftrag({
|
||||
kundenId: 'k1', projektId: 'p1', titel: 'Website anelia-wolf.com', zeitquelle: [], art: 'festpreis',
|
||||
positionen: [{ beschreibung: 'Website', menge: 1, einzelpreisCents: 60000 }, { beschreibung: 'Texte', menge: 2, einzelpreisCents: 10000 }],
|
||||
stundensatzCents: null, schaetzungMinuten: null, summeCents: 80000,
|
||||
zahlungsplan: { art: 'anzahlung', prozent: 50, text: '' }, zahlungszielTage: 14,
|
||||
auftragVom: '2026-10-05', auftragWeg: 'telefon', startAm: '', fertigBis: '', erstelltVon: 'kenso@webklar.com',
|
||||
...over,
|
||||
})
|
||||
return updateAuftrag(roh.id, { status: 'versendet', nummer: 'AB-2026-0002', jahr: 2026, laufnummer: 2, gesendetAm: '2026-10-06T08:15:00.000Z', ...patch })
|
||||
}
|
||||
|
||||
const unix = (iso) => Math.floor(new Date(iso).getTime() / 1000)
|
||||
|
||||
/**
|
||||
* Eine Projektrechnung wie nach createProjectInvoice: Stripe-Rechnung im Nachbau (status draft, open, paid oder void; ohneStripe: die
|
||||
* Rechnung gibt es bei Stripe nicht, z. B. im Dashboard gelöscht) plus aktiver Projektdatensatz. docNummer ist die Nummer im Datensatz
|
||||
* (Vorgabe: die der Rechnung, bei einem Entwurf leer).
|
||||
*/
|
||||
export async function legeRechnungAn(st, id, {
|
||||
status = 'draft', nummer = 'WEBKLAR-0007', total = 40000, art = 'aufwand', auftragId = '', projektId = 'p1',
|
||||
arbeitsblattIds = [], finalisiert = '2026-10-08T10:00:00.000Z', ohneStripe = false, docNummer,
|
||||
} = {}) {
|
||||
const entwurf = status === 'draft'
|
||||
if (!ohneStripe) {
|
||||
st.invoices.set(id, {
|
||||
id, object: 'invoice', number: entwurf ? null : nummer, status, customer: 'cus_x', created: unix(finalisiert), total, amount_due: total,
|
||||
amount_paid: status === 'paid' ? total : 0, amount_remaining: status === 'paid' ? 0 : total,
|
||||
status_transitions: { finalized_at: entwurf ? null : unix(finalisiert) }, metadata: { art }, lines: { object: 'list', data: [], has_more: false },
|
||||
})
|
||||
}
|
||||
await createProjektRechnung(id, {
|
||||
kundenId: 'k1', projektId, auftragId, art, betragCents: total, arbeitsblattIds,
|
||||
rechnungsnummer: docNummer ?? (entwurf ? '' : nummer), aktiv: true, erstelltVon: 'k', erstelltAm: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
/** Aufträge und Projektdatensätze löschen, Stripe-Nachbau (Rechnungen, Kunden, Aufrufe) leeren. */
|
||||
export async function leereRechnungsDaten(aw, st) {
|
||||
for (const sammlung of [config.collections.auftraege, config.collections.projektRechnungen]) {
|
||||
for (const id of aw.docs(sammlung).keys()) await deleteDocument(sammlung, id)
|
||||
}
|
||||
st.invoices.clear()
|
||||
st.customers.clear()
|
||||
st.calls.length = 0
|
||||
}
|
||||
Reference in new issue
Block a user