N16 — type the bill direction, and support both Tally ageing methods - #171
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b5eade6ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5c91b4fcf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8e819b34ae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Closes #164. Closes #114.
#164 — bill direction becomes a type
StatementBill.kindandOpenBillRow.kindwere&'static str, consumed with a catch-all thatsilently classified anything unrecognised as receivable. A payable would have been reported to a
client as a receivable — inverting the figure rather than failing. Reconciliation would still have
passed, because the bill still lands in a bucket and subtotals still sum to
bill_total. That iswhat made it quiet.
Now an
ExposureDirectionenum with an exhaustive match and no_arm, so a third variant fails tocompile. Same shape as #162's warning codes and #163's ageing subtotals — the catch-all removed here
sat one line above the match #163 made exhaustive.
Latent rather than live:
into_open_bill_rowvalidated at the IPC boundary, so the catch-all wasdead code. The point is that the guarantee now lives in the type rather than in one validation
function a second producer could bypass.
#114 — both ageing methods, and the basis is disclosed
Tally's
F6: Ageing Methodoffers ageing by bill date and by due date, and the choice changes thebucket. Bridge aged unconditionally from
BILLDATE, never readBILLCREDITPERIOD, and never statedwhich basis produced its buckets.
Now: credit periods are parsed, both bases are selectable in the single-company and all-client views,
the choice flows through native and legacy computation, and every export discloses the basis in
force.
Credit periods are not always in days
Measured on licensed TallyPrime 7.1 — Tally accepts and returns days, weeks and months:
2 Months2 Months3 Weeks3 Weeks1 Day1 Days— Tally normalises the singularA parser requiring a
" Days"suffix would fail the entire outstandings read on any book withmonth-based terms, which is ordinary. All six forms are handled; unrecognised units still fail
closed.
Month arithmetic matches Tally exactly
Months are a calendar operation, not a day count. Tally's rule, measured directly:
4 Weeks1 Month1 Month2 MonthsAdd N calendar months preserving day-of-month, clamping to the last day of the target month when it
does not exist. Note 30-Jan and 31-Jan collapse onto the same due date — a
+30 daysimplementation gets both wrong, and wrong differently.
add_months_clampedwas run against those exact cases and reproduces all of them, plus2024-01-31 + 1M → 2024-02-29.Verification
Full workspace, both feature configurations. Compatibility gate passes; pin mismatches resealed to
0 of 119 with the matrix repointed. All three sentinels pass.
Ageing behaviour was validated against
BRIDGE CORPUS AGEING, a purpose-built book in which seven ofeight bills fall in different buckets under the two bases — so an implementation silently using
one basis cannot pass. Before that book existed, #114 was unfalsifiable, because every bill in the
reference corpus had an empty credit period and the two methods coincided.
🤖 Generated with Claude Code