# Savings Down-Payment System

## Direct answer

The current savings system is a verified ledger. Account creation stores a plan and creates the first period. Deposits are always `PENDING`; only verification moves money into period allocations and verified totals. When verified deposits reach the account target, the account becomes `READY_FOR_CONTRACT`. Conversion then links the funded account to a matching pending contract and changes the account to `CONVERTED`.

## Core tables

| Table | Role | Important fields |
|---|---|---|
| `savings_accounts` | Savings plan and aggregate lifecycle | account/customer/product IDs, plan type, minimum per period, target, start date, status, converted contract, promotion totals |
| `savings_transactions` | Deposit ledger | account, amount, payment date/channel/reference, managed slip path, provider metadata, verification status, idempotency key |
| `savings_periods` | Planned periods and allocation progress | account, period number/start/end, target, allocated amount, status |
| `savings_allocations` | Join ledger from verified deposit to period | account, transaction, period, amount |
| `savings_account_promotions` | Promotion snapshot for an account | package and gross/fee/discount/subsidy/net amounts |
| `savings_account_credits` | Promotion event credit applied to account | account/application, credit amount/type/status |
| `promotion_redemptions` | Reserved benefits for savings/contract/cash use | package, savings account, benefit, status |
| `contracts` | Conversion target | customer/product match, down payment, loan/finance amount, contract status |

## Create savings account

Current route: `POST /savings/accounts`.

1. Resolve customer by ID/code and normalize plan type to `DAILY`, `WEEKLY`, or `MONTHLY`.
2. Validate positive minimum and target values. If product price/promotion data is present, the server owns the canonical target calculation. The default delivery milestone input is 45% unless a valid value is supplied.
3. Reject creation when the customer already has an active/pending contract or a `SAVING`/`READY_FOR_CONTRACT` account.
4. Generate `SA-YYYYMMDD-NNNN`, insert account with status `SAVING`, save promotion/event snapshots when applicable, and create period 1 with status `OPEN`.

## Plan and due date

The plan type determines period end from period start: daily adds one day, weekly adds seven days, monthly adds one month, then subtracts one day. A new period starts the day after the prior `period_end`. This is the implemented due-window model; there is no separate due-date API.

## Deposit

Current route: `POST /savings/accounts/:id/transactions`.

- Only an account in `SAVING` accepts deposits.
- Amount must be positive.
- Optional idempotency key returns the existing transaction when repeated.
- Duplicate active payment references are rejected.
- Provider is limited to `MANUAL`, `EASYSLIP`, or `OTHER`.
- The inserted transaction always begins as `PENDING`.

## Verification and balance

Current route: `POST /savings/transactions/:id/verify`.

- Decision is `VERIFIED` or `REJECTED`; only `PENDING` can be decided.
- Verification is idempotent for an already-verified transaction.
- A verified deposit is allocated to the earliest non-paid period. Each allocation updates the period to `PARTIAL` or `PAID`; new periods are created as needed.
- Verified balance is calculated as `SUM(savings_transactions.amount WHERE verification_status='VERIFIED')`.
- Pending balance is calculated separately. Rejected deposits do not contribute.
- Progress percent is `min(100, verified_total / target_amount * 100)`.

## Target and READY_FOR_CONTRACT

After allocation, the verified total is compared with `target_amount`. If it meets/exceeds the target and account status is still `SAVING`, the account becomes `READY_FOR_CONTRACT`. There is no client-supplied “mark ready” shortcut in the active handler.

## Transition to contract

Current route: `POST /savings/accounts/:id/convert`.

1. Require a concrete `contract_id`.
2. Lock savings account and contract in one database transaction.
3. Require account status `SAVING` or `READY_FOR_CONTRACT` and verified total at least target.
4. Require contract status `DRAFT`, `PENDING_DOCS`, or `PENDING_APPROVAL`.
5. Require matching customer and, when present on both records, matching product name, color, and capacity.
6. Set down payment to verified savings, loan/finance amount to total price minus verified savings, payment model to `Savings_Installment`, and contract status to `PENDING_APPROVAL`.
7. Set savings account status to `CONVERTED` and record the contract ID.

The existing flow inventory labels conversion `BROKEN`, but the current router contains this active atomic handler and disables the older handler with `if (false)`: **MAP/CODE CONFLICT**.

## Current implementation gaps

These requested concepts are not implemented as savings lifecycle routes in current code. They are documented as gaps, not invented rules:

| Concept | Current implementation |
|---|---|
| Missed savings payment | No background job or route marks a savings period missed/overdue. Periods use `OPEN`, `PARTIAL`, `PAID` in active allocation code. |
| Pause savings | No savings pause route/status transition. Customer payment-pause requests exist for contracts through `follow_up_logs`. |
| Cancel savings | Schema comments allow `CANCELLED`, but no current API handler changes a savings account to that status. |
| Resume savings | No route or transition found. |
| Notification/reminder | No savings notification worker, queue, or API found. |
| Refund savings | Schema comments mention `REFUNDED`, but no active savings refund route was found. |
| Verification UI | Backend route exists; no direct current frontend caller was found. |
| Conversion UI | Backend route exists; no direct current frontend caller was found. |

## Status model observed

```text
Account: SAVING -> READY_FOR_CONTRACT -> CONVERTED
Transaction: PENDING -> VERIFIED | REJECTED
Period: OPEN -> PARTIAL -> PAID
Contract after conversion: PENDING_APPROVAL -> (approval/activation flow) -> ACTIVE
```

