From 3a285a52baeecf76162c390e8c9deeecd5156162 Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Tue, 7 Apr 2026 17:22:06 +0700 Subject: [PATCH 1/7] update: add subdistrict, city, province, ward --- src/helpers/orm.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/orm.ts b/src/helpers/orm.ts index 3f05c86..71d99ee 100644 --- a/src/helpers/orm.ts +++ b/src/helpers/orm.ts @@ -1,7 +1,7 @@ import config from 'config'; import { DataSource } from "typeorm"; import { ILogObj, Logger } from 'tslog'; -import { RefferalHospital,RegistrationFee,FareType, RoomStock, Administrativu, Aldeia, District, Menu, Munisipo, Suco, Application, Nationality, Diagnosis, DiagnosticProcedure, Room, ServiceType, ServiceClass, Service, ServiceFare, ServicePackage, Department, DoctorSchedule, MeasurementUnit, ReferenceRange, QueueMonitoring, ExaminationDetail, ExaminationType, User, UserRole, HrmsEmployee, HrmsDepartment, HrmsPosition, HrmsShift, HospitalInformation, ItemOrigin, ItemType, ItemTypeDetail, Unit, ItemCategory, ItemStatus, UsageInstructions, UsageTime, ItemClass, ItemClassDetail, GenericName, Factory, Supplier, FactoryToSupplier, ItemMaster, ItemGroup, PharmacyInfo, ItemPrice, SellingPricePercentage, PatientGroup, PatientGuarantor, InitialStock, DoctorItem, DoctorItemPackage, UserToRoom, RoomToPharmacy, PlanningVerificator, ScheduleSeries, ScheduleException, CardType, PaymentMethod } from 'entity' +import { Province,City,Subdistrict,Ward, RefferalHospital,RegistrationFee,FareType, RoomStock, Administrativu, Aldeia, District, Menu, Munisipo, Suco, Application, Nationality, Diagnosis, DiagnosticProcedure, Room, ServiceType, ServiceClass, Service, ServiceFare, ServicePackage, Department, DoctorSchedule, MeasurementUnit, ReferenceRange, QueueMonitoring, ExaminationDetail, ExaminationType, User, UserRole, HrmsEmployee, HrmsDepartment, HrmsPosition, HrmsShift, HospitalInformation, ItemOrigin, ItemType, ItemTypeDetail, Unit, ItemCategory, ItemStatus, UsageInstructions, UsageTime, ItemClass, ItemClassDetail, GenericName, Factory, Supplier, FactoryToSupplier, ItemMaster, ItemGroup, PharmacyInfo, ItemPrice, SellingPricePercentage, PatientGroup, PatientGuarantor, InitialStock, DoctorItem, DoctorItemPackage, UserToRoom, RoomToPharmacy, PlanningVerificator, ScheduleSeries, ScheduleException, CardType, PaymentMethod } from 'entity' export class OrmHelper { static DB: DataSource = null @@ -54,7 +54,7 @@ export class OrmHelper { CardType, PaymentMethod, RegistrationFee, - RefferalHospital + RefferalHospital,Province,City,Subdistrict,Ward ], subscribers: [], migrations: [], From fb8c3525673d464130d2c689e9a71458e45ee2bd Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Wed, 8 Apr 2026 09:29:51 +0700 Subject: [PATCH 2/7] update: add option for region --- src/controllers/region.ts | 164 ++++++++++++++++++++++++++++++++++++++ src/routes/private.ts | 8 +- 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/controllers/region.ts diff --git a/src/controllers/region.ts b/src/controllers/region.ts new file mode 100644 index 0000000..68d7d1d --- /dev/null +++ b/src/controllers/region.ts @@ -0,0 +1,164 @@ +import { Response, NextFunction, Application } from "express"; +import { Request } from "express-jwt"; +import { ReturnHelper } from "../helpers/express/return"; +import { OrmHelper } from "../helpers/orm"; +import Joi from "joi"; +import { ILogObj, Logger } from "tslog"; +import { Language } from "../langs/lang"; +import { Paging } from "entity"; +import CommonHelper from "../helpers/common"; +import * as fastcsv from 'fast-csv'; +import dayjs from "dayjs"; +import { Province, City, Subdistrict, Ward } from "entity"; + +const log: Logger = new Logger({ name: '[RegionController]', type: 'pretty' }); + +export class RegionController { + static async province(req: Request, res: Response, next: NextFunction): Promise { + /* + #swagger.tags = ['Region'] + #swagger.security = [{ + "bearerAuth": [] + }] + */ + + try { + const repo = OrmHelper.DB.getRepository(Province); + + const res_count = repo.createQueryBuilder("province"); + const res_list = repo.createQueryBuilder("province"); + + const current_page = 1; + const total_count_data = await res_count.getCount(); + const list_data = await res_list.getMany(); + const count_data = CommonHelper.countObject(list_data); + + return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data); + } catch (e: unknown) { + log.error(e); + const err = e as Error; + + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message); + } + } + + static async city(req: Request, res: Response, next: NextFunction): Promise { + /* + #swagger.tags = ['Region'] + #swagger.security = [{ + "bearerAuth": [] + }] + #swagger.parameters['province_code'] = { + in: 'path', + description: 'Province Code.', + required: true, + type: 'string' + } + */ + + try { + const { province_code } = req.params; + + if (!/^\d{2}$/.test(province_code)) { + return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, 0, 1, 0, []); + } + + const repo = OrmHelper.DB.getRepository(City); + const res_count = repo.createQueryBuilder("city") + .where("city.code LIKE :province_code", { province_code: `${province_code}%` }); + const res_list = repo.createQueryBuilder("city") + .where("city.code LIKE :province_code", { province_code: `${province_code}%` }); + + const current_page = 1; + const total_count_data = await res_count.getCount(); + const list_data = await res_list.getMany(); + const count_data = CommonHelper.countObject(list_data); + + return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data); + } catch (e: unknown) { + log.error(e); + const err = e as Error; + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message); + } + } + + static async subdistric(req: Request, res: Response, next: NextFunction): Promise { + /* + #swagger.tags = ['Region'] + #swagger.security = [{ + "bearerAuth": [] + }] + #swagger.parameters['city_code'] = { + in: 'path', + description: 'City Code.', + required: true, + type: 'string' + } + */ + + try { + const { city_code } = req.params; + + if (!/^\d{2}\.\d{2}$/.test(city_code)) { + return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, 0, 1, 0, []); + } + + const repo = OrmHelper.DB.getRepository(Subdistrict); + const res_count = repo.createQueryBuilder("subdistrict") + .where("subdistrict.code LIKE :city_code", { city_code: `${city_code}%` }); + const res_list = repo.createQueryBuilder("subdistrict") + .where("subdistrict.code LIKE :city_code", { city_code: `${city_code}%` }); + + const current_page = 1; + const total_count_data = await res_count.getCount(); + const list_data = await res_list.getMany(); + const count_data = CommonHelper.countObject(list_data); + + return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data); + } catch (e: unknown) { + log.error(e); + const err = e as Error; + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message); + } + } + + static async ward(req: Request, res: Response, next: NextFunction): Promise { + /* + #swagger.tags = ['Region'] + #swagger.security = [{ + "bearerAuth": [] + }] + #swagger.parameters['subdistric_code'] = { + in: 'path', + description: 'Subdistric Code.', + required: true, + type: 'string' + } + */ + + try { + const { subdistric_code } = req.params; + + if (!/^\d{2}\.\d{2}\.\d{2}$/.test(subdistric_code)) { + return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, 0, 1, 0, []); + } + + const repo = OrmHelper.DB.getRepository(Ward); + const res_count = repo.createQueryBuilder("ward") + .where("ward.code LIKE :subdistric_code", { subdistric_code: `${subdistric_code}%` }); + const res_list = repo.createQueryBuilder("ward") + .where("ward.code LIKE :subdistric_code", { subdistric_code: `${subdistric_code}%` }); + + const current_page = 1; + const total_count_data = await res_count.getCount(); + const list_data = await res_list.getMany(); + const count_data = CommonHelper.countObject(list_data); + + return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data); + } catch (e: unknown) { + log.error(e); + const err = e as Error; + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message); + } + } +} \ No newline at end of file diff --git a/src/routes/private.ts b/src/routes/private.ts index 7a61655..dff3759 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -63,6 +63,7 @@ import { FareTypeController } from '../controllers/faretype'; import { CardTypeController } from '../controllers/card_type'; import { PaymentMethodController } from '../controllers/payment-method'; import { RegistrationFeeController } from '../controllers/registrationfee'; +import { RegionController } from '../controllers/region'; export class RoutePrivate { static setup(app: express.Application) { @@ -480,9 +481,14 @@ export class RoutePrivate { app.put('/api/payment-method/update/:id', PaymentMethodController.update) app.delete('/api/payment-method/delete/:id/:hard', PaymentMethodController.delete) app.put('/api/payment-method/restore/:id', PaymentMethodController.restore) - + app.get('/api/registration-fee/detail', RegistrationFeeController.detail) app.post('/api/registration-fee/create', RegistrationFeeController.create) app.put('/api/registration-fee/update/:id', RegistrationFeeController.update) + + app.get('/api/region/province', RegionController.province) + app.get('/api/region/city/:province_code', RegionController.city) + app.get('/api/region/subdistrict/:city_code', RegionController.subdistric) + app.get('/api/region/ward/:subdistric_code', RegionController.ward) } } From a67078580fb6d3f8b94aad0c6f17992373dbafb0 Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Wed, 8 Apr 2026 09:58:48 +0700 Subject: [PATCH 3/7] update : add region in master data --- src/controllers/pharmacy/pharmacy_info.ts | 119 ++++++++++++---------- src/controllers/region.ts | 2 +- src/model/pharmacy_info.ts | 6 +- src/routes/private.ts | 2 +- src/swagger/builder.js | 4 + 5 files changed, 77 insertions(+), 56 deletions(-) diff --git a/src/controllers/pharmacy/pharmacy_info.ts b/src/controllers/pharmacy/pharmacy_info.ts index c695638..e93b22a 100644 --- a/src/controllers/pharmacy/pharmacy_info.ts +++ b/src/controllers/pharmacy/pharmacy_info.ts @@ -79,28 +79,28 @@ export class PharmacyInfoController { } static async detail(req: Request, res: Response, next: NextFunction): Promise { - /* - #swagger.tags = ['Pharmacy - PharmacyInfo'] - #swagger.security = [{ "bearerAuth": [] }] - #swagger.parameters['id'] = { description: '', in: 'path', type: 'string' } - */ - try { - const schema = Joi.object().keys({ - id: Joi.string().uuid().required().label("Pharmacy Info Id"), - }); - - let param: any = await schema.validateAsync(req.params); - - let data = await PharmacyInfoModel.list({ "id": param.id }).then((q) => q.getOne()); - if (!data) throw { message: "Pharmacy Info Not Found" }; - - return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, data); - } catch (e: unknown) { - log.error(e); - const err = e as Error; - return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_not_found, err.message); - } + /* + #swagger.tags = ['Pharmacy - PharmacyInfo'] + #swagger.security = [{ "bearerAuth": [] }] + #swagger.parameters['id'] = { description: '', in: 'path', type: 'string' } + */ + try { + const schema = Joi.object().keys({ + id: Joi.string().uuid().required().label("Pharmacy Info Id"), + }); + + let param: any = await schema.validateAsync(req.params); + + let data = await PharmacyInfoModel.list({ "id": param.id }).then((q) => q.getOne()); + if (!data) throw { message: "Pharmacy Info Not Found" }; + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, data); + } catch (e: unknown) { + log.error(e); + const err = e as Error; + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_not_found, err.message); } + } static async export(req: Request, res: Response, next: NextFunction): Promise { /* @@ -144,7 +144,7 @@ export class PharmacyInfoController { var query = await PharmacyInfoModel.list(filter); const data = await query.getMany(); - + const filename = "pharmacy_info.csv"; res.setHeader('Content-Type', 'text/csv'); @@ -250,6 +250,11 @@ export class PharmacyInfoController { .uuid() .required() .label('Default Room'), + + province: Joi.string().allow("").optional().label("Province Code"), + city: Joi.string().allow("").optional().label("City Code"), + subdistrict: Joi.string().allow("").optional().label("Subdistrict Code"), + ward: Joi.string().allow("").optional().label("Ward Code"), }); const param: any = await schema.validateAsync(req.body); @@ -264,7 +269,11 @@ export class PharmacyInfoController { data.phone = param.phone data.default_room = param.default_room_id data.logo = param.logo - data.created_by = req.auth.data.name; + data.province = param.province + data.city = param.city + data.subdistrict = param.subdistrict + data.ward = param.ward + data.created_by = req.auth?.data.name; await OrmHelper.DB.manager.save(data); @@ -369,7 +378,11 @@ export class PharmacyInfoController { data.phone = param.phone data.default_room = param.default_room_id data.logo = param.logo - data.updated_by = req.auth.data.name; + data.province = param.province + data.city = param.city + data.subdistrict = param.subdistrict + data.ward = param.ward + data.updated_by = req.auth?.data.name; await repo.save(data); @@ -416,7 +429,7 @@ export class PharmacyInfoController { const existData = await repo.findOne({ where: { id: param.id } }); if (existData && !param.hard) { - existData.deleted_by = req.auth.data.name; + existData.deleted_by = req.auth?.data.name; await OrmHelper.DB.manager.save(existData); } @@ -625,33 +638,33 @@ export class PharmacyInfoController { } static async view(req: Request, res: Response, next: NextFunction): Promise { - /* - #swagger.tags = ['Pharmacy - PharmacyInfo'] - #swagger.security = [{ "bearerAuth": [] }] - */ - try { - const data = await OrmHelper.DB - .getRepository(PharmacyInfo) - .findOne({ - where: {} - }); - - if (!data) return ReturnHelper.errorResponse(res, 404, 404, Language.lang.failed_view, "data not found"); - - const result = { - id: data.id, - name: data.name, - address: data.address, - phone: data.phone, - logo: 'http://his.shiblysolution.id:3011/service-master-data/uploads/hospital-logo/' + data.logo - }; - - return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, result); - - } catch (e: unknown) { - log.error(e); - const err = e as Error; - return ReturnHelper.errorResponse(res,400,401,Language.lang.failed_view,err.message); - } + /* + #swagger.tags = ['Pharmacy - PharmacyInfo'] + #swagger.security = [{ "bearerAuth": [] }] + */ + try { + const data = await OrmHelper.DB + .getRepository(PharmacyInfo) + .findOne({ + where: {} + }); + + if (!data) return ReturnHelper.errorResponse(res, 404, 404, Language.lang.failed_view, "data not found"); + + const result = { + id: data.id, + name: data.name, + address: data.address, + phone: data.phone, + logo: 'http://his.shiblysolution.id:3011/service-master-data/uploads/hospital-logo/' + data.logo + }; + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, result); + + } catch (e: unknown) { + log.error(e); + const err = e as Error; + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message); } + } } \ No newline at end of file diff --git a/src/controllers/region.ts b/src/controllers/region.ts index 68d7d1d..ccc5156 100644 --- a/src/controllers/region.ts +++ b/src/controllers/region.ts @@ -82,7 +82,7 @@ export class RegionController { } } - static async subdistric(req: Request, res: Response, next: NextFunction): Promise { + static async subdistrict(req: Request, res: Response, next: NextFunction): Promise { /* #swagger.tags = ['Region'] #swagger.security = [{ diff --git a/src/model/pharmacy_info.ts b/src/model/pharmacy_info.ts index 4183138..781b2dc 100644 --- a/src/model/pharmacy_info.ts +++ b/src/model/pharmacy_info.ts @@ -18,7 +18,11 @@ export class PharmacyInfoModel { .leftJoinAndSelect("pharmacy_info.munisipiu", "munisipiu") .leftJoinAndSelect("pharmacy_info.postu_admin", "postu_admin") .leftJoinAndSelect("pharmacy_info.suco", "suco") - .leftJoinAndSelect("pharmacy_info.aldeia", "aldeia"); + .leftJoinAndSelect("pharmacy_info.aldeia", "aldeia") + .leftJoinAndSelect("pharmacy_info.province", "province") + .leftJoinAndSelect("pharmacy_info.city", "city") + .leftJoinAndSelect("pharmacy_info.subdistrict", "subdistrict") + .leftJoinAndSelect("pharmacy_info.ward", "ward"); if (whereAttr.length != 0) { query = query.where(whereAttr.join(" and "), whereVal); } diff --git a/src/routes/private.ts b/src/routes/private.ts index dff3759..0e582fa 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -488,7 +488,7 @@ export class RoutePrivate { app.get('/api/region/province', RegionController.province) app.get('/api/region/city/:province_code', RegionController.city) - app.get('/api/region/subdistrict/:city_code', RegionController.subdistric) + app.get('/api/region/subdistrict/:city_code', RegionController.subdistrict) app.get('/api/region/ward/:subdistric_code', RegionController.ward) } } diff --git a/src/swagger/builder.js b/src/swagger/builder.js index e39254f..fd25c0e 100644 --- a/src/swagger/builder.js +++ b/src/swagger/builder.js @@ -319,6 +319,10 @@ const doc = { $aldeia: "Aldeia ID (UUID)", $phone: "Phone (string, max 30, optional)", $default_room_id: "Default Room ID (UUID)", + $province: "Province Code", + $city: "City Code", + $subdistrict: "Subdistrict Code", + $ward: "Ward Code", }, item_price: { $item_master_id: "Item Master ID (required, UUID)", From b02a7f966e270f8243325453f888fa02ea85c650 Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Thu, 9 Apr 2026 15:04:01 +0700 Subject: [PATCH 4/7] update: list in user to room --- src/controllers/user_to_room.ts | 256 +++++++++++++++++++------------- 1 file changed, 150 insertions(+), 106 deletions(-) diff --git a/src/controllers/user_to_room.ts b/src/controllers/user_to_room.ts index a475b03..868ae93 100644 --- a/src/controllers/user_to_room.ts +++ b/src/controllers/user_to_room.ts @@ -44,7 +44,7 @@ export class UserToRoomController { const offset = (param.page - 1) * param.limit; let limit = param.limit; - const res_count = query; + const res_count = query.clone(); const res_list = query .orderBy("user_to_room." + param.order_field, param.order_direction) .offset(offset) @@ -58,9 +58,53 @@ export class UserToRoomController { const current_page = param.page; const total_count_data = await res_count.getCount(); const list_data = await res_list.getMany(); - const count_data = CommonHelper.countObject(list_data); - return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data); + // Transform data: Group by user, room dijadikan object berindeks + const grouped: Record = {}; + + list_data.forEach(item => { + if (!item.user) return; + + const userId = item.user.id; + + // Inisialisasi user jika belum ada + if (!grouped[userId]) { + grouped[userId] = { + id: item.user.id, + name: item.user.name, + username: item.user.username, + email: item.user.email, + status: item.user.status, + rooms: {} + }; + } + + // Hitung index room berikutnya + const roomIndex = Object.keys(grouped[userId].rooms).length + 1; + + // Tambahkan room ke user + if (item.room) { + grouped[userId].rooms[`room_${roomIndex}`] = { + id: item.room.id, + room: item.room.room, + code: item.room.code, + }; + } + }); + + const list_data_return = Object.values(grouped); + + const count_data = CommonHelper.countObject(list_data_return); + + return ReturnHelper.successResponselist( + res, + 200, + Language.lang.success_view, + count_data, + current_page, + total_count_data, + list_data_return + ); } catch (e: unknown) { log.error(e); const err = e as Error; @@ -110,7 +154,7 @@ export class UserToRoomController { var query = await UserToRoomModel.list(filter); const data = await query.getMany(); - + const filename = "user_to_room.csv"; res.setHeader('Content-Type', 'text/csv'); @@ -173,15 +217,15 @@ export class UserToRoomController { try { const schema = Joi.object().keys({ user_id: Joi.string() - .uuid() - .required() - .label("User ID"), + .uuid() + .required() + .label("User ID"), room_id: Joi.string() - .uuid() - .required() - .label("Room ID"), - + .uuid() + .required() + .label("Room ID"), + // room_ids: Joi.array() // .items(Joi.string().uuid().required()) // .min(1) @@ -205,7 +249,7 @@ export class UserToRoomController { const data = new UserToRoom() data.user = param.user_id; data.room = param.room_id; - data.created_by = req.auth.data.name; + data.created_by = req.auth?.data.name; await OrmHelper.DB.manager.save(data); @@ -234,117 +278,117 @@ export class UserToRoomController { } static async update(req: Request, res: Response, next: NextFunction): Promise { - /* - #swagger.tags = ['UserToRoom'] - #swagger.security = [{ - "bearerAuth": [] - }] - #swagger.parameters['user_id'] = { - in: 'path', - description: 'User ID.', - required: true, - type: 'string' - } - - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/user_to_room_update" - } - } + /* + #swagger.tags = ['UserToRoom'] + #swagger.security = [{ + "bearerAuth": [] + }] + #swagger.parameters['user_id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } + + #swagger.requestBody = { + required: true, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/user_to_room_update" + } } } - */ - - try { - const schema = Joi.object().keys({ - user_id: Joi.string().uuid().required().label('User ID'), - room_ids: Joi.array() + } + */ + + try { + const schema = Joi.object().keys({ + user_id: Joi.string().uuid().required().label('User ID'), + room_ids: Joi.array() .items(Joi.string().uuid().required()) .min(1) .required() .label("Factory IDs") - // room_id: Joi.string().uuid().required().label('Room ID'), - }); - - req.body.user_id = req.params['user_id']; - - const param: any = await schema.validateAsync(req.body); + // room_id: Joi.string().uuid().required().label('Room ID'), + }); - const userToRoomRepo = OrmHelper.DB.getRepository(UserToRoom); - const userRepo = OrmHelper.DB.getRepository(User); - const roomRepo = OrmHelper.DB.getRepository(Room); + req.body.user_id = req.params['user_id']; - await userToRoomRepo - .createQueryBuilder() - .delete() - .from(UserToRoom) - .where("service_id = :userId", { userId: param.user_id }) - .execute(); + const param: any = await schema.validateAsync(req.body); - let relation : UserToRoom; - for (const roomId of param.room_ids) { - relation = new UserToRoom(); - relation.user = await userRepo.findOneBy({ id: param.user_id }); - relation.room = await roomRepo.findOneBy({ id: roomId }); - relation.created_by = req.auth.data.name; + const userToRoomRepo = OrmHelper.DB.getRepository(UserToRoom); + const userRepo = OrmHelper.DB.getRepository(User); + const roomRepo = OrmHelper.DB.getRepository(Room); - await userToRoomRepo.save(relation); - } + await userToRoomRepo + .createQueryBuilder() + .delete() + .from(UserToRoom) + .where("service_id = :userId", { userId: param.user_id }) + .execute(); - const data = await userToRoomRepo.createQueryBuilder("user_to_room") + let relation: UserToRoom; + for (const roomId of param.room_ids) { + relation = new UserToRoom(); + relation.user = await userRepo.findOneBy({ id: param.user_id }); + relation.room = await roomRepo.findOneBy({ id: roomId }); + relation.created_by = req.auth.data.name; + + await userToRoomRepo.save(relation); + } + + const data = await userToRoomRepo.createQueryBuilder("user_to_room") .leftJoinAndSelect("user_to_room.user", "user") .leftJoinAndSelect("user_to_room.room", "room") .where("user_to_room.service_id = :userId", { userId: param.user_id }).getMany(); - return ReturnHelper.successResponseAny(res, 200, Language.lang.success_update, data); - - // const repo = OrmHelper.DB.getRepository(UserToRoom); - - // const data = await repo.findOneBy({ id: param.id }); - - // if (data != null) { - // data.user = param.user_id; - // data.room = param.room_id; - // data.updated_by = req.auth.data.name; - - // await repo.save(data); - - // return ReturnHelper.successResponseAny(res, 200, Language.lang.success_update, data); - // } else { - // return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found, ""); - // } - } catch (e: unknown) { - log.error(e); - const err = e as Error; - - return ReturnHelper.errorResponse(res, 500, 402, Language.lang.failed_update, err.message); - } + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_update, data); + + // const repo = OrmHelper.DB.getRepository(UserToRoom); + + // const data = await repo.findOneBy({ id: param.id }); + + // if (data != null) { + // data.user = param.user_id; + // data.room = param.room_id; + // data.updated_by = req.auth.data.name; + + // await repo.save(data); + + // return ReturnHelper.successResponseAny(res, 200, Language.lang.success_update, data); + // } else { + // return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found, ""); + // } + } catch (e: unknown) { + log.error(e); + const err = e as Error; + + return ReturnHelper.errorResponse(res, 500, 402, Language.lang.failed_update, err.message); } + } static async detail(req: Request, res: Response, next: NextFunction): Promise { - /* - #swagger.tags = ['UserToRoom'] - #swagger.security = [{ "bearerAuth": [] }] - #swagger.parameters['id'] = { description: '', in: 'path', type: 'string' } - */ - try { - const schema = Joi.object().keys({ - id: Joi.string().uuid().required().label("UserToRoom Id"), - }); - - let param: any = await schema.validateAsync(req.params); - - let data = await UserToRoomModel.list({ "id": param.id }).then((q) => q.getOne()); - if (!data) throw { message: "UserToRoom Not Found" }; - - return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, data); - } catch (e: unknown) { - log.error(e); - const err = e as Error; - return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_not_found, err.message); - } + /* + #swagger.tags = ['UserToRoom'] + #swagger.security = [{ "bearerAuth": [] }] + #swagger.parameters['id'] = { description: '', in: 'path', type: 'string' } + */ + try { + const schema = Joi.object().keys({ + id: Joi.string().uuid().required().label("UserToRoom Id"), + }); + + let param: any = await schema.validateAsync(req.params); + + let data = await UserToRoomModel.list({ "id": param.id }).then((q) => q.getOne()); + if (!data) throw { message: "UserToRoom Not Found" }; + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, data); + } catch (e: unknown) { + log.error(e); + const err = e as Error; + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_not_found, err.message); } + } } \ No newline at end of file From c4aed0c14282e4408510c48ca9c37840d8a6e885 Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Thu, 9 Apr 2026 15:16:58 +0700 Subject: [PATCH 5/7] update --- src/controllers/user_to_room.ts | 99 +++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/src/controllers/user_to_room.ts b/src/controllers/user_to_room.ts index 868ae93..fe4b167 100644 --- a/src/controllers/user_to_room.ts +++ b/src/controllers/user_to_room.ts @@ -39,61 +39,74 @@ export class UserToRoomController { let filter = param.filter && param.filter !== "" ? JSON.parse(param.filter) : {}; - var query = await UserToRoomModel.list(filter); - const offset = (param.page - 1) * param.limit; - let limit = param.limit; + const limit = param.limit; - const res_count = query.clone(); - const res_list = query - .orderBy("user_to_room." + param.order_field, param.order_direction) - .offset(offset) - .limit(limit); + // Query dari User sebagai root, leftJoin ke UserToRoom → Room + const userQuery = OrmHelper.DB.getRepository(User) + .createQueryBuilder("user") + .leftJoinAndSelect("user.roles", "roles") + .leftJoinAndMapMany( + "user.userToRooms", + UserToRoom, + "user_to_room", + "user_to_room.user = user.id" + ) + .leftJoinAndMapOne( + "user_to_room.roomData", + Room, + "room", + "room.id = user_to_room.room" + ) + .orderBy("user." + param.order_field, param.order_direction as "ASC" | "DESC"); if (param.with_deleted) { - res_count.withDeleted(); - res_list.withDeleted(); + userQuery.withDeleted(); } - const current_page = param.page; - const total_count_data = await res_count.getCount(); - const list_data = await res_list.getMany(); + // Filter jika ada + if (filter.name) { + userQuery.andWhere("user.name ILIKE :name", { name: `%${filter.name}%` }); + } + if (filter.status) { + userQuery.andWhere("user.status = :status", { status: filter.status }); + } - // Transform data: Group by user, room dijadikan object berindeks - const grouped: Record = {}; + const total_count_data = await userQuery.clone().getCount(); - list_data.forEach(item => { - if (!item.user) return; + const users = await userQuery + .offset(offset) + .limit(limit) + .getMany(); - const userId = item.user.id; + // Transform data + const list_data_return = users.map((user: any) => { + const userToRooms: any[] = user.userToRooms ?? []; - // Inisialisasi user jika belum ada - if (!grouped[userId]) { - grouped[userId] = { - id: item.user.id, - name: item.user.name, - username: item.user.username, - email: item.user.email, - status: item.user.status, - rooms: {} - }; - } + const rooms: Record = {}; + userToRooms.forEach((utr: any, index: number) => { + const room = utr.roomData ?? null; + rooms[`room_${index + 1}`] = room ? { + id: room.id, + room: room.room, + code: room.code, + } : null; + }); - // Hitung index room berikutnya - const roomIndex = Object.keys(grouped[userId].rooms).length + 1; - - // Tambahkan room ke user - if (item.room) { - grouped[userId].rooms[`room_${roomIndex}`] = { - id: item.room.id, - room: item.room.room, - code: item.room.code, - }; - } + return { + id: user.id, + name: user.name, + username: user.username, + email: user.email, + status: user.status, + roles: user.roles ? user.roles.map((role: any) => ({ + id: role.id, + name: role.name, + })) : [], + rooms: Object.keys(rooms).length > 0 ? rooms : null, + }; }); - const list_data_return = Object.values(grouped); - const count_data = CommonHelper.countObject(list_data_return); return ReturnHelper.successResponselist( @@ -101,7 +114,7 @@ export class UserToRoomController { 200, Language.lang.success_view, count_data, - current_page, + param.page, total_count_data, list_data_return ); From 81b91055235a486c6821eec8b115b4e9cbe194bb Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Thu, 9 Apr 2026 15:52:35 +0700 Subject: [PATCH 6/7] update: list in room to pharmacy --- src/controllers/room_to_pharmacy.ts | 178 +++++++++++++++++++--------- src/model/room_to_pharmacy.ts | 38 +++--- 2 files changed, 147 insertions(+), 69 deletions(-) diff --git a/src/controllers/room_to_pharmacy.ts b/src/controllers/room_to_pharmacy.ts index afa8ed1..3287d7d 100644 --- a/src/controllers/room_to_pharmacy.ts +++ b/src/controllers/room_to_pharmacy.ts @@ -36,31 +36,99 @@ export class RoomToPharmacyController { }); let param: Paging = await schema.validateAsync(req.query); - let filter = param.filter && param.filter !== "" ? JSON.parse(param.filter) : {}; - var query = await RoomToPharmacyModel.list(filter); - const offset = (param.page - 1) * param.limit; - let limit = param.limit; + const limit = param.limit; - const res_count = query; - const res_list = query - .orderBy("room_to_pharmacy." + param.order_field, param.order_direction) - .offset(offset) - .limit(limit); + const roomQuery = OrmHelper.DB.getRepository(Room) + .createQueryBuilder("room") + .leftJoinAndSelect("room.department", "department") + .leftJoinAndMapMany( + "room.roomToPharmacies", + RoomToPharmacy, + "room_to_pharmacy", + "room_to_pharmacy.room_id = room.id" + ) + .leftJoinAndMapOne( + "room_to_pharmacy.pharmacy_room", + Room, + "pharmacy_room", + "pharmacy_room.id = room_to_pharmacy.pharmacy_room_id" + ) + .where("department.id IN (:...departmentIds)", { + departmentIds: [ + '42bc42a7-e2a7-4f95-a788-9d9e51a51a20', + '3fc39ff1-b4bc-4276-92ef-def7ca9432fb', + 'a0d2ef11-6927-4e5c-8628-6acf26351071', + ] + }) + .orderBy("room." + param.order_field, param.order_direction as "ASC" | "DESC"); if (param.with_deleted) { - res_count.withDeleted(); - res_list.withDeleted(); + roomQuery.withDeleted(); } - const current_page = param.page; - const total_count_data = await res_count.getCount(); - const list_data = await res_list.getMany(); - const count_data = CommonHelper.countObject(list_data); + // Filter tambahan jika ada + if (filter.room) { + roomQuery.andWhere("room.room ILIKE :room", { room: `%${filter.room}%` }); + } + if (filter.status) { + roomQuery.andWhere("room.status = :status", { status: filter.status }); + } - return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data); + const total_count_data = await roomQuery.clone().getCount(); + + const rooms = await roomQuery + .offset(offset) + .limit(limit) + .getMany(); + + // Transform: { room_name: { pharmacy_room_1: {}, pharmacy_room_2: {} } } + const list_data_return = rooms.map((room: any) => { + const roomToPharmacies: any[] = room.roomToPharmacies ?? []; + + const pharmacyRooms: Record = {}; + roomToPharmacies.forEach((rtp: any, index: number) => { + const pharmacy: Room | null = rtp.pharmacy_room ?? null; + pharmacyRooms[`pharmacy_room_${index + 1}`] = pharmacy ? { + id: pharmacy.id, + room: pharmacy.room, + code: pharmacy.code, + description: pharmacy.description ?? null, + status: pharmacy.status, + } : null; + }); + + return { + rooms: { + id: room.id, + room: room.room, + code: room.code, + description: room.description ?? null, + status: room.status, + department: room.department ? { + id: room.department.id, + name: room.department.name, + } : null, + pharmacy_rooms: Object.keys(pharmacyRooms).length > 0 + ? pharmacyRooms + : null, + } + }; + }); + + const count_data = CommonHelper.countObject(list_data_return); + + return ReturnHelper.successResponselist( + res, + 200, + Language.lang.success_view, + count_data, + param.page, + total_count_data, + list_data_return + ); } catch (e: unknown) { log.error(e); const err = e as Error; @@ -110,7 +178,7 @@ export class RoomToPharmacyController { var query = await RoomToPharmacyModel.list(filter); const data = await query.getMany(); - + const filename = "room_to_pharmacy.csv"; res.setHeader('Content-Type', 'text/csv'); @@ -173,15 +241,15 @@ export class RoomToPharmacyController { try { const schema = Joi.object().keys({ pharmacy_room_id: Joi.string() - .uuid() - .required() - .label("Pharmacy Room ID"), + .uuid() + .required() + .label("Pharmacy Room ID"), room_id: Joi.string() - .uuid() - .required() - .label("Room ID"), - + .uuid() + .required() + .label("Room ID"), + // room_ids: Joi.array() // .items(Joi.string().uuid().required()) // .min(1) @@ -229,7 +297,7 @@ export class RoomToPharmacyController { } } - static async update(req: Request, res: Response, next: NextFunction): Promise { + static async update(req: Request, res: Response, next: NextFunction): Promise { /* #swagger.tags = ['RoomToPharmacy'] #swagger.security = [{ @@ -253,15 +321,15 @@ export class RoomToPharmacyController { id: Joi.string().uuid().required().label('ID'), pharmacy_room_id: Joi.string() - .uuid() - .required() - .label("Pharmacy Room ID"), + .uuid() + .required() + .label("Pharmacy Room ID"), room_id: Joi.string() - .uuid() - .required() - .label("Room ID"), - + .uuid() + .required() + .label("Room ID"), + // room_ids: Joi.array() // .items(Joi.string().uuid().required()) // .min(1) @@ -274,7 +342,7 @@ export class RoomToPharmacyController { const param: any = await schema.validateAsync(req.body); const repo = OrmHelper.DB.getRepository(RoomToPharmacy); - + const data = await repo.findOneBy({ id: param.id }); if (data != null) { @@ -295,26 +363,26 @@ export class RoomToPharmacyController { } static async detail(req: Request, res: Response, next: NextFunction): Promise { - /* - #swagger.tags = ['RoomToPharmacy'] - #swagger.security = [{ "bearerAuth": [] }] - #swagger.parameters['id'] = { description: '', in: 'path', type: 'string' } - */ - try { - const schema = Joi.object().keys({ - id: Joi.string().uuid().required().label("RoomToPharmacy Id"), - }); - - let param: any = await schema.validateAsync(req.params); - - let data = await RoomToPharmacyModel.list({ "id": param.id }).then((q) => q.getOne()); - if (!data) throw { message: "RoomToPharmacy Not Found" }; - - return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, data); - } catch (e: unknown) { - log.error(e); - const err = e as Error; - return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_not_found, err.message); - } - } + /* + #swagger.tags = ['RoomToPharmacy'] + #swagger.security = [{ "bearerAuth": [] }] + #swagger.parameters['id'] = { description: '', in: 'path', type: 'string' } + */ + try { + const schema = Joi.object().keys({ + id: Joi.string().uuid().required().label("RoomToPharmacy Id"), + }); + + let param: any = await schema.validateAsync(req.params); + + let data = await RoomToPharmacyModel.list({ "id": param.id }).then((q) => q.getOne()); + if (!data) throw { message: "RoomToPharmacy Not Found" }; + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, data); + } catch (e: unknown) { + log.error(e); + const err = e as Error; + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_not_found, err.message); + } + } } \ No newline at end of file diff --git a/src/model/room_to_pharmacy.ts b/src/model/room_to_pharmacy.ts index 3395c24..b143762 100644 --- a/src/model/room_to_pharmacy.ts +++ b/src/model/room_to_pharmacy.ts @@ -1,24 +1,34 @@ import { SelectQueryBuilder } from "typeorm"; import CommonHelper from "../helpers/common"; import { OrmHelper } from "../helpers/orm"; -import { RoomToPharmacy } from "entity"; +import { RoomToPharmacy, Room } from "entity"; export class RoomToPharmacyModel { static async list(filter = {}): Promise> { - const repo = OrmHelper.DB.getRepository(RoomToPharmacy); - let whereAttr: string[] = []; - let whereVal: any = {}; - if (filter && Object.keys(filter).length > 0) { - whereAttr = [...whereAttr, ...CommonHelper.handleQueryFilter(filter, "room_to_pharmacy").whereAttr]; - whereVal = { ...whereVal, ...CommonHelper.handleQueryFilter(filter, "room_to_pharmacy").whereVal }; - } + const repo = OrmHelper.DB.getRepository(Room); - var query = repo.createQueryBuilder("room_to_pharmacy") - .leftJoinAndSelect("room_to_pharmacy.room", "room") - .leftJoinAndSelect("room_to_pharmacy.pharmacy_room", "pharmacy_room"); - if (whereAttr.length != 0) { - query = query.where(whereAttr.join(" and "), whereVal); - } + const query = repo + .createQueryBuilder("room") + .leftJoinAndSelect("room.department", "department") + .leftJoinAndMapMany( + "room.roomToPharmacies", + RoomToPharmacy, + "room_to_pharmacy", + "room_to_pharmacy.room_id = room.id" + ) + .leftJoinAndMapOne( + "room_to_pharmacy.pharmacy_room", + Room, + "pharmacy_room", + "pharmacy_room.id = room_to_pharmacy.pharmacy_room_id" + ) + .where("department.id IN (:...departmentIds)", { + departmentIds: [ + '42bc42a7-e2a7-4f95-a788-9d9e51a51a20', + '3fc39ff1-b4bc-4276-92ef-def7ca9432fb', + 'a0d2ef11-6927-4e5c-8628-6acf26351071', + ] + }); return query; } From 4496afcbb92d12079bacb027f591cb03223d2f00 Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Fri, 10 Apr 2026 16:02:00 +0700 Subject: [PATCH 7/7] update room to pharmacy --- src/controllers/room_to_pharmacy.ts | 25 +++++++++++++++++---- src/model/room_to_pharmacy.ts | 35 ++++++++++------------------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/controllers/room_to_pharmacy.ts b/src/controllers/room_to_pharmacy.ts index 3287d7d..bb18f5c 100644 --- a/src/controllers/room_to_pharmacy.ts +++ b/src/controllers/room_to_pharmacy.ts @@ -262,7 +262,7 @@ export class RoomToPharmacyController { const data = new RoomToPharmacy() data.pharmacy_room = param.pharmacy_room_id; data.room = param.room_id; - data.created_by = req.auth.data.name; + data.created_by = req.auth?.data.name; await OrmHelper.DB.manager.save(data); @@ -348,7 +348,7 @@ export class RoomToPharmacyController { if (data != null) { data.pharmacy_room = param.pharmacy_room_id; data.room = param.room_id; - data.updated_by = req.auth.data.name; + data.updated_by = req.auth?.data.name; await repo.save(data); } @@ -370,12 +370,29 @@ export class RoomToPharmacyController { */ try { const schema = Joi.object().keys({ - id: Joi.string().uuid().required().label("RoomToPharmacy Id"), + id: Joi.string().uuid().required().label("Room Id"), }); let param: any = await schema.validateAsync(req.params); - let data = await RoomToPharmacyModel.list({ "id": param.id }).then((q) => q.getOne()); + let data = await OrmHelper.DB.getRepository(Room) + .createQueryBuilder("room") + .leftJoinAndSelect("room.department", "department") + .leftJoinAndMapMany( + "room.roomToPharmacies", + RoomToPharmacy, + "room_to_pharmacy", + "room_to_pharmacy.room_id = room.id" + ) + .leftJoinAndMapOne( + "room_to_pharmacy.pharmacy_room", + Room, + "pharmacy_room", + "pharmacy_room.id = room_to_pharmacy.pharmacy_room_id" + ) + .where("room.id = :id", { id: param.id }) + .getOne(); + if (!data) throw { message: "RoomToPharmacy Not Found" }; return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, data); diff --git a/src/model/room_to_pharmacy.ts b/src/model/room_to_pharmacy.ts index b143762..ac48dfd 100644 --- a/src/model/room_to_pharmacy.ts +++ b/src/model/room_to_pharmacy.ts @@ -5,30 +5,19 @@ import { RoomToPharmacy, Room } from "entity"; export class RoomToPharmacyModel { static async list(filter = {}): Promise> { - const repo = OrmHelper.DB.getRepository(Room); - const query = repo - .createQueryBuilder("room") - .leftJoinAndSelect("room.department", "department") - .leftJoinAndMapMany( - "room.roomToPharmacies", - RoomToPharmacy, - "room_to_pharmacy", - "room_to_pharmacy.room_id = room.id" - ) - .leftJoinAndMapOne( - "room_to_pharmacy.pharmacy_room", - Room, - "pharmacy_room", - "pharmacy_room.id = room_to_pharmacy.pharmacy_room_id" - ) - .where("department.id IN (:...departmentIds)", { - departmentIds: [ - '42bc42a7-e2a7-4f95-a788-9d9e51a51a20', - '3fc39ff1-b4bc-4276-92ef-def7ca9432fb', - 'a0d2ef11-6927-4e5c-8628-6acf26351071', - ] - }); + const repo = OrmHelper.DB.getRepository(RoomToPharmacy); + let whereAttr: string[] = []; + let whereVal: any = {}; + if (filter && Object.keys(filter).length > 0) { + whereAttr = [...whereAttr, ...CommonHelper.handleQueryFilter(filter).whereAttr]; + whereVal = { ...whereVal, ...CommonHelper.handleQueryFilter(filter).whereVal }; + } + + var query = repo.createQueryBuilder("room_to_pharmacy"); + if (whereAttr.length != 0) { + query = query.where(whereAttr.join(" and "), whereVal); + } return query; }