@hyperscale0/hsx 3.2.0 → 4.0.0
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/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/dist/src/ast.d.ts +17 -2
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js.map +1 -1
- package/dist/src/cli.js +1 -1
- package/dist/src/compile.d.ts +6 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +1194 -97
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +1 -1
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +52 -9
- package/dist/src/cost.js.map +1 -1
- package/dist/src/headers.d.ts +2 -2
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +3 -2
- package/dist/src/headers.js.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lex.d.ts +1 -1
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +5 -0
- package/dist/src/lex.js.map +1 -1
- package/dist/src/parse.js +91 -5
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +10 -9
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/version.d.ts +2 -2
- package/dist/src/version.js +2 -2
- package/docs/README.md +51 -27
- package/docs/headers.md +43 -40
- package/examples/cost-table.json +40 -324
- package/examples/library.hsx +12 -57
- package/package.json +7 -5
- package/src/ast.ts +11 -1
- package/src/cli.ts +1 -1
- package/src/compile.ts +1630 -114
- package/src/cost.ts +60 -16
- package/src/headers.ts +3 -2
- package/src/index.ts +12 -1
- package/src/lex.ts +5 -0
- package/src/parse.ts +87 -5
- package/src/std-bundle.ts +10 -9
- package/src/version.ts +2 -2
- package/std/approvals.hsx +3 -3
- package/std/collections.hsx +45 -4
- package/std/escrow.hsx +17 -8
- package/std/financing.hsx +292 -42
- package/std/insurance.hsx +4 -5
- package/std/lending.hsx +7 -8
- package/std/marketplace.hsx +90 -6
- package/std/money.hsx +15 -49
- package/std/reporting.hsx +1286 -0
- package/std/travel.hsx +5 -6
package/std/financing.hsx
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
header financing
|
|
2
2
|
|
|
3
|
-
instrument installments(months: integer(1, 366), profit: percent, disburse_to: enum(funds, borrower) = funds, 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%, down_payment: percent = 0%, funds: ref, approval: approval, borrower: party = party(person), capital: party = programOperator,
|
|
3
|
+
instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3, profit: percent, disburse_to: enum(funds, borrower) = funds, 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 = object(escrow.hold), approval: approval = programOperator, borrower: party = party(person), capital: party = programOperator, share: percent = 0%, limits: ref<financing.limits> = object(financing.limits)) {
|
|
4
|
+
revisioned: true
|
|
5
|
+
familyRevision: 1
|
|
4
6
|
summary: "Funding, profit recognition and payment order chosen by the program."
|
|
5
7
|
fields {
|
|
6
8
|
funds: ref<funds>, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>
|
|
7
9
|
months: integer = months, dates: list(date, months)
|
|
8
|
-
capital: account(capital, cash, "capital"), profitIncome: account(
|
|
9
|
-
principalReturned: money?, profitReturned: money?, lossRecorded: money?
|
|
10
|
+
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), loss: account(capital, claim, "loss")
|
|
11
|
+
downPaymentRemaining: money?, downPaymentCredit: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?
|
|
10
12
|
price: money = self.funds.price
|
|
11
13
|
downPayment: money = rate(self.price, down_payment)
|
|
12
14
|
principal: money = subtract(self.price, self.downPayment)
|
|
@@ -17,35 +19,49 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
17
19
|
invariants count of { instrument: all(financing.installments), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [active], limit: 366 } <= self.borrowerLimit.activePlans
|
|
18
20
|
invariants sum "principalReceivable.balance" of { instrument: all(financing.installments.slice), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [pending, due, paid], limit: 366 } <= self.borrowerLimit.limit
|
|
19
21
|
invariants sum "principalReceivable.balance" of { instrument: all(financing.installments.slice), reference: "portfolioLimit", anchor: self.portfolioLimit, states: [pending, due, paid], limit: 366 } <= self.portfolioLimit.limit
|
|
20
|
-
action create {
|
|
22
|
+
action create {
|
|
23
|
+
requires self.borrower == self.funds.payer; requires self.borrowerLimit.borrower == self.funds.payer
|
|
24
|
+
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 } } }]
|
|
25
|
+
}
|
|
21
26
|
action sign {
|
|
22
|
-
from: quoted, to: signed, input { signedOffer: text, affordabilityReference: text }
|
|
27
|
+
from: quoted, to: signed, actor: { party: borrower }, input { signedOffer: text, affordabilityReference: text }
|
|
23
28
|
requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366 } == self.months
|
|
24
29
|
}
|
|
25
30
|
action disburse {
|
|
26
31
|
from: signed, to: active
|
|
27
|
-
requires approval by approval
|
|
32
|
+
requires approval by approval protectedRequest self differentFromInitiator true
|
|
28
33
|
requires self.borrowerLimit in [approved]
|
|
29
34
|
requires self.portfolioLimit in [approved]
|
|
30
35
|
when disburse_to is funds {
|
|
31
36
|
requires self.funds in [pending]
|
|
32
|
-
|
|
33
|
-
moves self.principal from self.capital to self.funds.held
|
|
37
|
+
invoke: [{ reference: self.id, action: fund_down_payment, input: {} }]
|
|
34
38
|
}
|
|
35
39
|
when disburse_to is borrower { moves self.principal from self.capital to self.borrower }
|
|
36
40
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]
|
|
37
41
|
when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }
|
|
38
42
|
}
|
|
43
|
+
action fund_down_payment {
|
|
44
|
+
from: active, to: active, actor: { parent: current() }
|
|
45
|
+
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 }] }]
|
|
46
|
+
moves self.downPaymentRemaining from self.funds.payer to self.funds.held
|
|
47
|
+
moves self.principal from self.capital to self.funds.held
|
|
48
|
+
}
|
|
39
49
|
action complete {
|
|
40
50
|
from: active, to: paid
|
|
41
51
|
requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 } == 0
|
|
52
|
+
requires sum "receivable.balance" of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
53
|
+
requires sum "receivable.balance" of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
42
54
|
}
|
|
43
55
|
action restore { from: [active, paid], to: active, actor: { parent: payment } }
|
|
44
56
|
action payoff {
|
|
45
|
-
from: active, to: paid
|
|
57
|
+
from: active, to: paid, actor: { party: borrower }
|
|
58
|
+
input { quote: ref<payoff_quote> }
|
|
59
|
+
requires input.quote.plan == self.id; requires input.quote.revision == self.productRevision
|
|
60
|
+
deadline: { at: input.quote.expiresAt }
|
|
46
61
|
calculate: [
|
|
47
62
|
{ target: "principalReturned", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
48
63
|
]
|
|
64
|
+
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: {} }]
|
|
49
65
|
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: {} }]
|
|
50
66
|
}
|
|
51
67
|
action record_payoff {
|
|
@@ -54,11 +70,11 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
54
70
|
invoke: [{ instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: self.settlementIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned }, loss: { literal: "0" }, kind: { literal: "cash" } } }]
|
|
55
71
|
}
|
|
56
72
|
action write_off {
|
|
57
|
-
from: active, to: written_off
|
|
73
|
+
from: active, to: written_off, actor: { parent: write_off_request }
|
|
58
74
|
input { overdueSlice: ref<slice> }
|
|
59
75
|
calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }]
|
|
60
76
|
due: { at: input.overdueSlice.lossEligibleAt }
|
|
61
|
-
requires
|
|
77
|
+
requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > "0"
|
|
62
78
|
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" } } }]
|
|
63
79
|
}
|
|
64
80
|
action record_payment {
|
|
@@ -67,21 +83,125 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
67
83
|
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" } } }]
|
|
68
84
|
}
|
|
69
85
|
action cancel { from: quoted, to: cancelled }
|
|
86
|
+
action void { from: signed, to: cancelled, actor: { party: borrower } }
|
|
70
87
|
records {
|
|
71
|
-
|
|
72
|
-
fields { plan: ref<parent>,
|
|
73
|
-
|
|
88
|
+
write_off_request: {
|
|
89
|
+
fields { plan: ref<parent>, overdueSlice: ref<slice> }
|
|
90
|
+
lifecycle { states: [requested, applied, cancelled], initial: requested }
|
|
91
|
+
action create { requires self.overdueSlice.plan == self.plan; requires self.plan in [active] }
|
|
92
|
+
action apply {
|
|
93
|
+
from: requested, to: applied
|
|
94
|
+
requires approval by approval protectedRequest self differentFromInitiator true
|
|
95
|
+
invoke: [{ reference: self.plan, action: write_off, input: { overdueSlice: { field: self.overdueSlice } } }]
|
|
96
|
+
}
|
|
97
|
+
action cancel { from: requested, to: cancelled }
|
|
98
|
+
}
|
|
99
|
+
payoff_quote: {
|
|
100
|
+
fields {
|
|
101
|
+
plan: ref<parent>, revision: text?, effectiveAt: date?, expiresAt: date = after(self.createdAt, payoff_quote_expiry)
|
|
102
|
+
principal: money?, earnedProfit: money?, unearnedProfit: money?, rebate: money?, fines: money?, costs: money?, gross: money?, amount: money?
|
|
103
|
+
}
|
|
104
|
+
lifecycle { states: [quoted], initial: quoted }
|
|
105
|
+
action create {
|
|
106
|
+
requires self.plan in [active]
|
|
107
|
+
set: { revision: { field: self.productRevision }, effectiveAt: { field: self.now } }
|
|
108
|
+
invoke: [
|
|
109
|
+
{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending], limit: 366 }, action: quote_pending, input: { quote: { field: self.id } } }
|
|
110
|
+
{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [due], limit: 366 }, action: quote_due, input: { quote: { field: self.id } } }
|
|
111
|
+
{ reference: self.id, action: record, input: {} }
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
action record {
|
|
115
|
+
from: quoted, to: quoted, actor: { parent: current() }
|
|
116
|
+
calculate: [
|
|
117
|
+
{ target: principal, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
118
|
+
{ target: earnedProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "profitEarned.balance" } }
|
|
119
|
+
{ target: unearnedProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "profitReceivable.balance" } }
|
|
120
|
+
{ target: rebate, op: aggregate, selection: { instrument: quote_piece, reference: "quote", anchor: self.id, states: [recorded], limit: 366 }, measure: { sum: "rebate" } }
|
|
121
|
+
{ target: fines, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
122
|
+
{ target: costs, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
123
|
+
{ target: gross, op: sum, values: [{ field: self.principal }, { field: self.earnedProfit }, { field: self.unearnedProfit }, { field: self.fines }, { field: self.costs }] }
|
|
124
|
+
{ target: amount, op: subtract, base: { field: self.gross }, subtract: [{ field: self.rebate }] }
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
quote_piece: {
|
|
129
|
+
fields { quote: ref<payoff_quote>, slice: ref<slice>, rebate: money = rate(self.slice.profitReceivable.balance, payoff_rebate) }
|
|
74
130
|
lifecycle { states: [recorded], initial: recorded }
|
|
131
|
+
action create { actor: { parent: slice }, input { quote: ref<payoff_quote>, slice: ref<slice> }, requires unique "payoff_slice_quote" on [self.quote, self.slice] }
|
|
132
|
+
}
|
|
133
|
+
amendment: {
|
|
134
|
+
fields {
|
|
135
|
+
plan: ref<parent>, dates: list(date, months), agreement: text, waiverPercent: percent, waiverEvidence: text, maximumWaiver: percent = waiver_limit
|
|
136
|
+
revision: text?, acceptedAt: date?, staleReason: text?, expiresAt: date = after(self.createdAt, amendment_expiry)
|
|
137
|
+
fineDisposition: enum(carry, require_waiver) = assessed_fines, addedProfit: money = 0 SAR
|
|
138
|
+
principalSnapshot: money?, earnedProfitSnapshot: money?, unearnedProfitSnapshot: money?, finesSnapshot: money?, costsSnapshot: money?
|
|
139
|
+
extension: duration = max_extension, maximum: integer = max_amendments
|
|
140
|
+
}
|
|
141
|
+
lifecycle { states: [proposed, applied, declined, withdrawn, expired, superseded], initial: proposed }
|
|
142
|
+
action create {
|
|
143
|
+
actor: { party: borrower }
|
|
144
|
+
requires self.plan in [active]; requires self.waiverPercent <= self.maximumWaiver
|
|
145
|
+
set: { revision: { field: self.productRevision } }
|
|
146
|
+
calculate: [
|
|
147
|
+
{ target: principalSnapshot, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
148
|
+
{ target: earnedProfitSnapshot, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "profitEarned.balance" } }
|
|
149
|
+
{ target: unearnedProfitSnapshot, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "profitReceivable.balance" } }
|
|
150
|
+
{ target: finesSnapshot, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
151
|
+
{ target: costsSnapshot, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
152
|
+
]
|
|
153
|
+
requires count of { instrument: current(), reference: "plan", anchor: self.plan, states: [applied], limit: 366 } < self.maximum
|
|
154
|
+
invoke: [{ range: { count: { literal: months }, maximum: months, bind: position }, instrument: proposed_date, action: create, input: { amendment: { field: self.id }, position: { field: position } } }, { selection: { instrument: proposed_date, reference: "amendment", anchor: self.id, states: [recorded], limit: 366, where: { first: { literal: 0 } } }, action: check_order, input: {} }]
|
|
155
|
+
}
|
|
156
|
+
action accept {
|
|
157
|
+
from: proposed, to: applied, actor: { party: borrower }, deadline: { at: self.expiresAt }
|
|
158
|
+
requires self.plan in [active]; requires self.revision == self.productRevision
|
|
159
|
+
requires approval by approval protectedRequest self differentFromInitiator true
|
|
160
|
+
requires count of { instrument: current(), reference: "plan", anchor: self.plan, states: [applied], limit: 366 } < self.maximum
|
|
161
|
+
set: { acceptedAt: { field: self.now } }
|
|
162
|
+
when assessed_fines is require_waiver { requires self.waiverPercent == 10000 }
|
|
163
|
+
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 } } }]
|
|
164
|
+
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, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [proposed], limit: 366 }, action: cancel, input: {} }]
|
|
165
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: amend, input: { amendment: { field: self.id } } }]
|
|
166
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [paid], limit: 366 }, action: verify_fixed_date, input: { amendment: { field: self.id } } }]
|
|
167
|
+
}
|
|
168
|
+
action withdraw { from: proposed, to: withdrawn, actor: { party: borrower } }
|
|
169
|
+
action decline { from: proposed, to: declined, actor: { party: approval } }
|
|
170
|
+
action expire { from: proposed, to: expired, actor: clock, due: { at: self.expiresAt } }
|
|
171
|
+
action supersede { from: proposed, to: superseded, requires self.revision != self.productRevision, set: { staleReason: { literal: "economic_revision_changed" } } }
|
|
172
|
+
}
|
|
173
|
+
proposed_date: {
|
|
174
|
+
fields { amendment: ref<amendment>, position: integer(1, months), date: date = at(self.amendment.dates, self.position), first: integer = divide(1, self.position), prior: integer = subtract(self.position, 1), previousPosition: integer = sum(self.prior, self.first), previous: date = at(self.amendment.dates, self.previousPosition) }
|
|
175
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
176
|
+
action create { actor: { parent: amendment }, input { amendment: ref<amendment>, position: integer(1, months) }, requires self.date >= self.previous }
|
|
177
|
+
action check_order { from: recorded, to: recorded, actor: { parent: amendment }, requires self.date > self.previous }
|
|
178
|
+
}
|
|
179
|
+
date_change: {
|
|
180
|
+
fields { amendment: ref<amendment>, slice: ref<slice>, oldDate: date, newDate: date }
|
|
181
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
182
|
+
action create { actor: { parent: slice }, input { amendment: ref<amendment>, slice: ref<slice>, oldDate: date, newDate: date }, requires unique "amended_slice" on [self.amendment, self.slice] }
|
|
183
|
+
}
|
|
184
|
+
settlement: {
|
|
185
|
+
fields { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss), share: money?, retainedProfit: money?
|
|
186
|
+
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), lossAccount: account(capital, claim, "loss") }
|
|
187
|
+
lifecycle { states: [recorded, refunded], initial: recorded }
|
|
75
188
|
action create {
|
|
76
189
|
actor: { parent: parent }
|
|
77
190
|
input { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss) }
|
|
78
191
|
requires unique "settlement_identity" on [self.plan, self.identity]
|
|
192
|
+
calculate: [{ target: "share", op: "rate", base: { field: self.profit }, bps: { literal: share }, rounding: "floor" }, { target: retainedProfit, op: "subtract", base: { field: self.profit }, subtract: [{ field: self.share }] }]
|
|
193
|
+
moves self.share from self.profitIncome to self.allocatedPayable
|
|
194
|
+
}
|
|
195
|
+
action refund {
|
|
196
|
+
from: recorded, to: refunded, actor: { parent: payment }
|
|
197
|
+
moves self.share from self.allocatedPayable to self.profitIncome
|
|
79
198
|
}
|
|
80
199
|
}
|
|
81
200
|
slice: {
|
|
201
|
+
revisioned: true
|
|
82
202
|
fields {
|
|
83
203
|
plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>, position: integer(1, months)
|
|
84
|
-
|
|
204
|
+
originalDueAt: date = at(self.plan.dates, self.position), dueAt: date?, nextDueAt: date?, previousDueAt: date?, previousPosition: integer?, previousDate: date?, extensionLimit: date = after(self.originalDueAt, max_extension), lossEligibleAt: date?
|
|
85
205
|
principalBase: money = divide(self.plan.principal, months)
|
|
86
206
|
principalAllocated: money = multiply(self.principalBase, months)
|
|
87
207
|
principalRemainder: money = subtract(self.plan.principal, self.principalAllocated)
|
|
@@ -100,8 +220,44 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
100
220
|
}
|
|
101
221
|
lifecycle { states: [draft, pending, due, paid, written_off], initial: draft }
|
|
102
222
|
action create {
|
|
223
|
+
actor: { parent: parent }
|
|
224
|
+
input { plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>, position: integer(1, months) }
|
|
225
|
+
set: { dueAt: { field: self.originalDueAt } }
|
|
226
|
+
invoke: [{ reference: self.id, action: refresh_loss_date, input: {} }]
|
|
103
227
|
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
|
|
104
228
|
}
|
|
229
|
+
action quote_pending {
|
|
230
|
+
from: pending, to: pending, actor: { parent: payoff_quote }, input { quote: ref<payoff_quote> }
|
|
231
|
+
requires input.quote.plan == self.plan
|
|
232
|
+
invoke: [{ instrument: quote_piece, action: create, input: { quote: { field: input.quote }, slice: { field: self.id } } }]
|
|
233
|
+
}
|
|
234
|
+
action quote_due {
|
|
235
|
+
from: due, to: due, actor: { parent: payoff_quote }, input { quote: ref<payoff_quote> }
|
|
236
|
+
requires input.quote.plan == self.plan
|
|
237
|
+
invoke: [{ instrument: quote_piece, action: create, input: { quote: { field: input.quote }, slice: { field: self.id } } }]
|
|
238
|
+
}
|
|
239
|
+
action refresh_loss_date {
|
|
240
|
+
from: [draft, pending, due], to: draft, actor: { parent: current() }
|
|
241
|
+
calculate: [{ target: lossEligibleAt, op: shift, date: { field: self.dueAt }, milliseconds: { literal: write_off_after }, direction: after }]
|
|
242
|
+
}
|
|
243
|
+
action amend {
|
|
244
|
+
from: [pending, due], to: pending, actor: { parent: amendment }, input { amendment: ref<amendment> }
|
|
245
|
+
requires input.amendment.plan == self.plan
|
|
246
|
+
when allow_overdue is blocked { requires self.dueAt > self.now }
|
|
247
|
+
calculate: [
|
|
248
|
+
{ target: nextDueAt, op: at, list: input.amendment.dates, position: { field: self.position } }
|
|
249
|
+
{ target: lossEligibleAt, op: shift, date: { field: self.nextDueAt }, milliseconds: { literal: write_off_after }, direction: after }
|
|
250
|
+
]
|
|
251
|
+
requires self.nextDueAt > self.now; requires self.nextDueAt >= self.dueAt; requires self.nextDueAt <= self.extensionLimit
|
|
252
|
+
set: { previousDueAt: { field: self.dueAt }, dueAt: { field: self.nextDueAt } }
|
|
253
|
+
invoke: [{ instrument: date_change, action: create, input: { amendment: { field: input.amendment }, slice: { field: self.id }, oldDate: { field: self.previousDueAt }, newDate: { field: self.nextDueAt } } }]
|
|
254
|
+
when profit_earned is by_schedule { invoke: [{ selection: { instrument: recognition, reference: "slice", anchor: self.id, states: [pending], limit: 1 }, action: reschedule, input: {} }] }
|
|
255
|
+
}
|
|
256
|
+
action verify_fixed_date {
|
|
257
|
+
from: paid, to: paid, actor: { parent: amendment }, input { amendment: ref<amendment> }
|
|
258
|
+
calculate: [{ target: nextDueAt, op: at, list: input.amendment.dates, position: { field: self.position } }]
|
|
259
|
+
requires self.nextDueAt == self.dueAt
|
|
260
|
+
}
|
|
105
261
|
action activate {
|
|
106
262
|
from: draft, to: pending, actor: { parent: parent }
|
|
107
263
|
moves self.principal from self.debt to self.principalReceivable
|
|
@@ -113,21 +269,36 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
113
269
|
from: pending, to: due, actor: clock, due: { at: self.dueAt }
|
|
114
270
|
}
|
|
115
271
|
action collect {
|
|
116
|
-
from: [pending, due], to:
|
|
272
|
+
from: [pending, due], to: preserve, actor: { parent: payment }
|
|
117
273
|
input { payment: ref<payment> }
|
|
118
274
|
requires input.payment.plan == self.plan
|
|
119
275
|
when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }
|
|
120
276
|
when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
121
277
|
when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
122
|
-
when apply is fines_profit_principal {
|
|
123
|
-
invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }, { instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }]
|
|
124
|
-
}
|
|
125
278
|
when apply is principal_profit {
|
|
126
|
-
invoke: [
|
|
279
|
+
invoke: [
|
|
280
|
+
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }
|
|
281
|
+
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
|
|
282
|
+
{ 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: {} }
|
|
283
|
+
]
|
|
127
284
|
}
|
|
128
285
|
}
|
|
286
|
+
action collect_waterfall {
|
|
287
|
+
from: [pending, due], to: preserve, actor: { parent: payment }, input { payment: ref<payment> }
|
|
288
|
+
requires input.payment.plan == self.plan
|
|
289
|
+
when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }
|
|
290
|
+
when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
291
|
+
when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
292
|
+
invoke: [
|
|
293
|
+
{ 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 } } }
|
|
294
|
+
{ 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 } } }
|
|
295
|
+
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
|
|
296
|
+
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }
|
|
297
|
+
{ 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: {} }
|
|
298
|
+
]
|
|
299
|
+
}
|
|
129
300
|
action collect_pro_rata {
|
|
130
|
-
from: [pending, due], to:
|
|
301
|
+
from: [pending, due], to: preserve, actor: { parent: payment }, input { payment: ref<payment> }
|
|
131
302
|
requires input.payment.plan == self.plan
|
|
132
303
|
when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }
|
|
133
304
|
when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
@@ -141,13 +312,17 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
141
312
|
{ target: profitPayment, op: minimum, values: [{ field: self.profitBudget }, { field: self.collectibleProfit }] }
|
|
142
313
|
{ target: principalPayment, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.profitPayment }] }
|
|
143
314
|
]
|
|
144
|
-
invoke: [
|
|
315
|
+
invoke: [
|
|
316
|
+
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalPayment } } }
|
|
317
|
+
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }
|
|
318
|
+
{ 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: {} }
|
|
319
|
+
]
|
|
145
320
|
}
|
|
146
321
|
action complete {
|
|
147
322
|
from: [pending, due], to: paid
|
|
148
323
|
requires self.principalReceivable.balance == "0"; requires self.profitReceivable.balance == "0"; requires self.profitEarned.balance == "0"
|
|
149
324
|
}
|
|
150
|
-
action restore { from: [pending, due, paid], to:
|
|
325
|
+
action restore { from: [pending, due, paid], to: pending, actor: { parent: payment } }
|
|
151
326
|
action payoff {
|
|
152
327
|
from: [pending, due], to: paid, actor: { parent: parent }, input { identity: text }
|
|
153
328
|
set: { payoffIdentity: { field: input.identity } }
|
|
@@ -167,16 +342,18 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
167
342
|
fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }
|
|
168
343
|
lifecycle { states: [pending, recognised], initial: pending }
|
|
169
344
|
action create { actor: { parent: slice }, input { slice: ref<slice> }, requires unique "profit_recognition" on [self.slice] }
|
|
345
|
+
action reschedule { from: pending, to: pending, actor: { parent: slice } }
|
|
170
346
|
action mature { from: pending, to: recognised, actor: clock, due: { at: self.dueAt }, moves self.slice.profitReceivable.balance from self.slice.profitReceivable to self.slice.profitEarned }
|
|
171
347
|
}
|
|
172
348
|
payment: {
|
|
349
|
+
revisioned: true
|
|
173
350
|
fields { plan: ref<parent>, amount: money, paymentIdentity: text, held: account of self
|
|
174
|
-
capital: account(capital, cash, "capital"), profitIncome: account(profit_to, cash, "profitIncome"), loss: account(capital, claim, "loss")
|
|
175
351
|
outstandingPrincipal: money?, outstandingProfit: money?, outstandingFines: money?, outstandingCosts: money?, outstandingTotal: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money? }
|
|
176
352
|
|
|
177
353
|
lifecycle { states: [pending, paid, refunded], initial: pending }
|
|
178
354
|
action create { input { plan: ref<parent>, amount: money, paymentIdentity: text } requires self.plan in [active]; requires unique "payment_identity" on [self.plan, self.paymentIdentity] }
|
|
179
355
|
action pay {
|
|
356
|
+
expansionLimit: 8192
|
|
180
357
|
from: pending, to: paid
|
|
181
358
|
calculate: [{ target: outstandingPrincipal, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "principalReceivable.balance" } }]
|
|
182
359
|
when profit_earned is on_payment { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "profitReceivable.balance" } }] }
|
|
@@ -190,14 +367,13 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
190
367
|
requires self.plan in [active]; requires self.amount <= self.outstandingTotal
|
|
191
368
|
moves self.amount from self.plan.borrower to self.held
|
|
192
369
|
when apply is principal_profit {
|
|
193
|
-
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect, input: { payment: { field: self.id } } }]
|
|
370
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect, guard: { kind: compare, left: { field: self.held.balance }, operator: ">", right: { literal: "0" } }, input: { payment: { field: self.id } } }]
|
|
194
371
|
}
|
|
195
372
|
when apply is fines_profit_principal {
|
|
196
|
-
invoke: [{ selection: { instrument:
|
|
197
|
-
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect, input: { payment: { field: self.id } } }]
|
|
373
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect_waterfall, guard: { kind: compare, left: { field: self.held.balance }, operator: ">", right: { literal: "0" } }, input: { payment: { field: self.id } } }]
|
|
198
374
|
}
|
|
199
375
|
when apply is pro_rata {
|
|
200
|
-
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect_pro_rata, input: { payment: { field: self.id } } }]
|
|
376
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect_pro_rata, guard: { kind: compare, left: { field: self.held.balance }, operator: ">", right: { literal: "0" } }, input: { payment: { field: self.id } } }]
|
|
201
377
|
}
|
|
202
378
|
invoke: [{ reference: self.id, action: record, input: {} }]
|
|
203
379
|
}
|
|
@@ -210,6 +386,8 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
210
386
|
action refund {
|
|
211
387
|
from: paid, to: refunded
|
|
212
388
|
requires unique "settlement_consumption" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]
|
|
389
|
+
requires count of { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } } == 1
|
|
390
|
+
invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } }, action: refund, input: {} }]
|
|
213
391
|
when apply is fines_profit_principal {
|
|
214
392
|
invoke: [{ selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: refund, input: { payment: { field: self.id } } }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: refund, input: { payment: { field: self.id } } }]
|
|
215
393
|
}
|
|
@@ -217,9 +395,10 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
217
395
|
}
|
|
218
396
|
}
|
|
219
397
|
principal_piece: {
|
|
220
|
-
fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money
|
|
398
|
+
fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
|
|
221
399
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
222
400
|
action create {
|
|
401
|
+
calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.slice.principalReceivable.balance }, { field: self.limit }] }]
|
|
223
402
|
actor: { parent: slice }
|
|
224
403
|
input { slice: ref<slice>, payment: ref<payment>, limit: money }
|
|
225
404
|
requires unique "principal_receipt" on [self.slice, self.payment]
|
|
@@ -231,9 +410,10 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
231
410
|
}
|
|
232
411
|
}
|
|
233
412
|
profit_piece: {
|
|
234
|
-
fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money
|
|
413
|
+
fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
|
|
235
414
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
236
415
|
action create {
|
|
416
|
+
calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.limit }] }]
|
|
237
417
|
actor: { parent: slice }
|
|
238
418
|
input { slice: ref<slice>, payment: ref<payment>, limit: money }
|
|
239
419
|
requires unique "profit_receipt" on [self.slice, self.payment]
|
|
@@ -254,16 +434,18 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
254
434
|
}
|
|
255
435
|
|
|
256
436
|
instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d, fine: money = 50 SAR, cap: money = 25 SAR, fines_to: party = programOperator, costs_to: party = programOperator, approval: approval = programOperator, borrower: party = party(person)) {
|
|
437
|
+
revisioned: true
|
|
438
|
+
familyRevision: 1
|
|
257
439
|
summary: "Separate approved fine and evidenced recovery claims on an overdue installment."
|
|
258
440
|
fields {
|
|
259
|
-
plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date
|
|
441
|
+
plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date?
|
|
260
442
|
receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
|
|
261
443
|
}
|
|
262
444
|
lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }
|
|
263
|
-
action create { requires unique "late_assessment" on [self.slice]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan }
|
|
445
|
+
action create { 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 }
|
|
264
446
|
action assess {
|
|
265
447
|
from: proposed, to: assessed, due: { at: self.overdueAt }
|
|
266
|
-
requires self.slice.principalReceivable.balance > "0"; requires approval by approval
|
|
448
|
+
requires self.slice.principalReceivable.balance > "0"; requires approval by approval protectedRequest self differentFromInitiator true
|
|
267
449
|
moves self.fine from self.debt to self.receivable
|
|
268
450
|
}
|
|
269
451
|
action collect {
|
|
@@ -272,9 +454,20 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
272
454
|
requires input.payment.plan == self.plan
|
|
273
455
|
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: {} }]
|
|
274
456
|
}
|
|
457
|
+
action payoff {
|
|
458
|
+
from: [assessed, collected], to: collected
|
|
459
|
+
requires self.plan in [paid]
|
|
460
|
+
moves self.receivable.balance from self.plan.borrower to fines_to
|
|
461
|
+
moves self.receivable.balance from self.receivable to self.debt
|
|
462
|
+
}
|
|
275
463
|
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
276
464
|
action cancel { from: proposed, to: waived }
|
|
277
|
-
action
|
|
465
|
+
action waive_for_amendment {
|
|
466
|
+
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
467
|
+
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
468
|
+
invoke: [{ instrument: waiver, action: create, input: { assessment: { field: self.id }, amendment: { field: input.amendment } } }]
|
|
469
|
+
}
|
|
470
|
+
action waive { from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt }
|
|
278
471
|
action refund {
|
|
279
472
|
from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }
|
|
280
473
|
requires input.payment in [paid, refunded]
|
|
@@ -282,23 +475,45 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
282
475
|
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: {} }]
|
|
283
476
|
}
|
|
284
477
|
records {
|
|
478
|
+
waiver_request: {
|
|
479
|
+
fields { assessment: ref<parent>, reason: text, evidence: text }
|
|
480
|
+
lifecycle { states: [requested, applied, cancelled], initial: requested }
|
|
481
|
+
action create { requires self.assessment in [assessed, collected] }
|
|
482
|
+
action apply {
|
|
483
|
+
from: requested, to: applied, input { reason: text, evidence: text }
|
|
484
|
+
requires approval by approval protectedRequest self differentFromInitiator true
|
|
485
|
+
invoke: [{ reference: self.assessment, action: waive, input: { reason: { field: input.reason }, evidence: { field: input.evidence } } }]
|
|
486
|
+
}
|
|
487
|
+
action cancel { from: requested, to: cancelled }
|
|
488
|
+
}
|
|
489
|
+
waiver: {
|
|
490
|
+
fields { assessment: ref<parent>, amendment: ref<on.amendment>, amount: money? }
|
|
491
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
492
|
+
action create {
|
|
493
|
+
actor: { parent: parent }, input { assessment: ref<parent>, amendment: ref<on.amendment> }
|
|
494
|
+
calculate: [{ target: amount, op: rate, base: { field: self.assessment.receivable.balance }, bps: { field: self.amendment.waiverPercent }, rounding: floor }]
|
|
495
|
+
requires unique "amendment_waiver" on [self.assessment, self.amendment]
|
|
496
|
+
moves self.amount from self.assessment.receivable to self.assessment.debt
|
|
497
|
+
}
|
|
498
|
+
}
|
|
285
499
|
receipt: {
|
|
286
|
-
fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money
|
|
500
|
+
fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
287
501
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
288
|
-
action create { 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; moves self.amount from self.assessment.receivable to self.assessment.debt }
|
|
502
|
+
action create { 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; moves self.amount from self.assessment.receivable to self.assessment.debt }
|
|
289
503
|
action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }
|
|
290
504
|
}
|
|
291
505
|
|
|
292
506
|
cost: {
|
|
507
|
+
revisioned: true
|
|
293
508
|
fields {
|
|
294
|
-
plan: ref<on>, assessment: ref<parent>, overdueAt: date
|
|
509
|
+
plan: ref<on>, assessment: ref<parent>, overdueAt: date?, evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap)
|
|
295
510
|
receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
|
|
296
511
|
}
|
|
297
512
|
lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }
|
|
298
|
-
action create { requires unique "recovery_assessment" on [self.assessment]; requires self.plan == self.assessment.plan }
|
|
513
|
+
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 }
|
|
299
514
|
action assess {
|
|
300
515
|
from: proposed, to: assessed, due: { at: self.assessment.overdueAt }
|
|
301
|
-
requires self.assessment.slice.principalReceivable.balance > "0"; requires approval by approval
|
|
516
|
+
requires self.assessment.slice.principalReceivable.balance > "0"; requires approval by approval protectedRequest self differentFromInitiator true
|
|
302
517
|
moves self.amount from self.debt to self.receivable
|
|
303
518
|
}
|
|
304
519
|
action collect {
|
|
@@ -307,9 +522,20 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
307
522
|
requires input.payment.plan == self.plan
|
|
308
523
|
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: {} }]
|
|
309
524
|
}
|
|
525
|
+
action payoff {
|
|
526
|
+
from: [assessed, collected], to: collected
|
|
527
|
+
requires self.plan in [paid]
|
|
528
|
+
moves self.receivable.balance from self.plan.borrower to costs_to
|
|
529
|
+
moves self.receivable.balance from self.receivable to self.debt
|
|
530
|
+
}
|
|
310
531
|
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
311
532
|
action cancel { from: proposed, to: waived }
|
|
312
|
-
action
|
|
533
|
+
action waive_for_amendment {
|
|
534
|
+
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
535
|
+
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
536
|
+
invoke: [{ instrument: waiver, action: create, input: { assessment: { field: self.id }, amendment: { field: input.amendment } } }]
|
|
537
|
+
}
|
|
538
|
+
action waive { from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt }
|
|
313
539
|
action refund {
|
|
314
540
|
from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }
|
|
315
541
|
requires input.payment in [paid, refunded]
|
|
@@ -317,10 +543,31 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
317
543
|
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: {} }]
|
|
318
544
|
}
|
|
319
545
|
records {
|
|
546
|
+
waiver_request: {
|
|
547
|
+
fields { assessment: ref<parent>, reason: text, evidence: text }
|
|
548
|
+
lifecycle { states: [requested, applied, cancelled], initial: requested }
|
|
549
|
+
action create { requires self.assessment in [assessed, collected] }
|
|
550
|
+
action apply {
|
|
551
|
+
from: requested, to: applied, input { reason: text, evidence: text }
|
|
552
|
+
requires approval by approval protectedRequest self differentFromInitiator true
|
|
553
|
+
invoke: [{ reference: self.assessment, action: waive, input: { reason: { field: input.reason }, evidence: { field: input.evidence } } }]
|
|
554
|
+
}
|
|
555
|
+
action cancel { from: requested, to: cancelled }
|
|
556
|
+
}
|
|
557
|
+
waiver: {
|
|
558
|
+
fields { assessment: ref<parent>, amendment: ref<on.amendment>, amount: money? }
|
|
559
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
560
|
+
action create {
|
|
561
|
+
actor: { parent: parent }, input { assessment: ref<parent>, amendment: ref<on.amendment> }
|
|
562
|
+
calculate: [{ target: amount, op: rate, base: { field: self.assessment.receivable.balance }, bps: { field: self.amendment.waiverPercent }, rounding: floor }]
|
|
563
|
+
requires unique "amendment_waiver" on [self.assessment, self.amendment]
|
|
564
|
+
moves self.amount from self.assessment.receivable to self.assessment.debt
|
|
565
|
+
}
|
|
566
|
+
}
|
|
320
567
|
receipt: {
|
|
321
|
-
fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money
|
|
568
|
+
fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
322
569
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
323
|
-
action create { 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; moves self.amount from self.assessment.receivable to self.assessment.debt }
|
|
570
|
+
action create { 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; moves self.amount from self.assessment.receivable to self.assessment.debt }
|
|
324
571
|
action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }
|
|
325
572
|
}
|
|
326
573
|
}
|
|
@@ -329,6 +576,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
329
576
|
}
|
|
330
577
|
|
|
331
578
|
instrument limits(per_borrower: money, portfolio: money, borrower: party = party(person), active_plans: integer = 1) {
|
|
579
|
+
familyRevision: 1
|
|
332
580
|
summary: "One borrower ceiling and one shared portfolio ceiling."
|
|
333
581
|
fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }
|
|
334
582
|
lifecycle { states: [pending, approved], initial: pending }
|
|
@@ -345,6 +593,7 @@ instrument limits(per_borrower: money, portfolio: money, borrower: party = party
|
|
|
345
593
|
}
|
|
346
594
|
|
|
347
595
|
instrument credit_line(borrower: party, lender: party, limit: money, expires: date) {
|
|
596
|
+
familyRevision: 1
|
|
348
597
|
summary: "A revolving facility; each draw is a separate immutable advance."
|
|
349
598
|
fields { borrower: account of borrower, lender: account of lender, debt: account(borrower, claim, contra, "debt"), limit: money = limit, expiresAt: date = expires }
|
|
350
599
|
lifecycle { states: [pending, active, suspended, closed], initial: pending }
|
|
@@ -356,6 +605,7 @@ instrument credit_line(borrower: party, lender: party, limit: money, expires: da
|
|
|
356
605
|
}
|
|
357
606
|
|
|
358
607
|
instrument advance(line: ref<financing.credit_line>, amount: money = runtime) {
|
|
608
|
+
familyRevision: 1
|
|
359
609
|
summary: "A single facility draw with an immutable principal and full repayment."
|
|
360
610
|
fields { line: ref<line>, amount: money = amount, principalReceivable: account(self, claim) }
|
|
361
611
|
lifecycle { states: [pending, drawn, repaid, cancelled], initial: pending }
|
package/std/insurance.hsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
header insurance
|
|
2
2
|
|
|
3
|
-
instrument cover(holder: party, insurer: party, commission: percent = 0%, covers: ref, premium: money = runtime) {
|
|
3
|
+
instrument cover(holder: party, insurer: party, approval: approval = programOperator, commission: percent = 0%, covers: ref, premium: money = runtime) {
|
|
4
4
|
summary: "Recurring cover with separate premium slices and commission clawback."
|
|
5
5
|
fields { covers: ref<covers>, holder: account of holder, insurer: account of insurer, expiresAt: date }
|
|
6
6
|
lifecycle { states: [pending, active, cancelled, expired], initial: pending }
|
|
7
7
|
action create {}
|
|
8
|
-
action activate { from: pending, to: active,
|
|
8
|
+
action activate { from: pending, to: active, requires approval by approval protectedRequest self differentFromInitiator true }
|
|
9
9
|
action cancel {
|
|
10
10
|
from: active, to: cancelled
|
|
11
11
|
invoke: [{ selection: { instrument: slice, reference: "cover", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }]
|
|
@@ -17,7 +17,6 @@ instrument cover(holder: party, insurer: party, commission: percent = 0%, covers
|
|
|
17
17
|
fields {
|
|
18
18
|
cover: ref<parent>, covers: ref<covers>, premium: money = premium, startsAt: date, endsAt: date
|
|
19
19
|
commission: money = rate(self.premium, commission)
|
|
20
|
-
carrierNet: money = subtract(self.premium, self.commission)
|
|
21
20
|
}
|
|
22
21
|
lifecycle { states: [pending, paid, active, expired, refunded], initial: pending }
|
|
23
22
|
action create {
|
|
@@ -43,12 +42,12 @@ instrument cover(holder: party, insurer: party, commission: percent = 0%, covers
|
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
instrument claim(cover: ref<insurance.cover>, approved_by: approval) {
|
|
46
|
-
summary: "A
|
|
45
|
+
summary: "A claim decision reserves one claim payment before settlement."
|
|
47
46
|
fields { cover: ref<cover>, amount: money, expiresAt: date, evidence: text, reservation: text? }
|
|
48
47
|
lifecycle { states: [submitted, approved, paid, denied, expired], initial: submitted }
|
|
49
48
|
action create { requires self.cover in [active] }
|
|
50
49
|
action approve {
|
|
51
|
-
from: submitted, to: approved
|
|
50
|
+
from: submitted, to: approved
|
|
52
51
|
requires approval by approved_by
|
|
53
52
|
deadline: { at: self.expiresAt }
|
|
54
53
|
moves reserve self.amount from self.cover.insurer to self.cover.holder capture "reservation"
|