1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//! Budget-control port for the GL-posting chokepoint.
//!
//! Accounting owns the posting chokepoint but cannot know budget coverage: a
//! budget is a plan owned by the budget module (positions keyed account x
//! cost center x fiscal period), while the posting contract sees only lines.
//! The deployed app sees both modules and implements this port — a host-side
//! ACL, not a module edge. `None` (unwired) simply means no budget check:
//! accounting keeps working for hosts without a budget module.
//!
//! Wiring follows the deferred-tax lookup pattern: the port is Option-wired
//! through the module builder, the default is absent, and hosts that compose
//! a budget module inject an adapter. One deliberate deviation from the
//! ReconcileSink/DeferredTaxLookup shape is documented on the trait: this
//! port is NOT conn-taking. Posting validation holds no connection (the
//! `PostingService` is pool-free; persistence sits behind the repository
//! port) and the control is a snapshot read, not a write that must commit
//! atomically with the caller's transaction. The check-then-post race is
//! accepted snapshot semantics — two concurrent in-budget postings may
//! jointly overshoot a budget, the same class as any pre-commit validation
//! read. If that ever becomes unacceptable the check must move inside the
//! posting commit transaction, which is a repository-layer change, not a
//! port-signature change.
use async_trait;
use NaiveDate;
use Decimal;
use Uuid;
use cratePostingLine;
/// What the budget says to do when a position is exceeded. Local to accounting
/// so this crate never imports the budget module's enum; adapters map values.
/// One budget position this posting would push over its plan.
/// Host-implemented budget control for the posting chokepoint.
///
/// Contract:
/// - Every read is scoped to `company_id`. That key arrives as the posting's legacy tenancy
/// twin (ADR-0029): accounting itself carries no tenancy, so it forwards the
/// producer-supplied company id unchanged and the host adapter owns the scoping of its own
/// budget reads.
/// - Lines are matched by exact `(account, cost_center, period-covering
/// posting_date)` key; a `None` cost center matches only positions whose
/// cost center is also `None` (never an aggregate rollup).
/// - Only confirmed/active budgets participate; draft, closed, or cancelled
/// plans are invisible to the control.
/// - Achieved amounts are committed ledger movements through the posting
/// date, oriented by the account's normal balance; reversals reduce them.
/// - An empty result means within budget or no coverage — the posting
/// proceeds. An `Err` means the check itself is broken; the chokepoint
/// treats that as an internal failure and refuses the posting (a wired
/// budget module must not silently disable enforcement).