diff --git a/Agents.md b/Agents.md
index 42d24ef..0628625 100644
--- a/Agents.md
+++ b/Agents.md
@@ -113,12 +113,17 @@ Dexie tables must be implemented exactly as follows:
id: string
pick_list_id: string
product_id: string
- quantity_units: number
- quantity_bulk: number
+ quantity: number
+ is_carton: boolean
status: "pending" | "picked" | "skipped"
created_at: number
updated_at: number
+Pick items record a single packaging type per row: set `is_carton` to `true` when counting
+cartons (using the product's `bulk_name`) or `false` for single units (using `unit_type`). If
+both units and cartons are needed for the same product, store them as two PickItem records so
+quantities remain distinct.
+
------------------------------------------------------------------------
## 4. UI & UX Rules
@@ -162,7 +167,7 @@ Dexie tables must be implemented exactly as follows:
- Search\
- Category filter\
- Scan barcode\
-- Increment units & bulk\
+- Increment units & cartons separately (saved as distinct PickItems)\
- Add to pick list
### ManageProductsScreen
diff --git a/src/db/index.ts b/src/db/index.ts
index 8526a51..4b07329 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -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,
+ });
+ }),
+ );
+ });
}
}
diff --git a/src/models/PickItem.ts b/src/models/PickItem.ts
index 7e51e76..ab0ca96 100644
--- a/src/models/PickItem.ts
+++ b/src/models/PickItem.ts
@@ -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;
}
diff --git a/src/screens/ActivePickListScreen.tsx b/src/screens/ActivePickListScreen.tsx
index 5e30ec3..3bce4ac 100644
--- a/src/screens/ActivePickListScreen.tsx
+++ b/src/screens/ActivePickListScreen.tsx
@@ -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(),
});
};
diff --git a/src/screens/AddItemScreen.tsx b/src/screens/AddItemScreen.tsx
index 308aa40..71e2f87 100644
--- a/src/screens/AddItemScreen.tsx
+++ b/src/screens/AddItemScreen.tsx
@@ -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 = () => {
/>
)}
/>
-
-
+
+