> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jethings.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stock Reconciliation API

> Reconcile physical inventory counts with system-recorded quantities and automatically update stock levels

# Stock Reconciliation API

<Note>
  **Created:** December 25, 2025

  This documentation provides comprehensive information about the stock reconciliation endpoint, including multi-item support, asynchronous processing, and complete workflow examples.
</Note>

The Stock Reconciliation endpoint allows you to reconcile physical inventory counts with system-recorded quantities. This is essential for maintaining accurate inventory records, identifying discrepancies, and correcting stock levels. The endpoint processes multiple items in a single transaction and automatically updates stock ledger entries and bin quantities asynchronously.

## Endpoint Details

**Base URL:** `https://joptic.jethings.com`

### Required Headers

```
x-store-id: <store_id>
Authorization: Bearer <token>
```

***

## Endpoint

```
POST /stock-reconciliation
```

Creates stock reconciliation records for one or more items, calculates differences between system and actual quantities, and queues background jobs to update stock ledger entries and bin quantities.

***

## Create Stock Reconciliation

### Request

**Method:** `POST`\
**Path:** `/stock-reconciliation`

#### Headers

| Header          | Type   | Required | Description                     |
| --------------- | ------ | -------- | ------------------------------- |
| `x-store-id`    | string | ✅ Yes    | The store ID                    |
| `Authorization` | string | ✅ Yes    | Bearer token for authentication |
| `Content-Type`  | string | ✅ Yes    | `application/json`              |

#### Request Body

**Type:** `CreateReconciliationDto`

```typescript theme={null}
{
  items: Array<{                    // Required: At least one item required
    warehouse: string;              // Required: Warehouse ID
    itemVariant: string;            // Required: Item variant ID
    actualQty: number;              // Required: Physical count quantity
  }>;
  note?: string;                    // Optional: Note/reason for reconciliation
}
```

### Field Descriptions

#### Items Array

| Field                 | Type   | Required | Description                                            |
| --------------------- | ------ | -------- | ------------------------------------------------------ |
| `items`               | array  | ✅ Yes    | Array of items to reconcile (minimum 1 item required)  |
| `items[].warehouse`   | string | ✅ Yes    | The warehouse ID where the reconciliation is performed |
| `items[].itemVariant` | string | ✅ Yes    | The item variant ID being reconciled                   |
| `items[].actualQty`   | number | ✅ Yes    | The actual physical quantity counted during inventory  |

#### Note

| Field  | Type   | Required | Description                                                                                                                                                                                                                                        |
| ------ | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `note` | string | No       | Optional note or reason for the reconciliation (e.g., "Monthly inventory check", "Damaged goods adjustment"). This note is shared across all items in the request and will be stored in both the reconciliation record and the stock ledger entry. |

### Validation Rules

* At least one item must be provided in the `items` array
* Each item must have valid `warehouse`, `itemVariant`, and `actualQty` fields
* `actualQty` must be a number (can be positive, negative, or zero)

### Response

**Type:** `CreateStockReconciliationResponseDto`

```typescript theme={null}
{
  data: Array<{
    id: string;                     // Reconciliation record ID
    note?: string | null;           // Note (if provided)
    systemQty: string;              // System quantity at time of reconciliation
    actualQty: string;             // Actual physical quantity
    differenceQty: string;          // Difference: actualQty - systemQty
    createdAt: Date;                // Creation timestamp
    updatedAt: Date;                 // Last update timestamp
  }>;
}
```

### Field Descriptions

| Field                  | Type           | Description                                                                                                       |
| ---------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------- |
| `data`                 | array          | Array of created reconciliation records                                                                           |
| `data[].id`            | string         | Unique identifier for the reconciliation record                                                                   |
| `data[].note`          | string \| null | The note provided in the request (shared across all items)                                                        |
| `data[].systemQty`     | string         | The system-recorded quantity at the time of reconciliation (sum of all active bins for the warehouse/itemVariant) |
| `data[].actualQty`     | string         | The physical quantity that was counted                                                                            |
| `data[].differenceQty` | string         | Calculated as `actualQty - systemQty`                                                                             |
| `data[].createdAt`     | Date           | When the reconciliation record was created                                                                        |
| `data[].updatedAt`     | Date           | When the reconciliation record was last updated                                                                   |

**Difference Interpretation:**

* **Positive**: More items found than system shows (surplus)
* **Negative**: Fewer items found than system shows (shortage)
* **Zero**: Perfect match

***

## How It Works

### 1. Transaction Processing

All items are processed within a single database transaction to ensure atomicity. If any item fails, the entire operation is rolled back.

<Warning>
  All items in a single request are processed within one database transaction. If any item fails validation or processing, the entire request is rolled back.
</Warning>

### 2. For Each Item

#### Step 1: Calculate System Quantity

The system retrieves the current system quantity from the bin table:

```sql theme={null}
SELECT COALESCE(SUM(qty) FILTER (WHERE isActive = true), 0) as systemQty
FROM bin
WHERE warehouseId = :warehouse AND itemVariantId = :itemVariant
```

* Only **active bins** are included in the calculation
* If no bins exist, system quantity defaults to `0`
* The sum accounts for all active bins for the warehouse/itemVariant combination

#### Step 2: Calculate Difference

```typescript theme={null}
differenceQty = actualQty - systemQty
```

**Examples:**

* System: 100, Actual: 98 → Difference: -2 (shortage)
* System: 50, Actual: 55 → Difference: +5 (surplus)
* System: 0, Actual: 10 → Difference: +10 (new stock found)

#### Step 3: Create Reconciliation Record

A `stockReconciliation` record is created with:

* Warehouse ID
* Item variant ID
* System quantity (as string for precision)
* Actual quantity (as string for precision)
* Difference quantity (as string for precision)
* Note (shared across all items)

#### Step 4: Queue Stock Update Job

A background job is queued to update the stock ledger entry and bin table. The job runs asynchronously and includes:

* Reconciliation ID
* Warehouse ID
* Item variant ID
* Actual quantity
* Note

### 3. Asynchronous Stock Update (Background Job)

After the transaction commits, the queued job processes each reconciliation:

#### Stock Ledger Entry (SLE)

Creates a stock ledger entry for audit trail:

```typescript theme={null}
{
  warehouse: string;
  itemVariant: string;
  qtyChange: "0";                    // Always 0 for reconciliation
  qtyAfterTransaction: actualQty;    // Set to actual quantity
  voucherType: "stock_reconciliation";
  voucher: reconciliationId;          // Links to reconciliation record
  reason: note;                      // Note from reconciliation
}
```

<Note>
  The `qtyChange` is always `0` for reconciliations because we're setting an absolute quantity, not making a relative change.
</Note>

#### Bin Update

Updates or creates the bin entry:

* **If bin exists**: Updates the bin's `qty` to match `actualQty`
* **If bin doesn't exist**: Creates a new bin entry with `qty = actualQty` and `isActive = true`

The bin quantity is set to the **absolute actual quantity**, not adjusted by the difference.

<Note>
  Stock updates are processed asynchronously via a queue system. The endpoint returns immediately after creating reconciliation records. Background jobs process stock updates. Jobs have retry logic (3 attempts by default). If a job fails, it remains in the queue for manual review.
</Note>

### 4. Response Mapping

All created reconciliation records are mapped to response DTOs and returned in the response array.

***

## Example Requests

### Single Item Reconciliation

<CodeGroup>
  ```json Request Body theme={null}
  {
    "items": [
      {
        "warehouse": "warehouse_123",
        "itemVariant": "variant_456",
        "actualQty": 98
      }
    ],
    "note": "Monthly inventory check - found 2 units missing"
  }
  ```

  ```bash cURL theme={null}
  curl -X POST https://joptic.jethings.com/stock-reconciliation \
    -H "x-store-id: store_789" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "items": [
        {
          "warehouse": "warehouse_123",
          "itemVariant": "variant_456",
          "actualQty": 98
        }
      ],
      "note": "Monthly inventory check - found 2 units missing"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://joptic.jethings.com/stock-reconciliation', {
    method: 'POST',
    headers: {
      'x-store-id': 'store_789',
      'Authorization': 'Bearer <token>',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      items: [
        {
          warehouse: 'warehouse_123',
          itemVariant: 'variant_456',
          actualQty: 98
        }
      ],
      note: 'Monthly inventory check - found 2 units missing'
    })
  });

  const data = await response.json();
  ```

  ```typescript TypeScript theme={null}
  interface ReconciliationItem {
    warehouse: string;
    itemVariant: string;
    actualQty: number;
  }

  interface CreateReconciliationDto {
    items: ReconciliationItem[];
    note?: string;
  }

  const payload: CreateReconciliationDto = {
    items: [
      {
        warehouse: 'warehouse_123',
        itemVariant: 'variant_456',
        actualQty: 98
      }
    ],
    note: 'Monthly inventory check - found 2 units missing'
  };

  const response = await fetch('https://joptic.jethings.com/stock-reconciliation', {
    method: 'POST',
    headers: {
      'x-store-id': 'store_789',
      'Authorization': 'Bearer <token>',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  });

  const data = await response.json();
  ```
</CodeGroup>

**Scenario:**

* System quantity: 100
* Actual quantity: 98
* Difference: -2 (shortage)

### Multiple Items Reconciliation

<CodeGroup>
  ```json Request Body theme={null}
  {
    "items": [
  {
    "warehouse": "warehouse_123",
        "itemVariant": "variant_456",
        "actualQty": 98
      },
      {
    "warehouse": "warehouse_123",
        "itemVariant": "variant_789",
        "actualQty": 55
      },
      {
        "warehouse": "warehouse_456",
        "itemVariant": "variant_123",
        "actualQty": 10
      }
    ],
    "note": "End of month inventory reconciliation"
  }
  ```

  ```bash cURL theme={null}
  curl -X POST https://joptic.jethings.com/stock-reconciliation \
    -H "x-store-id: store_789" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "items": [
        {
          "warehouse": "warehouse_123",
          "itemVariant": "variant_456",
          "actualQty": 98
        },
        {
      "warehouse": "warehouse_123",
          "itemVariant": "variant_789",
          "actualQty": 55
        },
        {
          "warehouse": "warehouse_456",
          "itemVariant": "variant_123",
          "actualQty": 10
        }
      ],
      "note": "End of month inventory reconciliation"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://joptic.jethings.com/stock-reconciliation', {
    method: 'POST',
    headers: {
      'x-store-id': 'store_789',
      'Authorization': 'Bearer <token>',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      items: [
        {
          warehouse: 'warehouse_123',
          itemVariant: 'variant_456',
          actualQty: 98
        },
        {
          warehouse: 'warehouse_123',
          itemVariant: 'variant_789',
          actualQty: 55
        },
        {
          warehouse: 'warehouse_456',
          itemVariant: 'variant_123',
          actualQty: 10
        }
      ],
      note: 'End of month inventory reconciliation'
    })
  });

  const data = await response.json();
  ```
</CodeGroup>

**Processing:**

* All three items are processed in a single transaction
* Each item gets its own reconciliation record
* All records share the same note
* Three separate background jobs are queued (one per item)

### New Stock Found (No Previous Bin)

<CodeGroup>
  ```json Request Body theme={null}
  {
    "items": [
      {
        "warehouse": "warehouse_123",
        "itemVariant": "variant_new",
        "actualQty": 25
      }
    ],
    "note": "Found unrecorded stock in warehouse"
  }
  ```

  ```bash cURL theme={null}
  curl -X POST https://joptic.jethings.com/stock-reconciliation \
    -H "x-store-id: store_789" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "items": [
        {
      "warehouse": "warehouse_123",
          "itemVariant": "variant_new",
          "actualQty": 25
        }
      ],
      "note": "Found unrecorded stock in warehouse"
    }'
  ```
</CodeGroup>

**Scenario:**

* System quantity: 0 (no bin exists)
* Actual quantity: 25
* Difference: +25
* **Result:** New bin entry is created with qty = 25

### Zero Quantity Reconciliation

<CodeGroup>
  ```json Request Body theme={null}
  {
    "items": [
      {
        "warehouse": "warehouse_123",
        "itemVariant": "variant_456",
        "actualQty": 0
      }
    ],
    "note": "Item completely out of stock"
  }
  ```

  ```bash cURL theme={null}
  curl -X POST https://joptic.jethings.com/stock-reconciliation \
    -H "x-store-id: store_789" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "items": [
        {
      "warehouse": "warehouse_123",
          "itemVariant": "variant_456",
          "actualQty": 0
        }
      ],
      "note": "Item completely out of stock"
    }'
  ```
</CodeGroup>

**Scenario:**

* System quantity: 5
* Actual quantity: 0
* Difference: -5 (all items missing)
* **Result:** Bin is updated to qty = 0

***

## Example Responses

### Success Response (Single Item)

```json theme={null}
{
  "data": [
    {
      "id": "recon_abc123",
      "note": "Monthly inventory check",
      "systemQty": "100.00",
      "actualQty": "98.00",
      "differenceQty": "-2.00",
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    }
  ]
}
```

### Success Response (Multiple Items)

When multiple items are reconciled, each gets its own record in the response array:

```json theme={null}
{
  "data": [
    {
      "id": "recon_001",
      "note": "End of month reconciliation",
      "systemQty": "100.00",
      "actualQty": "98.00",
      "differenceQty": "-2.00",
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    },
    {
      "id": "recon_002",
      "note": "End of month reconciliation",
      "systemQty": "50.00",
      "actualQty": "55.00",
      "differenceQty": "5.00",
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    },
    {
      "id": "recon_003",
      "note": "End of month reconciliation",
      "systemQty": "0.00",
      "actualQty": "10.00",
      "differenceQty": "10.00",
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    }
  ]
}
```

***

## Error Responses

### 400 Bad Request

#### Validation Errors

* **Empty items array**: `"At least one item is required"`
  * Occurs when `items` array is empty or not provided

* **Missing required fields**: Validation errors for missing `warehouse`, `itemVariant`, or `actualQty`
  * `"warehouse must be a string"`
  * `"warehouse should not be empty"`
  * `"itemVariant must be a string"`
  * `"itemVariant should not be empty"`
  * `"actualQty must be a number"`

### 500 Internal Server Error

* Database transaction failures
* Queue processing errors
* Foreign key constraint violations (invalid warehouse or itemVariant IDs)

***

## Important Notes

### 1. Atomic Transaction

All items in a single request are processed within one database transaction. This means:

* **All succeed or all fail**: If any item fails validation or processing, the entire request is rolled back
* **Consistent state**: You won't have partial reconciliations

### 2. System Quantity Calculation

The system quantity is calculated as:

* Sum of all **active** bins for the warehouse/itemVariant combination
* Only bins where `isActive = true` are included
* If no active bins exist, system quantity is `0`

### 3. Difference Calculation

```
differenceQty = actualQty - systemQty
```

* **Positive difference**: More items found than system shows (surplus)
* **Negative difference**: Fewer items found than system shows (shortage)
* **Zero difference**: Perfect match

### 4. Asynchronous Stock Updates

Stock ledger entries and bin updates happen **asynchronously** via a queue system:

* The endpoint returns immediately after creating reconciliation records
* Background jobs process stock updates
* Jobs have retry logic (3 attempts by default)
* If a job fails, it remains in the queue for manual review

<Warning>
  The reconciliation record is created synchronously, but the actual stock adjustment happens asynchronously. The bin quantity will be updated shortly after the request completes.
</Warning>

### 5. Bin Quantity Update

The bin quantity is set to the **absolute actual quantity**, not adjusted by the difference:

* **Before reconciliation**: Bin qty = 100
* **Actual count**: 98
* **After reconciliation**: Bin qty = 98 (not 100 - 2)

This ensures the bin quantity matches the physical count exactly.

### 6. Stock Ledger Entry

Every reconciliation creates a stock ledger entry for audit purposes:

* `qtyChange` is always `0` (reconciliation sets absolute quantity, not relative)
* `qtyAfterTransaction` is set to the actual quantity
* `voucherType` is `"stock_reconciliation"`
* `voucher` links to the reconciliation record ID
* `reason` contains the note from the reconciliation

### 7. Shared Note

The `note` field is shared across all items in a single request. If you need different notes for different items, make separate requests.

### 8. Decimal Precision

All quantities are stored as strings (decimal type in database) to maintain precision. The response returns them as strings to preserve exact values.

### 9. Multiple Warehouses

You can reconcile items from different warehouses in a single request. Each item specifies its own warehouse.

### 10. New Stock Creation

If an item variant doesn't have a bin entry, the reconciliation will:

* Create a new bin entry with the actual quantity
* Set `isActive = true`
* Create a stock ledger entry documenting the new stock

***

## Workflow Example

### Typical Inventory Reconciliation Process

1. **Physical Count**
   * Staff counts physical inventory in the warehouse
   * Records actual quantities for each item variant

2. **Create Reconciliation**
   ```bash theme={null}
   POST /stock-reconciliation
   {
     "items": [
       {
         "warehouse": "warehouse_123",
         "itemVariant": "variant_456",
         "actualQty": 98
       }
     ],
     "note": "Monthly inventory - 2 units missing"
   }
   ```

3. **System Processing**
   * System calculates current quantity: 100
   * Calculates difference: -2
   * Creates reconciliation record
   * Queues background job

4. **Background Job Execution**
   * Creates stock ledger entry (qtyChange=0, qtyAfterTransaction=98)
   * Updates bin quantity to 98
   * Job completes successfully

5. **Verification**
   * Check reconciliation record via GET endpoint
   * Verify bin quantity was updated
   * Review stock ledger entry for audit trail

***

## Integration with Other Systems

### Stock Ledger

Every reconciliation creates a stock ledger entry:

* Provides complete audit trail
* Links to reconciliation record via `voucher` field
* Can be queried to see all stock adjustments

### Bin Management

Bin quantities are automatically updated:

* Reflects actual physical counts
* Maintains accurate inventory levels
* Supports multi-warehouse scenarios

### Reporting

Reconciliation records can be queried via:

* `GET /stock-reconciliation` - List all reconciliations with pagination
* Filter by warehouse, item variant, date range
* Analyze discrepancies over time

***

## Database Schema

### stockReconciliation Table

```typescript theme={null}
{
  id: string;                    // Primary key (CUID)
  warehouse: string;              // Foreign key to warehouse
  itemVariant: string;            // Foreign key to itemVariant
  systemQty: string;              // System quantity (decimal as string)
  actualQty: string;              // Actual quantity (decimal as string)
  differenceQty: string;          // Difference (decimal as string)
  note?: string;                  // Optional note
  createdAt: Date;               // Creation timestamp
  updatedAt: Date;               // Update timestamp
}
```

### stockLedgerEntry (Created by Background Job)

```typescript theme={null}
{
  id: string;                    // Primary key
  warehouse: string;              // Warehouse ID
  itemVariant: string;            // Item variant ID
  qtyChange: "0";                // Always 0 for reconciliation
  qtyAfterTransaction: string;    // Actual quantity
  voucherType: "stock_reconciliation";
  voucher: string;               // Reconciliation ID
  reason?: string;               // Note from reconciliation
  createdAt: Date;
  updatedAt: Date;
}
```

### bin (Updated by Background Job)

```typescript theme={null}
{
  id: string;
  warehouseId: string;
  itemVariantId: string;
  qty: number;                   // Updated to actualQty
  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
}
```

***

## Related Endpoints

* `GET /stock-reconciliation` - Get paginated list of all reconciliations
  * Supports search, pagination, and sorting
  * Filters by store (via warehouse relationship)

***

## Best Practices

1. **Batch Related Items**: Group items from the same warehouse or reconciliation session in a single request for better performance and consistency.

2. **Use Descriptive Notes**: Always provide meaningful notes to explain why the reconciliation was performed (e.g., "Monthly inventory", "Damaged goods adjustment", "Theft investigation").

3. **Verify Before Reconciling**: Check system quantities before performing reconciliation to understand expected differences.

4. **Monitor Background Jobs**: Ensure the queue processor is running to complete stock updates. Check job status if bin quantities aren't updating.

5. **Regular Reconciliation**: Perform regular inventory counts to maintain accurate stock levels and identify discrepancies early.

6. **Document Discrepancies**: Use the note field to document reasons for significant differences (theft, damage, counting errors, etc.).

7. **Review Stock Ledger**: Regularly review stock ledger entries to track all inventory adjustments and maintain audit trail.
