SurePhone ERD — Domain 1: Org & Access (กิจการ/บ้าน & การเข้าถึง)
บ้าน (org.businesses) = ระดับบนสุด สร้างโดย Super Admin เท่านั้น field ของ businesses ยึดจากฟอร์ม Figma “เพิ่มกิจการใหม่” (node 2957:413689) convention: schema prefix, audit_trail partial, enum, note, ref delete rule ·
[?]= รอ confirm
DBML
//// audit_c, audit_cu (partials) + sys.r2_file นิยามใน 00-shared.dbml (concat ก่อน compile)
//// ───────── org (บ้าน/กิจการ) ─────────
Table org.businesses {
id uuid [pk, default: `gen_random_uuid()`]
name varchar(255) [not null, note: 'ชื่อกิจการ/บ้าน — ห้ามซ้ำ (unique)']
logo_file_id uuid [not null, note: 'FK → sys.r2_file (โลโก้, is_public=true)']
// สำหรับร้านค้า (ช่องทางสื่อสารฝั่งร้าน)
facebook_link varchar(255) [not null]
facebook_qr_file_id uuid [not null, note: 'FK → sys.r2_file (QR Facebook, is_public=true)']
shop_line_messaging_token varchar(512) [not null, note: 'LINE Messaging Channel Access Token (ร้านค้า) [TODO] encrypt']
// สำหรับลูกค้า (LINE OA + LINE Login ฝั่งลูกค้า)
line_oa_link varchar(255) [not null]
line_oa_qr_file_id uuid [not null, note: 'FK → sys.r2_file (QR Line OA, is_public=true)']
line_login_channel_id varchar(255) [not null]
line_login_channel_secret varchar(512) [not null, note: '[TODO] encrypt']
customer_line_messaging_token varchar(512) [not null, note: 'LINE Messaging Channel Access Token (ลูกค้า) [TODO] encrypt']
phone_number varchar(10) [not null]
collect_vat bool [not null, default: true, note: 'เก็บ VAT 7% หรือไม่']
// ที่อยู่สำหรับคืนเครื่อง
return_name varchar(255) [not null, note: 'ชื่อกิจการสำหรับคืนเครื่อง']
return_address text [not null, note: 'ที่อยู่กิจการ (free text)']
return_phone varchar(10) [not null]
~audit_cu
indexes {
name [name: 'idx_business_name', unique, note: 'ชื่อบ้านห้ามซ้ำ']
}
checks {
`phone_number ~ '^[0-9]{10}$'`
`return_phone ~ '^[0-9]{10}$'`
}
Note: 'บ้าน/กิจการ — ระดับบนสุด สร้างโดย Super Admin เท่านั้น · ไม่มีลบ/ปิดใช้งาน (ไม่มี is_active/is_deleted) · ชื่อห้ามซ้ำ · ไม่มีสาขา (สาขาอยู่ที่ร้านพาร์ทเนอร์ — Domain 3) · รูปหน้าปก login ย้ายไป sys (ระดับระบบ)'
}
Ref fk_business_logo_file: org.businesses.logo_file_id > sys.r2_file.id [delete: restrict, update: no action]
Ref fk_business_fb_qr_file: org.businesses.facebook_qr_file_id > sys.r2_file.id [delete: restrict, update: no action]
Ref fk_business_lineoa_qr_file: org.businesses.line_oa_qr_file_id > sys.r2_file.id [delete: restrict, update: no action]
Table org.investors {
id uuid [pk, default: `gen_random_uuid()`]
business_id uuid [not null, note: 'นายทุน 1 คน ผูก 1 บ้าน']
display_name varchar(255) [not null, note: 'ชื่อ-นามสกุล / Display name (ฟิลด์เดียว)']
email varchar(255) [not null]
phone_number varchar(10) [not null, note: 'ใช้ login + OTP']
investment_amount bigint [not null, default: 0, note: 'เงินลงทุน (สตางค์)']
password varchar(255) [not null, note: 'Hash — รหัสผ่าน 8+ ตัว มีพิมพ์เล็ก/พิมพ์ใหญ่/ตัวเลข [TODO]']
is_phone_verified bool [not null, default: false, note: 'false = login ครั้งแรกต้องยืนยัน OTP; true แล้ว login ด้วยเบอร์+รหัส (เปลี่ยนเบอร์ → OTP ใหม่)']
~audit_cu
indexes {
phone_number [name: 'idx_investor_phone', unique]
}
checks {
`phone_number ~ '^[0-9]{10}$'`
}
Note: 'นายทุน/ผู้ลงทุนของบ้าน — login เอง (เบอร์+รหัสผ่าน+OTP) ดู dashboard (AF) · ลบถาวรได้ ไม่มีปิดใช้งาน (ไม่มี is_active/is_deleted) · field ตามฟอร์มสร้างนักลงทุน (node 1858:309897)'
}
Ref fk_investor_business: org.investors.business_id > org.businesses.id [delete: restrict, update: no action]
//// ───────── org: ข้อตกลง/เงื่อนไข (ระดับกิจการ) ─────────
Enum org.terms_type {
"TERMS_OF_SERVICE" [note: 'เงื่อนไขการให้บริการ — ใช้ทั้งร้านและลูกค้า (versioned, AD11.3) · เพิ่ม type อื่นได้ภายหลัง']
}
Table org.terms_and_conditions {
id uuid [pk, default: `gen_random_uuid()`]
business_id uuid [not null, note: 'ข้อตกลงระดับกิจการ — admin แต่ละบ้านแก้เอง (AD11.3)']
type org.terms_type [not null]
version integer [not null, note: 'เวอร์ชันของ (business, type)']
content text [not null]
is_latest bool [not null, default: false, note: 'เวอร์ชันล่าสุดที่บังคับใช้ต่อ (business, type)']
~audit_cu
indexes {
(business_id, type, version) [name: 'idx_terms_version', unique]
(business_id, type, is_latest) [name: 'idx_terms_latest']
}
Note: 'ข้อตกลง/เงื่อนไข ระดับกิจการ แยกตามประเภท + เวอร์ชัน · ร้าน/ลูกค้ารับเวอร์ชันล่าสุดก่อนใช้งาน · เก็บทุกเวอร์ชันเพื่ออ้างอิงการยอมรับ'
}
Ref fk_terms_business: org.terms_and_conditions.business_id > org.businesses.id [delete: cascade, update: no action]
//// ───────── staff (back-office: super admin / admin / employee) ─────────
Enum staff.role {
"SUPER_ADMIN" [note: 'สร้างบ้าน/สร้างพนักงานได้ เข้าทุกบ้าน จัดการระบบ']
"ADMIN" [note: 'ผู้ดูแล — ต้องถูกกำหนดบ้าน + สิทธิ์ ก่อนใช้งาน']
}
Table staff.users {
id uuid [pk, default: `gen_random_uuid()`]
profile_file_id uuid [note: 'FK → sys.r2_file (รูปโปรไฟล์, optional)']
phone_number varchar(10) [not null, note: 'login ด้วยเบอร์ + รหัสผ่าน']
display_name varchar(255) [not null, note: 'ชื่อ-นามสกุล / Display name (ฟิลด์เดียว)']
password varchar(255) [not null, note: 'Hash — รหัสผ่าน 8+ ตัว มีพิมพ์เล็ก/พิมพ์ใหญ่/ตัวเลข [TODO]']
must_change_password bool [not null, default: true, note: 'true = บังคับตั้งรหัสใหม่ตอน login ครั้งแรก → false หลังเปลี่ยน']
role staff.role [not null, default: 'ADMIN', note: '[?] ฟอร์มสร้างไม่เลือก role → default ADMIN; SUPER_ADMIN seed/พิเศษ']
is_active bool [not null, default: true, note: 'ปิดการใช้งานได้']
is_deleted bool [not null, default: false, note: 'Soft delete — ลบแบบเก็บข้อมูล']
~audit_cu
indexes {
phone_number [name: 'idx_staff_phone', unique, note: 'where is_deleted = false']
}
checks {
`phone_number ~ '^[0-9]{10}$'`
}
Note: 'ผู้ใช้หลังบ้าน (SUPER_ADMIN/ADMIN) — login เบอร์+รหัสผ่าน · สร้างเสร็จยังไม่ผูกบ้าน/ไม่มีสิทธิ์ ต้องกำหนดผ่าน user_businesses + user_permissions แยก · ปิดใช้งาน/ลบ(เก็บข้อมูล)ได้ · field ตามฟอร์ม node 1855:237840'
}
Ref fk_staff_profile_file: staff.users.profile_file_id > sys.r2_file.id [delete: set null, update: no action]
Table staff.user_businesses {
user_id uuid
business_id uuid
indexes {
(user_id, business_id) [pk]
}
Note: 'บ้านที่ staff เข้าถึงได้ (หน้าเลือกกิจการ) — staff ฝั่งระบบเข้าได้หลายบ้าน; SUPER_ADMIN เข้าทุกบ้าน เช็คที่ app layer'
}
Ref fk_userbiz_user: staff.user_businesses.user_id > staff.users.id [delete: cascade, update: no action]
Ref fk_userbiz_business: staff.user_businesses.business_id > org.businesses.id [delete: cascade, update: no action]
Table staff.permissions {
id smallserial [pk]
code varchar(30) [not null, unique, note: 'รหัสสั้นของ feature']
description varchar(255)
~audit_c
Note: 'แคตตาล็อก feature/สิทธิ์ทั้งหมดในระบบ — ผูกกับพนักงานผ่าน user_permissions (SA4)'
}
Table staff.user_permissions {
user_id uuid
permission_id smallint
indexes {
(user_id, permission_id) [pk]
}
Note: 'SA4 จัดการสิทธิ์ราย feature ต่อพนักงาน — per-user ทั้งระบบ (ไม่แยกตามบ้าน)'
}
Ref fk_userperm_user: staff.user_permissions.user_id > staff.users.id [delete: cascade, update: no action]
Ref fk_userperm_permission: staff.user_permissions.permission_id > staff.permissions.id [delete: cascade, update: no action]Mermaid ER
erDiagram businesses ||--o{ investors : "มีนายทุน" businesses ||--o{ user_businesses : "เข้าถึงโดย" staff_users ||--o{ user_businesses : "เข้าถึงบ้าน" staff_users ||--o{ user_permissions : "ได้รับสิทธิ์" permissions ||--o{ user_permissions : "เป็นสิทธิ์ใน" r2_file ||--o{ businesses : "โลโก้/QR" r2_file ||--o{ staff_users : "โปรไฟล์" businesses ||--o{ terms_and_conditions : "ข้อตกลง" businesses { uuid id PK varchar name UK uuid logo_file_id FK varchar facebook_link uuid facebook_qr_file_id FK varchar shop_line_messaging_token varchar line_oa_link uuid line_oa_qr_file_id FK varchar line_login_channel_id varchar line_login_channel_secret varchar customer_line_messaging_token varchar phone_number bool collect_vat varchar return_name text return_address varchar return_phone varchar created_by varchar updated_by timestamptz created_at timestamptz updated_at } investors { uuid id PK uuid business_id FK varchar display_name varchar email varchar phone_number UK bigint investment_amount varchar password bool is_phone_verified varchar created_by varchar updated_by timestamptz created_at timestamptz updated_at } staff_users { uuid id PK uuid profile_file_id FK varchar phone_number UK varchar display_name varchar password bool must_change_password enum role bool is_active bool is_deleted varchar created_by varchar updated_by timestamptz created_at timestamptz updated_at } user_businesses { uuid user_id PK uuid business_id PK } permissions { smallserial id PK varchar code UK varchar description varchar created_by timestamptz created_at } user_permissions { uuid user_id PK smallint permission_id PK } r2_file { uuid id PK varchar object_key UK bool is_public varchar mime_type } terms_and_conditions { uuid id PK uuid business_id FK enum type integer version text content bool is_latest varchar created_by varchar updated_by timestamptz created_at timestamptz updated_at }
✅ Resolved (Domain 1) — 16 มิ.ย. 2026
- บ้าน (org.businesses) = ระดับบนสุด สร้างโดย Super Admin · field ยึดฟอร์ม Figma จริง (node 2957:413689) ✓
- ตัดทิ้ง:
business_bank_accounts,branches,business_platforms, enumplatform_type— ไม่มีในฟอร์ม/ระบบ ✓ - โมเดล staff → ตารางเดียว + role; เข้าหลายบ้านผ่าน
user_businesses; สิทธิ์ per-user ทั้งระบบ ✓ - นายทุน →
org.investorslogin เอง, ผูก 1 บ้าน ✓ - สาขา (branches) → ย้ายไปฝั่งร้านพาร์ทเนอร์ (Domain 3) — บ้านไม่มีสาขา ✓
✅ Resolved — review รอบ 2 (16 มิ.ย. 2026)
- businesses ไม่มีลบ/ปิดใช้งาน → ตัด
is_active,is_deletedออก ✓ - ชื่อบ้านห้ามซ้ำ →
idx_business_nameเป็น unique ✓ - รูปหน้าปก login → ระดับระบบ ไม่ใช่ระดับบ้าน → ตัด
cover_img_pathออกจาก org ย้ายไป sys (Domain 8) ✓ - นายทุน → ฟิลด์ตามฟอร์มจริง (node 1858:309897):
display_nameฟิลด์เดียว, email required · ลบถาวรได้ ไม่มีปิดใช้งาน → ตัดis_active,is_deleted· มีis_phone_verified(OTP ครั้งแรก) ✓
✅ Resolved — review รอบ 3 (16 มิ.ย. 2026)
- check เบอร์โทร → มีทุกตารางที่มีเบอร์ เหมือนกันทั้งระบบ
~ '^[0-9]{10}$'(เพิ่มให้ businesses.phone_number + return_phone แล้ว) ✓ - นายทุน อายุ/สถานะ → ไม่มีการแสดง (ยืนยัน node 2961:417051) → ไม่เก็บ ✓
- พนักงานมี 2 role เท่านั้น → ตัด
EMPLOYEE; เหลือ SUPER_ADMIN, ADMIN ✓ - ฟอร์มสร้างพนักงาน (node 1855:237840) →
display_nameฟิลด์เดียว, ไม่มี email, profile_file_id optional · ปิดใช้งาน + soft delete ได้ ·must_change_passwordตั้งรหัสครั้งแรก ✓ - flow กำหนดสิทธิ์ → สร้างพนักงานเสร็จ = ยังไม่ผูกบ้าน/ไม่มีสิทธิ์ → กำหนดผ่าน
user_businesses(เลือกกิจการ node 2581:364525) +user_permissions(กำหนดสิทธิ์ node 592:115505) แยกขั้นตอน ✓
✅ Resolved — review รอบ 4 (R2 file storage, 16 มิ.ย. 2026)
- ไฟล์ R2 = Normalized FK (แบบ A) → ทุก
xxx_img_pathเปลี่ยนเป็นxxx_file_id uuidFK →sys.r2_file(นิยามใน 00-shared) ✓ - ไม่เก็บ public URL ถาวร → r2_file เก็บแค่
object_key+ flagis_public(โลโก้/QR = public; บัตร/เซลฟี่/รูปเครื่อง/สลิป/วิดีโอ = private เข้าผ่าน signed URL) · bucket เดียว = app config ✓ - Domain 1 แปลงแล้ว:
logo_file_id,facebook_qr_file_id,line_oa_qr_file_id(businesses, public),profile_file_id(staff, optional) ✓
📝 หมายเหตุข้ามโดเมน / ค้าง
customer.users.registered_shop_id(Domain 2) → ชี้ shop.shops (Domain 3) ✓ แก้แล้ว- รูปหน้าปก login: ของกิจการ →
org.business_settings.login_cover_file_id(01b) · ของ Portal หลัก →sys.portal_settings✓ - LINE tokens/secrets ควร encrypt at rest (มาร์ค [TODO])
- convention ใหม่: ทุก field เบอร์โทร มี check
~ '^[0-9]{10}$'เสมอ - [?] role assignment: ฟอร์มสร้างพนักงานไม่เลือก role → default ADMIN; SUPER_ADMIN มาจากไหน (seed/พิเศษ) รอเคลียร์