ข้ามไปที่เนื้อหา

Inventory & Warehouse — ขั้นตอนการทำงาน (Process flows)

สถาปัตยกรรมและวงจรชีวิตสร้างอัตโนมัติจากโค้ด (dispatch, CHECK status enums, การเขียน SET status) ส่วนลำดับการทำงานและหมายเหตุเป็นการเขียนกำกับเอง

สถาปัตยกรรม (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 inventory<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["item<br/>item_variant<br/>inventory_cycle_count<br/>inventory_cycle_count_line<br/>inventory_reservation<br/>inventory_pick_list<br/>inventory_pick_list_line"]

วงจรชีวิต (Lifecycle)

วงจรชีวิตเอกสารระดับ Kernel (erp_document.docstatus)

เอกสารทุกฉบับที่เคลื่อนไหวเงิน/สต็อกในโมดูลนี้จะโพสต์ผ่าน 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 --> [*]

inventory_pick_list.status

สถานะ: draft (เริ่มต้น) · allocated · ready · in_progress · posted · cancelled

inventory_pick_ticket.status

สถานะ: queued (เริ่มต้น) · allocated · posted · cancelled

inventory_repost_request.status

สถานะ: queued (เริ่มต้น) · in_progress · completed · failed

Flow สำคัญ: การจ่ายสินค้าออก Goods Issue (submitIssue)

การจ่ายสินค้าออก (goods-issue) เป็นการปลดมูลค่าสินค้าคงคลังอย่างถาวร จึงเคลื่อนไหวทั้ง subledger และ โพสต์ GL journal ที่สมดุลด้วยต้นทุนขาออกที่ kernel ตีมูลค่าไว้ (เมื่อใดก็ตามที่มูลค่านั้น > 0)

%%{init: {"theme":"base","themeVariables":{"darkMode":true,"background":"#1e1e1e","primaryColor":"#2d2d30","primaryBorderColor":"#569cd6","primaryTextColor":"#d4d4d4","lineColor":"#6796c6","actorBkg":"#2d2d30","actorBorder":"#569cd6","actorTextColor":"#d4d4d4","signalColor":"#9cdcfe","signalTextColor":"#d4d4d4","noteBkgColor":"#3a3d41","noteTextColor":"#dcdcaa","labelBoxBkgColor":"#3a3d41","labelBoxBorderColor":"#dcdcaa"},"sequence":{"useMaxWidth":false}}}%%
sequenceDiagram
  participant C as Client
  participant F as Inventory Fn
  participant S as Stock subledger
  participant P as Removal policy
  participant K as Kernel
  participant G as GL journal
  C->>F: POST issue/submit qty variant location
  F->>F: auth + capability check (inventory/erp/manage)
  F->>S: availability = onHand minus reserved
  Note over F,S: reserved = WMS + sales hard reservations
  alt qty > available
    F-->>C: 400 insufficient ATP for issue
  end
  F->>P: resolveRemovalPolicy (returns strategy)
  F->>S: pickLots order lots FIFO/FEFO/LIFO (CLOSEST degrades to FIFO here)
  F->>S: unitCostForOutbound = value / qty
  F->>K: createDocument WMS-ISSUE docstatus 0
  F->>K: submitDocument stockLines + journal (journal only if amount > 0)
  K->>K: idempotency fingerprint + posting-period lock
  K->>S: insert qty_delta value_delta (negative)
  K->>G: balanced Dr/Cr from reason (amount re-derived from stock)
  K->>K: flip docstatus 0 to 1
  K-->>F: document + stock_rows
  F-->>C: ok strategy picks document stock_rows

หมายเหตุ (Notes)

  • วัตถุประสงค์. endpoint WMS เพียงจุดเดียวครอบคลุมการเคลื่อนไหวสต็อกทั้งหมด — โอนย้าย (transfer), ปรับปรุง (adjustment), ตรวจนับ (cycle-count), จ่ายออก (issue), จัดเก็บเข้าที่ (putaway), หยิบ (pick) และการจอง (reservation) — พร้อมทั้ง surface สำหรับอ่าน (GET บน reservation / cycle-count(+line) / pick-list(+line) / putaway-rule / removal-policy / adjustment-reason / erp_stock_ledger_entry) ทุกอย่างถูก scope ด้วย company เพื่อแยกข้อมูลแบบ multi-tenant; surface POST ที่แก้ไขข้อมูลถูกกั้นด้วย capability inventory/erp/manage ส่วน surface GET ที่อ่านอย่างเดียวกั้นด้วย manage/erp/finance/accounting
  • การโพสต์ผ่าน Kernel. handler จะไม่ insert ลง erp_stock_ledger_entry หรือ erp_journal_entry เองโดยตรง แต่จะสร้าง intent (stockLines + journal แบบ optional) แล้วเรียก postKernelDocumentkernel.createDocument (สร้าง erp_document ที่ docstatus 0) → kernel.submitDocument (พลิก 0→1, เขียน subledger + GL) วงจรชีวิตเอกสารของ kernel คือ 0 draft → 1 submitted → 2 cancelled ซึ่งบังคับด้วย trigger erp_document_docstatus_lifecycle (erp_document_immutable_after_submit ล็อกเอกสารที่ submit แล้ว)
  • Subledger แบบ append-only + การตีมูลค่าด้วยต้นทุนเฉลี่ย. erp_stock_ledger_entry เป็น immutable — trigger BEFORE UPDATE/BEFORE DELETE จะ RAISE(ABORT) ยอดคงเหลือ (on-hand) และมูลค่าเป็นค่าที่ derived เสมอ: kernel.getOnHand รวม qty_delta/value_delta; ต้นทุนต่อหน่วยเฉลี่ยสำหรับขาออก = value / qty การจัดลำดับ lot สำหรับการจ่ายออกทำใน pickLots ซึ่งรองรับเฉพาะ FIFO / FEFO / LIFO เท่านั้น — removal policy แบบ CLOSEST จะตกไปใช้ลำดับ FIFO ในเส้นทางการจ่ายออก (CLOSEST ถูกใช้จริงเฉพาะใน stockQuantRows ซึ่งเป็นตัวจัดสรรของ pick-list/putaway)
  • ความปลอดภัยด้านเงิน / idempotency. idempotency_key ตัดการซ้ำ (dedupe) ของ replay ด้วยการตรวจ fingerprint — หากนำ key เดิมไปใช้ซ้ำกับ operation ที่ต่างออกไป จะถูกปฏิเสธอย่างชัดเจน (409) แทนที่จะคืน rows ของเอกสารเก่าที่ค้าง (stock rows แบบ legacy ที่มี fingerprint เป็น NULL จะ replay ได้โดยไม่ผ่านการตรวจนี้) การล็อกงวดการโพสต์ (posting-period lock) บังคับทั่วทั้ง kernel บน ทั้ง posting_date และ effective_date ดังนั้น effective_date ที่ย้อนหลังจึงไม่สามารถแอบดันสต็อกเข้าเดือนที่ปิดไปแล้วได้
  • ATP ข้ามโมดูล. reservedQty หักลบทั้งการจอง WMS (erp_inventory_reservation, สถานะ active/partially_consumed) และ การจองแบบ hard ฝั่งขายที่ยัง active อยู่ (erp_sales_reservation_entry, reservation_kind='hard', ยังไม่หมดอายุ) ดังนั้น WMS จึงไม่มีทางบริโภคสต็อกที่ฝ่ายขายได้สัญญาไว้แล้ว
  • ข้อควรระวังด้านความถูกต้องของมูลค่า. ใบรับจากการโอน (transfer receipts) สืบทอดต้นทุนที่บริโภคจริงของขาจ่าย (value_from_issue) แทนที่จะใช้ค่าเฉลี่ยทั้ง pool ทำให้มูลค่าใน subledger สอดคล้องกับ GL บนการโอนแบบ FIFO; ขนาดจำนวนเงินของ journal ถูก derive ใหม่โดย kernel จากการเคลื่อนไหวมูลค่าสต็อก (amount_from_stock) การตรวจนับ (cycle counts) บังคับการแยก maker/checker ผ่าน DB CHECK counted_by <> approved_by (และการตรวจล่วงหน้าฝั่ง JS) และจะโพสต์รายการปรับปรุงผลต่าง (variance adjustment) เฉพาะเมื่อ delta != 0 เท่านั้น