# @tailor-platform/erp-kit

## 0.45.0

### Minor Changes

- 363187c: **Breaking:** the `purchase` module no longer owns supplier bills, purchase payment terms, supplier profiles, or three-way matching. That domain belongs to the `account-payable` module.

  - Removed db types: `PurchaseBill`, `PurchaseBillLine`, `PurchasePaymentTerm`, `PurchasePaymentTermLine`, `SupplierProfile`, `ThreeWayMatchEvent`.
  - Removed commands: `createPurchaseBill`, `updatePurchaseBill`, `matchPurchaseBill`, `releasePurchaseBill`, `cancelPurchaseBill`, `createPurchasePaymentTerm`, `updatePurchasePaymentTerm`, `activatePurchasePaymentTerm`, `deactivatePurchasePaymentTerm`, `setSupplierDefaultPurchasePaymentTerm`.
  - Removed queries: `getPurchaseBill`, `calculatePurchaseBillDueSchedule`, `getPurchasePaymentTerm`, `getSupplierProfile`.
  - `PurchaseOrder` drops `paymentTermId` and `paymentTermSnapshotLines`; `createPurchaseOrder`, `updatePurchaseOrder`, `amendOrderedPurchaseOrder`, and `convertPurchaseRequisitionToPurchaseOrder` inputs no longer accept payment-term fields.
  - `definePurchaseModule` drops the `supplierProfile`, `purchasePaymentTerm`, `purchaseBill`, and `purchaseBillLine` extension params, and no longer requires `inventory.db`.

- d749f5d: **BREAKING** Remove the `erp-kit license check` and `erp-kit license list` CLI commands. The scaffolded `.github/workflows/erp-kit-check.yml` now checks licenses via the shared `tailor-platform/actions/check-licenses` GitHub Action instead of the bundled CLI, so the bundled license-classification logic is redundant. The scaffolded `license.config.json` still exists — it now configures the shared action's inputs instead of the removed CLI — so no scaffold structure changed there. If you invoke `erp-kit license check`/`erp-kit license list` directly (outside the generated workflow), there is no drop-in replacement — run the check via `tailor-platform/actions/check-licenses` in a GitHub Actions workflow instead.

### Patch Changes

- 8ad9019: Bump the `@tailor-platform/sdk` dependency (and the scaffold templates' pinned version) from 1.68.0 to 1.74.1, so CI's `apply`/`generate` checks against the scaffold apps actually exercise the SDK version that enforces forward-relation-name uniqueness and other newer validations, instead of silently missing them.
- 8ad9019: Fix `apply` failures against `@tailor-platform/sdk` 1.73.3+ caused by duplicate forward relation names. `InventoryExecutionLine` (`unitId`/`primaryUnitId`), `TransferOrder` (`sourceSiteId`/`destinationSiteId`), `PipelineStageTransition` (`fromStageId`/`toStageId`), and `ApprovalDecision` (`decidedByUserId`/`delegatedToUserId`) each had two relations to the same target type without a disambiguating `as`, which silently collided in `@tailor-platform/sdk` <1.73.3 (the second field's forward relation quietly overwrote the first) and now fails `apply` with "Forward relation name ... is duplicated" once a consumer upgrades. Each pair now has a distinct forward relation name.
- a399e52: Fix `erp-kit update` deleting your custom skills when `.claude/skills` is a copy instead of a symlink (e.g. on Windows). It now only replaces `erp-kit-*` skills and leaves your own skills untouched.

## 0.44.0

### Minor Changes

- f2e4eae: **Breaking:** the `account-payable` and `accounting-event` modules read foreign-module data through injected queries instead of raw `selectFrom`, dropping their `lib/_db_deps.ts` stub. `defineModule` for these modules now requires `queries` alongside `db` on the affected foreign-module dependencies (e.g. `organization: { db, queries }`).
- 61ff4a7: **Breaking:** the `purchase` module reads foreign-module data through injected queries instead of raw `selectFrom`, dropping its `lib/_db_deps.ts` stub. `defineModule` for purchase now requires `queries` alongside `db` on each foreign-module dependency (e.g. `organization: { db, queries }`).
- c7c193d: **Breaking:** the `coa-management`, `financial-accounting`, `manufacturing`, and `sales` modules read foreign-module data through injected queries instead of raw `selectFrom`, dropping their `lib/_db_deps.ts` stub. `defineModule` for these modules now requires `queries` alongside `db` on each foreign-module dependency (e.g. `organization: { db, queries }`).
- e90fb7c: Add frontend component testing.

  Custom components under `frontend/src/components/` now require a co-located
  `<name>.test.tsx`, enforced by `app sync-check`.
  Vendored `components/ui/**` and files marked `/* no-test: <reason> */` are exempt.

  The scaffold frontends ship the test toolchain (vitest, Testing Library, jsdom)
  and example tests.
  The frontend skill gains a test-first component step and a component-testing
  reference.

- 7c0b71a: Add lint rules for frontend tests to the shared oxlint config
  (`@tailor-platform/erp-kit/oxlint/frontend`).

  - Component tests (`*.test.{ts,tsx}`): Testing Library + jest-dom
  - E2E specs (`e2e/**/*.spec.ts`): Playwright

  These catch common test antipatterns, such as using `toBeNull()` instead of
  `toBeInTheDocument()`, or waiting on `networkidle` instead of web-first
  assertions.

  **Breaking:** the shared config now lists `eslint-plugin-testing-library`,
  `eslint-plugin-jest-dom`, and `eslint-plugin-playwright` as oxlint JS plugins.
  Oxlint resolves every JS plugin when it loads the config, so a project that
  extends this config must have all three installed or `oxlint .` fails before it
  lints any file. They are optional `peerDependencies` and are not installed
  automatically, so existing consumers must add them when updating:

  ```sh
  pnpm add -D eslint-plugin-testing-library eslint-plugin-jest-dom eslint-plugin-playwright
  ```

  Newly scaffolded apps already include them.

- 5639e38: - Add account-payable and accounting-event as first-class modules that can be wired from `@tailor-platform/erp-kit/module`:

  ```ts
  import {
    defineAccountPayableModule,
    defineAccountingEventModule,
  } from "@tailor-platform/erp-kit/module";

  const accountingEvent = defineAccountingEventModule({ organization });

  const accountPayable = defineAccountPayableModule({
    organization,
    businessPartner,
    primitives,
    accountingEvent,
    coaManagement,
    purchase,
    inventory,
  });
  ```

  - Add the account-payable command surface for AP document lifecycle, distribution, due schedule, hold, tolerance, correction, and posting workflows:

    ```ts
    accountPayable.commands.createAccountPayableDocument;
    accountPayable.commands.updateAccountPayableDocument;
    accountPayable.commands.registerAccountPayableDocument;
    accountPayable.commands.postAccountPayableDocument;
    accountPayable.commands.cancelAccountPayableDocument;
    accountPayable.commands.correctAccountPayableDocument;
    accountPayable.commands.releaseAccountPayableDocumentHold;
    accountPayable.commands.createInvoiceToleranceConfig;
    accountPayable.commands.updateInvoiceToleranceConfig;
    ```

  - Add the accounting-event handoff interface for append-only source-module event snapshots and resolver-specific processing records. AP posting uses this surface to emit an `ACCOUNT_PAYABLE_RECOGNIZED` v1 payload instead of creating a journal entry directly:

    ```ts
    await accountingEvent.commands.postAccountingEvent(
      db,
      {
        companyId,
        eventKind: "ACCOUNT_PAYABLE_RECOGNIZED",
        schemaVersion: "1",
        payload: {
          source: {
            documentType: "ACCOUNT_PAYABLE_DOCUMENT",
            documentId: accountPayableDocumentId,
            documentNumber,
          },
          documentType: "INVOICE",
          supplierId,
          documentDate,
          accountingDate,
          dueSchedule,
          currencyId,
          currencyCode,
          totalAmount,
          payableAccountId,
          lines,
          distributions,
        },
      },
      ctx
    );
    ```

  - Add purchase and inventory matching boundaries used by account-payable registration, so AP can reconcile invoice quantities without owning purchase or receipt storage:

    ```ts
    purchase.queries.listPurchaseOrderLinesForMatching;
    purchase.commands.recalculatePurchaseOrderBillingStatus;
    inventory.queries.listPurchaseReceiptQuantitiesForMatching;
    ```

  - **Breaking:** refactor financial-accounting journal entries to use `JournalLine` as the posted ledger surface, replace `deleteJournalEntry` with `cancelJournalEntry`, remove `AccountingLedger`, and preserve posted entries through reversal-only corrections:

    ```ts
    financialAccounting.commands.cancelJournalEntry;
    financialAccounting.commands.postJournalEntry;
    financialAccounting.commands.reverseJournalEntry;
    financialAccounting.db.journalEntry;
    financialAccounting.db.journalLine;
    ```

  - **Breaking:** refactor coa-management to a simplified company-scoped `Account` model, removing `ChartOfAccounts` and `AccountGroup` models, commands, queries, and docs. The remaining public interface is account-centric:

    ```ts
    coaManagement.db.account;
    coaManagement.commands.createAccount;
    coaManagement.commands.updateAccount;
    coaManagement.commands.deactivateAccount;
    coaManagement.commands.reactivateAccount;
    coaManagement.queries.getAccount;
    coaManagement.queries.listAccounts;
    ```

- 1d313a0: Add `erp-kit verify`: a CI gate that runs deterministic version and structure
  checks across the modules directory and each app. Exits non-zero on any
  violation.

  It also runs in the shipped `erp-kit-check` workflow, so consumers pick it up
  via `erp-kit update`.

## 0.43.0

### Minor Changes

- 03a90a5: - Add `cancelGoodsReceipt` and `cancelGoodsIssue` commands for soft-cancelling draft inventory executions before stock posting
  - Add `CANCELLED` to the `InventoryExecution` lifecycle and generated inventory status types
  - Expose goods receipt cancellation through the purchase module command surface

## 0.42.0

### Minor Changes

- 2e25eea: **Breaking:** the purchase module no longer enforces per-company uniqueness of `externalSupplierOrderReference`. The field is optional correlation metadata to the supplier's own order identifier — it carries no DB constraint and the same reference may legitimately appear on multiple purchase orders (e.g. blanket or split orders, or a supplier reusing its own numbering). The submit-time duplicate check has been removed.

  Adopters must update:

  - `submitPurchaseOrder` no longer rejects duplicate references; the `PURCHASE_DUPLICATE_EXTERNAL_SUPPLIER_ORDER_REFERENCE` error code is gone. Drop any handling of that error.
  - `getPurchaseOrder` no longer accepts lookup by `{ companyId, externalSupplierOrderReference }` — it only accepts `{ id }`. Because references are no longer guaranteed unique, lookup-by-reference was never well-defined; resolve the order id another way (e.g. a list query filtered by the reference) when more than one order can share it.

## 0.41.0

### Minor Changes

- 870138b: Roles can now be assigned to `PENDING` users in the `user-management` module.

  Previously `assignRoleToUser` rejected any non-`ACTIVE` user, so you had to activate a user before giving them a role. Now you can pre-assign roles at invitation time and activate later (invite → assign roles → activate on sign-in). Only `INACTIVE` (offboarded) users are rejected.

  A user's effective permissions are now non-empty only while the user is `ACTIVE`:

  - Roles assigned to a `PENDING` user are recorded but stay dormant (`permissions` is `[]`) until the user is activated.
  - `activateUser` / `reactivateUser` bring the assigned roles' permissions live.
  - `deactivateUser` clears effective permissions (the role assignments themselves are preserved).

  This guarantees `PENDING` and `INACTIVE` users carry no effective access.

  **Behavior change:** `deactivateUser` now empties a user's `permissions` (previously they were left as-is). If you read `user.permissions` for a deactivated user, expect `[]`. Existing `INACTIVE` users in the database are not migrated — their `permissions` are recomputed the next time the user is touched (e.g. reactivated, or a role is assigned/revoked).

- 37ffe14: **Breaking:** remove the `auditCompanyStub` export from `@tailor-platform/erp-kit/module`. It was a fallback Company type for wiring `defineAuditModule` without an organization module, and is no longer needed — just drop it. If you have no organization module, omit `organization` entirely and audit's `companyId` becomes a plain uuid.
- da031f5: The built-in field guard now covers the `notification` and `pipeline-management` modules. Like every other module, re-declaring a field these modules already define via the `fields` option is now a compile error that names the offending field.
- e49d650: Ship Oxlint integration as erp-kit subpaths: a natively implemented `erp-kit/no-relative-packages` plugin (`@tailor-platform/erp-kit/oxlint-plugin`) and shared configs (`@tailor-platform/erp-kit/oxlint/{module,backend,frontend}`).

  Scaffolds now use `oxlint.config.ts` that extends the shared config instead of an inlined `.oxlintrc.json`, so rule and plugin changes ship through erp-kit without consumers editing their config. `no-relative-packages` now applies to module, backend, and frontend scaffolds, and is implemented with `node:fs`/`node:path` only — no `eslint-plugin-import-x` / `unrs-resolver` native binding.

- 53d7830: Speed up the scaffolded test suites and add a unit-test CI workflow.

  **Faster integration tests**

  - Run many test files at once (`maxWorkers: 16`).
  - Make story tests self-contained so they pass in any order:
    - Browse and viewer tests create their own rows in `beforeAll`.
    - User and role tests create their own active role in `beforeAll`,
      instead of picking one from the shared list — which a parallel test could deactivate.

  **New unit-test workflow**

  - Add `erp-kit-unit-test.yml`, which runs the module unit tests.
  - It is independent of the integration workflow, so the two run in parallel.
  - New scaffolds also get `test:unit` and `test:integration` scripts.

  **Docs**

  - Add a guide on test parallelism and isolation.

- d4db90f: **BREAKING** rename the opt-in Slack integration's `SlackWorkspaceConnection` model to `SlackWorkspaceIntegration`. The GraphQL type name ending in `Connection` collided with the Relay connection naming convention and broke deploy; the new name also matches the existing `notification:slackWorkspaceIntegration:*` scope and the `slack-workspace-integration` feature naming.

  Adopters of the `slack` option on `defineNotificationModule` must update:

  - The DB type / GraphQL type `SlackWorkspaceConnection` → `SlackWorkspaceIntegration`.
  - The query `getSlackWorkspaceConnection` → `getSlackWorkspaceIntegration`.
  - The exported status constant `SLACK_WORKSPACE_CONNECTION_STATUSES` → `SLACK_WORKSPACE_INTEGRATION_STATUSES` (from `@tailor-platform/erp-kit/module`).

## 0.40.0

### Minor Changes

- a6e6164: Add the `notification` built-in module — a generic, domain-agnostic notification engine promoted from the tailor-crm repo. It covers the full pipeline: idempotent event ingress (`logNotificationEvent` + CDC-triggered dispatch executors), template-rendered fan-out across PERSONAL channels (IN_APP inbox with two-axis delivery/engagement lifecycle, EMAIL port) and DESTINATION channels (one at-most-once post per `ChannelRoutingBinding`), per-user preferences with category/channel opt-out and critical bypass, watcher subscriptions, and a dual-track delivery audit with a set-based retention sweep. Channel/recipient integration is injected through DI ports (`adapters.inApp/email/destination`, `resolveRecipient`), so the module has no compile-time dependency beyond user-management and **no provider-specific schema by default**: provider credentials/tables are created only when an integration is opted into (see the Slack option below). Unlike the crm original, all models are tenant-global: there is no `companyId` scoping — the deployment is the tenant.

  Bundle the Slack destination into `notification` as an **opt-in** integration, gated by the `slack` option on `defineNotificationModule`. When the option is omitted, no Slack schema or command is created — so host apps that do not deliver to Slack carry zero Slack tables — and when it is provided the module adds, and auto-wires, the full Slack surface: the `SlackWorkspaceConnection` singleton (AES-256-GCM-encrypted bot token, unique `teamId`, ACTIVE/REVOKED lifecycle, gateway auto-CRUD closed), the OAuth install handshake (`beginSlackWorkspaceInstall` issues an HMAC-SHA256-signed state token with actor and redirect-URI pinning; `completeSlackWorkspaceInstall` verifies it before exchanging the code), the signature-verified `app_uninstalled` webhook (`handleSlackAppUninstalled` takes raw `(rawBody, timestamp, signature, signingSecret)` and verifies Slack's v0 request signature with a ±5 min replay window), the redacted `getSlackWorkspaceConnection` projection, and the `createSlackDestinationAdapter` factory (chat.postMessage with a 10s timeout, normalized error classes, redacted provider response, auto-revoke on dead-token errors). With `slack` enabled the module owns `adapters.destination` itself (built against its own `SlackWorkspaceConnection` row through the executor db handle), so the host app supplies only `encryptionKey` and must not pass `destination`.

  Public surface (via `@tailor-platform/erp-kit/module`), all from `notification`: `defineNotificationModule`, `notificationPermissions`, the status/reason constants `NOTIFICATION_DELIVERY_STATUSES`, `NOTIFICATION_ENGAGEMENT_STATUSES`, `NOTIFICATION_EVENT_STATUSES`, `NOTIFICATION_REASONS`, the cross-module event contract types `NotificationEventPayload`, `NotificationReason`, `NotificationRecipientHint`, the `Notification`-prefixed adapter port types (`NotificationDispatchAdapters`, `NotificationInAppAdapter`, `NotificationEmailAdapter`, `NotificationDestinationAdapter`, `NotificationRecipientProfile`, `NotificationResolveRecipientFn`), and — for the opt-in Slack destination — `NotificationSlackOptions`, `createSlackDestinationAdapter` (+ `CreateSlackDestinationAdapterDeps`, `SlackWorkspaceForAdapter`), and `SLACK_WORKSPACE_CONNECTION_STATUSES`. The internal `encrypt` / `decrypt` helpers are not exported.

  Delivery integrity hardening (relative to the crm original): per-pair failure isolation in the dispatcher (including thrown adapters) with a `skipped[]` suppression ledger, and CDC dispatch idempotency keyed on the event's logical identity so duplicate event rows converge to the same notifications.

- 2732b6f: A module's `fields` option lets you add custom fields to a model. Re-declaring a field erp-kit already defines (e.g. `name` on User) used to silently collide with the built-in — now it's a compile error that names the offending field.

  UserRole had a `fields` option too, but no command ever set those fields, so it's removed: `defineUserManagementModule` no longer accepts a `userRole` option.

## 0.39.0

### Minor Changes

- 4843836: **BREAKING** Change every transactional document's create and update commands to edit line items incrementally instead of replacing the whole line set. Previously `update*` took the full `lines` array and replaced every line, so if the caller had loaded only some lines (e.g. a paginated form) the rest were silently deleted on save, and ids of unchanged lines churned. Now the caller states exactly which lines to add, change, or remove, and any line not mentioned is left untouched.

  Affected: PurchaseOrder, PurchaseBill, PurchaseRequisition, SalesOrder, SalesInvoice, SalesCreditNote, Shipment, GoodsReceipt, GoodsIssue, TransferOrder, StockAdjustment.

  Adopters must update call sites:

  - `update*`: replace the `lines` array with `headerPatch` plus `addLines` / `updateLines` (`{ lineId, linePatch }`) / `removeLineIds`. Updated lines keep their existing id and `createdAt`.
  - `amendOrderedPurchaseOrder`: `headerChanges` → `headerPatch`; line changes use the same `addLines` / `updateLines` / `removeLineIds` collections.
  - `create*`: the flat input becomes `{ header, lines }`.
  - These commands now return the header row only — read line items with a separate query.

- 1934cb7: Add the `pipeline-management` built-in module — a generic, domain-agnostic engine for pipeline-based (kanban) workflows. It provides Pipeline, PipelineStage, PipelineItem, PipelineStageTransition, PipelineItemChange, PipelineItemComment, PipelineLabel, and PipelineItemLabel models, with commands for the full item lifecycle (create/move/reorder/close/reopen), configurable per-pipeline stages, stage-transition and change history, comments, and pipeline-scoped labels. Every model is exposed as a `create<Model>Type` DB-type factory with a `fields` extension point, and `defineModule` accepts an optional `pipelineTypes` vocabulary, so consuming domain modules (e.g. deals, tickets) can compose on top via field extension or foreign-key linkage without forking the module. Exported as `definePipelineManagementModule` / `pipelineManagementPermissions`.

## 0.38.0

### Minor Changes

- e66f969: The `approval` module's custom field options are removed. No command ever persisted them, so they did nothing.

## 0.37.0

### Minor Changes

- dd6052a: - Add `AccountingLedger` as the posted-only general ledger fact model created by `postJournalEntry` and `reverseJournalEntry`, while keeping `JournalLine` as the draft/prepared line model
  - Improve `financial-accounting` journal entry workflows by creating and replacing journal lines through `createJournalEntry` and `updateJournalEntry`, validating source document references, and writing posted ledger facts only after balanced posting succeeds
  - Improve accounting period controls by simplifying the lifecycle to `NEVER_OPENED -> OPEN -> CLOSED -> PERMANENTLY_CLOSED` and allowing journal posting only in `OPEN` periods
  - Remove the legacy `accounting` module export and implementation so cost centers, profit centers, budgets, allocations, commitments, and variance analysis are no longer exposed through the package
  - Remove out-of-scope `financial-accounting` commands, queries, models, and docs for period-close orchestration, trial balance generation, subledger transfer verification, handoff processing, journal-line-only mutations, and multi-currency/journal-type classifications
  - Update related module documentation to describe `financial-accounting` as the financial record for posted business activity while keeping operational documents in sales, purchase, inventory, manufacturing, and other upstream modules
- 1b217e8: You can now set custom fields on journal entries, fiscal years, and accounting periods in `financial-accounting` when you create or update them. The accounting ledger is generated automatically from posted journal entries, so it no longer accepts custom fields.
- 84251af: Support custom fields on manufacturing routings, bills of material, and production orders. You can now declare custom fields for these on the manufacturing module and set them through their create and update commands, and an undeclared field is caught at compile time.
- 99394b5: Move `createMockServer` to a dedicated `@tailor-platform/erp-kit/testing/mock-server` entry. The `@tailor-platform/erp-kit/testing` entry is now free of `node:fs`, so it can be imported from module command tests that run in the Tailor Platform runtime. Update imports of `createMockServer` from `@tailor-platform/erp-kit/testing` to `@tailor-platform/erp-kit/testing/mock-server`.
- cc7c41d: You can now set custom fields on `sales` records when you create or update them. The models supported custom fields but the create and update commands ignored anything you passed.

### Patch Changes

- 360dc5c: Fix custom field type checking on purchase, inventory, and manufacturing commands. These commands used to accept any field name without complaint; they now only accept the custom fields you declared on the module, so typos and undeclared fields are caught at compile time.
- 4e6933c: Fix `license check` SPDX `OR` handling. An expression like `(MIT OR GPL-3.0)` is now allowed when any one of its licenses is allowed, matching SPDX semantics (the package may be used under any listed license). `AND` still requires every member to be allowed, and mixed `OR`/`AND` expressions conservatively require all members.
- 8518143: Fix manufacturing commands that accepted custom fields and silently dropped them. These records have no custom fields, so the commands no longer accept them and a stray field is now caught at compile time.
- 379327c: Simplify the `sync-check` summary to a single `Files checked` count, and fail when it is zero. The previous `Categories checked` / `Source files` / `Doc files` breakdown was inconsistent across checks and could contradict the listed errors. A run that checks nothing — usually a wrong `--path` — now fails instead of reporting a misleading pass.

## 0.36.0

### Minor Changes

- 58a17a5: Add send-back for revision to the approval module. An approver can return an in-flight request to the requester with `sendBackApprovalStep` instead of rejecting it; the requester fixes the target and calls `resubmitApprovalRequest`, which restarts approval from the first step. Use `listApprovalRequestsForRequester` to find requests awaiting resubmission.

### Patch Changes

- a009afa: - Fix scaffolded module `generate` script to also run `erp-kit module generate code`, so `pnpm generate` regenerates docs-driven `.generated.ts` files alongside the SDK type generation instead of silently leaving them stale
- 7052dd1: Add a page → screen doc reverse check to `erp-kit-app-7-impl-review`. It scans implemented frontend pages and flags those with no corresponding `docs/screen/` entry, catching undocumented pages that the existing screen doc → code check missed.
- a962a9a: Validate external inputs (configs, package.json, mock.json, subprocess output) with schemas so CLI commands fail with clear errors instead of crashing. Remove the `pkg-types` dependency.

## 0.35.0

### Minor Changes

- b87610a: - **BREAKING** Remove `createMockDb` from `@tailor-platform/erp-kit/testing`. Database tests now use `createKyselyMock` from `@tailor-platform/sdk/vitest`, which runs real Kysely queries against staged rows and records them for assertions — replacing the old approach of spying on query-builder calls. This is a different assertion model, so tests are rewritten rather than renamed: migrate your custom modules by following the testing skill and the already-migrated core-module tests. `testNotFound` / `testPermissionDenied` / `testIdempotent` are unchanged. The `@tailor-platform/sdk` peer range is raised to `^1.55.0`, where `createKyselyMock` landed.

### Patch Changes

- 04524cd: Fix `updateInventorySupplyPlan` leaking the primary key `id` into the update set. The command did not strip `id` from the input rest fields, so it issued `UPDATE ... SET id = ...`, which TailorDB rejects with an `internal error`. This caused the buyer's "accept order change request" flow (`amendOrderedPurchaseOrder`) to fail every time. `id` is now excluded from the update set, matching the sibling `updateTransferOrder` command.

## 0.34.1

### Patch Changes

- 28521d7: - Fix `receiveTransferOrder` to update transfer supply plan receipt quantities without unsupported TailorDB `CASE` expressions

## 0.34.0

### Minor Changes

- 5c3136e: Add inventory transfer order support for planning, shipping, receiving, and closing stock transfers between sites.

  This adds `TransferOrder` and `TransferOrderLine` models with a `DRAFT -> OPEN -> CLOSED` lifecycle. Transfer orders are created with a source site, destination site, planned shipment date, expected receipt date, and line inputs containing `itemId`, `unitId`, `orderedQuantity`, and optional lot/serial references. Draft orders can be updated by replacing their line set before they are opened.

  Transfer orders keep planning and execution responsibilities separate: transfer lines store ordered, shipped, and received progress, while shipment and receipt postings are recorded as InventoryExecution, InventoryExecutionLine, InventoryLedger, StockReservation, InventorySupplyPlan, and StockLevel effects.

  The new command interface is:

  - `createTransferOrder`: creates a draft transfer order and initializes line `shippedQuantity` and `receivedQuantity` to `0`.
  - `updateTransferOrder`: updates a draft transfer order and replaces its lines.
  - `openTransferOrder`: validates source-site availability, opens the order, creates transfer demand reservations at the source site, and creates matching supply plans at the destination site.
  - `shipTransferOrder`: posts a transfer shipment for selected lines by moving stock from source `AVAILABLE` inventory into the caller-supplied transit `IN_TRANSIT` location, consuming matching transfer reservations, creating posted inventory execution/ledger records, and incrementing line `shippedQuantity`.
  - `receiveTransferOrder`: posts a transfer receipt for selected lines by moving stock from the caller-supplied transit `IN_TRANSIT` location into destination `AVAILABLE` inventory, consuming matching transfer supply plans, creating posted inventory execution/ledger records, and incrementing line `receivedQuantity`.
  - `closeTransferOrder`: closes an open transfer order once all in-transit quantities are clear, and closes remaining transfer reservations and supply plans.

  This also adds `getTransferOrder` and `listTransferOrders` queries. Both return transfer orders with their lines and derived `shippingStatus` / `receivingStatus` values based on ordered, shipped, and received quantities.

  erp-kit records and validates transit locations as execution facts, but does not resolve transfer routes or default in-transit locations. Applications that do not need users to distinguish transit locations can pass a global default in-transit location or maintain a site-specific in-transit location and pass it to the ship/receive commands.

## 0.33.0

### Minor Changes

- 1d9fb6d: - Remove the deprecated inventory `receiveStock` and `issueStock` commands now that goods receipt and goods issue posting flow through `InventoryExecution`
  - Remove the obsolete `ReceiveStock` and `IssueStock` command documentation and tests, and update inventory docs to point callers to `createGoodsReceipt` / `postGoodsReceipt` and `createGoodsIssue` / `postGoodsIssue`
  - Refactor goods issue internals to use `postGoodsIssue` naming for serial number lifecycle transitions and reservation consumption helpers
  - Remove the purchase module's stale `receiveStock` dependency from inventory command wiring and test mocks
- 1d9fb6d: - Remove `amount` from `InventoryLedger` so inventory ledger entries only record stock movement quantity and direction
  - Update inventory posting commands, tests, generated Kysely types, and module documentation to stop writing or expecting ledger-level monetary amounts
  - Keep monetary impact in inventory valuation records using quantity and unit cost instead of duplicating it on the stock ledger
- a9a1dae: - Add a `Version alignment` dimension to `erp-kit-score` that catches version drift in your workspace. It flags when your Tailor-platform packages (`@tailor-platform/sdk`, `@tailor-platform/app-shell`, `@tailor-platform/app-shell-vite-plugin`) are outside the versions erp-kit's skills expect, when `@tailor-platform/erp-kit` is pinned to a different version than the one you're running, or when your installed skills are stale and need `erp-kit update skills`.

## 0.32.0

### Minor Changes

- ca92f07: - Add inventory-owned `InventoryExecution` and `InventoryExecutionLine` models for goods receipts, goods issues, and stock adjustments, with posting status, source document context, unit-conversion snapshots, and line-level stock effects.

  - Add inventory goods receipt commands and queries: `createGoodsReceipt`, `updateGoodsReceipt`, `postGoodsReceipt`, `getGoodsReceipt`, and `listGoodsReceipts`.
  - Add inventory goods issue commands and queries: `createGoodsIssue`, `updateGoodsIssue`, `postGoodsIssue`, `getGoodsIssue`, and `listGoodsIssues`.
  - Add `listPurchaseReceiptEvidenceForMatching` so purchase bill matching and purchase order receipt status calculation use posted inventory receipt evidence instead of purchase-owned goods receipt tables.
  - Refactor purchase goods receipt handling so purchase exposes inventory goods receipt commands as a facade while receipt execution data is stored in inventory.
  - Refactor purchase order approval, amendment, cancellation, closing, and bill matching flows to synchronize with inventory supply plans and posted inventory receipt evidence.
  - Refactor stock adjustments, direct receive/issue stock commands, lot/serial traceability, and inventory ledger rows to link stock movements through `InventoryExecution`.
  - Remove purchase-owned `GoodsReceipt` and `GoodsReceiptLine` models, lifecycle, commands, queries, permissions, generated types, and documentation.
  - Remove sales shipment inventory handoff fields and logic that duplicated inventory execution ownership.

  ## Migration Guide

  ### Purchase goods receipt data moved to inventory

  Applications that used purchase `GoodsReceipt` records must migrate to inventory `InventoryExecution` records with `executionType = "GOODS_RECEIPT"`.

  The new receipt aggregate is:

  - `InventoryExecution`: receipt header, posting status, business date, posted timestamp, and source document reference.
  - `InventoryExecutionLine`: receipt lines, item/location/stock type, input quantity, primary-unit quantity, unit conversion snapshot, optional lot/serial references, and optional unit cost.
  - `InventoryLedger`: immutable posted stock fact rows linked back to `InventoryExecution` through `inventoryExecutionId`.

  Map old purchase receipt data to inventory execution data as follows:

  | Previous purchase data                                 | New inventory data                                                                 |
  | ------------------------------------------------------ | ---------------------------------------------------------------------------------- |
  | `GoodsReceipt.id`                                      | `InventoryExecution.id`                                                            |
  | `GoodsReceipt.status`                                  | `InventoryExecution.status`                                                        |
  | `GoodsReceipt.receiptDate` or equivalent business date | `InventoryExecution.effectiveDate`                                                 |
  | `GoodsReceipt.postedAt`                                | `InventoryExecution.postedAt`                                                      |
  | `GoodsReceipt.purchaseOrderId`                         | `InventoryExecution.sourceDocumentId` with `sourceDocumentType = "PURCHASE_ORDER"` |
  | `GoodsReceiptLine.id`                                  | `InventoryExecutionLine.id`                                                        |
  | `GoodsReceiptLine.purchaseOrderLineId`                 | `InventoryExecutionLine.sourceLineId`                                              |
  | `GoodsReceiptLine.itemId`                              | `InventoryExecutionLine.itemId`                                                    |
  | `GoodsReceiptLine.storageLocationId`                   | `InventoryExecutionLine.storageLocationId`                                         |
  | `GoodsReceiptLine.quantity`                            | `InventoryExecutionLine.quantity`                                                  |
  | primary-unit receipt quantity                          | `InventoryExecutionLine.primaryQuantity`                                           |
  | receipt unit                                           | `InventoryExecutionLine.unitId`                                                    |
  | primary unit                                           | `InventoryExecutionLine.primaryUnitId`                                             |
  | unit conversion rate                                   | `InventoryExecutionLine.unitConversionRate`                                        |
  | receipt unit cost                                      | `InventoryExecutionLine.unitCost`                                                  |
  | lot reference                                          | `InventoryExecutionLine.lotId`                                                     |
  | serial reference                                       | `InventoryExecutionLine.serialNumberId`                                            |

  When migrating existing receipt rows:

  - Create one `InventoryExecution` for each historical purchase goods receipt.
  - Set `executionType = "GOODS_RECEIPT"`.
  - Set `sourceDocumentType = "PURCHASE_ORDER"` and `sourceDocumentId = PurchaseOrder.id` for purchase-linked receipts.
  - Create one `InventoryExecutionLine` for each historical receipt line with `direction = "IN"`, `stockType = "AVAILABLE"`, and `sourceLineId = PurchaseOrderLine.id`.
  - Preserve receipt quantities in both input unit fields and primary-unit fields. If old data only stored one quantity, use the item's primary unit as `unitId` and `primaryUnitId`, set `quantity` and `primaryQuantity` to the old quantity, and set `unitConversionRate = "1"`.
  - Preserve posted receipts as `status = "POSTED"` with `postedAt` populated. Draft receipts should remain `status = "DRAFT"` and must not have posted ledger or stock effects.
  - Keep existing ledger rows only if they can be linked to the new `InventoryExecution.id`; otherwise rebuild receipt ledger rows from posted execution lines during migration.

  ### Update command and query usage

  Purchase-owned goods receipt schema is removed, but the purchase module still publishes goods receipt commands as a facade over inventory execution. For purchase-order receipts, use the commands exposed by the purchase module so posting also keeps purchase order receipt status synchronized.

  | Previous purchase API                                            | Replacement                                                                                                   |
  | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
  | `purchase.commands.createGoodsReceipt` backed by purchase tables | `purchase.commands.createGoodsReceipt` facade backed by inventory execution                                   |
  | `purchase.commands.updateGoodsReceipt` backed by purchase tables | `purchase.commands.updateGoodsReceipt` facade backed by inventory execution                                   |
  | `purchase.commands.postGoodsReceipt` backed by purchase tables   | `purchase.commands.postGoodsReceipt` facade backed by inventory execution and purchase status synchronization |
  | `purchase.queries.getGoodsReceipt`                               | `inventory.queries.getGoodsReceipt`                                                                           |
  | purchase receipt-line matching queries                           | `inventory.queries.listPurchaseReceiptEvidenceForMatching`                                                    |

  The safest migration path for application code is:

  1. Replace read paths first. Use `inventory.queries.getGoodsReceipt` and `inventory.queries.listGoodsReceipts` for receipt screens, APIs, and audit views.
  2. Replace matching/status read paths. Use `inventory.queries.listPurchaseReceiptEvidenceForMatching` anywhere the application previously summed purchase receipt lines for 3-way matching or purchase order receipt progress.
  3. Replace purchase receipt write paths with `purchase.commands.createGoodsReceipt` and `purchase.commands.updateGoodsReceipt`. These commands create or revise inventory-owned receipt executions while preserving the purchase module boundary.
  4. Replace purchase receipt posting paths with `purchase.commands.postGoodsReceipt`, because it posts the inventory receipt and recalculates purchase order `receiptStatus` in one workflow.
  5. Use `inventory.commands.createGoodsReceipt`, `inventory.commands.updateGoodsReceipt`, or `inventory.commands.postGoodsReceipt` directly only for integrations that own their own purchase synchronization or for non-purchase receipt sources such as transfer or manufacturing receipts.

  When creating a purchase-order goods receipt through inventory commands, callers must provide:

  - `sourceDocumentType = "PURCHASE_ORDER"`
  - `sourceDocumentId = PurchaseOrder.id`
  - `lines[].sourceLineId = PurchaseOrderLine.id` for every line that should count toward purchase order receipt status and bill matching
  - `lines[].primaryQuantity`, `lines[].primaryUnitId`, and `lines[].unitConversionRate` as a snapshot at receipt creation time
  - `lines[].unitCost` if the receipt should create valuation effects during posting

  Lines without `sourceLineId` are treated as inventory receipt lines but cannot be attributed to a purchase order line for receipt status or bill matching.

  ### Update purchase matching integrations

  Three-way matching now allocates supplier bill lines against posted inventory receipt evidence.

  - `ThreeWayMatchEvent.inventoryExecutionLineId` replaces references to purchase goods receipt lines.
  - Matching only considers `InventoryExecution` rows with `executionType = "GOODS_RECEIPT"`, `status = "POSTED"`, `sourceDocumentType = "PURCHASE_ORDER"`, and the requested purchase order ID.
  - Matching allocation is FIFO by `InventoryExecutionLine.createdAt`.
  - Draft inventory receipts can be queried by passing `statuses`, but purchase matching should use the default posted-only behavior.

  If custom matching logic exists outside erp-kit commands:

  - Stop querying purchase receipt tables directly.
  - Query `listPurchaseReceiptEvidenceForMatching` once per purchase order, passing the purchase order ID and the relevant purchase order line IDs.
  - Allocate bill quantities against returned `inventoryExecutionLineId` values, not receipt header IDs.
  - Continue to detect price variance from purchase bill line price versus purchase order line price.
  - Detect receipt quantity variance by comparing bill line quantity with the remaining unallocated posted receipt evidence for that purchase order line.
  - Store matching consumption on `ThreeWayMatchEvent.inventoryExecutionLineId` so repeated matching does not reuse the same receipt line quantity.

  Unposted draft receipts are intentionally excluded from matching by default. If a UI needs to show draft receiving progress, call the inventory receipt queries or pass explicit `statuses`, but do not treat draft receipt quantities as matchable supplier bill evidence.

  ### Update purchase order receipt status integrations

  Purchase order `receiptStatus` is now derived from inventory receipt evidence.

  - On purchase order approval, physical receipt lines create inventory supply plans.
  - On goods receipt posting through the purchase facade, purchase order receipt status is recalculated from posted inventory receipt evidence.
  - If an application posts purchase-order receipts directly through `inventory.commands.postGoodsReceipt`, it must also run the purchase synchronization path or otherwise recalculate purchase order receipt status.

  This affects custom workflows that previously updated purchase receipt status from purchase receipt commands:

  - `NOT_RECEIVED` means no posted inventory receipt evidence exists for active physical receipt lines.
  - `PARTIALLY_RECEIVED` means posted inventory receipt evidence exists but at least one active physical receipt line is not fully received.
  - `FULLY_RECEIVED` means posted inventory receipt evidence covers all active physical receipt line quantities.
  - Non-physical receipt lines should not create inventory supply plans and should not require inventory receipt evidence.
  - Closing and cancellation checks should use posted inventory receipt evidence instead of purchase receipt rows.

  If an application bypasses the purchase module facade and calls inventory posting directly, add an explicit post-processing step that recalculates the affected purchase order. Without that step, inventory stock and ledger state will be correct, but the purchase order `receiptStatus` can remain stale.

  ### Update supply plan integrations

  Purchase order approval now creates inventory supply plans for lines that require physical receipt.

  - Use `PurchaseOrderLine.requiresPhysicalReceipt` to decide whether a line should create receipt supply.
  - Use `PurchaseOrderLine.receivingSiteId` when present; otherwise use the purchase order receiving site.
  - Receipt posting consumes matching open supply plans through the inventory posting workflow.
  - Amend, cancel, and close purchase order flows now need inventory supply plan commands and queries in module wiring.

  Applications with custom purchase order approval or amendment logic should either route through the erp-kit purchase commands or reproduce the supply plan synchronization rules. Otherwise, purchase orders may be commercially ordered while inventory has no expected inbound supply.

  ### Remove purchase goods receipt schema dependencies

  Remove application code, seed data, tests, and generated API assumptions that reference these deleted purchase artifacts:

  - `GoodsReceipt`
  - `GoodsReceiptLine`
  - `cancelGoodsReceipt`
  - `createGoodsReceipt` / `updateGoodsReceipt` / `postGoodsReceipt` implementations that read or write purchase receipt tables
  - `getGoodsReceipt` from purchase queries
  - purchase goods receipt permissions and generated TailorDB types

  Recommended cleanup checks:

  - Search for `GoodsReceipt`, `GoodsReceiptLine`, `goodsReceipt`, `createGoodsReceipt`, `updateGoodsReceipt`, `postGoodsReceipt`, `cancelGoodsReceipt`, and `getGoodsReceipt` in application code.
  - For command names that still exist through the purchase facade, verify the implementation no longer assumes purchase-owned receipt tables.
  - Update GraphQL/API response shapes if clients expected purchase receipt records or purchase receipt line records.
  - Update test fixtures so receipt setup creates `InventoryExecution` and `InventoryExecutionLine` records.
  - Update seed data so posted receipts include linked `InventoryLedger` rows through `inventoryExecutionId`.
  - Update permissions to use inventory goods receipt permissions for inventory-owned receipt operations.
  - Move custom field configuration from `purchase.defineModule({ goodsReceipt, goodsReceiptLine })` to `inventory.defineModule({ inventoryExecution, inventoryExecutionLine })`.
  - Replace imports of purchase helper types such as `GoodsReceipt`, `GoodsReceiptCreate`, `GoodsReceiptUpdate`, `GoodsReceiptLine`, and `GoodsReceiptLineCreate` with inventory helper types such as `InventoryExecution` and `InventoryExecutionLine`.

  ### Update ledger and traceability assumptions

  Inventory ledger rows now carry stock facts while source document context lives on `InventoryExecution`.

  - Use `InventoryLedger.inventoryExecutionId` to join from ledger rows to execution type and source document context.
  - Do not expect source document fields to be duplicated on each ledger row.
  - Lot, serial, and valuation traces should join through `InventoryExecution` when they need receipt/issue type or source document information.

  For reporting and traceability:

  - Filter inbound purchase receipts by joining `InventoryLedger` to `InventoryExecution` and filtering `executionType = "GOODS_RECEIPT"` and `sourceDocumentType = "PURCHASE_ORDER"`.
  - Filter outbound goods issues by joining through `InventoryExecution.executionType = "GOODS_ISSUE"`.
  - Use `InventoryExecutionLine` for business-unit receipt/issue quantities and unit conversion snapshots.
  - Use `InventoryLedger.quantity` for posted stock quantities in the item's primary unit.
  - Use `InventoryLedger.amount` and valuation records for posted value effects, not `InventoryExecutionLine.unitCost` alone.

  ### Suggested migration order

  1. Regenerate or update application schema/types against the new erp-kit version.
  2. Replace purchase goods receipt read APIs with inventory receipt queries.
  3. Migrate persisted purchase receipt headers and lines into `InventoryExecution` and `InventoryExecutionLine`.
  4. Link or rebuild receipt ledger rows so posted stock facts reference `InventoryExecution.id`.
  5. Update purchase bill matching to use `listPurchaseReceiptEvidenceForMatching` and `ThreeWayMatchEvent.inventoryExecutionLineId`.
  6. Update purchase order status, cancel, close, and amendment customizations to use inventory receipt evidence and inventory supply plan synchronization.
  7. Remove deleted purchase goods receipt tables, fixtures, generated types, and permissions from application code.
  8. Run end-to-end tests for purchase order approval, partial receipt, full receipt, purchase bill three-way matching, cancellation blocking, closing validation, and inventory valuation.

## 0.31.0

### Minor Changes

- 1a2be3d: - Run module unit tests under `@tailor-platform/sdk/vitest`'s `tailor-runtime` environment so `node:*` imports and Node-only globals in production module code error at test time. Applies to the module scaffold (`erp-kit init module`) as well.
- 2e4a0ed: **BREAKING** Singularize directory naming across scaffold and bundled module docs. Logic and runtime behavior are unchanged — only directory names and the configuration that points to them. Prefer running `erp-kit-update-advisor` before updating; it greps the patterns below and produces a per-file migration checklist.

  **Renamed app scaffold directories** (every consumer with a scaffolded app must `git mv`):

  - `backend/src/resolvers/` → `backend/src/resolver/`
  - `backend/src/executors/` → `backend/src/executor/`
  - `backend/src/tests/stories/` → `backend/src/tests/story/`
  - `docs/actors/` → `docs/actor/`

  **Renamed module docs directories** (only consumers with custom modules under `modules/`):

  - `docs/commands/` → `docs/command/`
  - `docs/queries/` → `docs/query/`
  - `docs/models/` → `docs/model/`
  - `docs/features/` → `docs/feature/`

  **CLI surface change:**

  - `erp-kit app generate doc actors <name>` → `erp-kit app generate doc actor <name>`

  **Configuration files to update:**

  - `backend/tailor.config.ts`: `resolver` and `executor` globs (`./src/resolvers/**` → `./src/resolver/**`, same for executor). ⚠ Skipping this silently breaks production deploy — `tailor-sdk apply` will not find resolver/executor files and they will be missing from the deployed GraphQL endpoint.
  - `backend/.oxlintrc.json`: `overrides[].files` glob (`["src/executors/**/*.ts", "src/resolvers/**/*.ts"]` → `["src/executor/**/*.ts", "src/resolver/**/*.ts"]`) and the inline message ("not allowed in executor/resolver.").
  - `.github/workflows/erp-kit-backend-integration.yml` (if copied from template into your repo): the bash dir existence check (`if [ -d "apps/$app/backend/src/tests/stories" ]` → `if [ -d "apps/$app/backend/src/tests/story" ]`).
  - Inline links in `docs/business-flow/*/README.md` and `docs/screen/*.md`: `../../actors/<actor>.md` → `../../actor/<actor>.md`, `../actors/<actor>.md` → `../actor/<actor>.md`.
  - Inline links in bundled module docs (only if consumers wrote custom modules with cross-references): `../commands/<X>.md` → `../command/<X>.md`, same pattern for `queries → query`, `models → model`, `features → feature`.

  **Migration steps:**

  1. Rename directories. For each app:

     ```bash
     git mv backend/src/resolvers backend/src/resolver
     git mv backend/src/executors backend/src/executor
     git mv backend/src/tests/stories backend/src/tests/story
     git mv docs/actors docs/actor
     ```

     For each custom module:

     ```bash
     git mv modules/<name>/docs/commands modules/<name>/docs/command
     git mv modules/<name>/docs/queries modules/<name>/docs/query
     git mv modules/<name>/docs/models modules/<name>/docs/model
     git mv modules/<name>/docs/features modules/<name>/docs/feature
     ```

  2. Update `tailor.config.ts` globs for resolver and executor (2 places per app).
  3. Update `.oxlintrc.json` override glob and the inline message.
  4. Update inline doc links for `actors/` and module docs cross-references.
  5. Update any custom scripts / CI that call `erp-kit app generate doc actors ...` to use `actor`. If you copied `erp-kit-backend-integration.yml` into your repo, also patch its `tests/stories` bash check to `tests/story`.
  6. Refresh skills so future Agent runs use the new paths: `pnpm erp-kit update skills`.
  7. Verify with `pnpm erp-kit app check -p <app-path>` and `pnpm erp-kit module check --path modules`.

  **Common pitfalls** (skipping these is what makes the migration go wrong):

  - `npm update` without renaming directories → `erp-kit app check` / `module check` fail loudly (easy to spot).
  - Directories renamed but `tailor.config.ts` glob left as `./src/resolvers/**` → **Silent**: CI is green, but `tailor-sdk apply` deploys without resolvers/executors.
  - `.oxlintrc.json` glob not updated → lint override (e.g. `no-restricted-imports` for Node builtins) silently stops applying to resolver/executor code.
  - Old `erp-kit-backend-integration.yml` bash dir check left as `tests/stories` → integration tests silently skipped; the workflow detects "no apps with integration tests" and exits 0.
  - Skills not refreshed (`pnpm erp-kit update skills` skipped) → Agent runs (e.g. `erp-kit-app-5-impl-backend`) still write to the old `src/resolvers/` directory.

## 0.30.0

### Minor Changes

- 7e43946: - Add bundled `erp-kit-score` and `erp-kit-update-advisor` skills for evaluating consumer repository alignment and advising erp-kit upgrade impact.
  - Add `erp-kit internal` CLI commands used by bundled skills, including repository thinness, structure, and GitHub Actions template drift checks.
  - Fix the scaffolded product-management frontend `AuthGuard` so generated apps wait for auth readiness before showing the sign-in screen.
- f0f12e6: - Declare `@tailor-platform/sdk` as a peer dependency (`^1.40.0`). `erp-kit` depends on SDK APIs at runtime, so consumers must provide a compatible SDK installation.
- 94dd4f2: - Add `StockReservation` as the inventory reservation balance cache. Reservations now live outside `StockLevel`; `createStockReservation`, `updateStockReservation`, and `closeStockReservation` manage the public reservation lifecycle, and none of these commands mutate StockLevel or write InventoryLedger rows.

  - Make reservations source-line, site, and ATP-slice scoped. `StockReservation` now carries `sourceType`, `sourceId`, non-unique `sourceLineId`, `siteId`, optional `storageLocationId`, `reservedQuantity`, `consumedQuantity`, `status`, and `requiredDate`. Supported source types are `SALES_ORDER`, `TRANSFER_ORDER`, and `MANUFACTURING_ORDER`; reservation status is `OPEN` or `CLOSED`.

  - Update availability calculations to subtract OPEN reservation balances from site-level `StockLevel(stockType="AVAILABLE")` totals in `createStockReservation`, `updateStockReservation`, `getSiteStockSummary`, `issueStock`, and `confirmStockAdjustment(BLOCK)`.

    ```ts
    openReservationQuantity = max(reservedQuantity - consumedQuantity, 0);
    siteAvailableQuantity =
      siteAvailableStockQuantity - openReservationQuantity;
    ```

  - `createStockReservation` now appends one reservation row and does not deduplicate by source line or inventory dimensions. `updateStockReservation` and `closeStockReservation` target one reservation row by `id`. Inventory execution consumes reservation `consumedQuantity` through internal helpers when execution uses reserved demand. Source-line reservation state is derived by aggregating rows by `(sourceType, sourceLineId)`.

  - Align `InventorySupplyPlan` with the same flat ATP ledger shape: `(sourceType, sourceLineId)` is a non-unique grouping key, create appends one supply row, and `updateInventorySupplyPlan` / `closeInventorySupplyPlan` target one supply row by `id`.

- 82d7e7a: - Refactor inventory stock balances from one `StockLevel` row per item/location into one row per item/location/stock type. This is a breaking API/schema change: `StockLevel` no longer stores `onHand`, `reserved`, and `blocked` columns directly. Instead, each row stores one `stockType` and its `quantity`.

  `StockLevel` records have changed from an aggregate balance row:

  ```ts
  // Before
  {
    itemId: "item-1",
    storageLocationId: "loc-1",
    onHand: "10",
    reserved: "2",
    blocked: "3",
  }
  ```

  to stock-type-specific balance rows:

  ```ts
  // After
  [
    {
      itemId: "item-1",
      storageLocationId: "loc-1",
      stockType: "AVAILABLE",
      quantity: "7",
    },
    {
      itemId: "item-1",
      storageLocationId: "loc-1",
      stockType: "BLOCKED",
      quantity: "3",
    },
  ];
  ```

  The unique key changes from `(itemId, storageLocationId)` to `(itemId, storageLocationId, stockType)`.

  - Add `stockType` to `InventoryLedger` rows so each ledger entry identifies the stock slice it affects.

    ```ts
    // Before
    {
      sourceType: "GOODS_RECEIPT",
      dcIndicator: "DR",
      quantity: "10",
    }

    // After
    {
      sourceType: "GOODS_RECEIPT",
      dcIndicator: "DR",
      stockType: "AVAILABLE",
      quantity: "10",
    }
    ```

  - Add stock type enum exports `InventoryLedgerStockType` and `StockLevelStockType`. The enum values are intentionally `"AVAILABLE"`, `"BLOCKED"`, and `"IN_TRANSIT"`; this release does not rename `AVAILABLE` to `UNRESTRICTED`.
  - Update inventory query payloads to expose stock-type slices. Consumers reading direct `StockLevel` columns such as `stockLevel.onHand`, `stockLevel.reserved`, or `stockLevel.blocked` from list results must migrate to `stockType`/`quantity` rows or use aggregate fields from `getStockLevel`.

    ```ts
    // Before: listStockLevels item
    {
      itemId: "item-1",
      storageLocationId: "loc-1",
      onHand: "85",
      reserved: "0",
      blocked: "10",
      availableQuantity: "75",
    }

    // After: listStockLevels item
    {
      itemId: "item-1",
      storageLocationId: "loc-1",
      stockType: "AVAILABLE",
      quantity: "75",
      availableQuantity: "75",
    }
    ```

    `getStockLevel` still returns aggregate values, but now also includes the underlying stock-type rows:

    ```ts
    // After: getStockLevel result
    {
      itemId: "item-1",
      storageLocationId: "loc-1",
      onHand: "85",
      reserved: "0",
      blocked: "10",
      inTransit: "4",
      availableQuantity: "75",
      stockLevels: [
        { stockType: "AVAILABLE", quantity: "75" },
        { stockType: "BLOCKED", quantity: "10" },
        { stockType: "IN_TRANSIT", quantity: "4" },
      ],
    }
    ```

  - Fix SCRAP confirmation from blocked stock so `StockAdjustment(SCRAP)` with a blocked source line writes one `SCRAP / CR / BLOCKED` ledger row and decreases the blocked `StockLevel(stockType="BLOCKED")` once.

    ```ts
    // StockAdjustmentLine
    {
      fromStockCategory: "BLOCKED",
      quantity: "3",
    }

    // Before: ledger rows created during confirm from main
    [
      { sourceType: "SCRAP", dcIndicator: "CR", quantity: "3" },
      { sourceType: "UNBLOCK", dcIndicator: "CR", quantity: "3" },
    ]

    // After: ledger rows created during confirm
    [
      { sourceType: "SCRAP", dcIndicator: "CR", stockType: "BLOCKED", quantity: "3" },
    ]
    ```

    Consumers that counted ledger rows for blocked scrap must update their expectations.

  - Fix concurrent `confirmStockAdjustment` execution by locking the `StockAdjustment` header row before status validation. This prevents two simultaneous confirmations of the same submitted adjustment from both creating ledger rows, especially for increase corrections where duplicate execution could previously double-increase stock.

### Patch Changes

- 6792859: - Fix inventory commands to import external module query dependency types from the inventory module boundary instead of hand-written local query interfaces
- 91b17ee: - Remove stray generated OS command documentation from the sales module command docs.

## 0.29.0

### Minor Changes

- e32822c: Redesign purchase-order amendment audit into a per-field model and add custom-field support to `amendOrderedPurchaseOrder`. **Contains breaking schema, type, and command-input changes — adopters must run a schema migration and update call sites.**

  ### Breaking — DB schema

  - Drop `PurchaseOrder.revisionNumber` (the per-PO counter now lives on the new envelope model).
  - Remove the `PurchaseOrderLineRevision` model (line-only snapshots) entirely.
  - Add `PurchaseOrderRevision`: the amendment envelope, one row per `amendOrderedPurchaseOrder` call, holds `revisionNumber` (per-PO sequence starting at 1), `reason`, and `amendedByUserId` for the user who initiated the amendment.
  - Add `PurchaseOrderFieldChange`: per-field delta rows attached to a revision envelope, with `recordType` (`HEADER` | `LINE`), `recordId`, `fieldName`, `changeKind` (`ADDED` | `MODIFIED` | `REMOVED`), `oldValue`, `newValue`. Header amendments and app-extension (CF/LCF) fields are now recorded alongside standard line fields.

  ### Breaking — module typeRefs

  - `module.db.purchaseOrderLineRevision` is removed.
  - New entries: `module.db.purchaseOrderRevision`, `module.db.purchaseOrderFieldChange`.

  ### Breaking — public types (`@tailor-platform/erp-kit/purchase`)

  - Remove `PurchaseOrderLineRevision<T>` and `PurchaseOrderLineRevisionCreate<T>`.
  - Add `PurchaseOrderRevision<T>` / `PurchaseOrderRevisionCreate<T>` and `PurchaseOrderFieldChange<T>` / `PurchaseOrderFieldChangeCreate<T>`.

  ### Breaking — `amendOrderedPurchaseOrder` input

  - Rename `input.changes` → `input.lineChanges` (still an array of ADD / MODIFY / REMOVE operations).
  - Add `input.headerChanges`: an object patch keyed by header field name. Standard amendable fields are `paymentTermId`, `receivingSiteId`, `orderDate`, `externalSupplierOrderReference`. `undefined` keys are ignored; `null` is an explicit clear for nullable fields. Changing `paymentTermId` revalidates the term and refreshes `paymentTermSnapshotLines`.
  - `EmptyAmendmentChangesError` now also fires when `headerChanges` and `lineChanges` resolve to no actual diff (post-comparison no-op), not only when the arrays/objects are empty.

  ### Breaking — new error codes

  - `ProtectedHeaderFieldError` (`PURCHASE_PROTECTED_HEADER_FIELD`) — runtime defense when an extension-field escape hatch targets a protected header field (`id`, `companyId`, `supplierId`, `orderStatus`, `receiptStatus`, `billingStatus`, `paymentTermSnapshotLines`, `supplierSnapshotName`, `rejectionReason`, `closeReason`, `createdAt`, `updatedAt`).
  - `ProtectedLineFieldError` (`PURCHASE_PROTECTED_LINE_FIELD`) — same idea for protected line fields (`id`, `purchaseOrderId`, `itemId`, `itemSnapshotName`, `itemSnapshotSku`, `unitId`, `createdAt`, `updatedAt`).

  ### Non-breaking additions

  - `amendOrderedPurchaseOrder` ADD and MODIFY accept an `LCF` generic for app-extension line fields (parity with `createPurchaseOrder` / `updatePurchaseOrder`).
  - `amendOrderedPurchaseOrder` `headerChanges` accepts a `CF` generic for app-extension header fields.
  - Audit rows are now written for extension fields on ADD, MODIFY, and REMOVE; MODIFY only emits a row when the value actually changes; values are serialized as ISO strings (Date), `"true"`/`"false"` (boolean), `String(n)` (number/bigint), or JSON otherwise.

  ### Migration notes

  - Drop the `revisionNumber` column from `PurchaseOrder` and the `PurchaseOrderLineRevision` table; create `PurchaseOrderRevision` and `PurchaseOrderFieldChange`. Existing line-revision history cannot be migrated automatically — back it up before applying.
  - Rename every `amendOrderedPurchaseOrder` call site's `changes` field to `lineChanges`; consolidate any header-level amendments (previously done as separate updates) into the new `headerChanges` patch.
  - Replace imports of `PurchaseOrderLineRevision*` types with `PurchaseOrderRevision*` and/or `PurchaseOrderFieldChange*` depending on whether you want the envelope or the per-field rows.

## 0.28.1

### Patch Changes

- 8162a5d: - Fix scaffold `UserProfileMenu` overflowing into the rail when the platform sidebar collapses to icon mode; avatar is now centered and the name/email and sign-out controls are hidden via `in-data-[collapsible=icon]` variants

## 0.28.0

### Minor Changes

- cc93d07: - Fix purchase applications so goods receipts can record the actual received quantity even when it exceeds the ordered quantity, instead of failing receipt creation, update, or posting with an open receipt quantity error
  - Improve purchase order application behavior by keeping remaining receive quantity clamped to `max(ordered - received, 0)` and exposing remaining received quantity available for supplier bill creation
  - Fix supplier bill matching so bills are blocked when the billed quantity exceeds either ordered quantity or available received quantity, while leaving overage payment handling to manual release
  - Update purchase module documentation and tests to describe over-receipt recording, partial billing from unbilled goods receipts, and blocked bill matching outcomes

## 0.27.0

### Minor Changes

- d6ea988: - Require logged-in users for default TailorDB model permissions and default GraphQL read/aggregate permissions.

## 0.26.0

### Minor Changes

- 0098212: - Drop unconditionally immutable fields from `update*` command input types. These were previously accepted but always rejected at runtime, creating a misleading contract.
  - `manufacturing/updateProductionOrder`: removed `orderedItemId`, `companyId`, `siteId` (and `ImmutableScopeFieldError`)
  - `purchase/updatePurchasePriceList`: removed `companyId`, `supplierId` (and `ImmutableOwnerChangeError`)
  - `accounting/updateCostElement`: removed `type` (and `TypeChangeNotAllowedError`)
- 3ef38f4: - Export the previously missing `accounting`, `approval`, `manufacturing`, and `financial-accounting` permissions from `@tailor-platform/erp-kit/app`.
  - Add `ensurePermission`, a throw-based variant of `requirePermission` for resolver entry points.
- ed72f26: - Standardize nullable field handling in `update*` commands: branch on `!== undefined` so `null` clears the column
  - Align input types with DB column nullability (some widened to accept `null`, some narrowed to reject it)
  - Update scaffold resolvers and edit forms to send `null` for cleared optional fields
- 823f1ad: - Add stateful `InventorySupplyPlan` for site-level future inbound supply. Supply projections are keyed by `(sourceType, sourceLineId)`, keep expected inbound quantity separate from location-level `StockLevel` availability, and remain as rows when closed.

  - Add source-document driven inventory supply plan commands:

    ```ts
    await modules.inventory.commands.createInventorySupplyPlan(
      db,
      {
        sourceType: "PURCHASE_ORDER",
        sourceId: purchaseOrder.id,
        sourceLineId: line.id,
        itemId: line.itemId,
        siteId: purchaseOrder.destinationSiteId,
        expectedQuantity: line.quantity,
        unitId: line.unitId,
        expectedDate: line.promisedDate,
        lotId: line.lotId ?? null,
        serialNumberId: null,
      },
      ctx
    );
    ```

    `createInventorySupplyPlan` is insert-only. Calling it for an existing source line returns `SupplyPlanAlreadyExistsError`, so applications should use it when a source document line first starts publishing expected supply.

  - Add `updateInventorySupplyPlan` for amendments to an existing source line. The command is a partial update and can update module-defined additional `InventorySupplyPlan` fields as well as core planning fields:

    ```ts
    await modules.inventory.commands.updateInventorySupplyPlan(
      db,
      {
        sourceType: "PURCHASE_ORDER",
        sourceLineId: line.id,
        expectedQuantity: line.revisedQuantity,
        expectedDate: line.revisedPromisedDate,
      },
      ctx
    );
    ```

    `updateInventorySupplyPlan` requires the projection to exist and remain `OPEN`. It does not modify `receivedQuantity`, does not auto-close when `openQuantity` becomes zero, and allows `expectedQuantity` to be reduced to zero so the source document can decide when to close.

  - Add `consumeInventorySupplyPlan` for receipt flows. Applications should call it after posting the physical stock movement, using the same source line identity:

    ```ts
    await modules.inventory.commands.receiveStock(
      db,
      {
        itemId: line.itemId,
        storageLocationId: receiptLine.storageLocationId,
        quantity: receiptLine.receivedQuantity,
        unitCost: receiptLine.unitCost,
        sourceId: receipt.id,
        lotId: receiptLine.lotId ?? null,
        serialNumberId: receiptLine.serialNumberId ?? null,
        executedAt: receipt.receivedAt,
      },
      ctx
    );

    await modules.inventory.commands.consumeInventorySupplyPlan(
      db,
      {
        sourceType: "PURCHASE_ORDER",
        sourceLineId: line.id,
        consumedQuantity: receiptLine.receivedQuantity,
      },
      ctx
    );
    ```

    Consumption increments `receivedQuantity` and leaves the supply plan `OPEN`; it returns an error when consuming more than the current open quantity. Closing remains the responsibility of the source document workflow.

  - Add `closeInventorySupplyPlan` for explicit source-driven closure. Applications can close by inventory supply plan id or by source line identity:

    ```ts
    await modules.inventory.commands.closeInventorySupplyPlan(
      db,
      {
        sourceType: "PURCHASE_ORDER",
        sourceLineId: line.id,
      },
      ctx
    );
    ```

    Closing transitions the projection from `OPEN` to `CLOSED` and does not delete the row, preserving receipt and planning history.

  - Add `getInventorySupplyPlan` and `listInventorySupplyPlans` queries with computed `openQuantity` clamped to zero. `getInventorySupplyPlan` can read an individual projection, while `listInventorySupplyPlans` returns only `OPEN` planning rows for open supply views.
  - Document the split between current stock and future supply, including source-owned closure, receipt consumption, and open supply planning behavior.

- 64a87dc: - Type `update*` command `.set(...)` accumulators with `Updateable<"TableName">` instead of `Record<string, unknown>` for static column/value checking
  - Narrow three `string`-typed enum inputs (`orderType`, `bomType`, `overheadAbsorptionMethod`) to the generated `{Table}{Column}` enum types; the now-redundant `VALID_OVERHEAD_METHODS` runtime guard is removed
- e76afbe: - Drop redundant `createdAt` / `updatedAt` assignments from every module command — these fields are auto-populated by `db.fields.timestamps()` via Tailor SDK hooks (`hooks.create: true` / `hooks.update: true`)
  - `update*` commands skip the `UPDATE` entirely and return the previously-loaded entity when no fields actually changed, instead of emitting a `.set({})` (which kysely compiles to invalid SQL like `update "T" set  where ...`)
  - Remove the touch-only parent-order rollup from `reportWorkOrderProgress` (it only bumped `updatedAt`; no consumer in the module reads it)
  - Align the `commands.md` skill doc example with the new convention so future generated commands don't bring the pattern back
- 452f8c7: - **BREAKING** Remove the `receiveStockWithInspection` inventory command and its purchase goods-receipt routing hook. Goods receipts now always call `receiveStock`; inbound inspection workflows are out of scope for the inventory module.
- d0152a7: - Narrow command/query enum inputs to the matching generated enum type from each module's `generated/enums.ts`.
- ea75a4b: - **BREAKING** Remove the `incoming` bucket from `inventory::StockLevel`. Future inbound supply is now represented by `InventorySupplyPlan`, so stock-level records only track current on-hand, reserved, and blocked quantities.
  - **BREAKING** Remove StockLevel `incoming` mutations from `receiveStock`, `receiveStockWithInspection`, and stock adjustment creation paths.
  - **BREAKING** Remove `totalIncoming` from `getSiteStockSummary`; callers should use `listInventorySupplyPlans` for open future supply.

### Patch Changes

- 941e7cb: - Reduce E2E flakiness in scaffold templates (CI retries, `expect.poll` health check, retry-with-backoff, viewport-safe combobox clicks)

## 0.25.0

### Minor Changes

- a24e3d3: - Add label-aware product variant SKU generation by passing `axisLabels` as a new optional fourth argument to `product-management` `skuStrategy` callbacks. Existing three-argument strategies continue to work unchanged.

  - Improve `generateVariants` so it fetches `ProductAttributeValue.label` values in a single query and provides labels in the same order as the variant axis value IDs passed to the strategy.
  - Enable applications to generate externally compatible SKUs from real variant labels instead of opaque value IDs. For example, an apparel catalog can configure `skuStrategy` to produce SKUs like `AOTSS1789BLKXL` from product code `AOTSS1789` and labels `["BLK", "XL"]`, matching warehouse, 3PL, or EDI-provided item codes while still preserving ID-based variant identity inside erp-kit.

  ```ts
  const productManagement = defineProductManagementModule({
    // ...
    skuStrategy: (
      productCode,
      _axisValues,
      _currentVariantCount,
      axisLabels = []
    ) => {
      return `${productCode}${axisLabels
        .map((label) => label.replaceAll("x", ""))
        .join("")}`;
    },
  });
  ```

## 0.24.0

### Minor Changes

- b2a2066: - Add `approval` module covering the full policy → request → step → decision flow with policy templates, request lifecycle, role-aware step routing (`required` flag + `roleQuorum` ALL/ANY), and immutable decision history
  - Add `listUsersByRole` query in `user-management` for role-membership lookup at runtime

### Patch Changes

- 200a662: - Improve `product-management` and `user-management` scaffold templates by removing the audit module wiring so generated apps no longer ship with audit logging out of the box (Tier 1-4 docs, backend resolvers, seed data, and frontend pages/e2e fixtures all updated)
  - Improve command performance by replacing per-row `INSERT` loops with single bulk `INSERT` across 22 command sites in `accounting`, `audit`, `finance-ledger`, `inventory`, `manufacturing`, `purchase`, and `sales` modules, reducing O(N) sequential round-trips when persisting child entities (lines, rules, work orders, variance lines, etc.)

## 0.23.0

### Minor Changes

- 79cb10b: - Add `invoker` support to `createContext` for machine-user-aware audit/permissions, and align scaffold templates with SDK 1.43
  - Bulk-insert audit `ChangeDetail` rows in `logAuditEvent`
  - Dependency updates (@tailor-platform/sdk 1.43, urql, tailwind, pnpm, etc.)

## 0.22.0

### Minor Changes

- 8427bca: - Add `compare-erp` skill for axis-aligned single-point ERP design comparison (SAP / Oracle / Dynamics 365 / NetSuite / Odoo) with parallelized per-ERP research subagents
  - Refactor inventory module: remove Warehouse entity and flatten hierarchy to Site → StorageLocation, dropping zone/bin (locationType, parentId) to match SAP MM's Plant → Storage Location pattern
  - Refactor skills: consolidate `erp-kit-app-shared` and `erp-kit-module-shared` into a single `erp-kit-shared` and update all consuming skills
  - Fix license checker by adding MIT-0 to the unencumbered license group so transitive MIT-0 deps (e.g. `@csstools/color-helpers`) are accepted
  - Regenerate sales module codegen
  - Mark generated `.agents/skills/erp-kit-*.md` files as `linguist-generated` to collapse them in PR diffs and exclude them from language stats

## 0.21.0

### Minor Changes

- aa36a08: Remove the `Warehouse` entity and flatten the inventory structure to two tiers (Site → StorageLocation):

  - `Warehouse` DB model, commands (`create/update/deactivate/reactivateWarehouse`), and queries (`getWarehouse`, `listWarehouses`) are removed
  - `StorageLocation` now references `Site` directly via `siteId`; `warehouseId`, `parentId`, and `locationType` fields are removed (zone/bin hierarchy is no longer modeled)
  - Name and code uniqueness for `StorageLocation` is enforced within a Site
  - `getWarehouseStockSummary` renamed to `getSiteStockSummary`; aggregates stock across all active StorageLocations under a Site
  - `defineModule` extension hook moved from `warehouse?:` to `storageLocation?:`; applications pass `{ fields: ... }` here to extend StorageLocation
  - Errors `WarehouseNotFoundError`, `WarehouseInactiveError`, `DuplicateWarehouseNameError`, `InvalidLocationTypeError`, `ParentZoneNotFoundError`, `ParentNotAZoneError`, `ParentInDifferentWarehouseError`, `WarehouseNotActiveError`, `WarehouseNotInactiveError` are removed
  - Stock operation rationale updated: all ledger writes now flow through atomic commands (`receiveStock`, `issueStock`) or the StockAdjustment approval workflow; no intermediate `InventoryRequest` entity

## 0.20.0

### Minor Changes

- 839d4b8: Apply `defineLifecycle` to every stateful model and wire it into commands, DB schema, and queries.

  Breaking changes for downstream apps:

  - **`Currency` / `Unit` / `UoMCategory` schema (data migration required):** `isActive: boolean` is replaced by `status: "ACTIVE" | "INACTIVE"`. Migrate existing rows (`true → "ACTIVE"`, `false → "INACTIVE"`), seed data, and any code reading these fields.
  - **`additionalStatuses` option removed** from `businessPartner`, `coa-management`, `item-management`, etc. Custom statuses on these modules are no longer supported.
  - **`from?: string[]` removed** from `activate*` / `deactivate*` / `reactivate*` commands. Valid source states are fixed by the lifecycle. Affects both TypeScript calls and GraphQL mutation arguments.
  - **DB column type narrowed** (no data migration): `db.string()` → `db.enum(...)` for `Company.status`, `Department.status`, `Site.status`, `AuditPolicy.status`. Generated Kysely/GraphQL enum types are stricter.
  - **Query input types narrowed** from `string` to generated enum types: `ListAuditPoliciesInput.status`, `GetDepartmentChildrenInput.status`, `ListDepartmentsByCompanyInput.status`, `ListSitesByCompanyInput.status`.

- c67bf3b: - Add `#### Filters`, `#### Line Items`, and `## Sheets` sections to `screen.yml` schema; convert `#### Available Actions` from bullet list to a table with Action / Condition / Requires Input / Confirmation columns
  - Add action-input-param handling in `screen-extraction` and `page-detail` skill references so DetailView mutations requiring user input generate a Dialog before calling the mutation
  - Improve frontend skill references (`erp-kit-app-6-impl-frontend`) by consolidating duplicated content and expanding component / page examples across `components`, `page-common`, `page-detail`, `page-form`, and `page-list`
  - Fix `generateErrors()` codegen silently dropping conflicting error descriptions; codegen now rejects conflicts at build time and unifies descriptions for generic codes (`INVALID_STATE`, `INVALID_STATE_TRANSITION`, `INVALID_STATUS_TRANSITION`, `UNAUTHORIZED`, `INSUFFICIENT_PERMISSION`, `VALIDATION_ERROR`) across modules
  - Fix empty ListView scaffold templates hiding the table header; `EmptyState` now renders inside `Table.Body` with `colSpan` so column headers remain visible

### Patch Changes

- 6fb4a1c: Fix unsafe non-null assertions in inventory commands

  - Make `itemManagementQueries` dependency parameter required instead of optional in 5 inventory commands (issueStock, receiveStock, receiveStockWithInspection, createStockAdjustment, updateStockAdjustment)
  - Replace `executeTakeFirst()` + `!` with `executeTakeFirstOrThrow()` for insert results in issueStock and confirmStockAdjustment

- dfe635a: Remove unsafe cross-module `as never` type casts and replace with proper typing

  - Remove ~100 unnecessary `as never` casts from accounting, finance-ledger, sales, and manufacturing modules where generated Namespace stubs already provided sufficient type information
  - Replace `Record<string, unknown>` result casts with native Kysely types for TrialBalanceLine queries
  - Add typed cross-module adapter functions for JournalEntry/JournalLine access in accounting/lib, confining `as never` to single boundary-crossing points
  - Fix enum narrowing in sales queries by typing input status fields with actual DB enum types
  - Fix array/date type narrowing in manufacturing queries

- 6179c26: Replace unsafe non-null assertions on executeTakeFirst results with executeTakeFirstOrThrow across all module commands
- 7620b5f: Address remaining type-audit P0 issues: unsafe `executeTakeFirst()` assertions and dynamic SQL `as never` casts

  - Replace `executeTakeFirst()` + `!` with `executeTakeFirstOrThrow()` for INSERT/UPDATE results in 9 sales and purchase commands (createSalesCreditNote, issueSalesInvoice, updateSalesInvoice, convertChannelOrder, createGoodsReceipt, createPurchaseBill, createPurchaseRequisition, updatePurchaseBill, updateGoodsReceipt)
  - Add proper null guards returning domain errors for SELECT results that previously used `!` in issueSalesInvoice and updateSalesInvoice
  - Eliminate `orderLine!.unitId` in updatePurchaseBill and updateGoodsReceipt by merging validation and insert-value construction into a single loop over `lines`, accumulating into `Insertable<"PurchaseBillLine">[]` / `Insertable<"GoodsReceiptLine">[]` directly (no intermediate `.map()`)
  - Remove unnecessary `as never` casts from accounting/createBudget, accounting/generateVarianceReport, and finance-ledger/listPeriodCloses by passing literal-union table and column names directly to Kysely
  - Confine the remaining `as never` in manufacturing/listWorkCentersBySite to the `workCenterType`/`name` Custom Fields filter and document the rationale (these fields are not part of the base WorkCenter schema)

## 0.19.0

### Minor Changes

- d1a0486: - Add atomic stock commands (receiveStock, issueStock) for cross-module inventory operations
  - Add receiveStockWithInspection command with presence-based inspection policy routing in purchase module
  - Add StockAdjustment workflow (DRAFT → SUBMITTED → CONFIRMED) for internal inventory corrections
  - Add InventoryLedger with debit/credit indicator (dcIndicator) for immutable stock change tracking
  - Add FIFO, AVCO, and STANDARD_COST valuation recalculation on stock movements
  - Improve scaffold app typecheck scripts with gql-tada validation
  - Fix scaffold template version pinning and @types/node alignment
  - Refactor inventory module: replace StockMovement/InventoryAdjustment with StockAdjustment + atomic commands
  - Refactor inventory module: remove InventoryCount (count variances now handled via StockAdjustment CORRECTION)
  - Refactor inventory numeric fields from float to decimal with Decimal arithmetic
  - Update dependencies: @tailor-platform/sdk, lucide-react, react-ecosystem, vitest, knip, build tools

### Patch Changes

- 79d291d: Remove unsafe non-null assertions on executeTakeFirst results across finance-ledger, sales, and purchase modules. SELECT queries now return domain errors on null, INSERT/UPDATE operations use executeTakeFirstOrThrow.
- 7b54121: Make cross-module query dependencies required in commands to eliminate unsafe non-null assertions. The affected commands (`createPartner`, `updatePartner`, `createItem`, `updateItem`, `createLot`, `assignSerialNumber`, `reserveStock`) previously declared dependency parameters as optional while accessing them with `!`, which would crash at runtime if a caller omitted them. All callers already pass these dependencies, so making them required restores type-level safety without behavioral changes.
- ba8e325: Migrate linter from ESLint to oxlint for significantly faster lint performance. All existing lint rules have been preserved using oxlint native rules, jsPlugins, and a custom module boundary plugin.

## 0.18.0

### Minor Changes

- 80e2056: - Add product-management app template with inventory, purchasing, and sales workflows
  - Improve skill references by moving db-field-api to shared and adding .files() documentation
  - Improve app-5-impl-backend skill with TailorDB API references for db-field-api and db-relations
  - Fix obsolete attrError parsing in use-toast hook across app templates
  - Refactor any types to proper Kysely types (Selectable, Transaction generics)
  - Refactor explicit tuple types to use `as const` and remove unnecessary spreads and casts

## 0.17.0

### Minor Changes

- ff4087d: - Add multiple app template support in `app init` command
  - Add spec-specific fixtures, sync-check rules (actor, screen/page-object), and audit-log e2e tests
  - Add `minimumReleaseAge` setting to scaffold `pnpm-workspace.yaml`
  - Add lexicographic sorting to generated GraphQL schema
  - Improve skill resolver extraction with module command list and state transition lookup
  - Refactor purchase module: strict field validation, amendment revision history, and PO model cleanup
  - Update frontend-skill reference documentation
  - Remove `@tailor-platform/function-types` dependency
  - Update dependencies: vite v8.0.5, @vitest/coverage-v8 v4.1.3, @playwright/test v1.59.1, eslint/typescript-eslint, react-hook-form v7.72.1, @tailor-platform/sdk

## 0.16.0

### Minor Changes

- 9bb24e2: - Add GitHub Actions composite action for Tailor Platform token fetching and reusable backend integration test workflow
  - Add stale erp-kit-\* workflow and action cleanup during `erp-kit update`
  - Improve app skills to adopt "adapt reference app" paradigm with init-time scaffolding and deploy workflow
  - Improve module init to scaffold boilerplate during `module init` instead of `module generate code`
  - Fix PurchasePaymentTermLine snapshot to select only snapshot-relevant fields, avoiding TailorDB rejection
  - Refactor app init to scaffold full boilerplate so `app generate code` focuses on doc-based stub generation
  - Refactor app-deploy into app-shared references instead of standalone skill

## 0.15.0

### Minor Changes

- 49c6c02: - Add `erp-kit run` interactive script runner command
  - Add edge-case tests and coverage setup for purchase module
  - Improve app-data skill with updated guidance
  - Add integration test workflow for scaffold backend

## 0.14.0

### Minor Changes

- b737558: - Add hierarchical permission matching: scope-level and module-level keys grant access to all commands under them

## 0.13.0

### Minor Changes

- 2b4cffe: - Add Result Checking rules to command and query skill documentation
  - Fix result handling violations in inventory commands (confirmStockMovement, createInventoryAdjustment, createStockMovement, updateStockMovement)
  - Fix result handling in purchase commands (createPurchaseBill, postGoodsReceipt) with proper error propagation
  - Improve integration test setup to auto-create and teardown workspace per test run
- 28a2125: add data-ingest skill

## 0.12.0

### Minor Changes

- 50c63b5: - Fix circular dependency in module barrel exports
  - Fix changeset-bump skill to recreate branch from main avoiding stale changesets
  - Update @tailor-platform/sdk dependencies
  - Update dependencies: typescript v6, lucide-react v1, pnpm v10.33.0, knip v6.1.0, @typescript-eslint/parser v8.58.0, pnpm/action-setup v5

## 0.11.0

### Minor Changes

- cedb4bd: - Add permission scope hierarchy to permission keys across all modules
  - Add app CI workflow template for application projects
  - Refactor user-management to replace PermissionGroup with direct Role permissions
  - Fix default workflow path inclusion in fileMatch override
  - Fix module-5-impl skill to include db field builder API reference
  - Update dependencies: @jackchuka/mdschema v0.12.8, pnpm/action-setup v5, build-tools

## 0.10.0

### Minor Changes

- 7a0d6a5: - Redesign user-management RBAC with permission groups for flexible role-based access control
  - Add debug logging to defineCommand/defineQuery for easier troubleshooting
  - Rename product-management setProductAttributeAssignment to assign/remove pattern (BREAKING)
  - Migrate purchase-management document lines from JSON columns to separate tables
  - Improve progress log error messages and schema-first logging
  - Fix performance.now() replaced with Date.now() for V8 compatibility
  - Remove unused scaffold permissions.ts template
  - Add erp-kit quickstart guide documentation
  - Add knip dead code detection with CI job

## 0.9.0

### Minor Changes

- afa2455: - Add seed:validate script and skill step to scaffold template
  - Add vitest to module scaffold template
  - Add version self-checking to all erp-kit skills
  - Fix command return properties to use full model names across all modules
  - Fix tree-shaking by adding sideEffects annotations to all module defineModule exports
  - Fix scaffold app integration tests
  - Fix inactive users being able to access the scaffold app
  - Fix skills CLI version check, remove erp-kit-shared dependency
  - Refactor audit pages out of user-management directory into standalone location
  - Remove audit dependency from coa-management module

## 0.8.0

### Minor Changes

- 16f35c6: - Add project scaffolding to `erp-kit init` command (pnpm workspace, package.json, directory structure)
  - Add accounting module implementation with models, commands, queries, and test fixtures
  - Add app-shell primitives and component cheat sheet reference for frontend implementation skill
  - Improve frontend templates by consolidating duplicate UI components into shared packages
  - Update manufacturing module documentation with additional model and command details
- cdb3690: - Add user-management scaffold template with full RBAC and audit support
  - Add `update` command pattern with union lookup keys for flexible record updates
  - Add app-shell primitives and detail-view reference for frontend implementation
  - Fix app scaffold expansion to exclude docs directory from template processing
  - Improve update command conventions with followup refinements
- 16f35c6: - Add manufacturing module with BOM, work orders, and production tracking
  - Add story test case sync-check and stub generation for `erp-kit test` command
  - Update workspace creation instructions and deployment steps in skills

### Patch Changes

- 16f35c6: - Fix relative shared imports to use package self-reference for consistent module resolution
  - Fix priority calibration in requirements review skill to improve accuracy
  - Fix severity validation in module-2 requirements review skill
  - Update module-4-plan-review skill and regenerate .agents/skills

## 0.7.0

### Minor Changes

- f30a314: - Add progress log tracking to app skill workflows for better visibility into skill execution
  - Add missing test file detection as errors in sync check
  - Fix APP_PATHS.storySegment reference to APP_PATHS.docs.story after path restructure
  - Fix CI git identity configuration and remove downstream-only workflow
  - Fix erp-kit command examples in skills to include npx prefix
  - Fix skill file sync and table formatting for CI compatibility
  - Fix app --path flag to expect direct app directory
  - Refactor progress logging section to top of app skill files for consistency
  - Remove resolver test generation from sync check

## 0.6.0

### Minor Changes

- 0cb94dc: - Add manufacturing module with BOM, work orders, and production tracking
  - Add story test case sync-check and stub generation for `erp-kit test` command
  - Update workspace creation instructions and deployment steps in skills

### Patch Changes

- 0cb94dc: - Fix relative shared imports to use package self-reference for consistent module resolution
  - Fix priority calibration in requirements review skill to improve accuracy
  - Fix severity validation in module-2 requirements review skill
  - Update module-4-plan-review skill and regenerate .agents/skills

## 0.5.1

### Patch Changes

- fb40e4d: - Fix mdschema binary resolution to use platform-specific native binaries, enabling Windows support
  - Fix init commands to be idempotent when directory already exists instead of erroring
  - Add package.json and dev tooling to module template
  - Update app skill to use erp-kit app generate seed

## 0.5.0

### Minor Changes

- 15c7f9c: - Add sales module with full TDD implementation (models, commands, queries, tests)
  - Add purchase module with full TDD implementation (29 commands, 8 queries, 104 process flow branches)
  - Add primitives module seed data (currencies, UoM categories, units, exchange rates) with deterministic UUID v5 IDs
  - Add `erp-kit app generate seed` CLI command for discovering module seed exports and writing JSONL files
  - Add module-3-update-plan skill for targeted doc fixes based on plan review feedback
  - Add scaffolded code orientation to app implementation skills (app-5-backend, app-6-frontend)
  - Add module metadata validation to app-4-plan-review skill
  - Add comprehensive documentation for inventory module Item model and lifecycle
  - Improve review skills with query-specific parity checks and enhanced test coverage
  - Improve support for N/A resolver status for background jobs and system events
  - Fix `.claude/skills` symlink on Windows using junction fallback
  - Refactor shared/testing exclusion handling by removing dead code
  - Update @tailor-platform/sdk to 1.25.4 (kysely now bundled in SDK)
- fb7e95a: - Add sales module with full TDD implementation
  - Add purchase module with full TDD implementation
  - Add N/A resolver status support for background jobs and system events
  - Add module-3-update-plan skill for targeted doc fixes
  - Add scaffolded code orientation to app implementation skills
  - Add module metadata validation to app review skills
  - Fix `erp-kit init` failing to create `.claude/skills` symlink on Windows (junction fallback + copy fallback)
  - Fix `erp-kit update skills` to refresh copied `.claude/skills` on Windows
  - Fix inventory documentation

## 0.4.1

### Patch Changes

- 4aada62: - Fix relative shared imports to use package self-reference for consistent module resolution
  - Fix priority calibration in requirements review skill to improve accuracy
  - Fix severity validation in module-2 requirements review skill
  - Update module-4-plan-review skill and regenerate .agents/skills

## 0.4.0

### Minor Changes

- d6f6d7e: - Add `erp-kit doc` command for AI-friendly module documentation rendering
  - Add `erp-kit doc search` command for searching across module documentation
  - Generate resolver code from app-level documentation specs
  - Add coa-management module with full GL account hierarchy support
  - Add business-partner module (renamed from supplier-management)
  - Add audit module with shared utilities for tracking changes
  - Add organization module with Company, Site, and Department models
  - Add tax module documentation with full feature specs
  - Add storage-master module documentation with full feature specs
  - Complete user-management RBAC documentation
  - Consolidate module roadmap from 21 to 12 modules, removing redundant scope
  - Remove obsolete modules per consolidated roadmap
  - Change resolver path convention from `src/modules/*/resolvers` to `src/resolvers`
  - Refactor and unify `src/` internals for cleaner module boundaries
  - Improve app skill workflow guidance and module integration quality
  - Update dependencies: eslint v10, vitest v4, vite v8, shadcn v4, @tailor-platform/sdk v1.25.2

## 0.3.0

### Minor Changes

- a8462ee: Reorganize scaffold into init/generate and update README

  - Refactor: replace `module scaffold` and `app scaffold` with `init` + `generate doc` / `generate code` subcommands
  - Feat: add `erp-kit-app-shared` skill consolidating duplicated references across app skills
  - Feat: add db stub generation and `reset:module` script
  - Refactor: run generate before impl agents in `erp-kit-module-5-impl`
  - Docs: rewrite README to align with current CLI commands, flags, skills (17), and testing exports

## 0.2.2

### Patch Changes

- 980a1f0: Add app-level scaffold with template files

## 0.2.1

### Patch Changes

- 4c94ba6: Add erp-kit-app-5-implementation skill, remove app-4-design and app-5-design-review skills

## 0.2.0

### Minor Changes

- 373b856: Add doc-driven deterministic code generation, defineQuery functions, type-safe command errors with Result type, license check command, auto-generated CLI docs, and item-management module. Fix cross-module type references in Kysely codegen and module scaffold structure. Refactor shared module skills, cross-module dependency handling, and scaffold templates.

## 0.1.2

### Patch Changes

- 0c9a2e5: CQRS query support and app/module subcommands

## 0.1.1

### Patch Changes

- 2f43392: Add MIT LICENSE and include CHANGELOG.md in published package
