# @tailor-platform/erp-kit

## 0.59.0

### Minor Changes

- c89874d: **BREAKING** Regenerate the account-receivable error classes from the module docs. Error codes are unchanged, but class names lose the `Ar` prefix (`ArDocumentNotFoundError` becomes `DocumentNotFoundError`) and some error messages now follow the doc wording.
- 26c1695: Enable Oxlint's `typescript/no-deprecated` rule as an error in erp-kit's own lint config and in the shared `@tailor-platform/erp-kit/oxlint/{module,backend,frontend}` configs, so calling a `@deprecated`-tagged API (SDK or otherwise) now fails lint instead of only showing a strikethrough in the editor.
  
  This also applies to scaffolded module/backend/frontend projects and, transitively, to existing consumer projects that extend the shared config — a project with an existing deprecated-API call will start failing lint after picking up this version. Two deprecated-Zod-API usages already in erp-kit and its ERP app template (`z.string().datetime()`, `z.ZodIssueCode.custom`) were migrated to their non-deprecated replacements as part of this change; the intentionally-still-callable `setBaseCurrency` deprecation stub is exempted via a scoped lint override / inline suppression rather than fixed, since it exists specifically to keep returning a deprecation error to callers.
- bd96431: Add a header-level transaction currency to `PurchaseOrder`.
  
  - **BREAKING** `PurchaseOrder` gains a required `currencyId` column, and `createPurchaseOrder` takes it as a new input. The kit does not handle FX, so the domain rejects a currency that is not the company's base currency with `PURCHASE_CURRENCY_MISMATCH`, and a missing company with `PURCHASE_COMPANY_NOT_FOUND`.
  - **BREAKING** The purchase module's `defineModule` requires `primitives.db.currency`.
- 6bd831b: Remove the `primitives` module's `setBaseCurrency` command. It had already been fully deprecated to a no-op that only ever returned `SetBaseCurrencyDeprecatedError`, directing callers to the organization module's company-level base currency assignment instead; no code path used it for anything else.
  
  **BREAKING** `setBaseCurrency` is no longer exported from the `primitives` module's command set. Callers were already unable to use it for anything beyond receiving a deprecation error, so this removes a command that could not previously succeed.
- 21053aa: Add `SalesItem` to the sales module. It is a 1:1 record for an item-management item, keyed by `itemId`, managed with `createSalesItem` / `updateSalesItem` / `deleteSalesItem`.
  
  - **BREAKING** `requiresPhysicalFulfillment` is no longer accepted on sales order line input. It is resolved from the line item's `SalesItem` and frozen onto the line.
  - **BREAKING** Selling an item now requires a `SalesItem`. The sales order commands reject items without one with `SALES_ITEM_NOT_SELLABLE`.
  - **BREAKING** A confirmed line's fulfillment expectation can no longer be amended. Use REMOVE + ADD to re-resolve it.
- 5831a7a: Move the transaction currency from `SalesOrderLine` to the `SalesOrder` header.
  
  - **BREAKING** `SalesOrder` gains a required `currencyId` column and `SalesOrderLine` loses its own. `createSalesOrder` takes `currencyId` on the header, and the line inputs of `createSalesOrder`, `updateSalesOrder`, and `amendConfirmedSalesOrder` no longer accept it. The kit does not handle FX, so the domain rejects a currency that is not the company's base currency with `SALES_CURRENCY_MISMATCH`, and a missing company with `SALES_COMPANY_NOT_FOUND`.
  - **BREAKING** The sales module's `defineModule` requires `organization.queries.getCompany`.
- c7a778f: Simplify the sales order fields.
  
  - **BREAKING** `SalesOrder` drops the unused `paymentTermId` / `paymentTermSnapshot` / `requestedShipDate` columns and `SalesOrderLine` drops the unused `billingPolicy` / `requestedShipDate` columns.
  - **BREAKING** The order addresses are renamed from `shippingAddressSnapshot` / `billingAddressSnapshot` to `shippingAddress` / `billingAddress` and stored as plain text instead of serialized JSON.
- 773584d: Apply the domain / repository / command layering (`docs/module-ddd-practices.md`) to the inbound-shipment and outbound-shipment modules.
  
  - **BREAKING** All shipment commands return ids only: `{ inboundShipmentId }` / `{ outboundShipmentId }`. Read data through the shipment queries
  - **BREAKING** Creating a shipment now validates the referenced items and storage locations before the line quantity invariants, so an input with both problems surfaces the master-data error first
- 2e938b7: Add an optional default expense account to supplier accounts, including validation, scaffold UI support, and non-PO invoice defaults.

### Patch Changes

- d4632ca: Restore automatic AppShell DataTable metadata generation in the ERP scaffold with `@tailor-platform/app-shell-sdk-plugin` 0.1.1, which supports the SDK 2.x table and relation APIs.

## 0.58.0

### Minor Changes

- 9b16329: Give apps a canonical directory structure in the structure check, mirroring what
  #741 did for modules (affects `erp-kit verify` and `internal measure structure`).
  `measureApps` previously only checked that `backend/` and `frontend/` existed.
  
  A directory is required only where some part of the app flow reads, writes or
  cross-checks it — being present in every real app is not on its own a reason:
  
  | directory | required because |
  | --- | --- |
  | `docs/actor/` | `app sync-check` pairs each doc with an actor in the e2e user fixture, both ways |
  | `docs/business-flow/` | `app generate code` reads its story docs; the story-test-case and e2e-scenario sync-checks use them as the doc side |
  | `docs/resolver/` | input to `app generate code`; sync-check pairs it 1:1 with `backend/src/resolver/` |
  | `docs/screen/` | `app sync-check` pairs each doc with an e2e page object |
  | `backend/src/`, `frontend/src/` | where both tsconfigs point (`"@/*": ["./src/*"]`, `include: ["src"]`), and the parent of every layer below |
  | `backend/src/resolver/` | output of `app generate code`; the code side of the resolver sync-check |
  | `backend/src/tests/` | holds `story/`, the test side of the story-test-case sync-check |
  | `frontend/src/components/` | `erp-kit verify` requires a co-located test for every file under it |
  | `frontend/src/pages/` | the impl-frontend step writes one directory per screen here; impl-review checks `pages/**/page.tsx` against the screen docs |
  | `frontend/src/graphql/` | the scaffold's `generate-graphql` script writes `graphql/generated/schema.graphql`, and `tsconfig.app.json` points gql.tada at that path — typecheck cannot run without it |
  | `frontend/e2e/` | the code side that the actor, screen and e2e-scenario sync-checks pair those `docs/` layers with; the impl-frontend step writes its `tests/`, `fixtures/` and `pages/` |
  
  `frontend/src/lib/` is **not** required. It exists in every real app, but only
  holds whatever helpers an app happens to need and nothing in the flow refers
  to it.
  
  Only the app root restricts what else may exist: a directory there outside
  `backend`/`frontend`/`docs` is reported as `unexpected-dir`, mirroring the module
  side. The nested rules only check that their required layers are present. Apps
  legitimately add their own directories — `i18n/`, `features/`, per-domain
  backend layers, and hand-written documentation next to the generated docs it
  describes — and none of that breaks anything.
  
  Layer names are read off `APP_PATHS`, the same constant the generators and
  sync-checks use, so renaming a layer there carries over. Only the layer name
  follows — the parent path is written out in the rule itself.
  
  Also: the naming rule tree carried its own `^(backend|frontend|e2e|docs)$`
  matcher on app children, which decided the same question as the new allow-list
  and disagreed with it about `e2e/`. Keeping both would report every
  non-canonical dir twice, once as `naming-violation` and once as
  `unexpected-dir`, so the matcher is removed and the allow-list is the only place
  that decides app-root shape. An app-root `e2e/` is no longer accepted — e2e
  tests belong in `frontend/e2e/`.
  
  Consumers will start seeing verify failures for apps that drift from this shape.
  The most likely one is a layer left pluralised by the 0.31.0 singular-naming
  migration (`docs/actors`, `backend/src/resolvers`): rename it to the singular
  form. Those are not cosmetic — the flow globs the singular path, so a
  `docs/actors` directory means `erp-kit app check` validates no actor docs at all
  while still exiting 0, and a `backend/src/resolvers` directory means every
  resolver doc is reported as having no implementation.
  
  A missing parent short-circuits its nested rules, so an app without `docs/`
  reports one violation rather than one per layer. Every rule root is itself
  required by an earlier rule, so a missing directory always surfaces exactly
  once instead of silently disabling the checks below it.
- 4a05d69: - Add company-scoped Customer Account and Supplier Account aggregates with independent lifecycles, account names, preferred currencies, and purpose-specific address and bank-account usages
  - Add partner-directory and account-management queries, commands, resolvers, and ERP scaffold screens for managing multiple customer and supplier accounts per Business Partner
  - Improve account reference validation by loading the owning Business Partner aggregate under a lock before creating or updating account usages
  - Update ERP scaffold seeds, generated types, forms, detail pages, and integration coverage for the account-based Business Partner model
  
  Breaking changes:
  
  - Refactor sales, purchasing, receivables, payables, and payment workflows to reference Customer Account or Supplier Account IDs instead of Business Partner IDs while exposing account and partner lifecycle states separately
  - Remove obsolete Business Partner role, contact-person, identification, activation, and unused query APIs; consumers using these commands, queries, types, or permission scopes must migrate to the new account-based model
  - Remove the IBAN and SWIFT fields from partner bank accounts; consumers must migrate any stored values before deploying the updated schema
- 6c25e14: Remove the purchase module's requisition and supplier-pricing subdomains — 17 commands, 3 queries and 5 db types that no app referenced.
  
  Breaking changes:
  
  - `purchase.db` loses `purchaseRequisition`, `purchaseRequisitionLine`, `purchaseRequisitionLineAllocation`, `purchasePriceList` and `purchasePriceRule`. Consumers that deployed these tables will see them dropped on the next `tailor deploy` — export the data first if it needs to be kept.
  - The `purchase:purchaseRequisition` and `purchase:purchasePricing` permission scopes no longer exist. Drop them from any role or user permission list.
  - `defineModule` no longer takes field extensions for the removed tables, and its `primitives` parameter narrows to `db: { unit }`.
- 6c60526: Add `PurchaseItem` to the purchase module and resolve a purchase order line's `requiresPhysicalReceipt` from it instead of from line input. `PurchaseItem` is a 1:1 record for an item-management item; its existence is the decision that the item may be procured, and it owns the purchasing defaults an order line freezes. It is managed with `createPurchaseItem` / `updatePurchaseItem` / `deletePurchaseItem`, keyed by `itemId`.
  
  - **BREAKING** `requiresPhysicalReceipt` is no longer accepted on purchase order line input. It is resolved from the line item's `PurchaseItem` and frozen onto the line by `createPurchaseOrder`, `updatePurchaseOrder`, and `amendOrderedPurchaseOrder`. The `PurchaseOrderLine.requiresPhysicalReceipt` column is unchanged, so receipt tracking and invoice matching read the same value as before
  - **BREAKING** Ordering an item now requires a `PurchaseItem`; the commands above and `approvePurchaseOrder` reject items without one with `PURCHASE_ITEM_NOT_PURCHASABLE`. Existing apps must create a `PurchaseItem` for every item they purchase
  - **BREAKING** An ordered line's receipt expectation can no longer be amended — it belongs to the item's purchasing record; use REMOVE + ADD to re-resolve it
- c559dae: Remove the sales module's channel, listing and customer-pricing subdomains — 21 commands, 11 queries and 6 db types that no app referenced.
  
  Breaking changes:
  
  - `sales.db` loses `channel`, `listing`, `salesPriceList`, `salesPriceRule`, `channelOrder` and `channelOrderLine`. Consumers that deployed these tables will see them dropped on the next `tailor deploy` — export the data first if it needs to be kept.
  - `SalesOrder.channelId` and `SalesOrderLine.matchedPriceRuleId` go with the tables they referenced, and `listSalesOrders` loses its `channelId` filter. `SalesOrderLine.priceSnapshot` goes too — it held the matched rule and price list behind a line price, which no longer exist.
  - The `sales:marketplace` and `sales:salesPricing` permission scopes no longer exist. Drop them from any role or user permission list.
  - `defineModule` no longer takes field extensions for the removed tables, and its `organization` and `primitives` parameters narrow to `db` only — no remaining command calls `getCompany` or `getCurrency`.
- ea1a6e4: Apply the domain / repository / command layering to the sales module (`SalesOrder`).
  
  - **BREAKING** All sales commands return ids only: `{ salesOrderId }` (amendment additionally returns `{ revisionId }`, the recalculate commands return `{ salesOrderIds }`). Read data through the sales queries
  - **BREAKING** Creating a sales order with no lines now fails with `EMPTY_ORDER_NOT_ALLOWED` instead of `INVALID_ORDER_LINE`, matching the update and submit invariant
  - **BREAKING** Amending a confirmed sales order now validates added lines with the same policy as create: the item must be `ACTIVE` (`ITEM_NOT_ACTIVE`) and belong to the order's company (`CROSS_COMPANY_REFERENCE`), not merely exist
- f4735ee: Make the region of per-PR test workspaces configurable in the shipped CI
  templates.
  
  `erp-kit-get-or-create-workspace` hardcoded `--region asia-northeast`, so a
  consumer whose production workspaces live in `us-west` ran its PR tests in a
  different region — diverging latency, data-residency posture, and any
  region-scoped org or folder quotas from what it actually ships. The
  interactive deploy guide already asked for the region, so the automated and
  manual paths disagreed.
  
  The action now takes a `region` input (default `asia-northeast`, so existing
  consumers need no migration), driven by a new `ERP_KIT_WORKSPACE_REGION` repo
  variable in `erp-kit-test-workspace-app.yml`. Region applies only when a
  workspace is created; `erp-kit-deploy` and `erp-kit-seed` take a workspace ID
  and are region-agnostic. Changing the variable therefore affects only
  workspaces created after the change — an existing `<prefix>-<app>-test-pr<N>`
  is reused wherever it already is.

### Patch Changes

- 1e0a661: Regenerate every `tailor.d.ts` / `generated/*.ts` file (in-package modules and the `erp` / `user-management` scaffold templates) against the current `@tailor-platform/sdk` / `@tailor-platform/sdk-plugin-seed` so the checked-in artifacts stop drifting from what `tailor generate` actually produces:
  
  - Seed `seed/data/*.schema.ts` files now `export` their `hook` and pass the TailorDB type as `createStandardSchema`'s third argument, matching `@tailor-platform/sdk-plugin-seed` 0.2.x. Without this, `tailor seed fill` failed immediately in a freshly scaffolded app with "does not export `hook`. Run `tailor generate` to regenerate the seed schema files.", and `tailor seed validate` silently skipped each type's own `validate` checks.
  - `SalesOrderFieldChange` / `SalesOrderRevision` (added by the sales order amendment feature) now have their seed schema/data files and `kysely-tailordb.ts` entries, and `JournalEntry.sourceDocumentType` includes `BANK_RECONCILIATION`.
  - Several modules' `updatedAt: Timestamp | null` is corrected to `Generated<Timestamp>` (TailorDB always populates it), and `tailor.d.ts` picks up the `AuthNamespaceNameRegistry` augmentation. Test fixtures that relied on the stale `null` type are updated to set `updatedAt` alongside `createdAt`.
  - Both scaffold templates gain a `seed:fill` script (matching what `tailor generate` now scaffolds), and their `seed` script switches to `tailor seed apply --upsert` now that every TailorDB row (`UserRole` included) carries a stable `id`.
- 594efb9: Fix `module generate code` and `app generate code` scaffolding stubs that could
  silently ship to production without ever being implemented.
  
  `renderCommandStub` and `renderQueryStub` returned `ok({})` / `{}` next to a
  `// TODO: implement` comment, so an unimplemented command or query passed its
  own generated test and lint without complaint. Both now `throw` instead, so
  the generated test fails until the stub is actually implemented.
  
  `renderCommandTestStub` embedded the placeholder as a literal permission
  string (`permissions: ["TODO:${name}"]`) rather than a comment — if that
  string reached production it would fail every permission check with no
  warning at write time. It now throws from an IIFE instead of returning a
  placeholder value.
  
  `renderResolverStub`'s transaction body and `renderStoryTestStub`'s generated
  `it()` blocks had the same silent-success shape (`return {}` / an empty test
  body with no assertions); both now throw for the same reason.
- 5614236: Fix `erp-kit module sync-check` crashing with `ENOENT` when `--path` is given
  an absolute path instead of a relative one.
  
  The model and test-case checks build fast-glob patterns from the raw `--path`
  value, so a match returned by fast-glob is itself absolute whenever `--path`
  was absolute. That match was then re-joined with `cwd` via `path.join`, which
  (unlike `path.resolve`) does not special-case an already-absolute second
  argument — the two absolute paths were concatenated into a nonexistent path
  and reading it threw `ENOENT`. Both checks now use `path.resolve`, which
  discards the base when the second argument is already absolute.

## 0.57.0

### Minor Changes

- 1120752: Refactor `amendOrderedPurchaseOrder` and the purchase-order recalculation commands into the domain / repository / command layering; revisions are now part of the PurchaseOrder aggregate.
  
  - **BREAKING** Purchase commands return ids only: `amendOrderedPurchaseOrder` returns `{ purchaseOrderId }`, the recalculation commands return `{ purchaseOrderIds }`. Read order data through the purchase queries
  - **BREAKING** The inbound-shipment `defineModule` requires `purchase.queries` with `listPurchaseOrderLinesForMatching`
  - **BREAKING** Amendment `addLines` require an ACTIVE item

### Patch Changes

- 59c54c3: Reproduce the full Tailor Platform ERP Kit License text in the package README. npmjs.com renders only the README — the bundled `LICENSE` file is never shown and the sidebar's `SEE LICENSE IN LICENSE` is plain text — so the terms were unreachable from the package page.

## 0.56.0

### Minor Changes

- b8c06c8: Relicense from MIT to the Tailor Platform ERP Kit License (proprietary). `package.json` now declares `SEE LICENSE IN LICENSE`; use of this package requires a separate written agreement with Tailor. Versions published up to 0.55.1 remain available under MIT.

## 0.55.1

### Patch Changes

- 0fdd8ca: - Update `@changesets/cli` to v3 and `changesets/action` to v2

## 0.55.0

### Minor Changes

- c70292c: - Add `shift-schedule` module — the planned side of work (シフト・勤務予定), the counterpart to the actuals `time-tracking` already owns. It defines reusable シフトパターン (`ShiftPattern`), the シフト表 period that is confirmed as a whole (`ShiftSchedule`, DRAFT → CONFIRMED), the concrete slots placed on dates within it (`Shift`), and the placements that staff them (`ShiftPlacement`). Patterns and shifts both carry ordered `segments`, so 中抜け is a genuine multi-segment shift rather than a flag, and 通し / 宿直 / 応援 / open shifts fall out of the same shape
  - Staffing is resolved only through `ShiftPlacement`: a `Shift` names nobody, so an open shift is simply one no ACTIVE placement references. Placements are superseded (swap) or cancelled (release) rather than deleted, so a confirmed シフト表 reads back as "who was planned here, and when did that change". `workforce.queries.getAssignment` is injected into the placement commands to verify the Assignment is effective on the shift's date — an invariant shiftSchedule cannot enforce with a join
  - Include `getShiftVariance` — the module-owned planned-vs-actual variance classification (遅刻 / 早退 / 超過 / 未出勤) computed from the planned segments and the `time-tracking` actuals the caller passes in; shiftSchedule never writes actuals
  - Every shift-schedule table is consumer-extensible: `defineModule` takes a field generic per table (`shiftSchedule`, `shift`, `shiftPattern`, `shiftPlacement`), and the commands writing them accept the matching custom fields — per entry for the bulk commands
  - Add the effective-dated 所定 planned-time baseline per `Assignment` (`WorkSchedule`) to the **workforce** module, with `createWorkSchedule` / `updateWorkSchedule` / `endWorkSchedule` and `getWorkSchedule` / `getWorkScheduleAsOf` / `listWorkSchedulesByAssignment` / `listActiveWorkSchedules`. It belongs with the employment facts it is one of — every Assignment has a 所定 regardless of whether shifts are planned for it, and the consumers of the baseline (overtime calculation, leave valuation) resolve it through workforce. `defineModule` takes a `workSchedule` field generic and the commands carry custom fields forward across generations
  - Keep `WorkSchedule` regime-neutral: it holds only the scheduled daily/weekly minutes. Employment-regime classification comes from workforce `WorkRegime`, while regime- and country-specific configuration — a flex core-time band, Japanese variable-working clearing periods, deemed hours — belongs in the consuming app through custom fields and app-owned models
- 8c07fcb: Expose custom fields for leave-management `AccrualPlan` definitions and persist them through create and effective-dated update commands.

  Add an application eligibility evaluator seam to the anniversary grant batch. Consumers can evaluate jurisdiction- or company-specific conditions stored in custom fields, reject a candidate, report it as not evaluable for retry, or override the generic proposed grant amount without copying the batch. An overridden amount must be a non-negative half-day increment within the plan's `annualCapDays`; an out-of-range amount counts the candidate as failed instead of being clamped.

  The evaluator is handed the effective plan row, so type its custom fields with `TailorDBSelectable` from `@tailor-platform/sdk/kysely` rather than the create-input shape — a row carries every column, and an unset optional field reads as `null`, not `undefined`.

  Behaviour change: `createAccrualPlan` / `updateAccrualPlan` now reject a `grantCondition.type` outside `NONE` / `MIN_WORKED_DAYS` with `INVALID_GRANT_CONDITION`, instead of storing an unrecognized runtime value. Jurisdiction-specific gates are expressed as custom fields read by the eligibility evaluator.

- 9b9438b: - **BREAKING** `createContext` no longer invents an actor for an anonymous caller. It returns a `CallerContext`, whose `actorId` is `null` when there is no caller, and it decides nothing about whether the call is allowed — that belongs to the resolver's `permission` in `tailor.config.ts` and to each command's own gate. Resolvers call it exactly as before:

  ```ts
  body: async (context) => {
    const ctx = createContext(context);
    // ... pass `ctx` to erp-kit commands
  },
  ```

  - Previously an anonymous caller got a context whose `actorId` was the nil UUID `00000000-0000-0000-0000-000000000000`. That is the platform's internal anonymous principal id, which both the function runtime and the SDK deliberately normalize to `null` before it reaches user code; erp-kit was re-materializing a value that had already been erased twice.
  - The `?? context.caller` fallback is gone and `invoker` is required on the argument type. It dated from the release that introduced `invoker`; under SDK v2 the resolver wrapper always injects `invoker` and leaves it `null` only for an anonymous call — the same case in which `caller` is `null` too.

  - **BREAKING** `defineCommand` rejects a caller with no actor before the implementation runs, returning the new `UnauthenticatedError` (`code: "UNAUTHENTICATED"`, exported from `@tailor-platform/erp-kit/core`). A command attributes its work to an actor — audit columns take the id — so there is nothing to run as without one.

    A resolver switching on a command's error code needs a `case` for it, beside the one it already has for `INSUFFICIENT_PERMISSION`:

    ```ts
    case "UNAUTHENTICATED":
      throw new DomainError("Authentication is required");
    ```

    A command implementation still receives `CommandContext` with `actorId: string`, so the places that read `ctx.actorId` are unchanged.

  - **BREAKING** A query implementation receives `CallerContext` instead of `QueryContext`. Queries carry no permission gate of their own, so nothing narrows the actor for them, and a query that scopes its result to the caller has to say what an absent actor means. erp-kit's notification queries return `UnauthenticatedError`, since every result they produce is the caller's own. The permission-gated form of `defineQuery` is unaffected — its gate rejects an absent actor, and its implementation still receives `QueryContext`.

- f7c8279: - **BREAKING** Value goods receipts at the purchase-order price effective at posting time: `postInboundShipment` now derives each line's unit cost from the linked `PurchaseOrderLine.unitPrice` (converted to the primary unit via `unitConversionRate`), instead of trusting a caller-supplied snapshot taken at creation
  - `unitCost` is removed from `CreateInboundShipmentInput` / `UpdateInboundShipmentInput`, along with the `INBOUND_SHIPMENT_INVALID_UNIT_COST` error; `InboundShipmentLine.unitCost` stays null while DRAFT and is written at posting
  - `recalculatePurchaseOrderReceiptStatus` additionally returns the updated `purchaseOrderLines`
- dcc83d7: Post invoice-versus-order price differences through a dedicated invoice price variance clearing account instead of the accrual account, so the accrual carries only order-priced uninvoiced receipts:

  - **BREAKING** `ValuationPolicy` takes a new required `invoicePriceVarianceAccountId`, and `invoiceCostVarianceAccountId` is renamed to `consumedPriceVarianceAccountId` (it expenses variances on already-consumed AVERAGE stock, not invoice differences)
  - **BREAKING** `postAcquisitionCostAdjustment` requires `varianceKind`, naming the clearing account the declared amounts wash against: `INVOICE_PRICE` (billed price differed from the order price) or `ORDER_PRICE` (the order price changed for quantity the accrual still carries). `AccountPayableDocument` postings declare `INVOICE_PRICE`; order reprices and cancellation compensations declare `ORDER_PRICE`. `AcquisitionCostAdjustment` rows carry a required `kind` (the variance kinds plus `REDISTRIBUTION` for the zero-amount rows receipt postings anchor), and allocations record the `varianceKind` they distribute
  - **BREAKING** A receipt-required purchase-order AP line now derives two distributions: an `ACCRUAL` row at quantity × order price and a signed `INVOICE_PRICE_VARIANCE` row (new `distributionType` value) for the remainder, so the AP journal relieves the accrual at the price the receipt accrued. `registerAccountPayableDocument` re-derives the split against the price it matches

- 9b9438b: - **BREAKING** Require `@tailor-platform/sdk` 2.1.0 or newer (`peerDependencies` and the `erp-kit measure` compat range both move to `^2.1.0`), and close the scaffold's resolver namespace to anonymous callers with the namespace-level `defaultPermission` that 2.1.0 adds:

  ```ts
  resolver: {
    "main-resolver": {
      files: [`./src/resolver/**/*.ts`],
      defaultPermission: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }],
    },
  },
  ```

  - A resolver that declares no `permission` of its own previously had no guard generated at all, and the gateway issues an anonymous JWT rather than rejecting an unauthenticated request, so anonymous callers reached `body` and were stopped only by the permission check inside each command. The namespace default now rejects them before `body` runs.
  - A resolver that is public by design opts out with `permission: "allowAnonymous"`; a resolver's own `permission` replaces the namespace default rather than merging with it.
  - Existing apps are unaffected until they add `defaultPermission` to their own `tailor.config.ts`. Scaffolds generated from this version have it from the start.

- f0486da: - **BREAKING** Require `@tailor-platform/sdk` 2.2.0 or later; the `peerDependencies` entry is now `^2.2.0`. Move the pin in every app and module package.json to `2.2.0` — `erp-kit measure` reports anything below it as a compat-range violation.

  - **BREAKING** `FieldsToInsertable` and `FieldsToSelectable` are removed from `@tailor-platform/erp-kit/core`. Use `TailorDBInsertable` / `TailorDBSelectable` from `@tailor-platform/sdk/kysely` instead — same position, same meaning:

    ```diff
    -import { type FieldsToInsertable } from "@tailor-platform/erp-kit/core";
    +import type { TailorDBInsertable } from "@tailor-platform/sdk/kysely";

     return {
       commands: {
    -    createX: createX<FieldsToInsertable<F>>(),
    +    createX: createX<TailorDBInsertable<F>>(),
       },
     };
    ```

    erp-kit derived both shapes from `output<F>` itself, which the SDK now does — including inside nested objects and for `date` / `datetime` columns, which `output<F>` alone cannot express. Keeping a second copy of those rules meant they could drift from the ones `kyselyTypePlugin` writes into the generated table types.

    The derived shapes change in four ways:

    - An extension field defined with `.default(...)` is now optional on create input. It used to be required even though the platform fills it in, so a caller had to supply a value it could not meaningfully choose.
    - An extension field defined with `.serial(...)` can no longer be given a value on create. It was previously accepted and written straight to the column, which leaves the sequence un-advanced and lets a later auto-assigned value collide with it. Remove such values from create-command calls; the platform assigns them.
    - On the read side a row still carries every column, and an unset optional field still reads as `null` rather than being omittable. What changes is the column type of a few field kinds: a `date` / `datetime` extension field reads back as `Date` instead of `string | Date`, a `db.object(...)` one resolves through `ObjectColumnType`, and an array of either through `ArrayColumnType`. Narrow any `typeof value === "string"` branch that only existed to satisfy the old union.
    - The type parameter accepts a table (`typeof myTable`) as well as a field map. Every `defineModule` field generic passes a field map, so no call site changes.

- 880a960: - Add the `account-receivable` module with customer invoice models and `createAccountReceivableDocument`, `registerAccountReceivableDocument`, `postAccountReceivableDocument`, and `cancelAccountReceivableDocument` commands, including sales-order billing synchronization and financial-accounting journal integration
  - Add `correctAccountReceivableDocument` to create and post immutable AR correction documents with line and distribution provenance, due schedules, accounting entries, and sales-order billing feedback
  - Add incoming-payment management with editable draft payments, invoice and credit-memo settlements, cumulative over-settlement protection, cancellation, posting, and linked reversal payments
  - Add `INCOMING_PAYMENT` as a financial-accounting journal source type so posted and reversed customer payments retain source-document traceability
  - Add the sales-owned `listSalesOrderLinesForMatching` query used by account-receivable to validate sales-order sources and snapshot current unit prices without reading sales tables directly

## 0.54.0

### Minor Changes

- bc46403: **BREAKING** Require `@tailor-platform/sdk` v2. `peerDependency` is now `^2.0.0`; sdk v1 is no longer supported.

  To upgrade an existing app:

  1. Move `@tailor-platform/sdk` to `2.0.0` and add `@tailor-platform/sdk-plugin-seed@0.1.0` if you seed data. The SDK ships codemods for most of the renames below — see its v2 migration guide before doing anything by hand.
  2. The CLI binary is now `tailor`, not `tailor-sdk`, and writes to `.tailor/` instead of `.tailor-sdk/`. Update package.json scripts, CI workflows, and `.gitignore`.
  3. Rename `db.type(...)` to `db.table(...)` in every TailorDB definition, and the auth-attribute module augmentation interface from `AttributeMap` to `Attributes`.
  4. Resolver and executor bodies receive `caller` / `invoker` (both `TailorPrincipal | null`) instead of `user`; guard against a `null` caller where you read the caller's identity. The executor option `authInvoker` is now `invoker`, and the `AuthInvoker` type is gone.
  5. The ambient `tailor.*` runtime globals are removed — import what you need from `@tailor-platform/sdk/runtime` (for example `idp` in place of `new tailor.idp.Client(...)`).
  6. Re-run `tailor generate`. `updatedAt` becomes `Generated<Timestamp>` (non-null, set on create), so drop `updatedAt: null` from insert values and set it alongside `createdAt` in fixtures. Mark type-only imports from the generated Kysely file with `import type`; the v2 type stripper does no cross-file inference, so a value import of a type-only export fails at runtime.
  7. If you generated seed data, `seedPlugin` no longer emits `seed/exec.mjs`. Replace `node <dist>/exec.mjs` with `tailor seed apply` and its `validate` form with `tailor seed validate`, then delete the stale file.

## 0.53.0

### Minor Changes

- a663a0c: - **BREAKING** Derive the accrual distribution of a receipt-required purchase-order AP line instead of accepting its account from the caller: `createAccountPayableDocument`, `updateAccountPayableDocument` and `correctAccountPayableDocument` resolve it from the accrual account of the valuation policy governing the ordered item, at the line's net amount, so the invoice relieves the account the goods receipt credited. Callers now supply distributions on such a line only for the remaining tax, and none at all when the line carries no tax; amount-only lines and lines needing no physical receipt are unchanged.
  - A receipt-required line that no policy governs fails with `ACCOUNT_PAYABLE_AP_ACCRUAL_ACCOUNT_UNRESOLVED`
  - New required field `AccountPayableDistributionLine.distributionType`: `MANUAL` (caller-supplied) or `ACCRUAL` (derived). Editing or removing an `ACCRUAL` row fails with `ACCOUNT_PAYABLE_AP_DERIVED_DISTRIBUTION_IMMUTABLE`; it is recomputed when its line changes
  - `defineAccountPayableModule` now requires `inventory.queries.resolveItemValuationPolicies`
  - Add inventory query `resolveItemValuationPolicies`, resolving an item to its own valuation policy assignment or the company default, so an invoice can still be captured before its goods arrive
  - Expose `itemId` on `listPurchaseOrderLinesForMatching`
- e53d077: Promote several private helpers that public commands force callers to replicate to public API, so consumers stop reimplementing (and drifting from) the module's own semantics:

  - **time-tracking `dayBreaker`** — `formReportedBlocks` requires a caller-computed `workDate`; expose `dayBreaker = { toWorkDate, addDays }` (the reference day-breaking) from the module and the `/module` registry.
  - **time-tracking `jpTimeEntryCodeCategories`** — `calculateTimeBlocks`/`recalculateRange` resolve each span to a `TimeEntryCode` by matching the JP strategy's category keys against `TimeEntryCode.category` and `WorkRule.premiumRatePercent[].category`. Those keys lived only in a private switch, so a mismatch failed derivation at runtime. Expose the exact keys as `jpTimeEntryCodeCategories`, and fix the `TimeEntryCode` doc that listed keys (`OVERTIME_WITHIN_STATUTORY`, `BREAK`, `LEAVE`) the strategy never emits.
  - **leave-management `computeGrantExpirationDate`** — a `LeaveGrant`'s `expirationDate` is `grantedDate + expirationMonths`; the anniversary batch computed it with private month arithmetic while `grantLeave` only validates a caller-supplied value. Expose `computeGrantExpirationDate` as the shared source of truth.
  - **workforce `APPOINTMENT_ACTIONS` / `AppointmentTypeAction`** — the closed action set required by `create`/`updateAppointmentType` was not re-exported; expose it so consumers reference the canonical values instead of hardcoding string literals.

- a5e7338: - **BREAKING** Reprice receipt costs when a purchase order price changes: amending an `ORDERED` line's unit price declares the price delta over the received-but-unbilled quantity to inventory
  - `postAcquisitionCostAdjustment` takes a generic source reference: `accountPayableDocumentId` becomes `sourceType` / `sourceId`, line `accountPayableDocumentLineId` becomes `sourceLineId`, and `AcquisitionCostAdjustment.sourceType` gains `PURCHASE_ORDER_REVISION`
  - `definePurchaseModule` now requires `inventory.commands.postAcquisitionCostAdjustment`
  - **BREAKING** Measure account-payable price variances against the order price the line was matched at, captured on the new system-written `AccountPayableDocumentLine.matchedPurchaseOrderUnitPrice`. A purchase-order sourced non-PRICE line without it fails to post
  - Declare the matched-versus-current price difference when a REGISTERED document is cancelled
  - Fix an empty journal entry being attempted when a redistribution's deltas net to zero in every account role
- 7039bb5: - Add independent order, fulfillment, and billing statuses to sales orders, with commands that recalculate progress from line quantities
  - Synchronize posted outbound shipments with sales-order fulfillment progress using transaction-safe quantity deltas
  - Improve quantity precision by using decimal values for sales-order quantities, prices, fulfillment, and billing across the module and ERP scaffold
  - Refactor the sales-order lifecycle to separate document control from execution progress and remove the credit-hold commands

## 0.52.0

### Minor Changes

- d1e5ebc: Re-export the `workforce`, `time-tracking`, `leave-management`, `inbound-shipment`, and `outbound-shipment` module permissions from `@tailor-platform/erp-kit/app`, so resolvers and config files can reach every module's `permissions` via the lightweight `/app` path instead of pulling in the whole module graph through `/module`. A test guard now fails if a module ships a `permissions.generated.ts` that `app.ts` doesn't re-export, so this surface can't silently drift as modules are added.
- 40796d9: Migrate shared logging from `@tailor-platform/function-logger` to `tailor.logger`

  `defineCommand`, `defineQuery`, the notification executors, and the time-tracking
  derivation now log through `tailor.logger` (via `@tailor-platform/sdk/runtime`)
  instead of the console-based `function-logger`. Per the platform's New Logger
  API announcement, `console` output is becoming stdout-only and will no longer be
  exported through TelemetryRouter — so without this change, command execution
  logs from erp-kit apps would stop reaching telemetry backends.

  Context that was previously interpolated into the message string is now attached
  as structured attributes (`command`, `query`, `permission`, `durationMs`,
  `input`, `eventId`, `reason`, `error`, `errorName`, `phase`), so backends can
  facet and filter on it directly.

  **Breaking:** the minimum supported `@tailor-platform/sdk` is now `1.81.0`
  (raised from `1.55.0`), the version that introduced `tailor.logger` and
  `mockLogger`. `@tailor-platform/function-logger` is no longer a dependency.

  Two notes for consumers:

  - Log message text changed. Anything grepping stdout for `[command:foo]` or
    `[notification-redrain-events] ...` should key off the new stable messages and
    attributes instead.
  - Command/query inputs are serialized with `JSON.stringify` into an `input`
    attribute, which means they now reach your telemetry backend rather than only
    stdout. Review this against your telemetry retention and PII policy.

## 0.51.0

### Minor Changes

- 31fbab7: - Add credit memo settlement support to outgoing payments so posted invoice and credit memo open items can be paid together using their signed net amount
  - Add system-generated signed amounts to account payable due schedule lines while keeping settlement command inputs as positive magnitudes
  - Generate direction-aware payable journal lines for invoice and credit memo settlements, with exact reversing entries when an outgoing payment is reversed
  - Require a complete due schedule when correction invoices and credit memos are created directly in `POSTED`

## 0.50.0

### Minor Changes

- 2bd7dbf: **BREAKING** Add invoice-driven acquisition cost adjustment: purchase receipts costed at the provisional order price are trued up to the invoiced price when account-payable posts.

  - Add `inventory.commands.postAcquisitionCostAdjustment` and the `AcquisitionCostAdjustment` / `AcquisitionCostAdjustmentAllocation` models. Account-payable declares a signed price variance per order line; inventory redistributes the cumulative amount over the receipt cost layers by the item's costing method.
  - `ValuationPolicy` requires a new `invoiceCostVarianceAccountId` account; `createValuationPolicy` / `updateValuationPolicy` now take it.
  - `defineAccountPayableModule` now requires `inventory.commands.postAcquisitionCostAdjustment`; `postAccountPayableDocument` calls it in the same transaction.
  - `InventoryLedger` persists the order reference (`orderDocumentType` / `orderDocumentId` / `orderDocumentLineId`) so receipt layers resolve.

- dc38e63: - Add `workforce` module — an effective-dated HR foundation (Worker, JobProfile, Position, WorkerEmployment, Assignment, AppointmentHistory) that extends the bundled `organization` and `user-management` modules. Employment type, work regime, and appointment type are company-scoped catalog entities (`EmploymentType`, `WorkRegime`, `AppointmentType`) referenced by id rather than fixed enums, so any org can add its own without a code change; each `AppointmentType` maps to a fixed assignment `action` that `recordAppointment` dispatches on
  - Add `time-tracking` module — the raw-punch → reported → calculated → timecard pipeline with work rules, time-entry codes, eligibility rules, and calendar-scoped holidays (`HolidayCalendar` + `CompanyHoliday`, so multi-entity/multi-country deployments classify holidays correctly). Span `category` and TimeClockEvent `source` are open string keys, not fixed enums
  - Add `leave-management` module — leave catalog (`LeaveType`), effective-dated accrual plans, a grant ledger with FIFO consumption, and the request/approval lifecycle; the accrual batch dispatches on a configurable `accrualMethod` and stamps a configurable `grantType`. Includes `getAnnualLeaveRegister` — a ledger-derived annual paid-leave compliance register with the Article 39 5-day mandatory-acquisition obligation (deadline, days taken, satisfied/at-risk)
  - Add a pluggable, per-jurisdiction `TimeClassificationStrategy` seam to `time-tracking` (default: Japan / Labor Standards Act) — apps inject their own strategy (e.g. US FLSA weekly overtime, AU penalty rates) via `defineModule({ timeCalculation: { strategy } })`; span categories are open string keys, and period logic is the strategy's responsibility. A strategy can declare a `recalculationWindow` so a single-day reported-block change re-derives its whole aggregation period (e.g. a week). Includes an authoring guide
  - Integrate the bundled `approval` module (ADR-003 wrapper, direct mode) into the leave request/approval and timecard submit/approve lifecycles, with self-approval and non-assignee guards
  - Add point-in-time reconstruction: `listAssignmentsAsOf` returns the org's worker-to-position bindings in force on any past or future date, and `applyDueAppointments` carries out future-dated CHANGE/END appointments once their effective date arrives
  - Ship JP catalog and default-organization seed data discovered by `erp-kit app generate seed` (employment types, work regimes, appointment types, leave types, a default Company, and the LEAVE time-entry codes the leave defaults reference)
  - Export `TimeClassificationStrategy` / `ClassifiedSpan` / `ClassifyInput` types and the bundled `jpTimeClassificationStrategy` from the module

## 0.49.0

### Minor Changes

- 6451558: Add step-mode send-back to the approval module: `sendBackApprovalStep` accepts an optional `targetApprovalStepId` that rewinds the request to a chosen earlier step so a prior approver can reconsider, without routing through the requester.

  - Requester mode (no target) is unchanged: the request transitions to `REVISION_REQUESTED` and step/assignee rows are reset only on `resubmitApprovalRequest`
  - Step mode (target supplied) keeps the request `PENDING`, resets the target-through-current steps to `PENDING` (their `APPROVED` assignees reset to `PENDING`, `DELEGATED` rows left as-is), and re-activates the target step to `IN_PROGRESS`, reusing the frozen assignee set without re-expanding roles
  - New nullable `ApprovalDecision.sentBackToStepId` column records the rewind target on step-mode `SEND_BACK` rows (null in requester mode and on all other decision types)
  - New `APPROVAL_INVALID_SEND_BACK_TARGET` error (`InvalidSendBackTargetError`) rejects a target that is not in the same request or not strictly earlier than the actor's current step

- f0dcb36: - Add outgoing payment management to account payable, including draft creation, updates, cancellation, posting, and reversal
  - Add auditable AP settlement allocation with deterministic locking, over-settlement protection, balanced journal posting, and reversal history
  - Improve account payable correction posting so exposure, settlement eligibility, and purchase-order billing status remain transactionally consistent
- 41c4e91: Rework `measure thin`'s frontend `appShellRatio` to be catalog-based and
  lie-free: classification is proven against the installed
  `@tailor-platform/app-shell` instead of hand-written tag lists, and tags that
  app-shell cannot replace no longer count against the score.

  - **Catalog matching**: the CLI resolves `@tailor-platform/app-shell` from the
    measured repo's `node_modules` and reads its `.d.ts`. A raw tag is
    _replaceable_ only when the catalog proves an equivalent — a same-name export
    (`<select>` → `Select`) or a `ComponentProps<"tag">` declaration
    (`<tr>` → `Table.*`). No guessing: with no resolvable catalog, raw tags stay
    neutral in `unclassifiedTags` and `appShellCatalog.source` is `"unresolved"`.
    Matching uses the installed version only — no network fetch.
  - **Denominator redesign**: `appShellRatio = app-shell / (app-shell +
replaceable + other-library)`. Structural tags (`<div>`/`<span>`/headings),
    candidate tags (no app-shell equivalent), and SVG drawing primitives
    (`svg`/`path`/`tspan`/…) are neutral — pages are no longer penalised for
    elements app-shell cannot replace. Candidates are aggregated as
    `appShellGaps` (`{ tag, count }`), a feature-request list for app-shell.
  - **Local components**: imports via relative paths, tsconfig `paths` aliases
    (`@/...`), and workspace packages are neutral instead of other-library
    (previously they inflated the denominator and falsely triggered the
    `otherLibraryTags` penalty). Neutral ≠ approved: the new
    `localComponentRisks` scans component definition files (app `components/**` +
    `packages/*/src`) and surfaces likely app-shell re-implementations as
    evidence (name shadows an app-shell export / leans on replaceable raw tags /
    structural-heavy). The CLI asserts no violations; judgement stays with the
    score skill, as with the new `suspectedHandRolled` page signal.
  - **Misc**: TS generic type arguments (`useState<string>()`) are no longer
    miscounted as JSX tags; `customComponents` counts `components/**/*.tsx`
    recursively (tests excluded); `rawHtmlHeavyPages` triggers on ≥5 replaceable
    tags and page ratio < 0.5.
  - **Output JSON**: drops `rawHtmlTags` / `replaceableRawHtmlTags` /
    `nonReplaceableRawHtmlTags`; adds `replaceableTags` / `candidateTags` /
    `structuralTags` / `unclassifiedTags` / `appShellCatalog` / `appShellGaps` /
    `suspectedHandRolled` / `localComponentRisks`. Top-level keys `resolvers` /
    `commands` are renamed to the singular `resolver` / `command` to match
    `frontend`. `dim-thinness.md` updated.

  Related: https://github.com/tailor-professional-service/knowledge/discussions/263

- 97adb6f: **Breaking.** Purchase orders now track their received quantity themselves, mirroring how billed quantity is pushed from account-payable. Posting an inbound shipment pushes the received quantities onto the purchase-order lines and updates the order's receipt status; purchase-order close/cancel/amend and invoice matching read this stored value instead of querying inbound-shipment.

  - Removed `purchase.commands.postInboundShipment` — call `inboundShipment.commands.postInboundShipment` directly.
  - The module dependency is reversed: `definePurchaseModule` no longer takes `inboundShipment`, and `defineInboundShipmentModule` now requires `purchase.commands.recalculatePurchaseOrderReceiptStatus`.
  - `defineAccountPayableModule` no longer takes `inboundShipment`.
  - Amendment guards now check posted receipts only; draft shipments are no longer inspected. A shipment referencing a removed purchase-order line fails at posting instead.

- 0966d4b: **BREAKING** Reject cross-company transfer orders. `createTransferOrder` and `updateTransferOrder` now return the new `INVENTORY_CROSS_COMPANY_TRANSFER_NOT_SUPPORTED` error (`CrossCompanyTransferNotSupportedError`) when the source and destination sites belong to different companies. Cross-company stock transfers are out of scope for the inventory module: moving stock at book value between companies without recording an intercompany sale does not satisfy accounting requirements, so such movements should be modeled as regular sales/purchase transactions instead.
- 2bcef4e: **BREAKING** Remove the `audit` module entirely. The `defineAuditModule` and `auditPermissions` exports from `@tailor-platform/erp-kit/module`, and the `auditPermissions` export from `@tailor-platform/erp-kit/app`, are gone.

### Patch Changes

- b9a3061: - Fix `erp-kit internal measure versions` (and `erp-kit verify`) reporting
  permanent `violations` in repos that use pnpm's `catalog:` protocol. The
  `compat-range` and `erp-kit-version` checks now resolve `catalog:` /
  `catalog:<name>` specifiers to their catalog spec via `pnpm config list
--json` before comparing, so a catalog that pins the exact installed version
  is correctly evaluated as `ok`.
  - Preserve the exact-pin-vs-range enforcement for catalog dependencies: a
    catalog entry declared as a range (e.g. `^1.55.0`) still warns to pin it,
    matching how directly-declared ranges are treated.
  - Only shell out to pnpm when a checked package actually declares a `catalog:`
    specifier; non-catalog repos are unaffected. A `catalog:` reference that
    can't be resolved (pnpm unavailable or the entry is missing) is reported as
    a warning instead of crashing.
- 0093cee: Fix `FieldsToInsertable` to recognize `.serial()` and `.hooks({ create })` fields as create-input-optional, matching their actual platform-populated behavior. Previously only nullable-output fields were excluded from the required set, so every serial/hook-populated custom field on a `defineCommand` create input needed an `...({} as { field: T })` cast workaround at every call site. The scaffold templates' `createPurchaseOrder`, `duplicatePurchaseOrder`, `createInboundShipment`, `createOutboundShipment`, `createRemainingInboundShipment`, and `createSalesOrder` resolvers no longer need this workaround.

## 0.48.0

### Minor Changes

- f4fb1d2: Remove the accounting-event module and its public exports. Account-payable posting now creates and posts a balanced source-referenced journal entry directly through financial-accounting in the same transaction, matching inventory's direct posting model. Consumers must replace the `accountingEvent` dependency passed to `defineAccountPayableModule` with `financialAccounting.commands.createJournalEntry`, `financialAccounting.commands.postJournalEntry`, and `financialAccounting.queries.getPeriodByDate`.
- d79296a: Add FIFO and moving-average actual costing to the inventory module, on a universal costed-quantity substrate.

  - Every costed movement now maintains the same quantity substrate regardless of costing method: receipts create a `CostLayer`, issues consume layers first-in-first-out and record a `CostLayerConsumption` per consumed layer; costed issues that the company's layered stock cannot cover fail with `INSUFFICIENT_STOCK`
  - `ValuationPolicy.costingMethod` becomes `STANDARD | FIFO | AVERAGE` (breaking: the previous `STANDARD_COST` value is renamed to `STANDARD`); FIFO and AVERAGE value movements at actual cost with no PPV, and their receipts require a unit cost (`UNIT_COST_REQUIRED` otherwise)
  - New `FifoCost` model keeps FIFO's price state per layer; issues are valued at the consumed layers' current cost
  - New `AverageCost` model keeps the moving average per item and company; receipts fold in at actual cost, issues are valued at the current average
  - `StandardCost.effectiveFrom` is replaced by a per-item `version` (breaking): movements are always valued at the item's current standard regardless of their effective date
  - `publishStandardCost` revalues each company's costed on-hand (the sum of its unconsumed cost layers) instead of physical StockLevel quantities, and skips companies whose assigned policy does not use standard cost; its `getSite` dependency and `SITE_NOT_FOUND` error are removed

## 0.47.0

### Minor Changes

- 84fe14d: **Breaking:** rebuild inventory costing around standard costing. FIFO and AVCO are removed, and costed movements post journal entries directly to `financial-accounting`.

  - Standard costs are per item and date-effective: the new `StandardCost` model holds append-only generations, published via the new `publishStandardCost` command. `ValuationPolicy.standardCostRate`, `CostLayer`, `listCostLayers`, and all FIFO/AVCO paths are removed.
  - `ValuationPolicy` now belongs to a company and carries that company's six posting accounts and a default marker (`defaultCompanyId`, unique so each company has at most one default). To give an item its own accounts, assign it a dedicated policy.
  - `ItemValuation` is now a plain item-to-policy assignment, unique per item and company, created via the new `setItemValuationPolicy` command. Its running totals are removed. An item without an assignment falls back to the movement company's default policy; without one, the movement fails.
  - `InventoryLedger.dcIndicator` (DR/CR) is renamed to `direction` (IN/OUT), and movement lines now require an `action` (`QUANTITY_CHANGE`, `TRANSFER`, or `STOCK_TYPE_CHANGE`) in place of the `affectsValuation` flag. Receipts post inventory at the standard and book the difference from actual cost as purchase price variance, issues relieve COGS, and adjustments post gains and losses; transfers and stock-type changes produce no costing.
  - `publishStandardCost` revalues each company's on-hand stock with a `STANDARD_COST_REVISION` entry; already-issued COGS is never restated.
  - `defineInventoryModule` now requires `financialAccounting` and `coaManagement` dependencies, and its `organization` dependency additionally requires the `company` type and the `getCompany` query.

- 2aed9b0: **Breaking:** every `.relation()` call in erp-kit's own db type definitions now sets an explicit `toward.as`, so the forward GraphQL field name is never left to the SDK's type-name default. An omitted `as` makes two relations toward the same type silently collide (the second field's forward name quietly overwrites the first) — this already happened four times and was patched ad hoc; this change closes the gap for good by fixing every remaining relation (self-relations excluded, since the SDK already derives their forward name from the field name) and adding an `erp-kit-internal/require-relation-as` oxlint rule (enforced on `src/modules/**/db/*.ts`) that fails `pnpm lint` if a future relation omits `as`.

  Most of the 244 relations end up with the same forward name they already had (the field name minus `Id` happened to match the type-name default), so there's no schema change for those. The following forward relation field names do change and require updating any existing GraphQL queries/fragments that reference the old name:

  - `account-payable/accountPayableDocument` (`payableControlAccountId`): `account` → `payableControlAccount`
  - `account-payable/invoiceToleranceConfig` (`supplierId`): `businessPartner` → `supplier`
  - `approval/approvalDecision` (`decidedByUserId`): `user` → `decidedByUser`
  - `approval/approvalRequest` (`requesterId`): `user` → `requester`
  - `approval/approvalRequest` (`sourcePolicyId`): `approvalPolicy` → `sourcePolicy`
  - `business-partner/businessPartner` (`preferredCurrencyId`): `currency` → `preferredCurrency`
  - `business-partner/contactPerson` (`partnerId`): `businessPartner` → `partner`
  - `business-partner/partnerAddress` (`partnerId`): `businessPartner` → `partner`
  - `business-partner/partnerBankAccount` (`partnerId`): `businessPartner` → `partner`
  - `business-partner/partnerIdentification` (`partnerId`): `businessPartner` → `partner`
  - `business-partner/partnerRole` (`partnerId`): `businessPartner` → `partner`
  - `inventory/transferOrder` (`sourceSiteId`): `site` → `sourceSite`
  - `manufacturing/billOfMaterial` (`parentItemId`): `item` → `parentItem`
  - `manufacturing/costVarianceLine` (`costSummaryId`): `manufacturingCostSummary` → `costSummary`
  - `manufacturing/manufacturingCostLine` (`costSummaryId`): `manufacturingCostSummary` → `costSummary`
  - `manufacturing/manufacturingCostSettlementRecord` (`costSummaryId`): `manufacturingCostSummary` → `costSummary`
  - `manufacturing/productionOrder` (`orderedItemId`): `item` → `orderedItem`
  - `manufacturing/routing` (`parentItemId`): `item` → `parentItem`
  - `notification/eventCategoryBinding` (`categoryId`): `notificationCategory` → `category`
  - `notification/notification` (`channelId`): `notificationChannel` → `channel`
  - `notification/notificationPreference` (`categoryId`): `notificationCategory` → `category`
  - `notification/notificationPreference` (`channelId`): `notificationChannel` → `channel`
  - `notification/notificationTemplate` (`channelId`): `notificationChannel` → `channel`
  - `organization/company` (`baseCurrencyId`): `currency` → `baseCurrency`
  - `pipeline-management/pipeline` (`createdByUserId`): `user` → `createdByUser`
  - `pipeline-management/pipelineItem` (`assigneeId`): `user` → `assignee`
  - `pipeline-management/pipelineItem` (`stageId`): `pipelineStage` → `stage`
  - `pipeline-management/pipelineItemChange` (`changedByUserId`): `user` → `changedByUser`
  - `pipeline-management/pipelineItemChange` (`itemId`): `pipelineItem` → `item`
  - `pipeline-management/pipelineItemComment` (`authorUserId`): `user` → `authorUser`
  - `pipeline-management/pipelineItemComment` (`itemId`): `pipelineItem` → `item`
  - `pipeline-management/pipelineItemLabel` (`itemId`): `pipelineItem` → `item`
  - `pipeline-management/pipelineItemLabel` (`labelId`): `pipelineLabel` → `label`
  - `pipeline-management/pipelineStageTransition` (`fromStageId`): `pipelineStage` → `fromStage`
  - `pipeline-management/pipelineStageTransition` (`itemId`): `pipelineItem` → `item`
  - `pipeline-management/pipelineStageTransition` (`movedByUserId`): `user` → `movedByUser`
  - `primitives/unit` (`categoryId`): `uoMCategory` → `category`
  - `product-management/productAttributeAssignment` (`attributeId`): `productAttribute` → `attribute`
  - `product-management/productAttributeAssignment` (`valueId`): `productAttributeValue` → `value`
  - `product-management/productAttributeValue` (`attributeId`): `productAttribute` → `attribute`
  - `product-management/productCategoryAssignment` (`categoryId`): `productCategory` → `category`
  - `purchase/purchaseOrder` (`receivingSiteId`): `site` → `receivingSite`
  - `purchase/purchaseOrder` (`supplierId`): `businessPartner` → `supplier`
  - `purchase/purchaseOrderFieldChange` (`revisionId`): `purchaseOrderRevision` → `revision`
  - `purchase/purchaseOrderLine` (`receivingSiteId`): `site` → `receivingSite`
  - `purchase/purchaseOrderRevision` (`amendedByUserId`): `user` → `amendedByUser`
  - `purchase/purchasePriceList` (`supplierId`): `businessPartner` → `supplier`
  - `purchase/purchasePriceRule` (`priceListId`): `purchasePriceList` → `priceList`
  - `purchase/purchaseRequisition` (`requesterId`): `user` → `requester`
  - `purchase/purchaseRequisitionLine` (`suggestedSupplierId`): `businessPartner` → `suggestedSupplier`
  - `sales/channelOrder` (`customerId`): `businessPartner` → `customer`
  - `sales/salesCreditNote` (`customerId`): `businessPartner` → `customer`
  - `sales/salesInvoice` (`customerId`): `businessPartner` → `customer`
  - `sales/salesInvoice` (`paymentTermId`): `salesPaymentTerm` → `paymentTerm`
  - `sales/salesOrder` (`customerId`): `businessPartner` → `customer`
  - `sales/salesOrder` (`paymentTermId`): `salesPaymentTerm` → `paymentTerm`
  - `sales/salesOrderLine` (`matchedPriceRuleId`): `salesPriceRule` → `matchedPriceRule`
  - `sales/salesPriceList` (`customerId`): `businessPartner` → `customer`
  - `sales/salesPriceRule` (`customerId`): `businessPartner` → `customer`
  - `sales/shipment` (`customerId`): `businessPartner` → `customer`
  - `sales/shipment` (`shipFromSiteId`): `site` → `shipFromSite`

- 2e9896a: Drop the structure and versions dimensions from the erp-kit-score skill.
  Both are deterministic pass/fail checks now enforced by the `erp-kit verify`
  CI gate (shipped in the `erp-kit-check` workflow), so the score skill keeps
  only the judgement-based dimensions: template sync and implementation
  thinness.
- 2e9896a: Rework the structure check's canonical module-directory rules (affects
  `erp-kit verify` and `internal measure structure`):

  - Required dirs are now `db/`, `command/`, `docs/`. `query/` (command-only
    modules) and `lib/` (helper-free modules) are optional.
  - A module child directory outside the canonical allow-list is flagged as a
    new `unexpected-dir` violation.
  - `lib/` accepts camelCase helper modules alongside `types.ts` and generated
    files, and generated/test file names are exempt from naming rules in every
    directory.
  - The module-root filename allow-list now includes `oxlint.config` (shipped
    per module by the scaffold since #777), so it is no longer flagged.

  Consumers whose modules lack `docs/` or carry non-canonical directories will
  start seeing verify failures — restore the doc-flow output or move the layer
  into an allowed directory.

### Patch Changes

- 75b0824: Fix cross-module type aliases so custom fields survive module boundaries without tripping TypeScript's generic-function assignability check (TS2719/TS2322).

  Modules referenced each other's shape via `ReturnType<typeof defineXxxModule>` without supplying field-generic type arguments. TypeScript resolves an uninstantiated generic function's `ReturnType` against its generic constraint rather than its `EmptyFields` default, which silently widens field-keyed types derived from it (e.g. the hooks `validate()` callback's `issues()` parameter). Passing a table built with real custom fields into a dependent module's params could then fail with a confusing "two different types with this name exist, but they are unrelated" error, even though the values were structurally compatible.

  All modules that reference another module's shape now import an explicit `EmptyFields`-instantiated type alias (e.g. `ItemManagementModule`, `OrganizationModule`) exported by that module, following the pattern already used by `pipeline-management`, instead of re-deriving `ReturnType<typeof defineXxxModule>` locally.

## 0.46.0

### Minor Changes

- e3ae658: **Breaking:** E2E is one journey per business flow — a single top-level `test()` walking the flow's `## Flow Diagram` main path — instead of per-scenario tests.

  `erp-kit app sync-check` no longer matches story `## Scenario Patterns` against E2E test titles. It checks file correspondence only: each business flow has a `frontend/e2e/tests/<flow>.spec.ts`, and each spec file matches a business flow. Spec content is not inspected, so existing suites keep passing; migrate incrementally.

  Business rules stay in integration tests; UI behavior moves to component tests. The requirements skill now requires the diagram's main path to read as one thread from the initiating actor to the final outcome, and impl review verifies journeys against the diagram and diagram claims against the implementation.

- 66698a8: **Breaking:** remove lot/batch and serial-number tracking from the `inventory` module for now.

  - Removed db types `Lot`, `LotStockLevel`, `SerialNumber` and their commands and queries.
  - Dropped the optional `lotId`/`serialNumberId` fields from `InventoryExecutionLine`, `InventoryLedger`, `InventorySupplyPlan`, `StockAdjustmentLine`, and `TransferOrderLine`, and from the goods-movement, stock-adjustment, transfer-order, and supply-plan command inputs.
  - `manufacturing`'s `completeWorkOrder` drops the lot/serial receipt inputs and handoff fields.

- 66698a8: **Breaking.** Split `InventoryExecution` out of the inventory module into two new document modules, `inbound-shipment` and `outbound-shipment`, and replace execution-based posting with a single posting API. Inventory now owns only the ledger, balances, and valuation.

  - **New modules** wired via `defineInboundShipmentModule` / `defineOutboundShipmentModule` (from `@tailor-platform/erp-kit/module`).

  - **`InventoryExecution` / `InventoryExecutionLine` removed**, replaced by `InboundShipment`(+Line) / `OutboundShipment`(+Line). Source references move to the **line** level (PO line for inbound, SO line for outbound), so one shipment can span multiple source documents. Ad-hoc movements go through stock adjustments.

  - **Goods-receipt/issue commands and queries are renamed** to their inbound-shipment / outbound-shipment equivalents (`createGoodsReceipt` → `createInboundShipment`, `listGoodsIssues` → `listOutboundShipments`, etc.); the `inventoryOperation` permission group is removed.

  - **One posting command, `postInventoryLedger`**, replaces goods-receipt/issue posting. It writes ledger rows, updates balances, applies valuation, and consumes supply plans (IN) / reservations (OUT); the new modules and inventory's own transfer/adjustment commands all post through it.

  - **`InventoryLedger`** replaces the `inventoryExecutionId` FK with a generic source reference (`sourceType` / `sourceId` / `sourceLineId`) plus a new `effectiveDate`.

  - **Dependency wiring**: purchase and account-payable now take an `inbound-shipment` dependency in place of the removed inventory goods-receipt queries; financial-accounting's `JOURNAL_ENTRY_SOURCE_DOCUMENT_TYPES` renames `INVENTORY_EXECUTION` to `INVENTORY_LEDGER` and drops the stale `PURCHASE_BILL`.

  - **Posting behavior**: every AVAILABLE OUT posting now runs the reservation-aware ATP check with no opt-out, so a stock adjustment that would draw down an open reservation is rejected (resolve the reservation first).

### Patch Changes

- 60733b6: Harden error handling in the scaffold app resolver templates and add a shared `DomainError` base class.

  Mutation resolvers now run their command inside the transaction and throw domain errors from within it, so kysely rolls back partial work instead of committing it — a domain error thrown after `execute()` returned previously left the partial transaction committed. Unexpected errors (connection drops, constraint violations, and the permission error's `Actor <id> lacks required permission: <scope>` detail) are masked behind a generic, action-specific message with the original error preserved on `cause`, while intentional user-facing messages survive the mask.

  `DomainError` is now exported from `@tailor-platform/erp-kit/core` and `@tailor-platform/erp-kit/app`, and `createDomainError` extends it, so every module's generated errors are `DomainError` instances. The scaffold resolvers throw and rethrow this shared class to mark user-facing failures (`err instanceof DomainError`) instead of each declaring a local `ClientError`. The `erp-kit-app-5-impl-backend` resolver-patterns skill doc is updated to match.

## 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
