import fetchHelper from './fetchHelper' // Central DHL main-account credentials + billing numbers, managed on the M_Shipper // record (Materials → Shippers — fill the DHL fields on exactly ONE shipper, the // main "DHL" one). Resolution is tiered: shipper record → env (.env DHLPASS etc.). // The env fallback keeps label printing alive when iDempiere is unreachable or the // shipper record is empty. Read with the service token (IDEMPIERETOKEN) so the // lookup works for every role and without a user session. 24-h TTL, fail-soft // (stale cache → env-only) — the label hot path must never break. The long TTL // is safe because the cache is invalidated at the write point (shipper store/ // update routes) and busted on a DHL auth failure (getToken in dhlHelper), so // credential changes apply instantly; the TTL is only a backstop for edits made // directly in iDempiere (and, in PM2 cluster mode, for sibling processes that // didn't handle the save request). // Per-order-source custom DHL accounts (c_ordersource.DHL_USE_CUSTOM) are a // separate tier handled by dhlCustomHelper and are NOT affected by this util. const TTL_MS = 24 * 60 * 60 * 1000 export interface DhlMainCredential { dhlurl: string dhlkey: string dhlsecret: string dhluser: string dhlpass: string billingDe: string billingInt: string billingKp: string billingWpInt: string billingRetoure: string } let cached: { cred: DhlMainCredential; at: number } | null = null const pick = (row: any, ...keys: string[]): string => { for (const key of keys) { const val = row?.[key] if (val !== undefined && val !== null && String(val).trim() !== '') { return String(val).trim() } } return '' } const envCredential = (): DhlMainCredential => { const config = useRuntimeConfig() return { dhlurl: config.api.dhlurl || '', dhlkey: config.api.dhlkey || '', dhlsecret: config.api.dhlsecret || '', dhluser: config.api.dhluser || '', dhlpass: config.api.dhlpass || '', billingDe: '', billingInt: '', billingKp: '', billingWpInt: '', billingRetoure: '' } } // Called after a shipper save and after a DHL auth failure so a credential // rotated on the shipper record takes effect immediately instead of after the TTL. export const invalidateDhlMainCredential = () => { cached = null } export default async function getDhlMainCredential(event: any): Promise { const now = Date.now() if (cached && now - cached.at < TTL_MS) { return cached.cred } const base = envCredential() try { const serviceToken = (useRuntimeConfig().api as any)?.idempieretoken if (!serviceToken) { return base } const res: any = await fetchHelper(event, 'models/m_shipper?$filter=IsActive eq true', 'GET', serviceToken, null) // The default account is the shipper row carrying credentials; lowest id wins // deterministically if someone fills more than one. const row = (res?.records ?? []) .filter((r: any) => pick(r, 'DHLKEY', 'dhlkey') !== '' && pick(r, 'DHLPASS', 'dhlpass') !== '') .sort((a: any, b: any) => Number(a?.id ?? 0) - Number(b?.id ?? 0))[0] const cred: DhlMainCredential = row ? { dhlurl: pick(row, 'DHLURL', 'dhlurl') || base.dhlurl, dhlkey: pick(row, 'DHLKEY', 'dhlkey') || base.dhlkey, dhlsecret: pick(row, 'DHLSECRET', 'dhlsecret') || base.dhlsecret, dhluser: pick(row, 'DHLUSER', 'dhluser') || base.dhluser, dhlpass: pick(row, 'DHLPASS', 'dhlpass') || base.dhlpass, billingDe: pick(row, 'dhl_billing_number_de', 'DHL_Billing_Number_DE'), billingInt: pick(row, 'dhl_billing_number_int', 'DHL_Billing_Number_INT'), billingKp: pick(row, 'dhl_billing_number_kp', 'DHL_Billing_Number_KP'), billingWpInt: pick(row, 'dhl_billing_number_wp_int', 'DHL_Billing_Number_WP_INT'), billingRetoure: pick(row, 'dhl_billing_number_retoure', 'DHL_Billing_Number_Retoure') } : base cached = { cred, at: now } return cred } catch (e: any) { console.warn('[dhlMainCredential] lookup failed, using ' + (cached ? 'stale cache' : 'env fallback') + ':', e?.message ?? e) if (cached) { return cached.cred } return base } }