import fetchHelper from './fetchHelper' // Resolves the "Abrechnung" (Accounting) R_RequestType used for billed tickets. // // Business rule: as soon as a ticket carries a billed amount (RequestAmt ≠ 0 — // time was booked via "Zeit abrechnen", a direct amount (positive or a // negative credit) was added, a mobile task stop, a receive task or an inline // amount edit) its request type MUST be "Abrechnung". The rule is // enforced centrally in requests/{update.put,store.post}.ts through // `enforceAccountingRequestType()` so every client flow is covered without // having to remember it in each of them. // // The type is looked up by name (Abrechnung / Accounting) so template/clone // instances with different IDs keep working; the well-known prod ID is the // fallback when the lookup fails. Cached per process (10-min TTL). const FALLBACK_ACCOUNTING_REQUEST_TYPE_ID = 1000001 const CACHE_TTL_MS = 10 * 60 * 1000 let cached: { id: number, at: number } | null = null export const resolveAccountingRequestTypeId = async (event: any, token: string | null): Promise => { if (cached && (Date.now() - cached.at) < CACHE_TTL_MS) return cached.id try { const res: any = await fetchHelper(event, 'models/r_requesttype?$filter=IsActive%20eq%20true&$top=100', 'GET', token, null) const records: any[] = res?.records || [] const match = records.find((rt) => /^abrechnung$/i.test(String(rt?.Name || '').trim())) || records.find((rt) => /^accounting$/i.test(String(rt?.Name || '').trim())) || records.find((rt) => /abrechnung|accounting/i.test(String(rt?.Name || ''))) const id = Number(match?.id) if (Number.isFinite(id) && id > 0) { cached = { id, at: Date.now() } return id } } catch (err: any) { console.warn('[accountingRequestType] lookup failed, using fallback', err?.message || err) } return FALLBACK_ACCOUNTING_REQUEST_TYPE_ID } // True when the incoming write carries a non-zero billed amount. Negative // amounts count too: a credit ticket (e.g. -12,50 € goodwill) must also become // "Abrechnung", otherwise the nightly request → fee-line SQL in // ../logship-scripts (filters on r_requesttype_id = Abrechnung) never picks it // up and the credit would silently never reach the customer's invoice order. export const hasBilledAmount = (requestAmt: any): boolean => { if (requestAmt === null || requestAmt === undefined || requestAmt === '') return false const n = typeof requestAmt === 'number' ? requestAmt : Number(String(requestAmt).replace(',', '.')) return Number.isFinite(n) && n !== 0 } // Mutates `newObjValue` so the iDempiere write sets R_RequestType_ID to the // accounting type whenever the body carries a billed amount. Returns the id // that was enforced (or null when the rule did not apply). export const enforceAccountingRequestType = async (event: any, token: string | null, body: any, newObjValue: any): Promise => { if (!hasBilledAmount(body?.requestAmt)) return null const id = await resolveAccountingRequestTypeId(event, token) newObjValue.R_RequestType_ID = { id, tableName: 'R_RequestType' } return id }