รายละเอียดคอลัมน์ของ schema appointment ทั้งหมด (location/service/staff/journey/booking)
ภาพรวม
Schema appointment มี 5 ตาราง เรียงตามลำดับการอ้างอิงคือ location (สาขา) → service
(บริการที่จองได้) / staff (ผู้ให้บริการ) / journey (การตั้งค่า flow การจองพร้อม token
สำหรับเปิด LIFF) แล้วปลายทางคือ booking (การจองจริง) schema นี้ apply แยกด้วย psql
ไม่ผ่าน Prisma (migration 001-004)
ตาราง appointment.location
| Column | Type | Nullable | Default | คำอธิบาย |
|---|
id | SERIAL | NO | auto increment | Primary key |
line_oa_id | INTEGER | NO | - | LINE OA ที่สาขานี้สังกัด |
organization_id | INTEGER | NO | - | องค์กรเจ้าของข้อมูล |
name | VARCHAR(255) | NO | - | ชื่อสาขา |
description | TEXT | YES | - | คำอธิบายสาขา |
address | TEXT | YES | - | ที่อยู่สาขา |
phone | VARCHAR(50) | YES | - | เบอร์โทรติดต่อ |
working_hours | JSONB | NO | '{}' | เวลาทำการรายวัน ใช้คำนวณ slot ที่จองได้ |
blocked_dates | JSONB | YES | '[]' | วันที่ปิดทำการ/ไม่รับจอง |
timezone | VARCHAR(50) | NO | 'Asia/Bangkok' | ไทม์โซนของสาขา |
status | VARCHAR(20) | NO | 'active' | สถานะการใช้งาน |
sort_order | INTEGER | NO | 0 | ลำดับการแสดงผล |
created_by | INTEGER | YES | - | ผู้สร้างเรกคอร์ด |
updated_by | INTEGER | YES | - | ผู้แก้ไขล่าสุด |
created_date | TIMESTAMPTZ(3) | NO | CURRENT_TIMESTAMP | วันเวลาที่สร้าง |
updated_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่แก้ไขล่าสุด |
deleted_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่ soft delete |
ตาราง appointment.service
| Column | Type | Nullable | Default | คำอธิบาย |
|---|
id | SERIAL | NO | auto increment | Primary key |
location_id | INTEGER | NO | - | FK ไปยัง appointment.location(id) ลบแบบ CASCADE |
line_oa_id | INTEGER | NO | - | LINE OA ที่บริการนี้สังกัด |
organization_id | INTEGER | NO | - | องค์กรเจ้าของข้อมูล |
name | VARCHAR(255) | NO | - | ชื่อบริการ |
description | TEXT | YES | - | คำอธิบายบริการ |
duration_minutes | INTEGER | NO | 30 | ระยะเวลาให้บริการ (นาที) ใช้คำนวณเวลาสิ้นสุด |
max_bookings_per_slot | INTEGER | NO | 1 | จำนวนการจองสูงสุดต่อหนึ่งช่วงเวลา |
requires_staff | BOOLEAN | NO | false | บริการนี้ต้องระบุผู้ให้บริการหรือไม่ |
price | DECIMAL(10,2) | YES | - | ราคาบริการ |
currency | VARCHAR(10) | YES | 'THB' | สกุลเงินของราคา |
status | VARCHAR(20) | NO | 'active' | สถานะการใช้งาน |
sort_order | INTEGER | NO | 0 | ลำดับการแสดงผล |
created_by | INTEGER | YES | - | ผู้สร้างเรกคอร์ด |
created_date | TIMESTAMPTZ(3) | NO | CURRENT_TIMESTAMP | วันเวลาที่สร้าง |
updated_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่แก้ไขล่าสุด |
deleted_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่ soft delete |
ตาราง appointment.staff
| Column | Type | Nullable | Default | คำอธิบาย |
|---|
id | SERIAL | NO | auto increment | Primary key |
location_id | INTEGER | NO | - | FK ไปยัง appointment.location(id) ลบแบบ CASCADE |
line_oa_id | INTEGER | NO | - | LINE OA ที่ผู้ให้บริการนี้สังกัด |
organization_id | INTEGER | NO | - | องค์กรเจ้าของข้อมูล |
name | VARCHAR(255) | NO | - | ชื่อผู้ให้บริการ |
title | VARCHAR(255) | YES | - | ตำแหน่ง/คำนำหน้าที่แสดงคู่กับชื่อ |
avatar_url | VARCHAR(500) | YES | - | URL รูปประจำตัว |
service_ids | JSONB | YES | '[]' | รายการ id ของบริการที่ผู้ให้บริการรายนี้รับได้ |
status | VARCHAR(20) | NO | 'active' | สถานะการใช้งาน |
sort_order | INTEGER | NO | 0 | ลำดับการแสดงผล |
created_by | INTEGER | YES | - | ผู้สร้างเรกคอร์ด |
created_date | TIMESTAMPTZ(3) | NO | CURRENT_TIMESTAMP | วันเวลาที่สร้าง |
updated_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่แก้ไขล่าสุด |
deleted_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่ soft delete |
ตาราง appointment.journey
| Column | Type | Nullable | Default | คำอธิบาย |
|---|
id | SERIAL | NO | auto increment | Primary key |
location_id | INTEGER | NO | - | FK ไปยัง appointment.location(id) ลบแบบ CASCADE |
line_oa_id | INTEGER | NO | - | LINE OA ที่ flow นี้สังกัด |
organization_id | INTEGER | NO | - | องค์กรเจ้าของข้อมูล |
name | VARCHAR(255) | NO | - | ชื่อ flow การจอง |
description | TEXT | YES | - | คำอธิบาย flow |
token | VARCHAR(64) | NO | - | โทเคนสาธารณะที่ใช้เปิดหน้าจองผ่าน LIFF (unique) |
auto_confirm | BOOLEAN | NO | true | ยืนยันการจองอัตโนมัติหรือรอผู้ดูแลอนุมัติ |
steps | JSONB | YES | - | นิยามขั้นตอน/ฟอร์มระหว่างการจอง |
status | VARCHAR(20) | NO | 'active' | สถานะการใช้งาน |
created_by | INTEGER | YES | - | ผู้สร้างเรกคอร์ด |
created_date | TIMESTAMPTZ(3) | NO | CURRENT_TIMESTAMP | วันเวลาที่สร้าง |
updated_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่แก้ไขล่าสุด |
deleted_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่ soft delete |
reminder_hours_before | INTEGER | YES | 24 | ส่งการแจ้งเตือนล่วงหน้ากี่ชั่วโมงก่อนถึงนัด (migration 003) |
no_show_grace_minutes | INTEGER | YES | 30 | ผ่อนผันกี่นาทีก่อนถือว่าลูกค้าไม่มาตามนัด (migration 003) |
ตาราง appointment.booking
| Column | Type | Nullable | Default | คำอธิบาย |
|---|
id | SERIAL | NO | auto increment | Primary key |
journey_id | INTEGER | NO | - | FK ไปยัง appointment.journey(id) — flow ที่ใช้จอง |
location_id | INTEGER | NO | - | FK ไปยัง appointment.location(id) — สาขาที่จอง |
service_id | INTEGER | NO | - | FK ไปยัง appointment.service(id) — บริการที่จอง |
staff_id | INTEGER | YES | - | FK ไปยัง appointment.staff(id) — ผู้ให้บริการ (ถ้าระบุ) |
user_id | VARCHAR(255) | NO | - | LINE user id ของผู้จอง |
line_oa_id | INTEGER | NO | - | LINE OA ที่การจองนี้สังกัด |
organization_id | INTEGER | NO | - | องค์กรเจ้าของข้อมูล |
booking_date | DATE | NO | - | วันที่นัดหมาย |
start_time | TIME | NO | - | เวลาเริ่มนัด |
end_time | TIME | NO | - | เวลาสิ้นสุดนัด |
status | VARCHAR(20) | NO | 'pending' | สถานะการจอง |
notes | TEXT | YES | - | หมายเหตุจากผู้จองหรือผู้ดูแล |
form_data | JSONB | YES | - | คำตอบจากฟอร์มที่กำหนดไว้ใน journey.steps |
cancel_reason | TEXT | YES | - | เหตุผลการยกเลิก |
created_date | TIMESTAMPTZ(3) | NO | CURRENT_TIMESTAMP | วันเวลาที่สร้าง |
updated_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่แก้ไขล่าสุด |
confirmed_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่ยืนยันการจอง |
cancelled_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่ยกเลิกการจอง |
completed_date | TIMESTAMPTZ(3) | YES | - | วันเวลาที่ให้บริการเสร็จสิ้น |
reminder_sent | BOOLEAN | YES | false | ส่งการแจ้งเตือนล่วงหน้าไปแล้วหรือยัง (migration 003) |
หมายเหตุ
- Foreign key:
service.location_id, staff.location_id, journey.location_id ทั้งสามตัว
ชี้ไปยัง appointment.location(id) แบบ ON DELETE CASCADE — ลบสาขาแล้วข้อมูลลูกหายตามทั้งหมด
ส่วน FK ทั้ง 4 ตัวใน booking (journey_id, location_id, service_id, staff_id)
ไม่ตั้ง cascade เพื่อไม่ให้ลบ master data ทิ้งการจองที่มีอยู่จริง
- Unique:
appointment.journey.token เป็น unique ทั้งใน column constraint และ index
idx_appt_journey_token เพราะเป็นกุญแจสาธารณะที่ใช้เปิดหน้าจอง
- Partial index: index ของ
location, service, staff, journey ที่กรองสถานะทั้งหมด
มีเงื่อนไข WHERE deleted_date IS NULL — ตรงกับ query ที่มองเฉพาะแถวที่ยังไม่ถูก soft delete
ส่วน idx_appt_booking_reminder มีเงื่อนไข WHERE reminder_sent = false เพื่อให้ worker
ที่ไล่ส่งการแจ้งเตือนสแกนเฉพาะแถวที่ยังไม่ได้ส่ง
- Trigger (migration 004):
trg_booking_event_insert และ trg_booking_event_update
บน appointment.booking เรียกฟังก์ชัน appointment.notify_booking_event() ซึ่งส่ง
pg_notify ไปยัง channel booking_event (ชนิด booking_created เมื่อ INSERT และ
booking_status_changed เมื่อ status เปลี่ยน) โดย pg-listener worker เป็นผู้รับ
พร้อมกันนั้นจะพยายาม INSERT ลงตาราง event_outbox เพื่อความน่าเชื่อถือ และข้ามไปเงียบๆ
ถ้ายังไม่มีตารางนั้น
booking.user_id เก็บ LINE user id เป็นสตริง ไม่ใช่ FK ไปยังตาราง line_user ใน schema public