Files
wyndham-ARR/.project-docs/40-domain/business-rules.md

77 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Business Rules
## Durable Rules
- Human access to the desktop Finance workspace, detailed health, generic business APIs, uploads, traces and downloads
requires an authenticated ARR Web session. The H5 mobile dashboard is an intentional anonymous read-only exception:
only its page/assets, `/api/public/h5/months`, `/api/public/h5/analytics` and no-detail `/healthz` are public; the H5
projection contains aggregate channel/room metrics and omits source hashes and operational metadata.
- The single-operator login identity is injected as `ARR_WEB_USERNAME` / `ARR_WEB_PASSWORD`; real credentials are never stored in source or returned/logged. Missing credentials fail Web startup closed.
- Login failures use a generic message and bounded attempt blocking. Logout revokes the server-side session; authenticated mutations continue to require CSRF.
- The user uploads XML; the user does not submit a report year, report month, cutoff date, or separate monthly-generation request in the primary workflow.
- Daily history and task trace show the validated browser-uploaded XML basename. Canonical internal source name
`source.xml` is not user-facing; historical rows with no recorded basename display an unknown marker rather than a
guessed filename.
- Daily, monthly-publication and company-generation histories have independent viewing months and default to the most
recent month containing that record type. Viewing-month state is read-only: it resets list paging but never changes
monthly derivation or a company generation request. Company `生成月份` and history `查看月份` are separate inputs.
- ARR's frozen processor generates complete structured data, which must be independently validated and successfully committed to the database before downstream monthly processing starts.
- The production XML path must not require Agent, MCP, prompts, model-generated JSON, fetch grants, or a public source URL.
- A successful database commit automatically triggers the monthly-report processing program through the business system.
- Automatic monthly processing runs in a dedicated worker process, not inside the Web request process.
- The worker derives the affected month from retained `ARRIVAL` facts belonging to the committed daily version.
- The monthly `as_of_date`/“更新至” is the greatest `ARRIVAL` actually included in the current monthly snapshot. The XML filename and wall-clock date are never authorities for it.
- Year, month, cutoff/as-of date, and similar fields are internal derived execution/audit values. Their presence in code or metadata does not make them user inputs.
- The monthly output contains the exact business dimension/field `TOTAL PRICE`.
- Each monthly XLSX data-row `TOTAL PRICE` cell is a formula whose business meaning is `REAL PRICE × NIGHTS × NO_OF_ROOMS` (current layout example: `=R2*C2*G2`).
- A stored database `total_price` may be used for integrity checking, but it does not replace the required formula in the exported workbook.
- Monthly business/guest rows remain derived from current Finance facts. PostgreSQL persists only publication identity, lineage, channel counts and artifact metadata.
- The outbox event is marked published only after a report is active/superseded and both workbook/result artifacts are registered; replay of the same snapshot must not create a duplicate publication.
- A published monthly version automatically appears in the open monthly list; the primary workflow has no manual list-refresh step.
- Company-channel detail generation is allowed for any period in the selected current month and for historical months. A
future report month is rejected so the system does not create an intentionally empty future workbook.
- Company-channel C/O periods keep fixed report cutoffs: `01-10` uses the 10th, `11-20` uses the 20th and
`21-month-end` uses the selected month's natural month-end. The workbook uses the current committed Finance snapshot
through that cutoff even when the calendar period has not ended; later-arriving facts require a rerun to be included.
- The company-channel UI labels calendar status as `周期未结束` or `周期已结束`. These labels describe whether the
selected period has reached its Bangkok completion boundary; they are not generation permission states.
- In a company-channel detail workbook, a Finance fact with no normalized Group Code remains visible as its own row;
`RES_COMMENT` and `Booking Room` are blank, and this expected blank does not create a warning or duplicate highlight.
- A Finance stay segment with a consistent nonblank Group Code keeps that Group Code and its normal aggregation. When
no current accepted Booking room can be resolved, only `Booking Room` is blank; the lookup miss does not create an
error or warning and does not block the company's workbook.
- The company-report Booking allocation model consists of normalized Group Code, room type and quantity. A raw intake
adapter may derive these fields from a source workbook, but raw workbook columns and normalized result dimensions are
separate contracts; worksheet, physical row, artifact hash and parse version remain provenance.
- A raw Booking workbook is recognized only on a worksheet containing one Tour Code/Group Code header and the exact
Thai `โรงแรม` header. Tour Codes are Unicode-normalized and all whitespace is removed before matching.
- For repeated Tour Codes, the physically last source row replaces earlier rows. A final row with an explicit textual
cancellation removes the Tour Code; cell color or other formatting alone never means cancellation.
- Room extraction reads parenthesized hotel segments. A `【label】` token takes the following integer as quantity and
defaults to one when no following integer exists. Multiple tokens become separate items even when they normalize to
the same room type.
- Numeric suffix variants of `U-TWN` and `U-DBL` normalize to `U-TWN` and `U-DBL`; `高级房TWN`/`高级房DBL` normalize
to `TWN`/`DBL`. Unknown bracket labels retain their raw text and parsed/default quantity as pending. Recognized
unbracketed names such as `6+4`, `Family Suite`, `Junior Suite` and `Superior Garden` also remain pending because the
source omitted brackets. Surcharge, child-breakfast and guide-room tokens are not guest-room allocations.
- Pending items do not contribute accepted Booking room quantity. Automatic and manual rows are both editable and
deletable; activation requires zero pending items and at least one confirmed item. Re-extraction rebuilds the review
draft from source and intentionally discards draft-only edits/deletions.
- The review UI pages at 50 records. A delete request contains 1-50 unique item IDs and is all-or-nothing: every item
must still be undeleted, belong to the same reviewing draft and be deletable, otherwise the transaction rolls back.
- An open Booking review displays the draft artifact's validated browser-uploaded filename directly below
`Booking记录提取`. Accepted historical-source readiness is not shown as provenance for draft extraction records.
- Once accepted, a manually uploaded Booking workbook replaces the complete current source rather than appending
silently to the previous workbook. Migrations 014/015 and the HTTP GET/POST/PATCH/DELETE adapter represent this
boundary; the first real draft activation remains a controlled business acceptance action.
- Booking normalizes the complete Group Code and globally aggregates repeated `(Group Code, room type)` rows by summed
quantity. Worksheet, company/channel and stay dates are not current Booking join dimensions. This contract therefore
requires one Group Code to identify one room allocation across those scopes; a different business reality requires a
schema/view/report-join change, not merely extra unused Excel columns.
- Contradictory Group Code source fields and invalid Finance dates, nights or price details remain strict company-report
errors; the blank fallback applies only to absent or unresolved Booking enrichment.
## Last Reviewed
2026-07-31