import { NextRequest, NextResponse } from "next/server"; import mongoose from "mongoose"; import { connectDb } from "@/lib/server/db"; import { Transaction } from "@/lib/server/models/Transaction"; import { DailyInventory } from "@/lib/server/models/DailyInventory"; import { authorize, badRequest, branchParam } from "@/lib/server/auth"; import { dayRangeNairobi, isBusinessDate, todayNairobi } from "@/lib/server/businessDay"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export async function GET(req: NextRequest) { await connectDb(); const gate = authorize(req); if (!gate.ok) return gate.response; const auth = gate.auth; const scope = branchParam(auth, req.nextUrl.searchParams.get("branchId")); if (!scope.ok) return scope.response; const branchId = scope.branchId; const date = req.nextUrl.searchParams.get("date") || todayNairobi(); if (!isBusinessDate(date)) return badRequest("date must be YYYY-MM-DD"); const invMatch: Record = { date, businessId: new mongoose.Types.ObjectId(auth.businessId) }; if (branchId) invMatch.branchId = new mongoose.Types.ObjectId(branchId); const inventoryRows = await DailyInventory.aggregate([ { $match: invMatch }, // Lookups are tenant-scoped too, so a row can never surface another // tenant's product or branch even if one were referenced by mistake. { $lookup: { from: "products", let: { pid: "$productId", biz: "$businessId" }, pipeline: [{ $match: { $expr: { $and: [{ $eq: ["$_id", "$$pid"] }, { $eq: ["$businessId", "$$biz"] }] } } }], as: "product", }, }, { $unwind: "$product" }, { $lookup: { from: "branches", let: { bid: "$branchId", biz: "$businessId" }, pipeline: [{ $match: { $expr: { $and: [{ $eq: ["$_id", "$$bid"] }, { $eq: ["$businessId", "$$biz"] }] } } }], as: "branch", }, }, { $unwind: "$branch" }, { $project: { _id: 0, branchId: 1, branchName: "$branch.branchName", productId: 1, productLabel: "$product.label", sizeLiters: "$product.sizeLiters", morningDispatched: 1, eveningPhysicalCount: 1, }, }, ]); const { start: dayStart, end: dayEnd } = dayRangeNairobi(date); const soldMatch: Record = { businessId: new mongoose.Types.ObjectId(auth.businessId), createdAt: { $gte: dayStart, $lt: dayEnd }, paymentStatus: "SUCCESS", }; if (branchId) soldMatch.branchId = new mongoose.Types.ObjectId(branchId); const soldRows = await Transaction.aggregate([ { $match: soldMatch }, { $group: { _id: { branchId: "$branchId", productId: "$productId" }, totalSold: { $sum: "$quantity" } } }, ]); const soldMap = new Map( soldRows.map((r) => [r._id.branchId.toString() + ":" + r._id.productId.toString(), r.totalSold as number]) ); const rows = inventoryRows.map((row: any) => { const key = row.branchId.toString() + ":" + row.productId.toString(); const totalSold = soldMap.get(key) ?? 0; const expectedRemaining = row.morningDispatched - totalSold; const reconciledOk = row.eveningPhysicalCount != null ? row.eveningPhysicalCount === expectedRemaining : null; return { ...row, totalSold, expectedRemaining, reconciledOk }; }); return NextResponse.json({ date, rows }); }