// "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) : '' }
}