nx_billing_lib.nx source
↩ module page · 95 lines · 4193 B
1// nx_billing_lib.nx -- Nishi Office: TIME & BILLING (F506-adjacent) core lib. Billable time
2// entries per matter on the immutable CID plane, aggregated into an invoice, then earned fees
3// applied FROM the IOLTA trust under Utah RPC 1.15. Legal-SOTA: (1) integer CENTS, no float
4// (amount = centihours*rate_cents/100, exact); (2) RPC 1.15 fee application -- earned fees move
5// trust->operating ONLY up to the invoice AND never exceeding the trust balance (NO-OVERDRAW,
6// reuses trust_disburse_ok); (3) append-only ledger (history sacred). Composes nx_trust_lib
7// (which pulls nx_matter_lib + registry + canon). LIB (no main). license_tier: ORIGINAL
8
9import "nx_trust_lib.nx"
10const K_MAGIC_1024: i64 = 1024
11
12// record a billable time entry for a matter; key "<matter>:<seq>". amount = centihours*rate/100
13// (centihours = hours*100; rate_cents = $/hr in cents). Returns the entry amount in cents.
14func bl_time_put(prefix: *u8, matter: *u8, seq: i64, centihours: i64, rate_cents: i64, desc: *u8) -> i64 {
15 let amount: i64 = centihours * rate_cents / 100
16 let id: *u8 = sys_mmap(128)
17 var o: i64 = mt_catcopy(id, 0, matter)
18 id[o] = 58 as u8
19 o = o + 1
20 o = mt_catn(id, o, seq)
21 id[o] = 0 as u8
22 let amt: *u8 = sys_mmap(32)
23 var ao: i64 = mt_catn(amt, 0, amount)
24 amt[ao] = 0 as u8
25 let k: *i64 = sys_mmap(8 * 3) as *i64
26 let v: *i64 = sys_mmap(8 * 3) as *i64
27 k[0] = ("amount" as *u8) as i64
28 v[0] = amt as i64
29 k[1] = ("matter" as *u8) as i64
30 v[1] = matter as i64
31 k[2] = ("desc" as *u8) as i64
32 v[2] = desc as i64
33 let rec: *u8 = sys_mmap(K_MAGIC_1024)
34 let rl: i64 = canon_encode(k, v, 3, rec)
35 reg_put(prefix, "time:" as *u8, "time:__idx__" as *u8, id, rec, rl)
36 return amount
37}
38
39// sum all billable time for a matter (the invoice total, cents). Filters the time index by
40// the "<matter>:" id prefix -- per-matter isolation, same pattern as trust_balance.
41func bl_matter_total(prefix: *u8, matter: *u8) -> i64 {
42 // SIZE-TO-NEED: buffer derived from the index itself. The pair this replaces
43 // returned -1 once the time index passed 1 MiB, and -1 skips the walk below
44 // -- a matter would have billed ZERO because it had too many time entries.
45 let idxbox: *i64 = sys_mmap(16) as *i64
46 let ilen: i64 = reg_index_read(prefix, "time:__idx__" as *u8, idxbox)
47 let idx: *u8 = idxbox[0] as *u8
48 let mp: *u8 = sys_mmap(128)
49 var mpl: i64 = mt_catcopy(mp, 0, matter)
50 mp[mpl] = 58 as u8
51 mpl = mpl + 1
52 mp[mpl] = 0 as u8
53 let idb: *u8 = sys_mmap(256)
54 let po: *i64 = sys_mmap(16) as *i64
55 let lo: *i64 = sys_mmap(16) as *i64
56 let af: *u8 = sys_mmap(64)
57 var total: i64 = 0
58 var ls: i64 = 0
59 var i: i64 = 0
60 while i <= ilen {
61 var eol: i64 = 0
62 if i == ilen { eol = 1 }
63 if i < ilen { if idx[i] == 10 as u8 { eol = 1 } }
64 if eol == 1 {
65 if i > ls {
66 var c: i64 = 0
67 while ls + c < i { idb[c] = idx[ls + c]; c = c + 1 }
68 idb[c] = 0 as u8
69 var mtch: i64 = 1
70 var x: i64 = 0
71 while mp[x] != (0 as u8) { if idb[x] != mp[x] { mtch = 0 } x = x + 1 }
72 if mtch == 1 {
73 if reg_get(prefix, "time:" as *u8, idb, po, lo) == 1 {
74 mt_field(po[0] as *u8, lo[0], "amount" as *u8, 6, af)
75 total = total + tr_atoi(af)
76 }
77 }
78 }
79 ls = i + 1
80 }
81 i = i + 1
82 }
83 return total
84}
85
86// RPC 1.15 invoice + fee application: bill the matter's total from the client's IOLTA trust.
87// Returns the applied cents on success; -1 if the trust balance can't cover it (NO-OVERDRAW,
88// REFUSED -- fees stay unearned/uncollected, trust untouched); 0 if nothing to bill.
89func bl_bill_from_trust(prefix: *u8, client: *u8, matter: *u8, trustprefix: *u8, seq: i64) -> i64 {
90 let total: i64 = bl_matter_total(prefix, matter)
91 if total <= 0 { return 0 }
92 if trust_disburse_ok(trustprefix, client, total) == 0 { return 0 - 1 }
93 trust_put(trustprefix, client, seq, matter, "disburse" as *u8, total)
94 return total
95}