@hyperscale0/hsx 1.0.222 → 1.0.235
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 +6 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +3 -0
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +15 -15
- package/dist/src/examples-bundle.js.map +1 -1
- package/examples/cards.hsx +35 -18
- package/examples/collections.hsx +45 -20
- package/examples/escrow.hsx +23 -16
- package/examples/financing.hsx +32 -12
- package/examples/insurance.hsx +29 -14
- package/examples/lending.hsx +46 -20
- package/examples/library.hsx +68 -12
- package/examples/marketplace.hsx +26 -11
- package/examples/money.hsx +45 -10
- package/examples/rental-deposit.hsx +25 -13
- package/examples/repair-approval.hsx +69 -25
- package/examples/reporting.hsx +36 -10
- package/examples/savings.hsx +20 -10
- package/examples/serviced.hsx +91 -21
- package/examples/wallet.hsx +21 -9
- package/package.json +4 -4
- package/src/compile.ts +3 -0
- package/src/examples-bundle.ts +15 -15
package/examples/lending.hsx
CHANGED
|
@@ -1,57 +1,83 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Qassim bakery funding raises 30,000 SAR for ovens from investors who commit at least 1,000 SAR each.
|
|
2
|
+
// An unclosed round refunds its wallets at expiry; funded loans distribute repayments or record investor losses.
|
|
3
|
+
program community_business_lending "Qassim bakery funding"
|
|
3
4
|
use lending
|
|
4
5
|
use financing
|
|
5
6
|
use wallet
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
// Set price to 30,000 SAR. The bakery owns the record; investors act as actor.
|
|
9
|
+
// Company fee: 5% of collected profit, with 15% VAT on that fee, at distribution.
|
|
10
|
+
// Principal and wallet loads have fee 0% and tax 0%; no service is sold there.
|
|
11
|
+
object business "Bakery oven funding" {
|
|
8
12
|
fields { businessName: text, purpose: text, price: money }
|
|
9
|
-
columns: [businessName, purpose, price]
|
|
13
|
+
columns: [businessName, purpose, price, fundingClosesAt]
|
|
10
14
|
attach allowance = financing.limits {
|
|
11
|
-
expose create as create_allowance
|
|
12
15
|
borrower: owner, per_borrower: 100000 SAR
|
|
16
|
+
expose create as request_bakery_limit
|
|
13
17
|
expose approve as approve_borrower
|
|
14
18
|
}
|
|
15
19
|
attach budget = financing.portfolio_limit {
|
|
16
|
-
expose create as create_budget
|
|
17
20
|
limit: 2000000 SAR
|
|
21
|
+
expose create as create_funding_budget
|
|
18
22
|
expose approve as approve_budget
|
|
19
23
|
}
|
|
24
|
+
// Twelve future monthly dates, 6% annual profit on the declining balance.
|
|
25
|
+
// Close the fully subscribed round before separately disbursing the loan.
|
|
20
26
|
attach loan = financing.installments {
|
|
21
|
-
|
|
22
|
-
borrower: owner, capital: operator
|
|
27
|
+
borrower: owner, capital: operator, share: 0%
|
|
23
28
|
months: 12, pricing: amortizing, profit_rate: 6%, disburse_to: borrower
|
|
29
|
+
payoff_rebate: 100%, refund_within: 30d, write_off_after: 90d
|
|
24
30
|
limits: allowance, portfolio: budget
|
|
31
|
+
expose create as quote_bakery_loan
|
|
25
32
|
expose sign as sign_loan
|
|
26
33
|
expose disburse as release_loan
|
|
34
|
+
expose cancel as decline_loan_quote
|
|
35
|
+
expose void as cancel_unfunded_loan
|
|
27
36
|
expose payoff as repay_early
|
|
37
|
+
expose complete as close_repaid_loan
|
|
28
38
|
}
|
|
29
39
|
attach investor_balance = wallet.balance {
|
|
40
|
+
holder: actor, spend_limit: 7500 SAR, topup_fee: 0%
|
|
30
41
|
expose create as create_investor_balance
|
|
31
|
-
holder: actor
|
|
32
42
|
expose activate as open_wallet
|
|
33
43
|
expose topup as add_funds
|
|
44
|
+
expose withdraw as withdraw_available_funds
|
|
45
|
+
expose close as close_empty_wallet
|
|
34
46
|
}
|
|
35
47
|
attach funding = lending.round {
|
|
36
|
-
borrower: owner, plan: loan
|
|
37
|
-
|
|
38
|
-
expose
|
|
48
|
+
borrower: owner, plan: loan, minimum_ticket: 1000 SAR, investor_cap: 25%
|
|
49
|
+
rename { closesAt: fundingClosesAt }
|
|
50
|
+
expose create as open_funding_round
|
|
51
|
+
expose close as close_fully_funded_round
|
|
39
52
|
}
|
|
40
53
|
attach investment = lending.commitment {
|
|
41
54
|
round: funding, wallet: investor_balance, investor: actor
|
|
42
|
-
expose create as
|
|
43
|
-
expose withdraw as
|
|
55
|
+
expose create as commit_investment
|
|
56
|
+
expose withdraw as withdraw_before_close
|
|
44
57
|
}
|
|
58
|
+
// For each recorded cash settlement, prepare once and create one share per
|
|
59
|
+
// funded commitment. The fee applies to profit, never returned principal.
|
|
45
60
|
attach returns = lending.distribution {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
expose prepare_cash as
|
|
49
|
-
expose distribute_cash as
|
|
61
|
+
round: funding, receipt: loan.settlement, mode: cash, fee: 5%, tax: 15%
|
|
62
|
+
expose create as record_cash_settlement
|
|
63
|
+
expose prepare_cash as calculate_investor_returns
|
|
64
|
+
expose distribute_cash as pay_investor_wallets
|
|
65
|
+
}
|
|
66
|
+
// Loss distribution moves claims only. It does not debit another cash fee.
|
|
67
|
+
attach losses = lending.distribution {
|
|
68
|
+
round: funding, receipt: loan.settlement, mode: loss, fee: 0%, tax: 0%
|
|
69
|
+
expose create as record_loss_settlement
|
|
70
|
+
expose prepare_loss as calculate_investor_losses
|
|
71
|
+
expose distribute_loss as allocate_investor_losses
|
|
50
72
|
}
|
|
51
73
|
}
|
|
52
74
|
|
|
53
|
-
expose business.loan.payment.create as
|
|
75
|
+
expose business.loan.payment.create as record_repayment
|
|
54
76
|
expose business.loan.payment.pay as pay_instalment
|
|
55
|
-
expose business.loan.payment.refund as
|
|
77
|
+
expose business.loan.payment.refund as refund_mistaken_repayment
|
|
56
78
|
expose business.loan.payoff_quote.create as request_payoff_quote
|
|
79
|
+
expose business.loan.write_off_request.create as request_overdue_write_off
|
|
80
|
+
expose business.loan.write_off_request.apply as approve_overdue_write_off
|
|
81
|
+
expose business.loan.write_off_request.cancel as cancel_write_off_request
|
|
57
82
|
expose business.returns.share.create as create_return_share
|
|
83
|
+
expose business.losses.share.create as create_loss_share
|
package/examples/library.hsx
CHANGED
|
@@ -1,15 +1,71 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Riyadh used van finance funds a 34,500 SAR van over three monthly instalments.
|
|
2
|
+
// The dealer is paid after delivery and acceptance; a verified return unwinds financing before the buyer gets a refund.
|
|
3
|
+
program cars "Riyadh used van finance"
|
|
3
4
|
use escrow
|
|
4
5
|
use financing
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
party dealer: business
|
|
7
|
+
|
|
8
|
+
// Bind dealer to the vehicle seller. Set price to its gross 34,500 SAR invoice;
|
|
9
|
+
// vehicle VAT stays on that invoice. The platform fee is 2% plus 15% VAT
|
|
10
|
+
// on the fee: 690 SAR and 103.50 SAR. The dealer receives 33,706.50 SAR.
|
|
11
|
+
object car "Financed van purchase" {
|
|
12
|
+
fields { make: text, model: text, year: integer, vin: text, price: money }
|
|
13
|
+
columns: [make, model, year, vin, price]
|
|
14
|
+
attach sale = escrow.hold {
|
|
15
|
+
payer: actor, payee: dealer
|
|
16
|
+
fee: { seller: 2%, tax: 15% }
|
|
17
|
+
deliver_within: 7d, accept_within: 3d
|
|
18
|
+
expose create as agree_van_purchase
|
|
19
|
+
expose fund as pay_cash_instead
|
|
20
|
+
expose deliver as confirm_van_delivery
|
|
21
|
+
expose accept as accept_van
|
|
22
|
+
expose dispute as dispute_van_condition
|
|
23
|
+
expose verify_return as verify_returned_van
|
|
24
|
+
expose refund as refund_buyer_after_unwind
|
|
25
|
+
expose refund_undelivered as refund_undelivered_cash_purchase
|
|
26
|
+
expose settle_release as resolve_for_dealer
|
|
27
|
+
expose settle_return as resolve_for_buyer
|
|
28
|
+
expose settle_refund as finish_buyer_refund
|
|
29
|
+
expose cancel as cancel_unfunded_purchase
|
|
30
|
+
}
|
|
31
|
+
// Limits are shared across purchases. Approve each once before disbursement.
|
|
32
|
+
attach limits = financing.limits {
|
|
33
|
+
borrower: actor, per_borrower: 60000 SAR
|
|
34
|
+
expose create as request_borrower_limit
|
|
35
|
+
expose approve as approve_borrower_limit
|
|
36
|
+
}
|
|
37
|
+
attach ceiling = financing.portfolio_limit {
|
|
38
|
+
limit: 1500000 SAR
|
|
39
|
+
expose create as create_lending_budget
|
|
40
|
+
expose approve as approve_lending_budget
|
|
41
|
+
}
|
|
42
|
+
// At 34,500 SAR, 2.5% flat profit is 862.50 SAR for the whole term.
|
|
43
|
+
// Financing fee: 0%, tax: 0% on principal; no separate financing service fee.
|
|
44
|
+
// Supply three future monthly dates and a unique settlementIdentity.
|
|
45
|
+
attach plan = financing.installments {
|
|
46
|
+
borrower: actor, capital: operator, share: 0%
|
|
47
|
+
months: 3, pricing: flat_total, profit_rate: 2.5%
|
|
48
|
+
funds: sale, limits: limits, portfolio: ceiling
|
|
49
|
+
returned_goods: refund_all, refund_within: 30d, write_off_after: 90d
|
|
50
|
+
expose create as finance
|
|
51
|
+
expose sign as sign_finance_agreement
|
|
52
|
+
expose disburse as fund_van_escrow
|
|
53
|
+
expose cancel as decline_finance_quote
|
|
54
|
+
expose void as cancel_signed_finance
|
|
55
|
+
expose payoff as repay_early
|
|
56
|
+
expose complete as close_repaid_finance
|
|
57
|
+
// Refund paid instalments before unwinding an active plan. Then refund
|
|
58
|
+
// the escrow remainder. A live plan blocks the escrow's direct refund.
|
|
59
|
+
expose unwind as unwind_returned_van_finance
|
|
60
|
+
expose unwind_undelivered as unwind_missing_van_finance
|
|
61
|
+
expose unwind_paid as unwind_repaid_van_finance
|
|
62
|
+
}
|
|
15
63
|
}
|
|
64
|
+
|
|
65
|
+
expose car.plan.payment.create as record_repayment
|
|
66
|
+
expose car.plan.payment.pay as pay_instalment
|
|
67
|
+
expose car.plan.payment.refund as refund_mistaken_repayment
|
|
68
|
+
expose car.plan.payoff_quote.create as request_payoff_quote
|
|
69
|
+
expose car.plan.write_off_request.create as request_overdue_write_off
|
|
70
|
+
expose car.plan.write_off_request.apply as approve_overdue_write_off
|
|
71
|
+
expose car.plan.write_off_request.cancel as cancel_write_off_request
|
package/examples/marketplace.hsx
CHANGED
|
@@ -1,34 +1,49 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Riyadh design market sells a 2,000 SAR restaurant menu project with delivery in seven days.
|
|
2
|
+
// The company keeps 10% plus 15% VAT on its fee; the designer receives 1,770 SAR after client acceptance.
|
|
3
|
+
program freelance_marketplace "Riyadh design market"
|
|
3
4
|
use marketplace
|
|
4
5
|
use escrow
|
|
6
|
+
party designer: business
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
// Bind designer to the design studio. The client owns the project record.
|
|
9
|
+
// Set price to 2,000 SAR and use the same field for offer, order and escrow.
|
|
10
|
+
// Any VAT on the designer's work belongs on its own invoice.
|
|
11
|
+
object project "Menu design project" {
|
|
7
12
|
fields { brief: text, deliverable: text, price: money }
|
|
8
13
|
columns: [brief, deliverable, price]
|
|
9
14
|
attach offer = marketplace.listing {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
seller: designer
|
|
16
|
+
rename { title: deliverable, price: price }
|
|
17
|
+
expose create as draft_offer
|
|
13
18
|
expose publish as publish_offer
|
|
14
19
|
expose withdraw as withdraw_offer
|
|
20
|
+
expose sell as close_sold_offer
|
|
15
21
|
}
|
|
16
22
|
attach order = marketplace.order {
|
|
23
|
+
listing: offer, buyer: owner, fulfiller: designer
|
|
17
24
|
rename { price: price }
|
|
18
|
-
expose create as
|
|
19
|
-
listing: offer, buyer: actor, fulfiller: owner
|
|
25
|
+
expose create as request_design
|
|
20
26
|
expose commit as place_order
|
|
21
27
|
expose fulfill as finish_order
|
|
28
|
+
expose cancel as cancel_order
|
|
22
29
|
}
|
|
30
|
+
// Finishing the order does not release money. Run the payment actions too.
|
|
23
31
|
attach payment = escrow.hold {
|
|
32
|
+
payer: owner, payee: designer
|
|
33
|
+
fee: { seller: 10%, tax: 15% }
|
|
34
|
+
deliver_within: 7d, accept_within: 3d
|
|
35
|
+
dispute: { refund_after: return_verified }
|
|
24
36
|
expose create as create_payment
|
|
25
|
-
payer: actor, payee: owner
|
|
26
37
|
expose fund as fund_project
|
|
27
38
|
expose deliver as submit_work
|
|
28
39
|
expose accept as accept_work
|
|
29
40
|
expose dispute as dispute_work
|
|
30
|
-
expose verify_return as
|
|
41
|
+
expose verify_return as verify_rejected_work
|
|
31
42
|
expose refund as refund_client
|
|
32
|
-
expose
|
|
43
|
+
expose refund_undelivered as refund_missed_delivery
|
|
44
|
+
expose settle_release as resolve_for_designer
|
|
45
|
+
expose settle_return as resolve_for_client
|
|
46
|
+
expose settle_refund as finish_client_refund
|
|
47
|
+
expose cancel as cancel_unpaid_project
|
|
33
48
|
}
|
|
34
49
|
}
|
package/examples/money.hsx
CHANGED
|
@@ -1,19 +1,54 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Riyadh tutoring studio charges 172.50 SAR for a private Arabic lesson.
|
|
2
|
+
// A paid booking can be refunded before the lesson or if the tutor cannot teach.
|
|
3
|
+
program tutoring_studio "Riyadh tutoring studio"
|
|
3
4
|
use money
|
|
4
5
|
|
|
5
|
-
object lesson
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
// The customer is the object's owner. Fee: 0% on top of the lesson price.
|
|
7
|
+
// The studio invoices 150 SAR plus 15% VAT, 22.50 SAR, outside this transfer.
|
|
8
|
+
// money.transfer sends the gross price to the studio without splitting VAT.
|
|
9
|
+
instrument lesson_booking(payment: ref<money.transfer>, student: party) {
|
|
10
|
+
summary: "Confirm a paid lesson, record teaching or refund the student."
|
|
11
|
+
fields { payment: ref<payment>, startsAt: date }
|
|
12
|
+
lifecycle { states: [booked, taught, refunded], initial: booked }
|
|
13
|
+
action create {
|
|
14
|
+
actor: { party: student }
|
|
15
|
+
requires self.payment in [paid]
|
|
16
|
+
requires unique "paid_lesson" on [self.payment]
|
|
17
|
+
requires self.startsAt > self.now
|
|
10
18
|
}
|
|
19
|
+
action teach {
|
|
20
|
+
from: booked, to: taught, actor: { party: programOperator }
|
|
21
|
+
due: { at: self.startsAt }
|
|
22
|
+
subject { lessonNotes: text }
|
|
23
|
+
}
|
|
24
|
+
action cancel {
|
|
25
|
+
from: booked, to: refunded, actor: { party: student }
|
|
26
|
+
deadline: { at: self.startsAt }
|
|
27
|
+
moves self.payment.amount from programOperator to student
|
|
28
|
+
}
|
|
29
|
+
action tutor_unavailable {
|
|
30
|
+
from: booked, to: refunded, actor: { party: programOperator }
|
|
31
|
+
subject { cancellationReason: text }
|
|
32
|
+
moves self.payment.amount from programOperator to student
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
object lesson "Arabic lesson" {
|
|
37
|
+
fields { student: text, tutor: text, startsAt: date }
|
|
11
38
|
columns: [student, tutor, startsAt]
|
|
12
39
|
attach payment = money.transfer {
|
|
40
|
+
payer: owner, payee: operator, amount: 172.50 SAR
|
|
41
|
+
economics pay { purpose: earning, sourceParty: owner }
|
|
13
42
|
expose create as create_payment
|
|
14
|
-
payer: actor, payee: operator, amount: 150 SAR
|
|
15
|
-
economics pay { purpose: earning, sourceParty: actor }
|
|
16
43
|
expose pay as pay_for_lesson
|
|
17
|
-
expose cancel as
|
|
44
|
+
expose cancel as cancel_unpaid_lesson
|
|
45
|
+
}
|
|
46
|
+
attach booking = lesson_booking {
|
|
47
|
+
payment: payment, student: owner
|
|
48
|
+
rename { startsAt: startsAt }
|
|
49
|
+
expose create as confirm_paid_lesson
|
|
50
|
+
expose teach as record_lesson_taught
|
|
51
|
+
expose cancel as cancel_and_refund_lesson
|
|
52
|
+
expose tutor_unavailable as refund_cancelled_lesson
|
|
18
53
|
}
|
|
19
54
|
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
program equipment_rental "
|
|
1
|
+
// Dammam equipment hire holds a 1,000 SAR deposit for a business renter.
|
|
2
|
+
// Return the deposit in full, or deduct a single 50 SAR late charge plus 7.50 SAR VAT.
|
|
3
|
+
program equipment_rental "Dammam equipment hire"
|
|
4
4
|
party renter: business
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// Bind renter to the business hiring the equipment. The deposit has fee 0%
|
|
7
|
+
// and tax 0% because it stays refundable principal. The late service has 15% VAT.
|
|
7
8
|
instrument deposit {
|
|
8
|
-
summary: "Hold
|
|
9
|
+
summary: "Hold security until return, with one disclosed late charge."
|
|
9
10
|
fields {
|
|
10
11
|
renter: account of renter
|
|
11
12
|
held: account of self
|
|
12
13
|
amount: money = 1000 SAR
|
|
13
14
|
lateFee: money = 50 SAR
|
|
15
|
+
lateTax: money = rate(self.lateFee, 15%)
|
|
16
|
+
startsAt: date
|
|
14
17
|
dueAt: date
|
|
15
18
|
refund: money?
|
|
16
19
|
}
|
|
@@ -18,10 +21,12 @@ instrument deposit {
|
|
|
18
21
|
action create {
|
|
19
22
|
actor: { party: renter }
|
|
20
23
|
requires self.dueAt > self.now
|
|
24
|
+
requires self.startsAt > self.now
|
|
25
|
+
requires self.dueAt > self.startsAt
|
|
21
26
|
}
|
|
22
27
|
action fund {
|
|
23
28
|
from: pending, to: funded, actor: { party: renter }
|
|
24
|
-
deadline: { at: self.
|
|
29
|
+
deadline: { at: self.startsAt }
|
|
25
30
|
moves self.amount from self.renter to self.held
|
|
26
31
|
}
|
|
27
32
|
action return_on_time {
|
|
@@ -34,24 +39,30 @@ instrument deposit {
|
|
|
34
39
|
from: funded, to: returned, actor: { party: programOperator }
|
|
35
40
|
due: { at: self.dueAt }
|
|
36
41
|
subject { returnReference: text }
|
|
37
|
-
calculate: [{ target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.lateFee }] }]
|
|
42
|
+
calculate: [{ target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.lateFee }, { field: self.lateTax }] }]
|
|
38
43
|
moves self.lateFee from self.held to programOperator
|
|
44
|
+
moves self.lateTax from self.held to programTax
|
|
39
45
|
moves self.refund from self.held to self.renter
|
|
40
46
|
}
|
|
47
|
+
action refund_before_pickup {
|
|
48
|
+
from: funded, to: cancelled, actor: { party: renter }
|
|
49
|
+
deadline: { at: self.startsAt }
|
|
50
|
+
moves self.held.balance from self.held to self.renter
|
|
51
|
+
}
|
|
41
52
|
action cancel { from: pending, to: cancelled, actor: { party: renter } }
|
|
42
53
|
}
|
|
43
54
|
|
|
44
|
-
object rental "Equipment
|
|
45
|
-
fields { equipment: text }
|
|
46
|
-
columns: [equipment, dueAt]
|
|
55
|
+
object rental "Equipment deposit" {
|
|
56
|
+
fields { equipment: text, serialNumber: text, renterName: text }
|
|
57
|
+
columns: [equipment, serialNumber, renterName, startsAt, dueAt]
|
|
47
58
|
attach security = deposit {
|
|
48
|
-
|
|
49
|
-
rename { dueAt: dueAt }
|
|
59
|
+
rename { startsAt: startsAt, dueAt: dueAt }
|
|
50
60
|
expose create as agree_rental
|
|
51
61
|
expose fund as pay_deposit
|
|
52
62
|
expose return_on_time as return_equipment
|
|
53
63
|
expose return_late as return_equipment_late
|
|
54
|
-
expose
|
|
64
|
+
expose refund_before_pickup as cancel_before_pickup
|
|
65
|
+
expose cancel as cancel_unpaid_rental
|
|
55
66
|
}
|
|
56
67
|
}
|
|
57
68
|
|
|
@@ -59,4 +70,5 @@ hide deposit.create
|
|
|
59
70
|
hide deposit.fund
|
|
60
71
|
hide deposit.return_on_time
|
|
61
72
|
hide deposit.return_late
|
|
73
|
+
hide deposit.refund_before_pickup
|
|
62
74
|
hide deposit.cancel
|
|
@@ -1,44 +1,88 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Jeddah phone repair quotes 350 SAR plus 52.50 SAR VAT for a screen replacement.
|
|
2
|
+
// The customer approves and funds the repair; acceptance pays the shop, while a failed repair returns all 402.50 SAR.
|
|
3
|
+
program repair_shop "Jeddah phone repair"
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// Fee: 0% beyond the repair price. The shop keeps 350 SAR and posts 15% VAT.
|
|
6
|
+
instrument assessment(customer: party) {
|
|
7
|
+
summary: "Inspect, approve, hold payment and settle a screen repair."
|
|
8
|
+
fields {
|
|
9
|
+
held: account of self
|
|
10
|
+
price: money = 350 SAR
|
|
11
|
+
vat: money = rate(self.price, 15%)
|
|
12
|
+
total: money = sum(self.price, self.vat)
|
|
13
|
+
quoteExpiresAt: date
|
|
14
|
+
repairDueAt: date
|
|
15
|
+
inspectedAt: date?
|
|
16
|
+
approvedAt: date?
|
|
17
|
+
}
|
|
18
|
+
lifecycle { states: [submitted, inspected, approved, ready_for_collection, disputed, completed, refunded, rejected, expired], initial: submitted }
|
|
19
|
+
action create {
|
|
20
|
+
actor: { party: customer }
|
|
21
|
+
requires self.quoteExpiresAt > self.now
|
|
22
|
+
requires self.repairDueAt > self.quoteExpiresAt
|
|
23
|
+
}
|
|
9
24
|
action inspect {
|
|
10
25
|
from: submitted, to: inspected, actor: { party: programOperator }
|
|
26
|
+
deadline: { at: self.quoteExpiresAt }
|
|
11
27
|
subject { inspectionNotes: text }
|
|
12
28
|
set: { inspectedAt: { field: self.now } }
|
|
13
29
|
}
|
|
14
30
|
action approve {
|
|
15
|
-
from: inspected, to: approved, actor: { party:
|
|
31
|
+
from: inspected, to: approved, actor: { party: customer }
|
|
32
|
+
deadline: { at: self.quoteExpiresAt }
|
|
16
33
|
subject { approvalReference: text }
|
|
17
34
|
set: { approvedAt: { field: self.now } }
|
|
35
|
+
moves self.total from customer to self.held
|
|
18
36
|
}
|
|
19
|
-
action reject {
|
|
20
|
-
|
|
21
|
-
}
|
|
37
|
+
action reject { from: [submitted, inspected], to: rejected, actor: { party: customer } }
|
|
38
|
+
action expire { from: [submitted, inspected], to: expired, actor: clock, due: { at: self.quoteExpiresAt } }
|
|
22
39
|
action complete {
|
|
23
|
-
from: approved, to:
|
|
40
|
+
from: approved, to: ready_for_collection, actor: { party: programOperator }
|
|
41
|
+
deadline: { at: self.repairDueAt }
|
|
24
42
|
subject { completionNotes: text }
|
|
25
43
|
}
|
|
44
|
+
action accept {
|
|
45
|
+
from: ready_for_collection, to: completed, actor: { party: customer }
|
|
46
|
+
moves self.price from self.held to programOperator
|
|
47
|
+
moves self.vat from self.held to programTax
|
|
48
|
+
}
|
|
49
|
+
action dispute {
|
|
50
|
+
from: ready_for_collection, to: disputed, actor: { party: customer }
|
|
51
|
+
subject { faultDescription: text }
|
|
52
|
+
}
|
|
53
|
+
action refund {
|
|
54
|
+
from: [approved, ready_for_collection, disputed], to: refunded, actor: { party: programOperator }
|
|
55
|
+
subject { refundReason: text }
|
|
56
|
+
moves self.held.balance from self.held to customer
|
|
57
|
+
}
|
|
58
|
+
action refund_overdue {
|
|
59
|
+
from: approved, to: refunded, actor: { party: customer }
|
|
60
|
+
due: { at: self.repairDueAt }
|
|
61
|
+
moves self.held.balance from self.held to customer
|
|
62
|
+
}
|
|
63
|
+
action accept_resolution {
|
|
64
|
+
from: disputed, to: completed, actor: { party: customer }
|
|
65
|
+
subject { resolutionReference: text }
|
|
66
|
+
moves self.price from self.held to programOperator
|
|
67
|
+
moves self.vat from self.held to programTax
|
|
68
|
+
}
|
|
26
69
|
}
|
|
27
70
|
|
|
28
|
-
object repair "
|
|
29
|
-
fields { item: text, problem: text }
|
|
30
|
-
columns: [item, problem]
|
|
71
|
+
object repair "Screen repair" {
|
|
72
|
+
fields { item: text, serialNumber: text, problem: text }
|
|
73
|
+
columns: [item, serialNumber, problem, quoteExpiresAt, repairDueAt]
|
|
31
74
|
attach review = assessment {
|
|
32
|
-
|
|
75
|
+
customer: owner
|
|
76
|
+
rename { quoteExpiresAt: quoteExpiresAt, repairDueAt: repairDueAt }
|
|
77
|
+
expose create as request_repair
|
|
33
78
|
expose inspect as inspect_repair
|
|
34
|
-
expose approve as
|
|
35
|
-
expose reject as
|
|
36
|
-
expose complete as
|
|
79
|
+
expose approve as approve_and_pay
|
|
80
|
+
expose reject as decline_quote
|
|
81
|
+
expose complete as mark_ready_for_collection
|
|
82
|
+
expose accept as accept_repair
|
|
83
|
+
expose dispute as report_failed_repair
|
|
84
|
+
expose refund as refund_repair
|
|
85
|
+
expose refund_overdue as refund_missed_deadline
|
|
86
|
+
expose accept_resolution as accept_repair_resolution
|
|
37
87
|
}
|
|
38
88
|
}
|
|
39
|
-
|
|
40
|
-
hide assessment.create
|
|
41
|
-
hide assessment.inspect
|
|
42
|
-
hide assessment.approve
|
|
43
|
-
hide assessment.reject
|
|
44
|
-
hide assessment.complete
|
package/examples/reporting.hsx
CHANGED
|
@@ -1,28 +1,54 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Tabuk workshop equipment finance lends 46,000 SAR and reports monthly repayments and arrears.
|
|
2
|
+
// Ageing bands at 30, 60 and 90 days distinguish overdue balances from loans written off after 90 days.
|
|
3
|
+
program equipment_loan_reports "Tabuk workshop equipment finance"
|
|
3
4
|
use reporting
|
|
4
5
|
use financing
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Set price to 46,000 SAR, the workshop's 40,000 SAR equipment invoice plus
|
|
8
|
+
// 15% VAT charged by its supplier outside this loan. Profit is 6% annually on
|
|
9
|
+
// the declining balance. Loan fee: 0%, tax: 0% on principal repayments.
|
|
10
|
+
// Reports have no customer fee or tax; they are the lender's internal records.
|
|
11
|
+
object purchase "Workshop equipment loan" {
|
|
12
|
+
fields { businessName: text, equipment: text, invoiceReference: text, price: money }
|
|
13
|
+
columns: [businessName, equipment, invoiceReference, price]
|
|
9
14
|
attach allowance = financing.limits {
|
|
10
|
-
expose create as create_allowance
|
|
11
15
|
borrower: owner, per_borrower: 200000 SAR
|
|
16
|
+
expose create as request_workshop_limit
|
|
12
17
|
expose approve as approve_business
|
|
13
18
|
}
|
|
14
19
|
attach budget = financing.portfolio_limit {
|
|
15
|
-
expose create as create_budget
|
|
16
20
|
limit: 5000000 SAR
|
|
21
|
+
expose create as create_lending_budget
|
|
17
22
|
expose approve as approve_budget
|
|
18
23
|
}
|
|
24
|
+
// Create twelve future monthly dates and approve both limits before funding.
|
|
19
25
|
attach loan = financing.installments {
|
|
20
|
-
|
|
21
|
-
borrower: owner, capital: operator
|
|
26
|
+
borrower: owner, capital: operator, share: 0%
|
|
22
27
|
months: 12, pricing: amortizing, profit_rate: 6%, disburse_to: borrower
|
|
28
|
+
payoff_rebate: 100%, refund_within: 30d, write_off_after: 90d
|
|
23
29
|
limits: allowance, portfolio: budget
|
|
30
|
+
expose create as quote_equipment_loan
|
|
24
31
|
expose sign as sign_purchase_loan
|
|
25
32
|
expose disburse as fund_purchase
|
|
33
|
+
expose cancel as decline_loan_quote
|
|
34
|
+
expose void as cancel_unfunded_loan
|
|
35
|
+
expose payoff as repay_early
|
|
36
|
+
expose complete as close_repaid_loan
|
|
37
|
+
}
|
|
38
|
+
// Report requests use reporting routes. This attachment declares their
|
|
39
|
+
// datasets and retention; it neither collects money nor sends a reminder.
|
|
40
|
+
attach reports = reporting.portfolio {
|
|
41
|
+
on: loan, default_days: 90, retention_years: 10
|
|
42
|
+
aging_first_days: 30, aging_second_days: 60, aging_third_days: 90
|
|
43
|
+
expose create as enable_portfolio_reports
|
|
26
44
|
}
|
|
27
|
-
attach reports = reporting.portfolio { expose create as create_reports, on: loan }
|
|
28
45
|
}
|
|
46
|
+
|
|
47
|
+
expose purchase.loan.payment.create as record_repayment
|
|
48
|
+
expose purchase.loan.payment.pay as pay_instalment
|
|
49
|
+
expose purchase.loan.payment.refund as refund_mistaken_repayment
|
|
50
|
+
expose purchase.loan.payoff_quote.create as request_payoff_quote
|
|
51
|
+
// Write-off is a capital decision, not an automatic consequence of a report.
|
|
52
|
+
expose purchase.loan.write_off_request.create as request_overdue_write_off
|
|
53
|
+
expose purchase.loan.write_off_request.apply as approve_overdue_write_off
|
|
54
|
+
expose purchase.loan.write_off_request.cancel as cancel_write_off_request
|
package/examples/savings.hsx
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Al Khobar shopkeepers circle collects 500 SAR from each of six members every month.
|
|
2
|
+
// Each member receives one 3,000 SAR pot in order; missed contributions remain payable and an unfunded pot waits.
|
|
3
|
+
program neighbourhood_savings "Al Khobar shopkeepers circle"
|
|
3
4
|
use savings
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// Fee: 0%. Tax: 0% because this circle only returns members' principal and
|
|
7
|
+
// charges no service. The organiser keeps none of the 18,000 SAR contributed.
|
|
8
|
+
object group "Shopkeepers savings circle" {
|
|
6
9
|
fields { groupName: text, purpose: text }
|
|
7
10
|
columns: [groupName, purpose]
|
|
8
11
|
attach circle = savings.circle {
|
|
9
|
-
expose create as create_circle
|
|
10
12
|
contribution: 500 SAR, members: 6, starts: 2027-01-01
|
|
11
|
-
|
|
12
|
-
expose
|
|
13
|
+
// At creation supply circleDates: the first of January through June 2027.
|
|
14
|
+
expose create as agree_circle_calendar
|
|
15
|
+
expose disband as cancel_unstarted_circle
|
|
16
|
+
expose close as close_completed_circle
|
|
13
17
|
}
|
|
14
18
|
attach membership = savings.membership {
|
|
15
|
-
expose create as create_membership
|
|
16
19
|
circle: circle, member: actor
|
|
17
|
-
|
|
18
|
-
expose
|
|
20
|
+
// Assign a distinct seat from 1 to 6 to each member at creation.
|
|
21
|
+
expose create as join_circle
|
|
22
|
+
expose activate as confirm_membership_schedule
|
|
23
|
+
expose withdraw as leave_cancelled_circle
|
|
19
24
|
}
|
|
20
|
-
// Contributions and each member's payout follow the declared calendar.
|
|
21
25
|
}
|
|
26
|
+
|
|
27
|
+
// Create periods 1 through 6 for EVERY member before activating membership.
|
|
28
|
+
// The clock activates the circle when all six have joined. Then confirm each
|
|
29
|
+
// membership. Contributions and payouts run on that calendar, including after
|
|
30
|
+
// a member has received their pot. A missed collection is not a promised retry.
|
|
31
|
+
expose group.membership.contribution.create as schedule_member_contribution
|