อัปเดตล่าสุด: 2026-08-08
nawin-resort-khaokho (ref espxwmnaoauhsdgckwpr, ap-southeast-1) — แยกขาดจาก nawin-hotel-management สมบูรณ์units, guests, guest_documents, bookings, booking_status_history, payments, notifications_log, profiles, audit_log, line_webhook_eventsprofiles, ตาราง units อ่านสาธารณะได้ (สำหรับ guest-app)btree_gist บน unit_id + ช่วงวันที่) — ทดสอบผ่านcheck_availability(unit_id, checkin, checkout) RPC — SECURITY DEFINER, grant ให้ anon/authenticatednotify_booking_confirmed (ยิง LINE push อัตโนมัติเมื่อ booking confirmed) + log_booking_status_change (log ทุกการเปลี่ยนสถานะ)guest-api, line-webhook, line-notify (ทดสอบ auth guard ผ่าน: ไม่มี idToken → 400, signature ผิด → 401, internal secret ผิด → 401)guest-app/ ครบ 3 หน้า: index.html (LIFF login), booking.html (เลือกยูนิต+วันที่+จอง), my-bookings.html (ดู/ยกเลิกการจอง)staff-app/ ใหม่ — index.html (LIFF login + เช็คว่าผูกบัญชีหรือยัง), booking.html (พนักงานคีย์จองแทนลูกค้า พร้อมราคาอัตโนมัติ+ปรับได้) — ใช้ edge function staff-api แยกจาก guest-apidashboard/ ครบ 4 หน้า: index.html (login), bookings.html (ปฏิทินรายเดือน + รายการจอง + ค้นหา + filter “จองโดย”), new-booking.html (walk-in, ราคาปรับได้), units.html (owner เท่านั้น — แก้ไขยูนิต, KPI รายได้/occupancy, สรุปคอมมิชชั่นรายพนักงาน, ผูกบัญชี LINE พนักงาน)owner (เห็นทุกอย่าง) / manager / staff (เห็นแค่การจอง+เพิ่มการจอง ไม่เห็นยูนิต/รายงาน)created_by อัตโนมัติ ใช้คิดคอมมิชชั่นได้khaokho.nawingroup.com ตั้งค่าแล้ว (DNS ชี้แล้ว รอ GitHub ออก SSL cert)@428gpjds, secrets ทั้งหมดอยู่ใน Supabase Vault แล้ว, LIFF ID ใส่ใน config.js ทั้ง 2 แอปแล้วkhaokho.nawingroup.com — DNS record เพิ่มที่ Squarespace (ผ่าน Google Workspace) แล้ว, GitHub Pages ตั้ง custom domain แล้ว — เหลือแค่รอ GitHub ออก SSL certificate เองอัตโนมัติ (มักไม่เกิน 1 ชม. หลัง DNS ตั้งเสร็จ)wayupatza@gmail.com) ผูก role owner แล้ว, login dashboard ได้เลยหน้า login เพิ่มปุ่ม “ลืมรหัสผ่าน?” แล้ว (ให้พนักงานกู้รหัสผ่านเองได้ ไม่ต้องให้ใครแตะรหัสผ่านคนอื่น) แต่ Supabase บล็อกไม่ให้ redirect ไปโดเมนที่ไม่ได้อยู่ใน allow-list ต้องตั้งค่า 1 ครั้ง:
https://khaokho.nawingroup.comhttps://khaokho.nawingroup.com/dashboard/reset-password.html (หรือใส่ https://khaokho.nawingroup.com/** แบบ wildcard ให้ครอบคลุมทุกหน้าในโดเมนนี้)แก้ปัญหาบีม login ไม่ได้เฉพาะหน้าตอนนี้เลย (เร็วกว่ารอ 4 ข้อบนเสร็จ): เข้า Supabase Dashboard → Authentication → Users → หา awatsada.beam@gmail.com → กด “…” → Send password recovery (ส่งอีเมลให้บีมตั้งรหัสใหม่เอง) — แต่ก็ยังต้องตั้งค่า URL Configuration ข้างบนก่อน ไม่งั้นลิงก์ในอีเมลจะ redirect ผิดที่
staff-app)staff-app ไม่มีหน้า login แยก — ระบุตัวพนักงานจาก LINE account ที่ผูกไว้ในตาราง profiles โดยตรง ขั้นตอน:
https://khaokho.nawingroup.com/staff-app/ ผ่าน LINE (ใน LINE app จริง ไม่ใช่เบราว์เซอร์ปกติ — ต้องเปิดจากลิงก์ที่ส่งผ่านแชท LINE หรือปุ่มเมนู richmenu ของ OA)staff-app ใหม่อีกครั้ง — คราวนี้จะเข้าหน้ากรอกจองได้เลย ไม่ต้อง login ซ้ำอีกทุกการจองที่พนักงานคีย์ผ่าน staff-app จะถูกบันทึก “จองโดย” อัตโนมัติ (เห็นในหน้า “การจอง” + นับรวมในตาราง “สรุปตามพนักงาน (สำหรับคิดคอมมิชชั่น)” ที่หน้า “ยูนิต & รายงาน” เหมือนการจองที่คีย์ผ่าน Dashboard ทุกประการ)
หมายเหตุเรื่อง internal_functions_secret: สุ่มค่าไว้แล้วตอน setup (SECURITY DEFINER trigger ↔ line-notify function ใช้ยืนยันกัน) ไม่ต้องแก้อะไรเพิ่ม เว้นแต่ต้องการหมุนค่าใหม่ (rotate) — ทำผ่าน vault.update_secret เหมือน secret อื่นๆ แล้วอย่าลืมว่าไม่มีที่อื่นอ้างอิงค่านี้นอกจาก DB trigger เอง
ตั้งค่าไว้ใน resort_settings แล้ว: PromptPay ID 088985000565236 (เลข 15 หลักจาก CLICX, รูปแบบ e-Wallet/merchant — ระบบรองรับแล้ว), ชื่อบัญชี “นายนาวิน อินทเสือ”, และ note “โอนแล้วแนบสลิปในแอปได้เลย” — ลูกค้าจองใหม่เห็น QR จริง+note ทันที ไม่ต้องทำอะไรเพิ่มในหน้า Dashboard
ค้างอยู่ — บอสตองต้องทดสอบสแกน QR ด้วยแอปธนาคารจริงก่อนเปิดใช้งานจริง (Claude ไม่มีมือถือ/แอปธนาคารให้ทดสอบเอง) — payload ตรวจสอบโครงสร้าง TLV/CRC ถูกต้องตาม spec แล้ว แต่ควรสแกนจริงยืนยันว่าขึ้นชื่อ “นายนาวิน อินทเสือ” ถูกต้องก่อนรับเงินจริง
เข้า Dashboard → เมนู “ชำระเงิน” — จะเห็นรายการสลิปที่รอตรวจสอบทั้งหมด (พนักงานทุกคนเห็นได้ ไม่ใช่แค่ owner) คลิกรูปเพื่อดูขยาย กด “ยืนยันการชำระเงิน” จะเปลี่ยนสถานะการจองเป็น “ยืนยันแล้ว” ทันทีและส่ง LINE แจ้งเตือนลูกค้าอัตโนมัติ หรือกด “ปฏิเสธสลิป” ถ้าสลิปไม่ถูกต้อง (ลูกค้าจะอัปโหลดใหม่ได้)
เข้า Dashboard → ยูนิต & รายงาน → เลื่อนลงหัวข้อ “รูปภาพยูนิต” อัปโหลดรูปจริงของแต่ละห้อง/เต็นท์ได้เลย (ไม่จำกัดจำนวน) รูปจะโชว์ให้ลูกค้าดูตอนเลือกยูนิตในหน้าจองทันที — ตอนนี้ยังไม่มีรูปเลย แนะนำอัปโหลดก่อนเริ่มรับจองจริงจัง
มี cron job รันทุก 15 นาที ยกเลิกการจองที่ยังเป็น “รอยืนยัน” (ลูกค้าจองเองผ่านแอป ยังไม่จ่ายเงิน) เกิน 24 ชม. อัตโนมัติ — การจองที่พนักงานคีย์ผ่าน Dashboard/staff-app เป็น “ยืนยันแล้ว” ทันทีอยู่แล้วไม่โดนยกเลิก
ระบบตั้งค่าไว้ให้แล้วว่า “จะไม่มีการขอให้โอนเงินผ่านแชท” (ขึ้นแบนเนอร์เตือนในแอปทุกครั้งก่อนโชว์ QR) แต่แนะนำเพิ่ม:
ข้อความต้อนรับ — ทำงานแล้วทันที ไม่ต้องทำอะไรเพิ่ม
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
แก้โดย:
guest-app/privacy.html (LINE บังคับต้องมี Privacy policy URL ก่อน Publish ได้) แล้วใส่ URL https://khaokho.nawingroup.com/guest-app/privacy.html ในช่อง Privacy policy URL ของ channelบั๊กที่สาม เจอตอนบอสตองทดสอบจริงหลังแก้ 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
แก้โดย:
guest-app/index.html ให้รับ query param ?next=my-bookings.html แล้ว redirect ไปหน้าที่ถูกต้องหลัง login สำเร็จ (ค่าเริ่มต้นคือ booking.html)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 Partner ผ่านบัญชี nawinresort.khaokho@gmail.com แล้วสร้าง listing เต็มรูปแบบเสร็จ:
ที่บอสตองต้องทำเอง (หลัง Agoda อนุมัติ listing):
สร้าง edge function agoda-intake ไว้แล้ว รับข้อมูลการจองที่แกะมาจากอีเมลแจ้งจองของ Agoda (หรือจากทางอื่นในอนาคต เช่น channel manager) แล้วสร้าง/อัปเดต/ยกเลิกแถวใน bookings อัตโนมัติ (source = agoda) — กันชนกับการจองตรงด้วย exclusion constraint เดิม (ถ้าชนวันที่จะได้ error กลับมาทันทีให้ไปตรวจมือ ไม่ทับข้อมูลมั่ว)
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"} แทน
ทำงานจริงแล้ว ผ่าน 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 ก.ย.:
Check-inเช็คอิน, Customer First Name ชื่อลูกค้า — โค้ดเดิมตัดป้ายอังกฤษออกแล้วเจอเศษข้อความไทยที่เหลือ เข้าใจผิดว่าเป็น “ค่า” (เพราะเช็คแค่ว่าไม่ว่างเปล่า) เลยไม่ไปอ่านบรรทัดถัดไปที่เป็นค่าจริง — ทำให้ checkIn/checkOut/guestName เป็น null/ผิดทุกครั้งที่เจอฟอร์แมตนี้ พัง totalAmount ด้วยเหตุผลคล้ายกัน (มีบรรทัดแปลไทยคั่นระหว่าง “Net rate” กับยอด THB) แก้แล้วทั้งสามจุด — มีผลกระทบจริงกับการจอง 3 รายการที่ค้างมาหลายวัน (แขก Panutda/Tinnakorn/Sujanya เช็คอิน ต.ค.-ธ.ค. 2569) คีย์เข้าระบบให้เรียบร้อยแล้วด้วยมือหลังแก้บั๊กalerted_errors — ยัง retry การ parse/ส่งจริงทุกรอบเหมือนเดิม แค่ไม่แจ้งซ้ำถ้าเพิ่งแจ้งไปแล้วและยังไม่หาย)curl ตาม endpoint ข้างบน, {"externalRef":"...","action":"cancel"}) เจอเคสนี้จริงกับ booking 2054180487 — เช็ค bookings.status เทียบกับ subject ล่าสุดของอีเมลเสมอถ้าสงสัยว่า sync ตกหล่นบ้านพัก (N1-N3) คิดราคาต่างกันตามวัน: จ.-พฤ. 1,500 บาท/คืน, ศ.-อา. 2,500 บาท/คืน และวันหยุดนักขัตฤกษ์คิดราคาเท่าวันหยุดสุดสัปดาห์ (2,500) แม้จะตรงกับวันธรรมดา — เต็นท์ (T1) ราคาคงที่เหมือนเดิมไม่เปลี่ยน
units: weekday_price, weekend_price — ถ้าเป็น null ทั้งคู่ (เช่นเต็นท์) ระบบจะใช้ base_price แบบเดิม (flat rate) แทนpublic_holidays (holiday_date, name) — ใส่ปฏิทินวันหยุดราชการปี 2569 ไว้แล้วครบ (อ้างอิงจากมติ ครม. ปี 2569, ไม่รวม 16 ต.ค. 2569 เพราะเป็นวันหยุดพิเศษเฉพาะ กทม. เท่านั้น รีสอร์ทอยู่เพชรบูรณ์ไม่เกี่ยว) — ต้องเพิ่มปฏิทินปีถัดไปเองทุกปีปลายปี ด้วย SQL insert into public_holidays (holiday_date, name) values (...)unit_night_rate(unit_id, night_date) และ calc_stay_total(unit_id, check_in, check_out) เป็น single source of truth การคิดราคา ใช้ทั้งจาก guest-api, staff-api, และหน้าเว็บ (เรียกตรงผ่าน supabase.rpc() เพื่อ preview ราคาก่อน submit — ฟังก์ชันนี้ grant ให้ anon/authenticated เรียกตรงได้ ต่างจากฟังก์ชันอื่นๆ ที่ล็อกไว้เฉพาะ service_role)
SECURITY DEFINER (ต่างจาก check_availability) ตอนแรก grant ให้ anon/authenticated แค่ calc_stay_total แต่มันเรียก unit_night_rate ข้างในด้วยสิทธิ์ผู้เรียก (ไม่ใช่สิทธิ์เจ้าของฟังก์ชัน) ทำให้ลูกค้าจริงเจอ error “คำนวณยอดรวมไม่สำเร็จ” เพราะ permission denied for function unit_night_rate — ต้อง grant execute ให้ unit_night_rate แยกต่างหากด้วย ถ้าจะเพิ่มฟังก์ชันคำนวณราคาใหม่ที่ไม่ใช่ SECURITY DEFINER และเรียกฟังก์ชันย่อยอื่นต่อ ต้อง grant ให้ครบทุกฟังก์ชันในเชน ไม่ใช่แค่ตัวนอกสุดunits.weekday_price/weekend_price ตรงๆ ผ่าน SQL หรือเพิ่ม UI ในหน้า dashboard ทีหลังได้ (ยังไม่มี UI แก้ราคา ต้องแก้ผ่าน SQL เท่านั้นตอนนี้)บอสตองต้องการระบบสมาชิกร่วมของกลุ่มนาวิน (พัก 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 แล้ว):
loyalty_ledger (guest_id, booking_id nullable, property khaokho/donmueang, event_type night_earned/free_night_redeemed, nights_delta, note, created_by, created_at) — เก็บเป็น ledger ไม่ใช่ยอดรวม เพื่อ audit ย้อนหลังได้ถ้าลูกค้าโต้แย้งยอดbookings_credit_loyalty (after insert or update — ครอบทั้ง 2 เคส หลังเจอบั๊กตอนทดสอบว่า after update เฉยๆ พลาดเคส insert ตรงเป็น checked_out) ยิงอัตโนมัติเมื่อ bookings.status → checked_out นับจำนวนคืนจาก check_out - check_in กันเครดิตซ้ำด้วย partial unique index บน booking_idget_loyalty_balance(guest_id) — คืน total_nights_earned / free_nights_redeemed / free_nights_available (= floor(total/10) - redeemed) grant ให้ anon+authenticated เรียกตรงได้ (เผื่อ guest-app จะโชว์ยอดสมาชิกในอนาคต)redeem_free_night(guest_id, property, booking_id?, note?) — staff/owner เรียกได้ (เช็คจาก profiles) ใช้ pg_advisory_xact_lock ต่อ guest กันสองคนกดแลกพร้อมกันเกินสิทธิ์จริงcredit_manual_stay(guest_id, property, nights, note?) — เฉพาะ owner/manager ใช้เครดิตคืนพักดอนเมือง (หรือแหล่งอื่นนอกระบบ) เข้า ledger เดียวกัน เพราะดอนเมืองไม่มี feed อัตโนมัติเข้ามายังไม่ได้ทำ (ตั้งใจเว้นไว้ก่อน รอบอสตองยืนยันจุดออกแบบ 2 เรื่องนี้ก่อนทำ UI):
redeem_free_night — ตอนนี้ staff ต้องเช็คเองว่าจะให้แลกยูนิตไหน ฟังก์ชันไม่ได้บล็อกdashboard/ (เรียก redeem_free_night/credit_manual_stay), หน้า “สิทธิ์สมาชิกของฉัน” ใน guest-app/ (เรียก get_loyalty_balance โชว์ยอดสะสม/คงเหลือ) — ยังไม่ได้ทำทั้งคู่notify_booking_confirmed/line-notify ที่มีอยู่แล้วได้เลย)credit_manual_stay เรียกได้เฉพาะผ่าน SQL/Dashboard ของบอสตอง (owner/manager) พนักงานดอนเมืองไม่มี profile ในระบบนี้เลย ต้องตัดสินใจก่อนว่าจะออกแบบยังไง (ให้ผู้จัดการเขาค้อกรอกแทนตอนสิ้นเดือน, หรือสร้างช่องทางแยกให้ frontdesk ดอนเมืองใช้)bookings/guests ตรงๆ เลย ทุกอย่างผ่าน guest-api edge function ด้วย service-role key + ยืนยัน LIFF ID token ทุก requestbtree_gist) ระดับ DB เป็นด่านสุดท้าย — RPC check_availability เป็นแค่ด่านตรวจก่อนเพื่อ UX ที่ดีกว่า (error message ชัดเจน) ไม่ใช่จุดเดียวที่กันชนline-notify ยิงจาก DB trigger ผ่าน pg_net (async, ไม่บล็อก transaction การอัปเดตสถานะจอง)