# Product Batch Expiry - Implemented Flow

This document explains exactly what was implemented in backend and how the runtime flow now works.

## What was added

### New module
- `src/modules/product-batches/product-batches.model.js`
- `src/modules/product-batches/product-batches.validation.js`
- `src/modules/product-batches/product-batches.service.js`
- `src/modules/product-batches/product-batches.controller.js`
- `src/modules/product-batches/product-batches.router.js`

### New routes
- `POST /api/v1/product-batches/receive`
- `GET /api/v1/product-batches/product/:productId`

Also available under alias:
- `POST /api/product-batches/receive`
- `GET /api/product-batches/product/:productId`

### Schema updates
- Bill item now stores `batchAllocations`.
- Invoice item now stores `batchAllocations`.

### Sales flow updates
- `createBill` now allocates stock by FEFO (first-expire-first-out) and stores exact allocation per line item.
- `createInvoice` (sale invoice) now allocates by FEFO in transaction and stores allocation per line item.

### Refund flow updates
- Full bill refund returns quantity back to original batches using saved `batchAllocations`.
- Single-item refund also returns quantity to original batches and reduces that line's allocation map.

### Data migration support
- Added script: `src/scripts/backfillProductBatches.js`
- Creates one legacy batch for products that have stock but no batches.

---

## Runtime flow

## 1) Receiving inventory

Client calls:

`POST /api/v1/product-batches/receive`

Body example:

```json
{
  "productId": "663f1e4a4f5f1e2b3c4d5e6f",
  "batchNumber": "RICE-2026-05-LOT-11",
  "expiryDate": "2026-12-30",
  "qty": 80,
  "costPrice": 420,
  "sellingPrice": 550,
  "supplierRef": "PO-1088"
}
```

Backend behavior:
1. Creates a new batch row.
2. Recalculates `Product.stock` from sum of available batches.
3. Recalculates `Product.expirydate` as nearest expiry among available batches.
4. If `sellingPrice` is provided, updates product `sellingprice` for future sales.

Result: old batch and new batch stay separate.

## 2) Selling from bills/invoices

When bill/invoice sale is created:
1. Backend starts a Mongo transaction.
2. For each item, FEFO service selects earliest-expiry batches with available qty.
3. Deducts qty from one or multiple batches.
4. Saves exact `batchAllocations` in bill/invoice line item.
5. Re-syncs product aggregate stock/nearest expiry.
6. Commits transaction.

Result: complete trace of which batch was sold.

## 3) Refunding

On full or partial refund:
1. Backend reads line `batchAllocations`.
2. Returns qty to original batches.
3. Re-syncs product aggregate stock/nearest expiry.
4. Updates bill totals/status.

Result: stock returns to correct expiry buckets.

---

## Why this works better

- Prevents expiry overwrite when new shipment arrives.
- Enables true near-expiry and expiry-loss analytics.
- Provides audit history per sale/refund.
- Scales for multiple shipments and mixed expiry stock.

---

## Rollout checklist

1. Deploy backend changes.
2. Run backfill once in production/staging:

```bash
node src/scripts/backfillProductBatches.js
```

3. Update frontend to call `/product-batches/receive` when receiving stock.
4. Stop using direct product stock edits for normal incoming inventory.
5. Validate with test scenario:
   - Old batch expires tomorrow.
   - New batch expires next month.
   - Sell item.
   - Confirm old batch consumed first.
