Skip to main content

แอปจองคิว/นัดหมาย

ภาพรวม

แอป appointment ให้ลูกค้าเปิดระบบจองคิวผ่าน LINE โครงสร้างข้อมูลแบ่งเป็นห้าส่วน

ส่วนความหมาย
Locationสาขาหรือสถานที่ให้บริการ
Serviceบริการที่จองได้ ผูกกับสาขา
Staffพนักงานที่ให้บริการ ผูกกับสาขา
Journeyเส้นทางหรือขั้นตอนการจองที่ผู้ใช้เห็น
Bookingรายการจองที่เกิดขึ้นจริง

ฝั่ง cms-api รับผิดชอบการตั้งค่าและการจัดการรายการจอง ส่วนการจองจริงของผู้ใช้ปลายทางทำผ่าน client-web และ client-api

ข้อสังเกตเรื่องความไม่สม่ำเสมอของ guard ซึ่งยกมาจากระบบ TypeScript เดิมโดยตรง คือ endpoint กลุ่ม booking อยู่บน group authed คู่กับ AppEnabledGuard, กลุ่ม journey, service และ staff อยู่บน group public คู่กับ JwtLoginAuth ส่วนกลุ่ม location บาง route อยู่บน group authed เฉย ๆ โดยไม่มี policy

Business Flow

ตั้งค่าครั้งแรก

  1. สร้างสาขาด้วย POST /api/apps/appointment/locations
  2. เพิ่มบริการในสาขาด้วย POST /api/apps/appointment/locations/:id/services
  3. เพิ่มพนักงานในสาขาด้วย POST /api/apps/appointment/locations/:id/staff
  4. ออกแบบขั้นตอนการจองด้วย POST /api/apps/appointment/journeys

ผู้ใช้ทำการจอง

  1. ผู้ใช้เปิด LIFF จาก LINE แล้วเลือกสาขา บริการ พนักงาน และเวลา ก่อนยืนยัน ขั้นตอนนี้ทำผ่าน client-api ไม่ใช่ endpoint ในเอกสารนี้
  2. ระบบสร้างแถวใหม่ในตาราง booking

ผู้ดูแลจัดการรายการจอง

  1. GET /api/apps/appointment/bookings พร้อม FilterBookingDto เพื่อดูรายการจอง
  2. GET /api/apps/appointment/bookings/:id ดูรายละเอียดการจอง
  3. PUT /api/apps/appointment/bookings/:id/status เปลี่ยนสถานะ เช่น ยืนยัน ยกเลิก หรือเสร็จสิ้น
    • การเปลี่ยนสถานะจะ publish ข้อความลง RabbitMQ คิว booking_event_trigger โดย payload มี byte-shape ตรงกับของเดิมทุกประการ รวมถึงลำดับ key
    • ทำให้ กฎทริกเกอร์อัตโนมัติ สั่งส่งข้อความแจ้งผู้จองต่อได้

รูปแบบ CRUD ที่ใช้ร่วมกัน

โครงสร้าง CRUD ของทุกเอนทิตีย่อยใน service_appointment.go เป็นรูปแบบเดียวกัน

  • _getCls() ดึง lineOaId, organizationId, selfId และ lang ออกจาก CLS
  • findAll และ findAllByLocation ประกอบ where และ order พร้อมกรองแถวที่ถูกลบออก แล้วคืน array ตรง ๆ โดยไม่ห่อ envelope
  • findById ค้นด้วยเงื่อนไข id ร่วมกับ lineOaId หากไม่พบจะโยน NotFoundException
  • create, update และ remove โดย remove เป็น soft delete
  • เอนทิตีกลุ่มนี้มี gorm.DeletedAt ทำให้ GORM เติมเงื่อนไข deleted_date IS NULL และแปลง Delete เป็น soft delete ให้อัตโนมัติ

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

โค้ดอยู่ที่ internal/modules/apps/ ประกอบด้วย controller.go, service.go, service_appointment.go และ guard.go

Bookings (group authed + AppEnabledGuard)

MethodRouteHandlerPolicy
GET/api/apps/appointment/bookingsct.bookingFindAllreadAll line-oa
GET/api/apps/appointment/bookings/:idct.bookingFindByIdread line-oa
PUT/api/apps/appointment/bookings/:id/statusct.bookingUpdateStatusupdate line-oa

Journeys (group public + JwtLoginAuth)

MethodRouteHandler
GET/api/apps/appointment/journeysct.journeyFindAll
GET/api/apps/appointment/journeys/:idct.journeyFindById
POST/api/apps/appointment/journeysct.journeyCreate
PUT/api/apps/appointment/journeys/:idct.journeyUpdate
DELETE/api/apps/appointment/journeys/:idct.journeyRemove

Locations

MethodRouteHandlerGroup
GET/api/apps/appointment/locationsct.locationFindAllauthed (ไม่มี policy)
POST/api/apps/appointment/locationsct.locationCreateauthed
GET/api/apps/appointment/locations/:idct.locationFindByIdauthed
PUT/api/apps/appointment/locations/:idct.locationUpdateauthed
DELETE/api/apps/appointment/locations/:idct.locationRemoveauthed

Services และ Staff (group public + JwtLoginAuth)

MethodRouteHandler
GET/api/apps/appointment/locations/:id/servicesct.serviceFindAllByLocation
POST/api/apps/appointment/locations/:id/servicesct.serviceCreate
GET/api/apps/appointment/services/:idct.serviceFindById
PUT/api/apps/appointment/services/:idct.serviceUpdate
DELETE/api/apps/appointment/services/:idct.serviceRemove
GET/api/apps/appointment/locations/:id/staffct.staffFindAllByLocation
POST/api/apps/appointment/locations/:id/staffct.staffCreate
GET/api/apps/appointment/staff/:idct.staffFindById
PUT/api/apps/appointment/staff/:idct.staffUpdate
DELETE/api/apps/appointment/staff/:idct.staffRemove

หมายเหตุเรื่อง routing: route ที่ใช้ชื่อ param เป็น :id แทน :locationId เป็นเพราะ gin บังคับให้ชื่อ param ใน subtree เดียวกันต้องตรงกัน ขณะที่ระบบ TypeScript เดิมใช้ :locationId

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