# Business rules — prsa_cio

Evidence date: 2026-10-06.

Source: `git@bitbucket.org:overdrive/prsa_cio.git`, `master` at `2832986` (2023-09-20).

No business-rules document exists in the source repository. Rules below are taken from code. Each rule is labelled **Inferred** when the code behaves that way and nobody wrote it down as policy. Nothing here is an approved rule unless a provenance line says otherwise.

There is no separate documented-requirement source. Git commit subjects are the only short statements of intent: "added landing page with tabs + file upload" and "adding API and BL functionality to upload Tracker sheet from excel".

## Identity

**Inferred.** The product is presented as PRSA - CIO on the login page and as a reporting system on `/`. The meaning of the name is **Unknown / Requires Confirmation**.

## Who may use the system

**Inferred** from the absence of auth middleware configuration beyond `UseAuthorization()` with no authentication scheme:

- Any caller who can reach the web host can open `/`, `/Login`, and `/Privacy`.
- Any caller who can reach the API can read all tracker rows and can ask the server to open a local Excel path.
- IIS Express profiles set anonymous authentication on and Windows authentication off. That is a local launch setting, not a proof of production IIS.

`Core.users.active` and `role_id` are not consulted. **Inferred:** the database may have been intended to hold users. The running code does not.

## Sign-in

**Inferred** from `Login.cshtml`:

- The screen collects an email-looking value and a password-looking value.
- Those inputs are not named, so a normal form post does not bind them.
- Sign In does not call `LoginModel`. The button posts to `/Index`.
- No rule rejects an unknown user, an inactive user, or a bad password, because no check exists.

**Unknown / Requires Confirmation:** whether sign-in was supposed to use `Core.users.ad_token` (the column name is the only Active Directory evidence).

## Reporting tabs

**Inferred** from the hard-coded list:

- The reporting screen always offers four tabs, in this order: Tracker, SalesForecast, StockBalance, QlikviewSales.
- The tab names are not read from the workbook and do not change after upload.
- The tables do not show rows. Commented script would have loaded them. That load is not a rule in force.

## Browser file upload

**Inferred** from `OnPostAsync` and the form:

- The browser is asked to prefer `.xlsx`. The server does not reject other types.
- A post with a non-empty file is treated as success even though the bytes are discarded.
- A post without a file does not show an error.
- The success text is "File uploaded successfully!" This is not the same sentence as the API success text.

## Tracker workbook import

These rules are **Inferred** from `FileUploadService.UploadTrackerSheet`. They run only when something calls the API. The web page does not.

- The file must already exist at `filePath` on the API machine. The API does not receive the workbook body.
- Only the first worksheet is imported.
- Rows 1 through 8 are not imported. The last eight rows of the used range are not imported. The business meaning of that header or footer is **Unknown / Requires Confirmation**.
- If the used range has fewer than 17 rows, the loop adds nothing, `SaveChanges` still runs, and the API still reports success.
- Each imported row is a new `tracker` row. Previous imports stay. Re-uploading the same file duplicates rows.
- `brand_group`, `item_description`, `pack_size`, and `stocking_type` are stored as the cell's text.
- `item_number` must be an integer. A non-integer fails the import.
- Numeric cells:
  - Text that contains `-` is stored as 0. A negative written with a leading minus therefore becomes 0, not a negative value.
  - Otherwise, text that contains a parenthesis is stored as the negative of the number with parentheses removed.
  - Percent columns (`perc_sold_incl`, `perc_sold_excl`) have `%` removed. The remaining number is stored as-is. "50%" becomes 50, not 0.5, if conversion succeeds.
  - Blank stock cover becomes 0. A blank in the other numeric columns is not given that exception and fails conversion.
- The actor recorded on the row is always user id `1`. The clock is the API server's local time.
- On a caught failure the caller still receives HTTP 200 and the text "An error has ocurred while trying to upload your file. Please contact your System Administrator. System Exception: " plus the exception message. The typo "ocurred" is in the source.
- If opening the workbook throws, the controller returns HTTP 400 and the exception message only.

String length limits from the EF model (**Implemented** as column configuration, **Inferred** as a rejection rule because SQL Server will reject an overflow): `brand_group`, `item_description`, `pack_size`, and `stocking_type` are required `varchar(255)`. Decimal columns on `tracker` are `decimal(18, 2)`.

## Tracker read

**Inferred** from `GetTrackerData`:

- The read returns every tracker row. There is no date filter, brand filter, or "latest upload" rule.
- Brand, FY-1, and RF1 are stored and are not returned by this endpoint.
- A database error becomes HTTP 400 with the exception message.

## Other import tables

**Inferred** from names and from the lack of writers:

- Tables `le3`, `mapping`, `master_data`, `open_orders`, `qlikview_sales`, `sales_forecast`, and `stock_balance` exist in schema `Imports`.
- `open_orders.tracker` and `stock_balance.tracker` are integers. The EF model does not declare a foreign key to `tracker.id`. Whether they must match a tracker row is **Unknown / Requires Confirmation**.
- `sales_forecast.processed` is a boolean. No code sets it.
- `mapping` carries warehouse (`wh`), two region fields, `bond_duty`, `rev_sa`, and `rev_nam`. No code interprets them.
- No rule in this repository loads those tables from Excel or from QlikView.

## Settings

**Inferred.** `Core.settings` stores `key_name` and `value`, both required, max length 255. No key is read. Which settings exist in a real database is **Unknown / Requires Confirmation**.

## Pipeline rule

**Implemented** in YAML, not a domain rule: analysis runs for `master` and `development` using Sonar project key `overdrive_prsa_cio_AYpSpeQrQY-eSvjrqMo7`. Tokens are `${SONAR_TOKEN}` and `${SONAR_HOST_URL}`. The YAML does not define a quality gate that fails the pipeline.

## Rules that need a human decision

- Should a tracker upload replace the table or append?
- Are the skipped Excel rows a fixed header and footer, and is that true for every workbook?
- Should a hyphen mean zero, or should a leading minus stay negative?
- Should percents be stored as 50 or as 0.5?
- Is user id `1` a real system user?
- Is the browser page allowed to claim success when the file is discarded?
- Who is allowed to call an endpoint that opens an arbitrary server path?
- Are the unused `Imports` tables in scope for this application or loaded by another process?
- What do PRSA and CIO stand for, and who is the user?
