-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Add out-of-stock filter to inventory view #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,17 @@ | |
| </svg> | ||
| </button> | ||
| </div> | ||
| <div class="stock-status-filter"> | ||
| <label for="stock-status-select" class="visually-hidden">{{ t('inventory.stockStatusFilter') }}</label> | ||
| <select | ||
| id="stock-status-select" | ||
| v-model="stockStatusFilter" | ||
| class="stock-status-select" | ||
| > | ||
| <option value="all">{{ t('inventory.stockStatusAll') }}</option> | ||
| <option value="out_of_stock">{{ t('inventory.stockStatusOutOfStock') }}</option> | ||
| </select> | ||
| </div> | ||
| </div> | ||
| <div class="table-container"> | ||
| <table> | ||
|
|
@@ -106,6 +117,10 @@ export default { | |
| const error = ref(null) | ||
| const items = ref([]) | ||
| const searchQuery = ref('') | ||
| // 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') | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CONFIRMED] Resetting filters won't clear this filter.
Consider adding this filter to |
||
|
|
||
| // Modal state | ||
| const showItemModal = ref(false) | ||
|
|
@@ -156,7 +171,8 @@ export default { | |
| // Inventory doesn't support month/status filters, only warehouse and category | ||
| items.value = await api.getInventory({ | ||
| warehouse: filters.warehouse, | ||
| category: filters.category | ||
| category: filters.category, | ||
| stock_status: stockStatusFilter.value | ||
| }) | ||
| } catch (err) { | ||
| error.value = 'Failed to load inventory: ' + err.message | ||
|
|
@@ -166,7 +182,7 @@ export default { | |
| } | ||
|
|
||
| // Watch for filter changes and reload data | ||
| watch([selectedLocation, selectedCategory], () => { | ||
| watch([selectedLocation, selectedCategory, stockStatusFilter], () => { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CONFIRMED] Unnecessary network round-trip for a client-computable filter.
|
||
| loadInventory() | ||
| }) | ||
|
|
||
|
|
@@ -209,6 +225,7 @@ export default { | |
| error, | ||
| items, | ||
| searchQuery, | ||
| stockStatusFilter, | ||
| filteredItems, | ||
| getStockStatus, | ||
| getStockStatusClass, | ||
|
|
@@ -317,6 +334,41 @@ export default { | |
| height: 18px; | ||
| } | ||
|
|
||
| .visually-hidden { | ||
| position: absolute; | ||
| width: 1px; | ||
| height: 1px; | ||
| padding: 0; | ||
| margin: -1px; | ||
| overflow: hidden; | ||
| clip: rect(0, 0, 0, 0); | ||
| white-space: nowrap; | ||
| border: 0; | ||
| } | ||
|
|
||
| .stock-status-filter { | ||
| display: flex; | ||
| align-items: center; | ||
| } | ||
|
|
||
| .stock-status-select { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [PLAUSIBLE] Near-duplicate of FilterBar's This new |
||
| padding: 0.5rem 0.75rem; | ||
| border: 1px solid #cbd5e1; | ||
| border-radius: 8px; | ||
| font-size: 0.875rem; | ||
| color: #0f172a; | ||
| background: #f8fafc; | ||
| transition: all 0.2s; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .stock-status-select:focus { | ||
| outline: none; | ||
| border-color: #3b82f6; | ||
| background: white; | ||
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); | ||
| } | ||
|
|
||
| .loading, | ||
| .error { | ||
| padding: 2rem; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,16 @@ def apply_filters(items: list, warehouse: Optional[str] = None, category: Option | |
|
|
||
| return filtered | ||
|
|
||
| def filter_by_stock_status(items: list, stock_status: Optional[str] = None) -> list: | ||
| """Filter inventory items by stock status derived from quantity_on_hand""" | ||
| if not stock_status or stock_status == 'all': | ||
| return items | ||
|
|
||
| if stock_status == 'out_of_stock': | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CONFIRMED] Unrecognized Any value other than |
||
| return [item for item in items if item.get('quantity_on_hand', 0) == 0] | ||
|
|
||
| return items | ||
|
|
||
| # CORS middleware | ||
| app.add_middleware( | ||
| CORSMiddleware, | ||
|
|
@@ -128,10 +138,12 @@ def root(): | |
| @app.get("/api/inventory", response_model=List[InventoryItem]) | ||
| def get_inventory( | ||
| warehouse: Optional[str] = None, | ||
| category: Optional[str] = None | ||
| category: Optional[str] = None, | ||
| stock_status: Optional[str] = None | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CONFIRMED] New server/CLAUDE.md's 'Adding New Endpoints' process (step 6) says 'Write tests in |
||
| ): | ||
| """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) | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CONFIRMED, currently latent] That endpoint filters |
||
|
|
||
| @app.get("/api/inventory/{item_id}", response_model=InventoryItem) | ||
| def get_inventory_item(item_id: str): | ||
|
|
||
There was a problem hiding this comment.
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 nooutOfStocktier, and thestatus.*translation catalog (en.js/ja.js) has nooutOfStockkey 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.