Current section
Files
Jump to
Current section
Files
phoenix_kit_warehouse
CHANGELOG.md
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
## 0.3.2 - 2026-08-16
### Fixed
- **Permission-matrix and admin-tab labels stay translated regardless of
unrelated wording changes elsewhere** (#18). Pins all eight admin-tab
labels and the `permission_metadata/0` label/description as gettext
msgids via a new `translatable_labels/0`, so they no longer depend on
`Web.WarehouseHeader`/`Web.TurnoverReportLive` happening to use the same
English strings. Before this, rewording either file would have silently
reverted eight admin tabs to English with no compile error and no failing
test — locked in with new test coverage.
### Changed
- Dependency updates (`phoenix_kit` 2.8.0, `phoenix_kit_catalogue` 0.16.1,
`ecto` 3.14.2, `grpc`/`grpc_core` 1.0.4, `beamlab_countries` 1.2.0,
`etcher` 0.12.1, `quic` 1.8.1).
## 0.3.1 - 2026-08-11
### Fixed
- **`priv/` is now shipped in the Hex package** (#17). It was missing from the
`files:` list, so `priv/gettext` never reached anyone installing from Hex and
every string rendered as its English msgid no matter what locale the host was
in. The translations were only ever working for people running from a
checkout of this repo.
### Changed
- Dependency updates (`phoenix_kit` 2.2.0, `phoenix_kit_catalogue` 0.14.0,
`phoenix_kit_comments` 0.4.0, `phoenix_kit_locations` 0.4.1, `phoenix`
1.8.10, `hackney` 4.7.3).
## 0.3.0 - 2026-08-10
### Changed
- **⚠️ Requires `phoenix_kit ~> 2.0`.** The core pin moved to `~> 2.0`, so this
release no longer resolves against core 1.7.
Core 2.0.0 squashes the migration chain into a single `V135` baseline and makes
V135 the chain's floor: `mix ecto.migrate` now *refuses* on a database below it
rather than migrating. Check `mix phoenix_kit.status` **before** upgrading. A
host below V135 must install `phoenix_kit 1.7.236` — the migration bridge, the
last release carrying the full pre-squash chain — migrate until the reported
version is at least V135, and only then move to 2.0.
This package does not call migration internals, so the change is the pin
itself.
- Sibling pins raised in step, each to that package's first release requiring
core 2.0: `phoenix_kit_billing` → `~> 0.7`, `phoenix_kit_catalogue` → `~> 0.13`,
`phoenix_kit_comments` → `~> 0.3`, `phoenix_kit_locations` → `~> 0.4`.
`phoenix_kit_comments` 0.3.0 is a **security release** (stored XSS in comment
bodies); see its CHANGELOG.
### Changed
- **Stops calling the deprecated `Scope.admin?/1` (PR #13).** Core renamed it to
`can_access_admin_area?/1` in 1.7.214 and kept the old name as a
`@deprecated` delegate, so this is a pure rename with no behaviour change — it
silences a deprecation warning host apps were eating on every compile with no
way to fix it themselves.
### Added
- **Gettext keys in `permission_metadata` (PR #14)** so the permissions-matrix
rows render translated labels instead of falling back to a raw key, plus a
test pinning the metadata shape — a missing key degrades silently into an
untranslated label rather than an error.
## Unreleased
### Changed
- **Documentation now matches the actual dependency contract.** The
`PhoenixKitWarehouse` moduledoc claimed `PhoenixKitComments` "stays optional
(guarded via `Code.ensure_loaded?/1`)", and `AGENTS.md` said the same. Neither
was true: `phoenix_kit_comments` is not declared `optional:` in `mix.exs`, and
six form LiveViews `use PhoenixKitComments.Embed`, which is macro expansion at
compile time and cannot be guarded. All four sibling packages are now
described as hard dependencies, with the distinction a reader actually needs
spelled out — a hard *package* dependency is not a required *module*, and
`required_modules/0` lists only `"catalogue"` and `"locations"`. The moduledoc
also no longer implies billing is enablement-checked; it isn't, its component
is imported unconditionally.
- `PhoenixKitWarehouse.Comments`' moduledoc now states what "unavailable" covers
(installed-but-disabled) and what it does not (version skew — `available?/0`
returns `true` against comments 0.2.6/0.2.7, where `subscribe/2` and the batch
`count_comments/2` do not yet exist; the `>= 0.2.8` floor is what closes that,
not any guard in the module). The `Code.ensure_loaded?/1` check and the
`@compile {:no_warn_undefined, ...}` attribute are labelled as the always-true
residue they are.
## 0.2.7 - 2026-08-06
### Fixed
- **Dependency floor raised to `phoenix_kit ~> 1.7.231`** (PR #12, from
`~> 1.7.214`) — seven index LiveViews `use PhoenixKitWeb.Live.UrlState`, which
first shipped in core 1.7.231. The old requirement declared a contract that
permitted a core without that module, so a consumer whose resolution landed
below 1.7.231 would fail to compile this package. `mix.lock` already carried
1.7.231, so this corrects the published contract, not the current build.
- **Dependency floor raised to `phoenix_kit_comments ~> 0.2 and >= 0.2.8`**
(post-merge review of PR #12, from `~> 0.2`) — the same class of defect PR #12
fixed for core, left in place three lines below it. Six form LiveViews `use
PhoenixKitComments.Embed`, which first shipped in comments 0.2.6; a `use` is
macro expansion at compile time and cannot be guarded. `PhoenixKitWarehouse.
Comments` additionally calls `subscribe/2`, `unsubscribe/2` and the list form
of `count_comments/2`, all three of which arrived in 0.2.8 — and its
`available?/0` guard only covers `PhoenixKitComments` being absent entirely,
not an older release that is present but missing those functions. The ceiling
stays `< 1.0.0` rather than becoming `~> 0.2.8`'s `< 0.3.0`, so a future
comments 0.3.0 still resolves.
- **The `—` placeholder never appeared in the read-only price and sum cells of
the inventory count sheet.** They rendered `format_input_decimal(value) ||
"—"`, but `format_input_decimal/1` returns `""` — never `nil` — for a missing
value, and `""` is truthy, so the cell went blank instead. Now goes through an
explicit `blank_to_dash/1`.
### Changed
- **`mix precommit` passes for the first time.** It had been failing on `main`
for a long time, on findings spread across the whole codebase rather than any
recent change. Cleared all 291 `mix credo --strict` issues (248
`Design.AliasUsage`, 37 `Readability.AliasOrder`, 2 `WithSingleClause`, plus
`PreferImplicitTry`, `StringSigils`, `CyclomaticComplexity` and `Nesting`) and
all four actionable dialyzer warnings.
- `SupplierOrders.import_from_internal_orders/3` is split into
`load_posted_internal_orders/1`, `load_items_by_uuid/1`,
`collect_import_lines/2`, `import_line/6`, `sole_supplier?/2` and
`merge_import_lines/2` — behaviour preserved exactly, cyclomatic complexity 16
→ under the limit and nesting depth 5 → 3.
- `StorageFolders` drops the `parent_uuid` parameter that both call sites always
passed as `nil`; the folder lookup is now explicitly root-scoped. Dead
`pick_name/1` fallback clauses removed from `Inventories` and
`Web.Components.WarehouseBrowser` — `safe_get_translation/2` already rescues
to `%{}`, which is the real defensive layer.
- New `.dialyzer_ignore.exs` documents the six `Ecto.Multi` opaqueness warnings
(one per context's `lock_status_step/3`), which are an artefact of Ecto's
`@opaque t()` and have no code-level fix. Wired up with
`list_unused_filters: true` so a stale filter is reported.
## 0.2.6 - 2026-08-05
### Added
- **Warehouse list search and sort now live in the URL** (PR #11) — all seven
index screens (`stock`, `inventories`, `internal_orders`, `supplier_orders`,
`goods_receipts`, `goods_issues`, `transfers`) adopt core's
`PhoenixKitWeb.Live.UrlState` in `:patch` mode, exposing `?q=`, `?sort=` and
`?dir=`. A filtered list is now a real URL: shareable, reload-proof, and Back
returns to the previous query instead of leaving the page. List loading moves
from `handle_params/3` to the `handle_url_state/2` callback, so one code path
serves the first render, a shared link and the Back button. The debounced
search box patches with `replace: true`, leaving one history entry rather
than one per pause in typing. `stock_view` and `warehouse_scope` stay
per-user `ViewConfig` preferences and are deliberately **not** in the URL.
- `StockLive` caches its ledger snapshot per mount behind an explicit
`:stock_items_loaded?` flag, so search and sort re-slice the loaded list
instead of re-running `Deficits.available_by_item/0`, the min-stock map and
the Catalogue load on every keystroke.
### Fixed
- **Hiding the active sort column could leave the list stale** — when a column
save hid the column being sorted by, `__view_config_changed__/1` re-picked
`List.first(selected_columns)`, which is the first *visible* column and need
not be **sortable** (`sub_order`, `supplier_order`, `location`,
`source_location`, `destination_location` never are, and column order is
user-controlled). `push_url_state/3` sanitizes anything outside the declared
`in:` whitelist back to the default, so the re-pick silently collapsed to
`"number"` / `"item"` — and when that was already the active sort, the URL
state did not move at all, `handle_url_state/2` never re-ran, and the column
or filter change that triggered it was never applied to the table. All seven
screens now pick the first visible column that is actually sortable and
refresh in place when the re-pick resolves to the current column.
- `mix.lock` carried eight unused entries (`igniter`, `sourceror`, `spitfire`,
`rewrite`, `owl`, `text_diff`, `ex_ast`, `glob_ex`) left over from the
dependency bump in 0.2.5, which `mix deps.unlock --check-unused` rejects.
### Testing
- New DB-less unit test pins each index LiveView's `?sort=` whitelist to the
`sortable?: true` columns of its `ColumnConfig` — the two are the same fact
written twice, and drift is silent: a sortable column left out of the
whitelist cannot be sorted by at all. Also asserts each declared default sort
column is itself sortable and that `?dir=` stays `cast: :atom` restricted to
`[:asc, :desc]`.
## 0.2.5 - 2026-07-27
### Changed
- **Stop calling the deprecated `PhoenixKit.Users.Auth.Scope.admin?/1`.** All 7
call sites — one per form/stock LiveView (`stock_live`,
`goods_receipt_form_live`, `goods_issue_form_live`,
`internal_order_form_live`, `transfer_form_live`,
`supplier_order_form_live`, `inventory_form_live`) — now call
`Scope.can_access_admin_area?/1`, the name core renamed it to in phoenix_kit
1.7.214. The old name is a pure `@deprecated` delegate, so **no behavior
change** — this only silences the deprecation warning host apps were eating
on every compile of this library, with no way to fix it themselves. The
`test/support/live_case.ex` doc comment was updated to match.
- **Dependency floor raised to `phoenix_kit ~> 1.7.214`** (from `~> 1.7.190`) —
`can_access_admin_area?/1` does not exist below it, so an older core would be
an `UndefinedFunctionError` at call time rather than a warning. This was not
hypothetical: the lockfile was resolving 1.7.205.
- Dependency lockfile bumps: `phoenix_kit` 1.7.205 → 1.7.216, `phoenix_kit_ai`
0.16.0 → 0.17.1, `phoenix_kit_catalogue` 0.12.1 → 0.12.3,
`phoenix_kit_comments` 0.2.14 → 0.2.15, `phoenix_live_view` 1.2.7 → 1.2.8,
`etcher` 0.8.1 → 0.9.0, `ex_ast` 0.12.10 → 0.13.1, `bandit` 1.12.0 → 1.12.4,
`grpc`/`grpc_core` 1.0.2 → 1.0.3, `igniter` 0.8.2 → 0.8.3, `leaf` 0.3.0 →
0.3.2, `plug_crypto` 2.1.1 → 2.2.0, `glob_ex` 0.1.11 → 0.1.12.
## 0.2.4 - 2026-07-20
### Fixed
- **Row-link overlay escaped its row on Safari/iPad** (PR #10) — Safari
doesn't honor `position: relative` on `<tr>`, so the whole-row-clickable
`::after` overlay on every index table (goods issues, goods receipts,
internal orders, supplier orders, stocktakes, transfers) escaped to the
`<table>`'s containing block; every row's overlay covered the entire
table and the last row won hit-testing, so any row tap on iOS/iPadOS
navigated to the last item. Fixed by adding Tailwind's `transform-gpu`
utility alongside the existing `relative` class on each row — WebKit does
honor a `transform` as a containing block on table rows. Reviewed: no
affected file was missed, and the fix doesn't interact badly with the
per-row `⋮` action menu (which portals to `<body>` on open) or any
pinned/sticky table variant.
### Changed (review of PR #9)
- **`permission_metadata/0`'s attempted gettext declaration was reverted**
— PR #9 added `gettext_backend`/`gettext_domain` to the warehouse's
`permission_metadata/0`, intending to translate its label in the admin
permissions matrix the same way `admin_tabs/0` already translates sidebar
labels. Against the dependency actually pinned in `mix.lock`
(`phoenix_kit` 1.7.205, published 2026-07-19, before core's
`localized_module_label/1` work landed), no code reads those two keys —
the permissions matrix still renders through the plain, untranslated
`Permissions.module_label/1` — so the addition was inert in production
and only introduced a new `mix dialyzer` `callback_type_mismatch`
against the published `permission_meta()` behaviour type. Reverted
`permission_metadata/0` to its original 4-key shape. Re-adding the two
keys is the correct follow-up once `phoenix_kit` publishes a Hex release
containing the localized-permission-labels feature and this repo's pin
is bumped to it — `admin_tabs/0`'s per-tab gettext declarations are
unaffected (that feature is real and already published). Full findings:
`dev_docs/pull_requests/2026/9-permission-label-i18n/CLAUDE_REVIEW.md`
and `dev_docs/pull_requests/2026/10-safari-row-link/CLAUDE_REVIEW.md`.
### Maintenance
- Removed a stale, unused `mix.lock` entry (`beamlab_ex_aws_sqs`, hex
package v4.0.0) left over from the prior `phoenix_kit` 1.7.199 → 1.7.205
bump, which renamed the dependency's key to `ex_aws_sqs` (same hex
package, v5.0.0) and made the old lock entry unreferenced. Caught by
`mix deps.unlock --check-unused`.
## 0.2.3 - 2026-07-16
### Changed
- **`resolve_suppliers/1` gained a guarded junction-primary fallback clause**
(PR #6), sitting between the `primary_supplier_uuid` scalar check and
manufacturer resolution: a `Code.ensure_loaded?`/`function_exported?`-guarded
call to `PhoenixKitCatalogue.Catalogue.Suppliers.primary_for_item/1` (the
catalogue V151 junction `is_primary` row), falling back to manufacturer
resolution (with a warning) when the primary row's supplier isn't locally
resolvable. The guard is correctly written — no crash risk against a
dependency that doesn't export the function.
### Fixed (review of PR #6)
- **Neither `resolve_suppliers/1` non-manufacturer clause is actually reachable
against the currently pinned `phoenix_kit_catalogue ~> 0.10` dependency** —
corrected during review, not a new bug from this PR. `phoenix_kit_catalogue`
0.10.0 (still Hex's latest as of this release) never shipped
`primary_supplier_uuid` *or* `Suppliers.primary_for_item/1`: both were added
and (in the scalar's case) removed entirely in catalogue commits *after*
0.10.0 was tagged, and remain unpublished. This also means the 0.2.2
changelog entry below and PR #5's review overstated the scalar's status —
it was never live in any published catalogue release, not just removed by a
later one. `resolve_suppliers/1` today is functionally equivalent to
manufacturer-only resolution for every item. Rewrote the misleading code
comments to state this plainly (with the mechanism activating automatically,
no code change needed, once catalogue publishes the junction release and
this repo's `mix.lock` picks it up), and replaced two pre-existing tests
that asserted unreachable scalar-based behavior (and would have failed
against the real dependency) with tests that lock in the real,
currently-shipping fallback behavior. Full findings:
`dev_docs/pull_requests/2026/6-junction-primary-fallback/CLAUDE_REVIEW.md`.
### Notes
- **Dependency lockfile advance** (no `mix.exs` constraint change):
`hackney` 4.5.2 → 4.6.0.
## 0.2.2 - 2026-07-14
### Added
- **Goods receipt lines can now set stock `unit_value` on posting** (PR #5).
When a receipt line carries a `"unit_value"` field, posting writes it to
`phoenix_kit_warehouse_stock.unit_value` for that item/location via
`StockLedger.receive_quantity/3`'s existing `:unit_value` option (last
posted receipt wins). Absent/`nil` leaves the existing value untouched.
### Fixed
- **`resolve_suppliers/1` stopped honoring `item.primary_supplier_uuid`** (PR
#5 fix, applied during review). PR #5 rewrote supplier resolution to prefer
a `PhoenixKitCatalogue.Catalogue.Suppliers.primary_for_item/1` junction
lookup, on the premise that core's V149 migration removed the
`primary_supplier_uuid` scalar. It didn't: V149 is a purely additive
per-supplier-pricing junction table, and the scalar remains the item's
documented default supplier. Against the pinned `phoenix_kit_catalogue ~>
0.10` dependency, `primary_for_item/1` doesn't exist, so the new code
silently fell through to manufacturer-only resolution — any item that
relied on `primary_supplier_uuid` (generic/unbranded materials with no
manufacturer, or breaking a tie between a manufacturer's multiple linked
suppliers) stopped auto-assigning a supplier during
`generate_from_internal_order/2`. Reverted `resolve_suppliers/1` to check
`primary_supplier_uuid` first, manufacturer as fallback — the original
pre-PR behavior. Full findings:
`dev_docs/pull_requests/2026/5-parties-resolver-unit-value/CLAUDE_REVIEW.md`.
## 0.2.1 - 2026-07-14
### Fixed
- **Stale `V143` migration references renumbered to `V144`** (PR #4). When core's
consolidation PR merged, the migration creating `phoenix_kit_warehouse_transfers`
and `phoenix_kit_warehouse_min_stock` was renumbered upstream — core's own V143
slot went to the new-login-security-alerts migration instead. Every `V143`
reference in this package (`AGENTS.md`, `min_stock_settings.ex` moduledoc,
`mix.exs` comments) now points to `V144`, and the `phoenix_kit` dependency pin is
tightened to `~> 1.7.190` (1.7.189 tops out at V142 and does not carry the
tables).
- **This CHANGELOG's own 0.2.0 entry still referenced `V143`/`>= 1.7.189` after
PR #4 landed** — corrected post-release as part of reviewing that PR (see
`dev_docs/pull_requests/2026/4-v144-renumber-and-pin/CLAUDE_REVIEW.md`).
## 0.2.0 - 2026-07-13
Wave 1: multi-warehouse, transfers, deficit control, turnover.
### Added
- **Multi-warehouse stock scope**: per-location stock balances
(`StockLedger.stock_map_for_location/1`, `get_quantity/2`), a warehouse
selector on goods receipt/issue/inventory drafts, and a "per warehouse /
all warehouses" scope toggle on the Stock page (persisted per user).
- **Transfers**: new document type with a draft → in_transit → done
lifecycle, atomic ship/receive stock postings, and cancellation (draft:
void; in_transit: reverses the ship posting back to the source
warehouse).
- **Deficit control**: per-item minimum stock, an available-quantity
calculation (on-hand minus posted reserves — draft documents don't
reserve), zero-stock deficits surfaced even with no `Stock` row, and a
"create supplier order" action from a deficit row.
- **Turnover report**: aggregated in/out/balance per item over a date
range, optionally scoped to one warehouse; `balance` is documented as
current on-hand, not a historical balance as of the end date.
- **Related documents**: a shared upstream/downstream linked-documents
list component on Internal Order / Supplier Order cards.
- `Transfer`/`MinStock` tables now ship in core `phoenix_kit`'s migration
V144 instead of this package's own migrator (which is retired); this
package defines schemas only and owns no DDL.
### Fixed
- `StockLedger.stock_map/0`, `Deficits`, `Inventories`, `GoodsReceipts`,
`GoodsIssues` previous-quantity audit snapshots, and the `SourceKinds`
link picker in the goods receipt/issue forms were all made
warehouse-aware or corrected for multi-location stock (see
[`dev_docs/pull_requests/2026/3-wave-1-multi-warehouse/CLAUDE_REVIEW.md`](dev_docs/pull_requests/2026/3-wave-1-multi-warehouse/CLAUDE_REVIEW.md)
for the full list, plus this release's own post-merge fixes below).
- Post-merge review fixes: `SupplierOrders.generate_from_internal_order/2`
and `import_from_internal_orders/3` now read on-hand stock from the
internal order's own warehouse instead of an arbitrary one;
`TransferFormLive`'s quantity input is now clamped non-negative
(a negative value could otherwise inflate source-warehouse stock and
permanently stall the transfer); `TurnoverReportLive` no longer queries
the database in `mount/3` (was doubling the report query on every page
load); `Transfer`/`MinStock` schemas now use `PhoenixKit.SchemaPrefix`,
matching every other table-backed schema in this package.
### Requires
- `phoenix_kit >= 1.7.190` — `Transfer`/`MinStock` tables ship in core
migration V144 (renumbered from a provisional V143 before publish;
1.7.189 tops out at V142, so it does *not* satisfy this pin — corrected
post-release, see PR #4).
## 0.1.0 - 2026-07-10
Initial release.
### Added
- **Inventory & stock**: `Stock` balances per item/location, `StockLedger`
context (upsert/receive/issue with an atomic, non-negative-guarded
conditional decrement), and stocktakes (`InventoryDocument`) that count
and post adjustments.
- **Goods receipts & goods issues**: transactional posting (`Ecto.Multi` +
`FOR UPDATE` compare-and-swap on status) that additively increases
(receipts) or conditionally decreases (issues) warehouse stock, with a
per-line `previous_quantity` audit trail.
- **Supplier orders & internal orders**: draft → posted document lifecycle,
many-to-many traceability via a generic `source_refs` / `SourceKinds`
registry (host apps can register their own linkable "order" kinds),
committed-quantity netting to avoid re-ordering already-requested stock.
- **Admin UI**: index + form LiveViews for every document type, a shared
`ColumnConfig` engine (sortable/filterable/configurable columns, per-user
view persistence), file attachments via `StorageFolders`, and comments
via `PhoenixKitComments` (optional — degrades gracefully when absent).
- Activity logging, i18n (en/et/ru), and the `PhoenixKit.Module` admin-tab
integration (`module_key: "warehouse"`).
- Project scaffold: `mix.exs` (with the `pk_dep/3` helper, `quality` /
`quality.ci` / `precommit` aliases, and dialyzer config), `config/`,
`.formatter.exs`, `.credo.exs`, `.gitignore`, `LICENSE`, and `AGENTS.md`.
### Requires
- `phoenix_kit >= 1.7.182` — the warehouse DB tables ship in core migration
V140, first published in that core release.
### Known issues
See [`dev_docs/pull_requests/2026/1-warehouse-module/CLAUDE_REVIEW.md`](dev_docs/pull_requests/2026/1-warehouse-module/CLAUDE_REVIEW.md)
for the full review. Notably: stocktake posting can clobber stock movements
that happened between opening the count and posting it (absolute-SET
semantics), and "Generate supplier orders" doesn't record `source_refs`, so
re-importing the same internal order into a second supplier order can
double the ordered quantity. Both need a maintainer decision on intended
semantics before being fixed.