# Frontend Drawer Integration Guide

Base URL: `/api/v1`  
Auth: `Authorization: Bearer <token>` on every request

---

## 1. Setup (one time)

### Create a cash account for the physical drawer

```http
POST /api/v1/cashincashout/accounts
Content-Type: application/json

{
  "accountId": "DRAWER-MAIN",
  "name": "Main Register Drawer",
  "description": "Front desk POS till"
}
```

Use `accountId: "DRAWER-MAIN"` on every shift for that register.

### Add frontend module

```
src/modules/drawer/
  drawer.api.ts
  drawer.types.ts
  DrawerPage.tsx
  OpenShiftModal.tsx
  CloseShiftModal.tsx
  DrawerLiveCard.tsx
  DrawerHistoryTable.tsx
  DrawerShiftHistoryPage.tsx
```

Add nav item: **Drawer** or **Cash Register**

---

## 2. API reference (all implemented)

| Action | Method | Route |
|--------|--------|-------|
| Open shift | `POST` | `/drawer-sessions/open` |
| Current open shift | `GET` | `/drawer-sessions/current?accountId=DRAWER-MAIN` |
| List shift history | `GET` | `/drawer-sessions?accountId=...&status=closed&page=1` |
| Get one shift | `GET` | `/drawer-sessions/:id` |
| Live expected cash | `GET` | `/drawer-sessions/:id/expected` |
| Merged timeline | `GET` | `/drawer-sessions/:id/history` |
| Close shift | `POST` | `/drawer-sessions/:id/close` |
| Manual cash in (shift) | `POST` | `/drawer-sessions/:id/cash-in` |
| Manual cash out (shift) | `POST` | `/drawer-sessions/:id/cash-out` |

---

## 3. `drawer.api.ts` (copy to frontend)

```typescript
const API = '/api/v1/drawer-sessions';

const authHeaders = (token: string) => ({
  Authorization: `Bearer ${token}`,
  'Content-Type': 'application/json',
});

export const drawerApi = {
  openShift: (token: string, body: {
    accountId: string;
    openingFloat: number;
    cashier?: string;
    currency?: string;
  }) =>
    fetch(`${API}/open`, {
      method: 'POST',
      headers: authHeaders(token),
      body: JSON.stringify(body),
    }).then((r) => r.json()),

  getCurrentShift: (token: string, accountId: string) =>
    fetch(`${API}/current?accountId=${encodeURIComponent(accountId)}`, {
      headers: authHeaders(token),
    }).then((r) => r.json()),

  getExpected: (token: string, sessionId: string) =>
    fetch(`${API}/${sessionId}/expected`, {
      headers: authHeaders(token),
    }).then((r) => r.json()),

  getHistory: (token: string, sessionId: string) =>
    fetch(`${API}/${sessionId}/history`, {
      headers: authHeaders(token),
    }).then((r) => r.json()),

  closeShift: (token: string, sessionId: string, body: {
    countedCash: number;
    closeNotes?: string;
    recordVarianceAsAdjustment?: boolean;
  }) =>
    fetch(`${API}/${sessionId}/close`, {
      method: 'POST',
      headers: authHeaders(token),
      body: JSON.stringify(body),
    }).then((r) => r.json()),

  listShifts: (token: string, params: Record<string, string>) => {
    const qs = new URLSearchParams(params).toString();
    return fetch(`${API}?${qs}`, { headers: authHeaders(token) }).then((r) => r.json());
  },

  cashIn: (token: string, sessionId: string, body: {
    amount: number;
    category: string;
    description?: string;
  }) =>
    fetch(`${API}/${sessionId}/cash-in`, {
      method: 'POST',
      headers: authHeaders(token),
      body: JSON.stringify(body),
    }).then((r) => r.json()),

  cashOut: (token: string, sessionId: string, body: {
    amount: number;
    category: string;
    description?: string;
  }) =>
    fetch(`${API}/${sessionId}/cash-out`, {
      method: 'POST',
      headers: authHeaders(token),
      body: JSON.stringify(body),
    }).then((r) => r.json()),
};
```

---

## 4. Main screen flow (`DrawerPage.tsx`)

### On page load

```typescript
const accountId = 'DRAWER-MAIN'; // or from settings
const res = await drawerApi.getCurrentShift(token, accountId);

if (res.data.session) {
  // Shift is OPEN → show live dashboard
  setSession(res.data.session);
  await refreshExpected(res.data.session._id);
} else {
  // No open shift → show "Open Shift" button
  setSession(null);
}
```

### Open shift modal

```typescript
await drawerApi.openShift(token, {
  accountId: 'DRAWER-MAIN',
  openingFloat: 5000,
  cashier: currentUser.name,
  currency: 'AF',
});
```

**Response `data.session`:**

```json
{
  "_id": "...",
  "sessionNumber": "DRW-2026-1",
  "accountId": "DRAWER-MAIN",
  "cashier": "zaki",
  "openingFloat": 5000,
  "status": "open",
  "openedAt": "2026-06-24T08:00:00.000Z",
  "expectedDrawer": 5000,
  "liveBreakdown": {
    "openingFloat": 5000,
    "cashPosSales": 0,
    "cashInvoiceCollectedAtSale": 0,
    "cashReceivablePayments": 0,
    "manualCashIn": 0,
    "cashRefunds": 0,
    "cashExpenses": 0,
    "cashVendorPayments": 0,
    "manualCashOut": 0,
    "expectedDrawer": 5000
  }
}
```

### Live dashboard (poll every 30s or after each sale)

```typescript
const refreshExpected = async (sessionId: string) => {
  const res = await drawerApi.getExpected(token, sessionId);
  setExpectedDrawer(res.data.expectedDrawer);
  setBreakdown(res.data.breakdown);
};
```

**Show these cards:**

| Card | Field |
|------|-------|
| Opening float | `breakdown.openingFloat` |
| Cash POS sales | `breakdown.cashPosSales` |
| Invoice cash | `breakdown.cashInvoiceCollectedAtSale` |
| Collections | `breakdown.cashReceivablePayments` |
| Manual in | `breakdown.manualCashIn` |
| Refunds | `breakdown.cashRefunds` |
| Expenses | `breakdown.cashExpenses` |
| Vendor payments | `breakdown.cashVendorPayments` |
| Manual out | `breakdown.manualCashOut` |
| **Expected in drawer** | `expectedDrawer` |

### After each POS bill / invoice payment

Call `refreshExpected(session._id)` so the drawer updates immediately.

---

## 5. Close shift modal

```typescript
const res = await drawerApi.closeShift(token, session._id, {
  countedCash: 13280,
  closeNotes: 'End of day',
  recordVarianceAsAdjustment: true, // posts CashIn/CashOut for variance
});
```

**Response `data.closeSummary`:**

```json
{
  "expectedDrawer": 13300,
  "countedCash": 13280,
  "variance": -20,
  "varianceRecorded": true,
  "breakdown": { "...": "..." }
}
```

**UI:**

- Green if `variance === 0`
- Red if `variance < 0` (short)
- Yellow if `variance > 0` (over)

After close → clear session state → show "Open Shift" again.

---

## 6. History tab

### Timeline for current / past shift

```typescript
const res = await drawerApi.getHistory(token, sessionId);
setTimeline(res.data.timeline);
```

**Table columns:** Time | Type | Description | In | Out | Running expected

```json
{
  "type": "opening_float",
  "direction": "in",
  "amount": 5000,
  "label": "Opening Float",
  "runningExpected": 5000
}
```

Types: `opening_float`, `pos_sale`, `invoice_cash_sale`, `receivable_payment`, `manual_in`, `manual_out`, `expense`, `vendor_payment`, `refund`

### Past shifts list

```http
GET /api/v1/drawer-sessions?accountId=DRAWER-MAIN&status=closed&page=1&limit=20
```

Click row → load `GET /drawer-sessions/:id` + history.

---

## 7. Manual cash in/out (during shift)

```typescript
// Petty cash in
await drawerApi.cashIn(token, session._id, {
  amount: 200,
  category: 'Petty Cash',
  description: 'Change from safe',
});

// Petty cash out
await drawerApi.cashOut(token, session._id, {
  amount: 100,
  category: 'Petty Cash',
  description: 'Paid courier',
});

await refreshExpected(session._id);
```

---

## 8. Formula (backend computes this for you)

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

**Only cash payment methods count** for sales/collections (paymentMethod contains "cash").

**Cashier filter:** if shift has `cashier`, only that cashier's POS bills are included in `cashPosSales`.

---

## 9. What triggers drawer updates automatically

| Event | Updates drawer? |
|-------|-----------------|
| Cash bill (`paymentMethod: cash`) | Yes — on next `GET .../expected` |
| Cash invoice `amountPaid` | Yes |
| Receivable payment (Cash) | Yes |
| Cash expense (paid) | Yes |
| Vendor payment (Cash) | Yes |
| Bill/invoice refund | Yes |
| Manual cash in/out via drawer API | Yes — immediate |

**No auto CashIn on bill create** — expected cash is computed from bills DB, not duplicate CashIn rows.

---

## 10. Recommended UI states

```
┌─────────────────────────────────────┐
│  Drawer — DRAWER-MAIN               │
│  Shift: DRW-2026-1 (OPEN)           │
│  Cashier: zaki                      │
├─────────────────────────────────────┤
│  EXPECTED IN DRAWER     13,300 AF   │
│  Opening    5,000                   │
│  POS Sales  8,000                   │
│  ... breakdown rows ...             │
├─────────────────────────────────────┤
│ [Cash In] [Cash Out] [Close Shift]  │
│ [History]                           │
└─────────────────────────────────────┘
```

---

## 11. Error handling

| Error | Meaning |
|-------|---------|
| `Cash account "X" not found` | Create account first (`POST /cashincashout/accounts`) |
| `already has an open shift` | Close current shift or use `GET /current` |
| `Drawer session is already closed` | Cannot close twice |
| `Cannot add cash to a closed drawer session` | Open new shift |

---

## 12. Do NOT use for drawer amount

- `GET /analytics/kpi` → `cashBalance` (business-wide, not till)
- `GET /cashincashout/currentblance` without shift scope

**Always use:** `GET /drawer-sessions/:id/expected`

---

## 13. Quick test sequence (Postman)

1. `POST /cashincashout/accounts` → create `DRAWER-MAIN`
2. `POST /drawer-sessions/open` → `{ accountId, openingFloat: 5000 }`
3. `GET /drawer-sessions/current?accountId=DRAWER-MAIN`
4. Create a cash bill in POS
5. `GET /drawer-sessions/:id/expected` → `cashPosSales` should increase
6. `GET /drawer-sessions/:id/history` → see timeline
7. `POST /drawer-sessions/:id/close` → `{ countedCash: 8000 }`

---

## 14. Types (`drawer.types.ts`)

```typescript
export interface DrawerBreakdown {
  openingFloat: number;
  cashPosSales: number;
  cashInvoiceCollectedAtSale: number;
  cashReceivablePayments: number;
  manualCashIn: number;
  cashRefunds: number;
  cashExpenses: number;
  cashVendorPayments: number;
  manualCashOut: number;
  totalInflows: number;
  totalOutflows: number;
  expectedDrawer: number;
}

export interface DrawerSession {
  _id: string;
  sessionNumber: string;
  accountId: string;
  cashier?: string;
  currency: string;
  openingFloat: number;
  openedAt: string;
  closedAt?: string | null;
  status: 'open' | 'closed';
  expectedDrawer?: number;
  expectedCash?: number;
  countedCash?: number | null;
  variance?: number | null;
  liveBreakdown?: DrawerBreakdown;
  breakdown?: DrawerBreakdown;
}

export interface DrawerTimelineEvent {
  id: string;
  type: string;
  direction: 'in' | 'out';
  amount: number;
  timestamp: string;
  label: string;
  description: string;
  reference?: string | null;
  runningExpected: number;
}
```
