Add Mark Gone action for stock items and enhance expiration and location filtering system
This commit is contained in:
@@ -29,6 +29,8 @@ const EXPIRATION_LEGEND = [
|
||||
{ key: 'none', label: 'No expiration', description: 'No expiration date is assigned.' },
|
||||
];
|
||||
|
||||
const EXPIRATION_KEYS = EXPIRATION_LEGEND.map((state) => state.key);
|
||||
|
||||
function todayAtMidnight() {
|
||||
const now = new Date();
|
||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
@@ -180,9 +182,12 @@ export function renderStockListPage() {
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-center gap-3 mb-3">
|
||||
<label class="form-label mb-0">Search stock</label>
|
||||
<button class="btn btn-link p-0 text-decoration-none stock-filter-reset" type="button" @click="clearFilters()">Reset</button>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label">Search stock</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
@@ -190,71 +195,128 @@ export function renderStockListPage() {
|
||||
placeholder="Search by item, description, location, or id"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="stock-filter-toolbar">
|
||||
<details class="stock-filter-details w-100">
|
||||
<summary class="btn btn-outline-secondary">More filters</summary>
|
||||
<div class="stock-filter-panel mt-3">
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Expiration filter</label>
|
||||
<select class="form-select" x-model="filters.expiration">
|
||||
<option value="">All expiration states</option>
|
||||
<option value="expired">Expired</option>
|
||||
<option value="use-first">Use first</option>
|
||||
<option value="upcoming">Upcoming expiration</option>
|
||||
<option value="within-date">Within date</option>
|
||||
<option value="none">No expiration</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Location</label>
|
||||
<select class="form-select" x-model="filters.location">
|
||||
<option value="">All locations</option>
|
||||
<template x-for="location in locations" :key="location.id">
|
||||
<option :value="location.uuid_b64" x-text="location.pathLabel"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<button class="btn btn-outline-secondary stock-filter-clear" type="button" @click="clearFilters()">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="card border-0 shadow-sm mb-4 stock-guide">
|
||||
<summary class="card-body p-4 d-flex justify-content-between align-items-center gap-3 stock-guide-summary">
|
||||
<div>
|
||||
<h2 class="h5 mb-1">Expiration overview</h2>
|
||||
<p class="text-body-secondary small mb-0">
|
||||
Show what each expiration color means.
|
||||
</p>
|
||||
</div>
|
||||
<div class="small text-body-secondary">
|
||||
<span class="fw-semibold text-body" x-text="filteredEntries.length"></span>
|
||||
item(s) visible
|
||||
</div>
|
||||
</summary>
|
||||
<div class="card-body pt-0 px-4 pb-4">
|
||||
<div class="row g-3">
|
||||
<template x-for="stateInfo in expirationLegend" :key="stateInfo.key">
|
||||
<div class="col-12 col-md-6 col-xl-4">
|
||||
<div class="legend-card h-100" :class="legendClass(stateInfo.key)">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-1">
|
||||
<div class="fw-semibold" x-text="stateInfo.label"></div>
|
||||
<div class="small fw-semibold" x-text="expirationCount(stateInfo.key)"></div>
|
||||
<div class="row g-4 mb-4 align-items-start" :class="overviewRowClass()">
|
||||
<div class="col-12" :class="overviewColClass('expiration')">
|
||||
<details
|
||||
class="card border-0 shadow-sm overview-panel"
|
||||
x-ref="expirationOverview"
|
||||
@toggle="setOverviewOpen('expiration', $event.target.open)"
|
||||
>
|
||||
<summary class="card-body p-4 overview-summary">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||
<div>
|
||||
<h2 class="h5 mb-1">Expiration overview</h2>
|
||||
<p class="text-body-secondary small mb-0">Tap to focus on one or more expiration states.</p>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-end gap-1">
|
||||
<button class="btn btn-link p-0 text-decoration-none stock-filter-reset" type="button" @click.prevent.stop="toggleAllExpirationFilters()">Show all</button>
|
||||
<div class="small text-body-secondary text-end">
|
||||
<span class="fw-semibold text-body" x-text="filteredEntries.length"></span>
|
||||
item(s) visible
|
||||
</div>
|
||||
<div class="small" x-text="stateInfo.description"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="card-body pt-0 px-4 pb-4">
|
||||
<div class="overview-list" :class="{ 'overview-list-split': isOnlyOverviewOpen('expiration') }">
|
||||
<template x-for="stateInfo in expirationLegend" :key="stateInfo.key">
|
||||
<button
|
||||
class="overview-option text-start"
|
||||
type="button"
|
||||
:class="legendClass(stateInfo.key)"
|
||||
@click="toggleExpirationOverviewFilter(stateInfo.key)"
|
||||
:aria-pressed="isExpirationFilterActive(stateInfo.key)"
|
||||
>
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-1">
|
||||
<div class="fw-semibold" x-text="stateInfo.label"></div>
|
||||
<div class="small fw-semibold" x-text="expirationCount(stateInfo.key)"></div>
|
||||
</div>
|
||||
<div class="small" x-text="stateInfo.description"></div>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</details>
|
||||
<div class="col-12" :class="overviewColClass('location')">
|
||||
<details
|
||||
class="card border-0 shadow-sm overview-panel"
|
||||
x-ref="locationOverview"
|
||||
@toggle="setOverviewOpen('location', $event.target.open)"
|
||||
>
|
||||
<summary class="card-body p-4 overview-summary">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||
<div>
|
||||
<h2 class="h5 mb-1">Location overview</h2>
|
||||
<p class="text-body-secondary small mb-0">Tap locations to focus the list. Parent locations include their children.</p>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-end gap-1">
|
||||
<button class="btn btn-link p-0 text-decoration-none stock-filter-reset" type="button" @click.prevent.stop="toggleAllLocations()">Show all</button>
|
||||
<div class="small text-body-secondary text-end" x-text="selectedLocationSummary()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="card-body pt-0 px-4 pb-4">
|
||||
<template x-if="isOnlyOverviewOpen('location')">
|
||||
<div class="overview-list overview-list-locations location-overview-columns">
|
||||
<template x-for="(columnGroups, columnIndex) in balancedLocationOverviewColumns()" :key="columnIndex">
|
||||
<div class="location-overview-column">
|
||||
<template x-for="group in columnGroups" :key="group.parent.id">
|
||||
<div class="location-overview-group">
|
||||
<template x-for="location in group.items" :key="location.id">
|
||||
<button
|
||||
class="overview-option overview-option-location text-start"
|
||||
type="button"
|
||||
:class="locationOverviewClass(location)"
|
||||
:style="locationOverviewStyle(location)"
|
||||
@click="toggleLocationOverviewFilter(location.uuid_b64)"
|
||||
:aria-pressed="isLocationFilterActive(location.uuid_b64)"
|
||||
>
|
||||
<span class="stock-filter-location-rail" x-show="location.depth"></span>
|
||||
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||
<div class="fw-semibold" :class="location.depth ? 'small mb-0' : ''" x-text="location.name"></div>
|
||||
<div class="small fw-semibold" x-text="locationCount(location.uuid_b64)"></div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!isOnlyOverviewOpen('location')">
|
||||
<div class="overview-list overview-list-locations">
|
||||
<template x-for="group in locationOverviewGroups()" :key="group.parent.id">
|
||||
<div class="location-overview-group">
|
||||
<template x-for="location in group.items" :key="location.id">
|
||||
<button
|
||||
class="overview-option overview-option-location text-start"
|
||||
type="button"
|
||||
:class="locationOverviewClass(location)"
|
||||
:style="locationOverviewStyle(location)"
|
||||
@click="toggleLocationOverviewFilter(location.uuid_b64)"
|
||||
:aria-pressed="isLocationFilterActive(location.uuid_b64)"
|
||||
>
|
||||
<span class="stock-filter-location-rail" x-show="location.depth"></span>
|
||||
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||
<div class="fw-semibold" :class="location.depth ? 'small mb-0' : ''" x-text="location.name"></div>
|
||||
<div class="small fw-semibold" x-text="locationCount(location.uuid_b64)"></div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template x-if="state.isLoading">
|
||||
<div class="alert alert-secondary">Loading stock review...</div>
|
||||
@@ -332,13 +394,14 @@ export function renderStockListPage() {
|
||||
</template>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-primary" type="button" @click="saveLevel(entry)">Save</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" @click="markGone(entry)">Mark gone</button>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="entry.stock_type === 'measured'">
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<input class="form-control form-control-sm quick-number" type="number" step="0.01" min="0" x-model="editForms[entry.id].quantity" />
|
||||
<button class="btn btn-sm btn-primary" type="button" @click="saveQuantity(entry)">Save qty</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" @click="markMeasuredGone(entry)">Gone</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" @click="markGone(entry)">Mark gone</button>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="editErrors[entry.id]">
|
||||
@@ -399,7 +462,10 @@ export function renderStockListPage() {
|
||||
<option :value="option.value" x-text="option.label"></option>
|
||||
</template>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-primary" type="button" @click="saveLevel(entry)">Save stock level</button>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-sm btn-primary" type="button" @click="saveLevel(entry)">Save stock level</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" @click="markGone(entry)">Mark gone</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="entry.stock_type === 'measured'">
|
||||
@@ -407,7 +473,7 @@ export function renderStockListPage() {
|
||||
<input class="form-control form-control-sm" type="number" step="0.01" min="0" x-model="editForms[entry.id].quantity" />
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-sm btn-primary" type="button" @click="saveQuantity(entry)">Save quantity</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" @click="markMeasuredGone(entry)">Gone</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" @click="markGone(entry)">Mark gone</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -436,10 +502,14 @@ export function stockListPageData(store) {
|
||||
editErrors: {},
|
||||
levelOptions: LEVEL_OPTIONS,
|
||||
expirationLegend: EXPIRATION_LEGEND,
|
||||
overviewOpen: {
|
||||
expiration: false,
|
||||
location: false,
|
||||
},
|
||||
filters: {
|
||||
search: '',
|
||||
expiration: '',
|
||||
location: '',
|
||||
expiration: [],
|
||||
location: [],
|
||||
},
|
||||
async init() {
|
||||
if (!store.isConnected) {
|
||||
@@ -495,10 +565,152 @@ export function stockListPageData(store) {
|
||||
clearFilters() {
|
||||
this.filters = {
|
||||
search: '',
|
||||
expiration: '',
|
||||
location: '',
|
||||
expiration: [],
|
||||
location: [],
|
||||
};
|
||||
},
|
||||
setOverviewOpen(key, open) {
|
||||
this.overviewOpen[key] = open;
|
||||
|
||||
if (!open || !this.isCompactOverviewLayout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const otherKey = key === 'expiration' ? 'location' : 'expiration';
|
||||
this.overviewOpen[otherKey] = false;
|
||||
|
||||
const otherPanel =
|
||||
otherKey === 'expiration'
|
||||
? this.$refs.expirationOverview
|
||||
: this.$refs.locationOverview;
|
||||
|
||||
if (otherPanel?.open) {
|
||||
otherPanel.open = false;
|
||||
}
|
||||
},
|
||||
isCompactOverviewLayout() {
|
||||
return window.matchMedia('(max-width: 1199.98px)').matches;
|
||||
},
|
||||
openOverviewCount() {
|
||||
return Number(this.overviewOpen.expiration) + Number(this.overviewOpen.location);
|
||||
},
|
||||
isOnlyOverviewOpen(key) {
|
||||
return this.openOverviewCount() === 1 && this.overviewOpen[key];
|
||||
},
|
||||
overviewRowClass() {
|
||||
return this.openOverviewCount() === 1 ? 'overview-row-single-open' : '';
|
||||
},
|
||||
overviewColClass(key) {
|
||||
if (this.openOverviewCount() === 1) {
|
||||
return 'col-xl-12';
|
||||
}
|
||||
|
||||
return key === 'expiration' ? 'col-xl-5' : 'col-xl-7';
|
||||
},
|
||||
expirationFilterSummary() {
|
||||
if (this.isAllExpirationSelected()) {
|
||||
return 'Show all';
|
||||
}
|
||||
|
||||
if (!this.filters.expiration.length) {
|
||||
return 'No expiration states selected';
|
||||
}
|
||||
|
||||
if (this.filters.expiration.length === 1) {
|
||||
return this.expirationLegend.find((state) => state.key === this.filters.expiration[0])?.label || '1 expiration state';
|
||||
}
|
||||
|
||||
return `${this.filters.expiration.length} expiration states selected`;
|
||||
},
|
||||
isAllExpirationSelected() {
|
||||
return this.filters.expiration.length === 0 || this.filters.expiration.length === EXPIRATION_KEYS.length;
|
||||
},
|
||||
toggleAllExpirationFilters() {
|
||||
this.filters.expiration = [];
|
||||
},
|
||||
toggleExpirationOverviewFilter(key) {
|
||||
if (this.isAllExpirationSelected()) {
|
||||
this.filters.expiration = [key];
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleExpirationFilter(key);
|
||||
},
|
||||
toggleExpirationFilter(key) {
|
||||
if (this.filters.expiration.includes(key)) {
|
||||
this.filters.expiration = this.filters.expiration.filter((value) => value !== key);
|
||||
return;
|
||||
}
|
||||
|
||||
this.filters.expiration = [...this.filters.expiration, key];
|
||||
},
|
||||
locationFilterSummary() {
|
||||
if (this.isAllLocationsSelected()) {
|
||||
return 'All locations';
|
||||
}
|
||||
|
||||
if (!this.filters.location.length) {
|
||||
return 'No locations selected';
|
||||
}
|
||||
|
||||
if (this.filters.location.length === 1) {
|
||||
return this.locationMap[this.filters.location[0]] || '1 location selected';
|
||||
}
|
||||
|
||||
return `${this.filters.location.length} locations selected`;
|
||||
},
|
||||
selectedLocationSummary() {
|
||||
if (this.isAllLocationsSelected()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!this.filters.location.length) {
|
||||
return 'No locations selected';
|
||||
}
|
||||
|
||||
return `${this.filters.location.length} selected`;
|
||||
},
|
||||
locationOverviewGroups() {
|
||||
const byParent = this.locations
|
||||
.filter((location) => location.depth === 0)
|
||||
.map((parent) => ({
|
||||
parent,
|
||||
items: this.locations.filter((location) =>
|
||||
location.lineage_uuid_b64[0] === parent.uuid_b64,
|
||||
),
|
||||
}));
|
||||
|
||||
return byParent;
|
||||
},
|
||||
balancedLocationOverviewColumns() {
|
||||
const columns = [[], []];
|
||||
const sizes = [0, 0];
|
||||
|
||||
this.locationOverviewGroups().forEach((group) => {
|
||||
const groupSize = group.items.length;
|
||||
const targetIndex = sizes[0] <= sizes[1] ? 0 : 1;
|
||||
columns[targetIndex].push(group);
|
||||
sizes[targetIndex] += groupSize;
|
||||
});
|
||||
|
||||
return columns;
|
||||
},
|
||||
isAllLocationsSelected() {
|
||||
return this.filters.location.length === 0 || (this.locations.length > 0 && this.filters.location.length === this.locations.length);
|
||||
},
|
||||
toggleAllLocations() {
|
||||
this.filters.location = [];
|
||||
},
|
||||
toggleLocationFilter(uuid) {
|
||||
const subtree = this.locationSubtree(uuid);
|
||||
|
||||
if (this.filters.location.includes(uuid)) {
|
||||
this.filters.location = this.filters.location.filter((value) => !subtree.includes(value));
|
||||
return;
|
||||
}
|
||||
|
||||
this.filters.location = [...new Set([...this.filters.location, ...subtree])];
|
||||
},
|
||||
get filteredEntries() {
|
||||
return this.entries.filter((entry) => {
|
||||
if (
|
||||
@@ -509,15 +721,17 @@ export function stockListPageData(store) {
|
||||
}
|
||||
|
||||
if (
|
||||
this.filters.expiration &&
|
||||
expirationInfo(entry).key !== this.filters.expiration
|
||||
this.filters.expiration.length &&
|
||||
this.filters.expiration.length !== EXPIRATION_KEYS.length &&
|
||||
!this.filters.expiration.includes(expirationInfo(entry).key)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.filters.location &&
|
||||
!this.locationMatchesFilter(entry.location_initial_uuid_b64, this.filters.location)
|
||||
this.filters.location.length &&
|
||||
this.filters.location.length !== this.locations.length &&
|
||||
!this.locationMatchesAnyFilter(entry.location_initial_uuid_b64, this.filters.location)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -528,6 +742,9 @@ export function stockListPageData(store) {
|
||||
expirationFor(entry) {
|
||||
return expirationInfo(entry);
|
||||
},
|
||||
isExpirationFilterActive(key) {
|
||||
return this.isAllExpirationSelected() || this.filters.expiration.includes(key);
|
||||
},
|
||||
rowClass(entry) {
|
||||
return `expiration-${expirationInfo(entry).key}`;
|
||||
},
|
||||
@@ -535,10 +752,34 @@ export function stockListPageData(store) {
|
||||
return `expiration-badge-${expirationInfo(entry).key}`;
|
||||
},
|
||||
legendClass(key) {
|
||||
return `legend-${key}`;
|
||||
const hasActiveFilters = this.filters.expiration.length > 0 && !this.isAllExpirationSelected();
|
||||
return [
|
||||
`legend-${key}`,
|
||||
hasActiveFilters && this.isExpirationFilterActive(key) ? 'legend-card-active' : '',
|
||||
hasActiveFilters && !this.isExpirationFilterActive(key) ? 'legend-card-inactive' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
},
|
||||
expirationCount(key) {
|
||||
return this.entries.filter((entry) => expirationInfo(entry).key === key).length;
|
||||
return this.entries.filter((entry) => {
|
||||
if (
|
||||
this.filters.search &&
|
||||
!searchBlob(entry, this.locationMap).includes(this.filters.search.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.filters.location.length &&
|
||||
this.filters.location.length !== this.locations.length &&
|
||||
!this.locationMatchesAnyFilter(entry.location_initial_uuid_b64, this.filters.location)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return expirationInfo(entry).key === key;
|
||||
}).length;
|
||||
},
|
||||
shortId(entry) {
|
||||
return entry.uuid_b64 ? entry.uuid_b64.slice(0, 10) : 'No id';
|
||||
@@ -546,6 +787,53 @@ export function stockListPageData(store) {
|
||||
locationLabel(entry) {
|
||||
return resolveLocationLabel(entry, this.locationMap);
|
||||
},
|
||||
isLocationFilterActive(uuid) {
|
||||
return this.isAllLocationsSelected() || this.filters.location.includes(uuid);
|
||||
},
|
||||
toggleLocationOverviewFilter(uuid) {
|
||||
if (this.isAllLocationsSelected()) {
|
||||
this.filters.location = [...this.locationSubtree(uuid)];
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleLocationFilter(uuid);
|
||||
},
|
||||
locationOverviewClass(location) {
|
||||
const hasActiveFilters = this.filters.location.length > 0 && !this.isAllLocationsSelected();
|
||||
return [
|
||||
'location-overview',
|
||||
`location-type-${location.type || 'unknown'}`,
|
||||
location.depth ? 'location-overview-child' : 'location-overview-parent',
|
||||
hasActiveFilters && this.isLocationFilterActive(location.uuid_b64) ? 'legend-card-active' : '',
|
||||
hasActiveFilters && !this.isLocationFilterActive(location.uuid_b64) ? 'legend-card-inactive' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
},
|
||||
locationOverviewStyle(location) {
|
||||
const offset = location.depth;
|
||||
return `margin-left: ${offset}rem; width: calc(100% - ${offset}rem);`;
|
||||
},
|
||||
locationCount(locationUuid) {
|
||||
return this.entries.filter((entry) => {
|
||||
if (
|
||||
this.filters.search &&
|
||||
!searchBlob(entry, this.locationMap).includes(this.filters.search.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.filters.expiration.length &&
|
||||
this.filters.expiration.length !== EXPIRATION_KEYS.length &&
|
||||
!this.filters.expiration.includes(expirationInfo(entry).key)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.locationMatchesFilter(entry.location_initial_uuid_b64, locationUuid);
|
||||
}).length;
|
||||
},
|
||||
locationMatchesFilter(entryLocationUuid, selectedLocationUuid) {
|
||||
if (!selectedLocationUuid) {
|
||||
return true;
|
||||
@@ -554,6 +842,18 @@ export function stockListPageData(store) {
|
||||
const allowed = this.locationDescendants[selectedLocationUuid] || [selectedLocationUuid];
|
||||
return allowed.includes(entryLocationUuid);
|
||||
},
|
||||
locationSubtree(selectedLocationUuid) {
|
||||
return this.locationDescendants[selectedLocationUuid] || [selectedLocationUuid];
|
||||
},
|
||||
locationMatchesAnyFilter(entryLocationUuid, selectedLocationUuids) {
|
||||
if (!selectedLocationUuids.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return selectedLocationUuids.some((selectedLocationUuid) =>
|
||||
this.locationMatchesFilter(entryLocationUuid, selectedLocationUuid),
|
||||
);
|
||||
},
|
||||
quantityLabel,
|
||||
stockTypeDetail(entry) {
|
||||
if (entry.stock_type === 'binary') {
|
||||
@@ -570,6 +870,11 @@ export function stockListPageData(store) {
|
||||
},
|
||||
async saveLevel(entry) {
|
||||
const level = this.editForms[entry.id]?.level || 'plenty';
|
||||
if (level === 'gone') {
|
||||
await this.deleteEntry(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.saveEntryUpdate(entry, {
|
||||
level,
|
||||
}, { level });
|
||||
@@ -585,7 +890,7 @@ export function stockListPageData(store) {
|
||||
quantity,
|
||||
}, { quantity });
|
||||
},
|
||||
async markMeasuredGone(entry) {
|
||||
async markGone(entry) {
|
||||
await this.deleteEntry(entry);
|
||||
},
|
||||
async saveEntryUpdate(entry, payload, localPatch) {
|
||||
|
||||
Reference in New Issue
Block a user