nx_billing_lib.nx source
↩ module page · 92 lines · 3961 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
11const K_MAGIC_1048576: i64 = 1048576
12
13// record a billable time entry for a matter; key "<matter>:<seq>". amount = centihours*rate/100
14// (centihours = hours*100; rate_cents = $/hr in cents). Returns the entry amount in cents.
15func bl_time_put(prefix: *u8, matter: *u8, seq: i64, centihours: i64, rate_cents: i64, desc: *u8) -> i64 {
16 let amount: i64 = centihours * rate_cents / 100
17 let id: *u8 = sys_mmap(128)
18 var o: i64 = mt_catcopy(id, 0, matter)
19 id[o] = 58 as u8
20 o = o + 1
21 o = mt_catn(id, o, seq)
22 id[o] = 0 as u8
23 let amt: *u8 = sys_mmap(32)
24 var ao: i64 = mt_catn(amt, 0, amount)
25 amt[ao] = 0 as u8
26 let k: *i64 = sys_mmap(8 * 3) as *i64
27 let v: *i64 = sys_mmap(8 * 3) as *i64
28 k[0] = ("amount" as *u8) as i64
29 v[0] = amt as i64
30 k[1] = ("matter" as *u8) as i64
31 v[1] = matter as i64
32 k[2] = ("desc" as *u8) as i64
33 v[2] = desc as i64
34 let rec: *u8 = sys_mmap(K_MAGIC_1024)
35 let rl: i64 = canon_encode(k, v, 3, rec)
36 reg_put(prefix, "time:" as *u8, "time:__idx__" as *u8, id, rec, rl)
37 return amount
38}
39
40// sum all billable time for a matter (the invoice total, cents). Filters the time index by
41// the "<matter>:" id prefix -- per-matter isolation, same pattern as trust_balance.
42func bl_matter_total(prefix: *u8, matter: *u8) -> i64 {
43 let idx: *u8 = sys_mmap(K_MAGIC_1048576)
44 let ilen: i64 = reg_index(prefix, "time:__idx__" as *u8, idx, K_MAGIC_1048576)
45 let mp: *u8 = sys_mmap(128)
46 var mpl: i64 = mt_catcopy(mp, 0, matter)
47 mp[mpl] = 58 as u8
48 mpl = mpl + 1
49 mp[mpl] = 0 as u8
50 let idb: *u8 = sys_mmap(256)
51 let po: *i64 = sys_mmap(16) as *i64
52 let lo: *i64 = sys_mmap(16) as *i64
53 let af: *u8 = sys_mmap(64)
54 var total: i64 = 0
55 var ls: i64 = 0
56 var i: i64 = 0
57 while i <= ilen {
58 var eol: i64 = 0
59 if i == ilen { eol = 1 }
60 if i < ilen { if idx[i] == 10 as u8 { eol = 1 } }
61 if eol == 1 {
62 if i > ls {
63 var c: i64 = 0
64 while ls + c < i { idb[c] = idx[ls + c]; c = c + 1 }
65 idb[c] = 0 as u8
66 var mtch: i64 = 1
67 var x: i64 = 0
68 while mp[x] != (0 as u8) { if idb[x] != mp[x] { mtch = 0 } x = x + 1 }
69 if mtch == 1 {
70 if reg_get(prefix, "time:" as *u8, idb, po, lo) == 1 {
71 mt_field(po[0] as *u8, lo[0], "amount" as *u8, 6, af)
72 total = total + tr_atoi(af)
73 }
74 }
75 }
76 ls = i + 1
77 }
78 i = i + 1
79 }
80 return total
81}
82
83// RPC 1.15 invoice + fee application: bill the matter's total from the client's IOLTA trust.
84// Returns the applied cents on success; -1 if the trust balance can't cover it (NO-OVERDRAW,
85// REFUSED -- fees stay unearned/uncollected, trust untouched); 0 if nothing to bill.
86func bl_bill_from_trust(prefix: *u8, client: *u8, matter: *u8, trustprefix: *u8, seq: i64) -> i64 {
87 let total: i64 = bl_matter_total(prefix, matter)
88 if total <= 0 { return 0 }
89 if trust_disburse_ok(trustprefix, client, total) == 0 { return 0 - 1 }
90 trust_put(trustprefix, client, seq, matter, "disburse" as *u8, total)
91 return total
92}