SurePhone ERD — Domain 2: Customer (ลูกค้า)

ลูกค้า login ผ่าน LINE · ผูกระดับบ้าน (ทำสัญญาได้ทุกสาขาในบ้าน) · เกรดล่าสุดแยกตามบ้าน · ยืนยัน OTP/PDPA [?] = รอ confirm business logic


DBML

//// audit_c, audit_cu (partials) + sys.r2_file นิยามใน 00-shared.dbml (concat ก่อน compile)
 
//// ───────── customer ─────────
Enum customer.grade {
  "A"
  "B"
  "C"
}
 
Table customer.users {
  id uuid [pk, default: `gen_random_uuid()`]
  business_id uuid [not null, note: 'ลูกค้าผูกระดับบ้าน — เกรดแยกตามบ้าน (คนเดียวกันคนละบ้าน = คนละ row)']
  registered_shop_id uuid [note: 'ร้านสาขาที่สมัครครั้งแรก (อ้างอิง) → shop.shops · ทำสัญญาได้ทุกสาขาในบ้าน']
  line_user_id varchar(255) [note: 'จาก LINE Login — null จนกว่าจะผูก LINE']
  first_name varchar(255) [not null]
  last_name varchar(255) [not null]
  phone_number varchar(10) [not null, note: 'ยืนยันด้วย OTP']
  date_of_birth date [note: 'ใช้คำนวณอายุ']
  email varchar(255)
  facebook_link varchar(255)
  line_id_display varchar(255) [note: 'Line ID ที่โชว์ในโปรไฟล์']
  grade customer.grade [note: 'เกรดล่าสุดของลูกค้าในบ้านนี้ (materialized, ไม่เก็บประวัติ)']
  grade_calculated_at timestamptz [note: 'เวลาคำนวณเกรดล่าสุด']
  ~audit_cu
 
  indexes {
    (business_id, line_user_id) [name: 'idx_customer_business_line', unique]
    (business_id, phone_number) [name: 'idx_customer_business_phone']
  }
  checks {
    `phone_number ~ '^[0-9]{10}$'`
  }
  Note: '''
    ลูกค้า — ไม่ลบ/ไม่ปิดใช้งาน (ไม่มี is_active/is_deleted)
    รูปโปรไฟล์ดึงจาก LINE avatar ผ่าน API — ไม่เก็บใน R2 (ไม่มี file_id)
    [PARKED] ชื่อ first/last vs full_name + field "สถานะ" → รอ verify Figma หลังเคลียร์ shop+contract
    [?] ตอนร้านทำสัญญาหน้าร้าน (SH5 ทาง A) record ลูกค้าไปอยู่ตารางนี้เลย หรือสร้างแยกผูก LINE ทีหลัง → เคลียร์ตอน Domain 6
  '''
}
Ref fk_customer_business: customer.users.business_id > org.businesses.id [delete: restrict, update: no action]
Ref fk_customer_shop: customer.users.registered_shop_id > shop.shops.id [delete: set null, update: no action] // ร้านสาขา = shop.shops (Domain 3)
 
Table customer.consents {
  id uuid [pk, default: `gen_random_uuid()`]
  user_id uuid [not null]
  terms_id uuid [not null, note: 'FK → org.terms_and_conditions (type=TERMS_OF_SERVICE)']
  accepted_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
 
  indexes {
    (user_id, terms_id) [name: 'idx_customer_consent', unique]
  }
  Note: 'PDPA/Consent — บันทึกการยอมรับข้อตกลงแต่ละเวอร์ชัน'
}
Ref fk_consent_user: customer.consents.user_id > customer.users.id [delete: cascade, update: no action]
Ref fk_consent_terms: customer.consents.terms_id > org.terms_and_conditions.id [delete: restrict, update: no action]

Mermaid ER

erDiagram
  businesses ||--o{ customer_users : "เป็นลูกค้าของ"
  shops ||--o{ customer_users : "สมัครที่ (อ้างอิง)"
  customer_users ||--o{ customer_consents : "ยอมรับ PDPA"
  terms_and_conditions ||--o{ customer_consents : "เวอร์ชัน"

  customer_users {
    uuid id PK
    uuid business_id FK
    uuid registered_shop_id FK
    varchar line_user_id UK
    varchar first_name
    varchar last_name
    varchar phone_number
    date date_of_birth
    varchar email
    varchar facebook_link
    varchar line_id_display
    enum grade
    timestamptz grade_calculated_at
    varchar created_by
    varchar updated_by
    timestamptz created_at
    timestamptz updated_at
  }
  customer_consents {
    uuid id PK
    uuid user_id FK
    uuid terms_id FK
    timestamptz accepted_at
  }
  businesses {
    uuid id PK
    varchar name
  }
  shops {
    uuid id PK
    varchar shop_name
    varchar branch_name
  }
  terms_and_conditions {
    uuid id PK
    enum type
    integer version
  }

✅ Resolved (Domain 2) — 16 มิ.ย. 2026

  1. เกรดลูกค้า → A/B/C, เก็บแค่เกรดล่าสุดบน customer.users (ไม่เก็บประวัติ) · เกรดแยกตามบ้านโดยปริยายเพราะลูกค้าผูกระดับบ้าน ✓

  2. ขอบเขตลูกค้า → ระดับบ้าน (business_id); ทำสัญญาได้ทุกสาขา · registered_shop_id (→ shop.shops) เก็บแค่ร้านสาขาที่สมัครครั้งแรก ✓

  3. ข้อมูลชาวต่างชาติ → เก็บที่ระดับสัญญา (signatory snapshot, Domain 6) ✓

  4. ลูกค้าไม่ลบ/ไม่ปิด → ตัด is_active + is_deleted ออก ✓ (16 มิ.ย.)

  5. consistency → partials อยู่ 00-shared, รูปโปรไฟล์ = LINE avatar (ไม่เก็บ R2) ✓

⏸️ PARKED — กลับมาทำหลังเคลียร์ shop (Domain 3) + contract (Domain 6)

  • ชื่อ: first_name/last_name → น่าจะเป็น full_name เหมือน staff/investor → verify ฟอร์ม CU จริง
  • field “สถานะ” (CU2 profile): ยังไม่รู้คืออะไร → ดู Figma
  • อาชีพ/รายได้ (SH5): ตัดสินว่าอยู่ contract-level (Domain 6) ไม่ใช่ customer
  • LINE customer = contract customer (ทาง A หน้าร้าน): ใช้ customer.users เดียวกันไหม → เคลียร์ตอน contract