@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/CHANGELOG.md +24 -0
- package/dist/index.d.ts +153 -13
- package/dist/index.js +1357 -192
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1344 -169
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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) ??
|
|
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) ??
|
|
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) ??
|
|
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) ??
|
|
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) ??
|
|
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) ??
|
|
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:
|
|
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
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
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("
|
|
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/
|
|
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 =
|
|
3083
|
-
site:
|
|
3084
|
-
supply:
|
|
3085
|
-
in:
|
|
3086
|
-
out:
|
|
3087
|
-
balance:
|
|
3088
|
-
remarks:
|
|
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
|
-
|
|
3094
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
3269
|
+
BadRequestError as BadRequestError19,
|
|
3129
3270
|
useCache as useCache6,
|
|
3130
|
-
logger as
|
|
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
|
-
|
|
3302
|
+
logger20.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
3158
3303
|
}).catch((err) => {
|
|
3159
|
-
|
|
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
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
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-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
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,
|
|
3188
|
-
const supply = await
|
|
3189
|
-
|
|
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
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
balance:
|
|
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
|
|
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 {
|
|
3438
|
+
return { createStock };
|
|
3208
3439
|
}
|
|
3209
3440
|
|
|
3210
|
-
// src/controllers/hygiene-
|
|
3211
|
-
import { BadRequestError as
|
|
3212
|
-
import
|
|
3213
|
-
function
|
|
3214
|
-
const {
|
|
3215
|
-
|
|
3216
|
-
|
|
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
|
|
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
|
-
|
|
3226
|
-
next(new
|
|
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
|
|
3231
|
-
res.status(201).json({ message: "
|
|
3462
|
+
const id = await _createStock(payload);
|
|
3463
|
+
res.status(201).json({ message: "Stock created successfully.", id });
|
|
3232
3464
|
return;
|
|
3233
3465
|
} catch (error2) {
|
|
3234
|
-
|
|
3466
|
+
logger21.log({ level: "error", message: error2.message });
|
|
3235
3467
|
next(error2);
|
|
3236
3468
|
return;
|
|
3237
3469
|
}
|
|
3238
3470
|
}
|
|
3239
|
-
async function
|
|
3471
|
+
async function getStocksBySupplyId(req, res, next) {
|
|
3240
3472
|
const query = { ...req.query, ...req.params };
|
|
3241
|
-
const validation =
|
|
3242
|
-
page:
|
|
3243
|
-
limit:
|
|
3244
|
-
search:
|
|
3245
|
-
site:
|
|
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
|
-
|
|
3250
|
-
next(new
|
|
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) ??
|
|
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
|
|
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
|
-
|
|
3502
|
+
logger21.log({ level: "error", message: error2.message });
|
|
3268
3503
|
next(error2);
|
|
3269
3504
|
return;
|
|
3270
3505
|
}
|
|
3271
3506
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
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
|
-
|
|
3283
|
-
res.json(data);
|
|
3284
|
-
return;
|
|
3545
|
+
value.supply = new ObjectId13(value.supply);
|
|
3285
3546
|
} catch (error2) {
|
|
3286
|
-
|
|
3287
|
-
next(error2);
|
|
3288
|
-
return;
|
|
3547
|
+
throw new BadRequestError22("Invalid supply ID format.");
|
|
3289
3548
|
}
|
|
3290
3549
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
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
|
-
|
|
3302
|
-
next(new
|
|
3966
|
+
logger24.log({ level: "error", message: error.message });
|
|
3967
|
+
next(new BadRequestError25(error.message));
|
|
3303
3968
|
return;
|
|
3304
3969
|
}
|
|
3305
3970
|
try {
|
|
3306
|
-
const
|
|
3307
|
-
|
|
3308
|
-
|
|
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
|
-
|
|
4086
|
+
logger24.log({ level: "error", message: error2.message });
|
|
3312
4087
|
next(error2);
|
|
3313
4088
|
return;
|
|
3314
4089
|
}
|
|
3315
4090
|
}
|
|
3316
|
-
async function
|
|
3317
|
-
const
|
|
3318
|
-
const validation =
|
|
3319
|
-
id:
|
|
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(
|
|
4097
|
+
const { error } = validation.validate(payload);
|
|
3322
4098
|
if (error) {
|
|
3323
|
-
|
|
3324
|
-
next(new
|
|
4099
|
+
logger24.log({ level: "error", message: error.message });
|
|
4100
|
+
next(new BadRequestError25(error.message));
|
|
3325
4101
|
return;
|
|
3326
4102
|
}
|
|
3327
4103
|
try {
|
|
3328
|
-
await
|
|
3329
|
-
res.json({ message: "
|
|
4104
|
+
await _disapproveRequestItem(payload.id, payload.remarks);
|
|
4105
|
+
res.json({ message: "Request item disapproved successfully." });
|
|
3330
4106
|
return;
|
|
3331
4107
|
} catch (error2) {
|
|
3332
|
-
|
|
4108
|
+
logger24.log({ level: "error", message: error2.message });
|
|
3333
4109
|
next(error2);
|
|
3334
4110
|
return;
|
|
3335
4111
|
}
|
|
3336
4112
|
}
|
|
3337
4113
|
return {
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
4114
|
+
createRequestItem,
|
|
4115
|
+
createRequestItemByBatch,
|
|
4116
|
+
getRequestItems,
|
|
4117
|
+
getRequestItemById,
|
|
4118
|
+
approveRequestItem,
|
|
4119
|
+
disapproveRequestItem
|
|
3343
4120
|
};
|
|
3344
4121
|
}
|
|
3345
4122
|
|
|
3346
|
-
// src/
|
|
3347
|
-
import {
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
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
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
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
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
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
|
-
|
|
3367
|
-
|
|
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
|
-
|
|
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-
|
|
3379
|
-
import { BadRequestError as
|
|
3380
|
-
import
|
|
3381
|
-
function
|
|
3382
|
-
const {
|
|
3383
|
-
|
|
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
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
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
|
-
|
|
3394
|
-
next(new
|
|
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 =
|
|
3399
|
-
|
|
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
|
-
|
|
4560
|
+
logger27.log({ level: "error", message: error2.message });
|
|
3403
4561
|
next(error2);
|
|
3404
4562
|
return;
|
|
3405
4563
|
}
|
|
3406
4564
|
}
|
|
3407
4565
|
return {
|
|
3408
|
-
|
|
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
|