@iservice365/module-hygiene 1.0.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -644,7 +644,7 @@ function useAreaController() {
644
644
  return;
645
645
  }
646
646
  const page = parseInt(req.query.page) ?? 1;
647
- const limit = parseInt(req.query.limit) ?? 20;
647
+ const limit = parseInt(req.query.limit) ?? 10;
648
648
  const search = req.query.search ?? "";
649
649
  const site = req.params.site ?? "";
650
650
  try {
@@ -790,8 +790,8 @@ function MUnit(value) {
790
790
  return {
791
791
  site: value.site,
792
792
  name: value.name,
793
- createdAt: /* @__PURE__ */ new Date(),
794
793
  status: "active",
794
+ createdAt: /* @__PURE__ */ new Date(),
795
795
  updatedAt: "",
796
796
  deletedAt: ""
797
797
  };
@@ -1193,7 +1193,7 @@ function useUnitController() {
1193
1193
  return;
1194
1194
  }
1195
1195
  const page = parseInt(req.query.page) ?? 1;
1196
- const limit = parseInt(req.query.limit) ?? 20;
1196
+ const limit = parseInt(req.query.limit) ?? 10;
1197
1197
  const search = req.query.search ?? "";
1198
1198
  const site = req.params.site ?? "";
1199
1199
  try {
@@ -1503,6 +1503,9 @@ function useParentChecklistRepo() {
1503
1503
  status,
1504
1504
  updatedAt: /* @__PURE__ */ new Date()
1505
1505
  };
1506
+ if (status === "completed") {
1507
+ updateValue.completedAt = /* @__PURE__ */ new Date();
1508
+ }
1506
1509
  const res = await collection.updateOne(
1507
1510
  { _id },
1508
1511
  { $set: updateValue },
@@ -1607,7 +1610,7 @@ function useParentChecklistController() {
1607
1610
  return;
1608
1611
  }
1609
1612
  const page = parseInt(req.query.page) ?? 1;
1610
- const limit = parseInt(req.query.limit) ?? 20;
1613
+ const limit = parseInt(req.query.limit) ?? 10;
1611
1614
  const search = req.query.search ?? "";
1612
1615
  const site = req.params.site ?? "";
1613
1616
  const startDate = req.query.startDate ?? "";
@@ -2626,7 +2629,7 @@ function useAreaChecklistController() {
2626
2629
  return;
2627
2630
  }
2628
2631
  const page = parseInt(req.query.page) ?? 1;
2629
- const limit = parseInt(req.query.limit) ?? 20;
2632
+ const limit = parseInt(req.query.limit) ?? 10;
2630
2633
  const search = req.query.search ?? "";
2631
2634
  const type = req.query.type ?? "";
2632
2635
  const schedule = req.params.schedule ?? "";
@@ -2664,7 +2667,7 @@ function useAreaChecklistController() {
2664
2667
  return;
2665
2668
  }
2666
2669
  const page = parseInt(req.query.page) ?? 1;
2667
- const limit = parseInt(req.query.limit) ?? 20;
2670
+ const limit = parseInt(req.query.limit) ?? 10;
2668
2671
  const search = req.query.search ?? "";
2669
2672
  const type = req.query.type ?? "";
2670
2673
  const schedule = req.params.schedule ?? "";
@@ -2722,7 +2725,7 @@ function useAreaChecklistController() {
2722
2725
  return;
2723
2726
  }
2724
2727
  const page = parseInt(req.query.page) ?? 1;
2725
- const limit = parseInt(req.query.limit) ?? 20;
2728
+ const limit = parseInt(req.query.limit) ?? 10;
2726
2729
  const search = req.query.search ?? "";
2727
2730
  const _id = req.params.id ?? "";
2728
2731
  try {
@@ -2797,8 +2800,7 @@ import { ObjectId as ObjectId9 } from "mongodb";
2797
2800
  var supplySchema = Joi9.object({
2798
2801
  site: Joi9.string().hex().required(),
2799
2802
  name: Joi9.string().required(),
2800
- unitOfMeasurement: Joi9.string().required(),
2801
- qty: Joi9.number().min(0).required()
2803
+ unitOfMeasurement: Joi9.string().required()
2802
2804
  });
2803
2805
  function MSupply(value) {
2804
2806
  const { error } = supplySchema.validate(value);
@@ -2817,7 +2819,7 @@ function MSupply(value) {
2817
2819
  site: value.site,
2818
2820
  name: value.name,
2819
2821
  unitOfMeasurement: value.unitOfMeasurement,
2820
- qty: value.qty,
2822
+ qty: 0,
2821
2823
  status: "active",
2822
2824
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2823
2825
  updatedAt: "",
@@ -2933,7 +2935,8 @@ function useSupplyRepository() {
2933
2935
  {
2934
2936
  $project: {
2935
2937
  name: 1,
2936
- qty: 1
2938
+ qty: 1,
2939
+ status: 1
2937
2940
  }
2938
2941
  },
2939
2942
  { $sort: { _id: -1 } },
@@ -2952,7 +2955,7 @@ function useSupplyRepository() {
2952
2955
  throw error;
2953
2956
  }
2954
2957
  }
2955
- async function getSupplyById(_id) {
2958
+ async function getSupplyById(_id, session) {
2956
2959
  try {
2957
2960
  _id = new ObjectId10(_id);
2958
2961
  } catch (error) {
@@ -2965,10 +2968,14 @@ function useSupplyRepository() {
2965
2968
  const cacheKey = makeCacheKey5(namespace_collection, {
2966
2969
  _id: _id.toString()
2967
2970
  });
2968
- const cachedData = await getCache(cacheKey);
2969
- if (cachedData) {
2970
- logger17.info(`Cache hit for key: ${cacheKey}`);
2971
- return cachedData;
2971
+ if (!session) {
2972
+ const cachedData = await getCache(cacheKey);
2973
+ if (cachedData) {
2974
+ logger17.info(`Cache hit for key: ${cacheKey}`);
2975
+ return cachedData;
2976
+ }
2977
+ } else {
2978
+ logger17.info(`Skipping cache during transaction for key: ${cacheKey}`);
2972
2979
  }
2973
2980
  try {
2974
2981
  const data = await collection.aggregate([
@@ -3022,7 +3029,7 @@ function useSupplyRepository() {
3022
3029
  } catch (error) {
3023
3030
  const isDuplicated = error.message.includes("duplicate");
3024
3031
  if (isDuplicated) {
3025
- throw new BadRequestError16("Area already exists.");
3032
+ throw new BadRequestError16("Supply already exists.");
3026
3033
  }
3027
3034
  throw error;
3028
3035
  }
@@ -3072,39 +3079,172 @@ function useSupplyRepository() {
3072
3079
  };
3073
3080
  }
3074
3081
 
3075
- // src/services/hygiene-supply.service.ts
3076
- import { useAtlas as useAtlas9 } from "@iservice365/node-server-utils";
3077
-
3078
- // src/models/hygiene-stock.model.ts
3082
+ // src/controllers/hygiene-supply.controller.ts
3079
3083
  import { BadRequestError as BadRequestError17, logger as logger18 } from "@iservice365/node-server-utils";
3080
3084
  import Joi10 from "joi";
3085
+ function useSupplyController() {
3086
+ const {
3087
+ createSupply: _createSupply,
3088
+ getSupplies: _getSupplies,
3089
+ getSupplyById: _getSupplyById,
3090
+ updateSupply: _updateSupply,
3091
+ deleteSupply: _deleteSupply
3092
+ } = useSupplyRepository();
3093
+ async function createSupply(req, res, next) {
3094
+ const payload = { ...req.body, ...req.params };
3095
+ const { error } = supplySchema.validate(payload);
3096
+ if (error) {
3097
+ logger18.log({ level: "error", message: error.message });
3098
+ next(new BadRequestError17(error.message));
3099
+ return;
3100
+ }
3101
+ try {
3102
+ const id = await _createSupply(payload);
3103
+ res.status(201).json({ message: "Supply created successfully.", id });
3104
+ return;
3105
+ } catch (error2) {
3106
+ logger18.log({ level: "error", message: error2.message });
3107
+ next(error2);
3108
+ return;
3109
+ }
3110
+ }
3111
+ async function getSupplies(req, res, next) {
3112
+ const query = { ...req.query, ...req.params };
3113
+ const validation = Joi10.object({
3114
+ page: Joi10.number().min(1).optional().allow("", null),
3115
+ limit: Joi10.number().min(1).optional().allow("", null),
3116
+ search: Joi10.string().optional().allow("", null),
3117
+ site: Joi10.string().hex().required()
3118
+ });
3119
+ const { error } = validation.validate(query);
3120
+ if (error) {
3121
+ logger18.log({ level: "error", message: error.message });
3122
+ next(new BadRequestError17(error.message));
3123
+ return;
3124
+ }
3125
+ const page = parseInt(req.query.page) ?? 1;
3126
+ const limit = parseInt(req.query.limit) ?? 10;
3127
+ const search = req.query.search ?? "";
3128
+ const site = req.params.site ?? "";
3129
+ try {
3130
+ const data = await _getSupplies({
3131
+ page,
3132
+ limit,
3133
+ search,
3134
+ site
3135
+ });
3136
+ res.json(data);
3137
+ return;
3138
+ } catch (error2) {
3139
+ logger18.log({ level: "error", message: error2.message });
3140
+ next(error2);
3141
+ return;
3142
+ }
3143
+ }
3144
+ async function getSupplyById(req, res, next) {
3145
+ const validation = Joi10.string().hex().required();
3146
+ const _id = req.params.id;
3147
+ const { error } = validation.validate(_id);
3148
+ if (error) {
3149
+ logger18.log({ level: "error", message: error.message });
3150
+ next(new BadRequestError17(error.message));
3151
+ return;
3152
+ }
3153
+ try {
3154
+ const data = await _getSupplyById(_id);
3155
+ res.json(data);
3156
+ return;
3157
+ } catch (error2) {
3158
+ logger18.log({ level: "error", message: error2.message });
3159
+ next(error2);
3160
+ return;
3161
+ }
3162
+ }
3163
+ async function updateSupply(req, res, next) {
3164
+ const payload = { id: req.params.id, ...req.body };
3165
+ const validation = Joi10.object({
3166
+ id: Joi10.string().hex().required(),
3167
+ name: Joi10.string().optional().allow("", null),
3168
+ unitOfMeasurement: Joi10.string().optional().allow("", null),
3169
+ qty: Joi10.number().min(0).optional().allow("", null)
3170
+ });
3171
+ const { error } = validation.validate(payload);
3172
+ if (error) {
3173
+ logger18.log({ level: "error", message: error.message });
3174
+ next(new BadRequestError17(error.message));
3175
+ return;
3176
+ }
3177
+ try {
3178
+ const { id, ...value } = payload;
3179
+ await _updateSupply(id, value);
3180
+ res.json({ message: "Supply updated successfully." });
3181
+ return;
3182
+ } catch (error2) {
3183
+ logger18.log({ level: "error", message: error2.message });
3184
+ next(error2);
3185
+ return;
3186
+ }
3187
+ }
3188
+ async function deleteSupply(req, res, next) {
3189
+ const id = req.params.id;
3190
+ const validation = Joi10.object({
3191
+ id: Joi10.string().hex().required()
3192
+ });
3193
+ const { error } = validation.validate({ id });
3194
+ if (error) {
3195
+ logger18.log({ level: "error", message: error.message });
3196
+ next(new BadRequestError17(error.message));
3197
+ return;
3198
+ }
3199
+ try {
3200
+ await _deleteSupply(id);
3201
+ res.json({ message: "Supply deleted successfully." });
3202
+ return;
3203
+ } catch (error2) {
3204
+ logger18.log({ level: "error", message: error2.message });
3205
+ next(error2);
3206
+ return;
3207
+ }
3208
+ }
3209
+ return {
3210
+ createSupply,
3211
+ getSupplies,
3212
+ getSupplyById,
3213
+ updateSupply,
3214
+ deleteSupply
3215
+ };
3216
+ }
3217
+
3218
+ // src/models/hygiene-stock.model.ts
3219
+ import { BadRequestError as BadRequestError18, logger as logger19 } from "@iservice365/node-server-utils";
3220
+ import Joi11 from "joi";
3081
3221
  import { ObjectId as ObjectId11 } from "mongodb";
3082
- var stockSchema = Joi10.object({
3083
- site: Joi10.string().hex().required(),
3084
- supply: Joi10.string().hex().required(),
3085
- in: Joi10.number().min(0).optional(),
3086
- out: Joi10.number().min(0).optional(),
3087
- balance: Joi10.number().min(0).required(),
3088
- remarks: Joi10.string().optional().allow("", null)
3222
+ var stockSchema = Joi11.object({
3223
+ site: Joi11.string().hex().required(),
3224
+ supply: Joi11.string().hex().required(),
3225
+ in: Joi11.number().min(0).optional(),
3226
+ out: Joi11.number().min(0).optional(),
3227
+ balance: Joi11.number().min(0).required(),
3228
+ remarks: Joi11.string().optional().allow("", null)
3089
3229
  });
3090
3230
  function MStock(value) {
3091
3231
  const { error } = stockSchema.validate(value);
3092
3232
  if (error) {
3093
- logger18.info(`Hygiene Stock Model: ${error.message}`);
3094
- throw new BadRequestError17(error.message);
3233
+ logger19.info(`Hygiene Stock Model: ${error.message}`);
3234
+ throw new BadRequestError18(error.message);
3095
3235
  }
3096
3236
  if (value.site) {
3097
3237
  try {
3098
3238
  value.site = new ObjectId11(value.site);
3099
3239
  } catch (error2) {
3100
- throw new BadRequestError17("Invalid site ID format.");
3240
+ throw new BadRequestError18("Invalid site ID format.");
3101
3241
  }
3102
3242
  }
3103
3243
  if (value.supply) {
3104
3244
  try {
3105
3245
  value.supply = new ObjectId11(value.supply);
3106
3246
  } catch (error2) {
3107
- throw new BadRequestError17("Invalid supply ID format.");
3247
+ throw new BadRequestError18("Invalid supply ID format.");
3108
3248
  }
3109
3249
  }
3110
3250
  return {
@@ -3122,12 +3262,15 @@ function MStock(value) {
3122
3262
  }
3123
3263
 
3124
3264
  // src/repositories/hygiene-stock.repository.ts
3265
+ import { ObjectId as ObjectId12 } from "mongodb";
3125
3266
  import {
3126
3267
  useAtlas as useAtlas8,
3127
3268
  InternalServerError as InternalServerError6,
3128
- BadRequestError as BadRequestError18,
3269
+ BadRequestError as BadRequestError19,
3129
3270
  useCache as useCache6,
3130
- logger as logger19
3271
+ logger as logger20,
3272
+ makeCacheKey as makeCacheKey6,
3273
+ paginate as paginate6
3131
3274
  } from "@iservice365/node-server-utils";
3132
3275
  function useStockRepository() {
3133
3276
  const db = useAtlas8.getDb();
@@ -3135,8 +3278,10 @@ function useStockRepository() {
3135
3278
  throw new InternalServerError6("Unable to connect to server.");
3136
3279
  }
3137
3280
  const namespace_collection = "site.supply.stocks";
3281
+ const supply_collection = "site.supplies";
3138
3282
  const collection = db.collection(namespace_collection);
3139
- const { delNamespace } = useCache6(namespace_collection);
3283
+ const { delNamespace, setCache, getCache } = useCache6(namespace_collection);
3284
+ const { delNamespace: delSupplyNamespace } = useCache6(supply_collection);
3140
3285
  async function createIndex() {
3141
3286
  try {
3142
3287
  await collection.createIndexes([
@@ -3154,49 +3299,135 @@ function useStockRepository() {
3154
3299
  value = MStock(value);
3155
3300
  const res = await collection.insertOne(value, { session });
3156
3301
  delNamespace().then(() => {
3157
- logger19.info(`Cache cleared for namespace: ${namespace_collection}`);
3302
+ logger20.info(`Cache cleared for namespace: ${namespace_collection}`);
3158
3303
  }).catch((err) => {
3159
- logger19.error(
3304
+ logger20.error(
3160
3305
  `Failed to clear cache for namespace: ${namespace_collection}`,
3161
3306
  err
3162
3307
  );
3163
3308
  });
3309
+ delSupplyNamespace().then(() => {
3310
+ logger20.info(`Cache cleared for namespace: ${supply_collection}`);
3311
+ }).catch((err) => {
3312
+ logger20.error(
3313
+ `Failed to clear cache for namespace: ${supply_collection}`,
3314
+ err
3315
+ );
3316
+ });
3164
3317
  return res.insertedId;
3165
3318
  } catch (error) {
3166
- const isDuplicated = error.message.includes("duplicate");
3167
- if (isDuplicated) {
3168
- throw new BadRequestError18("Stock already exists.");
3169
- }
3319
+ throw error;
3320
+ }
3321
+ }
3322
+ async function getStocksBySupplyId({
3323
+ page = 1,
3324
+ limit = 10,
3325
+ search = "",
3326
+ site,
3327
+ supply
3328
+ }) {
3329
+ page = page > 0 ? page - 1 : 0;
3330
+ const query = {
3331
+ status: { $ne: "deleted" }
3332
+ };
3333
+ const cacheOptions = {
3334
+ page,
3335
+ limit
3336
+ };
3337
+ try {
3338
+ site = new ObjectId12(site);
3339
+ query.site = site;
3340
+ cacheOptions.site = site.toString();
3341
+ } catch (error) {
3342
+ throw new BadRequestError19("Invalid site ID format.");
3343
+ }
3344
+ try {
3345
+ supply = new ObjectId12(supply);
3346
+ query.supply = supply;
3347
+ cacheOptions.supply = supply.toString();
3348
+ } catch (error) {
3349
+ throw new BadRequestError19("Invalid supply ID format.");
3350
+ }
3351
+ if (search) {
3352
+ query.$text = { $search: search };
3353
+ cacheOptions.search = search;
3354
+ }
3355
+ const cacheKey = makeCacheKey6(namespace_collection, cacheOptions);
3356
+ const cachedData = await getCache(cacheKey);
3357
+ if (cachedData) {
3358
+ logger20.info(`Cache hit for key: ${cacheKey}`);
3359
+ return cachedData;
3360
+ }
3361
+ try {
3362
+ const items = await collection.aggregate([
3363
+ { $match: query },
3364
+ {
3365
+ $project: {
3366
+ createdAt: 1,
3367
+ in: 1,
3368
+ out: 1,
3369
+ balance: 1
3370
+ }
3371
+ },
3372
+ { $sort: { _id: 1 } },
3373
+ { $skip: page * limit },
3374
+ { $limit: limit }
3375
+ ]).toArray();
3376
+ const length = await collection.countDocuments(query);
3377
+ const data = paginate6(items, page, limit, length);
3378
+ setCache(cacheKey, data, 15 * 60).then(() => {
3379
+ logger20.info(`Cache set for key: ${cacheKey}`);
3380
+ }).catch((err) => {
3381
+ logger20.error(`Failed to set cache for key: ${cacheKey}`, err);
3382
+ });
3383
+ return data;
3384
+ } catch (error) {
3170
3385
  throw error;
3171
3386
  }
3172
3387
  }
3173
3388
  return {
3174
3389
  createIndex,
3175
- createStock
3390
+ createStock,
3391
+ getStocksBySupplyId
3176
3392
  };
3177
3393
  }
3178
3394
 
3179
- // src/services/hygiene-supply.service.ts
3180
- function useSupplyService() {
3181
- const { createSupply: _createSupply } = useSupplyRepository();
3182
- const { createStock } = useStockRepository();
3183
- async function createSupply(value) {
3395
+ // src/services/hygiene-stock.service.ts
3396
+ import {
3397
+ NotFoundError as NotFoundError5,
3398
+ useAtlas as useAtlas9,
3399
+ BadRequestError as BadRequestError20
3400
+ } from "@iservice365/node-server-utils";
3401
+ function useStockService() {
3402
+ const { createStock: _createStock } = useStockRepository();
3403
+ const { getSupplyById, updateSupply } = useSupplyRepository();
3404
+ async function createStock(value, out = false) {
3184
3405
  const session = useAtlas9.getClient()?.startSession();
3185
3406
  try {
3186
3407
  session?.startTransaction();
3187
- const { qty, site } = value;
3188
- const supply = await _createSupply(value, session);
3189
- const createdSupply = await createStock(
3408
+ const { qty, ...stockData } = value;
3409
+ const supply = await getSupplyById(value.supply, session);
3410
+ if (!supply || supply.qty === void 0) {
3411
+ throw new NotFoundError5("Supply not found.");
3412
+ }
3413
+ const newSupplyQty = out ? supply.qty - qty : supply.qty + qty;
3414
+ if (out && newSupplyQty < 0) {
3415
+ throw new BadRequestError20(
3416
+ `Insufficient stock. Available: ${supply.qty}, Requested: ${qty}`
3417
+ );
3418
+ }
3419
+ await updateSupply(value.supply, { qty: newSupplyQty }, session);
3420
+ const createdStock = await _createStock(
3190
3421
  {
3191
- site,
3192
- supply: supply.toString(),
3193
- in: qty,
3194
- balance: qty
3422
+ ...stockData,
3423
+ in: out ? 0 : qty,
3424
+ out: out ? qty : 0,
3425
+ balance: newSupplyQty
3195
3426
  },
3196
3427
  session
3197
3428
  );
3198
3429
  await session?.commitTransaction();
3199
- return createdSupply;
3430
+ return createdStock;
3200
3431
  } catch (error) {
3201
3432
  await session?.abortTransaction();
3202
3433
  throw error;
@@ -3204,223 +3435,1163 @@ function useSupplyService() {
3204
3435
  await session?.endSession();
3205
3436
  }
3206
3437
  }
3207
- return { createSupply };
3438
+ return { createStock };
3208
3439
  }
3209
3440
 
3210
- // src/controllers/hygiene-supply.controller.ts
3211
- import { BadRequestError as BadRequestError19, logger as logger20 } from "@iservice365/node-server-utils";
3212
- import Joi11 from "joi";
3213
- function useSupplyController() {
3214
- const {
3215
- getSupplies: _getSupplies,
3216
- getSupplyById: _getSupplyById,
3217
- updateSupply: _updateSupply,
3218
- deleteSupply: _deleteSupply
3219
- } = useSupplyRepository();
3220
- const { createSupply: _createSupply } = useSupplyService();
3221
- async function createSupply(req, res, next) {
3441
+ // src/controllers/hygiene-stock.controller.ts
3442
+ import { BadRequestError as BadRequestError21, logger as logger21 } from "@iservice365/node-server-utils";
3443
+ import Joi12 from "joi";
3444
+ function useStockController() {
3445
+ const { getStocksBySupplyId: _getStocksBySupplyId } = useStockRepository();
3446
+ const { createStock: _createStock } = useStockService();
3447
+ async function createStock(req, res, next) {
3222
3448
  const payload = { ...req.body, ...req.params };
3223
- const { error } = supplySchema.validate(payload);
3449
+ const validation = Joi12.object({
3450
+ site: Joi12.string().hex().required(),
3451
+ supply: Joi12.string().hex().required(),
3452
+ qty: Joi12.number().min(0).required(),
3453
+ remarks: Joi12.string().optional().allow("", null)
3454
+ });
3455
+ const { error } = validation.validate(payload);
3224
3456
  if (error) {
3225
- logger20.log({ level: "error", message: error.message });
3226
- next(new BadRequestError19(error.message));
3457
+ logger21.log({ level: "error", message: error.message });
3458
+ next(new BadRequestError21(error.message));
3227
3459
  return;
3228
3460
  }
3229
3461
  try {
3230
- const id = await _createSupply(payload);
3231
- res.status(201).json({ message: "Supply created successfully.", id });
3462
+ const id = await _createStock(payload);
3463
+ res.status(201).json({ message: "Stock created successfully.", id });
3232
3464
  return;
3233
3465
  } catch (error2) {
3234
- logger20.log({ level: "error", message: error2.message });
3466
+ logger21.log({ level: "error", message: error2.message });
3235
3467
  next(error2);
3236
3468
  return;
3237
3469
  }
3238
3470
  }
3239
- async function getSupplies(req, res, next) {
3471
+ async function getStocksBySupplyId(req, res, next) {
3240
3472
  const query = { ...req.query, ...req.params };
3241
- const validation = Joi11.object({
3242
- page: Joi11.number().min(1).optional().allow("", null),
3243
- limit: Joi11.number().min(1).optional().allow("", null),
3244
- search: Joi11.string().optional().allow("", null),
3245
- site: Joi11.string().hex().required()
3473
+ const validation = Joi12.object({
3474
+ page: Joi12.number().min(1).optional().allow("", null),
3475
+ limit: Joi12.number().min(1).optional().allow("", null),
3476
+ search: Joi12.string().optional().allow("", null),
3477
+ site: Joi12.string().hex().required(),
3478
+ supply: Joi12.string().hex().required()
3246
3479
  });
3247
3480
  const { error } = validation.validate(query);
3248
3481
  if (error) {
3249
- logger20.log({ level: "error", message: error.message });
3250
- next(new BadRequestError19(error.message));
3482
+ logger21.log({ level: "error", message: error.message });
3483
+ next(new BadRequestError21(error.message));
3251
3484
  return;
3252
3485
  }
3253
3486
  const page = parseInt(req.query.page) ?? 1;
3254
- const limit = parseInt(req.query.limit) ?? 20;
3487
+ const limit = parseInt(req.query.limit) ?? 10;
3255
3488
  const search = req.query.search ?? "";
3256
3489
  const site = req.params.site ?? "";
3490
+ const supply = req.params.supply ?? "";
3257
3491
  try {
3258
- const data = await _getSupplies({
3492
+ const data = await _getStocksBySupplyId({
3259
3493
  page,
3260
3494
  limit,
3261
3495
  search,
3262
- site
3496
+ site,
3497
+ supply
3263
3498
  });
3264
3499
  res.json(data);
3265
3500
  return;
3266
3501
  } catch (error2) {
3267
- logger20.log({ level: "error", message: error2.message });
3502
+ logger21.log({ level: "error", message: error2.message });
3268
3503
  next(error2);
3269
3504
  return;
3270
3505
  }
3271
3506
  }
3272
- async function getSupplyById(req, res, next) {
3273
- const validation = Joi11.string().hex().required();
3274
- const _id = req.params.id;
3275
- const { error } = validation.validate(_id);
3276
- if (error) {
3277
- logger20.log({ level: "error", message: error.message });
3278
- next(new BadRequestError19(error.message));
3279
- return;
3507
+ return {
3508
+ createStock,
3509
+ getStocksBySupplyId
3510
+ };
3511
+ }
3512
+
3513
+ // src/models/hygiene-request-item.model.ts
3514
+ import { BadRequestError as BadRequestError22, logger as logger22 } from "@iservice365/node-server-utils";
3515
+ import Joi13 from "joi";
3516
+ import { ObjectId as ObjectId13 } from "mongodb";
3517
+ var allowedRequestItemStatus = [
3518
+ "pending",
3519
+ "approved",
3520
+ "disapproved"
3521
+ ];
3522
+ var requestItemSchema = Joi13.object({
3523
+ site: Joi13.string().hex().required(),
3524
+ supply: Joi13.string().hex().required(),
3525
+ supplyName: Joi13.string().required(),
3526
+ qty: Joi13.number().min(0).required(),
3527
+ createdBy: Joi13.string().hex().required(),
3528
+ createdByName: Joi13.string().required()
3529
+ });
3530
+ function MRequestItem(value) {
3531
+ const { error } = requestItemSchema.validate(value);
3532
+ if (error) {
3533
+ logger22.info(`Hygiene Request Item Model: ${error.message}`);
3534
+ throw new BadRequestError22(error.message);
3535
+ }
3536
+ if (value.site) {
3537
+ try {
3538
+ value.site = new ObjectId13(value.site);
3539
+ } catch (error2) {
3540
+ throw new BadRequestError22("Invalid site ID format.");
3280
3541
  }
3542
+ }
3543
+ if (value.supply) {
3281
3544
  try {
3282
- const data = await _getSupplyById(_id);
3283
- res.json(data);
3284
- return;
3545
+ value.supply = new ObjectId13(value.supply);
3285
3546
  } catch (error2) {
3286
- logger20.log({ level: "error", message: error2.message });
3287
- next(error2);
3288
- return;
3547
+ throw new BadRequestError22("Invalid supply ID format.");
3289
3548
  }
3290
3549
  }
3291
- async function updateSupply(req, res, next) {
3292
- const payload = { id: req.params.id, ...req.body };
3293
- const validation = Joi11.object({
3294
- id: Joi11.string().hex().required(),
3295
- name: Joi11.string().optional().allow("", null),
3296
- unitOfMeasurement: Joi11.string().optional().allow("", null),
3297
- qty: Joi11.number().min(0).optional().allow("", null)
3550
+ return {
3551
+ site: value.site,
3552
+ supply: value.supply,
3553
+ supplyName: value.supplyName,
3554
+ qty: value.qty,
3555
+ remarks: "",
3556
+ createdBy: value.createdBy,
3557
+ createdByName: value.createdByName,
3558
+ status: "pending",
3559
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3560
+ updatedAt: "",
3561
+ deletedAt: ""
3562
+ };
3563
+ }
3564
+
3565
+ // src/repositories/hygiene-request-item.repository.ts
3566
+ import { ObjectId as ObjectId14 } from "mongodb";
3567
+ import {
3568
+ useAtlas as useAtlas10,
3569
+ InternalServerError as InternalServerError7,
3570
+ useCache as useCache7,
3571
+ logger as logger23,
3572
+ makeCacheKey as makeCacheKey7,
3573
+ paginate as paginate7,
3574
+ BadRequestError as BadRequestError23,
3575
+ NotFoundError as NotFoundError6
3576
+ } from "@iservice365/node-server-utils";
3577
+ function useRequestItemRepository() {
3578
+ const db = useAtlas10.getDb();
3579
+ if (!db) {
3580
+ throw new InternalServerError7("Unable to connect to server.");
3581
+ }
3582
+ const namespace_collection = "site.supply.requests";
3583
+ const collection = db.collection(namespace_collection);
3584
+ const { delNamespace, setCache, getCache } = useCache7(namespace_collection);
3585
+ async function createIndex() {
3586
+ try {
3587
+ await collection.createIndexes([
3588
+ { key: { site: 1 } },
3589
+ { key: { supply: 1 } },
3590
+ { key: { status: 1 } }
3591
+ ]);
3592
+ } catch (error) {
3593
+ throw new InternalServerError7(
3594
+ "Failed to create index on hygiene request item."
3595
+ );
3596
+ }
3597
+ }
3598
+ async function createTextIndex() {
3599
+ try {
3600
+ await collection.createIndex({ supplyName: "text" });
3601
+ } catch (error) {
3602
+ throw new InternalServerError7(
3603
+ "Failed to create text index on hygiene supply."
3604
+ );
3605
+ }
3606
+ }
3607
+ async function createRequestItem(value, session) {
3608
+ try {
3609
+ value = MRequestItem(value);
3610
+ const res = await collection.insertOne(value, { session });
3611
+ delNamespace().then(() => {
3612
+ logger23.info(`Cache cleared for namespace: ${namespace_collection}`);
3613
+ }).catch((err) => {
3614
+ logger23.error(
3615
+ `Failed to clear cache for namespace: ${namespace_collection}`,
3616
+ err
3617
+ );
3618
+ });
3619
+ return res.insertedId;
3620
+ } catch (error) {
3621
+ throw error;
3622
+ }
3623
+ }
3624
+ async function getRequestItems({
3625
+ page = 1,
3626
+ limit = 10,
3627
+ search = "",
3628
+ site
3629
+ }) {
3630
+ page = page > 0 ? page - 1 : 0;
3631
+ const query = {
3632
+ status: { $ne: "deleted" }
3633
+ };
3634
+ const cacheOptions = {
3635
+ page,
3636
+ limit
3637
+ };
3638
+ try {
3639
+ site = new ObjectId14(site);
3640
+ query.site = site;
3641
+ cacheOptions.site = site.toString();
3642
+ } catch (error) {
3643
+ throw new BadRequestError23("Invalid site ID format.");
3644
+ }
3645
+ if (search) {
3646
+ query.$text = { $search: search };
3647
+ cacheOptions.search = search;
3648
+ }
3649
+ const cacheKey = makeCacheKey7(namespace_collection, cacheOptions);
3650
+ const cachedData = await getCache(cacheKey);
3651
+ if (cachedData) {
3652
+ logger23.info(`Cache hit for key: ${cacheKey}`);
3653
+ return cachedData;
3654
+ }
3655
+ try {
3656
+ const items = await collection.aggregate([
3657
+ { $match: query },
3658
+ {
3659
+ $project: {
3660
+ createdAt: 1,
3661
+ status: 1
3662
+ }
3663
+ },
3664
+ { $sort: { _id: -1 } },
3665
+ { $skip: page * limit },
3666
+ { $limit: limit }
3667
+ ]).toArray();
3668
+ const length = await collection.countDocuments(query);
3669
+ const data = paginate7(items, page, limit, length);
3670
+ setCache(cacheKey, data, 15 * 60).then(() => {
3671
+ logger23.info(`Cache set for key: ${cacheKey}`);
3672
+ }).catch((err) => {
3673
+ logger23.error(`Failed to set cache for key: ${cacheKey}`, err);
3674
+ });
3675
+ return data;
3676
+ } catch (error) {
3677
+ throw error;
3678
+ }
3679
+ }
3680
+ async function getRequestItemById(_id, session) {
3681
+ try {
3682
+ _id = new ObjectId14(_id);
3683
+ } catch (error) {
3684
+ throw new BadRequestError23("Invalid request item ID format.");
3685
+ }
3686
+ const query = { _id };
3687
+ const cacheKey = makeCacheKey7(namespace_collection, {
3688
+ _id: _id.toString()
3689
+ });
3690
+ if (!session) {
3691
+ const cachedData = await getCache(cacheKey);
3692
+ if (cachedData) {
3693
+ logger23.info(`Cache hit for key: ${cacheKey}`);
3694
+ return cachedData;
3695
+ }
3696
+ } else {
3697
+ logger23.info(`Skipping cache during transaction for key: ${cacheKey}`);
3698
+ }
3699
+ try {
3700
+ const data = await collection.aggregate([
3701
+ { $match: query },
3702
+ {
3703
+ $lookup: {
3704
+ from: "site.supply.items",
3705
+ localField: "supply",
3706
+ foreignField: "_id",
3707
+ as: "supplyDetails"
3708
+ }
3709
+ },
3710
+ {
3711
+ $unwind: {
3712
+ path: "$supplyDetails",
3713
+ preserveNullAndEmptyArrays: true
3714
+ }
3715
+ },
3716
+ {
3717
+ $project: {
3718
+ site: 1,
3719
+ supply: 1,
3720
+ supplyName: 1,
3721
+ qty: 1,
3722
+ status: 1,
3723
+ unitOfMeasurement: "$supplyDetails.unitOfMeasurement"
3724
+ }
3725
+ }
3726
+ ]).toArray();
3727
+ if (!data || data.length === 0) {
3728
+ throw new NotFoundError6("Request item not found.");
3729
+ }
3730
+ setCache(cacheKey, data[0], 15 * 60).then(() => {
3731
+ logger23.info(`Cache set for key: ${cacheKey}`);
3732
+ }).catch((err) => {
3733
+ logger23.error(`Failed to set cache for key: ${cacheKey}`, err);
3734
+ });
3735
+ return data[0];
3736
+ } catch (error) {
3737
+ throw error;
3738
+ }
3739
+ }
3740
+ async function approveRequestItem(_id, remarks, session) {
3741
+ try {
3742
+ _id = new ObjectId14(_id);
3743
+ } catch (error) {
3744
+ throw new BadRequestError23("Invalid request item ID format.");
3745
+ }
3746
+ try {
3747
+ const updateValue = {
3748
+ status: "approved",
3749
+ remarks: remarks || "",
3750
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3751
+ };
3752
+ const res = await collection.updateOne(
3753
+ { _id },
3754
+ { $set: updateValue },
3755
+ { session }
3756
+ );
3757
+ if (res.modifiedCount === 0) {
3758
+ throw new InternalServerError7("Unable to approve request item.");
3759
+ }
3760
+ delNamespace().then(() => {
3761
+ logger23.info(`Cache cleared for namespace: ${namespace_collection}`);
3762
+ }).catch((err) => {
3763
+ logger23.error(
3764
+ `Failed to clear cache for namespace: ${namespace_collection}`,
3765
+ err
3766
+ );
3767
+ });
3768
+ return res.modifiedCount;
3769
+ } catch (error) {
3770
+ throw error;
3771
+ }
3772
+ }
3773
+ async function disapproveRequestItem(_id, remarks, session) {
3774
+ try {
3775
+ _id = new ObjectId14(_id);
3776
+ } catch (error) {
3777
+ throw new BadRequestError23("Invalid request item ID format.");
3778
+ }
3779
+ try {
3780
+ const updateValue = {
3781
+ status: "disapproved",
3782
+ remarks: remarks || "",
3783
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3784
+ };
3785
+ const res = await collection.updateOne(
3786
+ { _id },
3787
+ { $set: updateValue },
3788
+ { session }
3789
+ );
3790
+ if (res.modifiedCount === 0) {
3791
+ throw new InternalServerError7("Unable to disapprove request item.");
3792
+ }
3793
+ delNamespace().then(() => {
3794
+ logger23.info(`Cache cleared for namespace: ${namespace_collection}`);
3795
+ }).catch((err) => {
3796
+ logger23.error(
3797
+ `Failed to clear cache for namespace: ${namespace_collection}`,
3798
+ err
3799
+ );
3800
+ });
3801
+ return res.modifiedCount;
3802
+ } catch (error) {
3803
+ throw error;
3804
+ }
3805
+ }
3806
+ return {
3807
+ createIndex,
3808
+ createTextIndex,
3809
+ createRequestItem,
3810
+ getRequestItems,
3811
+ getRequestItemById,
3812
+ approveRequestItem,
3813
+ disapproveRequestItem
3814
+ };
3815
+ }
3816
+
3817
+ // src/services/hygiene-request-item.service.ts
3818
+ import { BadRequestError as BadRequestError24, useAtlas as useAtlas11 } from "@iservice365/node-server-utils";
3819
+ import { useUserRepo } from "@iservice365/core";
3820
+ function useRequestItemService() {
3821
+ const {
3822
+ createRequestItem: _createRequestItem,
3823
+ getRequestItemById: _getRequestItemById,
3824
+ approveRequestItem: _approveRequestItem,
3825
+ disapproveRequestItem: _disapproveRequestItem
3826
+ } = useRequestItemRepository();
3827
+ const { getSupplyById } = useSupplyRepository();
3828
+ const { getUserById } = useUserRepo();
3829
+ const { createStock } = useStockService();
3830
+ async function createRequestItem(value) {
3831
+ try {
3832
+ const { supply, createdBy } = value;
3833
+ const supplyData = await getSupplyById(supply);
3834
+ const createdByData = await getUserById(createdBy);
3835
+ const createdRequestItem = await _createRequestItem({
3836
+ ...value,
3837
+ supplyName: supplyData?.name || "",
3838
+ createdByName: createdByData?.name || ""
3839
+ });
3840
+ return createdRequestItem;
3841
+ } catch (error) {
3842
+ throw error;
3843
+ }
3844
+ }
3845
+ async function createRequestItemByBatch(value) {
3846
+ const session = useAtlas11.getClient()?.startSession();
3847
+ try {
3848
+ session?.startTransaction();
3849
+ const { site, createdBy, items } = value;
3850
+ const createdByData = await getUserById(createdBy);
3851
+ const createdRequestItemIds = [];
3852
+ for (const item of items) {
3853
+ const supplyData = await getSupplyById(item.supply, session);
3854
+ const createdId = await _createRequestItem(
3855
+ {
3856
+ site,
3857
+ supply: item.supply,
3858
+ qty: item.qty,
3859
+ supplyName: supplyData?.name || "",
3860
+ createdBy,
3861
+ createdByName: createdByData?.name || ""
3862
+ },
3863
+ session
3864
+ );
3865
+ createdRequestItemIds.push(createdId);
3866
+ }
3867
+ await session?.commitTransaction();
3868
+ return createdRequestItemIds;
3869
+ } catch (error) {
3870
+ await session?.abortTransaction();
3871
+ throw error;
3872
+ } finally {
3873
+ await session?.endSession();
3874
+ }
3875
+ }
3876
+ async function approveRequestItem(id, remarks) {
3877
+ const session = useAtlas11.getClient()?.startSession();
3878
+ try {
3879
+ session?.startTransaction();
3880
+ await _approveRequestItem(id, remarks, session);
3881
+ const requestItem = await _getRequestItemById(id, session);
3882
+ if (requestItem.status !== "pending") {
3883
+ throw new BadRequestError24(
3884
+ "Only 'pending' request items can be approved."
3885
+ );
3886
+ }
3887
+ const createdStocks = await createStock(
3888
+ {
3889
+ site: requestItem.site.toString(),
3890
+ supply: requestItem.supply.toString(),
3891
+ qty: requestItem.qty,
3892
+ remarks
3893
+ },
3894
+ true
3895
+ );
3896
+ await session?.commitTransaction();
3897
+ return createdStocks;
3898
+ } catch (error) {
3899
+ await session?.abortTransaction();
3900
+ throw error;
3901
+ } finally {
3902
+ await session?.endSession();
3903
+ }
3904
+ }
3905
+ async function disapproveRequestItem(id, remarks) {
3906
+ const session = useAtlas11.getClient()?.startSession();
3907
+ try {
3908
+ session?.startTransaction();
3909
+ const result = await _disapproveRequestItem(id, remarks, session);
3910
+ const requestItem = await _getRequestItemById(id, session);
3911
+ if (requestItem.status !== "pending") {
3912
+ throw new BadRequestError24(
3913
+ "Only 'pending' request items can be disapproved."
3914
+ );
3915
+ }
3916
+ await session?.commitTransaction();
3917
+ return result;
3918
+ } catch (error) {
3919
+ await session?.abortTransaction();
3920
+ throw error;
3921
+ } finally {
3922
+ await session?.endSession();
3923
+ }
3924
+ }
3925
+ return {
3926
+ createRequestItem,
3927
+ createRequestItemByBatch,
3928
+ approveRequestItem,
3929
+ disapproveRequestItem
3930
+ };
3931
+ }
3932
+
3933
+ // src/controllers/hygiene-request-item.controller.ts
3934
+ import { BadRequestError as BadRequestError25, logger as logger24 } from "@iservice365/node-server-utils";
3935
+ import Joi14 from "joi";
3936
+ function useRequestItemController() {
3937
+ const {
3938
+ getRequestItems: _getRequestItems,
3939
+ getRequestItemById: _getRequestItemById
3940
+ } = useRequestItemRepository();
3941
+ const {
3942
+ createRequestItem: _createRequestItem,
3943
+ createRequestItemByBatch: _createRequestItemByBatch,
3944
+ approveRequestItem: _approveRequestItem,
3945
+ disapproveRequestItem: _disapproveRequestItem
3946
+ } = useRequestItemService();
3947
+ async function createRequestItem(req, res, next) {
3948
+ const cookies = req.headers.cookie ? req.headers.cookie.split(";").map((cookie) => cookie.trim().split("=")).reduce(
3949
+ (acc, [key, value]) => ({ ...acc, [key]: value }),
3950
+ {}
3951
+ ) : {};
3952
+ const createdBy = cookies["user"] || "";
3953
+ const payload = {
3954
+ ...req.body,
3955
+ ...req.params,
3956
+ createdBy
3957
+ };
3958
+ const validation = Joi14.object({
3959
+ site: Joi14.string().hex().required(),
3960
+ supply: Joi14.string().hex().required(),
3961
+ qty: Joi14.number().min(0).required(),
3962
+ createdBy: Joi14.string().hex().required()
3298
3963
  });
3299
3964
  const { error } = validation.validate(payload);
3300
3965
  if (error) {
3301
- logger20.log({ level: "error", message: error.message });
3302
- next(new BadRequestError19(error.message));
3966
+ logger24.log({ level: "error", message: error.message });
3967
+ next(new BadRequestError25(error.message));
3303
3968
  return;
3304
3969
  }
3305
3970
  try {
3306
- const { id, ...value } = payload;
3307
- await _updateSupply(id, value);
3308
- res.json({ message: "Supply updated successfully." });
3971
+ const id = await _createRequestItem(payload);
3972
+ res.status(201).json({ message: "Request item created successfully.", id });
3973
+ return;
3974
+ } catch (error2) {
3975
+ logger24.log({ level: "error", message: error2.message });
3976
+ next(error2);
3977
+ return;
3978
+ }
3979
+ }
3980
+ async function createRequestItemByBatch(req, res, next) {
3981
+ const cookies = req.headers.cookie ? req.headers.cookie.split(";").map((cookie) => cookie.trim().split("=")).reduce(
3982
+ (acc, [key, value]) => ({ ...acc, [key]: value }),
3983
+ {}
3984
+ ) : {};
3985
+ const createdBy = cookies["user"] || "";
3986
+ const payload = {
3987
+ ...req.body,
3988
+ ...req.params,
3989
+ createdBy
3990
+ };
3991
+ const validation = Joi14.object({
3992
+ site: Joi14.string().hex().required(),
3993
+ createdBy: Joi14.string().hex().required(),
3994
+ items: Joi14.array().items(
3995
+ Joi14.object({
3996
+ supply: Joi14.string().hex().required(),
3997
+ qty: Joi14.number().min(0).required()
3998
+ })
3999
+ ).min(1).required()
4000
+ });
4001
+ const { error } = validation.validate(payload);
4002
+ if (error) {
4003
+ logger24.log({ level: "error", message: error.message });
4004
+ next(new BadRequestError25(error.message));
4005
+ return;
4006
+ }
4007
+ try {
4008
+ await _createRequestItemByBatch(payload);
4009
+ res.status(201).json({ message: "Request items created successfully." });
4010
+ return;
4011
+ } catch (error2) {
4012
+ logger24.log({ level: "error", message: error2.message });
4013
+ next(error2);
4014
+ return;
4015
+ }
4016
+ }
4017
+ async function getRequestItems(req, res, next) {
4018
+ const query = { ...req.query, ...req.params };
4019
+ const validation = Joi14.object({
4020
+ page: Joi14.number().min(1).optional().allow("", null),
4021
+ limit: Joi14.number().min(1).optional().allow("", null),
4022
+ search: Joi14.string().optional().allow("", null),
4023
+ site: Joi14.string().hex().required()
4024
+ });
4025
+ const { error } = validation.validate(query);
4026
+ if (error) {
4027
+ logger24.log({ level: "error", message: error.message });
4028
+ next(new BadRequestError25(error.message));
4029
+ return;
4030
+ }
4031
+ const page = parseInt(req.query.page) ?? 1;
4032
+ const limit = parseInt(req.query.limit) ?? 10;
4033
+ const search = req.query.search ?? "";
4034
+ const site = req.params.site ?? "";
4035
+ try {
4036
+ const data = await _getRequestItems({
4037
+ page,
4038
+ limit,
4039
+ search,
4040
+ site
4041
+ });
4042
+ res.json(data);
4043
+ return;
4044
+ } catch (error2) {
4045
+ logger24.log({ level: "error", message: error2.message });
4046
+ next(error2);
4047
+ return;
4048
+ }
4049
+ }
4050
+ async function getRequestItemById(req, res, next) {
4051
+ const validation = Joi14.string().hex().required();
4052
+ const _id = req.params.id;
4053
+ const { error } = validation.validate(_id);
4054
+ if (error) {
4055
+ logger24.log({ level: "error", message: error.message });
4056
+ next(new BadRequestError25(error.message));
4057
+ return;
4058
+ }
4059
+ try {
4060
+ const data = await _getRequestItemById(_id);
4061
+ res.json(data);
4062
+ return;
4063
+ } catch (error2) {
4064
+ logger24.log({ level: "error", message: error2.message });
4065
+ next(error2);
4066
+ return;
4067
+ }
4068
+ }
4069
+ async function approveRequestItem(req, res, next) {
4070
+ const payload = { ...req.params, ...req.body };
4071
+ const validation = Joi14.object({
4072
+ id: Joi14.string().hex().required(),
4073
+ remarks: Joi14.string().optional().allow("", null)
4074
+ });
4075
+ const { error } = validation.validate(payload);
4076
+ if (error) {
4077
+ logger24.log({ level: "error", message: error.message });
4078
+ next(new BadRequestError25(error.message));
4079
+ return;
4080
+ }
4081
+ try {
4082
+ await _approveRequestItem(payload.id, payload.remarks);
4083
+ res.json({ message: "Request item approved successfully." });
3309
4084
  return;
3310
4085
  } catch (error2) {
3311
- logger20.log({ level: "error", message: error2.message });
4086
+ logger24.log({ level: "error", message: error2.message });
3312
4087
  next(error2);
3313
4088
  return;
3314
4089
  }
3315
4090
  }
3316
- async function deleteSupply(req, res, next) {
3317
- const id = req.params.id;
3318
- const validation = Joi11.object({
3319
- id: Joi11.string().hex().required()
4091
+ async function disapproveRequestItem(req, res, next) {
4092
+ const payload = { ...req.params, ...req.body };
4093
+ const validation = Joi14.object({
4094
+ id: Joi14.string().hex().required(),
4095
+ remarks: Joi14.string().optional().allow("", null)
3320
4096
  });
3321
- const { error } = validation.validate({ id });
4097
+ const { error } = validation.validate(payload);
3322
4098
  if (error) {
3323
- logger20.log({ level: "error", message: error.message });
3324
- next(new BadRequestError19(error.message));
4099
+ logger24.log({ level: "error", message: error.message });
4100
+ next(new BadRequestError25(error.message));
3325
4101
  return;
3326
4102
  }
3327
4103
  try {
3328
- await _deleteSupply(id);
3329
- res.json({ message: "Supply deleted successfully." });
4104
+ await _disapproveRequestItem(payload.id, payload.remarks);
4105
+ res.json({ message: "Request item disapproved successfully." });
3330
4106
  return;
3331
4107
  } catch (error2) {
3332
- logger20.log({ level: "error", message: error2.message });
4108
+ logger24.log({ level: "error", message: error2.message });
3333
4109
  next(error2);
3334
4110
  return;
3335
4111
  }
3336
4112
  }
3337
4113
  return {
3338
- createSupply,
3339
- getSupplies,
3340
- getSupplyById,
3341
- updateSupply,
3342
- deleteSupply
4114
+ createRequestItem,
4115
+ createRequestItemByBatch,
4116
+ getRequestItems,
4117
+ getRequestItemById,
4118
+ approveRequestItem,
4119
+ disapproveRequestItem
3343
4120
  };
3344
4121
  }
3345
4122
 
3346
- // src/services/hygiene-stock.service.ts
3347
- import { NotFoundError as NotFoundError5, useAtlas as useAtlas10 } from "@iservice365/node-server-utils";
3348
- function useStockService() {
3349
- const { createStock: _createStock } = useStockRepository();
3350
- const { getSupplyById, updateSupply } = useSupplyRepository();
3351
- async function createStock(value) {
3352
- const session = useAtlas10.getClient()?.startSession();
4123
+ // src/models/hygiene-schedule-task.model.ts
4124
+ import { BadRequestError as BadRequestError26, logger as logger25 } from "@iservice365/node-server-utils";
4125
+ import Joi15 from "joi";
4126
+ import { ObjectId as ObjectId15 } from "mongodb";
4127
+ var allowedFrequency = ["week", "month", "quarter", "year"];
4128
+ var allowedDays = [
4129
+ "Mon",
4130
+ "Tue",
4131
+ "Wed",
4132
+ "Thu",
4133
+ "Fri",
4134
+ "Sat",
4135
+ "Sun"
4136
+ ];
4137
+ var allowedQuarter = ["1st", "2nd", "3rd", "4th"];
4138
+ var allowedWeekOfMonth = [
4139
+ "1st",
4140
+ "2nd",
4141
+ "3rd",
4142
+ "4th",
4143
+ "last"
4144
+ ];
4145
+ var allowedMonths = [
4146
+ "January",
4147
+ "February",
4148
+ "March",
4149
+ "April",
4150
+ "May",
4151
+ "June",
4152
+ "July",
4153
+ "August",
4154
+ "September",
4155
+ "October",
4156
+ "November",
4157
+ "December"
4158
+ ];
4159
+ var scheduleTaskSchema = Joi15.object({
4160
+ site: Joi15.string().hex().required(),
4161
+ title: Joi15.string().required(),
4162
+ frequency: Joi15.string().valid(...allowedFrequency).required(),
4163
+ day: Joi15.string().valid(...allowedDays).required(),
4164
+ weekOfMonth: Joi15.string().valid(...allowedWeekOfMonth).when("frequency", {
4165
+ is: Joi15.string().valid("month", "quarter", "year"),
4166
+ then: Joi15.required(),
4167
+ otherwise: Joi15.optional().allow("", null)
4168
+ }),
4169
+ quarter: Joi15.string().valid(...allowedQuarter).when("frequency", {
4170
+ is: "quarter",
4171
+ then: Joi15.required(),
4172
+ otherwise: Joi15.optional().allow("", null)
4173
+ }),
4174
+ month: Joi15.string().valid(...allowedMonths).when("frequency", {
4175
+ is: Joi15.string().valid("quarter", "year"),
4176
+ then: Joi15.required(),
4177
+ otherwise: Joi15.optional().allow("", null)
4178
+ }),
4179
+ description: Joi15.string().optional().allow("", null),
4180
+ areas: Joi15.array().min(1).items(
4181
+ Joi15.object({
4182
+ name: Joi15.string().required(),
4183
+ value: Joi15.any().required()
4184
+ })
4185
+ ).required()
4186
+ });
4187
+ function MScheduleTask(value) {
4188
+ const { error } = scheduleTaskSchema.validate(value);
4189
+ if (error) {
4190
+ logger25.info(`Hygiene Schedule Task Model: ${error.message}`);
4191
+ throw new BadRequestError26(error.message);
4192
+ }
4193
+ if (value.site) {
3353
4194
  try {
3354
- session?.startTransaction();
3355
- const { qty, ...stockData } = value;
3356
- const supply = await getSupplyById(value.supply);
3357
- if (!supply || supply.qty === void 0) {
3358
- throw new NotFoundError5("Supply not found.");
4195
+ value.site = new ObjectId15(value.site);
4196
+ } catch (error2) {
4197
+ throw new BadRequestError26("Invalid site ID format.");
4198
+ }
4199
+ }
4200
+ if (value.areas && Array.isArray(value.areas)) {
4201
+ value.areas = value.areas.map((area) => {
4202
+ try {
4203
+ return {
4204
+ name: area.name,
4205
+ value: new ObjectId15(area.value.toString())
4206
+ };
4207
+ } catch (error2) {
4208
+ throw new BadRequestError26(`Invalid area value format: ${area.name}`);
3359
4209
  }
3360
- const newSupplyQty = supply.qty + qty;
3361
- await updateSupply(value.supply, { qty: newSupplyQty }, session);
3362
- const createdStock = await _createStock(
3363
- { ...stockData, in: qty, balance: newSupplyQty },
3364
- session
4210
+ });
4211
+ }
4212
+ return {
4213
+ site: value.site,
4214
+ title: value.title,
4215
+ frequency: value.frequency,
4216
+ day: value.day,
4217
+ weekOfMonth: value.weekOfMonth,
4218
+ quarter: value.quarter,
4219
+ month: value.month,
4220
+ description: value.description,
4221
+ areas: value.areas,
4222
+ status: "active",
4223
+ createdAt: /* @__PURE__ */ new Date(),
4224
+ updatedAt: "",
4225
+ deletedAt: ""
4226
+ };
4227
+ }
4228
+
4229
+ // src/repositories/hygiene-schedule-task.repository.ts
4230
+ import { ObjectId as ObjectId16 } from "mongodb";
4231
+ import {
4232
+ useAtlas as useAtlas12,
4233
+ InternalServerError as InternalServerError8,
4234
+ paginate as paginate8,
4235
+ BadRequestError as BadRequestError27,
4236
+ useCache as useCache8,
4237
+ logger as logger26,
4238
+ makeCacheKey as makeCacheKey8,
4239
+ NotFoundError as NotFoundError7
4240
+ } from "@iservice365/node-server-utils";
4241
+ function useScheduleTaskRepository() {
4242
+ const db = useAtlas12.getDb();
4243
+ if (!db) {
4244
+ throw new InternalServerError8("Unable to connect to server.");
4245
+ }
4246
+ const namespace_collection = "site.schedule-tasks";
4247
+ const collection = db.collection(namespace_collection);
4248
+ const { delNamespace, setCache, getCache } = useCache8(namespace_collection);
4249
+ async function createIndex() {
4250
+ try {
4251
+ await collection.createIndexes([
4252
+ { key: { site: 1 } },
4253
+ { key: { status: 1 } }
4254
+ ]);
4255
+ } catch (error) {
4256
+ throw new InternalServerError8(
4257
+ "Failed to create index on hygiene schedule task."
3365
4258
  );
3366
- await session?.commitTransaction();
3367
- return createdStock;
4259
+ }
4260
+ }
4261
+ async function createTextIndex() {
4262
+ try {
4263
+ await collection.createIndex({ title: "text", description: "text" });
4264
+ } catch (error) {
4265
+ throw new InternalServerError8(
4266
+ "Failed to create text index on hygiene schedule task."
4267
+ );
4268
+ }
4269
+ }
4270
+ async function createScheduleTask(value, session) {
4271
+ try {
4272
+ value = MScheduleTask(value);
4273
+ const res = await collection.insertOne(value, { session });
4274
+ delNamespace().then(() => {
4275
+ logger26.info(`Cache cleared for namespace: ${namespace_collection}`);
4276
+ }).catch((err) => {
4277
+ logger26.error(
4278
+ `Failed to clear cache for namespace: ${namespace_collection}`,
4279
+ err
4280
+ );
4281
+ });
4282
+ return res.insertedId;
3368
4283
  } catch (error) {
3369
- await session?.abortTransaction();
3370
4284
  throw error;
3371
- } finally {
3372
- await session?.endSession();
3373
4285
  }
3374
4286
  }
3375
- return { createStock };
4287
+ async function getScheduleTasks({
4288
+ page = 1,
4289
+ limit = 10,
4290
+ search = "",
4291
+ site
4292
+ }) {
4293
+ page = page > 0 ? page - 1 : 0;
4294
+ const query = {
4295
+ status: { $ne: "deleted" }
4296
+ };
4297
+ const cacheOptions = {
4298
+ page,
4299
+ limit
4300
+ };
4301
+ try {
4302
+ site = new ObjectId16(site);
4303
+ query.site = site;
4304
+ cacheOptions.site = site.toString();
4305
+ } catch (error) {
4306
+ throw new BadRequestError27("Invalid site ID format.");
4307
+ }
4308
+ if (search) {
4309
+ query.$or = [{ name: { $regex: search, $options: "i" } }];
4310
+ cacheOptions.search = search;
4311
+ }
4312
+ const cacheKey = makeCacheKey8(namespace_collection, cacheOptions);
4313
+ const cachedData = await getCache(cacheKey);
4314
+ if (cachedData) {
4315
+ logger26.info(`Cache hit for key: ${cacheKey}`);
4316
+ return cachedData;
4317
+ }
4318
+ try {
4319
+ const items = await collection.aggregate([
4320
+ { $match: query },
4321
+ {
4322
+ $project: {
4323
+ title: 1,
4324
+ areas: 1,
4325
+ status: 1
4326
+ }
4327
+ },
4328
+ { $sort: { _id: -1 } },
4329
+ { $skip: page * limit },
4330
+ { $limit: limit }
4331
+ ]).toArray();
4332
+ const length = await collection.countDocuments(query);
4333
+ const data = paginate8(items, page, limit, length);
4334
+ setCache(cacheKey, data, 15 * 60).then(() => {
4335
+ logger26.info(`Cache set for key: ${cacheKey}`);
4336
+ }).catch((err) => {
4337
+ logger26.error(`Failed to set cache for key: ${cacheKey}`, err);
4338
+ });
4339
+ return data;
4340
+ } catch (error) {
4341
+ throw error;
4342
+ }
4343
+ }
4344
+ async function getScheduleTaskById(_id, session) {
4345
+ try {
4346
+ _id = new ObjectId16(_id);
4347
+ } catch (error) {
4348
+ throw new BadRequestError27("Invalid schedule task ID format.");
4349
+ }
4350
+ const query = {
4351
+ _id,
4352
+ status: { $ne: "deleted" }
4353
+ };
4354
+ const cacheKey = makeCacheKey8(namespace_collection, {
4355
+ _id: _id.toString()
4356
+ });
4357
+ if (!session) {
4358
+ const cachedData = await getCache(cacheKey);
4359
+ if (cachedData) {
4360
+ logger26.info(`Cache hit for key: ${cacheKey}`);
4361
+ return cachedData;
4362
+ }
4363
+ } else {
4364
+ logger26.info(`Skipping cache during transaction for key: ${cacheKey}`);
4365
+ }
4366
+ try {
4367
+ const data = await collection.aggregate([
4368
+ { $match: query },
4369
+ {
4370
+ $project: {
4371
+ title: 1,
4372
+ frequency: 1,
4373
+ day: 1,
4374
+ weekOfMonth: 1,
4375
+ quarter: 1,
4376
+ month: 1,
4377
+ description: 1,
4378
+ areas: 1,
4379
+ status: 1,
4380
+ createdAt: 1
4381
+ }
4382
+ }
4383
+ ]).toArray();
4384
+ if (!data || data.length === 0) {
4385
+ throw new NotFoundError7("Schedule task not found.");
4386
+ }
4387
+ setCache(cacheKey, data[0], 15 * 60).then(() => {
4388
+ logger26.info(`Cache set for key: ${cacheKey}`);
4389
+ }).catch((err) => {
4390
+ logger26.error(`Failed to set cache for key: ${cacheKey}`, err);
4391
+ });
4392
+ return data[0];
4393
+ } catch (error) {
4394
+ throw error;
4395
+ }
4396
+ }
4397
+ async function updateScheduleTask(_id, value, session) {
4398
+ try {
4399
+ _id = new ObjectId16(_id);
4400
+ } catch (error) {
4401
+ throw new BadRequestError27("Invalid schedule task ID format.");
4402
+ }
4403
+ if (value.areas && Array.isArray(value.areas)) {
4404
+ value.areas = value.areas.map((area) => {
4405
+ try {
4406
+ return {
4407
+ name: area.name,
4408
+ value: new ObjectId16(area.value.toString())
4409
+ };
4410
+ } catch (error) {
4411
+ throw new BadRequestError27(`Invalid area value format: ${area.name}`);
4412
+ }
4413
+ });
4414
+ }
4415
+ try {
4416
+ const updateValue = { ...value, updatedAt: /* @__PURE__ */ new Date() };
4417
+ const res = await collection.updateOne(
4418
+ { _id },
4419
+ { $set: updateValue },
4420
+ { session }
4421
+ );
4422
+ if (res.modifiedCount === 0) {
4423
+ throw new InternalServerError8(
4424
+ "Unable to update hygiene schedule task."
4425
+ );
4426
+ }
4427
+ delNamespace().then(() => {
4428
+ logger26.info(`Cache cleared for namespace: ${namespace_collection}`);
4429
+ }).catch((err) => {
4430
+ logger26.error(
4431
+ `Failed to clear cache for namespace: ${namespace_collection}`,
4432
+ err
4433
+ );
4434
+ });
4435
+ return res.modifiedCount;
4436
+ } catch (error) {
4437
+ throw error;
4438
+ }
4439
+ }
4440
+ return {
4441
+ createIndex,
4442
+ createTextIndex,
4443
+ createScheduleTask,
4444
+ getScheduleTasks,
4445
+ getScheduleTaskById,
4446
+ updateScheduleTask
4447
+ };
3376
4448
  }
3377
4449
 
3378
- // src/controllers/hygiene-stock.controller.ts
3379
- import { BadRequestError as BadRequestError20, logger as logger21 } from "@iservice365/node-server-utils";
3380
- import Joi12 from "joi";
3381
- function useStockController() {
3382
- const { createStock: _createStock } = useStockService();
3383
- async function createStock(req, res, next) {
4450
+ // src/controllers/hygiene-schedule-task.controller.ts
4451
+ import { BadRequestError as BadRequestError28, logger as logger27 } from "@iservice365/node-server-utils";
4452
+ import Joi16 from "joi";
4453
+ function useScheduleTaskController() {
4454
+ const {
4455
+ createScheduleTask: _createScheduleTask,
4456
+ getScheduleTasks: _getScheduleTasks,
4457
+ getScheduleTaskById: _getScheduleTaskById,
4458
+ updateScheduleTask: _updateScheduleTask
4459
+ } = useScheduleTaskRepository();
4460
+ async function createScheduleTask(req, res, next) {
3384
4461
  const payload = { ...req.body, ...req.params };
3385
- const validation = Joi12.object({
3386
- site: Joi12.string().hex().required(),
3387
- supply: Joi12.string().hex().required(),
3388
- qty: Joi12.number().min(0).optional(),
3389
- remarks: Joi12.string().optional().allow("", null)
4462
+ const { error } = scheduleTaskSchema.validate(payload);
4463
+ if (error) {
4464
+ logger27.log({ level: "error", message: error.message });
4465
+ next(new BadRequestError28(error.message));
4466
+ return;
4467
+ }
4468
+ try {
4469
+ const id = await _createScheduleTask(payload);
4470
+ res.status(201).json({ message: "Schedule task created successfully.", id });
4471
+ return;
4472
+ } catch (error2) {
4473
+ logger27.log({ level: "error", message: error2.message });
4474
+ next(error2);
4475
+ return;
4476
+ }
4477
+ }
4478
+ async function getScheduleTasks(req, res, next) {
4479
+ const query = { ...req.query, ...req.params };
4480
+ const validation = Joi16.object({
4481
+ page: Joi16.number().min(1).optional().allow("", null),
4482
+ limit: Joi16.number().min(1).optional().allow("", null),
4483
+ search: Joi16.string().optional().allow("", null),
4484
+ site: Joi16.string().hex().required()
4485
+ });
4486
+ const { error } = validation.validate(query);
4487
+ if (error) {
4488
+ logger27.log({ level: "error", message: error.message });
4489
+ next(new BadRequestError28(error.message));
4490
+ return;
4491
+ }
4492
+ const page = parseInt(req.query.page) ?? 1;
4493
+ const limit = parseInt(req.query.limit) ?? 10;
4494
+ const search = req.query.search ?? "";
4495
+ const site = req.params.site ?? "";
4496
+ try {
4497
+ const data = await _getScheduleTasks({
4498
+ page,
4499
+ limit,
4500
+ search,
4501
+ site
4502
+ });
4503
+ res.json(data);
4504
+ return;
4505
+ } catch (error2) {
4506
+ logger27.log({ level: "error", message: error2.message });
4507
+ next(error2);
4508
+ return;
4509
+ }
4510
+ }
4511
+ async function getScheduleTaskById(req, res, next) {
4512
+ const validation = Joi16.string().hex().required();
4513
+ const _id = req.params.id;
4514
+ const { error } = validation.validate(_id);
4515
+ if (error) {
4516
+ logger27.log({ level: "error", message: error.message });
4517
+ next(new BadRequestError28(error.message));
4518
+ return;
4519
+ }
4520
+ try {
4521
+ const data = await _getScheduleTaskById(_id);
4522
+ res.json(data);
4523
+ return;
4524
+ } catch (error2) {
4525
+ logger27.log({ level: "error", message: error2.message });
4526
+ next(error2);
4527
+ return;
4528
+ }
4529
+ }
4530
+ async function updateScheduleTask(req, res, next) {
4531
+ const payload = { id: req.params.id, ...req.body };
4532
+ const validation = Joi16.object({
4533
+ id: Joi16.string().hex().required(),
4534
+ title: Joi16.string().optional().allow("", null),
4535
+ frequency: Joi16.string().valid(...allowedFrequency).optional().allow("", null),
4536
+ day: Joi16.string().valid(...allowedDays).optional().allow("", null),
4537
+ weekOfMonth: Joi16.string().valid(...allowedWeekOfMonth).optional().allow("", null),
4538
+ quarter: Joi16.string().valid(...allowedQuarter).optional().allow("", null),
4539
+ month: Joi16.string().valid(...allowedMonths).optional().allow("", null),
4540
+ description: Joi16.string().optional().allow("", null),
4541
+ areas: Joi16.array().min(1).items(
4542
+ Joi16.object({
4543
+ name: Joi16.string().required(),
4544
+ value: Joi16.any().required()
4545
+ })
4546
+ ).optional()
3390
4547
  });
3391
4548
  const { error } = validation.validate(payload);
3392
4549
  if (error) {
3393
- logger21.log({ level: "error", message: error.message });
3394
- next(new BadRequestError20(error.message));
4550
+ logger27.log({ level: "error", message: error.message });
4551
+ next(new BadRequestError28(error.message));
3395
4552
  return;
3396
4553
  }
3397
4554
  try {
3398
- const id = await _createStock(payload);
3399
- res.status(201).json({ message: "Stock created successfully.", id });
4555
+ const { id, ...value } = payload;
4556
+ await _updateScheduleTask(id, value);
4557
+ res.json({ message: "Schedule task updated successfully." });
3400
4558
  return;
3401
4559
  } catch (error2) {
3402
- logger21.log({ level: "error", message: error2.message });
4560
+ logger27.log({ level: "error", message: error2.message });
3403
4561
  next(error2);
3404
4562
  return;
3405
4563
  }
3406
4564
  }
3407
4565
  return {
3408
- createStock
4566
+ createScheduleTask,
4567
+ getScheduleTasks,
4568
+ getScheduleTaskById,
4569
+ updateScheduleTask
3409
4570
  };
3410
4571
  }
3411
4572
  export {
3412
4573
  MArea,
3413
4574
  MAreaChecklist,
3414
4575
  MParentChecklist,
4576
+ MRequestItem,
4577
+ MScheduleTask,
3415
4578
  MStock,
3416
4579
  MSupply,
3417
4580
  MUnit,
3418
4581
  allowedChecklistStatus,
4582
+ allowedDays,
4583
+ allowedFrequency,
4584
+ allowedMonths,
4585
+ allowedQuarter,
4586
+ allowedRequestItemStatus,
3419
4587
  allowedStatus,
3420
4588
  allowedTypes,
4589
+ allowedWeekOfMonth,
3421
4590
  areaChecklistSchema,
3422
4591
  areaSchema,
3423
4592
  parentChecklistSchema,
4593
+ requestItemSchema,
4594
+ scheduleTaskSchema,
3424
4595
  stockSchema,
3425
4596
  supplySchema,
3426
4597
  unitSchema,
@@ -3432,12 +4603,16 @@ export {
3432
4603
  useAreaService,
3433
4604
  useParentChecklistController,
3434
4605
  useParentChecklistRepo,
4606
+ useRequestItemController,
4607
+ useRequestItemRepository,
4608
+ useRequestItemService,
4609
+ useScheduleTaskController,
4610
+ useScheduleTaskRepository,
3435
4611
  useStockController,
3436
4612
  useStockRepository,
3437
4613
  useStockService,
3438
4614
  useSupplyController,
3439
4615
  useSupplyRepository,
3440
- useSupplyService,
3441
4616
  useUnitController,
3442
4617
  useUnitRepository,
3443
4618
  useUnitService