import { config } from '../../config.js' import { createDocument, deleteDocument, getDocument, listDocuments, Query, updateDocument } from '../appwriteAdmin.js' /** * Speicher fuer den Bankzugang (ein Dokument "finom" in bankConnections) und die Buchungen * (bankTransactions). Die Dokument-ID einer Buchung ist der Hash ihrer Bank-Referenz (siehe bankSync), * dieselbe Buchung landet also bei jedem Abruf im selben Dokument. */ /** ID des einen Verbindungsdokuments. */ export const CONNECTION_ID = 'finom' // Felder, die von der Bank kommen und bei jedem Abruf ueberschrieben werden duerfen. Alles andere an einer // Buchung (category, matched*, fixedCostId, note) gehoert dem Nutzer und bleibt beim Abruf unberuehrt. const BANK_FIELDS = [ 'status', 'bookingDate', 'valueDate', 'amountCents', 'currency', 'counterpartyName', 'counterpartyIban', 'remittance', 'mcc', 'entryReference', 'accountUid', 'syncedAt', ] // Zuordnungsfelder einer neuen Buchung: leer (Spec: "category leer" = noch nicht zugeordnet) const USER_FIELD_DEFAULTS = { category: '', matchedInvoiceId: '', matchedInvoiceNumber: '', matchedPayoutId: '', fixedCostId: '', note: '', } const withoutMeta = (doc) => Object.fromEntries(Object.entries(doc).filter(([key, value]) => !key.startsWith('$') && value !== undefined)) const pick = (doc, keys) => Object.fromEntries(keys.filter((key) => doc[key] !== undefined).map((key) => [key, doc[key]])) const isStatus = (err, status) => err?.status === status /** * Dokument aktualisieren; gibt es das Dokument noch nicht (404), wird es mit createData angelegt. Meldet * zwischen beiden Schritten ein anderer Aufruf dasselbe Dokument an (409), gilt wieder das Aktualisieren. * Liefert 'neu' oder 'aktualisiert' (und das Dokument). */ async function upsert(collectionId, id, patch, createData) { try { return { how: 'aktualisiert', doc: await updateDocument(collectionId, id, patch) } } catch (err) { if (!isStatus(err, 404)) throw err } try { return { how: 'neu', doc: await createDocument(collectionId, createData, id) } } catch (err) { if (!isStatus(err, 409)) throw err } return { how: 'aktualisiert', doc: await updateDocument(collectionId, id, patch) } } /** Was die Seite vom Bankzugang sieht (GET /bank und die Statuszeile der Übersicht): nie Sitzung, IBAN, state oder Konto-Kennungen. */ export function publicConnection(connection) { const pick = (key) => connection[key] ?? null return { status: pick('status'), ibanMasked: pick('ibanMasked'), validUntil: pick('validUntil'), lastSyncAt: pick('lastSyncAt'), lastSyncOk: pick('lastSyncOk'), lastError: pick('lastError'), balanceBookedCents: pick('balanceBookedCents'), balanceAvailableCents: pick('balanceAvailableCents'), balanceDate: pick('balanceDate'), latestBookingDate: pick('latestBookingDate'), } } /** Der Bankzugang oder null, wenn es noch keinen gibt. */ export async function getConnection() { try { return await getDocument(config.collections.bankConnections, CONNECTION_ID) } catch (err) { if (isStatus(err, 404)) return null throw err } } /** Bankzugang anlegen oder die genannten Felder aktualisieren (die anderen bleiben). Liefert das Dokument. */ export async function saveConnection(patch) { const data = withoutMeta(patch) return (await upsert(config.collections.bankConnections, CONNECTION_ID, data, data)).doc } /** * Buchung speichern. doc traegt die Dokument-ID in $id (so liefert sie toTransactionDoc). Neu: alle * Bankfelder plus leere Zuordnungsfelder; vorhanden: nur die Bankfelder. 'neu' | 'aktualisiert'. */ export async function upsertTransaction(doc) { const id = doc.$id if (!id) throw new Error('upsertTransaction: $id fehlt') const fields = pick(doc, BANK_FIELDS) return (await upsert(config.collections.bankTransactions, id, fields, { ...USER_FIELD_DEFAULTS, ...fields })).how } /** Gespeicherte Buchungen, neueste zuerst nach Buchungsdatum (hoechstens limit). */ export async function listTransactions({ limit = 1000 } = {}) { return listDocuments(config.collections.bankTransactions, [Query.orderDesc('bookingDate'), Query.limit(limit)]) } /** * Alle vorgemerkten Buchungen (Status PDNG). Die Suche nutzt den Index idx_status; fehlt er in Appwrite noch * (Setup-Skript nicht erneut gelaufen) oder scheitert die Suche, wird in den gespeicherten Buchungen gefiltert, * damit die Aufraeumung der Vormerkungen den Abruf nie bricht. */ export async function listPendingTransactions() { try { return await listDocuments(config.collections.bankTransactions, [Query.equal('status', 'PDNG'), Query.limit(1000)]) } catch { return (await listTransactions()).filter((doc) => doc.status === 'PDNG') } } /** Eine Buchung oder null. */ export async function getTransaction(id) { try { return await getDocument(config.collections.bankTransactions, id) } catch (err) { if (isStatus(err, 404)) return null throw err } } /** Felder einer Buchung aendern (z. B. Zuordnung, Notiz). */ export async function updateTransaction(id, patch) { return updateDocument(config.collections.bankTransactions, id, withoutMeta(patch)) } export async function deleteTransaction(id) { return deleteDocument(config.collections.bankTransactions, id) }