// "Exported to lexoffice as CSV" marker on C_Order. // // The settlement pages (Amazon / eBay) build a lexoffice bank-statement CSV from // the marketplace payout data. To give the bookkeeper an overview of which orders // already went out in such a file, two custom columns on C_Order record it: // - isLexofficeCsvExported (Yes-No, default 'N', IsAlwaysUpdateable=Y) // - lexofficeCsvExportDate (Date, nullable, IsAlwaysUpdateable=Y) // Both are custom (EntityType=U) columns without a generated model getter, so // iDempiere REST uses the ColumnName verbatim on reads AND writes — hence the // lowercase-first names (same style as C_Invoice.isUploadToLexoffice). // // FAIL-SOFT: until the columns exist every read that $selects them is answered // with `400 " is not a valid column of table C_Order"`. The settlement // routes therefore ask for them optimistically, and on that specific error // retry without them and remember (per process) that the feature is unavailable. // The frontend shows the CSV marker/filters only when `csvExportAvailable` is true. export const CSV_EXPORT_FLAG_COL = 'isLexofficeCsvExported' export const CSV_EXPORT_DATE_COL = 'lexofficeCsvExportDate' export const CSV_EXPORT_COLS = [CSV_EXPORT_FLAG_COL, CSV_EXPORT_DATE_COL] // null = not probed yet, true = columns exist, false = missing (retry-free fast path) let csvColumnsAvailable: boolean | null = null export const isCsvExportColumnsAvailable = () => csvColumnsAvailable !== false const errorText = (err: any): string => String(err?.data?.detail || err?.data?.message || err?.data?.title || err?.message || '') // True when the error is iDempiere complaining about one of OUR columns — // anything else (auth, network, other invalid column) must keep propagating. export const isCsvExportColumnError = (err: any): boolean => { const text = errorText(err) return CSV_EXPORT_COLS.some(col => new RegExp(`\\b${col}\\b`, 'i').test(text)) && /not a valid column|column .* not found|invalid column|no such column/i.test(text) } export const markCsvExportColumnsMissing = () => { csvColumnsAvailable = false } export const markCsvExportColumnsPresent = () => { csvColumnsAvailable = true } // Runs `request(extraSelectCols)` with the CSV columns appended to the $select // list; on the "not a valid column" error retries once without them. export const withOptionalCsvExportCols = async (request: (cols: string[]) => Promise): Promise => { if (csvColumnsAvailable === false) return request([]) try { const res = await request(CSV_EXPORT_COLS) csvColumnsAvailable = true return res } catch (err: any) { if (!isCsvExportColumnError(err)) throw err csvColumnsAvailable = false return request([]) } } const truthy = (v: any) => v === true || v === 'Y' || v === 'true' // Tolerant read of the marker off a c_order REST record (either casing). export const readCsvExportMark = (rec: any): { exported: boolean; date: string } => { if (!rec) return { exported: false, date: '' } const flag = rec[CSV_EXPORT_FLAG_COL] ?? rec.IsLexofficeCsvExported ?? rec.islexofficecsvexported const date = rec[CSV_EXPORT_DATE_COL] ?? rec.LexofficeCsvExportDate ?? rec.lexofficecsvexportdate return { exported: truthy(flag), date: date ? String(date) : '' } }