Add out-of-stock filter to inventory view#181
Conversation
Adds a stock_status query param to GET /api/inventory (out_of_stock = quantity_on_hand == 0), a matching filter dropdown in the Inventory view, and one demo item set to zero quantity so the filter has data to show.
| // Local filter for stock status - scoped to this component only. | ||
| // The global useFilters `selectedStatus` is for Order Status; inventory | ||
| // intentionally does not use it (see comment below in loadInventory). | ||
| const stockStatusFilter = ref('all') |
There was a problem hiding this comment.
[CONFIRMED] Resetting filters won't clear this filter.
stockStatusFilter is a component-local ref, not part of the shared useFilters() composable. useFilters.js's resetFilters() and hasActiveFilters only cover selectedPeriod/selectedLocation/selectedCategory/selectedStatus, so clicking FilterBar's reset button leaves this stock-status filter untouched and doesn't reflect it as an 'active filter'. A user who filters to Out of Stock and hits 'reset' will still see a filtered list.
Consider adding this filter to useFilters.js (even if page-scoped in usage) so it participates in reset/hasActiveFilters, or wire an explicit reset here.
| warehouse: Optional[str] = None, | ||
| category: Optional[str] = None | ||
| category: Optional[str] = None, | ||
| stock_status: Optional[str] = None |
There was a problem hiding this comment.
[CONFIRMED] New stock_status param has no test coverage.
server/CLAUDE.md's 'Adding New Endpoints' process (step 6) says 'Write tests in tests/backend/', and 'What to Test' explicitly lists 'Filters work correctly.' tests/backend/test_inventory.py currently has zero references to stock_status or out_of_stock — this new filter path is untested.
| if not stock_status or stock_status == 'all': | ||
| return items | ||
|
|
||
| if stock_status == 'out_of_stock': |
There was a problem hiding this comment.
[CONFIRMED] Unrecognized stock_status values are silently ignored.
Any value other than 'all'/falsy/'out_of_stock' falls through to return items (full unfiltered list, HTTP 200) instead of erroring. stock_status isn't constrained to a Literal/Enum in the endpoint signature, so a typo (e.g. out-of-stock) silently returns everything with no indication the filter wasn't applied.
|
|
||
| // Watch for filter changes and reload data | ||
| watch([selectedLocation, selectedCategory], () => { | ||
| watch([selectedLocation, selectedCategory, stockStatusFilter], () => { |
There was a problem hiding this comment.
[CONFIRMED] Unnecessary network round-trip for a client-computable filter.
stock_status only excludes items where quantity_on_hand === 0 — a field already present on every item in items.value after the initial load. Bundling stockStatusFilter into this watch triggers a full GET /api/inventory refetch (loading-state flip) on every toggle, even though the existing filteredItems computed already does equivalent client-side filtering (search) + sorting over the same in-memory data at zero network cost. Consider filtering client-side in filteredItems instead of round-tripping to the server.
| align-items: center; | ||
| } | ||
|
|
||
| .stock-status-select { |
There was a problem hiding this comment.
[PLAUSIBLE] Near-duplicate of FilterBar's .filter-select.
This new .stock-status-select rule shares the same structural intent (border color, focus border-color/box-shadow) as .filter-select in FilterBar.vue, though several concrete values (padding, border-radius, font-size, background) differ. Worth checking whether reusing/extending the existing class would keep this dropdown visually consistent with the other filter selects on the same page.
| class="stock-status-select" | ||
| > | ||
| <option value="all">{{ t('inventory.stockStatusAll') }}</option> | ||
| <option value="out_of_stock">{{ t('inventory.stockStatusOutOfStock') }}</option> |
There was a problem hiding this comment.
[CONFIRMED] Out-of-stock rows are visually identical to ordinary low-stock rows.
getStockStatusKey/getStockStatusClass (further down in this file) only classify into lowStock/adequate/inStock — there's no outOfStock tier, and the status.* translation catalog (en.js/ja.js) has no outOfStock key either. So filtering to this new 'Out of Stock' option (e.g. SRV-302, quantity_on_hand=0) shows the same red 'Low Stock' badge as any other low-stock item (e.g. quantity_on_hand=5, reorder_point=200), with nothing distinguishing a true stockout from a merely-low item.
Worth adding a 4th tier (e.g. 'outOfStock' when quantity_on_hand === 0) to that classifier so the badge and this filter share one source of truth.
| """Get all inventory items with optional filtering""" | ||
| return apply_filters(inventory_items, warehouse, category) | ||
| filtered = apply_filters(inventory_items, warehouse, category) | ||
| return filter_by_stock_status(filtered, stock_status) |
There was a problem hiding this comment.
[CONFIRMED, currently latent] get_dashboard_summary (elsewhere in this file) doesn't apply this new stock_status filter.
That endpoint filters inventory_items via apply_filters(inventory_items, warehouse, category) but never calls filter_by_stock_status, unlike get_inventory here. Root CLAUDE.md documents 'GET /api/dashboard/summary - All filters', so this is a documented-contract gap — currently unreachable since the Dashboard UI doesn't expose a stock_status filter yet, but will silently diverge from Inventory's filtering if/when it does.
Summary
stock_statusquery param toGET /api/inventory(out_of_stock=quantity_on_hand == 0)Test plan
uv run pytest tests/backend/ -v, 40/40 passed)stock_status=out_of_stockreturns only SRV-302;stock_status=all/omitted returns all 32 items🤖 Generated with Claude Code