diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 627bf6d..821f131 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -692,6 +692,73 @@ const IMPORTERS: ImporterConfig[] = [ return r; }, }, + // Trust ledger + { + key: "trust_ledger", + label: "Trust ledger → Trust accounting", + description: + "Trust account deposits and withdrawals. Expected columns: Date, Client, Type (deposit/withdrawal), Amount, Note, optional Case (case number or title), optional ExternalId, optional Debit/Credit (use instead of Type+Amount). Archived clients are accepted. Rows that don't match a client are SKIPPED.", + table: "trust_ledger_entries", + conflict: "external_id", + required: ["client_id", "entry_type", "amount"], + aliases: { + id: "external_id", externalid: "external_id", trustid: "external_id", + clientid: "_clientext", clientexternalid: "_clientext", + client: "_clientname", clientname: "_clientname", hoa: "_clientname", association: "_clientname", + caseid: "_caseext", matterid: "_caseext", casenumber: "_casenum", + casename: "_casename", casetitle: "_casename", matter: "_casename", mattername: "_casename", + type: "_type", entrytype: "_type", txtype: "_type", transactiontype: "_type", + amount: "_amount", total: "_amount", + deposit: "_deposit", credit: "_deposit", + withdrawal: "_withdrawal", debit: "_withdrawal", payment: "_withdrawal", + date: "entry_date", entrydate: "entry_date", txdate: "entry_date", + note: "note", notes: "note", memo: "note", description: "note", + }, + numeric: ["_amount", "_deposit", "_withdrawal"], + dateCols: ["entry_date"], + transform: (r, ctx) => { + // Resolve client (allow archived) + const cidExt = r._clientext ? ctx.clientByExt.get(String(r._clientext)) : null; + const cidName = r._clientname + ? ctx.clientByName.get(String(r._clientname).toLowerCase().trim()) + : null; + r.client_id = cidExt ?? cidName ?? null; + delete r._clientext; delete r._clientname; + if (!r.client_id) return null; + + // Resolve case (optional, archived ok — use full caseByTitle/Number maps) + r.case_id = + (r._caseext && ctx.caseByExt.get(String(r._caseext))) || + (r._casenum && ctx.caseByNumber.get(String(r._casenum))) || + (r._casename && ctx.caseByTitle.get(String(r._casename).toLowerCase().trim())) || + null; + delete r._caseext; delete r._casenum; delete r._casename; + + // Resolve type + amount. Support Type+Amount or separate Debit/Credit columns. + const dep = Number(r._deposit) || 0; + const wdr = Number(r._withdrawal) || 0; + let amount = Number(r._amount) || 0; + let type: string | null = null; + if (r._type) { + const t = String(r._type).toLowerCase().trim(); + if (["deposit", "credit", "in", "+"].includes(t)) type = "deposit"; + else if (["withdrawal", "withdraw", "debit", "out", "-", "payment"].includes(t)) type = "withdrawal"; + } + if (!type && dep > 0) { type = "deposit"; amount = dep; } + else if (!type && wdr > 0) { type = "withdrawal"; amount = wdr; } + // Negative amount → withdrawal + if (!type && amount < 0) { type = "withdrawal"; amount = Math.abs(amount); } + if (!type && amount > 0) { type = "deposit"; } + delete r._type; delete r._amount; delete r._deposit; delete r._withdrawal; + + if (!type || !amount || amount <= 0) return null; + r.entry_type = type; + r.amount = Math.abs(amount); + if (!r.entry_date) r.entry_date = new Date().toISOString().slice(0, 10); + r.created_by = ctx.userId; + return r; + }, + }, ]; interface ImportResult {