Buchungskalender: origin/main (Aufträge) zusammenführen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
fd971309a8
145 files changed
+42590
-129
No files matched your search
@@ -26,7 +26,10 @@ import http from 'node:http'
|
||||
* Wert loescht), .../finalize (nur draft -> open, vergibt die Nummer WK-0001, WK-0002, ... und die Faelligkeit),
|
||||
* .../send (nur open), .../void (nur open -> void), .../pay (nur open -> paid, auch paid_out_of_band; wie die echte API
|
||||
* liefert die Rechnung danach kein Feld paid_out_of_band, nur der Aufruf in calls zeigt es) und
|
||||
* DELETE /v1/invoices/:id (nur draft); jeder andere Zustand ist wie bei Stripe ein 400. Der Kunde einer neuen
|
||||
* DELETE /v1/invoices/:id (nur draft); jeder andere Zustand ist wie bei Stripe ein 400.
|
||||
* custom_fields (hoechstens 4, Name 1 bis 40, Wert 1 bis 140 Zeichen) und rendering[pdf][page_size] (a4, auto, letter) werden wie
|
||||
* bei Stripe geprueft, gespeichert (sonst null) und mit der Rechnung geliefert; Rechnungsposten mit amount (auch negativ)
|
||||
* tragen weder quantity noch unit_amount_decimal. Der Kunde einer neuen
|
||||
* Rechnung muss nicht in st.customers stehen (dann bleiben Name und E-Mail leer). Traegt ein Aufruf die Kopfzeile
|
||||
* Idempotency-Key, steht sie als idempotencyKey in calls; delayNext verzoegert eine Antwort (Zeitgrenzen testen).
|
||||
* Kunden: GET /v1/customers/search (Metadaten), POST /v1/customers (anlegen) und POST /v1/customers/:id (aendern:
|
||||
@@ -461,6 +464,22 @@ export async function startFakeStripe() {
|
||||
}
|
||||
const metadata = mergeMetadata({}, data.metadata)
|
||||
if (Array.isArray(metadata)) return metadata
|
||||
const customFields = data.custom_fields
|
||||
if (customFields !== undefined) {
|
||||
if (!Array.isArray(customFields) || customFields.length > 4) return invalid('custom_fields', 'You can specify up to 4 custom fields.')
|
||||
for (const [i, field] of customFields.entries()) {
|
||||
if (typeof field?.name !== 'string' || field.name.length < 1 || field.name.length > 40) {
|
||||
return invalid(`custom_fields[${i}][name]`, 'Custom field names must be between 1 and 40 characters.')
|
||||
}
|
||||
if (typeof field.value !== 'string' || field.value.length < 1 || field.value.length > 140) {
|
||||
return invalid(`custom_fields[${i}][value]`, 'Custom field values must be between 1 and 140 characters.')
|
||||
}
|
||||
}
|
||||
}
|
||||
const pageSize = data.rendering?.pdf?.page_size
|
||||
if (pageSize !== undefined && !['a4', 'auto', 'letter'].includes(pageSize)) {
|
||||
return invalid('rendering[pdf][page_size]', 'Invalid page_size: must be one of a4, auto, letter.')
|
||||
}
|
||||
const customer = customers.get(data.customer)
|
||||
const invoice = {
|
||||
id: nextId('in'),
|
||||
@@ -479,6 +498,8 @@ export async function startFakeStripe() {
|
||||
description: data.description ?? null,
|
||||
footer: data.footer ?? null,
|
||||
metadata,
|
||||
custom_fields: customFields ?? null,
|
||||
rendering: data.rendering ?? null,
|
||||
created: nowUnix(),
|
||||
total: 0,
|
||||
subtotal: 0,
|
||||
@@ -496,6 +517,9 @@ export async function startFakeStripe() {
|
||||
if (method === 'POST' && path === '/v1/invoiceitems') {
|
||||
const data = unflatten(body)
|
||||
if (!data.customer) return invalid('customer', 'Missing required param: customer.')
|
||||
if (data.amount !== undefined && (data.quantity !== undefined || data.unit_amount_decimal !== undefined)) {
|
||||
return invalid('amount', 'You may only specify one of these parameters: amount, or quantity with unit_amount_decimal.')
|
||||
}
|
||||
const quantity = data.quantity !== undefined ? Number(data.quantity) : 1
|
||||
if (!Number.isInteger(quantity) || quantity < 0) return invalid('quantity', 'Invalid non-negative integer: quantity.')
|
||||
let unit = null
|
||||
|
||||
+10
-3
@@ -3,6 +3,7 @@ import { TERMINE_COLLECTIONS } from '../../server/services/termine/schema.js'
|
||||
import { SHOP_COLLECTIONS } from '../../server/services/shop/schema.js'
|
||||
import { UNTERKUNFT_COLLECTIONS } from '../../server/services/unterkunft/schema.js'
|
||||
import { ZEITEN_COLLECTIONS, ZEITEN_FEATURE_ATTRIBUTES } from '../../server/services/zeiten/schema.js'
|
||||
import { AUFTRAEGE_COLLECTIONS, AUFTRAEGE_ZUSATZ_ATTRIBUTE } from '../../server/services/auftraege/schema.js'
|
||||
import { FEATURE_CONSENT_ATTRIBUTES, PORTAL_FEATURE_ATTRIBUTES } from '../../scripts/feature-setup.mjs'
|
||||
|
||||
// Setup-Skripte, die Collections in Appwrite anlegen (Blockformat: 'id', ... \n ],)
|
||||
@@ -18,9 +19,12 @@ const SETUP_SCRIPTS = ['setup-abo-collections.mjs', 'setup-finanzen.mjs']
|
||||
* die Zeiten-Collections aus server/services/zeiten/schema.js (scripts/zeiten-setup.mjs);
|
||||
* portalFeatures bekommt zusaetzlich die Attribute aus scripts/feature-setup.mjs (PORTAL_FEATURE_ATTRIBUTES) und die
|
||||
* Zeiten-Felder aus server/services/zeiten/schema.js (ZEITEN_FEATURE_ATTRIBUTES, angelegt von scripts/zeiten-setup.mjs);
|
||||
* featureConsents (Zustimmungsnachweis) steht ganz dort (FEATURE_CONSENT_ATTRIBUTES).
|
||||
* featureConsents (Zustimmungsnachweis) steht ganz dort (FEATURE_CONSENT_ATTRIBUTES);
|
||||
* die Aufträge-Collections kommen aus server/services/auftraege/schema.js (scripts/auftraege-setup.mjs), und ihre
|
||||
* Zusatzfelder bestehender Collections (AUFTRAEGE_ZUSATZ_ATTRIBUTE, z. B. financeSettings.steuernummer) werden angehängt.
|
||||
*/
|
||||
export function schemaKeys(collection) {
|
||||
if (Object.hasOwn(AUFTRAEGE_COLLECTIONS, collection)) return AUFTRAEGE_COLLECTIONS[collection].attributes.map((a) => a.key)
|
||||
if (Object.hasOwn(TERMINE_COLLECTIONS, collection)) return TERMINE_COLLECTIONS[collection].attributes.map((a) => a.key)
|
||||
if (Object.hasOwn(SHOP_COLLECTIONS, collection)) return SHOP_COLLECTIONS[collection].attributes.map((a) => a.key)
|
||||
if (Object.hasOwn(UNTERKUNFT_COLLECTIONS, collection)) return UNTERKUNFT_COLLECTIONS[collection].attributes.map((a) => a.key)
|
||||
@@ -32,8 +36,11 @@ export function schemaKeys(collection) {
|
||||
if (start < 0) continue
|
||||
const block = src.slice(start, src.indexOf('\n ],', start))
|
||||
const keys = [...block.matchAll(/key: '(\w+)'/g)].map((m) => m[1])
|
||||
return collection === 'portalFeatures'
|
||||
? [...keys, ...PORTAL_FEATURE_ATTRIBUTES.map((a) => a.key), ...ZEITEN_FEATURE_ATTRIBUTES.map((a) => a.key)]
|
||||
if (collection === 'portalFeatures') {
|
||||
return [...keys, ...PORTAL_FEATURE_ATTRIBUTES.map((a) => a.key), ...ZEITEN_FEATURE_ATTRIBUTES.map((a) => a.key)]
|
||||
}
|
||||
return Object.hasOwn(AUFTRAEGE_ZUSATZ_ATTRIBUTE, collection)
|
||||
? [...keys, ...AUFTRAEGE_ZUSATZ_ATTRIBUTE[collection].map((a) => a.key)]
|
||||
: keys
|
||||
}
|
||||
throw new Error(`Collection ${collection} fehlt im Setup-Skript`)
|
||||
|
||||
Reference in new issue
Block a user