# TourApp — Home Catalog และ Agency Reviews

ตรวจและแก้จาก source ปัจจุบัน วันที่ 7 ตุลาคม 2026

## HOME BEFORE/AFTER

ก่อนแก้:

- `HomePage._loadCatalog()` เรียก `_database.search(SearchIntent(...))` แล้วแปลง `TripMatch` กลับเป็น Trip
- `_DestinationCard` มี badge `${trip.match}% match` และ InkWell ที่เปิด `/group`
- ส่วน “ทริปที่กำลังได้รับความสนใจ” มีจำนวนสมาชิกและปุ่มเปิด `/group` เช่นกัน
- เมื่อ API ใช้ไม่ได้ Home ใช้รายการ static `trips` รวมราคาจาก fallback

หลังแก้:

- Home เรียก `DatabaseTripService.catalog()` ซึ่งอ่านข้อมูลจาก endpoint เดิมแบบ read-only ไม่สร้าง SearchIntent/TripMatch ไม่อ่าน matchScore เพื่อกำหนดคะแนน Catalog และเรียงชื่อรายการแทนการจัดอันดับด้วยคะแนน
- ทั้งการ์ดจุดหมายยอดนิยมและการ์ดไอเดียทริปมีเฉพาะรูป ชื่อ รายละเอียดสั้น และราคาเริ่มต้นเมื่อ field price เป็นบวก ไม่มี match badge, จำนวนสมาชิก, ปุ่มเข้ากลุ่ม หรือ action/navigation ของการ์ด
- Country filter กรองเฉพาะรายการที่อ่านมา ไม่เรียก Matching Engine หรือส่งคำขอ join
- Loading/error/empty ไม่ใช้ราคา fallback เป็นราคาจริง ราคาที่แสดงมาจาก API `/api/groups` ซึ่งอ่าน `tour_groups.price_per_person` ในฐานข้อมูลเดิม ถ้าไม่มีค่าบวกจะไม่แสดงราคา
- ทางเข้า AI Preferences และสถานะกลุ่มที่ Backend จัดให้ อยู่แยกจาก Catalog ตาม automatic flow เดิม การแก้ครั้งนี้ไม่เพิ่มการเลือกกลุ่มเอง

`catalog()` reuse `POST /api/groups` ที่มีอยู่แล้ว เพราะ endpoint นี้อ่านฐานข้อมูลและไม่มีการสร้าง membership การกรองข้อมูลทริปเดิมใน `search()` สำหรับ callers อื่นยังรองรับอยู่ ไม่จำเป็นต้องเพิ่ม Backend route/schema สำหรับงาน Home

Matching หลักยังเป็นฟอร์ม AI Preference → ผู้ใช้ตรวจ/ยืนยัน controls → Backend Matching → window รอ default 5 นาที → Auto Group ไม่ได้แก้ Matching Engine, waiting policy หรือ Majority/Payment/PromptPay/SlipOK

## REVIEW ROOT CAUSE

เป็น **Backend process เก่ายังไม่ได้ restart หลังเพิ่ม Review API** ไม่ใช่ path ของ Portal ผิด และไม่ใช่บริษัทไม่มีรีวิว

หลักฐานก่อนเปลี่ยน source ของ Review:

| สิ่งที่ตรวจ | ผล |
| --- | --- |
| Portal client | `agencyApi.reviews(token)` เรียก POST `/api/agency/reviews` |
| Request body | `{}` ไม่มี agency id/path parameter จาก client |
| Authentication | header `Authorization: Bearer ...` จาก agency session |
| Vite proxy | `/api` → `http://127.0.0.1:8787` |
| Backend source | `AgencyAdminApi.jsonPaths` และ `handleJson()` มี `/api/agency/reviews`; gemini_proxy รวม routes และ inject AgencyReviewService แล้ว |
| Runtime PID 9420 | เริ่ม 6 ตุลาคม 20:53:56 ซึ่งก่อนแก้ agency_admin_api เวลา 22:09:57 และ review service เวลา 22:38:29 |
| POST ตรงพอร์ต 8787 | 404 + `ไม่พบเส้นทางที่เรียก` |
| POST ผ่าน Portal 4173 | 404 ข้อความเดียวกัน |

Restart เฉพาะ Backend แล้ว PID ใหม่ 8368 ตอบ **401 `กรุณาเข้าสู่ระบบใหม่` เมื่อไม่มี session** ทั้ง URL ตรงและผ่าน Portal ยืนยันว่า route ใหม่ถูกโหลดแล้ว ไม่ได้แก้ server/Portal source เพื่อหลบ 404 และไม่แปลง 404 เป็น empty state

Session ของบริษัทเป็นข้อมูลใน memory หลัง restart จึงต้องลงชื่อเข้าใช้อีกครั้ง การมี route แต่ตอบ 401 ไม่มี auth เป็นพฤติกรรมถูกต้อง ไม่ใช่ route หาย

## API ROUTE

```text
Portal: POST http://127.0.0.1:4173/api/agency/reviews
  → Vite proxy
Backend: POST http://127.0.0.1:8787/api/agency/reviews
Headers: Content-Type: application/json
         Authorization: Bearer <agency session>
Body: {}
```

`AgencyAdminApi._agencyReviews()` หา `agency.id` จาก session แล้วเรียก `AgencyReviewService.readForAgency(agency.id)` Client ไม่ได้กำหนดบริษัทที่มีสิทธิ์อ่านด้วย path/body

| Response | พฤติกรรมที่ตรวจ |
| --- | --- |
| 200 + real reviews | mean, stars, count, latest 3 และ Modal ทั้งหมด |
| 200 + empty และเปิด demo + flag | seed ใน memory 6 รีวิว, latest 3, badge “ข้อมูลสาธิต” และ Modal ทั้ง 6 |
| 200 + empty และไม่เปิด demo | “ยังไม่มีรีวิวจากนักท่องเที่ยว” ไม่มี error/ดาวศูนย์/ปุ่มดูทั้งหมด |
| 401/403 | error จาก API ไม่เข้า empty branch |
| 404 | error ของ route/path ไม่เข้า empty branch |
| 500 | backend error ไม่เข้า empty branch |

ข้อมูลจริงอ่าน `trip_reviews.agency_rating` ผ่าน bids → tour_packages และตรวจ paid/completed ตาม repository เดิม Demo มีได้เฉพาะ environment ที่อนุญาต (ทดสอบ `TRAVELIN_ENV=demo`) พร้อม `TRAVELIN_ENABLE_DEMO_REVIEWS=true` และไม่มีรีวิวจริง guard ใน production/prod/staging ยังไม่อนุญาต demo แม้ flag เป็น true

## FILES CHANGED

Production source 2 ไฟล์:

- `lib/pages/home_page.dart` — ใช้ Catalog reader, ถอด badge/action/member count จาก Catalog cards, เพิ่มรายละเอียด, แสดงราคาเฉพาะข้อมูลบวก และ loading/error จาก API
- `lib/services/database_trip_service.dart` — เพิ่ม `catalog()`, ใช้ HTTP reader/Trip mapping ร่วมกับ search เดิม โดย Catalog ไม่สร้างคะแนนหรือใช้ intent

Tests 3 ไฟล์:

- `test/database_trip_service_test.dart` — read-only request, ไม่มี intent filters/scoring, เรียง Catalog ไม่ตามคะแนน และไม่สร้างราคาขณะ API error
- `test/widgets/home_catalog_test.dart` (ใหม่) — รูป/ชื่อ/รายละเอียด/ราคา, ไม่มี match/group action, กดการ์ดไม่ navigate, country filter และกรณีไม่รู้ราคา/โหลดล้มเหลว
- `test/widget_test.dart` — ปรับ Home fixture ให้มี catalog reader และ assertion จำนวนสมาชิกให้ตรวจจากสถานะกลุ่มที่ Backend จัดให้แทนการ์ด Catalog

Test helper และเอกสาร:

- `work/verify_home_review_card.cjs` (ใหม่) — render component Portal จริงพร้อม API client จริงใน DOM จำลอง ตรวจ real/demo/empty และการแสดง error status; ใช้ auth context ของ fixture โดย Backend ยังคงตรวจ session จริง
- รายงานนี้

ไม่มีการแก้ `server/...` หรือ `portal/src/...` สำหรับ Review เพราะ route ตรงกันอยู่แล้ว ไม่มีการเปลี่ยน Portal dependency/lockfile ติดตั้ง jsdom สำหรับ helper ไว้เฉพาะโฟลเดอร์ OS temp `TourAppReviewDomTests`

## TESTS

รันจาก `travelin_app`:

```powershell
flutter analyze
dart analyze server
flutter test test/database_trip_service_test.dart test/widgets/home_catalog_test.dart test/widget_test.dart test/server/agency_review_service_test.dart test/server/agency_admin_api_test.dart --reporter expanded
flutter test test/server --reporter expanded
```

ผล:

- `flutter analyze` และ `dart analyze server`: No issues found
- ชุด Home/widget/Review ที่เกี่ยวข้อง: **60 ผ่าน**
- Backend suite ทั้งหมด: **197 ผ่าน / 19 skipped** (รวม Review tests ที่อยู่ในชุดข้างต้น จึงไม่นับสองชุดบวกกัน)
- หลังปรับข้อความ empty state รอบสุดท้าย รัน Home/Catalog 6 tests ซ้ำ: **6 ผ่าน**
- Existing widget tests ของ automated preference submission, voting lifecycle และ payment states ยังผ่าน

ใน `portal`:

```powershell
npm.cmd run build
npm.cmd run lint
```

ทั้งสองคำสั่งผ่าน

ตรวจ real HTTP/SQL ด้วย `work/verify_agency_reviews.py` เดิม ซึ่งสร้าง schema/account fixtures ของตัวเองและรัน Backend 18787/Portal 4174 แยกจากของผู้ใช้:

- Review API/DB tests: **4 ผ่าน** ไม่ได้อ้าง skipped DB tests ว่าผ่านจริง
- ไม่มี session → 401
- รีวิวที่ paid/completed → real mean 4.7, count 3 และเรียงล่าสุดถูกต้อง
- บริษัทไม่มีรีวิว + demo flag → memory demo 6 รายการ การอ่านซ้ำไม่เพิ่มข้อมูล
- ส่ง agency id บริษัทอื่นมาเองก็อ่านบริษัทอื่นไม่ได้

DOM component verification ของ `AgencyReviewsCard.tsx` และ `portal/src/lib/api.ts` จริง:

- Real: mean/stars/count/latest 3; ปุ่มเรียก showModal, แสดงรายการครบและเรียก close
- Demo: badge “ข้อมูลสาธิต”, count 6, preview 3, Modal 6
- Normal/flag off: empty text ไม่มี error/ดาว/ปุ่มดูทั้งหมด
- Mock 401,403,404,500: notice error แยกจาก empty state
- Request URL/method/auth ตรง POST `/api/agency/reviews` ทุกครั้ง

ข้อจำกัดการตรวจ UI: เครื่องมือ browser ปฏิเสธการ bind แท็บเดิมที่อยู่บนหน้าข้อผิดพลาดภายใน browser ด้วย protocol ที่ไม่อนุญาต จึงใช้ DOM จำลองเป็นทางเลือก ไม่มีการข้าม policy ไปควบคุม browser ทางอื่น DOM test stub native `showModal/close` เพื่อทดสอบการเรียกจาก component และเนื้อหา ไม่ได้ยืนยัน native focus/Escape behavior ใน browser รอบนี้

หลังตรวจ ล้างเฉพาะ schema/account ที่ helper สร้างไว้เองและหยุด services ทดสอบ 18787/4174 แล้ว **checksum ฐานใช้งานจริงทั้ง 33 ตารางตรงเดิม** ไม่มีการลบหรือเขียนข้อมูลของผู้ใช้

## RESTART REQUIRED

ทำแล้ว:

- Backend 8787: restart จาก PID 9420 เป็น PID 8368 เพื่อโหลด Review API ตั้ง startup development-account seed เป็น 0 สำหรับ restart นี้ เพื่อไม่สร้างบัญชีฝึกเพิ่มเติม
- Flutter: attach กับแอปที่รันอยู่ Hot Reload ครั้งแรกยังไม่โหลด library ใหม่ จึงทำ Hot Restart เฉพาะ Dart app จากนั้นตรวจผ่าน Dart VM ว่า `DatabaseTripService.catalog()` มีจริงและ `_HomePageState._openTrip` ไม่มีแล้ว หลังแก้ข้อความรอบสุดท้าย Hot Reload โหลด 2 libraries สำเร็จ
- Detach process ที่ใช้ตรวจเพิ่มเติมแล้ว แอป Android PID 7097 ยังทำงาน

ไม่ได้ restart MariaDB, Emulator หรือ Portal ของผู้ใช้ Portal 4173 ยังเป็น PID 27652 เดิม และ Backend ใหม่ยังรันที่ 8787 ส่วน fixture services ถูกปิดแล้ว

ผู้ใช้ให้เปิด/refresh Portal แล้วลงชื่อเข้าบริษัทใหม่หนึ่งครั้ง เนื่องจาก restart Backend ทำให้ agency session เก่าใน memory หมดไป ไม่ต้อง restart services เพิ่มเพื่อใช้การแก้รอบนี้
