@hyperscale0/hsx 1.0.338 → 1.0.340
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/ast.d.ts +1 -0
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js.map +1 -1
- package/dist/src/attachment-economics.d.ts +19 -0
- package/dist/src/attachment-economics.d.ts.map +1 -1
- package/dist/src/attachment-economics.js +101 -3
- package/dist/src/attachment-economics.js.map +1 -1
- package/dist/src/binding-contract.d.ts.map +1 -1
- package/dist/src/binding-contract.js +10 -4
- package/dist/src/binding-contract.js.map +1 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +36 -8
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +1 -1
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +3 -1
- package/dist/src/headers.js.map +1 -1
- package/dist/src/parse.js +13 -1
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.js +6 -6
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/template-guide.d.ts.map +1 -1
- package/dist/src/template-guide.js +2 -1
- package/dist/src/template-guide.js.map +1 -1
- package/docs/README.md +5 -2
- package/examples/camera-rental.hsx +4 -2
- package/package.json +4 -4
- package/src/ast.ts +7 -1
- package/src/attachment-economics.ts +127 -2
- package/src/binding-contract.ts +13 -6
- package/src/compile.ts +63 -9
- package/src/examples-bundle.ts +1 -1
- package/src/headers.ts +6 -1
- package/src/parse.ts +16 -1
- package/src/std-bundle.ts +6 -6
- package/src/template-guide.ts +2 -1
- package/std/collections.hsx +4 -4
- package/std/financing.hsx +461 -266
- package/std/insurance.hsx +21 -5
- package/std/lending.hsx +13 -9
- package/std/money.hsx +88 -32
- package/std/reporting.hsx +6 -6
package/std/insurance.hsx
CHANGED
|
@@ -16,6 +16,9 @@ header insurance
|
|
|
16
16
|
// Cover.cancel invokes refund only on paid slices, before each startsAt. It returns
|
|
17
17
|
// insurerPremium from insurer and commission from broker to holder. Active or
|
|
18
18
|
// expired periods are excluded; insufficient recipient funds refuse the reversal.
|
|
19
|
+
// Each refund is two transfers, so cancel refunds 126 paid slices per operation.
|
|
20
|
+
// A cover with more reads cancelling, and its cancel_step clock refunds the next
|
|
21
|
+
// 126 a second later until none remain; then it reads cancelled.
|
|
19
22
|
// Slice refund is parent-owned, not a public alias. Expose cover.cancel instead.
|
|
20
23
|
// Claims require active cover. Expose claim.create, approve, deny and pay as needed.
|
|
21
24
|
// Inspector approves OR denies from submitted. After approval only pay or clock
|
|
@@ -25,21 +28,34 @@ header insurance
|
|
|
25
28
|
|
|
26
29
|
// Cost: each slice collection or refund declares two transfers. Claim approval
|
|
27
30
|
// reserves once; pay posts once and expiry voids once. Cover cancellation adds
|
|
28
|
-
//
|
|
31
|
+
// at most 252 transfers per step. Run hsx cost for composed counts, not provider prices.
|
|
29
32
|
|
|
30
33
|
instrument cover(holder: party, adapter: text, commission: percent = 0%, broker: party = programOperator, covers: ref?, premium: money = runtime, claim_limit: money = runtime) {
|
|
31
34
|
summary: "Recurring cover bound to an outside insurer across an ADL boundary."
|
|
32
|
-
fields { covers: ref<covers>?, holder: account of holder, insurer: account(adapter(adapter), cash, "premium"), expiresAt: date, claimLimit: money = claim_limit }
|
|
33
|
-
lifecycle { states: [pending, active, cancelled, expired], initial: pending }
|
|
35
|
+
fields { covers: ref<covers>?, holder: account of holder, insurer: account(adapter(adapter), cash, "premium"), expiresAt: date, claimLimit: money = claim_limit, cancelAt: date?, slicesLeft: integer? }
|
|
36
|
+
lifecycle { states: [pending, active, cancelling, cancelled, expired], initial: pending }
|
|
34
37
|
action create { actor: { party: holder } }
|
|
35
38
|
action activate {
|
|
36
39
|
from: pending, to: active, actor: { party: programOperator }
|
|
37
40
|
subject { adapter: adapter }
|
|
38
41
|
}
|
|
39
42
|
action cancel {
|
|
40
|
-
from: active, to:
|
|
41
|
-
invoke: [{
|
|
43
|
+
from: active, to: cancelling, actor: { party: holder }
|
|
44
|
+
invoke: [{ reference: self.id, action: cancel_batch, input: {} }]
|
|
42
45
|
}
|
|
46
|
+
action cancel_step { from: cancelling, to: preserve, actor: clock, due: { at: self.cancelAt }, invoke: [{ reference: self.id, action: cancel_batch, input: {} }] }
|
|
47
|
+
action cancel_batch {
|
|
48
|
+
from: cancelling, to: preserve, actor: { parent: current() }
|
|
49
|
+
calculate: [{ target: cancelAt, op: shift, date: { field: self.now }, milliseconds: { literal: 1000 }, direction: after }]
|
|
50
|
+
invoke: [{ selection: { instrument: slice, reference: "cover", anchor: self.id, states: [paid], limit: 126, batch: true, order: [startsAt] }, action: refund, input: {} }]
|
|
51
|
+
invoke: [{ reference: self.id, action: cancel_check, input: {} }]
|
|
52
|
+
}
|
|
53
|
+
action cancel_check {
|
|
54
|
+
from: cancelling, to: preserve, actor: { parent: current() }
|
|
55
|
+
calculate: [{ target: slicesLeft, op: aggregate, selection: { instrument: slice, reference: "cover", anchor: self.id, states: [paid], limit: 1, batch: true }, measure: count }]
|
|
56
|
+
invoke: [{ reference: self.id, action: finish_cancel, guard: { kind: compare, left: { field: self.slicesLeft }, operator: "==", right: { literal: 0 } }, input: {} }]
|
|
57
|
+
}
|
|
58
|
+
action finish_cancel { from: cancelling, to: cancelled, actor: { parent: current() } }
|
|
43
59
|
action expire { from: active, to: expired, actor: clock, due: { at: self.expiresAt } }
|
|
44
60
|
records {
|
|
45
61
|
slice: {
|
package/std/lending.hsx
CHANGED
|
@@ -26,6 +26,9 @@ header lending
|
|
|
26
26
|
// Cost: round.create declares one owned account. Close transfers its balance
|
|
27
27
|
// and invokes commitments; failure invokes their refunds. Distribution adds moves
|
|
28
28
|
// and bounded share invocations. Run hsx cost for counts after policy selection.
|
|
29
|
+
// A round takes at most 248 commitments, so failure refunds them in 248 ledger
|
|
30
|
+
// transfers and a cash distribution pays every share in 253, the most one
|
|
31
|
+
// ledger request takes.
|
|
29
32
|
|
|
30
33
|
instrument round(borrower: party, plan: ref<financing.installments> = object(financing.installments), operator: party = programOperator, minimum_ticket: money = 100 SAR, investor_cap: percent = 20%, commitments: ref<lending.commitment> = object(lending.commitment)) {
|
|
31
34
|
summary: "A threshold funding round; failed rounds return each original wallet contribution."
|
|
@@ -38,14 +41,14 @@ instrument round(borrower: party, plan: ref<financing.installments> = object(fin
|
|
|
38
41
|
action create { actor: { party: operator }, requires unique "funding_plan" on [self.plan]; requires self.borrower == self.plan.borrower }
|
|
39
42
|
action close {
|
|
40
43
|
from: open, to: funded, actor: { party: operator }
|
|
41
|
-
requires sum "amount" of { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit:
|
|
44
|
+
requires sum "amount" of { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 248 } == self.price
|
|
42
45
|
requires self.held.balance == self.price
|
|
43
46
|
moves self.price from self.held to self.plan.capital economics { purpose: principal, sourceParty: programOperator }
|
|
44
|
-
invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit:
|
|
47
|
+
invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 248 }, action: fund, input: {} }]
|
|
45
48
|
}
|
|
46
49
|
action fail {
|
|
47
50
|
from: open, to: failed, actor: clock, due: { at: self.closesAt }
|
|
48
|
-
invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit:
|
|
51
|
+
invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 248 }, action: refund, input: {} }]
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -53,8 +56,9 @@ instrument commitment(round: ref<lending.round>, wallet: ref<wallet.balance>, in
|
|
|
53
56
|
summary: "One investor's original-wallet contribution, capped cumulatively within the round."
|
|
54
57
|
fields { round: ref<round>, wallet: ref<wallet>, investor: account of investor, amount: money, loss: account(investor, claim, "loss") }
|
|
55
58
|
lifecycle { states: [committed, funded, withdrawn, refunded], initial: committed }
|
|
56
|
-
invariants sum "amount" of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit:
|
|
57
|
-
invariants sum "amount" of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit:
|
|
59
|
+
invariants sum "amount" of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit: 248, where: { investor: { field: self.investor } } } <= self.round.maximumTicket
|
|
60
|
+
invariants sum "amount" of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit: 248 } <= self.round.price
|
|
61
|
+
invariants count of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit: 248 } <= 248
|
|
58
62
|
action create {
|
|
59
63
|
actor: { party: investor }
|
|
60
64
|
requires self.round in [open]
|
|
@@ -108,9 +112,9 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
|
|
|
108
112
|
action distribute_cash { allowZero: true,
|
|
109
113
|
from: cash_ready, to: distributed, actor: { party: operator }
|
|
110
114
|
requires self.source in [recorded]; requires unique "settlement_consumption" on [self.source.plan, self.source.identity]
|
|
111
|
-
requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit:
|
|
115
|
+
requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 248 } == self.round.price
|
|
112
116
|
moves self.principal from self.source.capital to self.held; moves self.profit from self.source.profitIncome to self.held; moves self.charge from self.held to programOperator economics { purpose: internal, sourceParty: programOperator }; moves self.vat from self.held to programTax economics { purpose: pass_through, sourceParty: programOperator }
|
|
113
|
-
invoke: [{ reference: self.source, action: distribute, input: {} }, { selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit:
|
|
117
|
+
invoke: [{ reference: self.source, action: distribute, input: {} }, { selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 248 }, action: cash, input: {} }, { reference: self.id, action: residual_cash, input: {} }]
|
|
114
118
|
}
|
|
115
119
|
action residual_cash { allowZero: true,
|
|
116
120
|
from: distributed, to: distributed, actor: { parent: current() }
|
|
@@ -119,9 +123,9 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
|
|
|
119
123
|
action distribute_loss { allowZero: true,
|
|
120
124
|
from: loss_ready, to: distributed, actor: { party: operator }
|
|
121
125
|
requires self.source in [recorded]; requires unique "settlement_consumption" on [self.source.plan, self.source.identity]
|
|
122
|
-
requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit:
|
|
126
|
+
requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 248 } == self.round.price
|
|
123
127
|
moves self.net from self.source.lossAccount to self.losses
|
|
124
|
-
invoke: [{ reference: self.source, action: distribute, input: {} }, { selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit:
|
|
128
|
+
invoke: [{ reference: self.source, action: distribute, input: {} }, { selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 248 }, action: loss, input: {} }, { reference: self.id, action: residual_loss, input: {} }]
|
|
125
129
|
}
|
|
126
130
|
action residual_loss { allowZero: true,
|
|
127
131
|
from: distributed, to: distributed, actor: { parent: current() }
|
package/std/money.hsx
CHANGED
|
@@ -13,17 +13,21 @@ header money
|
|
|
13
13
|
// Bind released_by: payer for an escrow-style hold that only the payer releases.
|
|
14
14
|
// Only the payee refunds. A hold has no dispute or damage decision.
|
|
15
15
|
// The payee proposes a schedule and only the payer activates or cancels it; the
|
|
16
|
-
// payee stops it. Without every,
|
|
17
|
-
// dates
|
|
18
|
-
// charges amount, the first at startsAt, and paying one
|
|
19
|
-
// one piece waits ahead. Months step from startsAt and
|
|
20
|
-
// create refuses a startsAt in the past. An authored
|
|
21
|
-
// pieces; without one it runs until stopped or
|
|
22
|
-
//
|
|
23
|
-
//
|
|
16
|
+
// payee stops it. Without every, count is required and create makes count
|
|
17
|
+
// occurrences on the listed dates that sum to amount. With every (1 month, 2
|
|
18
|
+
// weeks), each occurrence charges amount, the first at startsAt, and paying one
|
|
19
|
+
// creates the next, so only one piece waits ahead. Months step from startsAt and
|
|
20
|
+
// clamp to the month's end. create refuses a startsAt in the past. An authored
|
|
21
|
+
// count ends it after count pieces; without one it runs until stopped or
|
|
22
|
+
// cancelled. The schedule keeps count as a field only when it is set, so reads
|
|
23
|
+
// name a piece "2 of 12" or, open-ended, "#2". Each due occurrence pays
|
|
24
|
+
// only while the schedule is active, and one clock drain posts at most the 253
|
|
25
|
+
// ledger transfers one request takes, 253 one-transfer pieces; the next drain
|
|
26
|
+
// continues a plan that fell further behind.
|
|
24
27
|
// A pool accepts whole contributions only up to its remaining target and at most
|
|
25
|
-
//
|
|
26
|
-
//
|
|
28
|
+
// 253 paid records, so a failure refunds every contributor in one ledger request.
|
|
29
|
+
// Expose pay to release the exact target. Clock failure refunds original
|
|
30
|
+
// contributors; excess acceptance and partial contribution are absent.
|
|
27
31
|
// A swap reserves both legs before exchange or voids reservations on expiry.
|
|
28
32
|
// Payout instruct reserves across the selected adapter; confirm/reject require
|
|
29
33
|
// matching terminal instruction evidence. An acknowledgement cannot settle it.
|
|
@@ -32,15 +36,30 @@ header money
|
|
|
32
36
|
// most 366 billed events across every metered attachment on the mandate may sum
|
|
33
37
|
// their payer debit, buyer fee and tax included, to its limit. The payer revokes
|
|
34
38
|
// it to stop billing. Only the payee cancels its own unbilled usage.
|
|
35
|
-
// The
|
|
36
|
-
// record's own due field. Once dueAt
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
39
|
+
// The company starts a late fee against its dueAt, so late tracking never waits
|
|
40
|
+
// on the payer; rename can bind dueAt to the record's own due field. Once dueAt
|
|
41
|
+
// plus grace plus one period has passed, the clock charges amount from payer to
|
|
42
|
+
// payee for each whole period, and the last charge takes only what is left under
|
|
43
|
+
// cap; then the fee is capped. Charge k is due at dueAt plus grace plus k
|
|
44
|
+
// periods. create copies dueAt. When any action on the record changes the bound
|
|
45
|
+
// field, follow runs on the fee in the same request and reads the field as it
|
|
46
|
+
// stands; the payee's reschedule runs follow directly. Follow keeps the charges
|
|
47
|
+
// already posted and moves the rest later, never earlier, because fees run from
|
|
48
|
+
// the dueAt set at create; past ones post on the next clock run. The clock never
|
|
49
|
+
// runs twice at one instant, so follow also needs the next charge after the last
|
|
50
|
+
// posted one. A move that breaks either rule refuses reschedule, while another
|
|
51
|
+
// action's change leaves the fee where it was and still runs. Period counts
|
|
52
|
+
// whole seconds. The payee runs stop when the obligation is met. Stop keeps what
|
|
53
|
+
// was owed: each charge due at or before it still posts, and the fee settles
|
|
54
|
+
// until the last of those posts, then stops. A period still running at the stop
|
|
55
|
+
// is not charged. A charge the payer cannot fund refuses as clock work and
|
|
56
|
+
// charging waits for its retry; the payee runs waive to forgive what is still
|
|
57
|
+
// owed. Grace is at least one second; there are no percentage or interest-style
|
|
58
|
+
// fees. Bind a hold's late_fee to the late fee on its object and refund also
|
|
59
|
+
// runs stop on that fee while it is running, so returning the deposit ends both.
|
|
60
|
+
// The link sits on refund because every good return refunds, and a fee already
|
|
61
|
+
// capped or stopped is skipped rather than blocking the refund. The hold links
|
|
62
|
+
// the fee at create, so the fee is agreed first, with the same payer and payee.
|
|
44
63
|
|
|
45
64
|
// Cost: transfer.pay declares one transfer. Hold.create declares one owned account;
|
|
46
65
|
// fund, release and refund each declare one transfer. Schedules and pools add bounded
|
|
@@ -59,11 +78,19 @@ instrument transfer(payer: party, payee: party, amount: money = runtime) {
|
|
|
59
78
|
action cancel { from: pending, to: cancelled, actor: { party: payer } }
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
instrument hold(payer: party, payee: party, amount: money = runtime, released_by: party = payee) {
|
|
81
|
+
instrument hold(payer: party, payee: party, amount: money = runtime, released_by: party = payee, late_fee: ref<money.late_fee>?) {
|
|
63
82
|
summary: "Hold the declared amount until release or refund."
|
|
64
|
-
fields {
|
|
83
|
+
fields {
|
|
84
|
+
payer: account of payer, payee: account of payee, amount: money = amount, price: money = self.amount, held: account of self
|
|
85
|
+
when late_fee is set { lateFee: ref<late_fee> }
|
|
86
|
+
}
|
|
65
87
|
lifecycle { states: [pending, funded, released, refunded, cancelled], initial: pending }
|
|
66
|
-
action create {
|
|
88
|
+
action create {
|
|
89
|
+
when late_fee is set {
|
|
90
|
+
requires self.lateFee.payer == self.payer
|
|
91
|
+
requires self.lateFee.payee == self.payee
|
|
92
|
+
}
|
|
93
|
+
}
|
|
67
94
|
action fund {
|
|
68
95
|
from: pending, to: funded, actor: { party: payer }
|
|
69
96
|
moves self.amount from payer to self.held economics { purpose: principal, sourceParty: payer }
|
|
@@ -79,6 +106,7 @@ instrument hold(payer: party, payee: party, amount: money = runtime, released_by
|
|
|
79
106
|
action refund {
|
|
80
107
|
from: funded, to: refunded, actor: { party: payee }
|
|
81
108
|
moves self.amount from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
109
|
+
when late_fee is set { invoke: [{ selection: { instrument: late_fee, reference: "payer", anchor: self.payer, states: [running], limit: 1, where: { id: { field: self.lateFee } } }, action: stop, input: {} }] }
|
|
82
110
|
}
|
|
83
111
|
action cancel { from: pending, to: cancelled }
|
|
84
112
|
}
|
|
@@ -92,12 +120,22 @@ instrument split(payer: party, amount: money = runtime, shares: split = { progra
|
|
|
92
120
|
action cancel { from: pending, to: cancelled }
|
|
93
121
|
}
|
|
94
122
|
|
|
95
|
-
instrument schedule(payer: party, payee: party, amount: money = runtime, count: integer(1, 366)
|
|
96
|
-
summary: "Pieces due on listed dates that sum to the total, or one amount each period from a start date."
|
|
123
|
+
instrument schedule(payer: party, payee: party, amount: money = runtime, count: integer(1, 366)?, every: period?) {
|
|
124
|
+
summary: "Pieces due on listed dates that sum to the total, or one amount each period from a start date until stopped, or for count periods."
|
|
125
|
+
dependencies {
|
|
126
|
+
count {
|
|
127
|
+
selector: every, is: unset
|
|
128
|
+
message: "Attachment `{attachment}` charges on a list of dates, so it needs `count`, the number of dates."
|
|
129
|
+
fix: "Add `count: 3` for three dated pieces, or add `every: 1 month` to charge each month until stopped or cancelled."
|
|
130
|
+
}
|
|
131
|
+
}
|
|
97
132
|
fields {
|
|
98
133
|
amount: money = amount
|
|
99
134
|
when every is unset { count: integer = count, dates: list(date, count) }
|
|
100
|
-
when every is set {
|
|
135
|
+
when every is set {
|
|
136
|
+
startsAt: date
|
|
137
|
+
when count is set { count: integer = count }
|
|
138
|
+
}
|
|
101
139
|
}
|
|
102
140
|
lifecycle { states: [draft, active, complete, cancelled], initial: draft }
|
|
103
141
|
action create {
|
|
@@ -167,13 +205,13 @@ instrument pool(payer: party, payee: party, target: money, closes: date) {
|
|
|
167
205
|
action fail {
|
|
168
206
|
from: open, to: failed, actor: clock, due: { at: self.closesAt }
|
|
169
207
|
requires self.held.balance < self.target
|
|
170
|
-
invoke: [{ selection: { instrument: contribution, reference: "pool", anchor: self.id, states: [paid], limit:
|
|
208
|
+
invoke: [{ selection: { instrument: contribution, reference: "pool", anchor: self.id, states: [paid], limit: 253 }, action: refund, input: {} }]
|
|
171
209
|
}
|
|
172
210
|
records {
|
|
173
211
|
contribution: {
|
|
174
212
|
fields { pool: ref<parent>, payer: account of payer, amount: money, remaining: money? }
|
|
175
213
|
lifecycle { states: [pending, paid, refunded], initial: pending }
|
|
176
|
-
invariants count of { instrument: current(), reference: "pool", anchor: self.pool, states: [paid], limit:
|
|
214
|
+
invariants count of { instrument: current(), reference: "pool", anchor: self.pool, states: [paid], limit: 253 } <= 253
|
|
177
215
|
action create { requires self.pool in [open] }
|
|
178
216
|
action pay { from: pending, to: paid, actor: { party: payer }, deadline: { at: self.pool.closesAt }, requires self.pool in [open], calculate: [{ target: remaining, op: subtract, base: { field: self.pool.target }, subtract: [{ field: self.pool.held.balance }] }], requires self.amount <= self.remaining, moves self.amount from self.payer to self.pool.held economics { purpose: principal, sourceParty: payer } }
|
|
179
217
|
action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from self.pool.held to self.payer economics { purpose: principal, sourceParty: programOperator } }
|
|
@@ -239,24 +277,42 @@ instrument late_fee(payer: party, payee: party, amount: money, cap: money, perio
|
|
|
239
277
|
fields {
|
|
240
278
|
payer: account of payer, payee: account of payee, amount: money = amount, cap: money = cap, ceiling: money = multiply(self.amount, 366)
|
|
241
279
|
dueAt: date, graceEndsAt: date?, firstAt: date?, nextAt: date?, followingAt: date?, charge: money?, left: money?, remaining: money?, stoppedAt: date?
|
|
280
|
+
stride: integer?, strideAt: date?, elapsed: integer?, lastAt: date?
|
|
242
281
|
}
|
|
243
282
|
lifecycle { states: [running, settling, capped, stopped], initial: running }
|
|
244
283
|
action create {
|
|
245
|
-
actor: { party:
|
|
284
|
+
actor: { party: programOperator }
|
|
246
285
|
requires self.amount > "0"
|
|
247
286
|
requires self.cap >= self.amount
|
|
248
287
|
requires self.cap <= self.ceiling
|
|
249
|
-
calculate: [{ target: graceEndsAt, op: shift, date: { field: self.dueAt }, milliseconds: { literal: grace }, direction: after }, { target: firstAt, op: shift, date: { field: self.graceEndsAt }, milliseconds: { literal: period }, direction: after }]
|
|
250
|
-
|
|
288
|
+
calculate: [{ target: graceEndsAt, op: shift, date: { field: self.dueAt }, milliseconds: { literal: grace }, direction: after }, { target: firstAt, op: shift, date: { field: self.graceEndsAt }, milliseconds: { literal: period }, direction: after }, { target: stride, op: divide, amount: { literal: period }, divisor: { literal: 1000 }, rounding: floor }, { target: strideAt, op: step, date: { field: self.graceEndsAt }, period: { literal: "PT1S" }, times: { field: self.stride } }, { target: elapsed, op: sum, values: [{ literal: 0 }] }]
|
|
289
|
+
requires self.strideAt == self.firstAt
|
|
290
|
+
set: { nextAt: { field: self.firstAt }, left: { field: self.cap }, lastAt: { literal: "1970-01-01T00:00:00.000Z" } }
|
|
251
291
|
}
|
|
252
292
|
action charge {
|
|
253
293
|
summary: "Charge late fee"
|
|
254
294
|
from: running, to: running, actor: clock, due: { at: self.nextAt }
|
|
255
|
-
calculate: [{ target: charge, op: minimum, values: [{ field: self.amount }, { field: self.left }] }, { target: remaining, op: subtract, base: { field: self.left }, subtract: [{ field: self.charge }] }, { target: followingAt, op: shift, date: { field: self.nextAt }, milliseconds: { literal: period }, direction: after }]
|
|
256
|
-
set: { left: { field: self.remaining }, nextAt: { field: self.followingAt } }
|
|
295
|
+
calculate: [{ target: charge, op: minimum, values: [{ field: self.amount }, { field: self.left }] }, { target: remaining, op: subtract, base: { field: self.left }, subtract: [{ field: self.charge }] }, { target: followingAt, op: shift, date: { field: self.nextAt }, milliseconds: { literal: period }, direction: after }, { target: elapsed, op: sum, values: [{ field: self.elapsed }, { field: self.stride }] }]
|
|
296
|
+
set: { left: { field: self.remaining }, nextAt: { field: self.followingAt }, lastAt: { field: self.nextAt } }
|
|
257
297
|
moves self.charge from payer to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: payer } }
|
|
258
298
|
invoke: [{ selection: { instrument: current(), reference: "payer", anchor: self.payer, states: [running], limit: 1, where: { id: { field: self.id }, left: { literal: "0" } } }, action: finish, input: {} }]
|
|
259
299
|
}
|
|
300
|
+
action reschedule {
|
|
301
|
+
summary: "Move the due time"
|
|
302
|
+
from: running, to: running, actor: { party: payee }
|
|
303
|
+
subject { dueAt: date }
|
|
304
|
+
invoke: [{ reference: self.id, action: follow, input: {} }]
|
|
305
|
+
}
|
|
306
|
+
action follow {
|
|
307
|
+
summary: "Follow the due time"
|
|
308
|
+
from: running, to: running, follows: dueAt
|
|
309
|
+
subject { dueAt: date }
|
|
310
|
+
// Fees run from the dueAt set at create, so the date only moves later.
|
|
311
|
+
requires subject.dueAt >= self.dueAt
|
|
312
|
+
calculate: [{ target: graceEndsAt, op: shift, date: { field: subject.dueAt }, milliseconds: { literal: grace }, direction: after }, { target: firstAt, op: shift, date: { field: self.graceEndsAt }, milliseconds: { literal: period }, direction: after }, { target: followingAt, op: step, date: { field: self.firstAt }, period: { literal: "PT1S" }, times: { field: self.elapsed } }]
|
|
313
|
+
requires self.followingAt > self.lastAt
|
|
314
|
+
set: { dueAt: { field: subject.dueAt }, nextAt: { field: self.followingAt } }
|
|
315
|
+
}
|
|
260
316
|
action collect {
|
|
261
317
|
summary: "Collect owed late fee"
|
|
262
318
|
from: settling, to: settling, actor: clock, due: { at: self.nextAt }
|
package/std/reporting.hsx
CHANGED
|
@@ -119,19 +119,19 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
|
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
name: "principal",
|
|
122
|
-
field: "
|
|
122
|
+
field: "openPrincipal",
|
|
123
123
|
type: { kind: "money", currency: "SAR" },
|
|
124
124
|
required: true
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
name: "profit",
|
|
128
|
-
field: "
|
|
128
|
+
field: "openProfit",
|
|
129
129
|
type: { kind: "money", currency: "SAR" },
|
|
130
130
|
required: true
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
name: "earnedProfit",
|
|
134
|
-
field: "
|
|
134
|
+
field: "earnedProfit",
|
|
135
135
|
type: { kind: "money", currency: "SAR" },
|
|
136
136
|
required: true
|
|
137
137
|
},
|
|
@@ -468,19 +468,19 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
|
|
|
468
468
|
},
|
|
469
469
|
{
|
|
470
470
|
name: "principal",
|
|
471
|
-
field: "
|
|
471
|
+
field: "openPrincipal",
|
|
472
472
|
type: { kind: "money", currency: "SAR" },
|
|
473
473
|
required: true
|
|
474
474
|
},
|
|
475
475
|
{
|
|
476
476
|
name: "profit",
|
|
477
|
-
field: "
|
|
477
|
+
field: "openProfit",
|
|
478
478
|
type: { kind: "money", currency: "SAR" },
|
|
479
479
|
required: true
|
|
480
480
|
},
|
|
481
481
|
{
|
|
482
482
|
name: "earnedProfit",
|
|
483
|
-
field: "
|
|
483
|
+
field: "earnedProfit",
|
|
484
484
|
type: { kind: "money", currency: "SAR" },
|
|
485
485
|
required: true
|
|
486
486
|
},
|