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

Marketing — Process flows

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

สถาปัตยกรรม (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 marketing<br/>bolt_session auth · company_id scope"]
  A --> D["dispatch<br/>roi · create · add · link · convert · multi_touch · record · post"]
  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["marketing_campaign<br/>marketing_campaign_member<br/>marketing_segment<br/>marketing_segment_list_member<br/>marketing_segment_snapshot<br/>marketing_automation_journey<br/>marketing_journey_enrollment<br/>marketing_lead"]

วงจรชีวิต (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 --> [*]

marketing_campaign.status

สถานะ: draft (เริ่มต้น) · scheduled · active · paused · completed · archived

marketing_campaign_member.member_status

สถานะ: active (เริ่มต้น) · suppressed · converted · removed

marketing_campaign_cost.status

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

Flow สำคัญ: บันทึกและโพสต์ค่าใช้จ่ายแคมเปญเข้า GL

cost.record จะเขียนเฉพาะแถวต้นทุนสถานะ recorded เท่านั้น ส่วน spend.post (หรือ post_spend: true) จะขับ kernel เพิ่มเติมด้วย: มันจะ resolve บัญชี marketing_expense และบัญชี offset, สร้างเอกสาร MARKETING-SPEND แล้ว submit journal ที่ balanced (Dr marketing_expense / Cr cash) ค่า idempotency_key ช่วยให้การ retry ทำ dedupe ที่ kernel เพื่อให้ต้นทุนของ ROI เท่ากับค่าใช้จ่ายใน GL เสมอ

%%{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 Pages Fn (recordCost)
  participant K as Kernel / GL
  participant D as D1 marketing_campaign_cost
  participant V as ROI projection view

  C->>F: POST spend.post (campaign_id, amount, idempotency_key)
  F->>F: mustCampaign + validate amount > 0
  F->>K: resolveAccount(marketing_expense) + offset(cash)
  F->>K: createDocument(MARKETING-SPEND, origin_ref campaign:spend)
  F->>K: submitDocument(intent: Dr expense / Cr offset)
  alt idempotent (key seen before)
    K-->>F: idempotent = true, prior journal_entry_id
    F->>D: SELECT existing cost by kernel_journal_entry_id
    D-->>F: prior cost row
    F-->>C: return prior row (no duplicate)
  else first post
    K-->>F: journal entry id
    F->>D: INSERT cost (status=posted, kernel ids)
    D-->>F: new cost row
    F-->>C: return posted cost row
  end
  C->>F: GET campaign/{id}/roi
  F->>V: SELECT roi_projection
  V-->>C: revenue / cost, roi_projection

Notes

  • จุดประสงค์: จัดการ campaign → audience (segment / member) → touch & attribution → ROI ครบวงจร; feed qualified lead และ attributed conversion ต่อให้ CRM/Sales (attributed_doctype = opportunity / sale / sales_order / invoice; lead ใช้ handoff_ref).
  • Money-safety: มีทางเดียวที่แตะ GL คือ recordCost เมื่อ post_spend — โพสต์ผ่าน kernel (createDocument + submitDocument) เป็น journal balanced Dr marketing_expense / Cr offset. ถ้าไม่ post แถวเป็นสถานะ recorded (kernel ids = NULL); schema CHECK บังคับว่า posted ต้องมี kernel_document_id.
  • Idempotency: หลายจุดกัน duplicate จริง — addMembers ดีดูปด้วย (campaign_id, party_id); linkAttribution ด้วย (member, doctype, doc_id); scoreLead ด้วย erp_marketing_lead_score_event (lead_id + rule_id); และ spend ดีดูปด้วย idempotency_key ที่ kernel — ถ้าซ้ำจะคืน cost row เดิม ไม่เขียนใหม่ เพื่อให้ ROI cost == GL expense.
  • ROI เป็น view ล้วน: erp_marketing_campaign_roi_projection = SUM(attribution_link.attributed_revenue) หารด้วย SUM(campaign_cost.amount); ถ้า cost = 0 จะคืน roi_projection = NULL (กันหารด้วยศูนย์).
  • Attribution มี 2 กลไก: linkAttribution (single link, model last/first/manual → ป้อน ROI revenue) และ attributeConversion (multi-touch: เลือก touches ตาม model first/last/linear แล้ว splitCredit แตกเครดิต ลง attribution_credit).
  • Gotchas: (1) ไม่มี endpoint เปลี่ยน status campaign / journey — ตั้งตอน create เท่านั้น (มี CHECK enum draft→archived / draft→retired); enrollJourney ต้อง journey status = active ไม่งั้น 409. (2) routeGet มี generic list/get ต่อทุกตาราง marketing ผ่าน whitelist _RRT (scoped ด้วย company_id). (3) capability gate ยอมรับ erp หรือ manage หรือ marketing.