# Business rules — PernodIntegration

Evidence date: 2026-10-07.

Source: `main` at `ecbc272`.

Rules are taken from `docs/integration-flow.md` and from `Module1.vb`. They are **Inferred** unless the flow doc or a comment states them. They are not a signed business-rules catalogue.

Status meanings below are the meanings this job uses. The staff portal's older script comment (Silicon Ark `pernodstaff` BR-024) says status `20` means processed successfully. This repository's flow doc agrees that `20` means the JDE HTTP call returned 2xx. A full status catalogue for `PSOrder` is not in this repo.

## Status

| ID | Rule | Provenance |
|---|---|---|
| BR-001 | Status `99` is unresolved and is the only status this job posts. | **Documented** and **Implemented** |
| BR-002 | Status `20` means the orchestrator HTTP status was 2xx. The job does not read the JSON body. A 2xx body that reports a JDE business error is still stored as `20`. | **Documented** and **Implemented** |
| BR-003 | Status `22` means the job will not send the order: it was a prior-period leftover, or it was a current-month failed order cancelled so a correction would fit under 12 bottles. | **Documented** and **Implemented** |
| BR-004 | Status `20` is not changed by expiry or by the overflow update. The overflow update only touches rows that are still `99`. | **Documented** for status `20` on expiry. **Implemented** for both updates |
| BR-005 | A failed send that still fits under 12 stays `99` and is how the job records "this order has an error", via an `ERROR_LOG` row with `orderid`. | **Documented** and **Implemented** |

## Calendar month

| ID | Rule | Provenance |
|---|---|---|
| BR-006 | The period is the calendar month of `ORDER_HEADER.Orderdate`, from month start inclusive to next month start exclusive. | **Documented** and **Implemented** |
| BR-007 | Month start is `DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)` in SQL Server. | **Implemented** |
| BR-008 | At the start of every run, status `99` with `Orderdate` before month start is set to `22`. | **Documented** and **Implemented** |
| BR-009 | If that update throws, the error is logged and the run continues. Those prior-month rows can then be posted. | **Implemented**. The flow doc's stated intent is that they are never posted. **Conflict — Requires Confirmation** |

## Twelve-bottle rule in this job

| ID | Rule | Provenance |
|---|---|---|
| BR-010 | The cap is 12 bottles. Bottles are `SUM(ORDER_DETAIL.Quantity)`. | **Documented** and **Implemented** (`MonthlyBottleCap`) |
| BR-011 | The cart-time cap is not applied here. This job only adjusts header status and `ERROR_LOG`. | **Documented** |
| BR-012 | Counted bottles for a user are current-month lines whose header status is not `22` and whose order id has no `ERROR_LOG` row. | **Implemented**. The flow doc also describes this set as pending `99` and successful `20`. The SQL includes any other non-`22` status with no error row |
| BR-013 | Failed bottles are current-month status `99` with at least one `ERROR_LOG` row for that order id, oldest `Orderdate` then `Orderid`. | **Documented** and **Implemented** |
| BR-014 | The error-row test is `orderid` only. It does not require component `PernodIntegration` or operation `CreateOrder`. | **Implemented**. Silicon Ark `pernodstaff` BR-023 requires both. **Conflict — Requires Confirmation** |
| BR-015 | If counted + failed is greater than 12, the oldest failed headers are set to `22` until the remainder would fit. Newer failed orders that still fit stay `99`. | **Documented** and **Implemented** |
| BR-016 | Worked examples, also covered by tests: failed 8 + new 6 cancels the failed order and sends the new one; failed 8 + new 3 sends both; failed 4, later failed 4, and new 6 cancels only the oldest failed order. | **Documented** and **Implemented** |
| BR-017 | Overflow runs once per distinct positive `userid` that still has a status `99` after expiry, and again after a `CreateOrder` failure for that user. | **Documented** and **Implemented** |
| BR-018 | A missing, blank, or non-positive `userid` skips overflow. The order can still be posted. | **Implemented** |
| BR-019 | A successful current-month order with no error row counts toward the 12. Cancelling failed orders does not reduce that counted total. | **Inferred** from the counted query (status not `22`, no error row) |

## What the next run does

| ID | Rule | Provenance |
|---|---|---|
| BR-020 | Status `20` is skipped. Status `22` is skipped. | **Documented** and **Implemented** |
| BR-021 | Current-month status `99` with an error row is retried unless overflow sets it to `22`. | **Documented** and **Implemented** |
| BR-022 | Current-month status `99` with no error row is posted. | **Documented** and **Implemented** |
| BR-023 | Prior-month status `99` is expired to `22` at the start of the run when the expiry statement succeeds. | **Documented** and **Implemented** |

There is no maximum retry count and no backoff. Provenance: **Inferred** from the absence of either.

## Payload rules

| ID | Rule | Provenance |
|---|---|---|
| BR-024 | `CustomerId` is sent as `Long Address Number`. `BusinessUnit` is sent as `Business Unit`. | **Implemented** |
| BR-025 | Each detail line sends trimmed `clientcode` as `Item Number` and trimmed `Quantity` as `Quantity Ordered`. | **Implemented** |
| BR-026 | Line type `Ln Ty` is always `S`. | **Implemented**. A comment says the default is `S`. What `S` means in JDE is **Unknown** |
| BR-027 | The AIS token in the body is the token from the start of the process. The `Authorization` bearer is a separate OAuth token fetched during that order. | **Documented** and **Implemented** |
| BR-028 | Device header `jde-AIS-Auth-Device` is the literal `BRIDGE` on both the token call and the order call. Config `JdeDeviceName` is sent only inside the token JSON body. | **Implemented**. The flow doc mentions `deviceName` from config and does not mention `BRIDGE` |

## Error log rules

| ID | Rule | Provenance |
|---|---|---|
| BR-029 | Component is always `PernodIntegration`. `useremail` is always null. | **Documented** and **Implemented** |
| BR-030 | `orderid` is stored for `CreateOrder` failures when the id parses as an integer greater than 0. Token, expiry, and overflow log rows do not set `orderid` (overflow mentions the user id in the message). | **Implemented** |
| BR-031 | JSON `token`, JSON `access_token`, and `Bearer` tokens are redacted before insert. | **Documented** and **Implemented** |
| BR-032 | A failure to write `ERROR_LOG` must not abort the batch. | **Documented** and **Implemented** |
| BR-033 | Message is stored up to 4000 characters and exception message up to 2000. Stack trace is stored without a length cut in code. | **Implemented**. The flow doc says long fields are truncated, which overstates the stack-trace behaviour |

## Explicitly not a rule of this program

- Who may place a staff order, the Employee# check, the 17:00 cutoff, VAT, and the cart cap. Those are documented for `pernodstaff`, not implemented here.
- Whether status `22` still counts toward the portal's monthly 12. This job excludes `22` from its own sum. The portal rule on record does not say it excludes `22`. **Requires Confirmation.**
- That HTTP 2xx means JDE accepted the sale as a business document. The code treats 2xx as sent.
