Catalog & Search Requirements
หน้านี้รวม Functional Requirements, Acceptance Criteria และ Business Rule Matrix สำหรับ QA/BA
อ้างอิง: PriceEstimationService.cs, QuoteOrchestrator.cs, AddOnService.cs, Catalog validators
US-C01: ค้นหารถ (Public Search)
As a ลูกค้า (Guest หรือ Member)
I want to ค้นหารถเช่าที่ว่างในช่วงวันและสาขาที่ต้องการ
So that ฉันเห็นรถที่พร้อมใช้งานพร้อมราคาที่ถูกต้อง
Acceptance Criteria
| # | เงื่อนไข | ผลลัพธ์ที่คาดหวัง |
|---|
| AC1 | ระบุ pickup/return branch, pickup/return datetime ที่ถูกต้อง | แสดงรายการรถที่ว่าง พร้อม local metadata (ชื่อ, รูป, เกียร์, เชื้อเพลิง) |
| AC2 | Pickup branch ไม่พบในระบบ | Error: "Pickup branch with code X not found" |
| AC3 | Pickup branch status = inactive | Error: "Pickup branch with code X is not active" |
| AC4 | Return branch ไม่พบในระบบ | Error: "Return branch with code X not found" |
| AC5 | Return branch status = inactive | Error: "Return branch with code X is not active" |
| AC6 | Filter TransmissionType | แสดงเฉพาะรถที่ ModelTransmissionDesc ตรงกับค่าที่ filter |
| AC7 | Filter FuelType | แสดงเฉพาะรถที่ ModelFuelDesc ตรงกับค่าที่ filter |
| AC8 | Filter MinPrice / MaxPrice | แสดงเฉพาะรถที่ pricePerDay อยู่ในช่วงที่กำหนด |
| AC9 | Filter CarGroup | แสดงเฉพาะ car group ที่ระบุ |
| AC10 | CarAvailable = 0 จาก CarPro | รถนั้นไม่แสดง หรือแสดงว่าไม่ว่าง |
| AC11 | Promotion code ส่งมาแต่ CarPro ตอบ invalid cdp | Fallback ค้นหาโดยไม่ส่ง CDP; ตั้ง IsInvalidPromotionCode = true |
| AC12 | CarPro timeout/error (ไม่ใช่ invalid cdp) | แสดง error ให้ลูกค้า |
Business Rules
| BR# | Rule | ตัวอย่าง |
|---|
| BR-C01-1 | Branch validation ใช้ BranchCarProSync (ไม่ใช่ Branch table โดยตรง) | Branch BKK01 ต้องอยู่ใน BranchCarProSync |
| BR-C01-2 | pricePerDay = TotalAmount / SoldDays (จาก CarPro response) | CarPro ส่ง 3,000 / 3 วัน → 1,000/วัน |
| BR-C01-3 | Filter Price เทียบกับ pricePerDay ไม่ใช่ total | MaxPrice 1,500 → กรองรถที่ราคา/วัน ≤ 1,500 |
| BR-C01-4 | ถ้า CarPro fallback ยังล้มเหลว → throw error | ไม่แสดงผลลัพธ์ที่ผิดพลาด |
US-C02: ดู Price Quote
As a ลูกค้า
I want to ดูราคาสรุปรวม add-on, เชื้อเพลิง, OWR ก่อนจอง
So that ฉันเห็นราคาที่แน่นอนก่อนยืนยัน
Acceptance Criteria
| # | เงื่อนไข | ผลลัพธ์ที่คาดหวัง |
|---|
| AC1 | ขอ quote โดยไม่มี add-on | แสดงราคารถ + VAT + grand total |
| AC2 | เลือก add-on type PerCar | ราคา add-on = price × 1 (ไม่คูณวัน หรือจำนวนคน) |
| AC3 | เลือก add-on type PerDriverUnder21YearsOld | ราคา = price × จำนวน driver อายุน้อยกว่า 21 |
| AC4 | เลือก add-on type PerDriverMoreThanOrEqual3 กับ 2 driver | คิดราคา 1 unit (minimum 1) |
| AC5 | เลือก add-on type PerDriverMoreThanOrEqual3 กับ 4 driver | คิดราคา 2 unit (driver ที่ 3 และ 4) |
| AC6 | add-on มี rate calculate = DayRate | ราคา = price × จำนวนวันเช่า |
| AC7 | เลือก root add-on (bundle) + ลูกทุกตัว | คิดราคา root อย่างเดียว |
| AC8 | เลือก root add-on + ลูกบางตัว | คิดราคา root + ลูกที่เลือก |
| AC9 | ไม่เลือก root แต่เลือกลูก | คิดราคาเฉพาะลูกที่เลือก |
| AC10 | add-on มี IsMaximumRate = true และคำนวณเกิน | cap ที่ MaximumRate |
| AC11 | add-on มี IsIncludedItem = true | ไม่คิดราคาเพิ่ม (รวมอยู่แล้ว) |
| AC12 | OWR (One-Way Rental) ต่างสาขา | แสดงราคา OWR จาก OneWayRentalService |
Business Rules — Add-on Pricing
| BR# | Rule | ตัวอย่าง |
|---|
| BR-C02-1 | PerCar → คิดครั้งเดียวไม่ขึ้นกับวันหรือคนขับ | PDW 500 → คิด 500 |
| BR-C02-2 | PerDriverUnder21YearsOld → คูณจำนวน driver อายุ < 21 | ราคา 200/คน, driver อายุต่ำกว่า 21 = 2 คน → 400 |
| BR-C02-3 | PerDriverMoreThanOrEqual3 → คิดตั้งแต่คนที่ 3 แต่ minimum = 1 unit | 2 driver → คิด 1 unit; 5 driver → คิด 3 unit |
| BR-C02-4 | DayRate → คูณ daysRental | ราคา 100/วัน, เช่า 3 วัน → 300 |
| BR-C02-5 | OneTimeRate → คิดครั้งเดียว ไม่คูณวัน | ค่าส่งรถ 200 → คิด 200 เสมอ |
| BR-C02-6 | Bundle: Root selected + ALL children → คิดแค่ root | Root 500 covers ทุก child |
| BR-C02-7 | Bundle: Root selected + SOME children → คิด root + selected children | Root 500 + child C ที่เลือก 100 = 600 |
| BR-C02-8 | Bundle: Root NOT selected → คิดแค่ selected children | ไม่มี root, child A 100 + child B 200 = 300 |
| BR-C02-9 | IsSpecifyQuantity = true → ไม่คิดราคาใน calculator | Insurance specify-quantity ราคาคำนวณแยก |
| BR-C02-10 | MaximumRate cap | คำนวณได้ 1,500 แต่ MaximumRate = 1,000 → คิด 1,000 |
VAT คำนวณอย่างไร
| รายการ | สูตร | ตัวอย่าง |
|---|
| Car VAT | totalIncVAT − (totalIncVAT / 1.07) | 3,210 − (3,210 / 1.07) = 210 |
| Add-on VAT | totalAddonPrice × 0.07 | Add-on 500 × 0.07 = 35 |
| Grand Total | totalIncVAT + totalAddonPrice + addonVat | 3,210 + 500 + 35 = 3,745 |
US-C03: จัดการ Master Data — Car (Backoffice)
As a Staff ที่มี permission setting:car:*
I want to เพิ่ม แก้ไข และจัดการข้อมูลรถ
So that ข้อมูลรถในระบบถูกต้องและแสดงผลต่อลูกค้าได้อย่างเหมาะสม
Acceptance Criteria
| # | เงื่อนไข | ผลลัพธ์ที่คาดหวัง |
|---|
| AC1 | เพิ่ม car ใหม่พร้อมข้อมูลครบ | Car ถูกบันทึกและแสดงในระบบ |
| AC2 | ปิดใช้งาน car (inactive) | Car ไม่แสดงในผลการค้นหา |
| AC3 | Staff ไม่มี permission | ปุ่ม action ไม่แสดง, API ตอบ 403 |
| AC4 | Sync ข้อมูล car group กับ CarPro | CarCarProSync ถูกอัปเดต |
As a Staff ที่มี permission setting:promotion:*
I want to จัดการโปรโมชั่น CDP
So that ลูกค้าสามารถใช้โปรโมชั่นได้ถูกต้อง
Acceptance Criteria
| # | เงื่อนไข | ผลลัพธ์ที่คาดหวัง |
|---|
| AC1 | สร้าง promotion ที่ valid | Promotion ถูกบันทึก และ CDP code ส่งไป CarPro ได้ |
| AC2 | Promotion หมดอายุ | ลูกค้าใช้ promotion ไม่ได้, แสดง error IsInvalidPromotionCode |
| AC3 | CDP code ไม่ valid จาก CarPro | Fallback ค้นหาใหม่โดยไม่ส่ง CDP |
| AC4 | Staff ไม่มี permission | API ตอบ 403 |
US-C05: จัดการ Add-on (Backoffice)
As a Staff ที่มี permission setting:add-on:*
I want to จัดการ add-on products และ pricing
So that ราคา add-on ที่ลูกค้าเห็นถูกต้อง
Acceptance Criteria
| # | เงื่อนไข | ผลลัพธ์ที่คาดหวัง |
|---|
| AC1 | เพิ่ม add-on ที่มี PriceRateType = PerCar | แสดงในหน้า booking step 3 พร้อมราคาคงที่ |
| AC2 | เพิ่ม add-on ที่มี PriceRateType = PerDriverUnder21YearsOld | ราคาคำนวณตามจำนวน driver อายุน้อยกว่า 21 |
| AC3 | ตั้ง add-on เป็น IsIncludedItem = true | ไม่คิดราคาเพิ่ม (remark ว่า included) |
| AC4 | ตั้ง add-on เป็น inactive | ไม่แสดงในหน้า booking |
| AC5 | กำหนด ParentId ให้ add-on | add-on เป็น child ของ parent (bundle tree) |
US-C06: จัดการ Branch และ OWR (Backoffice)
As a Staff ที่มี permission setting:branch:* / setting:owr:*
I want to จัดการสาขาและราคา One-Way Rental
So that ลูกค้าเห็นสาขาที่ถูกต้องและราคา OWR ที่แม่นยำ
Acceptance Criteria
| # | เงื่อนไข | ผลลัพธ์ที่คาดหวัง |
|---|
| AC1 | Branch active | แสดงในตัวเลือกสาขาของหน้าค้นหา |
| AC2 | Branch inactive | ไม่แสดงในตัวเลือก / search ไม่ได้ |
| AC3 | กำหนด OWR price ระหว่างสาขา A → B | แสดงราคา OWR ในหน้า quote เมื่อ pickup ≠ return |
| AC4 | ไม่มี OWR setup ระหว่าง A → B | OWR price = 0 หรือไม่แสดง |
Business Rule Summary — Catalog & Search
| ID | Rule | ค่า / สูตร |
|---|
| BR-C-01 | Branch validation ใช้ BranchCarProSync | ต้องพบและ Active |
| BR-C-02 | pricePerDay = TotalAmount / SoldDays | จาก CarPro |
| BR-C-03 | Filter price เทียบกับ pricePerDay | ไม่ใช่ total |
| BR-C-04 | CDP fallback เมื่อ VarianceReason มี "invalid cdp" | ค้นหาซ้ำไม่ส่ง CDP |
| BR-C-05 | Add-on PerCar | ราคาคงที่ × 1 |
| BR-C-06 | Add-on PerDriverUnder21 | ราคา × จำนวน driver อายุ < 21 |
| BR-C-07 | Add-on PerDriverMoreThanOrEqual3 | คิดตั้งแต่คนที่ 3; minimum 1 unit |
| BR-C-08 | Add-on DayRate | × daysRental |
| BR-C-09 | Add-on OneTimeRate | × 1 |
| BR-C-10 | Bundle: Root + All children | คิดแค่ root |
| BR-C-11 | Bundle: Root + Some children | root + selected |
| BR-C-12 | IsIncludedItem = true | ไม่คิดราคา |
| BR-C-13 | MaximumRate cap | cap ที่ maximum เมื่อคำนวณเกิน |
| BR-C-14 | Car VAT | totalIncVAT − (totalIncVAT / 1.07) |
| BR-C-15 | Add-on VAT | totalAddon × 0.07 |
| BR-C-16 | Grand total | carIncVAT + addon + addonVAT |
| BR-C-17 | Rounding | 2 ตำแหน่ง MidpointRounding.AwayFromZero |