Install
openclaw skills install @mailnike/erpclawAI-native ERP system. Full accounting, invoicing, inventory, purchasing, tax, billing, HR, payroll, advanced accounting (ASC 606/842, intercompany, consolidation), and financial reporting (including P&L / trial balance / spend grouped by department, project, cost center, location, or fund). 496 actions across 14 domains, 45 optional expansion modules (user-approved install from GitHub). Double-entry GL, immutable audit trail, US GAAP compliant. Licensed under GNU GPL v3 (the marketplace "MIT-0" badge is a ClawHub platform default; the LICENSE.txt in the bundle is GPL v3).
openclaw skills install @mailnike/erpclawFull-Stack ERP Controller for ERPClaw. Company setup, chart of accounts, journal entries, payments, tax, financial reports, customers, sales, suppliers, purchasing, inventory, billing, HR, US payroll, advanced accounting (ASC 606/842, intercompany, consolidation), and 45 optional industry modules. Local-first SQLite, double-entry GL, immutable audit trail.
Security: Local-first. Parameterized queries. RBAC (PBKDF2). Immutable GL. Sensitive fields encrypted at the column level. Network access limited to fetch-exchange-rates (public API) and user-approved install-module from github.com/avansaber/*.
Runtimes: Runs on OpenClaw (primary). Experimental support for the Hermes Agent runtime via a GitHub tap. Install root is set by the ERPCLAW_HOME environment variable; unset/blank defaults to ~/.openclaw/erpclaw (zero behavior change for OpenClaw).
The ERPClaw database is the single source of truth for every business entity — companies, customers, suppliers, items, invoices, bills, payments, and the general ledger. Before answering what exists or acting on an entity, look it up in the ERP and ground your reply in that result:
list-companies, list-customers, list-items). Never answer from memory, earlier conversations, workspace files, or any other context.resolve-item --name "<their words>" first; use the single match, or ask the user to choose when multiple_matches is true, before invoicing/ordering.--company "Northwind" (never ask for or invent an ID) and let the action resolve it. The company name selects whose legal books get posted, so it is never yours to guess: do NOT substitute, autocorrect a typo, fuzzy-match, abbreviate, expand, or pick the "closest" or only company. Exact match only: "Northwynd" is not "Northwind Traders", and "Northwind" is not "Northwind Traders". Read list-companies to ground, NOT to choose a near-match.--company "<name>" returns a not-found error (it lists available_companies), STOP: tell the user that company does not exist, show those available names, and ask which they mean. Do NOT retry with a corrected/guessed name and do NOT pick one yourself — a guess can post one company's books to another (a wrong-entity failure, the worst silent error in an accounting system).list-companies does not exist in the books — do not offer it.list-*/get-* for it in THIS turn and seen it returned. This is mandatory and has no exception. Asked to add or create something (a new customer, a received shipment of stock, a new supplier)? Do not refuse it as an existing duplicate from memory: call the relevant lookup (e.g. list-customers) this turn first; if it returns no match, CREATE it — the default for an add/create request is to act, not to refuse. A name, number, or "already set up" feeling from earlier in the chat, your workspace context, or training is NOT evidence it is in the books; if you have not run the lookup this turn, you do not know it exists. Conversely, when the ERP DOES return a document another session created, treat it as authoritative and act on it — do not refuse because a session was reset or your notes say the data is stale. The ERP query is the only truth; your memory is not.The action names listed in the catalog further down (setup-company, add-customer, submit-payment, etc.) are internal routing identifiers. Never use them in replies the user sees.
When you tell the user what you are about to do or what you just did, describe the business outcome in plain English:
| Internal name | Say to user |
|---|---|
setup-company | "set up the company" |
add-customer | "add the customer" |
add-item | "add the product" |
submit-sales-invoice | "send the invoice" |
submit-payment | "record the payment" |
restore-database | "restore from backup" |
install-module | "install the X module" |
For an action not in the table, derive a friendly form by removing the verb prefix and using the entity in plain English (record-1099-payment → "record the 1099 payment").
The user is a small business owner, founder, or store operator. They know "customer", "invoice", "payment". They have not seen the action catalog and never should.
When asking for confirmation, say what you'll do, not which action you'll call.
add-customer, confirm?"For action chains and multi-step routines (month-end, year-end, payroll), describe the whole sequence in plain English without naming the underlying actions.
add-customer ABC, then create-sales-invoice, then submit-sales-invoice." / "Month-end: revalue-foreign-balances, close-fiscal-year, trial-balance."When narrating a completed action, do not include the action name.
add-customer and got ID 12345."If the user explicitly asks "which command did you run?" or "what's the technical name?", politely decline.
add-customer with name=Bob, company=BigCo."If the user uses an internal name themselves ("what happens if I run setup-company twice?"), gently translate in your reply ("setting up a company twice would be rejected, since names are unique") without echoing the name or correcting the user.
The same rule applies to the bookkeeping behind an action: erpclaw returns the technical record (double-entry GL legs, ledger fields, status flags, internal IDs), but you confirm the business outcome, translate every internal label, and keep the mechanics out of the reply. Never describe the double-entry posting, the debit/credit legs, the account names, "no stock movement", or "stock ledger entry". Say what changed in business terms: "I recorded the bill, it's in your books and shows as owed to Gotham Steel."
Activate when user mentions: ERP, accounting, invoice, sales order, purchase order, customer, supplier, inventory, payment, GL, trial balance, P&L, balance sheet, P&L / spend / revenue by department or project or cost center or location or fund, tax, billing, modules, install module, onboard, CRM, manufacturing, healthcare, education, retail, employee, HR, payroll, salary, leave, attendance, expense claim, W-2, garnishment, integration. "By department / project / cost center / location / fund" reporting is a first-class capability (accounting dimensions), never hand-rolled: tag entries at booking time with --dimension-key/--dimension-value, then report with profit-and-loss --group-by <dimension> (P&L by that dimension) or multi-dim-trial-balance --group-by <dimension> (whole trial balance) — NOT a cost_center column or raw SQL — see the Journal Entries and Financial Reports rows.
When a user describes their business: detect type (e.g., "dental practice" → dental), ask the user to confirm before proceeding, then set the company up with that industry. (Internal routing only: invoke setup-company with --industry <type>. Never name the action to the user.) Industry values: retail, restaurant, healthcare, dental, veterinary, construction, manufacturing, legal, agriculture, hospitality, property, school, university, nonprofit, automotive, therapy, home-health, consulting, distribution, saas. When a user asks about a service or integration not currently installed, search the module registry and suggest installation (never auto-install without user approval).
python3 {baseDir}/scripts/erpclaw-setup/db_query.py --action initialize-database
python3 {baseDir}/scripts/db_query.py --action seed-defaults --company-id <id>
python3 {baseDir}/scripts/db_query.py --action setup-chart-of-accounts --company-id <id> --template us_gaap
High-impact actions require the --user-confirmed flag on every invocation; the foundation router rejects unflagged calls with a structured JSON error. Read-only actions (list, get, reports) run without the flag.
The flag confirms consent the user already gave — it is not a request to pause. When the user has clearly asked for an action ("send the invoice", "record the payment", "post that entry"), pass --user-confirmed in that same call and act. Do NOT draft the steps and then ask "want me to submit?" — that re-asks for a yes you already have, and nothing is recorded. This is the default for every routine, reversible action: submit-*, add-*, create-*, approve-*.
Re-confirm a second time ONLY for the small destructive set, where a mistake is hard or impossible to undo: closing the fiscal year (close-fiscal-year), restoring from backup (restore-database), installing a module (install-module), reconciling foundation files (rollback-foundation), and generating a bank-payment file (generate-nacha-file). For these, state plainly what will happen and get an explicit yes before passing the flag.
| Action | Description |
|---|---|
initialize-database / setup-company / update-company / get-company / list-companies | DB init & company CRUD |
migrate | Run pending schema migrations (migrations/NNN_*.py) in order, recording each in the erpclaw_schema_migration ledger. Idempotent + dialect-aware; --dry-run lists pending without applying. Run on install and on upgrades. |
add-currency / list-currencies / add-exchange-rate / get-exchange-rate / list-exchange-rates / fetch-exchange-rates | Currency & FX |
add-payment-terms / list-payment-terms / add-uom / list-uoms / add-uom-conversion | Terms & UoMs |
seed-defaults / seed-demo-data / check-installation / install-guide / setup-web-dashboard / tutorial / onboarding-step / status | Seeding & utilities |
add-account-type / list-account-types / deactivate-account-type / add-voucher-type / list-voucher-types / deactivate-voucher-type / validate-registry-completeness | Type/status registry admin (M0): register/inspect/soft-disable the account_type / voucher_type values that replaced hardcoded CHECK constraints |
add-custom-field / list-custom-fields / remove-custom-field / set-custom-field-value / get-custom-field-values | Custom fields (M1): define user-defined fields on any core table and store/read their values. Write-side actions in selling/buying/inventory accept --custom-fields '{"name":"value"}' and return them on get |
set-advance-account | Advances (S2): configure a company's B1-style advance sub-account (--type customer→liability, --type supplier→asset); submit-payment then routes the unallocated advance leg there |
add-user / update-user / get-user / list-users / set-password | User management |
add-role / list-roles / assign-role / revoke-role / seed-permissions | RBAC & security |
link-telegram-user / unlink-telegram-user / check-telegram-permission | Telegram integration |
backup-database / list-backups / verify-backup / restore-database / cleanup-backups | DB backup/restore. cleanup-backups permanently deletes old backup files per the retention policy (keeps 7 daily / 4 weekly / 12 monthly) and requires --user-confirmed like restore-database |
set-credential / get-credential / list-credentials / delete-credential / migrate-credentials | Encrypted credential management |
import-master-key-from-backup | Cross-machine restore: install master key from a backup taken on another machine |
get-audit-log / get-schema-version / update-regional-settings / onboard | System admin |
| Action | Description |
|---|---|
setup-chart-of-accounts / add-account / update-account / get-account / list-accounts | Account CRUD. update-account --account-type retypes an account (validated against account_type_registry, same group guard as add-account); when the account already carries posted GL entries it also needs --reclassify-posted, because every report that filters on account_type re-reads that history under the new type. root_type is not updatable. Both surfaces also refuse an account_type that cannot sit on the account's root_type (a bank/cash/receivable type on a P&L root, a revenue type on a balance-sheet root, and so on) — the accepted roots per type are ACCOUNT_TYPE_ROOT_TYPES in erpclaw-gl/db_query.py, derived from the shipped charts; a type nobody registered a root for is unconstrained |
freeze-account / unfreeze-account / get-account-balance / check-gl-integrity | Account management |
post-gl-entries / reverse-gl-entries / list-gl-entries | GL posting |
add-fiscal-year / list-fiscal-years / validate-period-close / close-fiscal-year / reopen-fiscal-year | Fiscal year |
add-cost-center / list-cost-centers / add-budget / list-budgets | Cost centers & budgets |
add-dimension / list-dimensions / update-dimension / deactivate-dimension | Accounting dimensions (M6): register/inspect/update/retire the dimension keys (department, project, location, fund, etc.) that drive gl_entry.dimensions_json (enforced as GL validation step 13). To make a posting reportable "by ", tag it at entry time with --dimension-key/--dimension-value on the journal/invoice/payment actions — then report with multi-dim-trial-balance / dimension-balance-report (see Financial Reports). deactivate-dimension is blocked while recent live GL still references the key |
seed-naming-series / next-series / revalue-foreign-balances | Naming & FX revaluation |
import-chart-of-accounts / import-opening-balances | CSV import |
| Action | Description |
|---|---|
add-journal-entry / update-journal-entry / get-journal-entry / list-journal-entries | JE CRUD. When the user attributes an entry to a department / project / location / fund / cost center (e.g. "office supplies for Engineering", "travel on the Apollo project"), tag it with --dimension-key department --dimension-value Engineering — do NOT add a "cost center" line/column or hand-roll the attribution; the dimension tag is what makes it reportable later with multi-dim-trial-balance. add-journal-entry --cwip-asset-id <A> tags the JE; submit records its CWIP debit leg as a cost accumulation against the asset (S3) |
submit-journal-entry / cancel-journal-entry / amend-journal-entry / delete-journal-entry / duplicate-journal-entry | JE lifecycle |
create-intercompany-je | Intercompany JE |
add-recurring-template / update-recurring-template / list-recurring-templates / get-recurring-template / process-recurring / delete-recurring-template | Recurring JEs |
| Action | Description |
|---|---|
add-payment / update-payment / get-payment / list-payments / submit-payment / cancel-payment / delete-payment | Payment CRUD & lifecycle. Plain-words route — "customer paid less; treat the missing amount as a write-off / discount / fee, in one payment": that is ONE add-payment with --deductions, then submit-payment — never a payment followed by a separate write-off step. add-payment --deductions (JSON array of {account_id, amount, type}; type = tds/commission/early_payment_discount/write_off/other) records short-pays: submit posts the deduction GL legs and the deducted total clears the allocated invoices ($980 wire + $20 discount fully clears a $1,000 invoice); cancel reverses them. write_off is the residual a customer never pays, taken at payment time ($950 on a $1,000 invoice) — for a no-cash write-off use write-off-invoice |
create-payment-ledger-entry / get-outstanding / get-unallocated-payments / allocate-payment / reconcile-payments / bank-reconciliation / write-off-invoice | Reconciliation. write-off-invoice writes off bad debt on ONE open invoice with no cash involved: --voucher-id + --write-off-amount + --write-off-account-id (bad-debt expense) + --reason posts a balanced GL pair under the invoice's own voucher and drops its outstanding (writing off the whole residual marks it paid); cancelling the invoice reverses it. Dated by the DECISION: posting date defaults to today, so an invoice in a closed year is still write-off-able; --posting-date backdates deliberately. Confirmation-gated. One invoice at a time — there is no batch write-off |
list-open-advances / apply-advance-to-invoice | Advances (S2): SAP Business One vocabulary aliases for get-unallocated-payments / allocate-payment (same semantics) |
| Action | Description |
|---|---|
add-tax-template / update-tax-template / get-tax-template / list-tax-templates / delete-tax-template | Tax template CRUD |
resolve-tax-template / calculate-tax / add-tax-category / list-tax-categories / add-tax-rule / list-tax-rules | Tax rules |
add-item-tax-template / add-tax-withholding-category / get-withholding-details | Withholding |
record-withholding-entry / record-1099-payment / generate-1099-data | 1099 reporting |
| Action | Description |
|---|---|
trial-balance / profit-and-loss / balance-sheet / cash-flow / general-ledger / party-ledger / multi-dim-trial-balance / dimension-balance-report | Core statements. For a "P&L by DEPARTMENT / project / cost center / location / fund / any dimension" ask, call profit-and-loss --group-by <dimension> — it returns revenue / expenses / net per dimension value (with an explicit (untagged) bucket), so you NEVER hand-roll the split or write SQL over cost_center. Example: "show me this month's P&L broken down by department" → profit-and-loss --group-by department --from-date <start> --to-date <end>. For grouping the WHOLE trial balance (all account types, not just income/expense) use multi-dim-trial-balance --group-by "project,department"; dimension-balance-report --dimension K gives one dimension's balances. --group-by takes ONE dimension on profit-and-loss; group by an UNREGISTERED dimension errors (run list-dimensions). Without --group-by, profit-and-loss returns ONE company-wide statement. All four headline statements + general-ledger also accept repeated --dimension-key/--dimension-value filters to scope (and profit-and-loss filters THEN groups) |
ar-aging / ap-aging / budget-vs-actual (alias: budget-variance) | Aging & budget |
tax-summary / payment-summary / gl-summary / comparative-pl / check-overdue | Summaries |
add-elimination-rule / list-elimination-rules / run-elimination / list-elimination-entries | RETIRED — do not call. They posted group eliminations into the operating companies' own books, which unbalanced each entity. Intercompany elimination is the consolidation layer's job: add-consolidation-group → add-group-entity (one per entity) → add-ic-transaction → approve-ic-transaction → post-ic-transaction → generate-elimination-entries → consolidation-trial-balance-report / ic-elimination-report. approve/post are required: only POSTED IC transactions are eliminated. Calling one returns that steer |
| Action | Description |
|---|---|
add-customer / update-customer / get-customer / list-customers / import-customers | Customer CRUD (add/update accept --email / --phone — dedicated structured columns; --default-price-list-id sets the customer's default selling price list, consulted first when a line has no explicit rate) |
add-quotation / update-quotation / get-quotation / list-quotations / submit-quotation / convert-quotation-to-so | Quotations |
add-sales-order / update-sales-order / get-sales-order / list-sales-orders / submit-sales-order / cancel-sales-order / amend-sales-order / close-sales-order | Sales orders |
add-blanket-order / get-blanket-order / list-blanket-orders / submit-blanket-order / create-so-from-blanket | Blanket orders |
create-delivery-note / get-delivery-note / list-delivery-notes / submit-delivery-note / cancel-delivery-note / add-packing-slip / get-packing-slip / list-packing-slips | Delivery & packing |
create-sales-invoice / update-sales-invoice / get-sales-invoice / list-sales-invoices / submit-sales-invoice / cancel-sales-invoice | Invoicing |
create-credit-note / list-credit-notes / update-invoice-outstanding | Credit notes; update-invoice-outstanding (AP twin: update-purchase-outstanding) applies a payment amount to an open invoice and posts the matching payment-ledger adjustment row in the same transaction (summary ≡ detail) |
add-sales-partner / list-sales-partners | Sales partners |
add-recurring-template / update-recurring-template / list-recurring-templates / generate-recurring-invoices | Recurring invoices |
add-intercompany-account-map / list-intercompany-account-maps / create-intercompany-invoice / list-intercompany-invoices / cancel-intercompany-invoice | Intercompany |
check-credit-limit / place-customer-on-hold | Credit control: compute available credit (limit minus outstanding AR); place customer on hold / suspend / restore active |
add-dunning-level / run-dunning-cycle / list-dunning-runs | Dunning: configure escalation levels (at N days overdue → email / call / hold / suspend); run a cycle that matches overdue invoices to their highest applicable level and applies the configured action — email levels enqueue a dunning email via the erpclaw-alerts send-email action and record the outbox id on dunning_run.generated_email_id (missing customer email or template skips-with-note, never failing the cycle); view run history |
| Action | Description |
|---|---|
add-supplier / update-supplier / get-supplier / list-suppliers / import-suppliers | Supplier CRUD (add/update accept --email / --phone — dedicated structured columns) |
add-material-request / submit-material-request / list-material-requests / get-material-request / create-po-from-material-request | Material requests. create-po-from-material-request --material-request-id MR --supplier-id S copies the remaining unordered lines onto a draft PO (rate = item's last purchase rate, else standard rate; per-line overrides via --items JSON, qty 0 skips a line), bumps each line's ordered_qty, and rolls the request status to partially_ordered/ordered — call again to order the remainder |
add-rfq / submit-rfq / list-rfqs / add-supplier-quotation / list-supplier-quotations / compare-supplier-quotations | RFQs & quotes |
add-purchase-order / update-purchase-order / get-purchase-order / list-purchase-orders / submit-purchase-order / cancel-purchase-order / close-purchase-order | Purchase orders |
add-blanket-po / get-blanket-po / list-blanket-pos / submit-blanket-po / create-po-from-blanket / create-po-from-so / create-drop-ship-order | Blanket POs & drop ship |
create-purchase-receipt / get-purchase-receipt / list-purchase-receipts / submit-purchase-receipt / cancel-purchase-receipt | Receipts |
create-purchase-invoice / update-purchase-invoice / get-purchase-invoice / list-purchase-invoices / submit-purchase-invoice / cancel-purchase-invoice | Purchase invoices. create-purchase-invoice --cwip-asset-id <A> (standalone cost bill) routes the expense GL to the asset's CWIP account + records a cost accumulation on submit (S3) |
create-debit-note / add-landed-cost-voucher / list-landed-cost-vouchers / get-landed-cost-voucher / cancel-landed-cost-voucher / update-receipt-tolerance / update-three-way-match-policy | Adjustments. Landed cost (freight/duty/insurance on received stock): add-landed-cost-voucher posts the GL AND reprices SLE valuation + FIFO layers in one step; cancel-landed-cost-voucher reverses both halves (cancel = reverse, never edit) |
add-recurring-bill-template / list-recurring-bill-templates / generate-recurring-bills | Recurring AP bills (rent, utilities, subscriptions billed TO you): template drafts a purchase invoice each cycle |
Receiving purchased stock — flow: to bring purchased goods into inventory, receive them against their source document so valuation carries automatically. Canonical flow: submit-purchase-order (confirms the order + rate) → create-purchase-receipt --purchase-order-id <PO> then submit-purchase-receipt (this values the stock at the PO rate and posts inventory GL) → create-purchase-invoice + submit-purchase-invoice for the bill (leave stock update off — the receipt already moved it) → pay. NEVER use a standalone add-stock-entry material_receipt for purchased goods — even with the cost stated, it does not mark the purchase order received, so the later bill/receipt flow will receive the goods AGAIN and double-count stock. Before any bare "receive stock" request, run list-purchase-orders for an open order covering the item; if one exists, receive via create-purchase-receipt --purchase-order-id. (A rate-less standalone receipt cannot be valued and will be refused regardless.)
| Action | Description |
|---|---|
add-item / update-item / get-item / list-items / resolve-item / import-items / add-item-group / list-item-groups | Item master (resolve-item: resolve a loose/plural user phrase like "20 Brake Pad Sets" to the stored item) |
add-item-attribute / create-item-variant / generate-item-variants / list-item-variants | Item variants |
add-item-supplier / list-item-suppliers / set-item-purchase-uom | Item suppliers |
add-warehouse / update-warehouse / list-warehouses | Warehouses |
add-stock-entry / add-repack-stock-entry / add-material-consumption / get-stock-entry / list-stock-entries / submit-stock-entry / cancel-stock-entry | Stock entries. --entry-type accepts receive / issue / transfer / manufacture / repack / subcontract / consume. A material_receipt requires a stated rate or the item's standard cost (non-purchase adjustments ONLY — check list-purchase-orders first: receiving PO-backed goods here leaves the PO un-received and the Buying flow will double-count the stock later; purchased goods go through the procure-to-pay flow). repack consumes input lines and produces output lines in one warehouse with input value == output value (cost-balanced within $0.01); add-repack-stock-entry --warehouse W --from-item-id I1 --from-qty Q1 --to-item-id I2 --to-qty Q2 [--standard-rate R] is the one-in/one-out shortcut. subcontract (send_to_subcontractor) transfers stock out to a --supplier-warehouse-id (a transit/production warehouse). consume (material_consumption) issues raw material against an active --work-order-id; add-material-consumption --warehouse W --work-order-id WO --item-id I --qty Q is the shortcut. Product-layer guard: a material_receipt for an item on an open purchase-order line, or a material_issue for an item on an open sales-order line, is refused at add and submit with a pointer to the order-backed flow (create-purchase-receipt / create-delivery-note) |
create-stock-ledger-entries / reverse-stock-ledger-entries | RETIRED — do not call. They wrote stock-ledger rows with no balancing GL leg, leaving stock untied to the books. Stock postings go through add-stock-entry → submit-stock-entry (both legs, one transaction); reversal is cancel-stock-entry; corrections are add-stock-reconciliation / revalue-stock. Calling one returns that steer |
get-stock-balance / stock-balance / stock-balance-report / stock-ledger-report / get-projected-qty | Stock reports. get-projected-qty's reserved_qty reads persisted active reservations (M5); falls back to open sales-order lines when none exist |
add-putaway-rule / list-putaway-rules / update-putaway-rule / delete-putaway-rule / apply-putaway-on-receipt | Putaway (M5, warehouse-level). Route received stock to a target warehouse by item or item-group match (--match-item I beats --match-item-group G, then --priority ASC). delete-putaway-rule soft-disables. apply-putaway-on-receipt --stock-entry SE computes the deterministic routing for a material_receipt |
create-pick-list / add-pick-list-item / submit-pick-list / mark-picked / complete-pick-list / cancel-pick-list | Pick lists (M5). create-pick-list --from-sales-order SO drafts a pick from open SO lines; submit-pick-list reserves the qty (hard); mark-picked --pick-list P --item I --picked-qty Q records actuals (full pick → picked); complete-pick-list consumes the reservations and generates a delivery note; cancel-pick-list releases them |
add-reservation / release-reservation / list-reservations | Hard stock reservations (M5, ADR-0026). add-reservation --voucher-type T --item I --warehouse W --qty Q holds stock and is refused if it would exceed available (actual − active reserved); a material_issue that would breach active reservations is blocked. release-reservation --id I frees it |
add-item-alternative / list-item-alternatives / get-best-alternative-for-item / remove-item-alternative | Item-global substitutes (S7, directional). add-item-alternative --item I --alternative A [--priority P --conversion-factor C --notes "..."] (lower priority = preferred; self-ref rejected; pair (a,b) unique but (b,a) is a distinct valid row). get-best-alternative-for-item --item I [--required-qty Q --warehouse W] returns the highest-priority active alternative with enough stock at W (ties by available qty); no match is a clean empty result. remove-item-alternative --id I soft-disables. Manufacturing BOM substitutes inherit from these when a BOM line has none of its own |
add-batch / list-batches / add-serial-number / list-serial-numbers / add-price-list / add-item-price / get-item-price / add-pricing-rule | Batch & serial; NAMED price lists (customer/currency tiers). Rate resolution at document entry (quotation / sales order / standalone invoice) for a line with no explicit rate follows the order: explicit --items rate (respected even at 0) > item_price (the customer's --default-price-list-id if set, else any enabled selling list, honoring min-qty + valid-from/to window) > item.standard_rate > 0. An item's default catalog price is still add-item/update-item --standard-rate; use add-item-price for customer/tier/date-specific selling prices |
add-stock-reconciliation / submit-stock-reconciliation / revalue-stock / list-stock-revaluations / get-stock-revaluation / cancel-stock-revaluation / check-reorder | Reconciliation, revaluation & reorder |
| Action | Description |
|---|---|
add-meter / update-meter / get-meter / list-meters / add-meter-reading / list-meter-readings | Meters |
add-usage-event / add-usage-events-batch | Usage tracking |
add-rate-plan / update-rate-plan / get-rate-plan / list-rate-plans / rate-consumption | Rate plans. All 7 plan types rate: flat / tiered / volume_discount / time_of_use / demand / prepaid_credit / hybrid. TOU tiers (time_of_use_period + time_of_use_hours ranges) must cover 24h with no gaps/overlaps (validated at add/update); demand plans need one demand_type='demand' tier (optional energy tier prices consumption); hybrid plans take --tier-strategy '{"components": [{"type": ..., "tiers": [...]}]}' (non-hybrid components only, no prepaid_credit). rate-consumption extras: --usage-by-period '{"peak": "120", ...}' (TOU), --peak-demand (demand), --customer-id (prepaid_credit — balance preview only, no deduction) |
create-billing-period / run-billing / generate-invoices / sync-billing-period-status / link-billing-period-invoice / unlink-billing-period-invoice / get-billing-period / list-billing-periods | Billing cycles. Plain-words routes — "bill July and make it a real invoice": create-billing-period → run-billing (rates it) → generate-invoices (drafts it) → submit that draft in selling; the period counts as invoiced only once its invoice is GL-posted. "Accounting already invoiced them by hand": that invoice must exist in the books exactly ONCE — if it is already in the system do NOT create it again; if it exists only on paper, record it once in selling (create → submit) first. Either way link-billing-period-invoice then attaches that one submitted invoice to the period; never run generate-invoices for a period already covered — the link is exactly what stops July being billed twice. run-billing reports per-meter rating failures in an errors list (a bad plan reference or unpartitionable TOU usage is a data error, never a silent skip) and deducts prepaid_credit charges from prepaid_credit_balance (insufficient balance = explicit over_limit outcome, no deduction). generate-invoices creates a DRAFT invoice and records the invoice_id link (the double-generation guard) while the period stays rated; it reads invoiced only once that invoice is submitted (GL-posted), materialized by sync-billing-period-status (also run at the start of generate-/run-billing) — a cancelled invoice auto-reverts the period to rated, and only rated/invoiced periods are ever written (other statuses reported, never touched); it also flags (never blocks/skips) a covering live invoice in a per-period warnings array. An invoice can be raised without closing its period: link-billing-period-invoice --billing-period-id P --invoice-id I attaches a submitted/GL-posted invoice (customer must match, period rated, one link only — a second is rejected) and marks the period invoiced; unlink-billing-period-invoice --billing-period-id P [--reason R] resets it to rated/NULL (--reason required while the invoice is live, and to remedy an invoiced-with-NULL-link period, which it reverts to rated); a rated period holding a live DRAFT link is deliberately not unlinkable — submit then cancel the draft in selling, then sync-billing-period-status. Failures leave a period rated for retry with the reason in results |
add-billing-adjustment / add-prepaid-credit / get-prepaid-balance | Adjustments & prepaid |
list-billing-runs / get-billing-run / resume-billing-run | Crash-safe batch registry (billing_run). run-billing, generate-recurring-invoices and process-recurring record one target per meter/template, each in its own transaction; resume-billing-run --run-id R re-runs a crashed/partial run with zero duplicate documents (completed runs refuse). list-billing-runs [--status S --run-type T --from-date D --to-date D]; get-billing-run --run-id R returns header + per-target statuses |
| Action | Description |
|---|---|
add-revenue-contract / update-revenue-contract / get-revenue-contract / list-revenue-contracts | Revenue contracts |
add-performance-obligation / list-performance-obligations / satisfy-performance-obligation | ASC 606 |
add-variable-consideration / list-variable-considerations / modify-contract | Variable consideration |
calculate-revenue-schedule / generate-revenue-entries / revenue-waterfall-report / revenue-recognition-summary | Revenue recognition |
recognize-schedule-entry / update-performance-obligation / update-schedule-amounts | Revenue schedule management |
add-lease / update-lease / get-lease / list-leases / classify-lease | ASC 842 leases |
calculate-rou-asset / calculate-lease-liability / generate-amortization-schedule / record-lease-payment | Lease calculations |
lease-maturity-report / lease-disclosure-report / lease-summary | Lease reports |
add-ic-transaction / update-ic-transaction / get-ic-transaction / list-ic-transactions | Intercompany |
approve-ic-transaction / post-ic-transaction / add-transfer-price-rule / list-transfer-price-rules | IC approvals |
ic-reconciliation-report / ic-elimination-report | IC reports |
add-consolidation-group / list-consolidation-groups / add-group-entity / add-currency-translation | Consolidation setup |
run-consolidation / generate-elimination-entries / consolidation-trial-balance-report / consolidation-summary / list-elimination-surplus / remove-elimination-surplus | Consolidation. generate-elimination-entries is safe to re-run: it eliminates only posted IC transactions this group+period has not eliminated yet, and outcome reports created / already_eliminated / nothing_to_eliminate. The trial balance also names any UNLINKED ic_elimination rows (residue an earlier duplicate-generation defect may have left, inflating total_eliminations); list-elimination-surplus shows them and remove-elimination-surplus deletes exactly those rows — report-only until --confirm, every deletion audited with the full removed row |
standards-compliance-dashboard | ASC 606/842 compliance |
| Action | Description |
|---|---|
add-employee / update-employee / get-employee / list-employees / record-lifecycle-event | Employee CRUD; --ssn stored encrypted, returned as last-4 only |
add-employee-bank-account / list-employee-bank-accounts / add-employee-document / get-employee-document / list-employee-documents / check-expiring-documents | Employee details |
add-department / list-departments / add-designation / list-designations | Org structure |
add-leave-type / list-leave-types / add-leave-allocation / get-leave-balance | Leave config |
add-leave-application / approve-leave / reject-leave / list-leave-applications | Leave requests |
mark-attendance / bulk-mark-attendance / list-attendance / add-holiday-list | Attendance |
add-shift-type / update-shift-type / list-shift-types / assign-shift / list-shift-assignments | Shift management |
add-regularization-rule / apply-attendance-regularization | Attendance regularization |
add-expense-claim / submit-expense-claim / approve-expense-claim / reject-expense-claim / update-expense-claim-status / list-expense-claims | Expenses |
add-salary-component / list-salary-components / add-salary-structure / get-salary-structure / list-salary-structures | Salary config |
add-salary-assignment / list-salary-assignments / add-income-tax-slab / add-state-tax-slab / update-employee-state-config | Payroll config |
update-fica-config / update-futa-suta-config / add-overtime-policy / calculate-overtime / calculate-retro-pay | Tax & overtime; retro calc is idempotent (skips periods already pending/applied) |
create-payroll-run / generate-salary-slips / get-salary-slip / list-salary-slips / submit-payroll-run / cancel-payroll-run | Payroll processing; slips auto-include pending retro pay as an earning line (applied on submit, reverted on cancel) |
generate-w2-data / generate-nacha-file / add-garnishment / update-garnishment / get-garnishment / list-garnishments | W-2, NACHA, garnishments |
get-amendment-history | Amendment tracking |
| Action | Description |
|---|---|
install-module / remove-module / update-modules / list-modules / available-modules / search-modules / module-status | Module catalog (install/remove require user approval) |
rebuild-action-cache / list-all-actions / list-profiles / onboard | Actions & profiles |
validate-module / list-articles / build-table-registry | Constitution + module discovery (read-only) |
schema-plan / schema-apply / schema-rollback / schema-drift | Schema migration (apply/rollback require user approval) |
regenerate-skill-md | Regenerate SKILL.md |
update-foundation / rollback-foundation / verify-trust-root | Reconcile installed foundation files with the published manifest; reconcile actions require user approval |
Foundation reconciliation. Reconciliation verifies an ed25519 signature on the registry against an embedded public key before trusting any file hash. Two user-invoked actions keep an installed foundation aligned with the published manifest in
module_registry.json.update-foundation --user-confirmedcompares each installed file's SHA256 against the signed manifest, and for any drift, replaces the file from the published source after re-verifying the declared hash; a pre-flight verifies all replacements before any rename, so a hash failure leaves the install unchanged. Each replaced file is preserved as.bakfor one cycle, androllback-foundation --user-confirmedreverts that cycle. A confirmed apply-pathupdate-foundationalso applies any pending schema migrations after the file reconcile (idempotent; loud, ledgered failure) — note thatrollback-foundationrestores files only, never schema (migrations are forward-only).verify-trust-rootprints the embedded key fingerprint for out-of-band verification. A periodic convenience check, suppressed by the marker file~/.openclaw/erpclaw/.skip_reconcileor the per-invocation flag--no-reconcile-check, may surface a reminder when version drift is present; the user runsupdate-foundationto apply. The router never modifies installed code without an explicit gated invocation. Signature verification is mandatory on the reconciliation path; the only exception is a documented operator recovery path that records to the audit log.
Module authoring + variant analysis (developer tooling): module generation, in-module feature injection, sandboxed test execution, deploy pipeline, variant analysis, gap detection, heartbeat analysis, semantic checks, and the OS-engine status command live in the optional
erpclaw-os-engineaddon (~30 actions, allos-prefixed). The addon is GitHub-only and not installed by default. Install viamodule_manager.py --action install-module --module-name erpclaw-os-engine. Foundation does not run module-generation or auto-deploy code paths.
Confirmation follows the two-class protocol in ## Runtime gate: for the destructive set (close-fiscal-year, restore-database, install-module, rollback-foundation, generate-nacha-file, plus initialize-database --force and remove-module/schema-rollback) get a genuine second yes before acting; for routine reversible work (submit-* / cancel-* / approve-* / reject-*, setup-company, onboard, run-consolidation) pass --user-confirmed on a clear request without re-asking. Speak in business terms; the action names are routing-only and never spoken to the user.
ERPClaw never installs cron jobs automatically. Two background workers are meant to run on a schedule once a business turns on email automation. Register them with the OpenClaw cron facility — the same openclaw cron add path used for any recurring ERPClaw job (confirm with the user first):
# Send queued emails — every 1 minute (erpclaw-alerts: process-email-queue)
openclaw cron add --name erpclaw-email-queue --cron "* * * * *" \
--message "Using erpclaw, run the process-email-queue action."
# Advance drip-campaign sends — every 5 minutes (erpclaw-growth: process-drip-sends)
openclaw cron add --name erpclaw-drip-sends --cron "*/5 * * * *" \
--message "Using erpclaw, run the process-drip-sends action."
process-email-queue (every 1 minute) drains the email outbox with exponential backoff; process-drip-sends (every 5 minutes) advances due drip enrollments. Both are idempotent, so re-running inside an interval will not double-send. Remove a schedule with openclaw cron remove --name <name>. SKILL.md cron: blocks are decorative and never auto-register — explicit openclaw cron add is the only active scheduling path (see CHANGELOG v4.1.0).
Router: scripts/db_query.py -> 14 core domains. Optional modules installed from GitHub (avansaber/*) to ~/.openclaw/erpclaw/modules/ (user-approved only). Single SQLite DB (WAL). 188 core tables (688 with modules). Money=TEXT(Decimal), IDs=TEXT(UUID4), GL immutable. Python 3.10+. All network activity limited to: (1) fetch-exchange-rates, the public exchange rate API; (2) install-module, git clone from github.com/avansaber/* only, requires user approval.