ข้อมูล Journey จองนัด และเครื่องคำนวณช่วงเวลาว่าง
ภาพรวม
ครึ่งแรกของฟีเจอร์จองนัดหมาย ประกอบด้วย 3 endpoint แบบอ่านอย่างเดียวที่ป้อนข้อมูลให้หน้าจองแสดงผลได้ ได้แก่ โครง journey (ขั้นตอนและ field ที่ตั้งค่าไว้ใน CMS) พร้อมสาขา บริการ และพนักงาน, ช่วงเวลาว่างของวันใดวันหนึ่ง และรายการวันที่ยังมีคิวว่าง
หัวใจของกลุ่มนี้คือ SlotEngine ตัวสร้างช่วงเวลาจากเวลาทำการและระยะเวลาบริการ แล้วหักด้วยการจองที่มีอยู่แล้ว โดยเขียนเป็น pure logic ที่ทดสอบแยกจาก HTTP ได้
Business Flow
ทั้งสาม endpoint ไม่ต้องยืนยันตัวตน ซึ่งต่างจาก endpoint สร้างการจอง และทุกตัวเริ่มต้นด้วยการ resolve journey ก่อนเสมอ
GET /api/appointment/public/:token
- ค้นหา journey ที่ active ด้วย
public_token— ไม่พบตอบ 404Journey not found - โหลดสาขาตาม
journey.location_id— ไม่พบตอบ 404Location not found - โหลดบริการที่ active และพนักงานที่ active ของสาขานั้น
- ตอบกลับ
{journey, location, services, staff}
GET /api/appointment/public/:token/slots
รับ query parameter serviceId และ date โดย resolve journey ก่อน (404 จะถูก propagate ออกไป) จากนั้นเรียก SlotEngine ด้วย locationId ที่ได้จาก journey ส่วน serviceId ถูก coerce แบบเดียวกับ Number() ของ JavaScript ค่าที่ parse ไม่ได้จะกลายเป็น 0 ซึ่งไม่ match แถวใด และคืน array ว่าง
GET /api/appointment/public/:token/dates
รับ query parameter serviceId และ daysAhead โดย daysAhead มีค่าเริ่มต้น 30 ระบบจะวนตรวจตั้งแต่วันนี้ (อ้างอิง UTC) ไปข้างหน้าตามจำนวนวันที่กำหนด แล้วเก็บเฉพาะวันที่มี slot ว่างอย่างน้อยหนึ่งช่อง รูปแบบวันที่ที่ส่งกลับคือ YYYY-MM-DD แบบ UTC
SlotEngine.GetAvailableSlots(locationID, serviceID, date) — 7 ขั้นตอน
- โหลด config ของสาขา ได้แก่
working_hoursและblocked_dates— ไม่มีแถวจะคืน array ว่าง - หากวันนั้นอยู่ใน
blocked_datesคืน array ว่าง - หา weekday ของวันที่ระบุ โดย parse เป็น UTC midnight เพื่อให้พฤติกรรมตรงกับ
new Date("YYYY-MM-DD")ของ JavaScript แล้วได้ทั้งชื่อเต็มตัวพิมพ์เล็ก เช่นmondayและ key แบบสามตัวอักษร เช่นmon - อ่านเวลาเปิด-ปิดจาก
working_hoursซึ่งรองรับ 2 รูปแบบ ได้แก่ array ของ object ที่มีday,enabled,openTimeหรือopen,closeTimeหรือcloseและแบบ object ที่ key เป็นวัน วันที่ไม่มี config หรือมีenabledเป็น falsy จะคืน array ว่าง - โหลด
duration_minutesและmax_bookings_per_slotของบริการ — ไม่มีจะคืน array ว่าง - สร้าง slot โดยก้าวจากเวลาเปิดทีละ
durationMinutesและ emit เวลารูปแบบHH:MMเฉพาะเมื่อเวลาเริ่มบวกระยะเวลายังไม่เกินเวลาปิด - ตรวจ capacity ของแต่ละ slot โดยนับการจองที่ซ้อนทับกัน (เงื่อนไข
slotStart < bookingEnd && slotEnd > bookingStart)- เต็มแล้วจะคืน
{time, available:false, reason:"booked", remaining:0} - ยังว่างจะคืน
{time, available:true, remaining}โดยไม่มี keyreason
- เต็มแล้วจะคืน
ฟังก์ชัน IsSlotAvailable ทำงานโดยสร้าง slot ของวันนั้นแล้วตรวจว่า startTime ที่ขอมามีสถานะ available หรือไม่ หากไม่พบ slot นั้นเลยจะคืน false ฟังก์ชันนี้ถูกใช้เป็นด่านตอนสร้างการจอง
หมายเหตุด้าน parity ที่ควรทราบ: SlotEngine ไม่สนใจพนักงานเลย แม้จะรับ staffId เข้ามาแต่ไม่ได้นำไปใช้ ซึ่งตรงกับ source เดิม การตรวจว่าพนักงานว่างหรือไม่เกิดขึ้นในขั้น auto-assign ของ endpoint สร้างการจอง
ไฟล์และฟังก์ชันหลัก
| Route | Handler |
|---|---|
GET /api/appointment/public/:token | internal/appointment/handler.go → (*Handler).GetJourneyByToken |
GET /api/appointment/public/:token/slots | (*Handler).GetAvailableSlots |
GET /api/appointment/public/:token/dates | (*Handler).GetAvailableDates |
internal/appointment/register.go—Register(r, deps)internal/appointment/service.go—(*ServiceLayer).GetJourneyByToken,JourneyViewinternal/appointment/slotengine.go—NewSlotEngine,GetAvailableSlots,GetAvailableDates,IsSlotAvailable,weekday,resolveWorkingHours,generateTimeSlots,timeToMinutes,splitHM,truthy,firstStringและ typeSlotinternal/appointment/repository.go—FindActiveJourneyByToken,FindLocationByID,FindActiveServicesByLocation,FindActiveStaffByLocation,FindLocationSlotConfig,FindServiceDuration,FindExistingBookingsinternal/appointment/handler.go—jsNumberIntซึ่งเลียนแบบพฤติกรรมของNumber()ใน JavaScript
จุดเชื่อมต่อกับ Service อื่น
- ฐานข้อมูล — ตาราง
appointment.journey,appointment.location(jsonbworking_hoursและblocked_dates),appointment.service(duration_minutes,max_bookings_per_slot,requires_staff),appointment.staff(jsonbservice_ids) และappointment.booking - ฟีเจอร์ที่เกี่ยวข้อง — ถูกใช้โดยฟีเจอร์สร้างการจองนัดหมาย ซึ่งเรียก
IsSlotAvailableเป็นด่านกันการจองซ้ำ - client-web — ตรงกับฟีเจอร์
appointment-booking