v0.21.0 — Pantry: bulk pull dialog
New "Pull from pantry" button opens a dialog where you search/filter all items, type a quantity next to each one you're taking, pick or type a reason, and submit all in one shot. Items with amounts entered highlight; over-stock amounts show inline error. Backed by new POST /api/pantry/bulk-use (transactional quantity deduction + ItemLog). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
61
src/app/api/pantry/bulk-use/route.ts
Normal file
61
src/app/api/pantry/bulk-use/route.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/lib/db";
|
||||
import { requireAuth } from "@/lib/auth";
|
||||
|
||||
const schema = z.object({
|
||||
pulls: z.array(z.object({
|
||||
id: z.string(),
|
||||
amount: z.number().positive(),
|
||||
})).min(1),
|
||||
notes: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const { pulls, notes } = schema.parse(await req.json());
|
||||
|
||||
const ids = pulls.map(p => p.id);
|
||||
const items = await db.item.findMany({
|
||||
where: { id: { in: ids }, kind: "CONSUMABLE", active: true },
|
||||
select: { id: true, name: true, quantity: true, unit: true },
|
||||
});
|
||||
|
||||
const itemMap = new Map(items.map(i => [i.id, i]));
|
||||
const results: { id: string; remaining: number }[] = [];
|
||||
|
||||
await db.$transaction(async tx => {
|
||||
for (const pull of pulls) {
|
||||
const item = itemMap.get(pull.id);
|
||||
if (!item) continue;
|
||||
const remaining = Math.max(0, Number(item.quantity) - pull.amount);
|
||||
await tx.item.update({ where: { id: pull.id }, data: { quantity: remaining } });
|
||||
await tx.itemLog.create({
|
||||
data: {
|
||||
itemId: pull.id,
|
||||
type: "NOTE",
|
||||
notes: `Used ${pull.amount} ${item.unit ?? ""}${notes ? ` — ${notes}` : ""}`.trim(),
|
||||
actorId: session.user.id,
|
||||
},
|
||||
});
|
||||
results.push({ id: pull.id, remaining });
|
||||
}
|
||||
|
||||
await tx.auditEvent.create({
|
||||
data: {
|
||||
action: "pantry.bulk-use",
|
||||
actorId: session.user.id,
|
||||
payload: { pulls: pulls.map(p => ({ id: p.id, amount: p.amount })), notes },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return NextResponse.json({ ok: true, results });
|
||||
} catch (err) {
|
||||
if (err instanceof z.ZodError) return NextResponse.json({ error: "Invalid input" }, { status: 400 });
|
||||
if (err instanceof Error && err.message === "Unauthorized") return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
console.error(err);
|
||||
return NextResponse.json({ error: "Server error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user