Skip to main content

สร้างการจองนัดหมาย

ภาพรวม

Endpoint เดียวในโดเมนจองนัดที่เขียนข้อมูล หน้าที่คือรับข้อมูลการจองจากหน้า LIFF ยืนยันตัวตนผู้จองด้วย LIFF token เลือกพนักงานให้อัตโนมัติเมื่อจำเป็น ตรวจสอบว่าช่วงเวลายังว่างอยู่จริง คำนวณเวลาสิ้นสุด บันทึกลงตาราง appointment.booking แล้วส่ง event 2 ตัวเข้า RabbitMQ สำหรับการแจ้งเตือนและการ trigger workflow

Endpoint นี้มี rate limit 5 ครั้งต่อ 60 วินาที ต่อหนึ่ง IP ตามการตั้งค่า throttle เดิม

Business Flow

POST /api/appointment/public/:token/book (rate limit 5/60s)

Body ที่รับ: {serviceId, staffId?, bookingDate, startTime, notes?, formData?}

  1. ยืนยันตัวตน ตามลำดับเดียวกับ controller เดิม
    • หากมี header x-liff-token จะเรียก verifyAccessToken ด้วย access token ที่มี หากล้มเหลวจะลองใหม่ด้วย LIFF token และหากล้มเหลวอีกจะคืน error จาก LINE ด้วยสถานะ 401
    • หากไม่มี แต่มี header x-liff-access-token จะเรียก verifyAccessToken ด้วยค่านั้น
    • หากไม่มีทั้งคู่ ตอบ 401 No authentication token provided
    • หมายเหตุ: เส้นทางนี้ใช้ verifyAccessToken เพียงอย่างเดียวและ ไม่มีการผูก channel ซึ่งต่างจากโดเมน bulletin และ loyalty เป็นการรักษา parity กับ source เดิม
    • userId ที่ได้คือค่า sub ของ profile
  2. Body ที่ว่างจะทำให้ทุก field เป็น zero value โดยไม่ error ส่วน body ที่เป็น JSON พังรูปจะตอบ 400 Invalid request body เนื่องจาก source เดิมไม่มี DTO validation บน route นี้
  3. Resolve journey จาก :token — ไม่พบตอบ 404 Journey not found
  4. โหลดบริการตาม serviceId — ไม่พบตอบ 404 Service not found
  5. Auto-assign พนักงาน เมื่อผู้จองไม่ได้เลือกมาและบริการนั้นมี requires_staff เป็นจริง
    • ดึงพนักงานที่มีสิทธิ์ในสาขานั้น แล้วกรองเฉพาะคนที่ให้บริการนี้ได้ โดยตรวจ jsonb service_ids แบบรับได้ทั้งค่าตัวเลขและค่าสตริง
    • เลือกคนแรกที่ไม่มีคิวซ้อนทับในวันนั้น ตามเงื่อนไข slotStart < bookingEnd && slotEnd > bookingStart
    • หากไม่มีใครว่างเลย staffId จะคงเป็น null แต่การจองยังคงเกิดขึ้น
  6. ตรวจสอบว่า slot ยังว่างอีกครั้ง ด้วย SlotEngine.IsSlotAvailable — หากไม่ว่างตอบ 409 Selected time slot is no longer available ขั้นนี้เป็นด่านกันกรณีมีสองคนกดจองพร้อมกัน
  7. คำนวณ endTime จาก startTime บวก service.duration_minutes แล้ว format เป็นรูปแบบสองหลักต่อสองหลัก
  8. กำหนดสถานะเริ่มต้น — หาก journey.auto_confirm เป็นจริงจะได้สถานะ confirmed พร้อมตั้งค่า confirmed เป็นจริง มิฉะนั้นได้สถานะ pending
  9. Insert แถวลง appointment.booking พร้อมข้อมูล journeyId, locationId, serviceId, staffId, userId, lineOaId, organizationId, bookingDate, startTime, endTime, status, notes และ formData
  10. Publish 2 message แบบ best-effort โดย error ถูกกลืนทั้งหมด การที่ RabbitMQ ล่มจึงไม่ทำให้การจองล้มเหลว
    • คิว booking_notification — payload {bookingId, journeyId, locationId, serviceId, staffId, userId, lineOaId, organizationId, status}
    • คิว booking_event_trigger — payload {type:"booking_created", bookingId, serviceId, serviceName, staffId, bookingDate, startTime, endTime, locationId, journeyId, status, userId, lineOaId, organizationId}
  11. ตอบกลับ entity Booking ที่บันทึกแล้วด้วยสถานะ 201

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

รายการค่า
RoutePOST /api/appointment/public/:token/book (rate limit 5/60s)
Registerinternal/appointment/register.goRegister(r, deps) ผูก middleware.RouteRateLimit(rdb, 5, 60) และหากไม่มี Redis จะ mount route โดยไม่มี limiter
Handlerinternal/appointment/handler.go(*Handler).CreateBooking, resolveUserID, bindJSON
Serviceinternal/appointment/service.go(*ServiceLayer).CreateBooking, publish, staffHasService
Repositoryinternal/appointment/repository.goFindServiceByID, FindEligibleStaffByLocation, FindStaffBookingsForDate, InsertBooking
Entityinternal/appointment/entity.goBooking, BookingBody, InsertBookingInput, intSlice, jsonMap

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

  • ฐานข้อมูล — ตาราง appointment.journey, appointment.service, appointment.staff และ appointment.booking
  • SlotEngine — เรียก IsSlotAvailable จากฟีเจอร์ journey และช่วงเวลาว่าง
  • RabbitMQ — คิว booking_notification และ booking_event_trigger ซึ่งชื่อคิวมาจาก deps.Config.RabbitMQ.QueueBookingNotification และ QueueBookingEventTrigger โดยผู้บริโภคคือ line-management-worker-go
  • LINE Platform — เรียกผ่าน internal/linehttp ด้วยฟังก์ชัน VerifyAccessToken
  • client-web — ตรงกับฟีเจอร์ appointment-booking