SurePhone ERD — Domain 3: Shop (ร้านค้าพาร์ทเนอร์) 🚧 WIP
กำลังไล่เก็บ field จากฟอร์มสมัครจริง (7 ขั้น) ทีละฟอร์ม — ยังไม่ fix ตาราง รอครบก่อนประกอบ
[?]= รอ confirm
Registration Flow (สมัครเป็นพาร์ทเนอร์)
- หน้า Login ร้าน (node 268:33167) → กดปุ่ม “สมัครเลย”
- ยอมรับข้อตกลงล่าสุด (181:21565, 181:21556) — ดึง terms version ล่าสุดให้รับก่อนเสมอ
- กรอกเบอร์โทร + OTP (181:21632)
- เช็คเบอร์ซ้ำ: ห้ามซ้ำข้ามร้านสาขา (เบอร์ unique ระดับร้านสาขา)
- OTP จัดการด้วย Redis (ephemeral — ไม่เก็บใน DB)
- ฟอร์มสมัคร 7 ขั้น (เริ่ม 532:88707) — ดู field inventory ด้านล่าง
หมายเหตุ: terms acceptance ของร้าน คล้าย customer.consents → ผูก sys.terms_and_conditions (Domain 8)
Field Inventory — ฟอร์มสมัคร 7 ขั้น
Form 1/7 — ข้อมูลผู้สมัคร (เจ้าของ/ตัวแทนร้าน) · node 532:88707
- ภาพบัตรประชาชน* → file_id (private) · 2–5 mb
- คำนำหน้าชื่อ* → enum: นาย / นาง / นางสาว
- หมายเลขบัตรประชาชน* → varchar(13), check
^[0-9]{13}$ - ชื่อ-นามสกุล* → owner_full_name (ฟิลด์เดียว — ชื่อตามบัตร)
- วันเดือนปีเกิด* → date
- ที่อยู่ตามบัตรประชาชน:
- เลขที่อยู่* → address_line (บ้านเลขที่ ซอย หมู่บ้าน)
- จังหวัด / เขต-อำเภอ / แขวง-ตำบล / รหัสไปรษณีย์* → เก็บ
sub_district_id(FK → sys.sub_districts) แล้ว derive จังหวัด/อำเภอ/ไปรษณีย์ ผ่าน join (แบบ phonerefun)
- รู้จักเราจากช่องทางไหน* → enum (ดูด้านล่าง)
Form 2/7 — ข้อมูลร้านค้าพาร์ทเนอร์ · node 181:22772
- ชื่อร้าน / บริษัท* →
shop_name - ชื่อสาขา* →
branch_name⭐ ไม่แยกตารางสาขา — 2 คอลัมน์ในตารางร้านเลย (แต่ละ row = 1 สาขา) - อีเมล* →
email - แนบลิงก์ช่องทางการขาย* (Facebook/IG/TikTok) →
sales_channel_link
Form 3/7 — ที่อยู่ร้าน + เอกสาร · (อธิบาย ยังไม่มี node)
- รูปหน้าร้าน* → file_id (private)
- ใบทะเบียนพาณิชย์ (ถ้ามี) → file_id (optional)
- ภ.พ.20 (ถ้ามี) → file_id (optional)
- ที่อยู่ตามใบทะเบียนไหม → bool toggle
- รายละเอียดที่อยู่ร้าน → address_line + sub_district_id (FK sys.sub_districts)
Form 4/7 — เลขที่บัญชีธนาคาร · node 181:24165
- ชื่อธนาคาร* (dropdown) →
bank_code(FK → sys.banks, Domain 8) - เลขที่บัญชี* →
bank_account_number - ชื่อบัญชี* →
bank_account_name
1 ร้านสาขา = 1 บัญชี → เก็บ inline ในตารางร้าน
Form 5/7 — สร้าง Account เจ้าของร้าน · (อธิบาย ยังไม่มี node)
- username + password (login ร้าน)
- ⭐ แต่ละร้านสาขา: เจ้าของ 1 + พนักงานได้อีก ไม่เกิน 5 คน → ตาราง
shop.users(role OWNER/STAFF, max 6/สาขา)
Form 6/7 — ลายเซ็นร้านค้า · node 485:86408
- ลายเซ็น (วาด) →
signature_file_id(private)
Form 7/7 — รีวิวสัญญา + ส่ง · (ปุ่ม “ดูพรีวิวสัญญา”)
- ส่งใบสมัคร → สถานะ PENDING รอแอดมิน (AD3) อนุมัติ
DBML
//// audit_c, audit_cu (partials) + sys.r2_file นิยามใน 00-shared.dbml
//// sys.banks, sys.sub_districts (geo) นิยามใน sys (Domain 8)
//// ───────── shop ─────────
Enum shop.title_prefix {
"MR" [note: 'นาย']
"MRS" [note: 'นาง']
"MISS" [note: 'นางสาว']
}
Enum shop.partner_status {
"PENDING" [note: 'รออนุมัติ (สมัครใหม่ หรือ แก้ไขส่งใหม่)']
"RETURNED" [note: 'ตีกลับให้แก้ไข — ถ้าไม่แก้ใน 14 วัน → CANCELED (auto)']
"REJECTED" [note: 'ไม่อนุมัติ → ประวัติ · ลบ row ได้เพื่อปล่อยเบอร์ให้สมัครใหม่']
"CANCELED" [note: 'ตีกลับแล้วไม่แก้ใน 14 วัน (auto) → ประวัติ · ลบ row ได้']
"ACTIVE" [note: 'เป็นพาร์ทเนอร์ ใช้งานได้ (เดิม APPROVED)']
"INACTIVE" [note: 'ยกเลิกพาร์ทเนอร์จาก ACTIVE — ปรับกลับ ACTIVE ได้']
}
Enum shop.grade {
"A" [note: 'หนี้เสีย ≤ 3% ของลูกค้าทำสัญญาทั้งหมดของร้าน']
"B" [note: '> 3% และ ≤ 8%']
"C" [note: '> 8% และ ≤ 12%']
"E" [note: '> 12% (ข้าม D)']
}
Enum shop.user_role {
"OWNER" [note: 'เจ้าของร้าน — 1 ต่อสาขา']
"STAFF" [note: 'พนักงานร้าน — ไม่เกิน 5 ต่อสาขา']
}
Table shop.referral_channels {
id smallserial [pk]
name varchar(100) [not null, unique, note: 'ช่องทางที่ลูกค้ารู้จัก']
is_active bool [not null, default: true]
~audit_c
Note: 'Lookup ช่องทางรู้จัก (form 1) — seed: Facebook, Line OA, TikTok, เพื่อน/คนรู้จักบอกต่อ, พนักงานบริษัทแนะนำ, อื่นๆ · แอดมินจัดการเพิ่ม/ปิดได้'
}
Table shop.shops {
id uuid [pk, default: `gen_random_uuid()`]
business_id uuid [not null, note: 'บ้านที่ร้านสมัครเข้าเป็นพาร์ทเนอร์']
// ───── ข้อมูลร้าน (form 2) — 1 row = 1 สาขา ─────
shop_name varchar(255) [not null, note: 'ชื่อร้าน/บริษัท']
branch_name varchar(255) [not null, note: 'ชื่อสาขา (ไม่แยกตารางสาขา)']
email varchar(255) [not null]
sales_channel_link varchar(255) [not null, note: 'ลิงก์ช่องทางการขาย (FB/IG/TikTok)']
phone_number varchar(10) [not null, note: 'เบอร์ร้าน — unique ต่อบ้าน · ยืนยัน OTP (Redis) · = เบอร์ owner (shop.users role=OWNER) sync กัน · แก้ไขได้']
// ───── เจ้าของ/ตัวแทน (form 1) ─────
owner_title_prefix shop.title_prefix [not null]
owner_national_id varchar(13) [not null]
owner_full_name varchar(255) [not null]
owner_date_of_birth date [not null]
owner_id_card_file_id uuid [not null, note: 'FK → sys.r2_file (รูปบัตร, private)']
owner_address_line varchar(255) [not null, note: 'ที่อยู่ตามบัตร']
owner_sub_district_id integer [not null, note: 'FK → sys.sub_districts']
referral_channel_id smallint [not null, note: 'FK → shop.referral_channels']
// ───── ที่อยู่ร้าน + เอกสาร (form 3) ─────
address_line varchar(255) [not null]
sub_district_id integer [not null, note: 'FK → sys.sub_districts']
address_same_as_registration bool [not null, default: false, note: 'ที่อยู่ร้านตามใบทะเบียนหรือไม่']
storefront_file_id uuid [not null, note: 'FK → sys.r2_file (รูปหน้าร้าน, private)']
commercial_reg_file_id uuid [note: 'FK → sys.r2_file (ใบทะเบียนพาณิชย์ — optional)']
por_por_20_file_id uuid [note: 'FK → sys.r2_file (ภ.พ.20 — optional)']
// ───── บัญชีธนาคาร (form 4) ─────
bank_code varchar(10) [not null, note: 'FK → sys.banks · แก้ไขได้']
bank_account_number varchar(20) [not null, note: 'แก้ไขได้']
bank_account_name varchar(255) [not null, note: 'ชื่อบัญชี — ห้ามเปลี่ยน']
// ───── ลายเซ็น (form 6) ─────
signature_file_id uuid [not null, note: 'FK → sys.r2_file (ลายเซ็น, private)']
// ───── เอกสารสัญญา + เชื่อม LINE group (AD3 ตอนพิจารณา) ─────
contract_file_id uuid [note: 'FK → sys.r2_file — เอกสารสัญญาที่ระบบ gen ตอนยื่น (เปลี่ยนตามข้อมูลที่กรอก, private)']
line_group_id varchar(255) [unique, note: 'groupId ของไลน์กลุ่ม — webhook จับเมื่อเจ้าของส่งรหัส (OTP/Redis) ในกลุ่ม · null จนผูกสำเร็จ']
line_group_name varchar(255) [note: 'ชื่อไลน์กลุ่ม (พนักงานกรอกตอนพิจารณา)']
line_connected_at timestamptz [note: 'เวลาที่ผูกกลุ่มไลน์สำเร็จ']
// ───── สถานะพาร์ทเนอร์ ─────
status shop.partner_status [not null, default: 'PENDING']
grade shop.grade [not null, default: 'A', note: 'เกรดร้าน (materialized) — ร้านใหม่ = A · คำนวณใหม่จากสัดส่วนลูกค้าหนี้เสีย/ลูกค้าทำสัญญาทั้งหมด']
grade_calculated_at timestamptz [note: 'เวลาคำนวณเกรดจริงล่าสุด — null = ยังไม่เคยคำนวณ (ใช้ default A)']
~audit_cu
indexes {
(business_id, phone_number) [name: 'idx_shop_business_phone', unique, note: 'เบอร์ร้านห้ามซ้ำต่อบ้าน']
business_id [name: 'idx_shop_business']
owner_national_id [name: 'idx_shop_owner_nid']
}
checks {
`phone_number ~ '^[0-9]{10}$'`
`owner_national_id ~ '^[0-9]{13}$'`
}
Note: 'ร้านค้าพาร์ทเนอร์ — 1 row = 1 สาขา (denormalized: เจ้าของ+ที่อยู่+บัญชี+ลายเซ็น+สถานะ รวมในตารางเดียว) · สมัคร 7 ขั้น · เจ้าของ identity อยู่ owner_* (พนักงานไม่มี) · REJECTED/CANCELED hard-delete row ได้เพื่อปล่อยเบอร์สมัครใหม่ · ACTIVE↔INACTIVE ยกเลิก/คืนสถานะพาร์ทเนอร์'
}
Ref fk_shop_business: shop.shops.business_id > org.businesses.id [delete: restrict, update: no action]
Ref fk_shop_referral: shop.shops.referral_channel_id > shop.referral_channels.id [delete: restrict, update: no action]
Ref fk_shop_owner_subdistrict: shop.shops.owner_sub_district_id > sys.sub_districts.id [delete: restrict, update: no action]
Ref fk_shop_subdistrict: shop.shops.sub_district_id > sys.sub_districts.id [delete: restrict, update: no action]
Ref fk_shop_bank: shop.shops.bank_code > sys.banks.code [delete: restrict, update: no action]
Ref fk_shop_idcard_file: shop.shops.owner_id_card_file_id > sys.r2_file.id [delete: restrict, update: no action]
Ref fk_shop_storefront_file: shop.shops.storefront_file_id > sys.r2_file.id [delete: restrict, update: no action]
Ref fk_shop_commreg_file: shop.shops.commercial_reg_file_id > sys.r2_file.id [delete: set null, update: no action]
Ref fk_shop_pp20_file: shop.shops.por_por_20_file_id > sys.r2_file.id [delete: set null, update: no action]
Ref fk_shop_signature_file: shop.shops.signature_file_id > sys.r2_file.id [delete: restrict, update: no action]
Ref fk_shop_contract_file: shop.shops.contract_file_id > sys.r2_file.id [delete: set null, update: no action]
Table shop.users {
id uuid [pk, default: `gen_random_uuid()`]
shop_id uuid [not null]
business_id uuid [not null, note: 'denormalize จาก shops.business_id — ใช้บังคับ username unique ต่อกิจการ']
username varchar(32) [not null, note: 'login · a-z A-Z 0-9 . _ - · ขึ้นต้นตัวอักษร · 3-32 ตัว · unique ต่อกิจการ']
display_name varchar(255) [not null, note: 'ชื่อ-นามสกุล แสดงในร้าน (ซ้ำได้)']
phone_number varchar(10) [not null, note: 'OWNER: เบอร์เดียวกับ shops.phone_number (sync)']
password varchar(255) [not null, note: 'Hash — 8+ ตัว พิมพ์เล็ก/พิมพ์ใหญ่/ตัวเลข [TODO]']
role shop.user_role [not null]
is_deleted bool [not null, default: false, note: 'Soft delete — ไม่มีปิดใช้งาน (is_active)']
~audit_cu
indexes {
(business_id, username) [name: 'idx_shop_user_username', unique, note: 'where is_deleted=false · username ห้ามซ้ำต่อกิจการ']
shop_id [name: 'idx_shop_user_shop']
}
checks {
`username ~ '^[a-zA-Z][a-zA-Z0-9._-]{2,31}$'`
`phone_number ~ '^[0-9]{10}$'`
}
Note: 'บัญชี login ร้าน (เจ้าของ + พนักงาน · OWNER 1 + STAFF ≤5/สาขา) · username unique ต่อกิจการ · display_name ซ้ำได้ · เปลี่ยน username/password/display_name/เบอร์ ได้เสมอ · soft delete (ไม่มีปิดใช้งาน)'
}
Ref fk_shopuser_shop: shop.users.shop_id > shop.shops.id [delete: cascade, update: no action]
Ref fk_shopuser_business: shop.users.business_id > org.businesses.id [delete: cascade, update: no action]
Table shop.partner_status_logs {
id uuid [pk, default: `gen_random_uuid()`]
shop_id uuid [not null]
from_status shop.partner_status [note: 'null = ตอนสมัครครั้งแรก']
to_status shop.partner_status [not null]
note text [note: 'เหตุผล (ไม่อนุมัติ/ขอแก้ไข/ยกเลิก)']
~audit_c
indexes {
shop_id [name: 'idx_shop_status_log']
}
Note: 'ประวัติเปลี่ยนสถานะพาร์ทเนอร์ (AD3 อนุมัติ/ปฏิเสธ/ขอแก้ไข/ยกเลิก)'
}
Ref fk_shop_statuslog_shop: shop.partner_status_logs.shop_id > shop.shops.id [delete: cascade, update: no action]
Enum shop.reason_applicable {
"RETURNED" [note: 'หัวข้อตอนตีกลับแก้ไข (= form section)']
"REJECTED" [note: 'เหตุผลตอนไม่อนุมัติ']
"INACTIVE" [note: 'เหตุผลตอนยกเลิกพาร์ทเนอร์ (ACTIVE→INACTIVE)']
}
Table shop.partner_reasons {
id smallserial [pk]
description varchar(255) [not null, note: 'หัวข้อแก้ไข / เหตุผลไม่อนุมัติ ที่ให้พนักงานเลือก']
applicable_to shop.reason_applicable [not null]
is_active bool [not null, default: true]
~audit_cu
Note: 'Lookup หัวข้อตีกลับ (RETURNED = form section → เด้งไปฟอร์มนั้น) + เหตุผลไม่อนุมัติ (REJECTED) + เหตุผลยกเลิกพาร์ทเนอร์ (INACTIVE)'
}
Table shop.partner_log_reasons {
log_id uuid [not null]
reason_id smallint [not null]
indexes {
(log_id, reason_id) [pk, name: 'idx_partner_log_reason']
}
Note: 'M:N เชื่อม status log กับหัวข้อ/เหตุผลที่เลือก (เลือกได้หลายข้อ · หมายเหตุอยู่ที่ partner_status_logs.note)'
}
Ref fk_partner_logreason_log: shop.partner_log_reasons.log_id > shop.partner_status_logs.id [delete: cascade, update: no action]
Ref fk_partner_logreason_reason: shop.partner_log_reasons.reason_id > shop.partner_reasons.id [delete: restrict, update: no action]
Table shop.consents {
id uuid [pk, default: `gen_random_uuid()`]
shop_user_id uuid [not null, note: 'ผู้ที่กดยอมรับ (ปกติ = OWNER) → shop.users · ร้านได้จาก join shop.users.shop_id']
terms_id uuid [not null, note: 'FK → org.terms_and_conditions (type=TERMS_OF_SERVICE)']
accepted_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
indexes {
(shop_user_id, terms_id) [name: 'idx_shop_consent', unique]
}
Note: 'การยอมรับเงื่อนไขบริการ ระดับบุคคล (ใครกด + เวอร์ชันไหน + เมื่อไร) — owner ยอมรับตอนสมัคร · symmetric กับ customer.consents'
}
Ref fk_shop_consent_user: shop.consents.shop_user_id > shop.users.id [delete: cascade, update: no action]
Ref fk_shop_consent_terms: shop.consents.terms_id > org.terms_and_conditions.id [delete: restrict, update: no action]
Table shop.grade_config {
id smallint [pk, default: 1, note: 'singleton — บังคับ 1 row (id=1)']
grade_a_max_pct numeric(5,2) [not null, default: 3, note: 'หนี้เสีย ≤ ค่านี้ = A (%)']
grade_b_max_pct numeric(5,2) [not null, default: 8, note: '> A และ ≤ ค่านี้ = B']
grade_c_max_pct numeric(5,2) [not null, default: 12, note: '> B และ ≤ ค่านี้ = C · เกินกว่านี้ = E']
~audit_cu
checks {
`id = 1`
`grade_a_max_pct < grade_b_max_pct`
`grade_b_max_pct < grade_c_max_pct`
}
Note: 'เกณฑ์เกรดร้าน (cutoff %) ระดับระบบ — singleton 1 row · เกรด = f( ลูกค้าหนี้เสียของร้าน / ลูกค้าทำสัญญาทั้งหมดของร้าน ×100 ) · A≤a_max, B≤b_max, C≤c_max, E>c_max'
}Mermaid ER
erDiagram businesses ||--o{ shops : "มีร้านพาร์ทเนอร์" referral_channels ||--o{ shops : "ช่องทางรู้จัก" shops ||--o{ shop_users : "เจ้าของ+พนักงาน" businesses ||--o{ shop_users : "username unique/กิจการ" shop_users ||--o{ shop_consents : "รับข้อตกลง" terms_and_conditions ||--o{ shop_consents : "เวอร์ชัน" shops ||--o{ partner_status_logs : "ประวัติสถานะ" partner_status_logs ||--o{ partner_log_reasons : "เหตุผล" partner_reasons ||--o{ partner_log_reasons : "ถูกเลือก" r2_file ||--o{ shops : "บัตร/หน้าร้าน/เอกสาร/ลายเซ็น/สัญญา" sub_districts ||--o{ shops : "ที่อยู่" banks ||--o{ shops : "บัญชีธนาคาร" shops { uuid id PK uuid business_id FK varchar shop_name varchar branch_name varchar email varchar sales_channel_link varchar phone_number enum owner_title_prefix varchar owner_national_id varchar owner_full_name date owner_date_of_birth uuid owner_id_card_file_id FK varchar owner_address_line int owner_sub_district_id FK smallint referral_channel_id FK varchar address_line int sub_district_id FK bool address_same_as_registration uuid storefront_file_id FK uuid commercial_reg_file_id FK uuid por_por_20_file_id FK varchar bank_code FK varchar bank_account_number varchar bank_account_name uuid signature_file_id FK uuid contract_file_id FK varchar line_group_id UK varchar line_group_name timestamptz line_connected_at enum status enum grade timestamptz grade_calculated_at varchar created_by varchar updated_by timestamptz created_at timestamptz updated_at } referral_channels { smallserial id PK varchar name UK bool is_active } shop_users { uuid id PK uuid shop_id FK uuid business_id FK varchar username UK varchar display_name varchar phone_number varchar password enum role bool is_deleted } partner_status_logs { uuid id PK uuid shop_id FK enum from_status enum to_status text note } partner_reasons { smallserial id PK varchar description enum applicable_to bool is_active } partner_log_reasons { uuid log_id PK smallint reason_id PK } shop_consents { uuid id PK uuid shop_user_id FK uuid terms_id FK timestamptz accepted_at } grade_config { smallint id PK numeric grade_a_max_pct numeric grade_b_max_pct numeric grade_c_max_pct }
✅ Resolved (Domain 3 — registration) — 16 มิ.ย. 2026
- ไม่แยกตารางสาขา →
shop_name+branch_name2 คอลัมน์ในshop.shops(1 row = 1 สาขา) ✓ - เจ้าของ identity → owner_* บน
shop.shops(พนักงานไม่มี identity) ✓ - referral → lookup table
shop.referral_channels✓ - shop ผูกบ้าน →
business_id✓ · เบอร์ unique ต่อบ้าน → idx (business_id, phone_number) ✓ - OTP → Redis (ephemeral) ไม่มีตารางใน DB ✓
- users →
shop.users(OWNER 1 + STAFF ≤5, รวม ≤6 บังคับ app layer) ✓ - ทุกไฟล์ (บัตร/หน้าร้าน/ใบทะเบียน/ภ.พ.20/ลายเซ็น) → file_id FK → sys.r2_file ✓
✅ Resolved (Domain 3 — AD3 พิจารณาคำขอ) — 16 มิ.ย. 2026
-
3 action (อนุมัติ/ตีกลับ/ไม่อนุมัติ) →
partner_status_logs.to_status(ACTIVE/RETURNED/REJECTED) ✓ 8b. lifecycle → enum: PENDING·RETURNED·REJECTED·CANCELED(timeout 14 วัน auto)·ACTIVE·INACTIVE · REJECTED/CANCELED ลบ row ปล่อยเบอร์ได้ · ACTIVE↔INACTIVE คืนสถานะได้ ✓ -
เหตุผล →
shop.partner_reasons(applicable_to RETURNED=หัวข้อแก้ไข / REJECTED=เหตุผล) + M:Npartner_log_reasons+ หมายเหตุที่ log.note ✓ -
เชื่อม LINE group →
shops.line_group_id(webhook จับ groupId, unique) +line_group_name(พนักงานกรอก) +line_connected_at· รหัสเชื่อม = OTP/Redis (ephemeral) ✓ -
เอกสารสัญญา gen ตอนยื่น →
shops.contract_file_id(regen ตามข้อมูล) ✓ -
badge ใหม่/แก้ไข ในรายการ → derive จาก partner_status_logs (เคยมี RETURNED = “แก้ไข”) ไม่ต้องมีคอลัมน์ ✓
-
shop.users (ฟอร์มสร้างพนักงานร้าน node 1119:256259) → username (unique ต่อกิจการ, pattern a-z A-Z 0-9 . _ - ขึ้นต้นตัวอักษร 3-32) + display_name (ซ้ำได้) + phone + password · ตัด is_active (ไม่มีปิดใช้งาน) เหลือ soft delete · เพิ่ม business_id (denormalize) ✓
-
shop.grade → enum A/B/C/E (ข้าม D) · materialized: เก็บ
grade+grade_calculated_atคำนวณ batch/event (ไม่คำนวณสด) · เกณฑ์ในshop.grade_config(A≤3%, B≤8%, C≤12%, E>12% ของลูกค้าหนี้เสีย/ลูกค้าทำสัญญาทั้งหมด) ปรับได้ · customer.grade ใช้แนวเดียวกัน (+grade_calculated_at) ✓
❓ ยังค้าง
- เกณฑ์/สูตร customer.grade (ยังไม่ระบุ — shop.grade เสร็จแล้ว)
- dependency:
sys.banks,sys.sub_districts,org.terms_and_conditions(✅ นิยามแล้ว)
Cross-domain dependencies (รอ Domain 8 / shared)
sys.provinces,sys.districts,sys.sub_districts— geo seed data (ที่อยู่)sys.terms_and_conditions— เวอร์ชันข้อตกลง (ร้านรับก่อนสมัคร)sys.r2_file— ภาพบัตร/เอกสาร (private)
Open modeling questions (ประกอบตารางหลังเก็บครบ)
- ข้อมูลเจ้าของ (form 1) อยู่ตาราง
shop.shops(owner_* fields) หรือแยกshop.owners? - เบอร์ unique “ระดับร้านสาขา” → โครงสร้าง shop ↔ branch ↔ phone เป็นยังไง (รอ form ถัดไป)