@hyperscale0/hsx 1.0.128 → 1.0.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli.js +1 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +173 -23
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +6 -6
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/parse.js +4 -1
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.js +10 -10
- package/dist/src/std-bundle.js.map +1 -1
- package/docs/README.md +78 -27
- package/examples/cards.hsx +7 -1
- package/examples/collections.hsx +4 -0
- package/examples/cost-table.json +80 -56
- package/examples/insurance.hsx +2 -1
- package/examples/marketplace.hsx +3 -1
- package/examples/rental-deposit.hsx +2 -0
- package/examples/wallet.hsx +1 -1
- package/package.json +4 -4
- package/src/cli.ts +1 -1
- package/src/compile.ts +246 -23
- package/src/examples-bundle.ts +6 -6
- package/src/parse.ts +4 -1
- package/src/std-bundle.ts +10 -10
- package/std/booking.hsx +1 -1
- package/std/cards.hsx +20 -11
- package/std/escrow.hsx +45 -10
- package/std/financing.hsx +214 -62
- package/std/insurance.hsx +3 -3
- package/std/lending.hsx +16 -8
- package/std/marketplace.hsx +4 -3
- package/std/money.hsx +28 -14
- package/std/savings.hsx +3 -2
- package/std/wallet.hsx +13 -8
package/std/financing.hsx
CHANGED
|
@@ -18,13 +18,56 @@ header financing
|
|
|
18
18
|
// Disburse first credits an active marketplace.reservation on the same hold,
|
|
19
19
|
// so list the plan in its converters; the deposit then counts as escrow credit.
|
|
20
20
|
// disburse_to: seller pays principal to the seller party at activation (pay later
|
|
21
|
-
// with the merchant paid on day one).
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// plan
|
|
27
|
-
//
|
|
21
|
+
// with the merchant paid on day one). Capital may refund any paid payment to
|
|
22
|
+
// the borrower. Payments stay refundable while the plan is active. The
|
|
23
|
+
// payment that clears the last slice completes the plan when no late charge or
|
|
24
|
+
// recovery cost is outstanding; otherwise expose complete to close it once they
|
|
25
|
+
// are settled or waived. When
|
|
26
|
+
// the plan leaves active (paid, written off or cancelled) it sets refundUntil to
|
|
27
|
+
// refund_within (default 30d) later, and payment, late charge and recovery cost
|
|
28
|
+
// refunds stop at that time. A refund that reopens the plan clears refundUntil, which is set
|
|
29
|
+
// from the calculated refundWindow so a restore can clear it. Once it passes,
|
|
30
|
+
// nothing can move the plan's payments or late charges, so they stop counting as
|
|
31
|
+
// open records. On a return, capital runs unwind: it takes the outstanding
|
|
32
|
+
// principal back to capital and cancels open slices with their claims. Seller
|
|
33
|
+
// mode debits the seller, who also returns the principal the borrower repaid;
|
|
34
|
+
// funds mode needs the hold in return_verified and debits the hold, after which
|
|
35
|
+
// the payee's escrow refund returns the down payment and the repaid principal.
|
|
36
|
+
// Before delivery, the Product
|
|
37
|
+
// operator runs unwind_undelivered on a funds-mode plan whose hold is still
|
|
38
|
+
// funded: principal goes back to capital and the hold's unwind_financed returns
|
|
39
|
+
// the down payment to the payer and closes the hold as refunded, all in one
|
|
40
|
+
// operation, so deliver is refused afterwards. It also cancels proposed and
|
|
41
|
+
// assessed late charges and recovery costs: goods that never arrived earn no
|
|
42
|
+
// fines. Escrow refund refuses while any plan that is
|
|
43
|
+
// not cancelled funds the hold, written off included, so the borrower never
|
|
44
|
+
// receives lender principal. A funds-mode plan writes off only once its hold
|
|
45
|
+
// has released principal to the seller: principal still held is not a loss, and
|
|
46
|
+
// unwind or unwind_undelivered returns it. unwind_undelivered needs no paid
|
|
47
|
+
// payments and no collected late charges; refund them first. unwind needs no
|
|
48
|
+
// assessed late charges; waive them first. When goods never arrive on a paid plan, the
|
|
49
|
+
// Product operator runs unwind_paid once the hold's deliverBy has passed, so an
|
|
50
|
+
// early payoff never cuts the seller's delivery window short. It refunds every
|
|
51
|
+
// payment, share, profit piece, fine and cost receipt and reverses the payoff's
|
|
52
|
+
// profit, fines and costs, cancels the plan, and returns the hold's whole price
|
|
53
|
+
// to the borrower; capital keeps the principal the borrower repaid. A settlement
|
|
54
|
+
// a lending.distribution has paid out keeps its profit and share, since investors
|
|
55
|
+
// hold that profit; only the other settlements' profit is refunded. unwind_paid
|
|
56
|
+
// selects at most max_history (default and most 366) of each record kind, so
|
|
57
|
+
// a payment refuses when it would leave more paid payments than max_history
|
|
58
|
+
// less one, keeping a settlement for the payoff, or more paid profit pieces,
|
|
59
|
+
// fine receipts or cost receipts than max_history; a fine or a recovery cost
|
|
60
|
+
// refuses past max_history unwaived fines or costs on the plan, counted across
|
|
61
|
+
// every late charge attachment, so a waived fine's cost keeps its own slot. A company lowers it to keep unwinds smaller. If any
|
|
62
|
+
// source account is short, the whole action refuses and nothing moves; a
|
|
63
|
+
// company that wants a partial refund writes that rule in its own program.
|
|
64
|
+
// returned_goods decides the split on returned goods, before or after payoff.
|
|
65
|
+
// keep_profit, the default, keeps the profit and fines paid so far with the
|
|
66
|
+
// company: unwind runs with payments still paid, and on a paid plan the Product
|
|
67
|
+
// operator runs return_hold after the refund window to return a return_verified
|
|
68
|
+
// hold's whole balance. refund_all makes unwind wait until capital has refunded
|
|
69
|
+
// every payment and collected charge, removes return_hold, and lets unwind_paid
|
|
70
|
+
// settle returned goods too.
|
|
28
71
|
// Borrower- and seller-directed disbursement exclude collect_down_payment. A down_payment
|
|
29
72
|
// there reduces principal but does not collect cash. Price is subject metadata,
|
|
30
73
|
// not the borrower limit. The escrow payer and price must match the plan.
|
|
@@ -37,17 +80,20 @@ header financing
|
|
|
37
80
|
// Expose late_charge.create as propose_assessment before the clock can assess;
|
|
38
81
|
// provide matching plan, slice and borrower. Fine is fixed money, not a percentage.
|
|
39
82
|
// Collection uses the minimum of payment cash and outstanding fine; a partial
|
|
40
|
-
// payment does not settle the remaining fine. Recovery cost needs its own record
|
|
83
|
+
// payment does not settle the remaining fine. Recovery cost needs its own record,
|
|
84
|
+
// which only the Product operator creates; only it cancels a proposed fine or cost.
|
|
41
85
|
// Profit share credits capital's allocatedProfitPayable, not an outside agency.
|
|
42
86
|
// Credit lines need adapter binding at activation; draws are ledger advances,
|
|
43
|
-
// not evidence of outside funding.
|
|
44
|
-
//
|
|
87
|
+
// not evidence of outside funding. Only the line's borrower, the Product operator
|
|
88
|
+
// by default, opens and closes the line and creates, draws, repays or cancels an
|
|
89
|
+
// advance on it. Only the Product operator creates the portfolio limit and
|
|
90
|
+
// approves borrower and portfolio limits. No clock declaration proves retries.
|
|
45
91
|
|
|
46
92
|
// Cost: installments include cash and claim moves, child-account creation and
|
|
47
93
|
// bounded invocations across slices and assessments. Month and charge limits affect
|
|
48
94
|
// the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
|
|
49
95
|
|
|
50
|
-
instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_total), profit_rate: percent, charge_limit: integer(1, 3) = 3, disburse_to: enum(funds, borrower, seller) = funds, seller: party?, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, write_off_after: duration = 90d, max_extension: duration = 90d, max_amendments: integer(0, 366) = 2, waiver_limit: percent = 100%, amendment_expiry: duration = 7d, payoff_quote_expiry: duration = 1h, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref<escrow.hold>?, borrower: party = party(person), capital: party = programOperator, share: percent = 0%, limits: ref<financing.limits> = object(financing.limits), portfolio: ref<financing.portfolio_limit> = object(financing.portfolio_limit)) {
|
|
96
|
+
instrument installments(months: integer(1, 366), max_history: integer(2, 366) = 366, pricing: enum(amortizing, flat_total), profit_rate: percent, charge_limit: integer(1, 3) = 3, disburse_to: enum(funds, borrower, seller) = funds, seller: party?, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, write_off_after: duration = 90d, max_extension: duration = 90d, max_amendments: integer(0, 366) = 2, waiver_limit: percent = 100%, amendment_expiry: duration = 7d, payoff_quote_expiry: duration = 1h, refund_within: duration = 30d, returned_goods: enum(keep_profit, refund_all) = keep_profit, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref<escrow.hold>?, borrower: party = party(person), capital: party = programOperator, share: percent = 0%, limits: ref<financing.limits> = object(financing.limits), portfolio: ref<financing.portfolio_limit> = object(financing.portfolio_limit)) {
|
|
51
97
|
dependencies {
|
|
52
98
|
funds {
|
|
53
99
|
selector: disburse_to, is: funds
|
|
@@ -65,9 +111,10 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
65
111
|
summary: "Funding, profit recognition and payment order chosen by the program."
|
|
66
112
|
fields {
|
|
67
113
|
funds: ref<funds>?, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>
|
|
68
|
-
months: integer = months, dates: list(date, months)
|
|
114
|
+
months: integer = months, dates: list(date, months), historyLimit: integer = max_history, paymentLimit: integer = subtract(max_history, 1)
|
|
69
115
|
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), loss: account(capital, claim, "loss")
|
|
70
|
-
downPaymentRemaining: money?, downPaymentCredit: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?
|
|
116
|
+
downPaymentRemaining: money?, downPaymentCredit: money?, principalOutstanding: money?, principalRepaid: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?, refundWindow: date?, refundUntil: date?
|
|
117
|
+
openSlices: integer?, openCharges: money?, openCosts: money?, payoffRefunds: integer?
|
|
71
118
|
price: money = subject.price
|
|
72
119
|
downPayment: money = rate(self.price, down_payment)
|
|
73
120
|
principal: money = subtract(self.price, self.downPayment)
|
|
@@ -79,6 +126,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
79
126
|
invariants sum "principalReceivable.balance" of { instrument: all(financing.installments.slice), reference: "portfolioLimit", anchor: self.portfolioLimit, states: [pending, due, paid], limit: 366 } <= self.portfolioLimit.limit
|
|
80
127
|
action create {
|
|
81
128
|
subject { price: money }
|
|
129
|
+
actor: { party: borrower }
|
|
82
130
|
requires self.borrowerLimit.borrower == self.borrower
|
|
83
131
|
when pricing is flat_total { calculate: [{ target: flatProfit, op: rate, base: { field: self.principal }, bps: { literal: profit_rate }, rounding: floor }] }
|
|
84
132
|
when disburse_to is funds { requires self.borrower == self.funds.payer; requires self.price == self.funds.price }
|
|
@@ -121,11 +169,13 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
121
169
|
}
|
|
122
170
|
action complete {
|
|
123
171
|
from: active, to: paid
|
|
172
|
+
calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
173
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
124
174
|
requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 } == 0
|
|
125
175
|
requires sum "receivable.balance" of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
126
176
|
requires sum "receivable.balance" of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
127
177
|
}
|
|
128
|
-
action restore { from: [active, paid], to: active, actor: { parent: payment } }
|
|
178
|
+
action restore { from: [active, paid], to: active, actor: { parent: payment }, input { refundUntil: date? }, set: { refundUntil: { field: input.refundUntil } } }
|
|
129
179
|
action payoff {
|
|
130
180
|
from: active, to: paid, actor: { party: borrower }
|
|
131
181
|
input { quote: ref<payoff_quote> }
|
|
@@ -133,7 +183,9 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
133
183
|
deadline: { at: input.quote.expiresAt }
|
|
134
184
|
calculate: [
|
|
135
185
|
{ target: "principalReturned", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
186
|
+
{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }
|
|
136
187
|
]
|
|
188
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
137
189
|
invoke: [{ selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: payoff, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: payoff, input: {} }]
|
|
138
190
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: payoff, input: { identity: { field: self.settlementIdentity } } }, { reference: self.id, action: record_payoff, input: {} }]
|
|
139
191
|
}
|
|
@@ -145,28 +197,95 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
145
197
|
action write_off {
|
|
146
198
|
from: active, to: written_off, actor: { parent: write_off_request }
|
|
147
199
|
input { overdueSlice: ref<slice> }
|
|
148
|
-
calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }]
|
|
200
|
+
calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }, { target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
201
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
149
202
|
due: { at: input.overdueSlice.lossEligibleAt }
|
|
150
203
|
requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > "0"
|
|
204
|
+
when disburse_to is funds { requires self.funds in [released] }
|
|
151
205
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: write_off, input: {} }, { instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: self.settlementIdentity }, principal: { literal: "0" }, profit: { literal: "0" }, loss: { field: self.lossRecorded }, kind: { literal: "loss" } } }]
|
|
152
206
|
}
|
|
153
207
|
action record_payment {
|
|
154
208
|
from: active, to: active, actor: { parent: payment }
|
|
155
209
|
input { identity: text, principal: money, profit: money }
|
|
210
|
+
calculate: [
|
|
211
|
+
{ target: openSlices, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: count }
|
|
212
|
+
{ target: openCharges, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
213
|
+
{ target: openCosts, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
214
|
+
]
|
|
156
215
|
invoke: [{ instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: input.identity }, principal: { field: input.principal }, profit: { field: input.profit }, loss: { literal: "0" }, kind: { literal: "cash" } } }]
|
|
216
|
+
invoke: [{ selection: { instrument: current(), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [active], limit: 1, where: { id: { field: self.id }, openSlices: { literal: 0 }, openCharges: { literal: "0" }, openCosts: { literal: "0" } } }, action: complete, input: {} }]
|
|
157
217
|
}
|
|
158
218
|
action cancel { from: quoted, to: cancelled }
|
|
159
219
|
action void { from: signed, to: cancelled, actor: { party: borrower } }
|
|
160
220
|
action unwind { allowZero: true,
|
|
161
221
|
from: active, to: cancelled, actor: { party: capital }
|
|
222
|
+
calculate: [
|
|
223
|
+
{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }
|
|
224
|
+
{ target: principalOutstanding, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
225
|
+
{ target: principalRepaid, op: subtract, base: { field: self.principal }, subtract: [{ field: self.principalOutstanding }] }
|
|
226
|
+
]
|
|
227
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
162
228
|
when disburse_to is borrower { requires true == false }
|
|
163
229
|
when disburse_to is funds { requires self.funds in [return_verified] }
|
|
230
|
+
when returned_goods is refund_all {
|
|
231
|
+
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
232
|
+
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
233
|
+
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
234
|
+
}
|
|
235
|
+
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed], limit: 366 } == 0
|
|
236
|
+
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed], limit: 366 } == 0
|
|
237
|
+
when disburse_to is funds { moves self.principalOutstanding from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
|
|
238
|
+
when disburse_to is seller {
|
|
239
|
+
moves self.principalOutstanding from seller to self.capital economics { purpose: principal, sourceParty: seller }
|
|
240
|
+
moves self.principalRepaid from seller to self.borrower economics { purpose: principal, sourceParty: seller }
|
|
241
|
+
}
|
|
242
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
243
|
+
}
|
|
244
|
+
action unwind_undelivered { allowZero: true,
|
|
245
|
+
from: active, to: cancelled, actor: { party: programOperator }
|
|
246
|
+
calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
247
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
248
|
+
when disburse_to is borrower { requires true == false }
|
|
249
|
+
when disburse_to is seller { requires true == false }
|
|
250
|
+
when disburse_to is funds { requires self.funds in [funded] }
|
|
164
251
|
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
165
|
-
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [
|
|
166
|
-
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [
|
|
252
|
+
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
253
|
+
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
167
254
|
when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
|
|
168
|
-
when disburse_to is seller { moves self.principal from seller to self.capital economics { purpose: principal, sourceParty: seller } }
|
|
169
255
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
256
|
+
invoke: [{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [proposed, assessed], limit: 366 }, action: cancel_undelivered, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [proposed, assessed], limit: 366 }, action: cancel_undelivered, input: {} }]
|
|
257
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: unwind_financed, input: {} }] }
|
|
258
|
+
}
|
|
259
|
+
action return_hold {
|
|
260
|
+
from: paid, to: paid, actor: { party: programOperator }
|
|
261
|
+
requires self.refundUntil < self.now
|
|
262
|
+
when disburse_to is borrower { requires true == false }
|
|
263
|
+
when disburse_to is seller { requires true == false }
|
|
264
|
+
when returned_goods is refund_all { requires true == false }
|
|
265
|
+
when disburse_to is funds {
|
|
266
|
+
requires self.funds in [return_verified]
|
|
267
|
+
invoke: [{ reference: self.funds, action: unwind_financed, input: {} }]
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
action unwind_paid { expansionLimit: 8192,
|
|
271
|
+
from: paid, to: cancelled, actor: { party: programOperator }
|
|
272
|
+
when disburse_to is borrower { requires true == false }
|
|
273
|
+
when disburse_to is seller { requires true == false }
|
|
274
|
+
when disburse_to is funds {
|
|
275
|
+
when returned_goods is keep_profit { requires self.funds in [funded] }
|
|
276
|
+
when returned_goods is refund_all { requires self.funds in [funded, return_verified] }
|
|
277
|
+
requires self.funds.deliverBy < self.now
|
|
278
|
+
}
|
|
279
|
+
invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.id, states: [recorded], limit: max_history }, action: unwind, input: {} }, { selection: { instrument: profit_piece, reference: "plan", anchor: self.id, states: [paid], limit: max_history, where: { payment.status: { literal: "refunded" } } }, action: refund, input: {} }, { selection: { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: unwind, input: {} }, { reference: self.id, action: unwind_payoff_profit, input: {} }]
|
|
280
|
+
invoke: [{ selection: { instrument: all(financing.late_charge.receipt), reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: refund, input: {} }, { selection: { instrument: all(financing.late_charge.cost.receipt), reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: refund, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: max_history }, action: unwind_payoff, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: max_history }, action: unwind_payoff, input: {} }]
|
|
281
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due, paid], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
282
|
+
invoke: [{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [proposed, assessed, collected], limit: max_history }, action: cancel_undelivered, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [proposed, assessed, collected], limit: max_history }, action: cancel_undelivered, input: {} }]
|
|
283
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: unwind_financed, input: {} }] }
|
|
284
|
+
}
|
|
285
|
+
action unwind_payoff_profit {
|
|
286
|
+
from: cancelled, to: cancelled, actor: { parent: current() }
|
|
287
|
+
calculate: [{ target: payoffRefunds, op: aggregate, selection: { instrument: settlement, reference: "plan", anchor: self.id, states: [refunded], limit: 1, where: { identity: { field: self.settlementIdentity } } }, measure: count }]
|
|
288
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [paid], limit: 366, where: { payoffIdentity: { field: self.settlementIdentity } } }, action: unwind_payoff, guard: { kind: compare, left: { field: self.payoffRefunds }, operator: "==", right: { literal: 1 } }, input: {} }]
|
|
170
289
|
}
|
|
171
290
|
records {
|
|
172
291
|
write_off_request: {
|
|
@@ -243,7 +362,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
243
362
|
set: { acceptedAt: { field: self.now } }
|
|
244
363
|
when assessed_fines is require_waiver { requires self.waiverPercent == 10000 }
|
|
245
364
|
invoke: [{ selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, action: waive_for_amendment, input: { amendment: { field: self.id } } }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, action: waive_for_amendment, input: { amendment: { field: self.id } } }]
|
|
246
|
-
invoke: [{ selection: { instrument: all(collections.case), reference: "plan", anchor: self.plan, states: [opened, assigned], limit: 366 }, action: recall, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [proposed], limit: 366 }, action:
|
|
365
|
+
invoke: [{ selection: { instrument: all(collections.case), reference: "plan", anchor: self.plan, states: [opened, assigned], limit: 366 }, action: recall, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [proposed], limit: 366 }, action: cancel_for_amendment, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [proposed], limit: 366 }, action: cancel_for_amendment, input: {} }]
|
|
247
366
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: amend, input: { amendment: { field: self.id } } }]
|
|
248
367
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [paid], limit: 366 }, action: verify_fixed_date, input: { amendment: { field: self.id } } }]
|
|
249
368
|
}
|
|
@@ -266,7 +385,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
266
385
|
settlement: {
|
|
267
386
|
fields { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss), share: money?, retainedProfit: money?
|
|
268
387
|
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), lossAccount: account(capital, claim, "loss") }
|
|
269
|
-
lifecycle { states: [recorded, refunded], initial: recorded }
|
|
388
|
+
lifecycle { states: [recorded, distributed, refunded], initial: recorded }
|
|
270
389
|
action create { allowZero: true,
|
|
271
390
|
actor: { parent: parent }
|
|
272
391
|
input { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss) }
|
|
@@ -278,6 +397,16 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
278
397
|
from: recorded, to: refunded, actor: { parent: payment }
|
|
279
398
|
moves self.share from self.allocatedPayable to self.profitIncome economics { purpose: internal, sourceParty: capital }
|
|
280
399
|
}
|
|
400
|
+
action distribute { from: recorded, to: distributed, actor: { parent: all(lending.distribution) } }
|
|
401
|
+
// profitIncome is one account per capital party, so refunding profit a
|
|
402
|
+
// distribution already paid out would spend other plans' profit. unwind_paid
|
|
403
|
+
// unwinds only settlements still recorded; each marks its payment refunded,
|
|
404
|
+
// and the plan refunds the profit pieces and payoff profit behind them.
|
|
405
|
+
action unwind { allowZero: true,
|
|
406
|
+
from: recorded, to: refunded, actor: { parent: parent }
|
|
407
|
+
moves self.share from self.allocatedPayable to self.profitIncome economics { purpose: internal, sourceParty: capital }
|
|
408
|
+
invoke: [{ selection: { instrument: payment, reference: "plan", anchor: self.plan, states: [paid], limit: 1, where: { paymentIdentity: { field: self.identity } } }, action: unwind, input: {} }]
|
|
409
|
+
}
|
|
281
410
|
}
|
|
282
411
|
slice: {
|
|
283
412
|
revisioned: true
|
|
@@ -289,7 +418,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
289
418
|
profitBase: money?, profitAllocated: money?, profitRemainder: money?, profitAdjustment: money?
|
|
290
419
|
principal: money, profit: money, instalment: money
|
|
291
420
|
principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)
|
|
292
|
-
payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?
|
|
421
|
+
payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?, payoffReceipt: text?
|
|
293
422
|
paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit_rate)
|
|
294
423
|
debt: account(borrower, claim, contra, "debt"), loss: account(capital, claim, "loss")
|
|
295
424
|
}
|
|
@@ -378,7 +507,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
378
507
|
when apply is principal_profit {
|
|
379
508
|
invoke: [
|
|
380
509
|
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }
|
|
381
|
-
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
|
|
510
|
+
{ instrument: profit_piece, action: create, input: { plan: { field: self.plan }, slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
|
|
382
511
|
{ selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [pending, due], limit: 1, where: { id: { field: self.id }, principalReceivable.balance: { literal: "0" }, profitReceivable.balance: { literal: "0" }, profitEarned.balance: { literal: "0" } } }, action: complete, input: {} }
|
|
383
512
|
]
|
|
384
513
|
}
|
|
@@ -392,7 +521,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
392
521
|
invoke: [
|
|
393
522
|
{ selection: { instrument: all(financing.late_charge), reference: "slice", anchor: self.id, states: [assessed], limit: charge_limit, order: [overdueAt] }, action: collect, input: { payment: { field: input.payment } } }
|
|
394
523
|
{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed], limit: charge_limit, order: [overdueAt], where: { assessment.slice: { field: self.id } } }, action: collect, input: { payment: { field: input.payment } } }
|
|
395
|
-
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
|
|
524
|
+
{ instrument: profit_piece, action: create, input: { plan: { field: self.plan }, slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
|
|
396
525
|
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }
|
|
397
526
|
{ selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [pending, due], limit: 1, where: { id: { field: self.id }, principalReceivable.balance: { literal: "0" }, profitReceivable.balance: { literal: "0" }, profitEarned.balance: { literal: "0" } } }, action: complete, input: {} }
|
|
398
527
|
]
|
|
@@ -417,7 +546,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
417
546
|
]
|
|
418
547
|
invoke: [
|
|
419
548
|
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalPayment } } }
|
|
420
|
-
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }
|
|
549
|
+
{ instrument: profit_piece, action: create, input: { plan: { field: self.plan }, slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }
|
|
421
550
|
{ selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [pending, due], limit: 1, where: { id: { field: self.id }, principalReceivable.balance: { literal: "0" }, profitReceivable.balance: { literal: "0" }, profitEarned.balance: { literal: "0" } } }, action: complete, input: {} }
|
|
422
551
|
]
|
|
423
552
|
}
|
|
@@ -431,17 +560,21 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
431
560
|
set: { payoffIdentity: { field: input.identity } }
|
|
432
561
|
calculate: [{ target: payoffRebate, op: rate, base: { field: self.profitReceivable.balance }, bps: { literal: payoff_rebate }, rounding: floor }, { target: payoffProfitTotal, op: sum, values: [{ field: self.profitReceivable.balance }, { field: self.profitEarned.balance }] }, { target: payoffProfit, op: subtract, base: { field: self.payoffProfitTotal }, subtract: [{ field: self.payoffRebate }] }]
|
|
433
562
|
moves self.principalReceivable.balance from self.plan.borrower to self.plan.capital economics { purpose: principal, sourceParty: borrower }
|
|
434
|
-
moves self.payoffProfit from self.plan.borrower to self.plan.profitIncome economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
|
|
563
|
+
moves self.payoffProfit from self.plan.borrower to self.plan.profitIncome capture "payoffReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
|
|
435
564
|
moves self.principalReceivable.balance from self.principalReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
436
565
|
moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
437
566
|
moves self.profitEarned.balance from self.profitEarned to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
438
567
|
}
|
|
568
|
+
action unwind_payoff { allowZero: true,
|
|
569
|
+
from: paid, to: paid, actor: { parent: parent }
|
|
570
|
+
moves self.payoffProfit from self.plan.profitIncome to self.plan.borrower economics { recipient: capital, company: { purpose: earning, sourceParty: capital, reversalOf: self.payoffReceipt }, participant: unclassified }
|
|
571
|
+
}
|
|
439
572
|
action write_off { allowZero: true,
|
|
440
573
|
from: [pending, due], to: written_off, actor: { parent: parent }
|
|
441
574
|
moves self.principalReceivable.balance from self.principalReceivable to self.loss economics { purpose: internal, sourceParty: programOperator }; moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitEarned.balance from self.profitEarned to self.loss economics { purpose: internal, sourceParty: programOperator }
|
|
442
575
|
}
|
|
443
576
|
action cancel { allowZero: true,
|
|
444
|
-
from: [pending, due], to: cancelled, actor: { parent: parent }
|
|
577
|
+
from: [pending, due, paid], to: cancelled, actor: { parent: parent }
|
|
445
578
|
moves self.principalReceivable.balance from self.principalReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitEarned.balance from self.profitEarned to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
446
579
|
}
|
|
447
580
|
}
|
|
@@ -487,11 +620,16 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
487
620
|
action record {
|
|
488
621
|
from: paid, to: paid, actor: { parent: current() }
|
|
489
622
|
requires self.held.balance == "0"
|
|
623
|
+
requires count of { instrument: current(), reference: "plan", anchor: self.plan, states: [paid], limit: 366 } <= self.plan.paymentLimit
|
|
624
|
+
requires count of { instrument: profit_piece, reference: "plan", anchor: self.plan, states: [paid], limit: 366 } <= self.plan.historyLimit
|
|
625
|
+
requires count of { instrument: all(financing.late_charge.receipt), reference: "plan", anchor: self.plan, states: [paid], limit: 366 } <= self.plan.historyLimit
|
|
626
|
+
requires count of { instrument: all(financing.late_charge.cost.receipt), reference: "plan", anchor: self.plan, states: [paid], limit: 366 } <= self.plan.historyLimit
|
|
490
627
|
calculate: [{ target: principalReturned, op: aggregate, selection: { instrument: principal_piece, reference: "payment", anchor: self.id, states: [paid], limit: 366 }, measure: { sum: "amount" } }, { target: profitReturned, op: aggregate, selection: { instrument: profit_piece, reference: "payment", anchor: self.id, states: [paid], limit: 366 }, measure: { sum: "amount" } }]
|
|
491
628
|
invoke: [{ reference: self.plan, action: record_payment, input: { identity: { field: self.paymentIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned } } }]
|
|
492
629
|
}
|
|
630
|
+
action unwind { from: paid, to: refunded, actor: { parent: [parent, settlement] } }
|
|
493
631
|
action refund {
|
|
494
|
-
from: paid, to: refunded, actor: { party: capital }
|
|
632
|
+
from: paid, to: refunded, actor: { party: capital }, deadline: { at: self.plan.refundUntil }
|
|
495
633
|
requires unique "settlement_consumption" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]
|
|
496
634
|
requires count of { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } } == 1
|
|
497
635
|
invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } }, action: refund, input: {} }]
|
|
@@ -517,12 +655,12 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
517
655
|
}
|
|
518
656
|
}
|
|
519
657
|
profit_piece: {
|
|
520
|
-
fields { cashReceipt: text?, slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
|
|
658
|
+
fields { cashReceipt: text?, plan: ref<parent>, slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
|
|
521
659
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
522
660
|
action create { allowZero: true,
|
|
523
661
|
calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.limit }] }]
|
|
524
662
|
actor: { parent: slice }
|
|
525
|
-
input { slice: ref<slice>, payment: ref<payment>, limit: money }
|
|
663
|
+
input { plan: ref<parent>, slice: ref<slice>, payment: ref<payment>, limit: money }
|
|
526
664
|
requires unique "profit_receipt" on [self.slice, self.payment]
|
|
527
665
|
moves self.amount from self.payment.held to self.slice.plan.profitIncome capture "cashReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
528
666
|
when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
@@ -530,7 +668,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
530
668
|
when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
531
669
|
}
|
|
532
670
|
action refund { allowZero: true,
|
|
533
|
-
from: paid, to: refunded, actor: { parent: payment }
|
|
671
|
+
from: paid, to: refunded, actor: { parent: [payment, parent] }
|
|
534
672
|
moves self.amount from self.slice.plan.profitIncome to self.slice.plan.borrower economics { recipient: capital, company: { purpose: earning, sourceParty: capital, reversalOf: self.cashReceipt }, participant: unclassified }
|
|
535
673
|
when profit_earned is on_payment { moves self.amount from self.slice.debt to self.slice.profitReceivable economics { purpose: internal, sourceParty: borrower } }
|
|
536
674
|
when profit_earned is by_schedule { moves self.amount from self.slice.debt to self.slice.profitEarned economics { purpose: internal, sourceParty: borrower } }
|
|
@@ -552,11 +690,11 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
552
690
|
familyRevision: 1
|
|
553
691
|
summary: "Separate fine and evidenced recovery claims on an overdue installment."
|
|
554
692
|
fields {
|
|
555
|
-
plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date?
|
|
693
|
+
plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date?, payoffAmount: money?, payoffReceipt: text?
|
|
556
694
|
receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
|
|
557
695
|
}
|
|
558
696
|
lifecycle { states: [proposed, assessed, collected, waived], initial: proposed }
|
|
559
|
-
action create { input { slice: ref<on.slice>? }, calculate: [{ target: overdueAt, op: shift, date: { field: self.slice.dueAt }, milliseconds: { literal: grace }, direction: after }], requires unique "late_assessment" on [self.slice, self.overdueAt]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan }
|
|
697
|
+
action create { input { slice: ref<on.slice>? }, calculate: [{ target: overdueAt, op: shift, date: { field: self.slice.dueAt }, milliseconds: { literal: grace }, direction: after }, { target: payoffAmount, op: sum, values: [{ literal: "0" }] }], requires unique "late_assessment" on [self.slice, self.overdueAt]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan; requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [proposed, assessed, collected], limit: 366 } < self.plan.historyLimit }
|
|
560
698
|
action assess { allowZero: true,
|
|
561
699
|
from: proposed, to: assessed, actor: clock, due: { at: self.overdueAt }
|
|
562
700
|
requires self.slice.principalReceivable.balance > "0"
|
|
@@ -566,16 +704,23 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
566
704
|
from: [assessed, collected], to: assessed, actor: { parent: child(on, slice) }, input { payment: ref<on.payment> }
|
|
567
705
|
requires input.payment in [paid]
|
|
568
706
|
requires input.payment.plan == self.plan
|
|
569
|
-
invoke: [{ instrument: receipt, action: create, input: { assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
|
|
707
|
+
invoke: [{ instrument: receipt, action: create, input: { plan: { field: self.plan }, assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
|
|
570
708
|
}
|
|
571
709
|
action payoff { allowZero: true,
|
|
572
710
|
from: [assessed, collected], to: collected, actor: { parent: on }
|
|
573
711
|
requires self.plan in [paid]
|
|
574
|
-
|
|
575
|
-
moves self.
|
|
712
|
+
calculate: [{ target: payoffAmount, op: sum, values: [{ field: self.receivable.balance }] }]
|
|
713
|
+
moves self.payoffAmount from self.plan.borrower to fines_to capture "payoffReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
|
|
714
|
+
moves self.payoffAmount from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
715
|
+
}
|
|
716
|
+
action unwind_payoff { allowZero: true,
|
|
717
|
+
from: collected, to: collected, actor: { parent: on }
|
|
718
|
+
moves self.payoffAmount from fines_to to self.plan.borrower economics { recipient: fines_to, company: { purpose: earning, sourceParty: fines_to, reversalOf: self.payoffReceipt }, participant: unclassified }
|
|
576
719
|
}
|
|
577
720
|
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
578
|
-
action cancel { from: proposed, to: waived }
|
|
721
|
+
action cancel { from: proposed, to: waived, actor: { party: programOperator } }
|
|
722
|
+
action cancel_for_amendment { from: proposed, to: waived, actor: { parent: child(on, amendment) } }
|
|
723
|
+
action cancel_undelivered { allowZero: true, from: [proposed, assessed, collected], to: waived, actor: { parent: on }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
579
724
|
action waive_for_amendment {
|
|
580
725
|
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
581
726
|
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
@@ -583,7 +728,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
583
728
|
}
|
|
584
729
|
action waive { allowZero: true, from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
585
730
|
action refund {
|
|
586
|
-
from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }
|
|
731
|
+
from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }, deadline: { at: self.plan.refundUntil }
|
|
587
732
|
requires input.payment in [paid, refunded]
|
|
588
733
|
requires input.payment.plan == self.plan
|
|
589
734
|
invoke: [{ selection: { instrument: receipt, reference: "payment", anchor: input.payment, states: [paid], limit: 1, where: { assessment: { field: self.id } } }, action: refund, input: {} }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
|
|
@@ -610,20 +755,20 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
610
755
|
}
|
|
611
756
|
}
|
|
612
757
|
receipt: {
|
|
613
|
-
fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
758
|
+
fields { cashReceipt: text?, plan: ref<on>, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
614
759
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
615
|
-
action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "fine_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to capture "cashReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
616
|
-
action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower economics { recipient: fines_to, company: { purpose: earning, sourceParty: fines_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
|
|
760
|
+
action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { plan: ref<on>, assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "fine_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to capture "cashReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
761
|
+
action refund { allowZero: true, from: paid, to: refunded, actor: { parent: [parent, on] }, moves self.amount from fines_to to self.payment.plan.borrower economics { recipient: fines_to, company: { purpose: earning, sourceParty: fines_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
|
|
617
762
|
}
|
|
618
763
|
|
|
619
764
|
cost: {
|
|
620
765
|
revisioned: true
|
|
621
766
|
fields {
|
|
622
|
-
plan: ref<on>, assessment: ref<parent>, overdueAt: date?, evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap)
|
|
767
|
+
plan: ref<on>, assessment: ref<parent>, overdueAt: date?, evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap), payoffAmount: money?, payoffReceipt: text?
|
|
623
768
|
receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
|
|
624
769
|
}
|
|
625
770
|
lifecycle { states: [proposed, assessed, collected, waived], initial: proposed }
|
|
626
|
-
action create { calculate: [{ target: overdueAt, op: shift, date: { field: self.assessment.overdueAt }, milliseconds: { literal: 0 }, direction: after }], requires unique "recovery_assessment" on [self.assessment]; requires self.plan == self.assessment.plan }
|
|
771
|
+
action create { actor: { party: programOperator }, calculate: [{ target: overdueAt, op: shift, date: { field: self.assessment.overdueAt }, milliseconds: { literal: 0 }, direction: after }, { target: payoffAmount, op: sum, values: [{ literal: "0" }] }], requires unique "recovery_assessment" on [self.assessment]; requires self.plan == self.assessment.plan; requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [proposed, assessed, collected], limit: 366 } < self.plan.historyLimit }
|
|
627
772
|
action assess { allowZero: true,
|
|
628
773
|
from: proposed, to: assessed, actor: clock, due: { at: self.assessment.overdueAt }
|
|
629
774
|
requires self.assessment.slice.principalReceivable.balance > "0"
|
|
@@ -633,16 +778,23 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
633
778
|
from: [assessed, collected], to: assessed, actor: { parent: child(on, slice) }, input { payment: ref<on.payment> }
|
|
634
779
|
requires input.payment in [paid]
|
|
635
780
|
requires input.payment.plan == self.plan
|
|
636
|
-
invoke: [{ instrument: receipt, action: create, input: { assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
|
|
781
|
+
invoke: [{ instrument: receipt, action: create, input: { plan: { field: self.plan }, assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
|
|
637
782
|
}
|
|
638
783
|
action payoff { allowZero: true,
|
|
639
784
|
from: [assessed, collected], to: collected, actor: { parent: on }
|
|
640
785
|
requires self.plan in [paid]
|
|
641
|
-
|
|
642
|
-
moves self.
|
|
786
|
+
calculate: [{ target: payoffAmount, op: sum, values: [{ field: self.receivable.balance }] }]
|
|
787
|
+
moves self.payoffAmount from self.plan.borrower to costs_to capture "payoffReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
|
|
788
|
+
moves self.payoffAmount from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
789
|
+
}
|
|
790
|
+
action unwind_payoff { allowZero: true,
|
|
791
|
+
from: collected, to: collected, actor: { parent: on }
|
|
792
|
+
moves self.payoffAmount from costs_to to self.plan.borrower economics { recipient: costs_to, company: { purpose: earning, sourceParty: costs_to, reversalOf: self.payoffReceipt }, participant: unclassified }
|
|
643
793
|
}
|
|
644
794
|
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
645
|
-
action cancel { from: proposed, to: waived }
|
|
795
|
+
action cancel { from: proposed, to: waived, actor: { party: programOperator } }
|
|
796
|
+
action cancel_for_amendment { from: proposed, to: waived, actor: { parent: child(on, amendment) } }
|
|
797
|
+
action cancel_undelivered { allowZero: true, from: [proposed, assessed, collected], to: waived, actor: { parent: on }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
646
798
|
action waive_for_amendment {
|
|
647
799
|
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
648
800
|
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
@@ -650,7 +802,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
650
802
|
}
|
|
651
803
|
action waive { allowZero: true, from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
652
804
|
action refund {
|
|
653
|
-
from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }
|
|
805
|
+
from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }, deadline: { at: self.plan.refundUntil }
|
|
654
806
|
requires input.payment in [paid, refunded]
|
|
655
807
|
requires input.payment.plan == self.plan
|
|
656
808
|
invoke: [{ selection: { instrument: receipt, reference: "payment", anchor: input.payment, states: [paid], limit: 1, where: { assessment: { field: self.id } } }, action: refund, input: {} }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
|
|
@@ -677,10 +829,10 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
677
829
|
}
|
|
678
830
|
}
|
|
679
831
|
receipt: {
|
|
680
|
-
fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
832
|
+
fields { cashReceipt: text?, plan: ref<on>, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
681
833
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
682
|
-
action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "cost_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to capture "cashReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
683
|
-
action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower economics { recipient: costs_to, company: { purpose: earning, sourceParty: costs_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
|
|
834
|
+
action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { plan: ref<on>, assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "cost_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to capture "cashReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
835
|
+
action refund { allowZero: true, from: paid, to: refunded, actor: { parent: [parent, on] }, moves self.amount from costs_to to self.payment.plan.borrower economics { recipient: costs_to, company: { purpose: earning, sourceParty: costs_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
|
|
684
836
|
}
|
|
685
837
|
}
|
|
686
838
|
}
|
|
@@ -693,8 +845,8 @@ instrument limits(per_borrower: money, borrower: party = party(person), active_p
|
|
|
693
845
|
summary: "One outstanding balance ceiling per borrower."
|
|
694
846
|
fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }
|
|
695
847
|
lifecycle { states: [pending, approved], initial: pending }
|
|
696
|
-
action create { requires unique "borrower_limit" on [self.borrower] }
|
|
697
|
-
action approve { from: pending, to: approved }
|
|
848
|
+
action create { actor: { party: borrower }, requires unique "borrower_limit" on [self.borrower] }
|
|
849
|
+
action approve { from: pending, to: approved, actor: { party: programOperator } }
|
|
698
850
|
}
|
|
699
851
|
|
|
700
852
|
instrument portfolio_limit(limit: money) {
|
|
@@ -703,8 +855,8 @@ instrument portfolio_limit(limit: money) {
|
|
|
703
855
|
summary: "One shared ceiling on the principal the product may hold."
|
|
704
856
|
fields { key: text = "portfolio", limit: money = limit }
|
|
705
857
|
lifecycle { states: [pending, approved], initial: pending }
|
|
706
|
-
action create { requires unique "portfolio_limit" on [self.key] }
|
|
707
|
-
action approve { from: pending, to: approved }
|
|
858
|
+
action create { actor: { party: programOperator }, requires unique "portfolio_limit" on [self.key] }
|
|
859
|
+
action approve { from: pending, to: approved, actor: { party: programOperator } }
|
|
708
860
|
}
|
|
709
861
|
|
|
710
862
|
instrument credit_line(borrower: party = programOperator, adapter: text, limit: money, expires: date) {
|
|
@@ -712,29 +864,29 @@ instrument credit_line(borrower: party = programOperator, adapter: text, limit:
|
|
|
712
864
|
summary: "A revolving facility from an outside lender; each draw is a separate immutable advance."
|
|
713
865
|
fields { borrower: account of borrower, facility: account(borrower, cash, "facility"), debt: account(borrower, claim, contra, "debt"), limit: money = limit, expiresAt: date = expires }
|
|
714
866
|
lifecycle { states: [pending, active, suspended, closed], initial: pending }
|
|
715
|
-
action create {}
|
|
867
|
+
action create { actor: { party: borrower } }
|
|
716
868
|
action activate {
|
|
717
869
|
from: pending, to: active, actor: { party: borrower }
|
|
718
870
|
subject { adapter: adapter }
|
|
719
871
|
}
|
|
720
872
|
action suspend { from: active, to: suspended, actor: { party: borrower } }
|
|
721
873
|
action resume { from: suspended, to: active, actor: { party: borrower } }
|
|
722
|
-
action close { from: [active, suspended], to: closed, due: { at: self.expiresAt }, requires count of { instrument: all(financing.advance), reference: "line", anchor: self.id, states: [drawn], limit: 366 } == 0 }
|
|
874
|
+
action close { from: [active, suspended], to: closed, actor: { party: borrower }, due: { at: self.expiresAt }, requires count of { instrument: all(financing.advance), reference: "line", anchor: self.id, states: [drawn], limit: 366 } == 0 }
|
|
723
875
|
}
|
|
724
876
|
|
|
725
|
-
instrument advance(line: ref<financing.credit_line>, amount: money = runtime) {
|
|
877
|
+
instrument advance(line: ref<financing.credit_line>, amount: money = runtime, borrower: party = programOperator) {
|
|
726
878
|
familyRevision: 1
|
|
727
879
|
summary: "A single facility draw with an immutable principal and full repayment."
|
|
728
|
-
fields { line: ref<line>, amount: money = amount, principalReceivable: account(self, claim) }
|
|
880
|
+
fields { line: ref<line>, borrower: account of borrower, amount: money = amount, principalReceivable: account(self, claim) }
|
|
729
881
|
lifecycle { states: [pending, drawn, repaid, cancelled], initial: pending }
|
|
730
|
-
action create { requires self.amount <= self.line.limit }
|
|
882
|
+
action create { actor: { party: borrower }, requires self.borrower == self.line.borrower; requires self.amount <= self.line.limit }
|
|
731
883
|
invariants sum "principalReceivable.balance" of { instrument: all(financing.advance), reference: "line", anchor: self.line, states: [drawn], limit: 366 } <= self.line.limit
|
|
732
884
|
action draw {
|
|
733
|
-
from: pending, to: drawn
|
|
885
|
+
from: pending, to: drawn, actor: { party: borrower }
|
|
734
886
|
requires self.line in [active]
|
|
735
887
|
deadline: { at: self.line.expiresAt }
|
|
736
888
|
moves self.amount from self.line.facility to self.line.borrower; moves self.amount from self.line.debt to self.principalReceivable
|
|
737
889
|
}
|
|
738
|
-
action repay { from: drawn, to: repaid, moves self.amount from self.line.borrower to self.line.facility; moves self.amount from self.principalReceivable to self.line.debt }
|
|
739
|
-
action cancel { from: pending, to: cancelled }
|
|
890
|
+
action repay { from: drawn, to: repaid, actor: { party: borrower }, moves self.amount from self.line.borrower to self.line.facility; moves self.amount from self.principalReceivable to self.line.debt }
|
|
891
|
+
action cancel { from: pending, to: cancelled, actor: { party: borrower } }
|
|
740
892
|
}
|