feat(arap): write off a bad debt in the engine (P4b S7)

An invoice that will not be collected comes off the receivables and is
deducted, the direct write-off method the owner chose (A-5; IRC
§166(a); Treas. Reg. §1.166-1(e), -2(a), -3(a); rules.md B-41).

Ledger::write_off posts one balanced entry, Dr 6990 Bad Debt Expense /
Cr 1200 Accounts Receivable, its receivable line tagged WriteOff so the
figures stay derived: what the invoice owes falls and what was written
off rises, and a reversal re-instates it without a new movement (D-49).
The amount is at most what is owed, and at most the least owed on any
day from the write-off's on: a later payment can't leave the invoice
owing less than nothing (B-38 INVARIANT 2). A partial write-off is
allowed, as §1.166-3(a) allows to the extent charged off. Cash-basis
books refuse it: an invoice never counted as income has no basis to
deduct (§1.166-1(e)). The bad_debts role (6990) is a control role, so
the account is created on demand and can be assigned like the others.

The same-year recovery D-49 records is a reversal of the write-off and
then a payment; a later year is a plain entry, and needs no movement.

Tests: half_paid_rest_written_off_is_written_off,
write_off_refused_on_cash_basis, write_off_above_outstanding_refused,
write_off_then_recovery_same_year.
This commit is contained in:
swaits committed 2026-10-27 12:44:45 -06:00
1 parent ed637f0e27
commit d5af44b9cb
4 files changed
+410 -5

No files matched your search

+6 -5
View File
@@ -154,9 +154,10 @@ impl DocRef {
}
/// Why a ledger line carries its document: what it does to what is owed.
/// Only `Origin` and `Payment` are written today (P4b S1); the rest are
/// the movements later slices write (P4b.6), named here so a tag's kind
/// never changes meaning.
/// `Origin`, `Payment` and `WriteOff` are written today (S1, S7); the rest
/// are movements only a later unit may write (P4b.6), named here so a
/// tag's kind never changes meaning. `Adopted` and `RoleMove` are
/// deferred (D-49).
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Movement {
@@ -168,9 +169,9 @@ pub enum Movement {
Discount,
/// A bad debt written off (S7).
WriteOff,
/// A legacy hand line adopted as the document (S7).
/// A legacy hand line adopted as the document (deferred, D-49).
Adopted,
/// The balance moved with its control role (S7).
/// The balance moved with its control role (deferred, D-49).
RoleMove,
}