a82f126031
* docs(bookkeeping): audit + runbook for template-caused mis-bookings Two of the template defects fixed this week produced postings that SUCCEEDED and are still sitting in customers' huvudbocker: travel_hotel debited 5820 Hyrbilskostnader instead of 5830 Kost och logi (#1397), and the representation template deducted 25% input VAT on a 12% restaurang supply (#1396). Fixing a template only changes future postings. Follows the pattern already established by SETTLEMENT_ACCOUNT_REMEDIATION.md for the same class of problem: read-only detection, per-entry evidence review, staged storno with explicit approval, no automated bulk mutation. Deliberately excludes vehicle_parking (5614) and it_cloud_hosting (5421). Those named accounts that never existed in BAS, so account-backfill could not seed them and every booking failed. Nothing was posted, nothing to remediate. Detection is by account signature and is diagnostic only, because there is no provenance link from a posted entry back to the template that produced it: template_id lives on mapping_rules, not on journal entries. Both signatures have legitimate shapes (5820 IS correct for real car hire; representation at 25% IS lawful when the supplier charged 25%), so a row is a question and never a verdict. The classifier is verified against seeded probes rather than assumed: a hotel booked to 5820 with a hotel counterparty ranks high, a genuine car hire on 5820 falls to manual review, a 25% representation ranks high, and a correct 12% representation does not appear at all. Query confirmed to run against the real schema (the lock date lives on company_settings, not companies). The runbook records what BFL 5 kap 5 § actually requires: both tracks, that storno is the only one available once a period is locked or the bookkeeping has been relied upon, and that there is NO numeric materiality threshold in BFL. Materiality decides whether a historical correction is worth making, never whether a silent one is allowed. For the VAT defect it also flags that a filed momsdeklaration makes this an omprovning question, not just a ledger one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(bookkeeping): harden template misbooking audit * fix(bookkeeping): retain mixed voucher audit candidates --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Emil <emilmattsson14@gmail.com>
69 lines
3.0 KiB
SQL
69 lines
3.0 KiB
SQL
-- Read-only classifier probes for scripts/audit-template-misbookings.sql.
|
|
-- Expected result: four rows, each with actual_result = expected_result.
|
|
|
|
with probe_lines(probe, account_number, debit_amount) as (
|
|
values
|
|
('hotel_on_5820', '5820', 1000.00::numeric),
|
|
('hotel_on_5820', '2641', 120.00::numeric),
|
|
('car_hire_on_5820', '5820', 1000.00::numeric),
|
|
('car_hire_on_5820', '2641', 250.00::numeric),
|
|
('representation_25_multi_line', '6072', 60.00::numeric),
|
|
('representation_25_multi_line', '6072', 40.00::numeric),
|
|
('representation_25_multi_line', '2641', 10.00::numeric),
|
|
('representation_25_multi_line', '2641', 15.00::numeric),
|
|
('representation_25_with_extra_vat', '6072', 100.00::numeric),
|
|
('representation_25_with_extra_vat', '2641', 25.00::numeric),
|
|
('representation_25_with_extra_vat', '2641', 20.00::numeric),
|
|
('representation_12', '6072', 100.00::numeric),
|
|
('representation_12', '2641', 12.00::numeric)
|
|
),
|
|
probe_context(probe, transaction_description, expected_result) as (
|
|
values
|
|
('hotel_on_5820', 'Scandic Stockholm', 'high_hotel_counterparty_on_car_hire_account'),
|
|
('car_hire_on_5820', 'Hertz rental car', 'manual_review_5820_no_corroborating_signal'),
|
|
('representation_25_multi_line', 'Customer dinner', 'high_vat_is_25pct_of_6072_cost'),
|
|
('representation_25_with_extra_vat', 'Mixed expense voucher', 'manual_review_6072_with_vat'),
|
|
('representation_12', 'Customer dinner', 'manual_review_6072_with_vat')
|
|
),
|
|
line_groups as (
|
|
select probe, account_number, sum(debit_amount) as debit_amount
|
|
from probe_lines
|
|
group by probe, account_number
|
|
),
|
|
classified as (
|
|
select
|
|
context.probe,
|
|
context.expected_result,
|
|
case
|
|
when hotel.debit_amount is not null
|
|
and context.transaction_description ~* '(hotel|hotell|scandic|elite|best western|nordic choice|clarion|quality inn|radisson|booking\.com|airbnb|logi|övernattning)'
|
|
then 'high_hotel_counterparty_on_car_hire_account'
|
|
when hotel.debit_amount is not null
|
|
and abs(coalesce(vat.debit_amount, 0) - hotel.debit_amount * 0.12) < 0.02
|
|
then 'medium_12pct_vat_on_car_hire_account'
|
|
when hotel.debit_amount is not null
|
|
then 'manual_review_5820_no_corroborating_signal'
|
|
when representation.debit_amount is not null
|
|
and abs(coalesce(vat.debit_amount, 0) - representation.debit_amount * 0.25) < 0.02
|
|
then 'high_vat_is_25pct_of_6072_cost'
|
|
when representation.debit_amount is not null and vat.debit_amount is not null
|
|
then 'manual_review_6072_with_vat'
|
|
else 'absent'
|
|
end as actual_result
|
|
from probe_context context
|
|
left join line_groups hotel
|
|
on hotel.probe = context.probe and hotel.account_number = '5820'
|
|
left join line_groups representation
|
|
on representation.probe = context.probe and representation.account_number = '6072'
|
|
left join line_groups vat
|
|
on vat.probe = context.probe and vat.account_number = '2641'
|
|
)
|
|
|
|
select
|
|
probe,
|
|
expected_result,
|
|
actual_result,
|
|
actual_result = expected_result as passed
|
|
from classified
|
|
order by probe;
|