fix(accounting): address the-luap PR #636 review
- #1 resolveTaxTreatment: an unconfigured (empty) reclaim-countries list no longer auto-classifies every supplier — incl. the admin's own domestic one — as foreign; defer auto-classification until the setting is set (+ test). - #2 pending re-bills on customer erase: eraseCustomer now returns the customer's not-yet-billed inbound docs to the inbox (null customer + unsorted) so they aren't billable to an anonymized account. (NB: picpeak has no hard customer delete — erase anonymizes in place — so the orphan/404 premise can't occur; this is hardening.) - #4 VatRateSelect: when >1 configured code shares the same rate, fall through to the legacy "(not configured)" option instead of silently picking the first. - #5 unwindBilledLine: delete the (mutable, never-issued) invoice when the unwound re-bill was its only line, instead of leaving a net-zero survivor. - #6 isInvoiceMutable: clarify in a comment that invoices have no 'draft' status (the editable state is 'scheduled' w/o send-at) — no behaviour change. - nit: collapse normalizeCurrency's tautological ternary. - Fix VAT picker i18n: t('vat.legacyRate') → 'ledger.vat.legacyRate' (the key's real home), so the legacy label localizes instead of always showing English. - Remove dead i18n keys left by the settings refactor (businessProfile.field VAT /hourly + profileFields.title/savedToast).
This commit is contained in:
@@ -785,6 +785,18 @@ async function eraseCustomer(id, erasedByAdminId) {
|
||||
|
||||
// Active reset tokens for this customer should be invalidated.
|
||||
await trx('customer_password_resets').where('customer_account_id', id).del();
|
||||
|
||||
// Pending re-bills (incoming invoices, migration 132) attached to this
|
||||
// customer would otherwise stay billable to the now-anonymized account —
|
||||
// return the not-yet-billed ones to the inbox for re-triage so they're not
|
||||
// silently lost or billed to a ghost (PR #636 review #2). Guarded for
|
||||
// schema drift on installs that predate migration 132.
|
||||
if (await trx.schema.hasColumn('inbound_documents', 'customer_account_id')) {
|
||||
await trx('inbound_documents')
|
||||
.where({ customer_account_id: id })
|
||||
.whereNull('billed_invoice_id')
|
||||
.update({ customer_account_id: null, disposition: null, status: 'unsorted', updated_at: new Date() });
|
||||
}
|
||||
});
|
||||
|
||||
await logActivity('customer_erased',
|
||||
|
||||
@@ -76,6 +76,10 @@ function resolveTaxTreatment(payloadTreatment, supplierCountry, reclaimCountries
|
||||
if (TAX_TREATMENTS.includes(payloadTreatment)) return payloadTreatment;
|
||||
const cc = String(supplierCountry || '').toUpperCase();
|
||||
if (!cc) return 'domestic';
|
||||
// Don't auto-classify until the admin has actually configured their reclaim
|
||||
// countries — an unset (empty) list must not make every supplier, including
|
||||
// the admin's own domestic one, "foreign". (PR #636 review #1.)
|
||||
if (!reclaimCountries || reclaimCountries.length === 0) return 'domestic';
|
||||
return reclaimCountries.includes(cc) ? 'domestic' : 'foreign_vat_non_reclaimable';
|
||||
}
|
||||
|
||||
@@ -287,6 +291,10 @@ const BOOKING_DISPOSITIONS = ['rebill', 'durchlaufend'];
|
||||
function isInvoiceMutable(invoice) {
|
||||
if (!invoice) return true; // referenced invoice gone — treat as not billed
|
||||
if (invoice.is_monthly_draft === true || invoice.is_monthly_draft === 1) return true;
|
||||
// NB: invoices have no 'draft' status (only quotes do). The editable,
|
||||
// not-yet-sent invoice state IS 'scheduled' with no scheduled_send_at (or a
|
||||
// future one), handled below — so there is no plain-'draft' case to slot in
|
||||
// here (PR #636 review #6).
|
||||
if (invoice.status !== 'scheduled') return false;
|
||||
if (!invoice.scheduled_send_at) return true;
|
||||
return new Date(invoice.scheduled_send_at).getTime() > Date.now();
|
||||
@@ -312,6 +320,15 @@ async function unwindBilledLine(trx, doc) {
|
||||
}
|
||||
if (invoice) {
|
||||
const allItems = await trx('invoice_line_items').where({ invoice_id: invoice.id });
|
||||
if (allItems.length === 0) {
|
||||
// The unwound re-bill was the only line — a net-zero invoice has no reason
|
||||
// to survive, and these would otherwise pile up over re-categorisations.
|
||||
// It's mutable (checked above) and never issued, so delete it outright
|
||||
// (PR #636 review #5). For a monthly draft this just means the next append
|
||||
// re-creates one.
|
||||
await trx('invoices').where({ id: invoice.id }).del();
|
||||
return;
|
||||
}
|
||||
let netMinor = 0;
|
||||
for (const li of allItems) {
|
||||
if (li.parent_line_item_id == null) netMinor += Number(li.line_total_minor || 0);
|
||||
|
||||
Reference in New Issue
Block a user