# Cash Drawer Module — Implementation Guide (Frontend + Formulas)

This document explains how to build a **realistic POS cash drawer** on top of your **existing backend**, what number is “100% sure”, which formulas to use, and whether you need a **new frontend module**.

---

## 1. What you already have (do not duplicate)

Your codebase already has these pieces:

| Area | Module | Base route | What it tracks |
|------|--------|------------|----------------|
| Manual cash movements | `cash-in-cash-out` | `/api/v1/cashincashout` | `CashIn` / `CashOut` entries |
| Multiple tills / safes | `cash-accounts` | `/api/v1/cashincashout/accounts` | Named accounts with balance |
| POS sales | `bills` | `/api/v1/bills` | Instant sales, `paymentMethod`, `cashier` |
| Credit sales | `invoices` | `/api/v1/invoices` | `amountPaid`, loan-linked payments |
| Customer cash collection | `receivable-payments` | `/api/v1/receivable-payments` | `paymentMethod` |
| Vendor cash paid | `payable-payments` | `/api/v1/payable-payments` | `paymentMethod` |
| Operating cash out | `expenses` | `/api/v1/expenses` | `paymentMethod`, `status` |
| Business-wide cash KPI | `analytics` | `/api/v1/analytics/kpi` | `cashBalance` (see warning below) |

**Important:** Analytics `cashBalance` is **business cash position**, not necessarily **physical drawer cash**.

```text
analytics.cashBalance =
  cashSales (all paid bills in range)
  + customerPayments (invoice/loan collections)
  - vendorPayments
  - paidOperatingExpenses
```

That formula **does not** know:
- which sales were **cash vs bank**
- opening float in the physical drawer
- manual petty cash in/out unless logged in `CashIn`/`CashOut`
- per-cashier / per-shift boundaries

So for a **drawer you can count in hand**, use the formulas in section 3 — not KPI `cashBalance` alone.

---

## 2. Do you need a new frontend module?

### Short answer: **Yes — add a Drawer / Shift UI**, but **reuse existing APIs** first.

| Approach | New frontend page? | New backend module? |
|----------|-------------------|---------------------|
| **Phase 1 (recommended now)** | Yes — one **Drawer** screen | No — use `cashincashout` + bills + payments |
| **Phase 2 (100% shift control)** | Extend same Drawer screen | **Yes — `drawer-sessions` API (implemented)** |

### Why a new frontend section?

- `cash-in-cash-out` is for **manual** in/out with categories — cashiers need **open shift → expected cash → count → close shift → variance**.
- Bills/invoices already create sales but **do not auto-write** to `CashIn` today.
- History for a drawer must **merge** bills + cash in/out + cash payments in one timeline.

### Suggested frontend structure

```
src/modules/drawer/          ← NEW frontend folder
  DrawerPage.tsx             ← main screen
  OpenShiftModal.tsx
  CloseShiftModal.tsx
  DrawerHistoryTable.tsx
  drawer.api.ts              ← wraps existing backend routes
  drawer.utils.ts              ← formulas below
```

You do **not** need a full duplicate of `cash-in-cash-out`. Link to it for “manual adjustment” only.

Optional nav label: **Drawer**, **Cash Register**, or **Shift**.

---

## 3. The formula for “100% sure” drawer amount

### Per shift (realistic POS model)

Define a **shift** with:

- `openingFloat` — cash placed in drawer at open (counted)
- `openedAt` / `closedAt`
- `cashier` (match `bill.cashier` / logged-in user)
- `accountId` — link to one `CashAccount` (e.g. `DRAWER-MAIN`)

#### Expected cash in drawer (system)

```text
expectedDrawer =
  openingFloat
+ cashPosSales
+ cashInvoiceCollectedAtSale
+ cashReceivablePayments
+ manualCashIn
- cashRefunds
- cashExpenses
- cashVendorPayments
- manualCashOut
```

#### Physical count (human)

```text
countedDrawer = sum of actual notes/coins in the till
```

#### Variance (over / short)

```text
variance = countedDrawer - expectedDrawer
```

- `variance = 0` → drawer matches system  
- `variance > 0` → over (extra cash)  
- `variance < 0` → short (missing cash)

---

### How to compute each line from **your current APIs**

Use the same **date range** as the shift: `openedAt` → `closedAt` (or now if shift still open).

#### A) `cashPosSales`

Cash received at POS from bills:

```text
SUM(bill.total)
WHERE bill.status IN ('completed', 'paid')
  AND bill.date BETWEEN openedAt AND closedAt
  AND LOWER(bill.paymentMethod) IN ('cash', 'نقد', 'cash payment')
  AND bill.cashier = currentCashier   -- optional but recommended
```

**API today:** `GET /api/v1/bills?startDate=...&endDate=...`  
Filter client-side by `paymentMethod` and `cashier` until backend adds `paymentMethod=cash` filter.

#### B) `cashInvoiceCollectedAtSale`

Cash collected when invoice was created (not later loan payments):

```text
For each invoice in shift window:
  cashAtSale = MIN(invoice.amountPaid, invoice.totalAmount)
  -- only if invoice.paymentMethod is cash
```

**API:** `GET /api/v1/invoices?startDate=...&endDate=...`  
Filter `paymentMethod` ≈ cash and sum `amountPaid` (capped by `totalAmount`).

#### C) `cashReceivablePayments`

Later collections on credit invoices:

```text
SUM(receivablePayment.amount)
WHERE paymentMethod = 'Cash'
  AND paymentDate BETWEEN openedAt AND closedAt
```

**API:** `GET /api/v1/receivable-payments?startDate=...&endDate=...`

#### D) `manualCashIn` / `manualCashOut`

Petty cash, owner puts money in, safe transfer, etc.:

```text
manualCashIn  = SUM(CashIn.amount)  for accountId = DRAWER-MAIN in shift
manualCashOut = SUM(CashOut.amount) for accountId = DRAWER-MAIN in shift
```

**APIs:**

```http
GET /api/v1/cashincashout/transactions/summary?accountId=DRAWER-MAIN&startDate=...&endDate=...
GET /api/v1/cashincashout/transactions?accountId=DRAWER-MAIN&type=all&startDate=...&endDate=...
```

Balance formula used by backend (all time for that account):

```text
accountBalance = totalCashIn - totalCashOut
```

#### E) `cashRefunds`

Cash returned to customers:

```text
SUM(refund amounts from bills/invoices marked refunded in shift)
WHERE original payment was cash
```

**API today:**

- Bill refunds: `POST /api/v1/bills/:id/refund` / `refund-item` (reduces bill total)
- Invoice refunds: `POST /api/v1/invoices/:id/refund` / `refund-item`

For drawer math, subtract refunded **cash** amounts in the shift window (from refund response `refundSummary.totalRefundAmount` if cash sale).

#### F) `cashExpenses`

```text
SUM(expense.amount)
WHERE status = 'paid'
  AND paymentMethod = 'Cash'
  AND date BETWEEN openedAt AND closedAt
```

**API:** `GET /api/v1/expenses?status=paid&startDate=...&endDate=...`

#### G) `cashVendorPayments`

```text
SUM(payablePayment.amount)
WHERE paymentMethod = 'Cash'
  AND paymentDate BETWEEN openedAt AND closedAt
```

**API:** `GET /api/v1/payable-payments?startDate=...&endDate=...`

---

## 4. Phase 1 — Implement drawer **without** new backend

### Step 1 — Create one cash account per physical drawer

```http
POST /api/v1/cashincashout/accounts
{
  "accountId": "DRAWER-MAIN",
  "name": "Main Register Drawer",
  "description": "Front desk POS drawer"
}
```

Use this `accountId` on every **manual** cash in/out for that till.

### Step 2 — Open shift (frontend only for now)

Store in frontend state + `localStorage` (until backend session exists):

```json
{
  "shiftId": "uuid",
  "accountId": "DRAWER-MAIN",
  "cashier": "zaki",
  "openingFloat": 5000,
  "openedAt": "2026-06-24T08:00:00.000Z",
  "status": "open"
}
```

Optional: record opening float as CashIn:

```http
POST /api/v1/cashincashout/cashin
{
  "amount": 5000,
  "category": "Drawer Opening Float",
  "description": "Shift open - zaki",
  "accountId": "DRAWER-MAIN",
  "currency": "AF"
}
```

> If you post opening float as CashIn, include it in `manualCashIn` **or** treat `openingFloat` separately — **not both**, or you double-count.

**Recommended:** keep `openingFloat` only in shift state; use CashIn/CashOut for **extra** movements during the day.

### Step 3 — Live expected drawer (dashboard card)

While shift is open, poll every 30–60s or refresh after each sale:

```javascript
const expectedDrawer =
  shift.openingFloat +
  cashPosSales +
  cashInvoiceAtSale +
  cashReceivablePayments +
  manualCashIn -
  cashRefunds -
  cashExpenses -
  cashVendorPayments -
  manualCashOut;
```

Display:

- **Opening float**
- **Expected in drawer** (formula above)
- **Cash sales today**
- **Cash in / cash out** (manual)

### Step 4 — Close shift

1. Cashier enters **countedDrawer** (physical count).
2. Compute `variance = countedDrawer - expectedDrawer`.
3. Save close record in frontend (Phase 1) or POST to future API (Phase 2).
4. If variance ≠ 0, optional adjustment:

```http
POST /api/v1/cashincashout/cashin   // if over (source unknown)
POST /api/v1/cashincashout/cashout  // if short
category: "Drawer Variance"
description: "Shift close variance -0.50 - zaki"
```

### Step 5 — History screen

Merge into one table sorted by `timestamp`:

| Source | API | `type` |
|--------|-----|--------|
| Manual in | `GET .../transactions?type=in` | `in` |
| Manual out | `GET .../transactions?type=out` | `out` |
| POS sale | `GET /bills` | `sale` |
| Invoice payment | `GET /receivable-payments` | `collection` |
| Expense | `GET /expenses` | `expense` |
| Vendor payment | `GET /payable-payments` | `vendor` |

Show columns: **time**, **type**, **description**, **amount +/-**, **running expected balance**.

Running balance column (per shift):

```text
runningExpected += amount   // for inflows
runningExpected -= amount   // for outflows
```

---

## 5. Phase 2 — Backend `drawer-sessions` ✅ IMPLEMENTED

See **`redmes/FRONTEND_DRAWER_INTEGRATION.md`** for full frontend guide.

### Routes (live)

```http
POST   /api/v1/drawer-sessions/open
GET    /api/v1/drawer-sessions/current?accountId=DRAWER-MAIN
GET    /api/v1/drawer-sessions
GET    /api/v1/drawer-sessions/:id
GET    /api/v1/drawer-sessions/:id/expected
GET    /api/v1/drawer-sessions/:id/history
POST   /api/v1/drawer-sessions/:id/close
POST   /api/v1/drawer-sessions/:id/cash-in
POST   /api/v1/drawer-sessions/:id/cash-out
```

Backend computes `expectedDrawer` with the same formula as section 3.

### DrawerSession document (stored on close)

```json
{
  "accountId": "DRAWER-MAIN",
  "cashier": "zaki",
  "openingFloat": 5000,
  "openedAt": "...",
  "closedAt": null,
  "expectedCash": 12500,
  "countedCash": 12480,
  "variance": -20,
  "status": "open | closed",
  "breakdown": {
    "cashPosSales": 8000,
    "cashInvoiceAtSale": 0,
    "cashReceivablePayments": 500,
    "manualCashIn": 0,
    "cashRefunds": 0,
    "cashExpenses": 0,
    "cashVendorPayments": 0,
    "manualCashOut": 0
  }
}
```

### ~~Suggested routes (not built yet)~~ Routes above are live.

```http
POST   /api/v1/drawer-sessions/open
GET    /api/v1/drawer-sessions/current?accountId=DRAWER-MAIN
GET    /api/v1/drawer-sessions/:id
GET    /api/v1/drawer-sessions?cashier=zaki&from=...&to=...
GET    /api/v1/drawer-sessions/:id/history
POST   /api/v1/drawer-sessions/:id/close
```

~~Backend would compute `expectedCash` with the same formula as section 3.~~ Done in `drawerBreakdown.service.js`.

---

## 6. What NOT to use for physical drawer

| Field | Why not |
|-------|---------|
| `analytics.cashBalance` | Mixes all payment types + full business scope |
| `analytics.totalRevenue` | Includes credit sales never in drawer |
| `CashAccount.balance` all-time | Not shift-scoped unless you filter dates |
| Invoice `totalAmount` | Credit — not cash in drawer until paid |

Use analytics **only** as a management KPI, not as drawer count.

---

## 7. Currency note

Your system uses **AF** and **USD** in different places (`CashIn.currency`, product batches).

For one drawer:

- Either **one currency per drawer account**, or
- Show **two subtotals**: `expectedDrawerAF`, `expectedDrawerUSD`

Do not mix currencies in a single sum without conversion.

---

## 8. Frontend checklist

- [ ] New nav item: **Drawer**
- [ ] Create / select `CashAccount` (`DRAWER-MAIN`)
- [ ] Open shift → enter opening float + cashier
- [ ] Show **expected drawer** using formula (section 3)
- [ ] After each bill/invoice payment → refresh expected
- [ ] Close shift → physical count → show variance
- [ ] History tab → merged timeline (section 4 step 5)
- [ ] Link “Manual cash in/out” → existing cash-in-cash-out UI with `accountId` preset
- [ ] Do **not** build a second expenses or bills module

---

## 9. Quick API reference for drawer screen

```http
# Account balance (manual movements only, all time)
GET /api/v1/cashincashout/accounts/DRAWER-MAIN/balance

# Shift-scoped manual movements
GET /api/v1/cashincashout/transactions/summary?accountId=DRAWER-MAIN&startDate=...&endDate=...
GET /api/v1/cashincashout/transactions?accountId=DRAWER-MAIN&type=all&startDate=...&endDate=...

# Sales
GET /api/v1/bills?startDate=...&endDate=...

# Invoices & collections
GET /api/v1/invoices?startDate=...&endDate=...
GET /api/v1/receivable-payments?startDate=...&endDate=...

# Cash outflows
GET /api/v1/expenses?status=paid&startDate=...&endDate=...
GET /api/v1/payable-payments?startDate=...&endDate=...

# Manual adjust
POST /api/v1/cashincashout/cashin
POST /api/v1/cashincashout/cashout
```

---

## 10. Summary

| Question | Answer |
|----------|--------|
| Is drawer update in backend today? | **Partial** — cash accounts + transactions yes; **shift session** no |
| Formula for sure drawer amount? | `openingFloat + cash inflows - cash outflows` (section 3) |
| New frontend module? | **Yes** — Drawer/Shift UI; reuse existing APIs |
| New backend module required now? | **Done** — use `/api/v1/drawer-sessions` |
| History? | `GET /drawer-sessions/:id/history` + `GET /drawer-sessions?status=closed` |
| Frontend guide | **`redmes/FRONTEND_DRAWER_INTEGRATION.md`** |

---

## 11. Example numbers (sanity check)

| Line | Amount |
|------|--------|
| Opening float | 5,000 |
| Cash POS sales | + 8,000 |
| Cash invoice at sale | + 1,000 |
| Cash receivable payments | + 500 |
| Manual cash in | + 200 |
| Cash expense | - 300 |
| Cash vendor payment | - 1,000 |
| Manual cash out | - 100 |
| **Expected drawer** | **13,300** |

Physical count: **13,280** → variance **-20** (short 20).

This is the number the cashier should trust — not `analytics.cashBalance`.
