Reactive store with $state/$derived for items, locations, checked-out items, overdue items, and low-stock items. Supports CRUD, check-in/out, sighting recording, and location tree queries. 12 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
128 lines
3.6 KiB
TypeScript
128 lines
3.6 KiB
TypeScript
import { getDB } from '$lib/data/db';
|
|
import { getAllItems, createItem as dbCreateItem, updateItem as dbUpdateItem, removeItem as dbRemoveItem } from '$lib/data/items';
|
|
import { getAllLocations, seedDefaultLocations } from '$lib/data/locations';
|
|
import { createSighting } from '$lib/data/sightings';
|
|
import { generateItemId, generateSightingId } from '$lib/utils/id';
|
|
import { getConfidence } from '$lib/utils/confidence';
|
|
import { buildCheckOutUpdate, buildCheckInUpdate } from '$lib/utils/custody';
|
|
import type { Item, Location, Sighting, CheckOutReason } from '$lib/types';
|
|
|
|
class InventoryStore {
|
|
items = $state<Item[]>([]);
|
|
locations = $state<Location[]>([]);
|
|
loading = $state(true);
|
|
|
|
checkedOutItems = $derived(
|
|
this.items.filter((i) => i.custodyState === 'checked-out')
|
|
);
|
|
|
|
overdueItems = $derived(
|
|
this.items.filter(
|
|
(i) =>
|
|
i.custodyState === 'checked-out' &&
|
|
i.checkedOutSince &&
|
|
Date.now() - new Date(i.checkedOutSince).getTime() > 7 * 86400000
|
|
)
|
|
);
|
|
|
|
lowStockItems = $derived(
|
|
this.items.filter(
|
|
(i) =>
|
|
i.currentQuantity != null &&
|
|
i.lowThreshold != null &&
|
|
i.currentQuantity <= i.lowThreshold
|
|
)
|
|
);
|
|
|
|
async loadAll() {
|
|
await seedDefaultLocations();
|
|
this.items = await getAllItems();
|
|
this.locations = await getAllLocations();
|
|
this.loading = false;
|
|
}
|
|
|
|
async createItem(data: Omit<Item, 'shortId' | 'createdAt' | 'updatedAt' | 'barcodeUri' | 'barcodeFormat'>): Promise<Item> {
|
|
const now = new Date().toISOString();
|
|
const shortId = generateItemId();
|
|
const item: Item = {
|
|
...data,
|
|
shortId,
|
|
barcodeFormat: 'qr',
|
|
barcodeUri: `https://haus.toph.so/${shortId}`,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
await dbCreateItem(item);
|
|
this.items = [...this.items, item];
|
|
return item;
|
|
}
|
|
|
|
async updateItem(shortId: string, updates: Partial<Item>): Promise<Item | undefined> {
|
|
const updated = await dbUpdateItem(shortId, updates);
|
|
if (updated) {
|
|
this.items = this.items.map((i) => (i.shortId === shortId ? updated : i));
|
|
}
|
|
return updated;
|
|
}
|
|
|
|
async removeItem(shortId: string): Promise<void> {
|
|
await dbRemoveItem(shortId);
|
|
this.items = this.items.filter((i) => i.shortId !== shortId);
|
|
}
|
|
|
|
async checkOut(
|
|
itemId: string,
|
|
reason: CheckOutReason,
|
|
fromLocationId: string,
|
|
toLocationId: string | null = null,
|
|
note: string = ''
|
|
): Promise<void> {
|
|
const updates = buildCheckOutUpdate(reason, fromLocationId, toLocationId, note);
|
|
await this.updateItem(itemId, updates);
|
|
}
|
|
|
|
async checkIn(itemId: string, returnToLocationId: string): Promise<void> {
|
|
const updates = buildCheckInUpdate(returnToLocationId);
|
|
await this.updateItem(itemId, updates);
|
|
}
|
|
|
|
async recordSighting(
|
|
itemId: string,
|
|
locationId: string,
|
|
type: 'scan' | 'manual' | 'camera-detect' | 'audit-verify' = 'scan'
|
|
): Promise<void> {
|
|
const now = new Date().toISOString();
|
|
const sighting: Sighting = {
|
|
id: generateSightingId(),
|
|
itemId,
|
|
locationId,
|
|
timestamp: now,
|
|
sightingType: type,
|
|
confidence: 'confirmed',
|
|
notes: '',
|
|
createdBy: null,
|
|
};
|
|
await createSighting(sighting);
|
|
await this.updateItem(itemId, {
|
|
lastSeenAt: locationId,
|
|
lastSeenTimestamp: now,
|
|
locationConfidence: 'confirmed',
|
|
});
|
|
}
|
|
|
|
getItemsByLocation(locationId: string): Item[] {
|
|
return this.items.filter((i) => i.lastSeenAt === locationId);
|
|
}
|
|
|
|
getLocationTree(): Map<string | null, Location[]> {
|
|
const tree = new Map<string | null, Location[]>();
|
|
for (const loc of this.locations) {
|
|
const children = tree.get(loc.parentId) ?? [];
|
|
children.push(loc);
|
|
tree.set(loc.parentId, children);
|
|
}
|
|
return tree;
|
|
}
|
|
}
|
|
|
|
export const inventory = new InventoryStore();
|