SurePhone ERD — Domain 4: Device Catalog (สินค้า/แคตตาล็อก · AD12) 🚧

building blocks ของสินค้า (ต่อกิจการ): ยี่ห้อ, ประเภทอุปกรณ์, ความจุ, เรทชำระ 🚧 ตาราง “สินค้า/รุ่น” (product) ที่รวมทุกอย่าง + ราคา → รอฟอร์มสร้างสินค้า schema = device · audit อยู่ 00-shared · ทุก catalog ผูก business_id (ต่อกิจการ)


DBML

//// audit_c, audit_cu นิยามใน 00-shared.dbml · org.businesses ใน 01-org-access.md
 
Enum device.device_type {
  "SMARTPHONE"
  "TABLET"
  "LAPTOP"
}
 
//// ───────── ยี่ห้อ (custom ต่อกิจการ) ─────────
Table device.brands {
  id smallserial [pk]
  business_id uuid [not null]
  name varchar(255) [not null, note: 'ชื่อยี่ห้อ — ตั้งชื่ออย่างเดียว']
  ~audit_cu
 
  indexes {
    (business_id, name) [name: 'idx_brand_business_name', unique]
  }
  Note: 'ยี่ห้อ (custom) — ลบได้ถ้าไม่มีสินค้าใช้ (FK products.brand_id = restrict) · ไม่มี soft delete'
}
Ref fk_brand_business: device.brands.business_id > org.businesses.id [delete: cascade, update: no action]
 
//// ───────── ความจุ (config ต่อกิจการ) ─────────
Table device.capacities {
  id smallserial [pk]
  capacity_gb integer [not null, note: 'ความจุ (GB)']
  ~audit_cu
 
  indexes {
    capacity_gb [name: 'idx_capacity_gb', unique]
  }
  checks {
    `capacity_gb > 0`
  }
  Note: 'ความจุเครื่อง (GB) — ระดับระบบ (shared ทุกกิจการ) ไม่ผูกกิจการ'
}
 
//// ───────── เรทชำระ (custom ต่อกิจการ) — header + items ─────────
Table device.payment_rates {
  id smallserial [pk]
  business_id uuid [not null]
  name varchar(255) [not null, note: 'ชื่อเรทชำระ — ห้ามซ้ำ (ต่อกิจการ)']
  document_fee integer [not null, note: 'ค่าเอกสาร (สตางค์) — ฟอร์มกรอกเป็นบาท']
  ~audit_cu
 
  indexes {
    (business_id, name) [name: 'idx_rate_business_name', unique]
  }
  checks {
    `document_fee >= 0`
  }
  Note: 'เรทชำระ (custom) — มีรายการ (จำนวนเดือน/ตัวคูณ/ค่าคอม) ใน payment_rate_items'
}
Ref fk_rate_business: device.payment_rates.business_id > org.businesses.id [delete: cascade, update: no action]
 
Table device.payment_rate_items {
  id serial [pk]
  rate_id smallint [not null]
  months smallint [not null, note: 'จำนวนเดือน — จำนวนเต็ม ≥ 1']
  multiplier numeric(10,4) [not null, note: 'ตัวคูณ — > 0']
  commission_pct numeric(5,2) [not null, note: 'ค่าคอมฯ (%) — > 0']
 
  indexes {
    (rate_id, months) [name: 'idx_rate_item_months', unique, note: 'เดือนห้ามซ้ำในเรทเดียวกัน']
  }
  checks {
    `months >= 1`
    `multiplier > 0`
    `commission_pct > 0`
  }
  Note: 'รายการอัตราในเรทชำระ (อย่างน้อย 1 รายการ บังคับที่ app layer)'
}
Ref fk_rate_item_rate: device.payment_rate_items.rate_id > device.payment_rates.id [delete: cascade, update: no action]
 
//// ───────── สินค้า/รุ่น (product) + ราคาต่อความจุ ─────────
Table device.products {
  id serial [pk]
  business_id uuid [not null]
  brand_id smallint [not null, note: 'FK → device.brands']
  device_type device.device_type [not null]
  name varchar(255) [not null, note: 'ชื่อรุ่น — ห้ามซ้ำ (ต่อกิจการ)']
  colors text[] [not null, note: 'สี (ชื่ออะไรก็ได้ ไม่มี config) — text array']
  payment_rate_id smallint [not null, note: 'เรทชำระเดียว ใช้กับทุกความจุของรุ่น']
  ~audit_cu
 
  indexes {
    (business_id, name) [name: 'idx_product_business_name', unique]
  }
  checks {
    `cardinality(colors) >= 1`
  }
  Note: 'รุ่นสินค้า — brand + type + ชื่อ + สี(array) + เรทชำระ(1) · ราคา/ความจุ อยู่ product_capacities · ค่าที่ใช้คำนวณเงินถูก snapshot ตอนทำสัญญา (Domain 5)'
}
Ref fk_product_business: device.products.business_id > org.businesses.id [delete: cascade, update: no action]
Ref fk_product_brand: device.products.brand_id > device.brands.id [delete: restrict, update: no action]
Ref fk_product_rate: device.products.payment_rate_id > device.payment_rates.id [delete: restrict, update: no action]
 
Table device.product_capacities {
  id serial [pk]
  product_id integer [not null]
  capacity_id smallint [not null]
  price_new integer [note: 'ราคามือหนึ่งสูงสุด (สตางค์) — nullable']
  price_used integer [note: 'ราคามือสองสูงสุด (สตางค์) — nullable']
  price_foreign integer [note: 'ราคาเครื่องนอก (สตางค์) — nullable']
  down_payment_percents integer[] [not null, note: 'อัตรายอดชำระก่อนรับสินค้า (เงินดาวน์) เป็น % หลายอัตรา เช่น {10,20,30}']
 
  indexes {
    (product_id, capacity_id) [name: 'idx_product_capacity', unique]
  }
  checks {
    `price_new IS NOT NULL OR price_used IS NOT NULL`
    `price_new IS NULL OR price_new > 0`
    `price_used IS NULL OR price_used > 0`
    `price_foreign IS NULL OR price_foreign > 0`
    `cardinality(down_payment_percents) >= 1`
  }
  Note: 'ราคาต่อความจุ — มือหนึ่ง/มือสอง/เครื่องนอก + เงินดาวน์ (% array) · บังคับกรอกมือหนึ่งหรือมือสองอย่างน้อย 1 · child ของ products (ไม่มี audit แยก)'
}
Ref fk_product_capacity_product: device.product_capacities.product_id > device.products.id [delete: cascade, update: no action]
Ref fk_product_capacity_capacity: device.product_capacities.capacity_id > device.capacities.id [delete: restrict, update: no action]

Mermaid ER

erDiagram
  businesses ||--o{ brands : "ยี่ห้อ"
  businesses ||--o{ payment_rates : "เรทชำระ"
  businesses ||--o{ products : "สินค้า/รุ่น"
  payment_rates ||--o{ payment_rate_items : "รายการอัตรา"
  brands ||--o{ products : "ยี่ห้อ"
  payment_rates ||--o{ products : "เรทชำระ"
  products ||--o{ product_capacities : "ราคา/ความจุ"
  capacities ||--o{ product_capacities : "ความจุ"

  brands {
    smallserial id PK
    uuid business_id FK
    varchar name
    varchar created_by
    timestamptz created_at
  }
  capacities {
    smallserial id PK
    integer capacity_gb
  }
  payment_rates {
    smallserial id PK
    uuid business_id FK
    varchar name
    integer document_fee
  }
  payment_rate_items {
    serial id PK
    smallint rate_id FK
    smallint months
    numeric multiplier
    numeric commission_pct
  }
  products {
    serial id PK
    uuid business_id FK
    smallint brand_id FK
    enum device_type
    varchar name
    text_array colors
    smallint payment_rate_id FK
  }
  product_capacities {
    serial id PK
    integer product_id FK
    smallint capacity_id FK
    integer price_new
    integer price_used
    integer price_foreign
    int_array down_payment_percents
  }

✅ Resolved

  • ยี่ห้อ device.brands (custom, ชื่ออย่างเดียว, unique/กิจการ, ลบได้ถ้าไม่ถูกใช้ผ่าน FK restrict)

  • ประเภท Enum device.device_type (SMARTPHONE/TABLET/LAPTOP)

  • ความจุ device.capacities (config, GB) — ระดับระบบ (ไม่ผูกกิจการ)

  • เรทชำระ device.payment_rates (ชื่อ+ค่าเอกสาร) + device.payment_rate_items (เดือน/ตัวคูณ/ค่าคอม, เดือนห้ามซ้ำ, ทุกค่ามี check)

  • สินค้า/รุ่น device.products (brand+type+ชื่อ unique+สี text[]+เรทชำระ 1 ตัว) + device.product_capacities (ราคา มือหนึ่ง/มือสอง/เครื่องนอก + เงินดาวน์ % array · ความจุ M:N) — ฟอร์ม node 1095:158862 ✓ (รีไฟแนนซ์ใน UI = ทำเกิน ไม่มีจริง)

❓ ยังค้าง

  • contract snapshot — ตอนทำสัญญาต้อง snapshot ราคา/เรท/เงินดาวน์ ที่เลือก (Domain 5) เพื่อกันราคาเปลี่ยนกระทบสัญญาเก่า