التوثيق

ابنِ مداريًّا كاملًا

ابنِ مسار موافقة على المصروفات من حالة واحدة إلى أربع، مع شرط يفرض قاعدة إنفاق.

يبني هذا الدرس موافقة على المصروفات: تُكتب المطالبة مسودةً، ثم تُقدَّم، ثم يُوافَق عليها أو تُرفض. وفي الطريق يضيف الحالات والشروط والشاشات لكل خطوة. يعمل المداريّ المكتمل في أسفل الصفحة.

1. الكيان

ابدأ بالبيانات. للمطالبة مبلغ وملاحظة، وتعيش في الذاكرة ما دامت الصفحة مفتوحة.

lolo
1entity Expense [runtime] { 2 id : string! 3 amount : number = 120 4 note : string = "" 5}

2. حالة واحدة

تحتاج كل سمة إلى حالة واحدة على الأقل وشاشة لها. يُطلق INIT حين تُفتح الصفحة:

lolo
1app expense-step-2 "1.0.0" 2"Step 2: one state and its screen." 3orbital ExpenseOrbital { 4 entity Expense [runtime] { 5 id : string! 6 amount : number = 120 7 } 8 9 trait Approval -> Expense [interaction, instance] { 10 initial: draft 11 12 state draft { 13 INIT -> draft 14 (set @entity.amount 120) 15 (render-ui main { type: typography, content: (str/concat "Draft claim: " @entity.amount " EUR"), variant: h4 }) 16 } 17 } 18 19 page "/expenses" -> Approval 20}

تحقق منه بـ orb validate. ينجح: حالة واحدة، وشاشة، وصفحة تعرض شيئًا. لاحظ (set @entity.amount 120): كل حقل تقرؤه السمة يجب أن يعيّنه انتقال ما. لا شيء يُفترض ضمنيًا.

3. الانتقال بين الحالات

تُقدَّم المطالبة ثم يُوافَق عليها أو تُرفض. كل خطوة حالة، وكل زر يرسل حدثًا تقبله حالة ما. إن أضفت زرًا لا تتعامل أي حالة مع حدثه، يرفضه orb validate. جرّب ذلك.

lolo
1state draft { 2 SUBMIT -> submitted 3 (render-ui main { type: stack, direction: horizontal, gap: sm, children: [ 4 { type: button, label: "Approve", action: APPROVE, variant: primary }, 5 { type: button, label: "Reject", action: REJECT, variant: secondary } 6 ] }) 7} 8 9state submitted { 10 APPROVE -> approved 11 (set @entity.note "Approved") 12 REJECT -> rejected 13 (set @entity.note "Rejected") 14}

لاحظ أن approved وrejected تحتاجان أيضًا إلى مخرج: الحالة التي لا انتقال خارجًا منها طريق مسدود، والمدقق يقول ذلك. تضيف النسخة المكتملة NEW_CLAIM وREVISE.

4. قاعدة على شكل شرط

قاعدة الشركة: المطالبات فوق 500 يورو لا يُوافَق عليها هنا. اكتبها على الانتقال:

lolo
1 2 APPROVE -> approved when (<= @entity.amount 500) (set @entity.note "Approved")

حين يكون الشرط خاطئًا يُرفض APPROVE وتبقى المطالبة مقدَّمة. تعيش القاعدة في مكان واحد بالضبط، والمختبِر يجرّب جانبيها.

5. المداريّ المكتمل

آلة الحالات

Approval

on Expense · states: 4 · transitions: 7
emitsMOVED
INIT
RAISE
SUBMIT
(> @entity.amount 0)
APPROVE
(<= @entity.amount 500)
REJECT
NEW_CLAIM
REVISE
nowdraft
submitted
approved
rejected
يعمل الآن مباشرة

Draft claim: EUR

البرنامج أعلاه كآلات حالات، واحدة لكل سمة: كل مربع حالة، وكل سهم حدث ينقلها، والأيقونات على السهم هي التأثيرات التي يشغّلها ذلك الحدث. الرمز ◇ على السهم شرط حارس؛ مرّر المؤشر فوقه لتقرأ الشرط. كيف تقرأ AVL

std-orb-docex-expense.lolololo
1app std-orb-docex-expense "1.0.0" 2"std-orb-docex-expense — an approval flow: four states, a guard on the amount, and a rule the shape of the program enforces." 3orbital ExpenseOrbital { 4 entity Expense [runtime] { 5 id : string! 6 amount : number = 120 7 note : string = "" 8 } 9 10 trait Approval -> Expense [interaction, instance] { 11 initial: draft 12 13 state draft { 14 INIT -> draft 15 (render-ui main { type: stack, direction: vertical, gap: sm, children: [ 16 { type: typography, content: (str/concat "Draft claim: " @entity.amount " EUR"), variant: h4 }, 17 { type: stack, direction: horizontal, gap: sm, children: [ 18 { type: button, label: "Add 50 EUR", action: RAISE, variant: ghost }, 19 { type: button, label: "Submit", action: SUBMIT, variant: primary } 20 ] } 21 ] }) 22 RAISE -> draft 23 (emit MOVED { event: "RAISE" }) 24 (set @entity.amount (+ @entity.amount 50)) 25 SUBMIT -> submitted when (> @entity.amount 0) 26 (emit MOVED { event: "SUBMIT" }) 27 (render-ui main { type: stack, direction: vertical, gap: sm, children: [ 28 { type: typography, content: (str/concat "Submitted: " @entity.amount " EUR"), variant: h4 }, 29 { type: typography, content: "Claims over 500 EUR cannot be approved here.", variant: small, color: muted }, 30 { type: stack, direction: horizontal, gap: sm, children: [ 31 { type: button, label: "Approve", action: APPROVE, variant: primary }, 32 { type: button, label: "Reject", action: REJECT, variant: secondary } 33 ] } 34 ] }) 35 } 36 37 state submitted { 38 APPROVE -> approved when (<= @entity.amount 500) 39 (emit MOVED { event: "APPROVE" }) 40 (set @entity.note "Approved") 41 (render-ui main { type: stack, direction: vertical, gap: sm, children: [ 42 { type: badge, label: "Approved", variant: success }, 43 { type: button, label: "New claim", action: NEW_CLAIM, variant: ghost } 44 ] }) 45 REJECT -> rejected 46 (emit MOVED { event: "REJECT" }) 47 (set @entity.note "Rejected") 48 (render-ui main { type: stack, direction: vertical, gap: sm, children: [ 49 { type: badge, label: "Rejected", variant: error }, 50 { type: button, label: "Revise", action: REVISE, variant: primary } 51 ] }) 52 } 53 54 state approved { 55 NEW_CLAIM -> draft 56 (emit MOVED { event: "NEW_CLAIM" }) 57 (set @entity.amount 120) 58 (render-ui main { type: stack, direction: vertical, gap: sm, children: [ 59 { type: typography, content: (str/concat "Draft claim: " @entity.amount " EUR"), variant: h4 }, 60 { type: stack, direction: horizontal, gap: sm, children: [ 61 { type: button, label: "Add 50 EUR", action: RAISE, variant: ghost }, 62 { type: button, label: "Submit", action: SUBMIT, variant: primary } 63 ] } 64 ] }) 65 } 66 67 state rejected { 68 REVISE -> draft 69 (emit MOVED { event: "REVISE" }) 70 (render-ui main { type: stack, direction: vertical, gap: sm, children: [ 71 { type: typography, content: (str/concat "Draft claim: " @entity.amount " EUR"), variant: h4 }, 72 { type: stack, direction: horizontal, gap: sm, children: [ 73 { type: button, label: "Add 50 EUR", action: RAISE, variant: ghost }, 74 { type: button, label: "Submit", action: SUBMIT, variant: primary } 75 ] } 76 ] }) 77 } 78 79 emits { 80 MOVED -> external { 81 event : string! 82 } 83 @description "Each move names the event it took, so the diagram beside the demo can follow it." 84 } 85 } 86 87 page "/expenses" -> Approval 88}

ما الذي يحدث عندما يقع الحدث APPROVE:

submittedAPPROVE
→
approved
GuardThe transition only fires when this check is true.
(<= @entity.amount 500)
@entity.amount500<=
Effects (2)What runs, in order, when the transition fires.
(set @entity.note "Approved")
@entity.note"Approved"set

شغّل orb verify عليه: يُجرَّب كل انتقال، ويُختبر الشرط بمبلغ فوق الحد وتحته، وتُفحص كل شاشة.

Orb

اللغة التي تكون فيها القاعدة هي البرنامج.

© 2026 المدار. ليوبليانا، سلوفينيا.
Not reported here