import { StockOpnameLocationRes, StockOpnameRequest } from "@/common/types/stockOpname"; import { Prisma, Team } from "prisma/generated/client"; import { NextRequest, NextResponse } from "next/server"; import { prisma } from "prisma/client"; import _ from "lodash"; import getServerCredential from "@/common/libs/getServerCredential"; type Quantity = { [key in keyof typeof Team]: number | null; }; export async function GET(request: NextRequest) { const PAGE_SIZE = 30; const params = request.nextUrl.searchParams; const companyId = params.get("companyId"); const search = params.get("search"); const page = params.get("page") ?? null; const show = params.get("show"); const intPage = page ? parseInt(page) : 1; if (!companyId) { return NextResponse.json({ error: "Bad Request. Missing companyId" }, { status: 400 }); } const where: Prisma.ProductWhereInput = { AND: { stockOpnames: { some: {} }, companyId: parseInt(companyId), isDifferent: show ? (show == "diff" ? true : false) : undefined, OR: [ { name: { mode: "insensitive", contains: search ?? "" } }, { itemCode: { mode: "insensitive", contains: search ?? "" } }, { barcode: { mode: "insensitive", contains: search ?? "" } }, { stockOpnames: { some: { location: { name: { mode: "insensitive", contains: search ?? "" } } } } }, ], }, }; const products = await prisma.product.findMany({ skip: (intPage - 1) * PAGE_SIZE, take: PAGE_SIZE, where, }); const productCount = await prisma.product.count({ where }); const pagination = { page: intPage, totalPage: Math.ceil(productCount / PAGE_SIZE), }; type ProductWithSum = (typeof products)[0] & { quantity: Quantity }; const productsWithSum: ProductWithSum[] = []; for (const product of products) { const quantity = await calculateOpnameQuantity({ productId: product.id, companyId: parseInt(companyId), }); productsWithSum.push({ ...product, quantity }); } return NextResponse.json({ result: productsWithSum, ...pagination, }); } const calculateOpnameQuantity = async (where: { productId: number; companyId: number }): Promise => { const quantity: Quantity = { COUNT1: null, COUNT2: null, COUNT3: null, VERIFICATION: null }; for (const team of Object.values(Team)) { const opnameQty = await prisma.stockOpname.groupBy({ by: ["productId", "team"], _sum: { quantity: true }, where: { team, ...where }, }); if (opnameQty.length === 0) continue; quantity[team] = opnameQty[0]._sum.quantity; } return quantity; }; export async function POST(request: NextRequest) { const credential = getServerCredential(); if (!credential) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const body: StockOpnameRequest = await request.json(); const { companyId, team } = credential; const query = { locationId: body.location, productId: body.product, companyId, team, }; const stockOpname = await prisma.stockOpname.findFirst({ where: query }); const data = { ...query, userId: credential.id, quantity: body.quantity, isDifferent: false, }; let newStockOpname = null; if (!stockOpname) { newStockOpname = await prisma.stockOpname.create({ data }); } else { newStockOpname = await prisma.stockOpname.update({ where: { id: stockOpname.id }, data, }); } await computeIsDifferent({ productId: body.product, companyId: companyId }); return NextResponse.json(newStockOpname); } const SELF_HOST = process.env.SELF_HOST as string; const computeIsDifferent = async ({ companyId, productId }: { companyId: number; productId: number }) => { const totalQty: { [key in keyof typeof Team]: number | 0 } = { COUNT1: 0, COUNT2: 0, COUNT3: 0, VERIFICATION: 0, }; const searchParams = new URLSearchParams({ companyId: companyId.toString(), productId: productId.toString(), }); const stockOpnamesFetch = await fetch(`${SELF_HOST}/api/stock-opname/location?${searchParams}`); const stockOpnames: StockOpnameLocationRes[] = await stockOpnamesFetch.json(); let isDifferent: boolean = false; let verificationCounter: number = 0; for (const opname of stockOpnames) { let { COUNT1, COUNT2, COUNT3, VERIFICATION } = opname; if (totalQty["COUNT1"] === null && _.isNumber(COUNT1.quantity)) totalQty["COUNT1"] = 0; if (totalQty["COUNT2"] === null && _.isNumber(COUNT2.quantity)) totalQty["COUNT2"] = 0; if (totalQty["COUNT3"] === null && _.isNumber(COUNT3.quantity)) totalQty["COUNT3"] = 0; if (totalQty["VERIFICATION"] === null && _.isNumber(VERIFICATION.quantity)) totalQty["VERIFICATION"] = 0; if (_.isNumber(totalQty["COUNT1"]) && _.isNumber(COUNT1.quantity)) totalQty["COUNT1"] += COUNT1.quantity; if (_.isNumber(totalQty["COUNT2"]) && _.isNumber(COUNT2.quantity)) totalQty["COUNT2"] += COUNT2.quantity; if (_.isNumber(totalQty["COUNT3"]) && _.isNumber(COUNT3.quantity)) totalQty["COUNT3"] += COUNT3.quantity; if (_.isNumber(totalQty["VERIFICATION"]) && _.isNumber(VERIFICATION.quantity)) totalQty["VERIFICATION"] += VERIFICATION.quantity; if (_.isNumber(VERIFICATION.quantity)) verificationCounter++; } const product = await prisma.product.findFirst({ where: { id: productId } }); if (!product) return; const onhandQty = product.onhandQty; const allQty = totalQty["COUNT1"] + totalQty["COUNT2"] + totalQty["COUNT3"] + totalQty["VERIFICATION"]; const differenceQty = onhandQty - allQty; // const differenceQty = product.differenceQty // const allQty = onhandQty + differenceQty const zeroCount1: boolean = totalQty["COUNT1"] === 0; const zeroCount2: boolean = totalQty["COUNT2"] === 0; const zeroCount3: boolean = totalQty["COUNT3"] === 0; // Jika ada verifikasi qty, langsung AMAN if (verificationCounter > 0) { isDifferent = false; } else { // Jika tidak ada qty verifikasi, cek kondisi tim 1/2/3 const conditional = { // anyCountEqWithOnhand: [totalQty['COUNT1'], totalQty['COUNT2'], totalQty['COUNT3']].includes(onhandQty), anyCountEqWithOnhand: (totalQty["COUNT1"] === onhandQty && zeroCount2 && zeroCount3) || (totalQty["COUNT2"] === onhandQty && zeroCount3) || (totalQty["COUNT3"] === onhandQty), anyCountEqWithAllQty: [totalQty["COUNT1"], totalQty["COUNT2"], totalQty["COUNT3"]].includes(allQty), count1EqWithCount2: totalQty["COUNT1"] !== null && totalQty["COUNT2"] !== null && totalQty["COUNT1"] === totalQty["COUNT2"] && totalQty["COUNT3"] === null, count1EqWithCount3: totalQty["COUNT1"] !== null && totalQty["COUNT3"] !== null && totalQty["COUNT1"] === totalQty["COUNT3"], count2EqWithCount3: totalQty["COUNT2"] !== 0 && totalQty["COUNT3"] !== 0 && totalQty["COUNT2"] === totalQty["COUNT3"], }; if (conditional.anyCountEqWithOnhand || conditional.count1EqWithCount2 || conditional.count1EqWithCount3 || conditional.count2EqWithCount3) { isDifferent = false; } else { isDifferent = true; } } for (const opname of stockOpnames) { let { COUNT1, COUNT2, COUNT3, VERIFICATION } = opname; // Normalize: treat missing (null/undefined) same as 0 const count1 = _.isNumber(COUNT1.quantity) ? COUNT1.quantity : 0; const count2 = _.isNumber(COUNT2.quantity) ? COUNT2.quantity : 0; const count3 = _.isNumber(COUNT3.quantity) ? COUNT3.quantity : 0; const detailCondition = { verificationCheckAll: _.isNumber(VERIFICATION.quantity), anyCountEqWithOnhand: (totalQty["COUNT1"] === onhandQty && zeroCount2 && zeroCount3) || (totalQty["COUNT2"] === onhandQty && zeroCount3) || (totalQty["COUNT3"] === onhandQty), count1EqWithCount2: totalQty["COUNT3"] === 0 && count1 === count2, // count1EqWithCount3: count1 === count3, count1EqWithCount3: count1 != 0 && count1 === count3, count2EqWithCount3: totalQty["COUNT2"] !== 0 && totalQty["COUNT3"] !== 0 && count2 === count3, // count3EqWithCount1_2: (COUNT3.quantity !== COUNT1.quantity) && (COUNT3.quantity !== COUNT2.quantity) && (COUNT1.quantity === COUNT2.quantity) && (COUNT3.quantity !== onhandQty) }; if (detailCondition.verificationCheckAll || detailCondition.anyCountEqWithOnhand || detailCondition.count1EqWithCount2 || detailCondition.count1EqWithCount3 || detailCondition.count2EqWithCount3) { isDifferent = false; } else { isDifferent = true; break; } } await prisma.product.update({ where: { id: product.id }, data: { isDifferent }, }); };