93 lines
4.9 KiB
Markdown
93 lines
4.9 KiB
Markdown
# Business Rules
|
|
|
|
## Fixed processing order
|
|
|
|
1. Validate the invocation and parse one fixed `RES_DETAIL` XML.
|
|
2. Require exactly one XML business date and keep one audit record for every `G_RESERVATION` in XML order.
|
|
3. Require `RATE_CODE` so whitelist membership is knowable.
|
|
4. Classify a trimmed, uppercased rate code outside the whitelist as `excluded_rate_code`.
|
|
5. Validate every whitelist candidate; classify invalid rows as `validation_failed`.
|
|
6. Deduplicate valid candidates by `DISP_ROOM_NO + ARRIVAL`; retain the first XML occurrence and classify later occurrences as `duplicate` pointing to the first source sequence.
|
|
7. Compute integer `NIGHTS = DEPARTURE - ARRIVAL`. Zero is legal; negative is invalid.
|
|
8. Validate the complete fixed price table, then apply the approved zero-price exception or exact three-key match. Classify unmatched candidates as `price_unmatched`.
|
|
9. Assign `channel_key` and conditional `kb_amount` as row facts.
|
|
10. Generate the 19-column daily XLSX, `result.json`, and path-free `structured-result.json` from the same in-memory records.
|
|
11. Independently replay the XML, rules, prices, routing, workbook, artifacts, outcomes, and structured records before reporting success.
|
|
|
|
Any row-level validation or price error fails the formal invocation. On failure, already excluded, duplicate, or price-unmatched outcomes remain explicit. Other candidates that did not reach a validated result become `validation_failed` with `BATCH_NOT_VALIDATED`.
|
|
|
|
## Rate-code whitelist
|
|
|
|
`GRPA1`, `GRPA2`, `GRPA3`, `GRPA4`, `GRP1`, `WHO1`, `WHO2`, `WHO3`, `WHO4`, `LTLT`, `LBLT`, `LBSM`, `LBMS`, `LBW1`, `LBKB`, `LBLS`, `WHKR2100B`, `GL2100B`, `GL2200KR`, `GLSPCB`.
|
|
|
|
## Reservation text
|
|
|
|
For reservation-level `RES_COMMENT` and `TRACE_TEXT`, take the first non-empty value in XML order. Leave blank when every candidate is empty. Never concatenate values or expand one reservation into multiple rows.
|
|
|
|
## Company normalization
|
|
|
|
Normalize company identity for price matching and channel facts:
|
|
|
|
1. trim, uppercase, and keep only `A-Z` and `0-9`;
|
|
2. use fixed substring keywords:
|
|
- `LIANTAI` → `LIAN TAI`
|
|
- `QBD` → `QBD`
|
|
- `RAINBOW` → `RAINBOW/AI`
|
|
- `FENGRUN` → `FENGRUN`
|
|
- `HANATOUR` or `HANA` → `HANA TOUR`
|
|
- `HONGTAI` → `HONGTAI`
|
|
- `GUANGZHOUGOEASY` or `GOEASY` → `GUANGZHOU GO EASY`
|
|
3. when no keyword matches, use the compact normalized name;
|
|
4. when more than one keyword group matches, fail instead of guessing.
|
|
|
|
This is deterministic keyword matching, not edit distance.
|
|
|
|
## Pricing
|
|
|
|
Use bundled `价格对照.xlsx` as a fixed, non-user-editable rule source.
|
|
|
|
- Normalize `RATE_CODE` with `upper(trim(value))`.
|
|
- Compare amounts numerically, so `900` equals `900.00`.
|
|
- Reject negative reference amounts/totals, invalid rows, ambiguous companies, and every duplicate normalized `COMPANY + RATE_CODE + Opera amount` key.
|
|
- Before the normal lookup, set numeric `REAL PRICE = 0` only when:
|
|
- company identity is `RAINBOW/AI` or `GUANGZHOU GO EASY`; and
|
|
- rate code is `LBMS` or `LBSM`.
|
|
- The exception ignores `EFFECTIVE_RATE_AMOUNT`.
|
|
- Every other retained row must exactly match normalized `COMPANY_NAME + RATE_CODE + EFFECTIVE_RATE_AMOUNT`.
|
|
- Write reference `总价` as static `REAL PRICE`.
|
|
- Write static `TOTAL PRICE = REAL PRICE * NO_OF_ROOMS * NIGHTS`.
|
|
- An unmatched candidate fails the entire invocation.
|
|
|
|
## Channel facts
|
|
|
|
Keep original XML `COMPANY_NAME`; assign only the derived `channel_key`.
|
|
|
|
| Condition | `channel_key` |
|
|
|---|---|
|
|
| `QBD` | `QBD` |
|
|
| `LIAN TAI` and rate in `{LBLT, LTLT}` | `LIANTAI-FIT` |
|
|
| `LIAN TAI` and another whitelisted rate | `LIANTAI-GROUP` |
|
|
| `RAINBOW/AI` or `GUANGZHOU GO EASY`, any rate | `DY-AI-Easy-KB` |
|
|
| `FENGRUN` | `FENGRUN` |
|
|
| any other company | sanitized actual company name |
|
|
|
|
For other companies, trim, remove `: \ / ? * [ ]`, and truncate to 31 characters. Resolve different-company collisions with `-2`, `-3`, and so on. Set `kb_amount = NO_OF_ROOMS * 100` only for `DY-AI-Easy-KB`; do not multiply by nights and do not add it to `TOTAL PRICE`.
|
|
|
|
## Business date, XLSX, and types
|
|
|
|
- Derive the business date only from XML group dates, never runtime time or filename.
|
|
- Daily filename and sheet name: `M.D.xlsx` and `M.D`.
|
|
- Preserve retained XML order.
|
|
- Use the bundled daily template; do not add monthly or channel worksheets.
|
|
- Headers occupy row 1; data begins row 2.
|
|
- Write dates as real Excel dates with `DD-MMM-YY`.
|
|
- Write counts and prices as static numbers, never formulas.
|
|
- Write identifiers and descriptive fields as text.
|
|
- Require `ADULTS >= 0`, `CHILDREN >= 0`, `NO_OF_ROOMS > 0`, `EFFECTIVE_RATE_AMOUNT >= 0`, and `DEPARTURE >= ARRIVAL`.
|
|
- `DEPARTURE = ARRIVAL` yields `NIGHTS = 0` and `TOTAL PRICE = 0`.
|
|
- Preserve allowed blank fields and complete selected comment/trace text.
|
|
|
|
## Prohibited behavior
|
|
|
|
Do not access OSS, embed credentials, generate/update a monthly workbook, query/write a database, infer Group Code from `BLOCK_CODE`, or derive structured facts by reopening the generated XLSX.
|