@devx-retailos/cms
v0.4.0
Published
Cash Management System for retailOS: shift ops, handovers, petty cash, accumulation.
Downloads
930
Keywords
Readme
@devx-retailos/cms
Cash Management System (CMS) for retailOS. Manages shift operations, cash handovers, petty cash, and in-store cash accumulation for POS stores.
Covers the full cash lifecycle for a shift: open → intraday handovers and petty-cash movements → close, with a running per-store accumulation balance.
Installation
pnpm add @devx-retailos/cmsRegister in medusa-config.ts:
import { CmsModule } from "@devx-retailos/cms"
export default defineConfig({
modules: [
{
resolve: "@devx-retailos/cms",
options: {},
},
],
})Sync permissions at startup (alongside other retailOS modules):
import { CMS_PERMISSIONS } from "@devx-retailos/cms"
import { syncAllPermissions } from "@devx-retailos/rbac"
await syncAllPermissions([...CMS_PERMISSIONS])Run migrations after adding the module:
medusa plugin:db:generate @devx-retailos/cmsConfiguration
No plugin options are required. The module registers under the identifier "cms" and is fully configured through Medusa's dependency injection system.
Usage
Resolve the service from the Medusa container:
import { CMS_MODULE } from "@devx-retailos/cms"
import type { CmsModuleService } from "@devx-retailos/cms"
const cmsService: CmsModuleService = container.resolve(CMS_MODULE)Shift operations
// Open a shift
await cmsService.dayStart({
store_id: "store_01",
employee_id: "emp_01",
opening_amount: 5000,
})
// Close a shift
await cmsService.dayEnd({
store_id: "store_01",
employee_id: "emp_01",
closing_amount: 4800,
})Cash handover
const handover = await cmsService.handover({
store_id: "store_01",
employee_id: "emp_01",
handover_amount: 2000,
type: "CR", // "CR" = cash coming in, "DB" = cash going out
remark: "shift change",
})Petty cash
// Add petty cash
await cmsService.addPettyCash({
store_id: "store_01",
employee_id: "emp_01",
amount: 500,
source: "self", // "self" | "from_petty_cash"
})
// Record an expense
await cmsService.addExpense({
store_id: "store_01",
employee_id: "emp_01",
amount: 150,
category: "supplies",
sub_category: "stationery",
reason: "pens and paper",
})Queries
// Current in-store cash balance
const accumulation = await cmsService.getAccumulation("store_01")
// Shift open/close log
const logs = await cmsService.getShiftLogs({
store_id: "store_01",
from: new Date("2024-01-01"),
to: new Date("2024-01-31"),
limit: 50,
})
// Daily cash reconciliation report
const report = await cmsService.getReconciliationReport({
store_id: "store_01",
date: "2026-06-23", // YYYY-MM-DD (UTC)
net_cash_sales: 28450, // optional — cash collected from orders (default 0)
})
// report.expected_closing_balance = opening + net_cash_sales
// + petty_cash_in - petty_cash_out + total_handovers_in - total_handovers_out
// report.variance = closing_balance - expected (null until day_end runs)
// report.is_balanced = variance === 0
net_cash_salescomes from your order module. Pass it from the brand's order query or viasdk-client. When omitted the report uses 0 and computes everything from CMS data only.
Permissions
| Key | Description |
|-----|-------------|
| cms.handover.read | View cash handover records |
| cms.handover.create | Create a cash handover record |
| cms.shift.operate | Open and close a shift (day start / day end) |
| cms.petty_cash.read | View petty cash balance and transactions |
| cms.petty_cash.operate | Add petty cash or record expenses |
| cms.accumulation.read | View current cash accumulation for a store |
| cms.reconciliation.read | View daily cash reconciliation report with variance analysis |
HTTP API
All routes are mounted under /admin/retailos/cms and require a valid Medusa admin session.
Handovers
| Method | Path | Description |
|--------|------|-------------|
| GET | /admin/retailos/cms/handovers | List handovers with filtering, pagination, and linked employee/store data |
| POST | /admin/retailos/cms/handovers | Create a cash handover |
| GET | /admin/retailos/cms/handovers/:id | Retrieve a single handover by ID |
GET /admin/retailos/cms/handovers query params:
| Param | Type | Description |
|-------|------|-------------|
| store_id | string | Filter by store |
| employee_id | string | Filter by employee |
| type | CR | DB | Filter by direction |
| handover_id | string | Exact match on handover_id |
| q | string | Full-text search on handover_id (ILIKE) |
| created_at | string / object | Date range filter |
| limit | number | Max 500, default 20 |
| offset | number | Default 0 |
| order | string | Field name; prefix - for DESC (e.g. -created_at) |
Response includes handovers[] and count. Each handover carries an embedded employee (id, first_name, last_name) and store (id, name, store_code) when module links resolve.
POST /admin/retailos/cms/handovers body (Zod-validated):
{
"store_id": "store_01",
"employee_id": "emp_01",
"handover_amount": 2000,
"type": "CR",
"remark": "shift change",
"handover_id": null,
"image_url": null,
"metadata": null
}Petty cash
| Method | Path | Description |
|--------|------|-------------|
| GET | /admin/retailos/cms/petty-cash | List petty cash transactions with employee enrichment |
Query params: store_id, limit (max 500, default 100), offset.
Each transaction includes an embedded employee object when the employee module is available.
Reconciliation
| Method | Path | Description |
|--------|------|-------------|
| GET | /admin/retailos/cms/reconciliation | Daily cash reconciliation report |
Required params: store_id, date (YYYY-MM-DD). Optional: net_cash_sales (number).
Export
| Method | Path | Description |
|--------|------|-------------|
| GET | /admin/retailos/cms/export | Download all handovers as a CSV file |
Query params: store_id (optional). Returns text/csv with Content-Disposition: attachment.
sdk-client
The cms namespace on RetailOSClient exposes typed wrappers for all routes above. Key methods:
// Reconciliation report
const report = await client.cms.getReconciliationReport({
store_id: "store_01",
date: "2026-06-26",
net_cash_sales: 28450,
})
// List handovers (paginated)
const { handovers, count } = await client.cms.listHandovers({
store_id: "store_01",
limit: 20,
offset: 0,
})
// Export CSV (returns raw text)
const csv = await client.cms.exportHandovers({ store_id: "store_01" })Extension points
This module has no pluggable adapters or strategy interfaces. All cash logic is handled internally by CmsModuleService.
Module links — resolved automatically by Medusa's link layer:
| Link | Cardinality | |------|-------------| | Store → CmsHandover | 1:N | | Employee → CmsHandover | 1:N |
