Warranty & Claims — Process flows
Architecture & lifecycle auto-generated from code (dispatch, CHECK status enums, SET status writes). Sequences & notes are authored.
Architecture
%%{init: {"theme":"base","themeVariables":{"darkMode":true,"background":"#1e1e1e","primaryColor":"#2d2d30","primaryBorderColor":"#569cd6","primaryTextColor":"#d4d4d4","lineColor":"#6796c6","secondaryColor":"#3a3d41","secondaryBorderColor":"#dcdcaa","tertiaryColor":"#252526","tertiaryBorderColor":"#569cd6","clusterBkg":"#252526","clusterBorder":"#569cd6","titleColor":"#569cd6","noteBkgColor":"#3a3d41","noteTextColor":"#dcdcaa"},"flowchart":{"useMaxWidth":false},"state":{"useMaxWidth":false}}}%%
flowchart LR
C["Client"] --> A["Pages Fn claims<br/>bolt_session auth · company_id scope"]
A --> D["dispatch<br/>register_warranty · transfer_warranty · warranty_transfer · claim_intake · rma_quarantine · restock_refurb · claim_disposition · rma_disposition …"]
D --> K["Kernel<br/>erp_document + journal<br/>atomic GL / stock"]
K --> DB[("D1 tables")]
D --> DB
D --> EV["events<br/>append audit trail"]
EV --> DB
DB -.-> T["claims_warranty_registration<br/>claims_warranty_transfer<br/>claims_warranty_case<br/>claims_warranty_claim_line<br/>claims_warranty_entitlement_check<br/>claims_warranty_rma_stock_movement<br/>claims_warranty_triage_decision<br/>claims_warranty_repair_order"]
Lifecycle
Kernel document lifecycle (erp_document.docstatus)
Every money/stock-moving document in this module posts through the shared kernel, walking a fixed docstatus:
%%{init: {"theme":"base","themeVariables":{"darkMode":true,"background":"#1e1e1e","primaryColor":"#2d2d30","primaryBorderColor":"#569cd6","primaryTextColor":"#d4d4d4","lineColor":"#6796c6","secondaryColor":"#3a3d41","secondaryBorderColor":"#dcdcaa","tertiaryColor":"#252526","tertiaryBorderColor":"#569cd6","clusterBkg":"#252526","clusterBorder":"#569cd6","titleColor":"#569cd6","noteBkgColor":"#3a3d41","noteTextColor":"#dcdcaa"},"flowchart":{"useMaxWidth":false},"state":{"useMaxWidth":false}}}%%
stateDiagram-v2
[*] --> Draft: createDocument (docstatus 0)
Draft --> Submitted: submitDocument (docstatus 1, journal posted)
Submitted --> Cancelled: cancelDocument (docstatus 2, reversed)
Submitted --> [*]
Cancelled --> [*]
claims_warranty_case.status
States: draft (initial) · accepted · rejected · triaged · in_repair · resolved · closed
| status | set by (grepped SET status='…') |
|---|---|
triaged |
triageClaim |
in_repair |
startRepair, resolveClaim |
claims_warranty_case.operational_status
States: draft (initial) · triaged · in_repair · resolved · closed · rejected
claims_warranty_claim_line.entitlement_status
States: in_warranty (initial) · out_of_warranty · unregistered · void · goodwill
claims_warranty_claim_line.line_status
States: accepted (initial) · rejected · goods_in · triaged · in_repair · in_replacement · in_refund · resolved · closed
| status | set by (grepped SET line_status='…') |
|---|---|
triaged |
triageClaim |
in_repair |
startRepair |
claims_warranty_registration.status
States: provisional (initial) · active · voided · superseded
Key flow: Warranty replacement (resolve_claim, kind=replace)
The richest money-touching path: an in-warranty line is resolved by issuing a fresh unit. Inventory is permanently relieved, so the kernel posts stock-out and an offsetting GL entry atomically, keyed for idempotent replay.
%%{init: {"theme":"base","themeVariables":{"darkMode":true,"background":"#1e1e1e","primaryColor":"#2d2d30","primaryBorderColor":"#569cd6","primaryTextColor":"#d4d4d4","lineColor":"#6796c6","secondaryColor":"#3a3d41","secondaryBorderColor":"#dcdcaa","secondaryTextColor":"#d4d4d4","tertiaryColor":"#252526","tertiaryBorderColor":"#569cd6","noteBkgColor":"#3a3d41","noteTextColor":"#dcdcaa","clusterBkg":"#252526","clusterBorder":"#569cd6","titleColor":"#569cd6","actorBkg":"#2d2d30","actorBorder":"#569cd6","actorTextColor":"#d4d4d4","signalColor":"#9cdcfe","signalTextColor":"#d4d4d4","noteBkgColor":"#3a3d41","labelBoxBkgColor":"#3a3d41","labelBoxBorderColor":"#dcdcaa"},"flowchart":{"useMaxWidth":false},"sequence":{"useMaxWidth":false},"state":{"useMaxWidth":false},"er":{"useMaxWidth":false}}}%%
sequenceDiagram
participant C as Client
participant FN as claims Fn
participant DB as D1 claims tables
participant K as kernel
participant L as stock ledger + GL
C->>FN: POST action=resolve_claim, kind=replace
FN->>FN: getIdentityAsync -> company + cap
FN->>DB: SELECT claim_line + case
FN->>DB: prior resolution (line, replace)?
Note over FN,DB: exists -> return idempotent, no re-post
FN->>L: currentAvgCost(variant, from_location)
Note over FN: replaceValue = qty * avg cost
FN->>K: createDocument(STOCK)<br/>stock -qty + Dr warranty_claim_expense / Cr inventory
K->>L: post stock-out + GL (amount_from_stock)
FN->>K: submitDocument(idempotency_key claims-resolve:line:replace)
K-->>FN: document id
FN->>DB: INSERT replacement_order (state closed, carryover)
FN->>DB: UPDATE line + case -> closed
FN->>DB: appendEvent claims.resolution.completed
FN-->>C: { ok, resolution }
Notes
- จุดประสงค์: จัดการ warranty แบบผูก serial ตั้งแต่ลงทะเบียน (
register_warranty) โอนสิทธิ์ (transfer_warranty) รับเคลม (claim_intake) กัก RMA (rma_quarantine) จนถึงปิดเคส (resolve_claim) และเคลมคืน supplier (supplier_claim_back) — ทั้งหมด company-scoped ผ่านgetIdentityAsync. - Entitlement เป็น gate จริง:
claim_intakeตัดสินจากกฎclaim_date <= warranty_expiryเทียบ registration ล่าสุดของ serial; ผ่าน = caseaccepted, ไม่ผ่าน = case+linerejectedแล้ว throw 422 พร้อม snapshot ลงerp_claims_warranty_entitlement_check.triage_claimบล็อกเคสไม่มีสิทธิ์เว้นแต่ decision =reject. - Money-safety (subledger == GL): ทุกการขยับ stock ที่มีมูลค่าไปผ่าน kernel
createDocument/submitDocumentเท่านั้น.rma_quarantineเป็น transfer value-neutral (in-cost =currentAvgCostของ source, ไม่มี journal).restock_refurbclamp inbound cost ไม่ให้เกิน carrying (write down ได้อย่างเดียว) แล้วโพสต์ส่วนต่าง Drwarranty_claim_expense/ Crinventory.scrapและreplacerelieve inventory จริงพร้อม writeoff/expense offset. - Idempotency:
resolve_claimมี re-resolution guard ด้วย UNIQUE(claim_line, kind)(retry คืนของเดิม);replace/refund/creditยังฝัง deterministicidempotency_key(claims-resolve:<line>:<kind>) ให้ kernel replay โพสต์เดิมแทน double-post;restock_refurbเช็ค operation_group ซ้ำก่อน move. - Location invariants: hold ต้องเป็น
quality_hold/claim_cage; restock source = hold location ที่บันทึกตอน quarantine และปลายทางต้องเป็น sellable (site/warehouse/zone/bin); scrap ต้องเป็น location typescrap. กันของ strand หรือมูลค่าค้าง balance sheet. - Gotchas: ทุก action append เข้า
erp_claims_warranty_event_log(append-only, มีcustomer_visibleflag; มีแค่ intake accepted ที่ตั้ง 1). GET read surface จำกัด resource เป็น whitelist ของ 8 ตารางกัน SQL injection และต้อง capmanage/erp/finance/accounting.resolve_claimkind=replaceปิดเป็นclosed, ส่วน repair/refund/credit/reject/return_as_is ปิดเป็นresolved.