# Modul Sales (Penjualan)

Mencakup: Laporan Transaksi (Reports), Daftar Transaksi (Transactions) + Detail Transaksi, Shift Log + Detail Shift. Semua read-only (tidak ada create/edit/delete — data berasal dari aktivitas kasir POS, manager hanya melihat & export laporan).

File: [reportService.js](../../src/services/reportService.js), [Reports.jsx](../../src/pages/sales/Reports.jsx), [Transactions.jsx](../../src/pages/sales/Transactions.jsx), [ReportDetail.jsx](../../src/pages/sales/ReportDetail.jsx), [Shifts.jsx](../../src/pages/sales/Shifts.jsx), [ShiftDetail.jsx](../../src/pages/sales/ShiftDetail.jsx)

> **Catatan koreksi memori sebelumnya:** `ReportDetail.jsx` **sudah terhubung** ke backend asli (`GET /v1/reports/orders/{id}`) — bukan dummy seperti tercatat sebelumnya. Update: hanya **Users/Roles** dan **Settings Tax** yang masih benar-benar dummy/belum ada endpoint BE per saat ini.

## Pola filter yang sama di semua halaman Sales

Semua 3 halaman list (Reports, Transactions, Shifts) pakai filter periode yang identik:
- Dropdown: `today` (hari ini), `7days` (6 hari lalu s/d hari ini), `30days` (29 hari lalu s/d hari ini), `custom` (date picker manual `from`/`to`)
- Filter cabang (dropdown, default = cabang aktif di topbar, bisa pilih "Semua Cabang")
- Query params dikirim ke backend: `{ from, to, branch_id? }` — filter tanggal & cabang dilakukan **di backend**, bukan di FE (beda dengan modul Master Data yang filter di FE).

## 1. Laporan Transaksi — `Reports.jsx`

`GET /v1/reports/sales` params `{ from, to, branch_id? }`

**Expected response:**
```json
{ "data": { "summary": { "total_orders": 0, "total_revenue": "...", "total_discount": "...", "total_tax": "..." }, "daily_rows": [{ "date": "YYYY-MM-DD", "total_orders": 0, "revenue": "...", "discount": "...", "tax": "..." }] } }
```
4 summary card (Total Transaksi, Pendapatan, Diskon, Pajak) + tabel breakdown harian. Klik ikon mata di satu baris tanggal → navigate ke `/sales/transactions?from={date}&to={date}` (Transactions otomatis mode `custom` dengan range itu).

**Export:** `GET /v1/reports/sales/excel`, `GET /v1/reports/sales/pdf` (blob, params sama dengan filter aktif).

## 2. Daftar Transaksi — `Transactions.jsx`

`GET /v1/reports/orders` params `{ from, to, branch_id? }` — return array order. Search nama/no order dilakukan **di FE** (client-side filter di atas hasil yang sudah difilter tanggal/cabang oleh backend).

**Status badge** (`STATUS_LABEL` map di FE, label Indonesia untuk status backend):
| Status backend | Label UI |
|---|---|
| `pending` | Pending |
| `in_kitchen` | Di Dapur |
| `ready` | Siap |
| `completed` | Selesai |
| `void` | Void |
| `cancelled` | Dibatalkan |

**Export:** `GET /v1/reports/orders/excel` (blob). **Tidak ada PDF** untuk list transaksi (beda dari Reports & Shifts yang punya PDF).

Klik ikon mata → navigate ke `/sales/reports/{id}` (detail).

## 3. Detail Transaksi — `ReportDetail.jsx`

`GET /v1/reports/orders/{id}`

**Expected response (cukup detail):**
```json
{
  "data": {
    "order_number": "...", "created_at": "...", "branch": {"name":"..."}, "table": {"code":"..."},
    "order_type": "dine_in|takeaway|delivery", "source": "kasir|qr",
    "status": "...", "payment_status": "...",
    "paymentRequests": [{ "status": "paid", "paymentMethod": { "name": "..." } }],
    "items": [{ "id", "product_name", "qty", "unit_price", "item_total",
                "modifier_options": [{ "name", "option", "price" }], "notes": "..." }],
    "subtotal": 0, "discount_amount": 0, "tax_amount": 0, "rounding": 0, "total": 0
  }
}
```
Metode pembayaran ditentukan dari `paymentRequests` — cari yang `status === 'paid'`, fallback ke item pertama kalau tidak ada yang paid.

**Export:** `GET /v1/reports/orders/{id}/pdf` (cetak struk/invoice transaksi individual, blob).

## 4. Shift Log — `Shifts.jsx`

`GET /v1/reports/shifts` params `{ from, to, branch_id? }` — daftar sesi kasir (shift buka-tutup). Search nama kasir di FE.

**Expected response per shift:** `{ id, user: {name}, branch: {name}, branch_id, started_at, ended_at, orders_count, total_revenue }`. `ended_at` null → ditampilkan label "Aktif" (shift masih berjalan, belum ditutup kasir).

**Export:** `GET /v1/reports/shifts/excel`, `GET /v1/reports/shifts/pdf` (blob, list, bukan per-shift).

## 5. Detail Shift — `ShiftDetail.jsx`

`GET /v1/reports/shifts/{id}`

**Expected response:**
```json
{
  "shift_number": "...", "branch": "...", "kasir": "...",
  "started_at": "...", "ended_at": "...|null", "notes": "...",
  "opening_cash": 0, "closing_cash": 0,
  "orders": { "total": 0, "completed": 0, "void": 0, "revenue": 0 },
  "payment_summary": [{ "method": "...", "total": 0 }]
}
```
**Catatan:** field `branch` dan `kasir` di response detail ini berupa **string langsung** (bukan object `{name}` seperti di list shift) — perhatikan inkonsistensi ini kalau memodifikasi tampilan.

**Export:** `GET /v1/reports/shifts/{id}/pdf` (cetak laporan tutup shift individual, blob).

## Ringkasan endpoint Sales

| Aksi | Method | Endpoint |
|---|---|---|
| Laporan penjualan (summary+harian) | GET | `/v1/reports/sales` |
| Export laporan penjualan | GET | `/v1/reports/sales/excel`, `/v1/reports/sales/pdf` |
| List transaksi/order | GET | `/v1/reports/orders` |
| Export list transaksi | GET | `/v1/reports/orders/excel` |
| Detail 1 transaksi | GET | `/v1/reports/orders/{id}` |
| Export detail transaksi | GET | `/v1/reports/orders/{id}/pdf` |
| List shift | GET | `/v1/reports/shifts` |
| Export list shift | GET | `/v1/reports/shifts/excel`, `/v1/reports/shifts/pdf` |
| Detail 1 shift | GET | `/v1/reports/shifts/{id}` |
| Export detail shift | GET | `/v1/reports/shifts/{id}/pdf` |

Semua endpoint list/summary menerima params `{ from, to, branch_id? }` (format tanggal `YYYY-MM-DD`).
