Fixed Assets — กระบวนการทำงาน (Process flows)
สถาปัตยกรรม & lifecycle สร้างอัตโนมัติจากโค้ด (dispatch, CHECK status enums, การเขียน SET status) ส่วน sequences & notes เขียนกำกับเอง
สถาปัตยกรรม (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 fixed-assets<br/>bolt_session auth · company_id scope"]
A --> D["dispatch<br/>REST resources"]
D --> K["Kernel<br/>erp_document + journal<br/>atomic GL / stock"]
K --> DB[("D1 tables")]
D --> DB
DB -.-> T["fixed_asset<br/>fixed_asset_depreciation<br/>fixed_asset_disposal"]
วงจรชีวิต (Lifecycle)
วงจรชีวิตของเอกสารระดับ Kernel (erp_document.docstatus)
ทุกเอกสารในโมดูลนี้ที่ทำให้เกิดการเคลื่อนไหวของเงิน/สต็อก จะ post ผ่าน kernel ที่ใช้ร่วมกัน โดยเดินไปตาม 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 --> [*]
fixed_asset.status
สถานะ: registered (เริ่มต้น) · depreciating · fully_depreciated · disposed
| status | กำหนดโดย (grep จาก SET status='…') |
|---|---|
disposed |
disposeAsset |
Key flow: การรันค่าเสื่อมราคา (POST /depreciation/run)
การคิดค่าเสื่อมราคาแบบเส้นตรงหนึ่งงวดสำหรับสินทรัพย์รายการเดียว จำนวนเงินถูก clamp
ไว้ไม่ให้ Σ เกิน cost − salvage ได้เด็ดขาด และงวดสุดท้ายจะรับ remainder ที่ทศนิยม 6
ตำแหน่งพอดี คู่รายการ GL (Dr expense / Cr accumulated) ถูก post ผ่าน kernel ภายใต้
idempotency key แบบ deterministic จากนั้น status ที่ cache ไว้ของสินทรัพย์จะถูกคำนวณใหม่
จากผลรวมของ entry ทั้งหมด — ไม่ใช่การบวกเพิ่มแบบไม่ตรวจสอบ
%%{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 F as runDepreciation
participant DEP as erp_fixed_asset_depreciation
participant K as kernel (GL)
participant JE as erp_journal_entry
participant A as erp_fixed_asset
C->>F: POST asset_id, period_code
F->>A: loadAsset (reject if disposed)
F->>DEP: existing entry for (asset,period)?
alt already posted
DEP-->>F: row
F-->>C: 200 idempotent
else new period
F->>DEP: depreciationTotals (periods, accumulated)
Note over F: amount = final ? remainder : min(monthly, remaining)
F->>K: postJournal FA_DEPRECIATION<br/>key fa-dep:id:period
K->>JE: Dr expense / Cr accumulated (balanced, atomic)
JE-->>K: journalEntryId + documentId
K-->>F: posted refs
F->>DEP: INSERT entry (UNIQUE asset,period)
F->>A: syncAssetDepreciation -> depreciating / fully_depreciated
F-->>C: 201 entry + status + book_value
end
หมายเหตุ (Notes)
- จุดประสงค์: ทะเบียนสินทรัพย์ถาวร + ค่าเสื่อมราคาแบบเส้นตรง (straight-line เท่านั้นใน phase นี้) + การจำหน่าย โดยผูก GL ผ่าน kernel ล้วนๆ ทั้ง 3 doctype:
FA_CAPITALIZATION,FA_DEPRECIATION,FA_DISPOSAL. - โมดูลนี้ไม่แตะ stock ledger เลย และไม่เขียน
erp_journal_entry(_line)ตรง — ทุก posting ผ่านpostJournal → kernel.createDocument + submitDocumentซึ่ง assert Σdr==Σcr และ commit แบบ atomic (assertBalancedยังกันเองอีกชั้นก่อนส่ง). - Money-safety (accumulated ไม่ drift):
accumulated_depreciationบนerp_fixed_assetเป็นแค่ cache — recompute จากSUM(erp_fixed_asset_depreciation.amount)ทุกครั้งในsyncAssetDepreciation(ไม่เคย +=). Depreciable base =cost − salvage_value, งวดสุดท้ายเก็บ remainder 6dp พอดี ไม่มีเศษค้าง. - Idempotency: deterministic key ต่อ business event —
fa-cap:{code},fa-dep:{id}:{period},fa-disp:{id}— บวก UNIQUE constraints ((company,asset,period),(company,asset)สำหรับ disposal). ถ้า crash คั่นกลางระหว่าง kernel post กับ INSERT ของเรา retry จะ replay journal เดิมที่ kernel ไม่ double-post;postJournalยัง lookup journal จริงด้วย idempotency_key เผื่อ replay path ที่ submitDocument ไม่คืน.journal. - Account wiring validated, zero hard-coded:
requireAccountเช็คว่า code มีจริงใน chart of accounts, status='active', และ kind ถูกชนิด (asset/expense/income) — asset account จะถูกผูกกับ income account ไม่ได้. - Gotchas: capitalization posting เกิดเฉพาะเมื่อ caller ระบุ
capitalization_account/clearing_account(ไม่งั้น register แค่บันทึกทะเบียน status='registered' ไม่ post GL); disposal ต้องส่งclearing_accountเมื่อ proceeds>0 และgain_account/loss_accountตาม gain/loss ที่คำนวณได้; รัน depreciation ก่อนเดือน acquire ไม่ได้ และรันซ้ำงวดเดิมคืน entry เดิม (idempotent) ไม่ทับ.