143 lines
3.9 KiB
TypeScript
143 lines
3.9 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { ObjectId } from 'mongodb';
|
|
import { getDb } from '@/lib/mongodb';
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const { searchParams } = request.nextUrl;
|
|
const page = Math.max(1, parseInt(searchParams.get('page') || '1'));
|
|
const limit = Math.min(100, Math.max(1, parseInt(searchParams.get('limit') || '10')));
|
|
const search = searchParams.get('search') || '';
|
|
const categoryId = searchParams.get('categoryId');
|
|
const subcategoryId = searchParams.get('subcategoryId');
|
|
|
|
const db = await getDb();
|
|
|
|
const matchStage: Record<string, unknown> = {};
|
|
|
|
if (categoryId) {
|
|
matchStage.categoryId = new ObjectId(categoryId);
|
|
}
|
|
if (subcategoryId) {
|
|
matchStage.subcategoryId = new ObjectId(subcategoryId);
|
|
}
|
|
if (search) {
|
|
matchStage.$or = [
|
|
{ name: { $regex: search, $options: 'i' } },
|
|
{ code: { $regex: search, $options: 'i' } },
|
|
];
|
|
}
|
|
|
|
const pipeline = [
|
|
{ $match: matchStage },
|
|
{
|
|
$lookup: {
|
|
from: 'categories',
|
|
localField: 'categoryId',
|
|
foreignField: '_id',
|
|
as: 'categoryDoc',
|
|
},
|
|
},
|
|
{
|
|
$lookup: {
|
|
from: 'subcategories',
|
|
localField: 'subcategoryId',
|
|
foreignField: '_id',
|
|
as: 'subcategoryDoc',
|
|
},
|
|
},
|
|
{
|
|
$lookup: {
|
|
from: 'suppliers',
|
|
localField: 'supplierId',
|
|
foreignField: '_id',
|
|
as: 'supplierDoc',
|
|
},
|
|
},
|
|
{
|
|
$project: {
|
|
code: 1,
|
|
name: 1,
|
|
category: { $arrayElemAt: ['$categoryDoc.name', 0] },
|
|
subcategory: { $arrayElemAt: ['$subcategoryDoc.name', 0] },
|
|
quantity: 1,
|
|
unit: 1,
|
|
unitPrice: 1,
|
|
vat: 1,
|
|
supplier: { $arrayElemAt: ['$supplierDoc.name', 0] },
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
},
|
|
},
|
|
{ $sort: { createdAt: -1 as const } },
|
|
];
|
|
|
|
const [data, countResult] = await Promise.all([
|
|
db.collection('ingredients')
|
|
.aggregate(pipeline)
|
|
.skip((page - 1) * limit)
|
|
.limit(limit)
|
|
.toArray(),
|
|
db.collection('ingredients')
|
|
.countDocuments(matchStage),
|
|
]);
|
|
|
|
return NextResponse.json({
|
|
data,
|
|
total: countResult,
|
|
page,
|
|
limit,
|
|
});
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
const body = await request.json();
|
|
const { name, categoryId, subcategoryId, quantity, unit, unitPrice, vat, supplierId } = body;
|
|
|
|
if (!name || !categoryId || !subcategoryId || !quantity || !unit || unitPrice == null || vat == null || !supplierId) {
|
|
return NextResponse.json({ error: 'All fields are required' }, { status: 400 });
|
|
}
|
|
|
|
const db = await getDb();
|
|
|
|
// Auto-generate code
|
|
const last = await db.collection('ingredients')
|
|
.find({}, { projection: { code: 1 } })
|
|
.sort({ code: -1 })
|
|
.limit(1)
|
|
.next();
|
|
const lastNum = last?.code ? parseInt(last.code.replace('ING-', '')) : 0;
|
|
const code = `ING-${String(lastNum + 1).padStart(3, '0')}`;
|
|
|
|
const now = new Date();
|
|
const doc: Record<string, unknown> = {
|
|
code,
|
|
name,
|
|
categoryId: new ObjectId(categoryId),
|
|
subcategoryId: new ObjectId(subcategoryId),
|
|
quantity,
|
|
unit,
|
|
unitPrice,
|
|
vat,
|
|
supplierId: new ObjectId(supplierId),
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
|
|
// Optional discount fields
|
|
if (body.discountType) {
|
|
doc.discountType = body.discountType;
|
|
doc.discountValue = body.discountValue ?? 0;
|
|
doc.applyDiscountToNet = body.applyDiscountToNet ?? false;
|
|
}
|
|
|
|
// Optional advanced fields
|
|
if (body.minStockLevel != null) doc.minStockLevel = body.minStockLevel;
|
|
if (body.storageInstructions) doc.storageInstructions = body.storageInstructions;
|
|
if (body.shelfLifeDays != null) doc.shelfLifeDays = body.shelfLifeDays;
|
|
if (body.notes) doc.notes = body.notes;
|
|
|
|
const result = await db.collection('ingredients').insertOne(doc);
|
|
|
|
return NextResponse.json({ _id: result.insertedId, ...doc }, { status: 201 });
|
|
}
|