import { CabinetItemModel } from '../../schemas/cabinet-item.schema.js'; import type { CabinetItemStatus, CreateCabinetItemInput, UpdateCabinetItemInput } from '@meshitrack/shared'; interface FindByHouseholdQuery { medicineId?: string; status?: CabinetItemStatus; expiringWithin?: number; cursor?: string; limit: number; } export class CabinetRepository { public async findByHousehold(householdId: string, query: FindByHouseholdQuery) { const filter: Record = { householdId, isDeleted: false }; if (query.medicineId) filter['medicineId'] = query.medicineId; if (query.status) filter['status'] = query.status; if (query.expiringWithin) { const cutoff = new Date(); cutoff.setDate(cutoff.getDate() + query.expiringWithin); filter['expirationDate'] = { $lte: cutoff, $gt: new Date() }; filter['status'] = 'active'; } if (query.cursor) { const id = Buffer.from(query.cursor, 'base64').toString(); filter['_id'] = { $gt: id }; } const limit = query.limit; const items = await CabinetItemModel.find(filter) .sort({ _id: 1 }) .limit(limit + 1) .lean() .exec(); const hasMore = items.length > limit; const data = hasMore ? items.slice(0, limit) : items; const cursor = data.length > 0 ? Buffer.from(data[data.length - 1]._id.toString()).toString('base64') : null; return { data, pagination: { cursor: hasMore ? cursor : null, hasMore } }; } public async findById(id: string, householdId: string) { return CabinetItemModel.findOne({ _id: id, householdId, isDeleted: false }).lean().exec(); } public async getAggregateSummary(householdId: string) { return CabinetItemModel.aggregate([ { $match: { householdId, isDeleted: false, status: 'active' } }, { $group: { _id: '$medicineId', medicineName: { $first: '$medicineName' }, medicineStrength: { $first: '$medicineStrength' }, medicineStrengthUnit: { $first: '$medicineStrengthUnit' }, medicineForm: { $first: '$medicineForm' }, totalQuantity: { $sum: '$quantity' }, unit: { $first: '$unit' }, earliestExpiry: { $min: '$expirationDate' }, itemCount: { $sum: 1 }, }, }, { $sort: { medicineName: 1 } }, ]).exec(); } public async create( data: CreateCabinetItemInput & { medicineName: string; medicineStrength: number; medicineStrengthUnit: string; medicineForm: string; medicineProductBrand?: string; concentration?: number; concentrationUnit?: string; }, householdId: string, createdBy: string, ) { const item = new CabinetItemModel({ ...data, householdId, createdBy }); const saved = await item.save(); return saved.toObject(); } public async update(id: string, householdId: string, data: UpdateCabinetItemInput) { return CabinetItemModel.findOneAndUpdate( { _id: id, householdId, isDeleted: false }, { $set: data }, { new: true, lean: true }, ).exec(); } public async adjustQuantity(id: string, householdId: string, delta: number) { const item = await CabinetItemModel.findOne({ _id: id, householdId, isDeleted: false, }) .lean() .exec(); if (!item) return null; const newQuantity = Math.max(0, item.quantity + delta); const newStatus = newQuantity === 0 ? 'depleted' : item.status === 'depleted' ? 'active' : item.status; return CabinetItemModel.findOneAndUpdate( { _id: id, householdId, isDeleted: false }, { $set: { quantity: newQuantity, status: newStatus } }, { new: true, lean: true }, ).exec(); } public async findExpiringSoon(householdId: string, withinDays: number) { const cutoff = new Date(); cutoff.setDate(cutoff.getDate() + withinDays); return CabinetItemModel.find({ householdId, isDeleted: false, status: 'active', expirationDate: { $lte: cutoff, $gt: new Date() }, }) .sort({ expirationDate: 1 }) .lean() .exec(); } public async countByMedicineId(medicineId: string): Promise { return CabinetItemModel.countDocuments({ medicineId, isDeleted: false }).exec(); } public async softDelete(id: string, householdId: string) { return CabinetItemModel.findOneAndUpdate( { _id: id, householdId, isDeleted: false }, { $set: { isDeleted: true } }, { new: true, lean: true }, ).exec(); } }