Skip to main content

ข้อมูล Journey จองนัด และเครื่องคำนวณช่วงเวลาว่าง

ภาพรวม

ครึ่งแรกของฟีเจอร์จองนัดหมาย ประกอบด้วย 3 endpoint แบบอ่านอย่างเดียวที่ป้อนข้อมูลให้หน้าจองแสดงผลได้ ได้แก่ โครง journey (ขั้นตอนและ field ที่ตั้งค่าไว้ใน CMS) พร้อมสาขา บริการ และพนักงาน, ช่วงเวลาว่างของวันใดวันหนึ่ง และรายการวันที่ยังมีคิวว่าง

หัวใจของกลุ่มนี้คือ SlotEngine ตัวสร้างช่วงเวลาจากเวลาทำการและระยะเวลาบริการ แล้วหักด้วยการจองที่มีอยู่แล้ว โดยเขียนเป็น pure logic ที่ทดสอบแยกจาก HTTP ได้

Business Flow

ทั้งสาม endpoint ไม่ต้องยืนยันตัวตน ซึ่งต่างจาก endpoint สร้างการจอง และทุกตัวเริ่มต้นด้วยการ resolve journey ก่อนเสมอ

GET /api/appointment/public/:token

  1. ค้นหา journey ที่ active ด้วย public_token — ไม่พบตอบ 404 Journey not found
  2. โหลดสาขาตาม journey.location_id — ไม่พบตอบ 404 Location not found
  3. โหลดบริการที่ active และพนักงานที่ active ของสาขานั้น
  4. ตอบกลับ {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 ขั้นตอน

  1. โหลด config ของสาขา ได้แก่ working_hours และ blocked_dates — ไม่มีแถวจะคืน array ว่าง
  2. หากวันนั้นอยู่ใน blocked_dates คืน array ว่าง
  3. หา weekday ของวันที่ระบุ โดย parse เป็น UTC midnight เพื่อให้พฤติกรรมตรงกับ new Date("YYYY-MM-DD") ของ JavaScript แล้วได้ทั้งชื่อเต็มตัวพิมพ์เล็ก เช่น monday และ key แบบสามตัวอักษร เช่น mon
  4. อ่านเวลาเปิด-ปิดจาก working_hours ซึ่งรองรับ 2 รูปแบบ ได้แก่ array ของ object ที่มี day, enabled, openTime หรือ open, closeTime หรือ close และแบบ object ที่ key เป็นวัน วันที่ไม่มี config หรือมี enabled เป็น falsy จะคืน array ว่าง
  5. โหลด duration_minutes และ max_bookings_per_slot ของบริการ — ไม่มีจะคืน array ว่าง
  6. สร้าง slot โดยก้าวจากเวลาเปิดทีละ durationMinutes และ emit เวลารูปแบบ HH:MM เฉพาะเมื่อเวลาเริ่มบวกระยะเวลายังไม่เกินเวลาปิด
  7. ตรวจ capacity ของแต่ละ slot โดยนับการจองที่ซ้อนทับกัน (เงื่อนไข slotStart < bookingEnd && slotEnd > bookingStart)
    • เต็มแล้วจะคืน {time, available:false, reason:"booked", remaining:0}
    • ยังว่างจะคืน {time, available:true, remaining} โดยไม่มี key reason

ฟังก์ชัน IsSlotAvailable ทำงานโดยสร้าง slot ของวันนั้นแล้วตรวจว่า startTime ที่ขอมามีสถานะ available หรือไม่ หากไม่พบ slot นั้นเลยจะคืน false ฟังก์ชันนี้ถูกใช้เป็นด่านตอนสร้างการจอง

หมายเหตุด้าน parity ที่ควรทราบ: SlotEngine ไม่สนใจพนักงานเลย แม้จะรับ staffId เข้ามาแต่ไม่ได้นำไปใช้ ซึ่งตรงกับ source เดิม การตรวจว่าพนักงานว่างหรือไม่เกิดขึ้นในขั้น auto-assign ของ endpoint สร้างการจอง

ไฟล์และฟังก์ชันหลัก

RouteHandler
GET /api/appointment/public/:tokeninternal/appointment/handler.go(*Handler).GetJourneyByToken
GET /api/appointment/public/:token/slots(*Handler).GetAvailableSlots
GET /api/appointment/public/:token/dates(*Handler).GetAvailableDates
  • internal/appointment/register.goRegister(r, deps)
  • internal/appointment/service.go(*ServiceLayer).GetJourneyByToken, JourneyView
  • internal/appointment/slotengine.goNewSlotEngine, GetAvailableSlots, GetAvailableDates, IsSlotAvailable, weekday, resolveWorkingHours, generateTimeSlots, timeToMinutes, splitHM, truthy, firstString และ type Slot
  • internal/appointment/repository.goFindActiveJourneyByToken, FindLocationByID, FindActiveServicesByLocation, FindActiveStaffByLocation, FindLocationSlotConfig, FindServiceDuration, FindExistingBookings
  • internal/appointment/handler.gojsNumberInt ซึ่งเลียนแบบพฤติกรรมของ Number() ใน JavaScript

จุดเชื่อมต่อกับ Service อื่น

  • ฐานข้อมูล — ตาราง appointment.journey, appointment.location (jsonb working_hours และ blocked_dates), appointment.service (duration_minutes, max_bookings_per_slot, requires_staff), appointment.staff (jsonb service_ids) และ appointment.booking
  • ฟีเจอร์ที่เกี่ยวข้อง — ถูกใช้โดยฟีเจอร์สร้างการจองนัดหมาย ซึ่งเรียก IsSlotAvailable เป็นด่านกันการจองซ้ำ
  • client-web — ตรงกับฟีเจอร์ appointment-booking