import refreshTokenHelper from "../../utils/refreshTokenHelper" import forceLogoutHelper from "../../utils/forceLogoutHelper" import errorHandlingHelper from "../../utils/errorHandlingHelper" import toIdempiereDate from "../../utils/idempiereDate" import { CSV_EXPORT_FLAG_COL, CSV_EXPORT_DATE_COL, isCsvExportColumnError, markCsvExportColumnsMissing, markCsvExportColumnsPresent } from "../../utils/lexofficeCsvExportMark" // Marks (or un-marks) orders as "exported to lexoffice via settlement CSV". // POST body: { orderIds: number[], exported: boolean } // → { status, updatedCount, updatedIds, errors? } // Called by the settlement pages right after a positions-containing CSV download // (exported=true, all matched orders of the exported periods) and by their // "CSV-Export-Markierung zurücksetzen" actions (exported=false). Writes are // independent per order (fail-soft per id, bounded concurrency) — a single bad // order never blocks the rest. See server/utils/lexofficeCsvExportMark.ts for // the column contract. const CONCURRENCY = 5 const handleFunc = async (event: any, authToken: any = null) => { const token = authToken ?? await getTokenHelper(event) const body = await readBody(event) const orderIds: number[] = [...new Set((body?.orderIds || []).map((id: any) => Number(id)).filter((id: number) => Number.isFinite(id) && id > 0))] const exported = body?.exported !== false if (!orderIds.length) { return { status: 400, message: 'No order IDs provided' } } const payload: Record = { [CSV_EXPORT_FLAG_COL]: exported, [CSV_EXPORT_DATE_COL]: exported ? toIdempiereDate(new Date()) : null } const errors: string[] = [] const updatedIds: number[] = [] let columnMissing = false let authError: any = null const queue = [...orderIds] const worker = async () => { while (queue.length) { if (columnMissing || authError) return const orderId = queue.shift() as number try { await event.context.fetch(`models/c_order/${orderId}`, 'PUT', token, payload) updatedIds.push(orderId) } catch (err: any) { if (isCsvExportColumnError(err)) { columnMissing = true; return } const status = Number(err?.statusCode || err?.status || err?.data?.status || 0) if (status === 401) { authError = err; return } errors.push(`Order ${orderId}: ${err?.data?.detail || err?.data?.message || err?.message || 'Unknown error'}`) } } } await Promise.all(Array.from({ length: Math.min(CONCURRENCY, orderIds.length) }, worker)) // Let the outer handler run the token-refresh retry. if (authError) throw authError if (columnMissing) { markCsvExportColumnsMissing() return { status: 400, message: `Die Spalten ${CSV_EXPORT_FLAG_COL} / ${CSV_EXPORT_DATE_COL} fehlen auf C_Order — die CSV-Export-Markierung kann erst nach dem Anlegen der Spalten in iDempiere gespeichert werden.`, updatedCount: updatedIds.length, updatedIds, columnMissing: true } } markCsvExportColumnsPresent() if (updatedIds.length > 0) { return { status: 200, message: exported ? `${updatedIds.length} Auftrag/Aufträge als CSV-exportiert markiert` : `CSV-Export-Markierung für ${updatedIds.length} Auftrag/Aufträge zurückgesetzt`, updatedCount: updatedIds.length, updatedIds, errors: errors.length ? errors : undefined } } return { status: 400, message: exported ? 'Keine Aufträge markiert' : 'Keine Markierung zurückgesetzt', updatedCount: 0, updatedIds, errors } } export default defineEventHandler(async (event) => { let data: any = {} try { data = await handleFunc(event) } catch (err: any) { try { let authToken: any = await refreshTokenHelper(event) data = await handleFunc(event, authToken) } catch (error) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) forceLogoutHelper(event, data) } } return data })