From aa9746a94c96ce01ab61b29bd57ecb5b1e622ed2 Mon Sep 17 00:00:00 2001 From: avicenan Date: Wed, 18 Mar 2026 14:29:18 +0700 Subject: [PATCH] feat(api)!: add room-based stock to doctor item list --- src/controllers/doctor_item.ts | 81 ++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/src/controllers/doctor_item.ts b/src/controllers/doctor_item.ts index c533458..93d2300 100644 --- a/src/controllers/doctor_item.ts +++ b/src/controllers/doctor_item.ts @@ -1,4 +1,4 @@ -import { DoctorItem, GenericName, ItemMaster, ItemPrice, ItemTypeDetail, User, SellingPricePercentage } from "entity"; +import { DoctorItem, GenericName, ItemMaster, ItemPrice, ItemTypeDetail, User, SellingPricePercentage, PharmacyInfo, RoomToPharmacy } from "entity"; import { NextFunction, Response } from "express"; import { Request } from "express-jwt"; import Joi from "joi"; @@ -20,6 +20,7 @@ export class DoctorItemController { #swagger.tags = ['Doctor Item'] #swagger.security = [{ "bearerAuth": [] }] #swagger.parameters['doctor_id'] = { in: 'query', required: true, type: 'string' } + #swagger.parameters['room_id'] = { in: 'query', required: true, type: 'string' } #swagger.parameters['filter'] = { description: '', in: 'query', type: 'string' } #swagger.parameters['page'] = { in: 'query', required: true, type: 'number' } #swagger.parameters['limit'] = { in: 'query', required: true, type: 'number' } @@ -29,6 +30,7 @@ export class DoctorItemController { try { const schema = Joi.object().keys({ doctor_id: Joi.string().uuid().required().label("Doctor ID"), + room_id: Joi.string().uuid().required().label("Room ID"), filter: Joi.string().allow("").optional().label("Filter"), page: Joi.number().required().min(1).label("Page"), limit: Joi.number().required().min(1).label("Limit"), @@ -45,7 +47,6 @@ export class DoctorItemController { delete filterObj.any; // filterObj = { ...filterObj}; - const repo = OrmHelper.DB.getRepository(ItemMaster); let whereAttr: string[] = []; @@ -75,6 +76,29 @@ export class DoctorItemController { const offset = (param.page - 1) * param.limit; + const pharmacyInfoRow = await OrmHelper.DB + .getRepository(PharmacyInfo) + .createQueryBuilder("pharmacy_info") + .select("pharmacy_info.default_room_id", "default_room_id") + .where("pharmacy_info.deleted_at IS NULL") + .orderBy("pharmacy_info.created_at", "DESC") + .limit(1) + .getRawOne<{ default_room_id: string | null }>(); + + const defaultRoomId = pharmacyInfoRow?.default_room_id ?? null; + + const roomToPharmacyRow = await OrmHelper.DB + .getRepository(RoomToPharmacy) + .createQueryBuilder("room_to_pharmacy") + .select("room_to_pharmacy.pharmacy_room_id", "pharmacy_room_id") + .where("room_to_pharmacy.deleted_at IS NULL") + .andWhere("room_to_pharmacy.room_id = :roomId", { roomId: param.room_id }) + .orderBy("room_to_pharmacy.created_at", "DESC") + .limit(1) + .getRawOne<{ pharmacy_room_id: string | null }>(); + + const pharmacyRoomId = roomToPharmacyRow?.pharmacy_room_id ?? null; + let res_count = repo.createQueryBuilder("item_master") .leftJoin("item_master.generic_name", "generic_name") .leftJoin("item_master.type_detail", "item_type_detail") @@ -113,8 +137,12 @@ export class DoctorItemController { "item_master.item_name as item_name", "generic_name.generic_name as item_generic_name", "item_type_detail.type_detail_name as item_type", - // "pharmacy_stock", - // "storage_stock", + (pharmacyRoomId + ? `COALESCE((SELECT SUM(rs.total_stock)::text FROM pharmacy_logistic.room_stock rs WHERE rs."roomId" = :pharmacyRoomId AND rs."itemmasterId" = item_master.id AND rs.deleted_at IS NULL), '0') as pharmacy_stock` + : "'0' as pharmacy_stock"), + (defaultRoomId + ? `COALESCE((SELECT SUM(rs.total_stock)::text FROM pharmacy_logistic.room_stock rs WHERE rs."roomId" = :defaultRoomId AND rs."itemmasterId" = item_master.id AND rs.deleted_at IS NULL), '0') as storage_stock` + : "'0' as storage_stock"), "ROUND(CASE WHEN selling_price_percentage.percentage IS NULL " + "THEN COALESCE(item_price.selling_price, 0) " + "ELSE COALESCE(item_price.selling_price, 0) + (COALESCE(item_price.selling_price, 0) * COALESCE(selling_price_percentage.percentage, 0) / 100) END, 2) as price", @@ -124,6 +152,13 @@ export class DoctorItemController { .offset(offset) .limit(param.limit) + if (defaultRoomId) { + res_list = res_list.setParameter("defaultRoomId", defaultRoomId); + } + if (pharmacyRoomId) { + res_list = res_list.setParameter("pharmacyRoomId", pharmacyRoomId); + } + const current_page = param.page; const total_count_data = await res_count.getCount(); const list_data = await res_list.getRawMany(); @@ -142,6 +177,7 @@ export class DoctorItemController { #swagger.tags = ['Doctor Item'] #swagger.security = [{ "bearerAuth": [] }] #swagger.parameters['doctor_id'] = { in: 'query', required: true, type: 'string' } + #swagger.parameters['room_id'] = { in: 'query', required: true, type: 'string' } #swagger.parameters['filter'] = { description: '', in: 'query', type: 'string' } #swagger.parameters['page'] = { in: 'query', required: true, type: 'number' } #swagger.parameters['limit'] = { in: 'query', required: true, type: 'number' } @@ -151,6 +187,7 @@ export class DoctorItemController { try { const schema = Joi.object().keys({ doctor_id: Joi.string().uuid().required().label("Doctor ID"), + room_id: Joi.string().uuid().required().label("Room ID"), filter: Joi.string().allow("").optional().label("Filter"), page: Joi.number().required().min(1).label("Page"), limit: Joi.number().required().min(1).label("Limit"), @@ -196,6 +233,29 @@ export class DoctorItemController { const offset = (param.page - 1) * param.limit; + const pharmacyInfoRow = await OrmHelper.DB + .getRepository(PharmacyInfo) + .createQueryBuilder("pharmacy_info") + .select("pharmacy_info.default_room_id", "default_room_id") + .where("pharmacy_info.deleted_at IS NULL") + .orderBy("pharmacy_info.created_at", "DESC") + .limit(1) + .getRawOne<{ default_room_id: string | null }>(); + + const defaultRoomId = pharmacyInfoRow?.default_room_id ?? null; + + const roomToPharmacyRow = await OrmHelper.DB + .getRepository(RoomToPharmacy) + .createQueryBuilder("room_to_pharmacy") + .select("room_to_pharmacy.pharmacy_room_id", "pharmacy_room_id") + .where("room_to_pharmacy.deleted_at IS NULL") + .andWhere("room_to_pharmacy.room_id = :roomId", { roomId: param.room_id }) + .orderBy("room_to_pharmacy.created_at", "DESC") + .limit(1) + .getRawOne<{ pharmacy_room_id: string | null }>(); + + const pharmacyRoomId = roomToPharmacyRow?.pharmacy_room_id ?? null; + let res_count = repo.createQueryBuilder("item_master") .innerJoin( DoctorItem, @@ -234,6 +294,12 @@ export class DoctorItemController { "item_master.item_name as item_name", "generic_name.generic_name as item_generic_name", "item_type_detail.type_detail_name as item_type", + (pharmacyRoomId + ? `COALESCE((SELECT SUM(rs.total_stock)::text FROM pharmacy_logistic.room_stock rs WHERE rs."roomId" = :pharmacyRoomId AND rs."itemmasterId" = item_master.id AND rs.deleted_at IS NULL), '0') as pharmacy_stock` + : "'0' as pharmacy_stock"), + (defaultRoomId + ? `COALESCE((SELECT SUM(rs.total_stock)::text FROM pharmacy_logistic.room_stock rs WHERE rs."roomId" = :defaultRoomId AND rs."itemmasterId" = item_master.id AND rs.deleted_at IS NULL), '0') as storage_stock` + : "'0' as storage_stock"), "ROUND(CASE WHEN selling_price_percentage.percentage IS NULL " + "THEN COALESCE(item_price.selling_price, 0) " + "ELSE COALESCE(item_price.selling_price, 0) + (COALESCE(item_price.selling_price, 0) * COALESCE(selling_price_percentage.percentage, 0) / 100) END, 2) as price", @@ -243,6 +309,13 @@ export class DoctorItemController { .offset(offset) .limit(param.limit); + if (defaultRoomId) { + res_list = res_list.setParameter("defaultRoomId", defaultRoomId); + } + if (pharmacyRoomId) { + res_list = res_list.setParameter("pharmacyRoomId", pharmacyRoomId); + } + const current_page = param.page; const total_count_data = await res_count.getCount(); const list_data = await res_list.getRawMany();