สร้างการจองนัดหมาย
ภาพรวม
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?}
- ยืนยันตัวตน ตามลำดับเดียวกับ 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
- หากมี header
- Body ที่ว่างจะทำให้ทุก field เป็น zero value โดยไม่ error ส่วน body ที่เป็น JSON พังรูปจะตอบ 400
Invalid request bodyเนื่องจาก source เดิมไม่มี DTO validation บน route นี้ - Resolve journey จาก
:token— ไม่พบตอบ 404Journey not found - โหลดบริการตาม
serviceId— ไม่พบตอบ 404Service not found - Auto-assign พนักงาน เมื่อผู้จองไม่ได้เลือกมาและบริการนั้นมี
requires_staffเป็นจริง- ดึงพนักงานที่มีสิทธิ์ในสาขานั้น แล้วกรองเฉพาะคนที่ให้บริการนี้ได้ โดยตรวจ jsonb
service_idsแบบรับได้ทั้งค่าตัวเลขและค่าสตริง - เลือกคนแรกที่ไม่มีคิวซ้อนทับในวันนั้น ตามเงื่อนไข
slotStart < bookingEnd && slotEnd > bookingStart - หากไม่มีใครว่างเลย
staffIdจะคงเป็น null แต่การจองยังคงเกิดขึ้น
- ดึงพนักงานที่มีสิทธิ์ในสาขานั้น แล้วกรองเฉพาะคนที่ให้บริการนี้ได้ โดยตรวจ jsonb
- ตรวจสอบว่า slot ยังว่างอีกครั้ง ด้วย
SlotEngine.IsSlotAvailable— หากไม่ว่างตอบ 409Selected time slot is no longer availableขั้นนี้เป็นด่านกันกรณีมีสองคนกดจองพร้อมกัน - คำนวณ
endTimeจากstartTimeบวกservice.duration_minutesแล้ว format เป็นรูปแบบสองหลักต่อสองหลัก - กำหนดสถานะเริ่มต้น — หาก
journey.auto_confirmเป็นจริงจะได้สถานะconfirmedพร้อมตั้งค่า confirmed เป็นจริง มิฉะนั้นได้สถานะpending - Insert แถวลง
appointment.bookingพร้อมข้อมูลjourneyId,locationId,serviceId,staffId,userId,lineOaId,organizationId,bookingDate,startTime,endTime,status,notesและformData - 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}
- คิว
- ตอบกลับ entity
Bookingที่บันทึกแล้วด้วยสถานะ 201
ไฟล์และฟังก์ชันหลัก
| รายการ | ค่า |
|---|---|
| Route | POST /api/appointment/public/:token/book (rate limit 5/60s) |
| Register | internal/appointment/register.go → Register(r, deps) ผูก middleware.RouteRateLimit(rdb, 5, 60) และหากไม่มี Redis จะ mount route โดยไม่มี limiter |
| Handler | internal/appointment/handler.go → (*Handler).CreateBooking, resolveUserID, bindJSON |
| Service | internal/appointment/service.go → (*ServiceLayer).CreateBooking, publish, staffHasService |
| Repository | internal/appointment/repository.go → FindServiceByID, FindEligibleStaffByLocation, FindStaffBookingsForDate, InsertBooking |
| Entity | internal/appointment/entity.go → Booking, 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