diff --git a/docs/FSD.md b/docs/FSD.md index 82dc15b..162d239 100644 --- a/docs/FSD.md +++ b/docs/FSD.md @@ -54,6 +54,7 @@ Each brand-type-chemistry combination can have batteries in three states: - Can select from predefined chemistries (NiMH, Li-ion, etc.) or add custom - Can enter brand name - Can set initial quantity and status +- If the same brand-type-chemistry combination already exists, the quantity is added to the existing group (upsert behavior) ### US-02: View Inventory > As a user, I want to see all my batteries at a glance so I know what's available. @@ -87,6 +88,14 @@ Each brand-type-chemistry combination can have batteries in three states: - Mark charging complete (moves to available) - Optional: track charging start time +### US-06: Add Batteries to Existing Group +> As a user, I want to add more batteries to an existing group when I purchase more or find additional batteries. + +**Acceptance Criteria:** +- When adding batteries with same brand-type-chemistry as existing group, quantity is added to that group +- User receives feedback indicating batteries were added to existing group vs new group created +- Works seamlessly whether user intentionally or accidentally selects existing combination + ## 5. Data Model (Conceptual) ``` diff --git a/docs/TSD.md b/docs/TSD.md index 05450bb..346b9d9 100644 --- a/docs/TSD.md +++ b/docs/TSD.md @@ -193,7 +193,7 @@ POST /api/brands # Create brand # Battery Groups GET /api/batteries # List all battery groups (with brand/type/chemistry info) -POST /api/batteries # Create battery group +POST /api/batteries # Create or add to battery group (upsert) GET /api/batteries/:id # Get single battery group PATCH /api/batteries/:id # Update battery group DELETE /api/batteries/:id # Delete battery group @@ -309,6 +309,49 @@ async function assignToDevice(groupId: number, deviceId: number, quantity: numbe } ``` +### 6.4 Add Batteries (Upsert Behavior) + +When adding batteries, the system checks if a group with the same brand-type-chemistry combination exists: +- **If exists**: Adds the quantity to the existing group's available count +- **If not exists**: Creates a new battery group + +```typescript +// POST /api/batteries - Upsert behavior +async function addBatteries(brandId: number, typeId: number, chemistryId: number, quantity: number) { + // Check for existing group + const existing = await db + .select() + .from(batteryGroups) + .where(and( + eq(batteryGroups.brandId, brandId), + eq(batteryGroups.typeId, typeId), + eq(batteryGroups.chemistryId, chemistryId) + )) + .get(); + + if (existing) { + // Add to existing group + await db + .update(batteryGroups) + .set({ + availableCount: sql`available_count + ${quantity}`, + updatedAt: new Date() + }) + .where(eq(batteryGroups.id, existing.id)); + return { ...existing, isNew: false }; + } else { + // Create new group + const result = await db + .insert(batteryGroups) + .values({ brandId, typeId, chemistryId, availableCount: quantity }) + .returning(); + return { ...result[0], isNew: true }; + } +} +``` + +Response includes `isNew` flag to allow UI to show appropriate feedback message. + ## 7. UI/UX Design Details ### Color Scheme diff --git a/src/app/api/batteries/route.ts b/src/app/api/batteries/route.ts index 72f25b5..8995641 100644 --- a/src/app/api/batteries/route.ts +++ b/src/app/api/batteries/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from 'next/server'; import { db, schema } from '@/lib/db'; -import { eq, sql } from 'drizzle-orm'; +import { eq, sql, and } from 'drizzle-orm'; export async function GET() { try { @@ -56,19 +56,58 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Brand, type, and chemistry are required' }, { status: 400 }); } - const result = await db - .insert(schema.batteryGroups) - .values({ - brandId, - typeId, - chemistryId, - availableCount: availableCount || 0, - chargingCount: chargingCount || 0, - notes, - }) - .returning(); + // Check if a group with the same brand-type-chemistry already exists + const existing = await db + .select() + .from(schema.batteryGroups) + .where( + and( + eq(schema.batteryGroups.brandId, brandId), + eq(schema.batteryGroups.typeId, typeId), + eq(schema.batteryGroups.chemistryId, chemistryId) + ) + ) + .get(); - return NextResponse.json(result[0], { status: 201 }); + if (existing) { + // Add to existing group + const quantityToAdd = availableCount || 0; + const chargingToAdd = chargingCount || 0; + + await db + .update(schema.batteryGroups) + .set({ + availableCount: sql`${schema.batteryGroups.availableCount} + ${quantityToAdd}`, + chargingCount: sql`${schema.batteryGroups.chargingCount} + ${chargingToAdd}`, + notes: notes || existing.notes, + updatedAt: sql`(datetime('now'))`, + }) + .where(eq(schema.batteryGroups.id, existing.id)); + + // Fetch updated record + const updated = await db + .select() + .from(schema.batteryGroups) + .where(eq(schema.batteryGroups.id, existing.id)) + .get(); + + return NextResponse.json({ ...updated, isNew: false }, { status: 200 }); + } else { + // Create new group + const result = await db + .insert(schema.batteryGroups) + .values({ + brandId, + typeId, + chemistryId, + availableCount: availableCount || 0, + chargingCount: chargingCount || 0, + notes, + }) + .returning(); + + return NextResponse.json({ ...result[0], isNew: true }, { status: 201 }); + } } catch (error) { console.error('Failed to create battery group:', error); return NextResponse.json({ error: 'Failed to create battery group' }, { status: 500 }); diff --git a/src/components/battery/AddBatteryModal.tsx b/src/components/battery/AddBatteryModal.tsx index 20550fe..b6ed7c0 100644 --- a/src/components/battery/AddBatteryModal.tsx +++ b/src/components/battery/AddBatteryModal.tsx @@ -93,7 +93,15 @@ export function AddBatteryModal({ isOpen, onClose, types, brands, chemistries }: throw new Error(error.error || 'Failed to add battery'); } - showToast('success', 'Battery added successfully'); + const result = await res.json(); + const quantity = (parseInt(formData.availableCount) || 0) + (parseInt(formData.chargingCount) || 0); + + if (result.isNew) { + showToast('success', `New battery group created with ${quantity} batteries`); + } else { + showToast('success', `${quantity} batteries added to existing group`); + } + onClose(); router.refresh();