> For the complete documentation index, see [llms.txt](https://docs.digit.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.digit.org/health/design/architecture/low-level-design/services/health-services/stock.md).

# Stock

## Overview

Stock is the **supply-chain ledger** for a health campaign. Every time a commodity (vaccine, bed net, deworming tablet) moves — received into a warehouse, sent to a facility, handed out to a household, lost, or adjusted — Stock records one row for that movement. It also stores **physical-count reconciliations** so a facility's book stock can be checked against what's actually on the shelf.

In short: *"what did we have, where did it go, and does the count still add up?"*

## Business Flow

* **During campaign setup**, opening stock is received into central/warehouse facilities.
* **During the campaign (runtime)**, field workers and storekeepers record transfers (warehouse → facility → sub-facility) and distributions (facility → beneficiary). Every hand-out of a commodity is a stock **OUT** transaction.
* **Periodically**, a storekeeper does a physical count and submits a **reconciliation** so discrepancies (theft, breakage, miscount) surface early.
* The numbers feed the **dashboards** (via the transformer → Elasticsearch) so programme managers can see stock-on-hand and consumption per facility.

## Key APIs / Entry Points

Base path `/stock/v1` (transactions) and `/stock/reconciliation/v1` (counts). Each entity has single + bulk create/update/delete, and a search.

Transaction endpoints record commodity movements. Reconciliation endpoints record physical stock counts.

| Endpoint                                            | Purpose                                                                                           |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `POST /stock/v1/_create`, `/bulk/_create`           | Record a stock movement (single or bulk).                                                         |
| `POST /stock/v1/_update`, `/bulk/_update`           | Correct a movement.                                                                               |
| `POST /stock/v1/_delete`, `/bulk/_delete`           | Soft-delete a movement.                                                                           |
| `POST /stock/v1/_search`                            | Find movements (by facility, sender/receiver, product variant, **campaignNumber**, since-time …). |
| `POST /stock/reconciliation/v1/_create` … `_search` | Same shape for physical-count reconciliations.                                                    |

**Kafka entry points (async).** Bulk requests land on `create-stock-bulk-topic` / `update-…` / `delete-…` (and the reconciliation equivalents) and are processed by the service's own consumer. Persisted results go out on `save-stock-topic` / `update-stock-topic` / `delete-stock-topic` (+ reconciliation topics) for the persister and transformer.

**Swagger contract:**

{% embed url="<https://editor.swagger.io/?url=https://raw.githubusercontent.com/egovernments/health-campaign-services/v1.0.0/docs/health-api-specs/contracts/stock.yml>" %}

#### Kafka topics

<table><thead><tr><th>Topic</th><th width="165.421875">Dir</th><th>Purpose</th></tr></thead><tbody><tr><td><code>create-stock-bulk-topic</code></td><td>in</td><td>Bulk stock-movement create requests</td></tr><tr><td><code>update-stock-bulk-topic</code></td><td>in</td><td>Bulk stock-movement update requests</td></tr><tr><td><code>delete-stock-bulk-topic</code></td><td>in</td><td>Bulk stock-movement (soft) delete requests</td></tr><tr><td><code>create-stock-reconciliation-bulk-topic</code></td><td>in</td><td>Bulk reconciliation create requests</td></tr><tr><td><code>update-stock-reconciliation-bulk-topic</code></td><td>in</td><td>Bulk reconciliation update requests</td></tr><tr><td><code>delete-stock-reconciliation-bulk-topic</code></td><td>in</td><td>Bulk reconciliation delete requests</td></tr><tr><td><code>save-stock-topic</code></td><td>out</td><td>Persist new stock rows (persister + transformer)</td></tr><tr><td><code>update-stock-topic</code></td><td>out</td><td>Persist stock updates</td></tr><tr><td><code>delete-stock-topic</code></td><td>out</td><td>Persist stock soft-deletes</td></tr><tr><td><code>save-stock-reconciliation-topic</code></td><td>out</td><td>Persist new reconciliations</td></tr><tr><td><code>update-stock-reconciliation-topic</code></td><td>out</td><td>Persist reconciliation updates</td></tr><tr><td><code>delete-stock-reconciliation-topic</code></td><td>out</td><td>Persist reconciliation deletes</td></tr></tbody></table>

## Dependencies

* **idgen** — generates stock record IDs.
* **facility** — validates the facility a movement is for.
* **product** — validates the product variant being moved.
* **project / project-facility** — optional link of a movement to a project's facility.
* **health-services-common / -models** — shared clients, validators, POJOs.
* **Kafka** — async create/update/delete pipeline.
* **egov-persister** (deployed via the `configs/` repo) — actually writes the rows to Postgres off the `save-*` topics.
* **transformer → Elasticsearch** — builds the dashboard read-model from the same topics.
* **Redis** — caching used by the shared repository layer.

## Process Flow

Writes are **asynchronous**: the API validates, enriches and acknowledges, then a Kafka consumer persists. The service does not write Postgres directly — it emits a `save-*` event that **egov-persister** turns into a row, while the **transformer** indexes the same event into Elasticsearch for dashboards.

#### Sequence Diagrams

{% tabs %}
{% tab title="Stock" %}

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/WUqucpw1vR0Yb5B0x5qU/stock_create.png" alt=""><figcaption><p>Stock - Create</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/G2L3tcxwp11wbGJLgArD/stock_update.png" alt=""><figcaption><p>Stock - Update</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/7qXN1DKF0F9iy5g6no9D/stock_search.png" alt=""><figcaption><p>Stock - Search</p></figcaption></figure>
{% endtab %}

{% tab title="Reconciliation" %}

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/Rf6dNnh9YCUEhAVDYy2H/product_variant_create.png" alt=""><figcaption><p>Reconciliation - Create</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/gTJ6v7yvgO0Q97zsUxaV/product_variant_update.png" alt=""><figcaption><p>Reconciliation - Update</p></figcaption></figure>

<figure><img src="https://content.gitbook.com/content/I0KFbSBTySIQ7TCOKnJF/blobs/0a6mKSIvUmvEOjzddmXG/product_variant_search.png" alt=""><figcaption><p>Reconciliation - Search</p></figcaption></figure>
{% endtab %}
{% endtabs %}

#### Data Model

<figure><img src="https://2077406040-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FI0KFbSBTySIQ7TCOKnJF%2Fuploads%2FYClqjeAusj9zTQlrFFqm%2Fimage.png?alt=media&amp;token=3010b3ac-1421-455c-be7e-18a43047f5c4" alt=""><figcaption></figcaption></figure>

### Failure / Retry Handling

* **Async, no batch rollback.** A bulk request returns `202` before persistence. If one record in the batch fails validation in the consumer, it does not roll back the others — check consumer logs and the record's status.
* **Idempotency** is via `clientReferenceId` — re-submitting the same one should not create a duplicate row.
* **Optimistic locking** via `rowVersion` protects concurrent transfer updates. Reconciliation count submissions allow concurrent writes and use last-write-wins.
* **Soft delete** (`isDeleted`) everywhere — nothing is hard-deleted; unique constraints include the delete flag.
* If the **persister config** for the stock topics is missing/stale in an environment, the API will accept writes but rows will silently not appear in Postgres — a classic "it worked in QA" trap.

### Known Risks / Limitations

* **`quantity` is signed** — negative values are valid (loss/consumption) and there is no DB constraint; bad data is an app/validation concern, not a DB guard.
* **`transactingPartyId` + `transactingPartyType` are polymorphic with no foreign key** — validation is app-level only.
* **`transactionType` / `transactionReason` are free strings.** Convention is IN/OUT/LOSS/ADJUSTMENT/TRANSFER, but the DB won't stop other values.
* **Reconciliation is per facility, not per product variant** — variant-level discrepancies live in a comments field, not structured columns.
* **`dateOfEntry` (field date) ≠ `createdTime` (system date)** — both matter for audit; don't conflate them.
* **Relaxed concurrency on the count path** (v2.1) means simultaneous count submissions are accepted with last-write-wins — acceptable for counts, but a behavioural change QA must be aware of.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.digit.org/health/design/architecture/low-level-design/services/health-services/stock.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
