# TSHIRTORDER-1634 — Prepaid changes: API for Frontend

One section per mockup in `files/`. All endpoints are under `/api/v1`, with `Authorization: Bearer <token>` as usual.
Background and business rules: `prepaid-changes.md` in this folder.

---

## 0. Common: the prepaid fields

The backend adds the same 5 fields to **every order** (detail, lists, the response of create/update) and **every invoice** (detail, list):

| Field | Type | Meaning |
|---|---|---|
| `prepaidActive` | bool | The channel of this row has prepaid **on**. `false` → show nothing prepaid-related for this row |
| `prepaidCurrentSum` | number \| null | **Remaining prepaid sum of the channel** (not the amount of this order/invoice). `null` when `prepaidActive = false` |
| `prepaidLow` | bool | `prepaidCurrentSum` < `prepaidWatcherLevel` of the channel → show the sum in **red** |
| `prepaidExcluded` | bool | The order's `dateOrder` (invoice: `dateInvoice`) is **on or before** the channel's `prepaidDate` → **not included** in prepaid. Show a text instead of the sum, e.g. *"Ingår ej i prepaid"* |
| `prepaidGuestVisible` | bool | = `guestLoginShowPrepaid` of the channel. For users with `role = "ROLE_CHANNEL"` (guest accounts), show the prepaid info only when this is `true` |

Display rule used everywhere:

```
if (!row.prepaidActive)                                       → show nothing
else if (user.role === 'ROLE_CHANNEL' && !row.prepaidGuestVisible) → show nothing
else if (row.prepaidExcluded)                                 → "Ingår ej i prepaid"
else                                                          → formatKr(row.prepaidCurrentSum), red if row.prepaidLow
```

Notes:
- Every row of the same channel returns the **same** `prepaidCurrentSum` (boss decision: the column shows the remaining sum, not a per-row amount).
- The sum can be **negative** (orders are never blocked when the prepaid runs out).
- Amounts are incl. moms, rounded to 2 decimals.
- *Deducted* = channel has `prepaidActive = true` **and** `dateOrder` is after the end of the `prepaidDate` day (`prepaidDate` date + 23:59:59). A `Credit` (table `credits`) of a deducted order is added back. All calculated by the backend.

---

## 1. Channel → Prepaid tab — `files/01-channel-prepaid-tab.jpg`

Mockup: *"Add prepaid on / off"*, *"Add payment ID, count up so we see each transaction"*, *"Status = on / off"*.

### 1a. On/off switch (= "Status on/off", for the whole prepaid module)

Read — `GET /channels/{id}`:

| Field | Type | Change |
|---|---|---|
| `prepaidActive` | bool | **New** |
| `prepaidCurrentSum` | number | **Changed**: now calculated live when `prepaidActive = true` (before it was a stale snapshot). Use it for *Tillgänglig summa inkl moms* |
| `prepaidLow` | bool | **New**. `true` (`prepaidCurrentSum` < `prepaidWatcherLevel`) → show *Tillgänglig summa* in red |
| `prepaidSum`, `prepaidWatcherLevel`, `prepaidDate`, `guestLoginShowPrepaid` | | Unchanged |

Write — `POST /channels/{id}` (existing channel save), send:

```json
{ "prepaidActive": true }
```

- When `prepaidActive = false`: nothing is deducted, orders do not get the Förskott `paymentTypeId`, and every order/invoice of the channel returns `prepaidActive = false`.
- ⚠️ `prepaidSum` and `prepaidCurrentSum` are now **ignored** by `POST /channels/{id}`. `prepaidSum` can only change through the *Lägg till summa* button (1b), so every change has a row in `prepaidLogs`.

### 1b. "Skicka" → "Lägg till summa" (additive transactions + Payment ID)

**Rename the button to `Lägg till summa`.** It no longer replaces the total: the admin types the amount **received** and it is **added**.

`POST /channels/{id}/get-prepaid-current-sum` (same URL, new body):

| Body field | Type | Change |
|---|---|---|
| `amount` | number | **New.** Amount added to `prepaidSum`, incl. moms. May be negative to correct a mistake. Empty/0 = only save `prepaidWatcherLevel` / `prepaidDateString` |
| `paymentId` | string | **New.** Free text typed by the admin (e.g. bank reference), to find the payment later |
| `prepaidWatcherLevel` | number | Unchanged |
| `prepaidDateString` | `"YYYY-MM-DD HH:mm:ss"` | Unchanged |
| ~~`prepaidSum`~~ | | **Removed / ignored.** Do not send the total any more |

Form changes:
- Field *Förinbetalt belopp inkl. moms* → becomes the **amount to add** (label e.g. *"Belopp att lägga till inkl. moms"*), empty after each submit.
- Add a text field **Payment ID**.
- Show the current total (`prepaidSum` from `GET /channels/{id}`) as read-only text if you want.

Response `200`:

```json
{
  "current_prepaid_sum": 13750,
  "channel_prepaid_sum": 15000,
  "order_sum": 1000,
  "order_tax": 250,
  "total_credited": 0,
  "prepaid_low": false
}
```

After success, reload `GET /channels/{id}` (`prepaidLogs` + `prepaidCurrentSum`) and `GET /channels/{id}/prepaid-orders` (ticket 1633).

- One `ChannelPrepaidLog` row (table `channels_prepaid_logs`, returned in `prepaidLogs[]`) is saved per real change: when `amount` ≠ 0, or `prepaidWatcherLevel` / `prepaidDate` changed. Clicking with nothing changed saves no row.
- This button **no longer sends the low-balance e-mail**. The e-mail is sent automatically once a day by a cronjob.

### 1c. History table `prepaidLogs` (right side of the mockup)

`GET /channels/{id}` → `prepaidLogs[]` (entity `ChannelPrepaidLog`, table `channels_prepaid_logs`), now sorted by `dateCreated` **newest first**:

| Field | Type | Change / column |
|---|---|---|
| `id` | int | |
| `amount` | number \| null | **New.** Column *"Belopp"*: amount added by this transaction (0 = only `prepaidWatcherLevel` / `prepaidDate` changed) |
| `paymentId` | string \| null | **New.** Column *"Payment ID"* |
| `prepaidSum` | number | Total **after** this transaction (column *Förinbetalt belopp inkl. moms*) |
| `prepaidWatcherLevel` | number | Unchanged |
| `prepaidCurrentSum` | number | Available sum at that moment (column *Tillgänglig summa*) |
| `prepaidDate` | datetime | Unchanged |
| `dateCreated` | datetime \| null | Transaction time (old rows may be `null`) |

Old rows were migrated: `amount` = `prepaidSum` of that row − `prepaidSum` of the previous row (old "Skicka" clicks that only recalculated have `amount = 0`).

---

## 2. Orders → Skapa ny — `files/02-order-create.jpg`

Mockup: *"If select channel prepaid = on show sum"* (empty box next to *Kvar att betala*).

### 2a. When a Kanal is selected

`GET /channels/{channelId}` → use `prepaidActive`, `prepaidCurrentSum`, `prepaidLow` (see 1a).

- `prepaidActive = true` → show in the box: **`Prepaid: {prepaidCurrentSum} kr`**, red if `prepaidLow`.
- `prepaidActive = false` → keep the box empty.
- Users with `role = "ROLE_CHANNEL"`: only when the channel has `guestLoginShowPrepaid = true`.

### 2b. After clicking Skapa — warning popup

`POST /orders/` (unchanged request). The response is the new order and contains the prepaid fields (section 0), **already including this new order** in the sum.

Show a popup **every time** an order is created when:

```
res.prepaidActive && !res.prepaidExcluded && res.prepaidLow
```

Example text: *"Prepaid-saldot för kanalen är lågt: {prepaidCurrentSum} kr"*.

### 2c. Betalningstyp

Nothing to send. If the order is deducted from prepaid, the backend always sets `paymentTypeId` = Förskott (see section 3), whatever `paymentTypeId` is sent and whatever the `defaultPaymentTypeId` of the customer/channel is.

---

## 3. Order detail → Betalningstyp — `files/03-order-detail-betaltyp.jpg`

Mockup: *"If created on channel prepaid = on change status on betaltyp to prepaid"*.

`GET /orders/{id}`: for an order deducted from prepaid, `paymentType = "Förskott"` and `paymentTypeId` = id of the `status_list` row with `type = "order_payment_type"`, `uniqueKey = "order_payment_type_prepaid"` (boss: *Förskott = Prepaid*, no new type). The id differs between environments; if FE needs it, find it by `uniqueKey` via `GET /status-list?type=order_payment_type`.

**Lock the dropdown** (boss: *"should not be able to change if prepaid is used"*):

```
const paymentTypeLocked = order.prepaidActive && !order.prepaidExcluded
```

- Locked → Betalningstyp dropdown **disabled**, optional hint *"Betalas med prepaid"*.
- `POST /orders/{id}` still accepts the full form. Sending the **same** `paymentTypeId` is fine. Sending a **different** one returns:

```json
// 400
{ "message": "Payment type cannot be changed on an order paid by prepaid" }
```

- Orders with `prepaidExcluded = true` and orders on a channel with `prepaidActive = false` stay editable as before.

---

## 4. Order detail → prepaid sum — `files/04-order-detail-sum.jpg`

Mockup: *"And show sum prepaid here"* (box next to *Skapad av / Skapad datum*).

`GET /orders/{id}` → fields from section 0. Apply the display rule:

| Case | Box shows |
|---|---|
| `prepaidActive = false` | nothing (hide the box) |
| `prepaidExcluded = true` | *"Ingår ej i prepaid"* |
| otherwise | *"Prepaid: {prepaidCurrentSum} kr"* (remaining sum of the channel), red if `prepaidLow` |

After saving the order (`POST /orders/{id}`), the response has the updated fields — refresh the box from it.

---

## 5. Invoice detail — `files/05-invoice-detail.jpg`

Mockup: *"Add prepaid sum - here If its activated"*, *"Prepaid = 10 kr"* in the *Betalningsstatus / Totalt betalt / Kvar att betala* bar.

### 5a. Prepaid box

`GET /invoices/{id}` → fields from section 0 (for invoices, `prepaidExcluded` is based on `dateInvoice`). Same display rule as section 4; put the box between *Betalningsstatus* and *Totalt betalt*.

⚠️ **Information only.** Prepaid does **not** change *Totalt betalt*, *Kvar att betala* or *Betalningsstatus* (boss: *"prepaid is only calculated on order"*).

### 5b. Marking the invoice as paid by prepaid

Boss: *"in invoice we need to manually add down payment"*. Use the existing **Lägg till inbetalning** button (creates a `DownPaymentRow`, table `down_payment_rows`, through the `/down-payment-rows/...` endpoints already in use), no new endpoint. A new `status_list` row was added with `type = "down_payment_row_type"`, `name = "Prepaid"`:

```
GET /status-list?type=down_payment_row_type
```

→ contains `{ "name": "Prepaid", "uniqueKey": "down_payment_row_type_prepaid", ... }`.

- If the type dropdown of *Lägg till inbetalning* is loaded from this endpoint, "Prepaid" appears automatically. If it is hard-coded, add it (find it by `uniqueKey`, the `id` differs between environments).
- Saving works exactly like the other types (send its `id` as `typeId`). This down payment reduces *Kvar att betala* but does **not** reduce the prepaid balance again.

---

## 6. Orders → Att fakturera löpande — `files/06-order-list-att-fakturera.jpg`

Mockup: *"Add column prepaid sum if channel is turn on"* (new column between *Bulkorder* and *Åtgärder*).

`GET /orders/?orderStatusId=22&invoiceIndividual=yes` (unchanged) → each `items[]` row has the fields from section 0.

- Column header: **Prepaid**.
- Show the column when **at least one row** has `prepaidActive = true` (otherwise hide it).
- Cell: display rule from section 0 (empty for rows whose channel has `prepaidActive = false`).
- The backend calculates the sum once per channel per request, so there is no extra call per row.

---

## 7. Ekonomi → Fakturor — `files/07-invoice-list.jpg`

Mockup: *"Invoice list show if channel is turn on prepaid"* (new column between *Betalningsstatus* and *Kvar att betala*).

`GET /invoices/?not_invoiceJournal_id=yes` (unchanged) → every invoice in the array has the fields from section 0.

- Column header: **Prepaid**; same rules as section 6 (show the column if any row has `prepaidActive = true`, cell by the display rule).
- Invoices with `createdFrom = "credit"` (column *Skapa från = Kredit*) follow the same rule (they show the channel's remaining sum too).

---

## Summary of breaking changes

| Where | Before | Now |
|---|---|---|
| `POST /channels/{id}/get-prepaid-current-sum` | body `prepaidSum` = new total, replaces it | body `amount` = amount to **add** (+ `paymentId`); `prepaidSum` ignored |
| Same endpoint | sent the watcher e-mail | no e-mail (daily cronjob instead) |
| `POST /channels/{id}` | could overwrite `prepaidSum` | `prepaidSum` / `prepaidCurrentSum` ignored; new `prepaidActive` |
| `GET /channels/{id}` → `prepaidCurrentSum` | stale snapshot | live value when `prepaidActive = true` |
| `GET /channels/{id}/prepaid-orders` (1633) | always calculated when `prepaidDate` is set | nothing deducted when `prepaidActive = false`; new fields `prepaidActive`, `prepaidLow`, `totalCredited`; `prepaidCurrentSum` now includes credits added back |
| `POST /orders/{id}` | `paymentTypeId` always editable | `400` when changing `paymentTypeId` of an order deducted from prepaid |

FE checklist:
- [ ] 1 — `prepaidActive` toggle, *Lägg till summa* (`amount` + `paymentId`), `prepaidLogs` columns *Belopp* (`amount`) + *Payment ID* (`paymentId`), red *Tillgänglig summa* when `prepaidLow`
- [ ] 2 — sum box on channel select + popup after create
- [ ] 3 — disable Betalningstyp when `prepaidActive && !prepaidExcluded`
- [ ] 4 — prepaid box on order detail
- [ ] 5 — prepaid box on invoice detail + "Prepaid" type (`down_payment_row_type_prepaid`) in *Lägg till inbetalning*
- [ ] 6 — Prepaid column in *Att fakturera löpande*
- [ ] 7 — Prepaid column in *Fakturor*
- [ ] Users with `role = "ROLE_CHANNEL"`: hide everything when `prepaidGuestVisible` / `guestLoginShowPrepaid` is `false`
