code wiki / (root) / nx_billing_lib.nx

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}