Skip to content

Add out-of-stock filter to inventory view#181

Open
ryotaro09 wants to merge 1 commit into
beck-source:mainfrom
ryotaro09:feature/out-of-stock-filter
Open

Add out-of-stock filter to inventory view#181
ryotaro09 wants to merge 1 commit into
beck-source:mainfrom
ryotaro09:feature/out-of-stock-filter

Conversation

@ryotaro09

Copy link
Copy Markdown

Summary

  • Adds a stock_status query param to GET /api/inventory (out_of_stock = quantity_on_hand == 0)
  • Adds a Stock Status filter dropdown to the Inventory view, wired to the new param
  • Sets one demo item (SRV-302) to zero quantity so the filter has data to demonstrate

Test plan

  • Existing backend test suite passes (uv run pytest tests/backend/ -v, 40/40 passed)
  • Verified via curl: stock_status=out_of_stock returns only SRV-302; stock_status=all/omitted returns all 32 items
  • Manual UI check: open Inventory view, select "Out of Stock" in the new dropdown, confirm only SRV-302 shows

🤖 Generated with Claude Code

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')

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread server/main.py
warehouse: Optional[str] = None,
category: Optional[str] = None
category: Optional[str] = None,
stock_status: Optional[str] = None

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread server/main.py
if not stock_status or stock_status == 'all':
return items

if stock_status == 'out_of_stock':

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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], () => {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread server/main.py
"""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)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant