code wiki / (root) / nx_billing_lib.nx

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}