Add carton-aware pick item quantities and migration
This commit is contained in:
@@ -7,8 +7,7 @@ import { useSwipe } from '../hooks/useSwipe';
|
||||
interface PickItemRowProps {
|
||||
item: PickItem;
|
||||
product?: Product | null;
|
||||
onIncrementUnit: () => void;
|
||||
onIncrementBulk: () => void;
|
||||
onIncrement: () => void;
|
||||
onSwipeLeft: () => void;
|
||||
onSwipeRight: () => void;
|
||||
}
|
||||
@@ -22,13 +21,13 @@ const statusColor: Record<PickItemStatus, 'default' | 'success' | 'warning'> = {
|
||||
export const PickItemRow = ({
|
||||
item,
|
||||
product,
|
||||
onIncrementUnit,
|
||||
onIncrementBulk,
|
||||
onIncrement,
|
||||
onSwipeLeft,
|
||||
onSwipeRight,
|
||||
}: PickItemRowProps) => {
|
||||
const longPressHandlers = useLongPress({ onLongPress: onIncrementBulk, onClick: onIncrementUnit });
|
||||
const longPressHandlers = useLongPress({ onLongPress: onIncrement, onClick: onIncrement });
|
||||
const swipeHandlers = useSwipe({ onSwipeLeft, onSwipeRight });
|
||||
const unitLabel = item.is_carton ? product?.bulk_name ?? 'cartons' : product?.unit_type ?? 'units';
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -43,7 +42,7 @@ export const PickItemRow = ({
|
||||
<div>
|
||||
<Typography variant="subtitle1">{product?.name ?? 'Unknown product'}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{item.quantity_units} units / {item.quantity_bulk} bulk
|
||||
{item.quantity} {unitLabel}
|
||||
</Typography>
|
||||
</div>
|
||||
<Chip label={item.status} color={statusColor[item.status]} size="small" />
|
||||
|
||||
@@ -90,6 +90,64 @@ export class StockFillDB extends Dexie {
|
||||
pickItems: 'id, pick_list_id, product_id, status, created_at, updated_at',
|
||||
categories: 'id, name, created_at, updated_at',
|
||||
});
|
||||
|
||||
this.version(5)
|
||||
.stores({
|
||||
products: 'id, name, category, &barcode, archived, created_at, updated_at',
|
||||
areas: 'id, name, created_at, updated_at',
|
||||
pickLists: 'id, area_id, created_at, completed_at',
|
||||
pickItems:
|
||||
'id, pick_list_id, product_id, status, is_carton, quantity, created_at, updated_at',
|
||||
categories: 'id, name, created_at, updated_at',
|
||||
})
|
||||
.upgrade(async (tx) => {
|
||||
const items = await tx.table('pickItems').toArray();
|
||||
const now = Date.now();
|
||||
|
||||
await Promise.all(
|
||||
items.map(async (item) => {
|
||||
const hasNewFields =
|
||||
typeof (item as PickItem).quantity === 'number' &&
|
||||
typeof (item as PickItem).is_carton === 'boolean';
|
||||
|
||||
if (hasNewFields) return undefined;
|
||||
|
||||
const legacyUnits = Number((item as PickItem).quantity_units ?? 0);
|
||||
const legacyBulk = Number((item as PickItem).quantity_bulk ?? 0);
|
||||
|
||||
if (legacyUnits > 0 && legacyBulk > 0) {
|
||||
await tx.table('pickItems').update(item.id, {
|
||||
quantity: legacyUnits,
|
||||
is_carton: false,
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
return tx.table('pickItems').add({
|
||||
...item,
|
||||
id: uuidv4(),
|
||||
quantity: legacyBulk,
|
||||
is_carton: true,
|
||||
created_at: (item as PickItem).created_at ?? now,
|
||||
updated_at: now,
|
||||
});
|
||||
}
|
||||
|
||||
if (legacyBulk > 0) {
|
||||
return tx.table('pickItems').update(item.id, {
|
||||
quantity: legacyBulk,
|
||||
is_carton: true,
|
||||
updated_at: now,
|
||||
});
|
||||
}
|
||||
|
||||
return tx.table('pickItems').update(item.id, {
|
||||
quantity: legacyUnits,
|
||||
is_carton: false,
|
||||
updated_at: now,
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ export interface PickItem {
|
||||
id: string;
|
||||
pick_list_id: string;
|
||||
product_id: string;
|
||||
quantity_units: number;
|
||||
quantity_bulk: number;
|
||||
quantity: number;
|
||||
is_carton: boolean;
|
||||
status: PickItemStatus;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
// Legacy fields retained for backward compatibility with pre-v5 data.
|
||||
quantity_units?: number;
|
||||
quantity_bulk?: number;
|
||||
}
|
||||
|
||||
@@ -19,20 +19,11 @@ export const ActivePickListScreen = () => {
|
||||
[areas, pickList?.area_id],
|
||||
);
|
||||
|
||||
const handleIncrementUnit = async (itemId: string) => {
|
||||
const handleIncrement = async (itemId: string) => {
|
||||
const existing = await db.pickItems.get(itemId);
|
||||
if (!existing) return;
|
||||
await db.pickItems.update(itemId, {
|
||||
quantity_units: existing.quantity_units + 1,
|
||||
updated_at: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
const handleIncrementBulk = async (itemId: string) => {
|
||||
const existing = await db.pickItems.get(itemId);
|
||||
if (!existing) return;
|
||||
await db.pickItems.update(itemId, {
|
||||
quantity_bulk: existing.quantity_bulk + 1,
|
||||
quantity: existing.quantity + 1,
|
||||
updated_at: Date.now(),
|
||||
});
|
||||
};
|
||||
@@ -68,8 +59,7 @@ export const ActivePickListScreen = () => {
|
||||
key={item.id}
|
||||
item={item}
|
||||
product={products.find((p) => p.id === item.product_id)}
|
||||
onIncrementUnit={() => handleIncrementUnit(item.id)}
|
||||
onIncrementBulk={() => handleIncrementBulk(item.id)}
|
||||
onIncrement={() => handleIncrement(item.id)}
|
||||
onSwipeLeft={() => handleSwipeLeft(item.id)}
|
||||
onSwipeRight={() => handleSwipeRight(item.id)}
|
||||
/>
|
||||
|
||||
@@ -25,26 +25,18 @@ export const AddItemScreen = () => {
|
||||
const [units, setUnits] = useState(1);
|
||||
const [bulk, setBulk] = useState(0);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const existingProductIds = useMemo(
|
||||
() => new Set(items.map((item) => item.product_id)),
|
||||
[items],
|
||||
);
|
||||
|
||||
const availableProducts = useMemo(
|
||||
() => products.filter((product) => !existingProductIds.has(product.id)),
|
||||
[existingProductIds, products],
|
||||
);
|
||||
const unitLabel = selectedProduct?.unit_type ?? 'Units';
|
||||
const cartonLabel = selectedProduct?.bulk_name ?? 'Cartons';
|
||||
|
||||
const filteredProducts = useMemo(() => {
|
||||
const normalizedQuery = query.trim().toLowerCase();
|
||||
if (!normalizedQuery) return availableProducts;
|
||||
if (!normalizedQuery) return products;
|
||||
|
||||
return availableProducts.filter((product) => {
|
||||
return products.filter((product) => {
|
||||
const searchableText = `${product.name} ${product.category} ${product.barcode ?? ''}`.toLowerCase();
|
||||
return searchableText.includes(normalizedQuery);
|
||||
});
|
||||
}, [availableProducts, query]);
|
||||
}, [products, query]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedProduct) return;
|
||||
@@ -56,17 +48,35 @@ export const AddItemScreen = () => {
|
||||
const addItem = async () => {
|
||||
const productId = selectedProduct?.id;
|
||||
|
||||
if (!id || !productId || existingProductIds.has(productId)) return;
|
||||
await db.pickItems.add({
|
||||
id: uuidv4(),
|
||||
pick_list_id: id,
|
||||
product_id: productId,
|
||||
quantity_units: units,
|
||||
quantity_bulk: bulk,
|
||||
status: 'pending',
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
});
|
||||
if (!id || !productId) return;
|
||||
|
||||
const addOrUpdateItem = async (is_carton: boolean, quantity: number) => {
|
||||
if (quantity <= 0) return;
|
||||
const existing = items.find(
|
||||
(item) => item.product_id === productId && item.is_carton === is_carton,
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
await db.pickItems.update(existing.id, {
|
||||
quantity: existing.quantity + quantity,
|
||||
updated_at: Date.now(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await db.pickItems.add({
|
||||
id: uuidv4(),
|
||||
pick_list_id: id,
|
||||
product_id: productId,
|
||||
quantity,
|
||||
is_carton,
|
||||
status: 'pending',
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
await Promise.all([addOrUpdateItem(false, units), addOrUpdateItem(true, bulk)]);
|
||||
navigate(`/pick-lists/${id}`);
|
||||
};
|
||||
|
||||
@@ -113,8 +123,8 @@ export const AddItemScreen = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<NumericStepper label="Units" value={units} onChange={setUnits} />
|
||||
<NumericStepper label="Bulk" value={bulk} onChange={setBulk} />
|
||||
<NumericStepper label={unitLabel} value={units} onChange={setUnits} />
|
||||
<NumericStepper label={cartonLabel} value={bulk} onChange={setBulk} />
|
||||
<Button variant="contained" disabled={!selectedProduct} onClick={addItem}>
|
||||
Add to List
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user