@hyperscale0/hsx 1.0.125 → 1.0.130
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/README.md +2 -2
- 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 +155 -21
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +9 -9
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/headers.d.ts +1 -1
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +1 -0
- package/dist/src/headers.js.map +1 -1
- package/dist/src/parse.js +4 -1
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +8 -7
- package/dist/src/std-bundle.js.map +1 -1
- package/docs/README.md +102 -50
- package/examples/cards.hsx +4 -0
- package/examples/collections.hsx +5 -1
- package/examples/cost-table.json +80 -56
- package/examples/financing.hsx +1 -1
- package/examples/insurance.hsx +5 -4
- package/examples/lending.hsx +1 -1
- package/examples/library.hsx +1 -1
- package/examples/rental-deposit.hsx +1 -1
- package/examples/reporting.hsx +1 -1
- package/examples/serviced.hsx +1 -1
- package/package.json +4 -4
- package/src/cli.ts +1 -1
- package/src/compile.ts +223 -27
- package/src/examples-bundle.ts +9 -9
- package/src/headers.ts +1 -0
- package/src/parse.ts +4 -1
- package/src/std-bundle.ts +8 -7
- package/std/booking.hsx +75 -0
- package/std/cards.hsx +5 -3
- package/std/escrow.hsx +35 -9
- package/std/financing.hsx +158 -36
- package/std/insurance.hsx +9 -5
- package/std/marketplace.hsx +3 -3
- package/std/money.hsx +3 -3
- package/std/wallet.hsx +20 -9
package/std/booking.hsx
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
header booking
|
|
2
|
+
|
|
3
|
+
// Economics: Deposit and balance payments and every refund are principal. Completion
|
|
4
|
+
// and any forfeit are earnings for the company, or pass the customer's cash through
|
|
5
|
+
// to a bound outside operator.
|
|
6
|
+
|
|
7
|
+
// Expose create, pay_deposit and pay_balance for the booking path, and
|
|
8
|
+
// cancel_early, cancel_middle and cancel_late for the customer. Create captures
|
|
9
|
+
// subject departure, price, deposit and balance, and refuses a deposit and balance
|
|
10
|
+
// that do not add up to the price. The balance is payable only after the deposit
|
|
11
|
+
// and before balance_due ahead of departure, so a cancelled booking never offers
|
|
12
|
+
// it. cancel_early runs until early_until before departure and returns everything
|
|
13
|
+
// held. cancel_middle runs from then until late_from; cancel_late runs from then
|
|
14
|
+
// until departure, even when the clock's complete runs late. They keep
|
|
15
|
+
// middle_forfeit or late_forfeit of the held money and return the rest. Both
|
|
16
|
+
// forfeits default to 0%, so nothing is kept unless the author sets them. At
|
|
17
|
+
// departure the clock completes a paid booking and pays operator, and lapses an
|
|
18
|
+
// unpaid one on cancel_late's terms, so nothing stays held after departure.
|
|
19
|
+
|
|
20
|
+
// Cost: create declares one owned account. Each payment declares one transfer;
|
|
21
|
+
// cancellation and completion move the held balance. Run hsx cost for the
|
|
22
|
+
// composed counts.
|
|
23
|
+
|
|
24
|
+
instrument reservation(customer: party = party(person), operator: party = programOperator, early_until: duration = 30d, late_from: duration = 15d, balance_due: duration = 15d, middle_forfeit: percent = 0%, late_forfeit: percent = 0%) {
|
|
25
|
+
revisioned: true
|
|
26
|
+
summary: "Take a deposit and a balance for a dated booking, with cancellation windows before departure."
|
|
27
|
+
fields {
|
|
28
|
+
customer: account of customer, operator: account of operator, held: account of self
|
|
29
|
+
departure: date, price: money = subject.price
|
|
30
|
+
deposit: money = subject.deposit, balance: money = subject.balance
|
|
31
|
+
split: money = sum(self.deposit, self.balance)
|
|
32
|
+
balanceAt: date = before(self.departure, balance_due)
|
|
33
|
+
earlyAt: date = before(self.departure, early_until)
|
|
34
|
+
lateAt: date = before(self.departure, late_from)
|
|
35
|
+
refund: money?, forfeit: money?
|
|
36
|
+
}
|
|
37
|
+
lifecycle { states: [reserved, deposit_paid, paid, completed, cancelled], initial: reserved }
|
|
38
|
+
action create {
|
|
39
|
+
subject { departure: date, price: money, deposit: money, balance: money }
|
|
40
|
+
actor: { party: customer }
|
|
41
|
+
requires self.departure > self.now
|
|
42
|
+
requires self.split == self.price
|
|
43
|
+
}
|
|
44
|
+
action pay_deposit {
|
|
45
|
+
from: reserved, to: deposit_paid, actor: { party: customer }
|
|
46
|
+
moves self.deposit from customer to self.held economics { purpose: principal, sourceParty: customer }
|
|
47
|
+
}
|
|
48
|
+
action pay_balance {
|
|
49
|
+
from: deposit_paid, to: paid, actor: { party: customer }, deadline: { at: self.balanceAt }
|
|
50
|
+
moves self.balance from customer to self.held economics { purpose: principal, sourceParty: customer }
|
|
51
|
+
}
|
|
52
|
+
action complete {
|
|
53
|
+
from: paid, to: completed, actor: clock, due: { at: self.departure }
|
|
54
|
+
moves self.held.balance from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
55
|
+
}
|
|
56
|
+
action cancel_early { allowZero: true,
|
|
57
|
+
from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, deadline: { at: self.earlyAt }
|
|
58
|
+
moves self.held.balance from self.held to customer economics { purpose: principal, sourceParty: programOperator }
|
|
59
|
+
}
|
|
60
|
+
action cancel_middle { allowZero: true,
|
|
61
|
+
from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, due: { at: self.earlyAt }, deadline: { at: self.lateAt }
|
|
62
|
+
calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: middle_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
|
|
63
|
+
moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
64
|
+
}
|
|
65
|
+
action cancel_late { allowZero: true,
|
|
66
|
+
from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, due: { at: self.lateAt }, deadline: { at: self.departure }
|
|
67
|
+
calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: late_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
|
|
68
|
+
moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
69
|
+
}
|
|
70
|
+
action lapse { allowZero: true,
|
|
71
|
+
from: [reserved, deposit_paid], to: cancelled, actor: clock, due: { at: self.departure }
|
|
72
|
+
calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: late_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
|
|
73
|
+
moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
74
|
+
}
|
|
75
|
+
}
|
package/std/cards.hsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
header cards
|
|
2
2
|
|
|
3
3
|
// Activate holder and card before creating and approving an authorization.
|
|
4
|
+
// Only the Product operator activates, suspends or resumes a cardholder; the
|
|
5
|
+
// holder freezes, unfreezes and cancels their own card.
|
|
4
6
|
// Expose authorization.create, approve and capture; expose decline and reverse
|
|
5
7
|
// separately if callers need those paths. Issuer declines only from pending;
|
|
6
8
|
// merchant captures or reverses an approved reservation. Expiry voids approved
|
|
@@ -21,9 +23,9 @@ instrument cardholder(holder: party) {
|
|
|
21
23
|
fields { holder: account of holder }
|
|
22
24
|
lifecycle { states: [pending, active, suspended, closed], initial: pending }
|
|
23
25
|
action create {}
|
|
24
|
-
action activate { from: pending, to: active }
|
|
25
|
-
action suspend { from: active, to: suspended }
|
|
26
|
-
action resume { from: suspended, to: active }
|
|
26
|
+
action activate { from: pending, to: active, actor: { party: programOperator } }
|
|
27
|
+
action suspend { from: active, to: suspended, actor: { party: programOperator } }
|
|
28
|
+
action resume { from: suspended, to: active, actor: { party: programOperator } }
|
|
27
29
|
action close { from: [active, suspended], to: closed }
|
|
28
30
|
}
|
|
29
31
|
|
package/std/escrow.hsx
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
header escrow
|
|
2
2
|
|
|
3
3
|
// Economics: Funding and unspent returns are principal. Acceptance realizes earnings for
|
|
4
|
-
// the company
|
|
4
|
+
// the company; the payer's cash passes through to a bound outside seller, less the
|
|
5
|
+
// fee the company earns. Tax is pass-through.
|
|
5
6
|
|
|
6
7
|
// Expose create, fund, deliver and accept for the purchase path. Create captures
|
|
7
8
|
// subject.price; acceptance pays payee less the configured fee, not payer.
|
|
8
9
|
// Expose dispute, verify_return and refund separately for the return path.
|
|
9
|
-
// Payer disputes delivered goods; payee verifies return evidence from disputed
|
|
10
|
-
//
|
|
10
|
+
// Payer disputes delivered goods; payee verifies return evidence from disputed.
|
|
11
|
+
// Payee refunds the whole held balance before delivery, or from return_verified
|
|
12
|
+
// by default after a dispute. While a financing plan funds the hold in any state
|
|
13
|
+
// but cancelled, both refunds refuse, so a written-off plan never hands lender
|
|
14
|
+
// principal to the payer. Only unwind returns the principal to capital and
|
|
15
|
+
// cancels the plan; after that, refund returns the rest. Before delivery the
|
|
16
|
+
// Product operator runs the plan's unwind_undelivered instead, which invokes
|
|
17
|
+
// unwind_financed here to return the rest to the payer and close the hold.
|
|
18
|
+
// After a paid plan's refund window, the plan's return_hold runs it from funded
|
|
19
|
+
// or return_verified to return the whole balance to the payer.
|
|
20
|
+
// Only an installments plan can run confirm and unwind_financed; without
|
|
21
|
+
// financing neither action exists, so no hold reaches funded without fundedAt. The plan's confirm leaves fundedAt unset, so
|
|
22
|
+
// refund_undelivered never comes due on a financed hold and no schedule offers it.
|
|
23
|
+
// Expose refund_undelivered to let the payer take the whole held price back once
|
|
24
|
+
// deliver_within has passed since funding without delivery. It is a payer action
|
|
25
|
+
// that becomes available at that time; nothing runs it automatically.
|
|
11
26
|
// Acceptance expiry enters disputed, never released. Neither verify_return nor
|
|
12
27
|
// refund becomes public merely because dispute is exposed. Clock actions remain
|
|
13
28
|
// internal. Expose cancel for a pending hold, including a checkout-funded hold;
|
|
@@ -18,12 +33,12 @@ header escrow
|
|
|
18
33
|
// transfer; accept expands the configured fee and tax into moves. Run hsx cost
|
|
19
34
|
// on the composed program for instruction counts. The executor supplies prices.
|
|
20
35
|
|
|
21
|
-
instrument hold(payer: party = party(person), payee: party = programOperator, accept_within: duration = 48h, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {
|
|
36
|
+
instrument hold(payer: party = party(person), payee: party = programOperator, accept_within: duration = 48h, deliver_within: duration = 14d, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {
|
|
22
37
|
revisioned: true
|
|
23
38
|
summary: "Hold the subject price through delivery, acceptance and verified return."
|
|
24
39
|
fields {
|
|
25
40
|
payer: account of payer, payee: account of payee
|
|
26
|
-
held: account of self, deliveredAt: date?
|
|
41
|
+
held: account of self, fundedAt: date?, deliveredAt: date?
|
|
27
42
|
price: money = subject.price
|
|
28
43
|
}
|
|
29
44
|
lifecycle { states: [pending, funded, delivered, disputed, return_verified, released, refunded, cancelled], initial: pending }
|
|
@@ -33,11 +48,11 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
33
48
|
}
|
|
34
49
|
action fund {
|
|
35
50
|
from: pending, to: funded, actor: { party: payer }
|
|
36
|
-
|
|
51
|
+
set: { fundedAt: { field: self.now } }
|
|
37
52
|
moves self.price from payer to self.held economics { purpose: principal, sourceParty: payer }
|
|
38
53
|
}
|
|
39
54
|
action confirm {
|
|
40
|
-
from: pending, to: funded
|
|
55
|
+
from: pending, to: funded, actor: { parent: all(financing.installments) }
|
|
41
56
|
requires self.held.balance == self.price
|
|
42
57
|
}
|
|
43
58
|
action deliver {
|
|
@@ -48,7 +63,7 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
48
63
|
from: delivered, to: released, actor: { party: payer }
|
|
49
64
|
input { acceptanceReference: text }
|
|
50
65
|
deadline: { at: self.deliveredAt, offset: accept_within }
|
|
51
|
-
moves self.price from self.held to payee fee fee economics { payee: { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose:
|
|
66
|
+
moves self.price from self.held to payee fee fee economics { payee: { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: programOperator } }, fee: { purpose: earning, sourceParty: payer }, tax: { purpose: pass_through, sourceParty: programOperator } }
|
|
52
67
|
}
|
|
53
68
|
action expire_acceptance {
|
|
54
69
|
from: delivered, to: disputed, actor: clock
|
|
@@ -63,9 +78,20 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
63
78
|
input { returnEvidence: text }
|
|
64
79
|
}
|
|
65
80
|
action refund {
|
|
66
|
-
from: dispute.refund_after, to: refunded, actor: { party: payee }
|
|
81
|
+
from: [funded, dispute.refund_after], to: refunded, actor: { party: payee }
|
|
82
|
+
requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [quoted, signed, active, paid, written_off], limit: 1 } == 0
|
|
83
|
+
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
84
|
+
}
|
|
85
|
+
action refund_undelivered {
|
|
86
|
+
from: funded, to: refunded, actor: { party: payer }
|
|
87
|
+
due: { at: self.fundedAt, offset: deliver_within }
|
|
88
|
+
requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [quoted, signed, active, paid, written_off], limit: 1 } == 0
|
|
67
89
|
moves self.price from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
68
90
|
}
|
|
91
|
+
action unwind_financed { allowZero: true,
|
|
92
|
+
from: [funded, return_verified], to: refunded, actor: { parent: all(financing.installments) }
|
|
93
|
+
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
94
|
+
}
|
|
69
95
|
action cancel { allowZero: true,
|
|
70
96
|
from: pending, to: cancelled, actor: { party: payer }
|
|
71
97
|
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
package/std/financing.hsx
CHANGED
|
@@ -2,9 +2,14 @@ header financing
|
|
|
2
2
|
|
|
3
3
|
// Economics: Principal advances and repayments are excluded from earnings. Claims, waivers,
|
|
4
4
|
// write-offs and allocations are internal. Paid profit and company charges are
|
|
5
|
-
// earnings; receipt refunds reverse captured cash.
|
|
6
|
-
//
|
|
5
|
+
// earnings; receipt refunds reverse captured cash. Borrower cash paid to outside
|
|
6
|
+
// capital or recovery recipients passes through, whether held first or paid direct.
|
|
7
7
|
|
|
8
|
+
// Pricing is required. amortizing reads profit_rate as an annual rate on the
|
|
9
|
+
// declining balance: equal monthly instalments, the last absorbing rounding.
|
|
10
|
+
// flat_total reads it as one charge on principal for the whole term (murabaha,
|
|
11
|
+
// fixed-fee pay-later). 12,000 at 18% over 12 months costs 13,201.95 amortizing
|
|
12
|
+
// and 14,160 flat_total.
|
|
8
13
|
// Expose create and sign on installments, then collect_down_payment as checkout
|
|
9
14
|
// for disburse_to: funds. Checkout requires a signed plan and pending escrow;
|
|
10
15
|
// it tops up only the missing down payment, without advancing lender principal.
|
|
@@ -12,13 +17,44 @@ header financing
|
|
|
12
17
|
// the fully funded hold. Existing escrow credit counts once toward the down payment.
|
|
13
18
|
// Disburse first credits an active marketplace.reservation on the same hold,
|
|
14
19
|
// so list the plan in its converters; the deposit then counts as escrow credit.
|
|
15
|
-
//
|
|
20
|
+
// disburse_to: seller pays principal to the seller party at activation (pay later
|
|
21
|
+
// with the merchant paid on day one). For a return, capital refunds each paid
|
|
22
|
+
// payment to 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. After the refunds, capital runs unwind: it takes principal back to
|
|
32
|
+
// capital and cancels open slices with their claims. Seller mode debits the seller; funds mode
|
|
33
|
+
// needs the hold in return_verified and debits the hold, after which the payee's
|
|
34
|
+
// escrow refund returns only the down payment. Before delivery, the Product
|
|
35
|
+
// operator runs unwind_undelivered on a funds-mode plan whose hold is still
|
|
36
|
+
// funded: principal goes back to capital and the hold's unwind_financed returns
|
|
37
|
+
// the down payment to the payer and closes the hold as refunded, all in one
|
|
38
|
+
// operation, so deliver is refused afterwards. It also cancels proposed and
|
|
39
|
+
// assessed late charges and recovery costs: goods that never arrived earn no
|
|
40
|
+
// fines. Escrow refund refuses while any plan that is
|
|
41
|
+
// not cancelled funds the hold, written off included, so the borrower never
|
|
42
|
+
// receives lender principal. A funds-mode plan writes off only once its hold
|
|
43
|
+
// has released principal to the seller: principal still held is not a loss, and
|
|
44
|
+
// unwind or unwind_undelivered returns it. Both unwinds need no paid payments and no
|
|
45
|
+
// collected late charges; refund them first. unwind also needs no assessed
|
|
46
|
+
// late charges; waive them first. Once a paid plan's refund window has passed,
|
|
47
|
+
// its payments can no longer be refunded, so the Product operator runs
|
|
48
|
+
// return_hold to return an undelivered or returned hold's whole balance to the
|
|
49
|
+
// borrower, who has repaid the principal.
|
|
50
|
+
// Borrower- and seller-directed disbursement exclude collect_down_payment. A down_payment
|
|
16
51
|
// there reduces principal but does not collect cash. Price is subject metadata,
|
|
17
52
|
// not the borrower limit. The escrow payer and price must match the plan.
|
|
18
53
|
// Expose escrow cancel to return checkout cash if financing is abandoned; plan
|
|
19
54
|
// void alone cancels the agreement and does not refund its separate escrow.
|
|
20
55
|
// Approve borrower and portfolio limits before disbursement. Create builds the
|
|
21
|
-
// dated slice records
|
|
56
|
+
// dated slice records from future, strictly increasing dates; disbursement
|
|
57
|
+
// refuses once a slice is due. Payments need a separate payment record and pay action.
|
|
22
58
|
// Expose parent payoff separately and create a current, unexpired payoff_quote.
|
|
23
59
|
// Expose late_charge.create as propose_assessment before the clock can assess;
|
|
24
60
|
// provide matching plan, slice and borrower. Fine is fixed money, not a percentage.
|
|
@@ -26,19 +62,25 @@ header financing
|
|
|
26
62
|
// payment does not settle the remaining fine. Recovery cost needs its own record.
|
|
27
63
|
// Profit share credits capital's allocatedProfitPayable, not an outside agency.
|
|
28
64
|
// Credit lines need adapter binding at activation; draws are ledger advances,
|
|
29
|
-
// not evidence of outside funding.
|
|
30
|
-
// need the Product's operation
|
|
65
|
+
// not evidence of outside funding. Only the Product operator approves borrower
|
|
66
|
+
// and portfolio limits; caller-authorized advances need the Product's operation
|
|
67
|
+
// authority. No clock declaration proves retries.
|
|
31
68
|
|
|
32
69
|
// Cost: installments include cash and claim moves, child-account creation and
|
|
33
70
|
// bounded invocations across slices and assessments. Month and charge limits affect
|
|
34
71
|
// the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
|
|
35
72
|
|
|
36
|
-
instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3,
|
|
73
|
+
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, refund_within: duration = 30d, 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)) {
|
|
37
74
|
dependencies {
|
|
38
75
|
funds {
|
|
39
76
|
selector: disburse_to, is: funds
|
|
40
77
|
message: "Attachment `{attachment}` disburses into a hold, but `funds` is not bound."
|
|
41
|
-
fix: "Bind `funds: sale` to an escrow attachment. Choose `disburse_to: borrower` only if the borrower should receive the money directly."
|
|
78
|
+
fix: "Bind `funds: sale` to an escrow attachment. Choose `disburse_to: seller` to pay a seller on activation, or `disburse_to: borrower` only if the borrower should receive the money directly."
|
|
79
|
+
}
|
|
80
|
+
seller {
|
|
81
|
+
selector: disburse_to, is: seller
|
|
82
|
+
message: "Attachment `{attachment}` pays a seller, but `seller` is not bound."
|
|
83
|
+
fix: "Bind `seller: merchant` to the declared business that receives the principal."
|
|
42
84
|
}
|
|
43
85
|
}
|
|
44
86
|
revisioned: true
|
|
@@ -48,12 +90,12 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
48
90
|
funds: ref<funds>?, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>
|
|
49
91
|
months: integer = months, dates: list(date, months)
|
|
50
92
|
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), loss: account(capital, claim, "loss")
|
|
51
|
-
downPaymentRemaining: money?, downPaymentCredit: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?
|
|
93
|
+
downPaymentRemaining: money?, downPaymentCredit: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?, refundWindow: date?, refundUntil: date?
|
|
94
|
+
openSlices: integer?, openCharges: money?, openCosts: money?
|
|
52
95
|
price: money = subject.price
|
|
53
96
|
downPayment: money = rate(self.price, down_payment)
|
|
54
97
|
principal: money = subtract(self.price, self.downPayment)
|
|
55
|
-
|
|
56
|
-
total: money = sum(self.principal, self.profit)
|
|
98
|
+
flatProfit: money?
|
|
57
99
|
}
|
|
58
100
|
lifecycle { states: [quoted, signed, active, paid, written_off, cancelled], initial: quoted }
|
|
59
101
|
invariants count of { instrument: all(financing.installments), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [active], limit: 366 } <= self.borrowerLimit.activePlans
|
|
@@ -62,8 +104,9 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
62
104
|
action create {
|
|
63
105
|
subject { price: money }
|
|
64
106
|
requires self.borrowerLimit.borrower == self.borrower
|
|
107
|
+
when pricing is flat_total { calculate: [{ target: flatProfit, op: rate, base: { field: self.principal }, bps: { literal: profit_rate }, rounding: floor }] }
|
|
65
108
|
when disburse_to is funds { requires self.borrower == self.funds.payer; requires self.price == self.funds.price }
|
|
66
|
-
invoke: [{ range: { count: { literal: months }, maximum: months, bind: position }, instrument: slice, action: create, input: { plan: { field: self.id }, borrowerLimit: { field: self.borrowerLimit }, portfolioLimit: { field: self.portfolioLimit }, position: { field: position } } }]
|
|
109
|
+
invoke: [{ range: { count: { literal: months }, maximum: months, bind: position }, instrument: slice, action: create, input: { plan: { field: self.id }, borrowerLimit: { field: self.borrowerLimit }, portfolioLimit: { field: self.portfolioLimit }, position: { field: position } } }, { selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: months, where: { first: { literal: 0 } } }, action: check_order, input: {} }]
|
|
67
110
|
}
|
|
68
111
|
action sign {
|
|
69
112
|
from: quoted, to: signed, actor: { party: borrower }, input { signedOffer: text, affordabilityReference: text }
|
|
@@ -72,6 +115,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
72
115
|
action collect_down_payment { allowZero: true,
|
|
73
116
|
from: signed, to: signed, actor: { party: borrower }
|
|
74
117
|
when disburse_to is borrower { requires true == false }
|
|
118
|
+
when disburse_to is seller { requires true == false }
|
|
75
119
|
when disburse_to is funds {
|
|
76
120
|
requires self.funds in [pending]
|
|
77
121
|
calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: self.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: self.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
|
|
@@ -87,6 +131,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
87
131
|
invoke: [{ selection: { instrument: all(marketplace.reservation), reference: "funds", anchor: self.funds, states: [active], limit: 1 }, action: credit, input: { funds: { field: self.funds }, contribution: { field: self.downPayment } } }, { reference: self.id, action: fund_down_payment, input: {} }]
|
|
88
132
|
}
|
|
89
133
|
when disburse_to is borrower { moves self.principal from self.capital to self.borrower economics { purpose: principal, sourceParty: capital } }
|
|
134
|
+
when disburse_to is seller { moves self.principal from self.capital to seller economics { purpose: principal, sourceParty: capital } }
|
|
90
135
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]
|
|
91
136
|
when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }
|
|
92
137
|
}
|
|
@@ -100,11 +145,13 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
100
145
|
}
|
|
101
146
|
action complete {
|
|
102
147
|
from: active, to: paid
|
|
148
|
+
calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
149
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
103
150
|
requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 } == 0
|
|
104
151
|
requires sum "receivable.balance" of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
105
152
|
requires sum "receivable.balance" of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
106
153
|
}
|
|
107
|
-
action restore { from: [active, paid], to: active, actor: { parent: payment } }
|
|
154
|
+
action restore { from: [active, paid], to: active, actor: { parent: payment }, input { refundUntil: date? }, set: { refundUntil: { field: input.refundUntil } } }
|
|
108
155
|
action payoff {
|
|
109
156
|
from: active, to: paid, actor: { party: borrower }
|
|
110
157
|
input { quote: ref<payoff_quote> }
|
|
@@ -112,7 +159,9 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
112
159
|
deadline: { at: input.quote.expiresAt }
|
|
113
160
|
calculate: [
|
|
114
161
|
{ target: "principalReturned", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
162
|
+
{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }
|
|
115
163
|
]
|
|
164
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
116
165
|
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: {} }]
|
|
117
166
|
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: {} }]
|
|
118
167
|
}
|
|
@@ -124,18 +173,64 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
124
173
|
action write_off {
|
|
125
174
|
from: active, to: written_off, actor: { parent: write_off_request }
|
|
126
175
|
input { overdueSlice: ref<slice> }
|
|
127
|
-
calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }]
|
|
176
|
+
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 }]
|
|
177
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
128
178
|
due: { at: input.overdueSlice.lossEligibleAt }
|
|
129
179
|
requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > "0"
|
|
180
|
+
when disburse_to is funds { requires self.funds in [released] }
|
|
130
181
|
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" } } }]
|
|
131
182
|
}
|
|
132
183
|
action record_payment {
|
|
133
184
|
from: active, to: active, actor: { parent: payment }
|
|
134
185
|
input { identity: text, principal: money, profit: money }
|
|
186
|
+
calculate: [
|
|
187
|
+
{ target: openSlices, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: count }
|
|
188
|
+
{ target: openCharges, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
189
|
+
{ 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" } }
|
|
190
|
+
]
|
|
135
191
|
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" } } }]
|
|
192
|
+
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: {} }]
|
|
136
193
|
}
|
|
137
194
|
action cancel { from: quoted, to: cancelled }
|
|
138
195
|
action void { from: signed, to: cancelled, actor: { party: borrower } }
|
|
196
|
+
action unwind { allowZero: true,
|
|
197
|
+
from: active, to: cancelled, actor: { party: capital }
|
|
198
|
+
calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
199
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
200
|
+
when disburse_to is borrower { requires true == false }
|
|
201
|
+
when disburse_to is funds { requires self.funds in [return_verified] }
|
|
202
|
+
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
203
|
+
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == 0
|
|
204
|
+
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == 0
|
|
205
|
+
when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
|
|
206
|
+
when disburse_to is seller { moves self.principal from seller to self.capital economics { purpose: principal, sourceParty: seller } }
|
|
207
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
208
|
+
}
|
|
209
|
+
action unwind_undelivered { allowZero: true,
|
|
210
|
+
from: active, to: cancelled, actor: { party: programOperator }
|
|
211
|
+
calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
212
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
213
|
+
when disburse_to is borrower { requires true == false }
|
|
214
|
+
when disburse_to is seller { requires true == false }
|
|
215
|
+
when disburse_to is funds { requires self.funds in [funded] }
|
|
216
|
+
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
217
|
+
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
218
|
+
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
219
|
+
when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
|
|
220
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
221
|
+
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: {} }]
|
|
222
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: unwind_financed, input: {} }] }
|
|
223
|
+
}
|
|
224
|
+
action return_hold {
|
|
225
|
+
from: paid, to: paid, actor: { party: programOperator }
|
|
226
|
+
requires self.refundUntil < self.now
|
|
227
|
+
when disburse_to is borrower { requires true == false }
|
|
228
|
+
when disburse_to is seller { requires true == false }
|
|
229
|
+
when disburse_to is funds {
|
|
230
|
+
requires self.funds in [funded, return_verified]
|
|
231
|
+
invoke: [{ reference: self.funds, action: unwind_financed, input: {} }]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
139
234
|
records {
|
|
140
235
|
write_off_request: {
|
|
141
236
|
fields { plan: ref<parent>, overdueSlice: ref<slice> }
|
|
@@ -251,31 +346,48 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
251
346
|
revisioned: true
|
|
252
347
|
fields {
|
|
253
348
|
plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>, position: integer(1, months)
|
|
254
|
-
originalDueAt: date = at(self.plan.dates, self.position), dueAt: date?, nextDueAt: date?, previousDueAt: date?, previousPosition: integer
|
|
255
|
-
principalBase: money = divide(self.plan.principal, months)
|
|
256
|
-
principalAllocated: money = multiply(self.principalBase, months)
|
|
257
|
-
principalRemainder: money = subtract(self.plan.principal, self.principalAllocated)
|
|
258
|
-
profitBase: money = divide(self.plan.profit, months)
|
|
259
|
-
profitAllocated: money = multiply(self.profitBase, months)
|
|
260
|
-
profitRemainder: money = subtract(self.plan.profit, self.profitAllocated)
|
|
349
|
+
originalDueAt: date = at(self.plan.dates, self.position), dueAt: date?, nextDueAt: date?, previousDueAt: date?, prior: integer = subtract(self.position, 1), previousPosition: integer = sum(self.prior, self.first), previousDate: date = at(self.plan.dates, self.previousPosition), extensionLimit: date = after(self.originalDueAt, max_extension), lossEligibleAt: date?
|
|
261
350
|
first: integer = divide(1, self.position)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
principal: money
|
|
265
|
-
profit: money = sum(self.profitBase, self.profitAdjustment)
|
|
351
|
+
principalBase: money?, principalAllocated: money?, principalRemainder: money?, principalAdjustment: money?
|
|
352
|
+
profitBase: money?, profitAllocated: money?, profitRemainder: money?, profitAdjustment: money?
|
|
353
|
+
principal: money, profit: money, instalment: money
|
|
266
354
|
principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)
|
|
267
355
|
payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?
|
|
268
|
-
paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000,
|
|
356
|
+
paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit_rate)
|
|
269
357
|
debt: account(borrower, claim, contra, "debt"), loss: account(capital, claim, "loss")
|
|
270
358
|
}
|
|
271
|
-
lifecycle { states: [draft, pending, due, paid, written_off], initial: draft }
|
|
359
|
+
lifecycle { states: [draft, pending, due, paid, written_off, cancelled], initial: draft }
|
|
272
360
|
action create {
|
|
273
361
|
actor: { parent: parent }
|
|
274
362
|
input { plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>, position: integer(1, months) }
|
|
275
363
|
set: { dueAt: { field: self.originalDueAt } }
|
|
364
|
+
when pricing is flat_total {
|
|
365
|
+
calculate: [
|
|
366
|
+
{ target: principalBase, op: divide, amount: { field: self.plan.principal }, divisor: { literal: months }, rounding: floor }
|
|
367
|
+
{ target: principalAllocated, op: multiply, amount: { field: self.principalBase }, units: { literal: months } }
|
|
368
|
+
{ target: principalRemainder, op: subtract, base: { field: self.plan.principal }, subtract: [{ field: self.principalAllocated }] }
|
|
369
|
+
{ target: principalAdjustment, op: multiply, amount: { field: self.principalRemainder }, units: { field: self.first } }
|
|
370
|
+
{ target: principal, op: sum, values: [{ field: self.principalBase }, { field: self.principalAdjustment }] }
|
|
371
|
+
{ target: profitBase, op: divide, amount: { field: self.plan.flatProfit }, divisor: { literal: months }, rounding: floor }
|
|
372
|
+
{ target: profitAllocated, op: multiply, amount: { field: self.profitBase }, units: { literal: months } }
|
|
373
|
+
{ target: profitRemainder, op: subtract, base: { field: self.plan.flatProfit }, subtract: [{ field: self.profitAllocated }] }
|
|
374
|
+
{ target: profitAdjustment, op: multiply, amount: { field: self.profitRemainder }, units: { field: self.first } }
|
|
375
|
+
{ target: profit, op: sum, values: [{ field: self.profitBase }, { field: self.profitAdjustment }] }
|
|
376
|
+
{ target: instalment, op: sum, values: [{ field: self.principal }, { field: self.profit }] }
|
|
377
|
+
]
|
|
378
|
+
}
|
|
379
|
+
when pricing is amortizing {
|
|
380
|
+
calculate: [
|
|
381
|
+
{ target: principal, op: annuity, amount: { field: self.plan.principal }, rate: { literal: profit_rate }, periods: { literal: months }, position: { field: self.position }, part: principal }
|
|
382
|
+
{ target: profit, op: annuity, amount: { field: self.plan.principal }, rate: { literal: profit_rate }, periods: { literal: months }, position: { field: self.position }, part: interest }
|
|
383
|
+
{ target: instalment, op: sum, values: [{ field: self.principal }, { field: self.profit }] }
|
|
384
|
+
]
|
|
385
|
+
}
|
|
276
386
|
invoke: [{ reference: self.id, action: refresh_loss_date, input: {} }]
|
|
277
387
|
requires self.plan in [quoted]; requires unique "signed_position" on [self.plan, self.position]; requires self.borrowerLimit == self.plan.borrowerLimit; requires self.portfolioLimit == self.plan.portfolioLimit
|
|
388
|
+
requires self.originalDueAt > self.now; requires self.originalDueAt >= self.previousDate
|
|
278
389
|
}
|
|
390
|
+
action check_order { from: draft, to: draft, actor: { parent: parent }, requires self.originalDueAt > self.previousDate }
|
|
279
391
|
action quote_pending {
|
|
280
392
|
from: pending, to: pending, actor: { parent: payoff_quote }, input { quote: ref<payoff_quote> }
|
|
281
393
|
requires input.quote.plan == self.plan
|
|
@@ -310,6 +422,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
310
422
|
}
|
|
311
423
|
action activate { allowZero: true,
|
|
312
424
|
from: draft, to: pending, actor: { parent: parent }
|
|
425
|
+
requires self.dueAt > self.now
|
|
313
426
|
moves self.principal from self.debt to self.principalReceivable economics { purpose: internal, sourceParty: borrower }
|
|
314
427
|
moves self.profit from self.debt to self.profitReceivable economics { purpose: internal, sourceParty: borrower }
|
|
315
428
|
when profit_earned is at_disbursement { moves self.profit from self.profitReceivable to self.profitEarned economics { purpose: internal, sourceParty: programOperator } }
|
|
@@ -356,7 +469,10 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
356
469
|
calculate: [
|
|
357
470
|
{ target: collectibleTotal, op: sum, values: [{ field: self.principalReceivable.balance }, { field: self.collectibleProfit }] }
|
|
358
471
|
{ target: paymentBudget, op: minimum, values: [{ field: input.payment.held.balance }, { field: self.collectibleTotal }] }
|
|
359
|
-
|
|
472
|
+
]
|
|
473
|
+
when pricing is flat_total { calculate: [{ target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { literal: 10000 }, denominator: { field: self.ratioDenominator }, rounding: floor }] }
|
|
474
|
+
when pricing is amortizing { calculate: [{ target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { field: self.principal }, denominator: { field: self.instalment }, rounding: floor }] }
|
|
475
|
+
calculate: [
|
|
360
476
|
{ target: principalBasePayment, op: minimum, values: [{ field: self.principalRatio }, { field: self.principalReceivable.balance }] }
|
|
361
477
|
{ target: profitBudget, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.principalBasePayment }] }
|
|
362
478
|
{ target: profitPayment, op: minimum, values: [{ field: self.profitBudget }, { field: self.collectibleProfit }] }
|
|
@@ -387,6 +503,10 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
387
503
|
from: [pending, due], to: written_off, actor: { parent: parent }
|
|
388
504
|
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 }
|
|
389
505
|
}
|
|
506
|
+
action cancel { allowZero: true,
|
|
507
|
+
from: [pending, due], to: cancelled, actor: { parent: parent }
|
|
508
|
+
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 }
|
|
509
|
+
}
|
|
390
510
|
}
|
|
391
511
|
recognition: {
|
|
392
512
|
fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }
|
|
@@ -434,7 +554,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
434
554
|
invoke: [{ reference: self.plan, action: record_payment, input: { identity: { field: self.paymentIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned } } }]
|
|
435
555
|
}
|
|
436
556
|
action refund {
|
|
437
|
-
from: paid, to: refunded, actor: { party: capital }
|
|
557
|
+
from: paid, to: refunded, actor: { party: capital }, deadline: { at: self.plan.refundUntil }
|
|
438
558
|
requires unique "settlement_consumption" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]
|
|
439
559
|
requires count of { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } } == 1
|
|
440
560
|
invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } }, action: refund, input: {} }]
|
|
@@ -467,7 +587,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
467
587
|
actor: { parent: slice }
|
|
468
588
|
input { slice: ref<slice>, payment: ref<payment>, limit: money }
|
|
469
589
|
requires unique "profit_receipt" on [self.slice, self.payment]
|
|
470
|
-
moves self.amount from self.payment.held to self.slice.plan.profitIncome capture "cashReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose:
|
|
590
|
+
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 } }
|
|
471
591
|
when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
472
592
|
when profit_earned is by_schedule { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
473
593
|
when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
@@ -519,6 +639,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
519
639
|
}
|
|
520
640
|
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
521
641
|
action cancel { from: proposed, to: waived }
|
|
642
|
+
action cancel_undelivered { allowZero: true, from: [proposed, assessed], to: waived, actor: { parent: on }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
522
643
|
action waive_for_amendment {
|
|
523
644
|
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
524
645
|
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
@@ -526,7 +647,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
526
647
|
}
|
|
527
648
|
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 } }
|
|
528
649
|
action refund {
|
|
529
|
-
from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }
|
|
650
|
+
from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }, deadline: { at: self.plan.refundUntil }
|
|
530
651
|
requires input.payment in [paid, refunded]
|
|
531
652
|
requires input.payment.plan == self.plan
|
|
532
653
|
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: {} }]
|
|
@@ -555,7 +676,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
555
676
|
receipt: {
|
|
556
677
|
fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
557
678
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
558
|
-
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:
|
|
679
|
+
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 } }
|
|
559
680
|
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 } }
|
|
560
681
|
}
|
|
561
682
|
|
|
@@ -586,6 +707,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
586
707
|
}
|
|
587
708
|
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
588
709
|
action cancel { from: proposed, to: waived }
|
|
710
|
+
action cancel_undelivered { allowZero: true, from: [proposed, assessed], to: waived, actor: { parent: on }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
589
711
|
action waive_for_amendment {
|
|
590
712
|
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
591
713
|
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
@@ -593,7 +715,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
593
715
|
}
|
|
594
716
|
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 } }
|
|
595
717
|
action refund {
|
|
596
|
-
from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }
|
|
718
|
+
from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }, deadline: { at: self.plan.refundUntil }
|
|
597
719
|
requires input.payment in [paid, refunded]
|
|
598
720
|
requires input.payment.plan == self.plan
|
|
599
721
|
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: {} }]
|
|
@@ -622,7 +744,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
622
744
|
receipt: {
|
|
623
745
|
fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
624
746
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
625
|
-
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:
|
|
747
|
+
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 } }
|
|
626
748
|
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 } }
|
|
627
749
|
}
|
|
628
750
|
}
|
|
@@ -637,7 +759,7 @@ instrument limits(per_borrower: money, borrower: party = party(person), active_p
|
|
|
637
759
|
fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }
|
|
638
760
|
lifecycle { states: [pending, approved], initial: pending }
|
|
639
761
|
action create { requires unique "borrower_limit" on [self.borrower] }
|
|
640
|
-
action approve { from: pending, to: approved }
|
|
762
|
+
action approve { from: pending, to: approved, actor: { party: programOperator } }
|
|
641
763
|
}
|
|
642
764
|
|
|
643
765
|
instrument portfolio_limit(limit: money) {
|
|
@@ -647,7 +769,7 @@ instrument portfolio_limit(limit: money) {
|
|
|
647
769
|
fields { key: text = "portfolio", limit: money = limit }
|
|
648
770
|
lifecycle { states: [pending, approved], initial: pending }
|
|
649
771
|
action create { requires unique "portfolio_limit" on [self.key] }
|
|
650
|
-
action approve { from: pending, to: approved }
|
|
772
|
+
action approve { from: pending, to: approved, actor: { party: programOperator } }
|
|
651
773
|
}
|
|
652
774
|
|
|
653
775
|
instrument credit_line(borrower: party = programOperator, adapter: text, limit: money, expires: date) {
|