const WarehouseItem = require('./warehouseItemModel') const WarehouseItemTransaction = require('./warehouseItemTransactionModel') const WarehouseItemStock = require('./warehouseItemStockModel') const AppError = require('../../globalErrorHandler/AppError') const mongoose = require('mongoose') // ─── ITEM CRUD ──────────────────────────────────────────────────────────────── const createItem = async (data) => { const item = await WarehouseItem.create({ nameFa: data.nameFa, nameEn: data.nameEn, code: data.code || undefined, unitFa: data.unitFa, unitEn: data.unitEn, category: data.category, description: data.description, }) return item } const listItems = async (query = {}) => { const page = parseInt(query.page) || 1 const limit = parseInt(query.limit) || 50 const skip = (page - 1) * limit const filter = {} if (query.search) { filter.$or = [ { nameFa: { $regex: query.search, $options: 'i' } }, { nameEn: { $regex: query.search, $options: 'i' } }, { code: { $regex: query.search, $options: 'i' } }, ] } if (query.category) filter.category = query.category if (query.isActive !== undefined) filter.isActive = query.isActive === 'true' const [items, total] = await Promise.all([ WarehouseItem.find(filter).sort({ createdAt: -1 }).skip(skip).limit(limit), WarehouseItem.countDocuments(filter), ]) return { items, total, page, limit, pages: Math.ceil(total / limit) } } const getItem = async (itemId) => { const item = await WarehouseItem.findById(itemId) if (!item) throw new AppError('آیتم انبار یافت نشد', 404, {}) return item } const updateItem = async (itemId, data) => { const allowed = ['nameFa', 'nameEn', 'code', 'unitFa', 'unitEn', 'category', 'description', 'isActive'] const update = {} allowed.forEach((field) => { if (data[field] !== undefined) update[field] = data[field] }) const item = await WarehouseItem.findByIdAndUpdate(itemId, update, { new: true, runValidators: true }) if (!item) throw new AppError('آیتم انبار یافت نشد', 404, {}) return item } const deleteItem = async (itemId) => { const item = await WarehouseItem.findByIdAndUpdate(itemId, { isActive: false }, { new: true }) if (!item) throw new AppError('آیتم انبار یافت نشد', 404, {}) return item } // ─── TRANSACTIONS ───────────────────────────────────────────────────────────── const createTransaction = async (data, user) => { const { itemId, warehouseId, type, quantity, reason, note, date } = data if (!itemId || !warehouseId || !type || !quantity || !reason) { throw new AppError('اطلاعات ناقص است', 400, {}) } const item = await WarehouseItem.findById(itemId) if (!item) throw new AppError('آیتم انبار یافت نشد', 404, {}) if (!item.isActive) throw new AppError('این آیتم غیرفعال است', 400, {}) const qty = parseFloat(quantity) if (isNaN(qty) || qty <= 0) throw new AppError('مقدار باید عدد مثبت باشد', 400, {}) // Prevent negative stock on OUT if (type === 'OUT') { const stock = await WarehouseItemStock.findOne({ item: itemId, warehouse: warehouseId }) const currentQty = stock ? stock.quantity : 0 if (currentQty < qty) { throw new AppError( `موجودی کافی نیست. موجودی فعلی: ${currentQty} ${item.unitFa}`, 400, { currentQuantity: currentQty, requestedQuantity: qty } ) } } // Create the transaction record const transaction = await WarehouseItemTransaction.create({ item: itemId, warehouse: warehouseId, type, quantity: qty, unit: item.unitFa, unitEn: item.unitEn, reason, actor: user._id, actorName: user.name, note, date: date || Date.now(), }) // Update the stock snapshot atomically const delta = type === 'IN' ? qty : -qty await WarehouseItemStock.findOneAndUpdate( { item: itemId, warehouse: warehouseId }, { $inc: { quantity: delta }, $set: { unit: item.unitFa, unitEn: item.unitEn }, }, { upsert: true, new: true } ) return transaction } const listTransactions = async (query = {}) => { const page = parseInt(query.page) || 1 const limit = parseInt(query.limit) || 50 const skip = (page - 1) * limit const filter = {} if (query.itemId) filter.item = query.itemId if (query.warehouseId) filter.warehouse = query.warehouseId if (query.type) filter.type = query.type if (query.actorId) filter.actor = query.actorId if (query.dateFrom || query.dateTo) { filter.date = {} if (query.dateFrom) filter.date.$gte = new Date(query.dateFrom) if (query.dateTo) filter.date.$lte = new Date(query.dateTo) } const [transactions, total] = await Promise.all([ WarehouseItemTransaction.find(filter) .sort({ date: -1 }) .skip(skip) .limit(limit) .populate('item', 'nameFa nameEn code unitFa unitEn') .populate('warehouse', 'name code location') .populate('actor', 'name email'), WarehouseItemTransaction.countDocuments(filter), ]) return { transactions, total, page, limit, pages: Math.ceil(total / limit) } } const deleteTransaction = async (transactionId) => { const transaction = await WarehouseItemTransaction.findById(transactionId) if (!transaction) throw new AppError('تراکنش یافت نشد', 404, {}) // Reverse the stock effect const delta = transaction.type === 'IN' ? -transaction.quantity : transaction.quantity // Check if reversal would cause negative stock (for IN reversals) if (transaction.type === 'IN') { const stock = await WarehouseItemStock.findOne({ item: transaction.item, warehouse: transaction.warehouse, }) const currentQty = stock ? stock.quantity : 0 if (currentQty < transaction.quantity) { throw new AppError( 'حذف این تراکنش ممکن نیست: موجودی کافی برای برگشت وجود ندارد', 400, { currentQuantity: currentQty } ) } } await WarehouseItemStock.findOneAndUpdate( { item: transaction.item, warehouse: transaction.warehouse }, { $inc: { quantity: delta } } ) await WarehouseItemTransaction.findByIdAndDelete(transactionId) return null } // ─── STOCK QUERIES ──────────────────────────────────────────────────────────── const getStockByItem = async (itemId) => { const item = await WarehouseItem.findById(itemId) if (!item) throw new AppError('آیتم انبار یافت نشد', 404, {}) const stocks = await WarehouseItemStock.find({ item: itemId }) .populate('warehouse', 'name code location description') .sort({ quantity: -1 }) const totalQuantity = stocks.reduce((sum, s) => sum + s.quantity, 0) return { item, stocks, totalQuantity, unit: item.unitFa, unitEn: item.unitEn, } } const getStockByWarehouse = async (warehouseId) => { const stocks = await WarehouseItemStock.find({ warehouse: warehouseId }) .populate('item', 'nameFa nameEn code unitFa unitEn category isActive') .sort({ quantity: -1 }) return stocks } const getTotalStock = async () => { const result = await WarehouseItemStock.aggregate([ { $group: { _id: '$item', totalQuantity: { $sum: '$quantity' }, warehouseCount: { $sum: 1 }, unit: { $first: '$unit' }, unitEn: { $first: '$unitEn' }, }, }, { $lookup: { from: 'warehouseitems', localField: '_id', foreignField: '_id', as: 'item', }, }, { $unwind: '$item' }, { $match: { 'item.isActive': true } }, { $project: { _id: 0, item: { _id: '$item._id', nameFa: '$item.nameFa', nameEn: '$item.nameEn', code: '$item.code', category: '$item.category' }, totalQuantity: 1, warehouseCount: 1, unit: 1, unitEn: 1, }, }, { $sort: { totalQuantity: -1 } }, ]) return result } module.exports = { createItem, listItems, getItem, updateItem, deleteItem, createTransaction, listTransactions, deleteTransaction, getStockByItem, getStockByWarehouse, getTotalStock, }