nawin-resort-khaokho

HANDOFF — Nawin Resort Khaokho

อัปเดตล่าสุด: 2026-08-08

สถานะปัจจุบัน (v1 — เสร็จแล้ว)

สิ่งที่ทำเสร็จแล้วทั้งหมด (ไม่ต้องทำอะไรเพิ่ม)

สิ่งที่บอสตองต้องทำเอง — ตั้งค่าให้ “ลืมรหัสผ่าน?” ใช้งานได้

หน้า login เพิ่มปุ่ม “ลืมรหัสผ่าน?” แล้ว (ให้พนักงานกู้รหัสผ่านเองได้ ไม่ต้องให้ใครแตะรหัสผ่านคนอื่น) แต่ Supabase บล็อกไม่ให้ redirect ไปโดเมนที่ไม่ได้อยู่ใน allow-list ต้องตั้งค่า 1 ครั้ง:

  1. เข้า Supabase Dashboard → Authentication → URL Configuration
  2. Site URL: ใส่ https://khaokho.nawingroup.com
  3. Redirect URLs: เพิ่ม https://khaokho.nawingroup.com/dashboard/reset-password.html (หรือใส่ https://khaokho.nawingroup.com/** แบบ wildcard ให้ครอบคลุมทุกหน้าในโดเมนนี้)
  4. Save

แก้ปัญหาบีม login ไม่ได้เฉพาะหน้าตอนนี้เลย (เร็วกว่ารอ 4 ข้อบนเสร็จ): เข้า Supabase Dashboard → Authentication → Users → หา awatsada.beam@gmail.com → กด “…” → Send password recovery (ส่งอีเมลให้บีมตั้งรหัสใหม่เอง) — แต่ก็ยังต้องตั้งค่า URL Configuration ข้างบนก่อน ไม่งั้นลิงก์ในอีเมลจะ redirect ผิดที่

สิ่งที่บอสตองต้องทำเอง (บล็อกไม่ได้ เพราะต้องเป็นเจ้าของบัญชี)

ผูกบัญชี LINE ของพนักงานแต่ละคน (สำหรับ staff-app)

staff-app ไม่มีหน้า login แยก — ระบุตัวพนักงานจาก LINE account ที่ผูกไว้ในตาราง profiles โดยตรง ขั้นตอน:

  1. ให้พนักงานเปิด https://khaokho.nawingroup.com/staff-app/ ผ่าน LINE (ใน LINE app จริง ไม่ใช่เบราว์เซอร์ปกติ — ต้องเปิดจากลิงก์ที่ส่งผ่านแชท LINE หรือปุ่มเมนู richmenu ของ OA)
  2. รอบแรกจะขึ้นข้อความ “บัญชี LINE นี้ยังไม่ได้ผูกกับพนักงาน” พร้อม LINE User ID ให้คัดลอก
  3. ให้พนักงานส่ง LINE User ID นั้นมาให้บอสตอง (หรือแคปหน้าจอ)
  4. เข้า Dashboard → ยูนิต & รายงาน → เลื่อนลงหา “พนักงาน (ผูกบัญชี LINE)” → วาง LINE User ID ลงช่องของพนักงานคนนั้น → กด Enter หรือคลิกออกจากช่อง (บันทึกอัตโนมัติ)
  5. ให้พนักงานเปิด staff-app ใหม่อีกครั้ง — คราวนี้จะเข้าหน้ากรอกจองได้เลย ไม่ต้อง login ซ้ำอีก

ทุกการจองที่พนักงานคีย์ผ่าน staff-app จะถูกบันทึก “จองโดย” อัตโนมัติ (เห็นในหน้า “การจอง” + นับรวมในตาราง “สรุปตามพนักงาน (สำหรับคิดคอมมิชชั่น)” ที่หน้า “ยูนิต & รายงาน” เหมือนการจองที่คีย์ผ่าน Dashboard ทุกประการ)

หมายเหตุเรื่อง internal_functions_secret: สุ่มค่าไว้แล้วตอน setup (SECURITY DEFINER trigger ↔ line-notify function ใช้ยืนยันกัน) ไม่ต้องแก้อะไรเพิ่ม เว้นแต่ต้องการหมุนค่าใหม่ (rotate) — ทำผ่าน vault.update_secret เหมือน secret อื่นๆ แล้วอย่าลืมว่าไม่มีที่อื่นอ้างอิงค่านี้นอกจาก DB trigger เอง

ตั้งค่า PromptPay สำหรับรับชำระเงิน — กรอกครบแล้ว เหลือแค่ทดสอบสแกนจริง

ตั้งค่าไว้ใน resort_settings แล้ว: PromptPay ID 088985000565236 (เลข 15 หลักจาก CLICX, รูปแบบ e-Wallet/merchant — ระบบรองรับแล้ว), ชื่อบัญชี “นายนาวิน อินทเสือ”, และ note “โอนแล้วแนบสลิปในแอปได้เลย” — ลูกค้าจองใหม่เห็น QR จริง+note ทันที ไม่ต้องทำอะไรเพิ่มในหน้า Dashboard

ค้างอยู่ — บอสตองต้องทดสอบสแกน QR ด้วยแอปธนาคารจริงก่อนเปิดใช้งานจริง (Claude ไม่มีมือถือ/แอปธนาคารให้ทดสอบเอง) — payload ตรวจสอบโครงสร้าง TLV/CRC ถูกต้องตาม spec แล้ว แต่ควรสแกนจริงยืนยันว่าขึ้นชื่อ “นายนาวิน อินทเสือ” ถูกต้องก่อนรับเงินจริง

ตรวจสอบสลิปที่ลูกค้าอัปโหลด

เข้า Dashboard → เมนู “ชำระเงิน” — จะเห็นรายการสลิปที่รอตรวจสอบทั้งหมด (พนักงานทุกคนเห็นได้ ไม่ใช่แค่ owner) คลิกรูปเพื่อดูขยาย กด “ยืนยันการชำระเงิน” จะเปลี่ยนสถานะการจองเป็น “ยืนยันแล้ว” ทันทีและส่ง LINE แจ้งเตือนลูกค้าอัตโนมัติ หรือกด “ปฏิเสธสลิป” ถ้าสลิปไม่ถูกต้อง (ลูกค้าจะอัปโหลดใหม่ได้)

อัปโหลดรูปภาพยูนิต

เข้า Dashboard → ยูนิต & รายงาน → เลื่อนลงหัวข้อ “รูปภาพยูนิต” อัปโหลดรูปจริงของแต่ละห้อง/เต็นท์ได้เลย (ไม่จำกัดจำนวน) รูปจะโชว์ให้ลูกค้าดูตอนเลือกยูนิตในหน้าจองทันที — ตอนนี้ยังไม่มีรูปเลย แนะนำอัปโหลดก่อนเริ่มรับจองจริงจัง

ระบบยกเลิกอัตโนมัติ 24 ชม. (ทำงานอยู่แล้ว ไม่ต้องทำอะไร)

มี cron job รันทุก 15 นาที ยกเลิกการจองที่ยังเป็น “รอยืนยัน” (ลูกค้าจองเองผ่านแอป ยังไม่จ่ายเงิน) เกิน 24 ชม. อัตโนมัติ — การจองที่พนักงานคีย์ผ่าน Dashboard/staff-app เป็น “ยืนยันแล้ว” ทันทีอยู่แล้วไม่โดนยกเลิก

ป้องกันมิจฉาชีพ — สิ่งที่ควรทำเพิ่ม

ระบบตั้งค่าไว้ให้แล้วว่า “จะไม่มีการขอให้โอนเงินผ่านแชท” (ขึ้นแบนเนอร์เตือนในแอปทุกครั้งก่อนโชว์ QR) แต่แนะนำเพิ่ม:

Rich Menu + ข้อความต้อนรับ LINE OA

ข้อความต้อนรับ — ทำงานแล้วทันที ไม่ต้องทำอะไรเพิ่ม line-webhook ตอบข้อความต้อนรับอัตโนมัติทันทีที่มีคนกดเพิ่มเพื่อน OA (2 ข้อความ: แนะนำรีสอร์ท+ลิงก์จอง และเตือนเรื่องความปลอดภัย “ไม่ขอโอนเงินผ่านแชท”)

Rich Menu — ใช้งานจริงแล้ว ตั้งเป็นเมนูหลักแล้ว (แก้บั๊ก login 2026-08-17) ริชเมนู 6 ปุ่ม (จองห้องพัก, การจองของฉัน, รูปห้องพัก, แผนที่/เส้นทาง, โทรติดต่อเรา, ติดต่อแอดมิน) — richMenuId ปัจจุบัน: richmenu-857b66c3e1c33d6e5084e11343513dcc (ชื่อ “Nawin Resort Khaokho - main v4 (fixed liff links)”) ตั้งเป็นเมนูหลักแล้ว ลูกค้าเห็นทันทีที่เปิดแชท OA

บั๊กที่แก้ไป (2026-08-17): ปุ่ม “จองห้องพัก”/”รูปห้องพัก”/”การจองของฉัน” เดิมชี้ไปโดเมนเราตรงๆ (https://khaokho.nawingroup.com/guest-app/...) แทนที่จะเป็นลิงก์ https://liff.line.me/<LIFF_ID>/... — ผลคือ LINE เปิดเป็นเว็บธรรมดาแทนที่จะเป็น LIFF context จริง ทำให้ทุกครั้งต้อง login ผ่านหน้าเว็บ access.line.me (ต้องมีอีเมล/รหัสผ่าน LINE ซึ่งผู้ใช้ส่วนใหญ่ไม่เคยตั้งไว้) → ลูกค้าเจอ “เกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ” กดจองไม่ได้เลย แม้กดจากในแอป LINE เอง (ไม่ใช่แค่เปิดนอกแอป) เพราะปุ่มลิงก์ตรงเป็นตัวปัญหาอยู่แล้ว บอสตองที่ทดสอบตอนแรกผ่านเพราะบัญชีมี session/อีเมลตั้งไว้อยู่แล้วเลยไม่เจอ แก้โดยสร้างริชเมนูใหม่ เปลี่ยน 3 ปุ่มนี้เป็น https://liff.line.me/2011027975-pX2LznMW/booking.html และ .../my-bookings.html (LIFF รองรับต่อ path หลัง LIFF ID แล้วส่งต่อไปยัง endpoint URL ที่ลงทะเบียนไว้ได้เลย ไม่ต้องแก้โค้ด) — ปุ่มอื่น (แผนที่/โทร/ติดต่อแอดมิน) ไม่เปลี่ยน — ปุ่ม “แผนที่” ยังชี้ไปพิกัดหมุด https://www.google.com/maps?q=16.628785,101.002473 (หมุดลอย ไม่ผ่าน verify เพราะยังไม่ได้ไปเขาค้อยืนยันตัวธุรกิจ)

บั๊กที่สอง เจอหลังแก้อันแรก (2026-08-17, เจอจากลูกค้าจริงที่จองไม่ได้): แม้ลิงก์เป็น liff.line.me ถูกต้องแล้ว ลูกค้ายังเจอหน้า error “400 Bad Request — This channel is now developing status. User need to have developer role.” สาเหตุคือ LINE Login channel “Nawin Resort Khaokho” (channel ID 2011027975, คนละตัวกับ Messaging API channel) ยังอยู่สถานะ Developing มาตั้งแต่สร้าง — ตอนที่บอสตองทดสอบเองผ่านเพราะบัญชี Admin ใช้ได้เสมอไม่ว่าสถานะไหน แต่ลูกค้าทั่วไปใช้ไม่ได้เลยจนกว่าจะ Publish

แก้โดย:

  1. เพิ่มหน้านโยบายความเป็นส่วนตัว guest-app/privacy.html (LINE บังคับต้องมี Privacy policy URL ก่อน Publish ได้) แล้วใส่ URL https://khaokho.nawingroup.com/guest-app/privacy.html ในช่อง Privacy policy URL ของ channel
  2. อัปโหลด Channel icon (ช่องนี้ก็บังคับก่อน Publish ได้เหมือนกัน แต่เดิมว่างอยู่) — ใช้ไอคอนตัวอักษร “NRK” ธีมเขียวไปก่อน เปลี่ยนเป็นโลโก้จริงทีหลังได้ (Basic settings → Channel icon → Edit)
  3. คลิกที่ badge สถานะ “Developing” ที่หัวหน้าเพจ channel → เด้ง popup “Publish this channel?” → กด Publish (เป็นการกระทำที่ย้อนกลับไม่ได้ — ถ้าจะกลับไป Developing ต้องลบ channel แล้วสร้างใหม่) → สถานะเปลี่ยนเป็น Published แล้ว (2026-08-17)

บั๊กที่สาม เจอตอนบอสตองทดสอบจริงหลังแก้ 2 บั๊กแรก (2026-08-17): เจอหน้า 404 ของ GitHub Pages แทน — สาเหตุคือ LIFF Endpoint URL ที่ลงทะเบียนไว้เป็นไฟล์เจาะจง .../guest-app/index.html ไม่ใช่โฟลเดอร์ พอลิงก์ริชเมนูใช้รูปแบบ https://liff.line.me/<LIFF_ID>/booking.html (ต่อ path หลัง LIFF ID) LINE เอา path ไปต่อท้าย endpoint URL ตรงๆ กลายเป็น .../index.html/booking.html ซึ่งไม่มีไฟล์นี้จริง เลย 404

แก้โดย:

  1. แก้ guest-app/index.html ให้รับ query param ?next=my-bookings.html แล้ว redirect ไปหน้าที่ถูกต้องหลัง login สำเร็จ (ค่าเริ่มต้นคือ booking.html)
  2. เปลี่ยนลิงก์ริชเมนูกลับไปใช้ LIFF root เฉยๆ ไม่ต่อ path: “จองห้องพัก”/”รูปห้องพัก” → https://liff.line.me/2011027975-pX2LznMW, “การจองของฉัน” → https://liff.line.me/2011027975-pX2LznMW?next=my-bookings.html (query string ต่อท้ายแบบนี้ LINE forward ให้ตรงๆ ไม่มีปัญหาเหมือนต่อ path) — richMenuId ปัจจุบัน richmenu-857b66c3e1c33d6e5084e11343513dcc

บทเรียน: การต่อ path หลัง LIFF ID (liff.line.me/<id>/path) ใช้ได้เฉพาะตอน Endpoint URL ที่ลงทะเบียนไว้เป็น “โฟลเดอร์” (ลงท้ายด้วย /) เท่านั้น ถ้าลงทะเบียนเป็นไฟล์เจาะจงแบบนี้ ต้องใช้ query string (?param=value) แทนเสมอ — เช็ค Endpoint URL จริงที่ LINE Developers Console → channel → LIFF → เลือกแอป → ดูช่อง “Endpoint URL” ก่อนเลือกวิธีต่อ path ทุกครั้ง

ยังไม่ทดสอบจริงหลังแก้ครบ 3 บั๊ก — บอสตองควรลองกดปุ่ม “จองห้องพัก” จากแอป LINE จริงอีกครั้งเพื่อยืนยันว่า login+redirect ผ่านแบบไม่มี error แล้ว ก่อนถือว่าปิดเคสนี้สมบูรณ์

ค้างอยู่ — ชื่อสถานที่บน Google Maps (submission แรก) ยังรอ Google รีวิว ส่งคำแนะนำเพิ่มสถานที่ “Nawin Resort Khaokho” (พิกัดเดียวกับปุ่มแผนที่ด้านบน) เข้า Google Maps แบบ “Add a missing place” แล้ว แต่ยังไม่ถูกอนุมัติ/ยังค้นหาไม่เจอในระบบ (รอ Google รีวิว จะมีอีเมลแจ้งบอสตองเมื่อรีวิวเสร็จ) — พอสถานที่นี้ขึ้นบน Maps แล้ว (ค้นหาเจอ) ต้องส่ง “แนะนำการแก้ไข” เปลี่ยนชื่อเป็น “Nawin Resort เขาค้อ” (ดูหัวข้อด้านล่าง จริงๆ ได้ Google Business Profile แยกไปแล้วซึ่งใช้ชื่อนี้ถูกต้อง — submission นี้อาจไม่จำเป็นแล้วถ้า Business Profile ด้านล่างขึ้นก่อน)

ค้างอยู่ — Google Business Profile “nawin resort เขาค้อ” สร้างแล้ว รอบอสตองยืนยันตัวตนด้วยวิดีโอ สร้าง Business Profile ผ่านบัญชี nawinresort.khaokho@gmail.com เรียบร้อยแล้ว (ชื่อ “nawin resort เขาค้อ”, หมวด Resort hotel, ที่อยู่ 365/1 ต.เขาค้อ อ.เขาค้อ จ.เพชรบูรณ์ 67270, โทร 080-691-8190, เว็บไซต์ https://khaokho.nawingroup.com/guest-app/, พิกัดตรงกับปุ่มแผนที่) — สถานะปัจจุบัน “Verification required” ยังไม่ขึ้นค้นหาให้ลูกค้าเห็นจนกว่าจะยืนยันตัวตน

ค้างอยู่ — ลิงก์ “เว็บไซต์” ใน Google Business Profile เป็นบั๊กเดียวกับริชเมนู ตอนนี้ชี้ไป https://khaokho.nawingroup.com/guest-app/ ตรงๆ ซึ่งจะพังแบบเดียวกับที่แก้ในริชเมนูด้านบน (บังคับ login ผ่านเว็บแทน LIFF จริง) เมื่อลูกค้ากดจาก Google Maps/Search — บอสตองต้องไปแก้เองในหน้า Google Business Profile (ผมแก้แทนไม่ได้ ต้อง login บัญชี Google) เปลี่ยนเป็น https://liff.line.me/2011027975-pX2LznMW แทน

สิ่งที่บอสตองต้องทำเอง: Google เสนอวิธียืนยันตัวตนวิธีเดียวคือ อัดวิดีโอธุรกิจ (ถ่ายให้เห็นสถานที่ อุปกรณ์ และหลักฐานว่าเป็นผู้ดูแล) — ต้องไปที่รีสอร์ทจริงแล้วอัดคลิปส่ง ทำแทนไม่ได้ ไปที่ Google Business Profile Manager (ล็อกอินด้วย nawinresort.khaokho@gmail.com) → กด “Get verified” ที่แถวของ nawin resort เขาค้อ

Agoda listing — publish ส่งแล้ว รอ Agoda รีวิว (1-3 วัน)

สมัคร Agoda Partner ผ่านบัญชี nawinresort.khaokho@gmail.com แล้วสร้าง listing เต็มรูปแบบเสร็จ:

ที่บอสตองต้องทำเอง (หลัง Agoda อนุมัติ listing):

  1. ตั้งจำนวนห้องจริงในแต่ละ Room type (Deluxe Bungalow = 3 ห้อง, Standard Room = 1 ห้อง) ผ่าน Availability Center/Calendar ใน Agoda YCS
  2. กรอกเลขบัญชีธนาคารจริง สำหรับรับเงินจาก Agoda (payout setup) — ผมกรอกให้ไม่ได้เพราะเป็นข้อมูลการเงิน
  3. ตรวจ/แก้ไขขนาดห้อง (ตร.ม.) ที่เป็นตัวเลขประมาณการไว้ ถ้าต้องการความแม่นยำกว่านี้
  4. ตั้งชื่อห้องให้ตรงกับ Agoda — เข้า Dashboard → ยูนิต & รายงาน → กรอกช่อง “ชื่อห้องใน Agoda” ของแต่ละห้อง ให้ตรงกับชื่อ room type ที่ตั้งไว้ใน Agoda YCS เป๊ะๆ (เช่น “Deluxe Bungalow” สำหรับ N1/N2/N3, “Standard Room” สำหรับ T1) ไม่งั้นระบบ agoda-intake จับคู่ไม่ถูกห้อง

เชื่อม Agoda เข้าระบบ (ไม่ผ่าน channel manager) — เตรียมไว้แล้ว รอต่อฝั่งรับอีเมล

สร้าง edge function agoda-intake ไว้แล้ว รับข้อมูลการจองที่แกะมาจากอีเมลแจ้งจองของ Agoda (หรือจากทางอื่นในอนาคต เช่น channel manager) แล้วสร้าง/อัปเดต/ยกเลิกแถวใน bookings อัตโนมัติ (source = agoda) — กันชนกับการจองตรงด้วย exclusion constraint เดิม (ถ้าชนวันที่จะได้ error กลับมาทันทีให้ไปตรวจมือ ไม่ทับข้อมูลมั่ว)

  1. ต่อฝั่งอ่านอีเมล — เลือกอย่างใดอย่างหนึ่ง:
    • ให้ Hermes เพิ่ม cron job อ่านอีเมลแจ้งจองจาก Agoda (ที่ inbox ที่ Agoda YCS ส่งแจ้งเข้า) แกะข้อมูล แล้วยิง POST ไปที่ endpoint ด้านล่าง
    • หรือใช้ Zapier/Make (free tier) ตั้ง Gmail trigger → webhook ไปที่ endpoint เดียวกัน
  2. Endpoint ที่ต้องยิง:
    POST https://espxwmnaoauhsdgckwpr.supabase.co/functions/v1/agoda-intake/booking
    Header: x-internal-secret: <ขอค่าจริงจาก Claude ในแชท ไม่เก็บไว้ในไฟล์นี้>
    Body (JSON):
    {
      "externalRef": "เลขที่จอง Agoda (ใช้กันจองซ้ำ/ใช้ยกเลิก)",
      "roomTypeName": "ชื่อ room type ตามที่ Agoda ส่งมา",
      "guestName": "ชื่อแขก",
      "guestPhone": "เบอร์โทร (ถ้ามี)",
      "checkIn": "YYYY-MM-DD",
      "checkOut": "YYYY-MM-DD",
      "numGuests": 2,
      "totalAmount": 1800
    }
    

    ยิงซ้ำด้วย externalRef เดิมได้ (จะ update ไม่สร้างซ้ำ) — ยกเลิกให้ส่ง {"externalRef": "...", "action": "cancel"} แทน

  3. ทดสอบผ่านแล้วด้วย curl จริง (สร้าง/แก้ไข/ยกเลิก/ชนวันที่/ห้องไม่ตรง) — ครบทุกกรณี ก่อน apply กับข้อมูลจริง

ทำงานจริงแล้ว ผ่าน Hermes cron “Agoda Booking Sync (เขาค้อ)” ทุก 30 นาที เรียก .hermes/scripts/agoda_booking_sweep.py (สคริปต์อยู่ใน .hermes/ ของ repo หลัก claude — gitignored ไม่ commit เข้าที่นี่ ตาม CODEX_CLAUDE_AGENT.md) อ่าน Gmail nawinresort.khaokho@gmail.com แกะอีเมล Agoda แล้วยิงเข้า agoda-intake ตามข้างบน

บั๊กที่เจอและแก้แล้ว (2026-09-29) — สาเหตุที่ LINE push ของ Hermes ชนโควตารายเดือนตั้งแต่ 17 ก.ย.:

  1. Parse bug จริง: อีเมลจองของ Agoda บางฉบับ (subject ภาษาไทย “หมายเลขการจองของอโกด้า…”) มีป้ายกำกับอังกฤษ+ไทยติดกันบนบรรทัดเดียวไม่มีตัวคั่น เช่น Check-inเช็คอิน, Customer First Name ชื่อลูกค้า — โค้ดเดิมตัดป้ายอังกฤษออกแล้วเจอเศษข้อความไทยที่เหลือ เข้าใจผิดว่าเป็น “ค่า” (เพราะเช็คแค่ว่าไม่ว่างเปล่า) เลยไม่ไปอ่านบรรทัดถัดไปที่เป็นค่าจริง — ทำให้ checkIn/checkOut/guestName เป็น null/ผิดทุกครั้งที่เจอฟอร์แมตนี้ พัง totalAmount ด้วยเหตุผลคล้ายกัน (มีบรรทัดแปลไทยคั่นระหว่าง “Net rate” กับยอด THB) แก้แล้วทั้งสามจุด — มีผลกระทบจริงกับการจอง 3 รายการที่ค้างมาหลายวัน (แขก Panutda/Tinnakorn/Sujanya เช็คอิน ต.ค.-ธ.ค. 2569) คีย์เข้าระบบให้เรียบร้อยแล้วด้วยมือหลังแก้บั๊ก
  2. Alert spam ที่ตามมา: สคริปต์เดิมไม่เคย mark ข้อความที่ parse ไม่ผ่านว่า “เคยแจ้งแล้ว” เลยแจ้ง error เดิมซ้ำทุก 30 นาที ติดต่อกันหลายวัน (นับได้ ~345 ข้อความใน 10 วันจากรายการเดิมแค่ 3-4 ฉบับ) จนโควตา LINE push รายเดือนของ Hermes หมด ทำให้แจ้งเตือนอื่นๆ (Morning Brief, Evening Summary ฯลฯ) ส่งไม่ได้ไปด้วย — แก้โดยเพิ่ม cooldown 24 ชม. ต่อ message ID ใน state file (alerted_errors — ยัง retry การ parse/ส่งจริงทุกรอบเหมือนเดิม แค่ไม่แจ้งซ้ำถ้าเพิ่งแจ้งไปแล้วและยังไม่หาย)
  3. Quirk เดิมที่ยังไม่ได้แก้ในโค้ด (มี workaround แล้ว): ถ้าอีเมล confirm+cancel ของ booking เดียวกันมาในชุดเดียว sweep จะประมวลผล cancel ก่อน (Gmail คืนผลใหม่สุดก่อน) ทำให้ cancel ได้ 404 (ยังไม่มี booking ให้ยกเลิก) แล้ว confirm ที่มาทีหลังในลูปเดียวกันดันสร้างจองขึ้นมาทับ ต้องยิง cancel ซ้ำมือทีหลัง (curl ตาม endpoint ข้างบน, {"externalRef":"...","action":"cancel"}) เจอเคสนี้จริงกับ booking 2054180487 — เช็ค bookings.status เทียบกับ subject ล่าสุดของอีเมลเสมอถ้าสงสัยว่า sync ตกหล่น

ราคาบ้านพักตามวัน (เพิ่ม 2026-08-20)

บ้านพัก (N1-N3) คิดราคาต่างกันตามวัน: จ.-พฤ. 1,500 บาท/คืน, ศ.-อา. 2,500 บาท/คืน และวันหยุดนักขัตฤกษ์คิดราคาเท่าวันหยุดสุดสัปดาห์ (2,500) แม้จะตรงกับวันธรรมดา — เต็นท์ (T1) ราคาคงที่เหมือนเดิมไม่เปลี่ยน

ระบบสมาชิก — พัก 10 คืน ฟรี 1 คืน ข้ามสาขา (DB layer เสร็จ 2026-09-23, UI ยังไม่ทำ)

บอสตองต้องการระบบสมาชิกร่วมของกลุ่มนาวิน (พัก 10 คืน ฟรี 1 คืน ใช้ข้ามสาขาระหว่าง Nawin Resort เขาค้อ กับโรงแรมนาวิน ดอนเมือง ได้) — สำคัญ: โปรเจกต์ nawin-hotel-management (Supabase loxhiqsutuboxyllmysw) ที่เคยวางแผนสำหรับดอนเมืองไม่ได้ใช้งานจริงแล้ว บอสตอง pause ไว้ตั้งใจ ปัจจุบันดอนเมืองใช้ PMS บุคคลที่สาม (Hoteliers.Guru + OTA channel manager) ไม่มีระบบของเราเองที่เก็บ guest identity/LINE ผูกกับการเข้าพัก — ระบบนี้ (นาวิน เขาค้อ) จึงเป็นฐานเดียวที่มี guest+booking+LINE identity จริงให้ต่อยอด สเปกเต็ม (business rules, risk analysis) อยู่ที่ /Users/bosstong/Documents/claude/docs/loyalty-program-spec.md (คนละ repo — เขียนไว้ตอนยังเข้าใจผิดว่าโปรเจกต์ดอนเมืองยังใช้อยู่ ต้องอ่านโดยรู้ว่าส่วน “สถาปัตยกรรม” ในนั้นผิด แต่ส่วนกติกา/ความเสี่ยงธุรกิจยังใช้ได้)

สิ่งที่ทำเสร็จแล้ว (migration 0016_loyalty_program.sql + 0017_loyalty_credit_on_insert_too.sql, apply ขึ้น Supabase แล้ว):

ยังไม่ได้ทำ (ตั้งใจเว้นไว้ก่อน รอบอสตองยืนยันจุดออกแบบ 2 เรื่องนี้ก่อนทำ UI):

  1. จำกัดคืนฟรีให้ใช้ได้เฉพาะห้อง/ยูนิตราคาต่ำสุดหรือไม่ — สเปกเสนอไว้ว่าควรจำกัด (กันลูกค้าสะสมคืนถูกที่ดอนเมืองแล้วมาแลกบ้านพัก 2,500 บาทของเขาค้อฟรี) แต่ยังไม่ได้ใส่เป็นเงื่อนไขบังคับใน redeem_free_night — ตอนนี้ staff ต้องเช็คเองว่าจะให้แลกยูนิตไหน ฟังก์ชันไม่ได้บล็อก
  2. หน้า UI: ปุ่ม “ใช้สิทธิ์คืนฟรี” ใน dashboard/ (เรียก redeem_free_night/credit_manual_stay), หน้า “สิทธิ์สมาชิกของฉัน” ใน guest-app/ (เรียก get_loyalty_balance โชว์ยอดสะสม/คงเหลือ) — ยังไม่ได้ทำทั้งคู่
  3. Push LINE แจ้งเตือน เมื่อคืนสะสมเพิ่ม/ได้สิทธิ์ใหม่ — ยังไม่ได้ต่อ (ทำตาม pattern เดียวกับ notify_booking_confirmed/line-notify ที่มีอยู่แล้วได้เลย)
  4. ดอนเมืองยังไม่มีหน้าจอ/ทางให้ staff ที่นั่นเครดิตคืนได้เอง — ตอนนี้ credit_manual_stay เรียกได้เฉพาะผ่าน SQL/Dashboard ของบอสตอง (owner/manager) พนักงานดอนเมืองไม่มี profile ในระบบนี้เลย ต้องตัดสินใจก่อนว่าจะออกแบบยังไง (ให้ผู้จัดการเขาค้อกรอกแทนตอนสิ้นเดือน, หรือสร้างช่องทางแยกให้ frontdesk ดอนเมืองใช้)

หมายเหตุสถาปัตยกรรม