@eeplatform/core 1.4.5 → 1.5.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 +6 -0
- package/dist/index.d.ts +138 -3
- package/dist/index.js +1167 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1176 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -10389,7 +10389,7 @@ var require_ms = __commonJS({
|
|
|
10389
10389
|
options = options || {};
|
|
10390
10390
|
var type = typeof val;
|
|
10391
10391
|
if (type === "string" && val.length > 0) {
|
|
10392
|
-
return
|
|
10392
|
+
return parse4(val);
|
|
10393
10393
|
} else if (type === "number" && isNaN(val) === false) {
|
|
10394
10394
|
return options.long ? fmtLong(val) : fmtShort(val);
|
|
10395
10395
|
}
|
|
@@ -10397,7 +10397,7 @@ var require_ms = __commonJS({
|
|
|
10397
10397
|
"val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
|
|
10398
10398
|
);
|
|
10399
10399
|
};
|
|
10400
|
-
function
|
|
10400
|
+
function parse4(str) {
|
|
10401
10401
|
str = String(str);
|
|
10402
10402
|
if (str.length > 100) {
|
|
10403
10403
|
return;
|
|
@@ -28410,7 +28410,10 @@ function useBuildingUnitService() {
|
|
|
28410
28410
|
try {
|
|
28411
28411
|
await session.startTransaction();
|
|
28412
28412
|
for (let index = 0; index < value.qty; index++) {
|
|
28413
|
-
await _add(
|
|
28413
|
+
await _add(
|
|
28414
|
+
{ ...value.building, name: `${value.building.name} ${index + 1}` },
|
|
28415
|
+
session
|
|
28416
|
+
);
|
|
28414
28417
|
}
|
|
28415
28418
|
await session.commitTransaction();
|
|
28416
28419
|
return "Building unit added successfully.";
|
|
@@ -29925,6 +29928,10 @@ import Joi41 from "joi";
|
|
|
29925
29928
|
import { ObjectId as ObjectId46 } from "mongodb";
|
|
29926
29929
|
var schemaPlantilla = Joi41.object({
|
|
29927
29930
|
_id: Joi41.string().hex().optional().allow(null, ""),
|
|
29931
|
+
org: Joi41.string().hex().required(),
|
|
29932
|
+
orgUnitCode: Joi41.string().optional().allow(null, ""),
|
|
29933
|
+
employmentType: Joi41.string().optional().allow(null, ""),
|
|
29934
|
+
personnelType: Joi41.string().required(),
|
|
29928
29935
|
itemNumber: Joi41.string().required(),
|
|
29929
29936
|
positionTitle: Joi41.string().required(),
|
|
29930
29937
|
positionCategory: Joi41.string().required(),
|
|
@@ -29933,8 +29940,7 @@ var schemaPlantilla = Joi41.object({
|
|
|
29933
29940
|
division: Joi41.string().hex().optional().allow(null, ""),
|
|
29934
29941
|
divisionName: Joi41.string().optional().allow(null, ""),
|
|
29935
29942
|
salaryGrade: Joi41.number().required(),
|
|
29936
|
-
|
|
29937
|
-
incumbent: Joi41.string().optional().allow(null, ""),
|
|
29943
|
+
employeeName: Joi41.string().optional().allow(null, ""),
|
|
29938
29944
|
annualSalary: Joi41.number().optional().allow(null, 0),
|
|
29939
29945
|
monthlySalary: Joi41.number().optional().allow(null, 0),
|
|
29940
29946
|
status: Joi41.string().required(),
|
|
@@ -29962,14 +29968,18 @@ function MPlantilla(data) {
|
|
|
29962
29968
|
positionCategory: data.positionCategory ?? "",
|
|
29963
29969
|
status: data.status ?? "active",
|
|
29964
29970
|
salaryGrade: data.salaryGrade ?? 0,
|
|
29965
|
-
step: data.step ?? 0,
|
|
29966
29971
|
monthlySalary: data.monthlySalary ?? 0,
|
|
29967
29972
|
annualSalary: data.annualSalary ?? 0,
|
|
29968
29973
|
region: data.region ?? "",
|
|
29969
29974
|
regionName: data.regionName ?? "",
|
|
29970
29975
|
division: data.division ?? "",
|
|
29971
29976
|
divisionName: data.divisionName ?? "",
|
|
29972
|
-
|
|
29977
|
+
org: data.org ?? "",
|
|
29978
|
+
orgUnitCode: data.orgUnitCode ?? "",
|
|
29979
|
+
employmentType: data.employmentType ?? "",
|
|
29980
|
+
personnelType: data.personnelType ?? "",
|
|
29981
|
+
employee: data.employee ?? "",
|
|
29982
|
+
employeeName: data.employeeName ?? "",
|
|
29973
29983
|
createdAt: data.createdAt ? new Date(data.createdAt) : /* @__PURE__ */ new Date(),
|
|
29974
29984
|
updatedAt: data.updatedAt ? new Date(data.updatedAt) : "",
|
|
29975
29985
|
deletedAt: data.deletedAt ? new Date(data.deletedAt) : ""
|
|
@@ -30319,7 +30329,8 @@ function usePlantillaService() {
|
|
|
30319
30329
|
salaryGrade: parseInt(
|
|
30320
30330
|
plantillaData.salarygrade || plantillaData.salary_grade || "1"
|
|
30321
30331
|
) || 1,
|
|
30322
|
-
|
|
30332
|
+
org: "",
|
|
30333
|
+
personnelType: "",
|
|
30323
30334
|
status: status.trim() || "active"
|
|
30324
30335
|
};
|
|
30325
30336
|
if (region)
|
|
@@ -30332,9 +30343,6 @@ function usePlantillaService() {
|
|
|
30332
30343
|
if (plantillaData.divisionname || plantillaData.division_name) {
|
|
30333
30344
|
plantilla.divisionName = plantillaData.divisionname || plantillaData.division_name;
|
|
30334
30345
|
}
|
|
30335
|
-
if (plantillaData.incumbent) {
|
|
30336
|
-
plantilla.incumbent = plantillaData.incumbent;
|
|
30337
|
-
}
|
|
30338
30346
|
if (plantillaData.employee) {
|
|
30339
30347
|
plantilla.employee = plantillaData.employee;
|
|
30340
30348
|
}
|
|
@@ -30615,6 +30623,1154 @@ function usePlantillaController() {
|
|
|
30615
30623
|
bulkAddPlantillas
|
|
30616
30624
|
};
|
|
30617
30625
|
}
|
|
30626
|
+
|
|
30627
|
+
// src/models/office.model.ts
|
|
30628
|
+
import { BadRequestError as BadRequestError78 } from "@eeplatform/nodejs-utils";
|
|
30629
|
+
import Joi43 from "joi";
|
|
30630
|
+
import { ObjectId as ObjectId48 } from "mongodb";
|
|
30631
|
+
var schemaOffice = Joi43.object({
|
|
30632
|
+
_id: Joi43.string().hex().optional().allow(null, ""),
|
|
30633
|
+
name: Joi43.string().required(),
|
|
30634
|
+
code: Joi43.string().required(),
|
|
30635
|
+
type: Joi43.string().required(),
|
|
30636
|
+
parent: Joi43.string().hex().optional().allow(null, ""),
|
|
30637
|
+
path: Joi43.string().required(),
|
|
30638
|
+
status: Joi43.string().required(),
|
|
30639
|
+
createdAt: Joi43.date().iso().optional().allow(null, ""),
|
|
30640
|
+
updatedAt: Joi43.date().iso().optional().allow(null, ""),
|
|
30641
|
+
deletedAt: Joi43.date().iso().optional().allow(null, "")
|
|
30642
|
+
});
|
|
30643
|
+
function MOffice(data) {
|
|
30644
|
+
const { error } = schemaOffice.validate(data);
|
|
30645
|
+
if (error) {
|
|
30646
|
+
throw new BadRequestError78(error.message);
|
|
30647
|
+
}
|
|
30648
|
+
if (data._id && typeof data._id === "string") {
|
|
30649
|
+
try {
|
|
30650
|
+
data._id = new ObjectId48(data._id);
|
|
30651
|
+
} catch (error2) {
|
|
30652
|
+
throw new BadRequestError78("Invalid _id.");
|
|
30653
|
+
}
|
|
30654
|
+
}
|
|
30655
|
+
if (data.parent && typeof data.parent === "string") {
|
|
30656
|
+
try {
|
|
30657
|
+
data.parent = new ObjectId48(data.parent);
|
|
30658
|
+
} catch (error2) {
|
|
30659
|
+
throw new BadRequestError78("Invalid parent.");
|
|
30660
|
+
}
|
|
30661
|
+
}
|
|
30662
|
+
return {
|
|
30663
|
+
_id: data._id,
|
|
30664
|
+
name: data.name ?? "",
|
|
30665
|
+
code: data.code ?? "",
|
|
30666
|
+
type: data.type ?? "",
|
|
30667
|
+
parent: data.parent ?? "",
|
|
30668
|
+
path: data.path ?? "",
|
|
30669
|
+
status: data.status ?? "active",
|
|
30670
|
+
createdAt: data.createdAt ? new Date(data.createdAt) : /* @__PURE__ */ new Date(),
|
|
30671
|
+
updatedAt: data.updatedAt ? new Date(data.updatedAt) : "",
|
|
30672
|
+
deletedAt: data.deletedAt ? new Date(data.deletedAt) : ""
|
|
30673
|
+
};
|
|
30674
|
+
}
|
|
30675
|
+
|
|
30676
|
+
// src/repositories/office.repository.ts
|
|
30677
|
+
import {
|
|
30678
|
+
AppError as AppError19,
|
|
30679
|
+
BadRequestError as BadRequestError79,
|
|
30680
|
+
InternalServerError as InternalServerError29,
|
|
30681
|
+
logger as logger38,
|
|
30682
|
+
makeCacheKey as makeCacheKey24,
|
|
30683
|
+
paginate as paginate20,
|
|
30684
|
+
useAtlas as useAtlas40,
|
|
30685
|
+
useCache as useCache25
|
|
30686
|
+
} from "@eeplatform/nodejs-utils";
|
|
30687
|
+
import { ObjectId as ObjectId49 } from "mongodb";
|
|
30688
|
+
function useOfficeRepo() {
|
|
30689
|
+
const db = useAtlas40.getDb();
|
|
30690
|
+
if (!db) {
|
|
30691
|
+
throw new Error("Unable to connect to server.");
|
|
30692
|
+
}
|
|
30693
|
+
const namespace_collection = "offices";
|
|
30694
|
+
const collection = db.collection(namespace_collection);
|
|
30695
|
+
const { getCache, setCache, delNamespace } = useCache25(namespace_collection);
|
|
30696
|
+
async function createIndexes() {
|
|
30697
|
+
try {
|
|
30698
|
+
await collection.createIndexes([
|
|
30699
|
+
{
|
|
30700
|
+
key: { name: 1, code: 1 },
|
|
30701
|
+
unique: true,
|
|
30702
|
+
name: "unique_name_code_index"
|
|
30703
|
+
},
|
|
30704
|
+
{ key: { type: 1 } },
|
|
30705
|
+
{ key: { parent: 1 } },
|
|
30706
|
+
{ key: { status: 1 } }
|
|
30707
|
+
]);
|
|
30708
|
+
} catch (error) {
|
|
30709
|
+
throw new Error("Failed to create index on offices.");
|
|
30710
|
+
}
|
|
30711
|
+
}
|
|
30712
|
+
async function add(value, session, clearCache = true) {
|
|
30713
|
+
try {
|
|
30714
|
+
value = MOffice(value);
|
|
30715
|
+
const res = await collection.insertOne(value, { session });
|
|
30716
|
+
if (clearCache) {
|
|
30717
|
+
delCachedData();
|
|
30718
|
+
}
|
|
30719
|
+
return res.insertedId;
|
|
30720
|
+
} catch (error) {
|
|
30721
|
+
logger38.log({
|
|
30722
|
+
level: "error",
|
|
30723
|
+
message: error.message
|
|
30724
|
+
});
|
|
30725
|
+
if (error instanceof AppError19) {
|
|
30726
|
+
throw error;
|
|
30727
|
+
} else {
|
|
30728
|
+
const isDuplicated = error.message.includes("duplicate");
|
|
30729
|
+
if (isDuplicated) {
|
|
30730
|
+
throw new BadRequestError79("Office already exists.");
|
|
30731
|
+
}
|
|
30732
|
+
throw new Error("Failed to create office.");
|
|
30733
|
+
}
|
|
30734
|
+
}
|
|
30735
|
+
}
|
|
30736
|
+
async function updateById(_id, value, session) {
|
|
30737
|
+
try {
|
|
30738
|
+
_id = new ObjectId49(_id);
|
|
30739
|
+
} catch (error) {
|
|
30740
|
+
throw new BadRequestError79("Invalid ID.");
|
|
30741
|
+
}
|
|
30742
|
+
value.updatedAt = /* @__PURE__ */ new Date();
|
|
30743
|
+
try {
|
|
30744
|
+
const res = await collection.updateOne(
|
|
30745
|
+
{ _id },
|
|
30746
|
+
{ $set: value },
|
|
30747
|
+
{ session }
|
|
30748
|
+
);
|
|
30749
|
+
delCachedData();
|
|
30750
|
+
return res;
|
|
30751
|
+
} catch (error) {
|
|
30752
|
+
logger38.log({
|
|
30753
|
+
level: "error",
|
|
30754
|
+
message: error.message
|
|
30755
|
+
});
|
|
30756
|
+
if (error instanceof AppError19) {
|
|
30757
|
+
throw error;
|
|
30758
|
+
} else {
|
|
30759
|
+
throw new Error("Failed to update office.");
|
|
30760
|
+
}
|
|
30761
|
+
}
|
|
30762
|
+
}
|
|
30763
|
+
async function getAll({
|
|
30764
|
+
search = "",
|
|
30765
|
+
page = 1,
|
|
30766
|
+
limit = 10,
|
|
30767
|
+
sort = {},
|
|
30768
|
+
type = "",
|
|
30769
|
+
parent = "",
|
|
30770
|
+
status = "active"
|
|
30771
|
+
} = {}) {
|
|
30772
|
+
page = page > 0 ? page - 1 : 0;
|
|
30773
|
+
const query = {
|
|
30774
|
+
status
|
|
30775
|
+
};
|
|
30776
|
+
sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
|
|
30777
|
+
if (search) {
|
|
30778
|
+
query.$text = { $search: search };
|
|
30779
|
+
}
|
|
30780
|
+
if (type) {
|
|
30781
|
+
query.type = type;
|
|
30782
|
+
}
|
|
30783
|
+
if (parent) {
|
|
30784
|
+
try {
|
|
30785
|
+
query.parent = new ObjectId49(parent);
|
|
30786
|
+
} catch (error) {
|
|
30787
|
+
throw new BadRequestError79("Invalid parent ID.");
|
|
30788
|
+
}
|
|
30789
|
+
}
|
|
30790
|
+
const cacheParams = {
|
|
30791
|
+
page,
|
|
30792
|
+
limit,
|
|
30793
|
+
sort: JSON.stringify(sort)
|
|
30794
|
+
};
|
|
30795
|
+
if (search)
|
|
30796
|
+
cacheParams.search = search;
|
|
30797
|
+
if (type)
|
|
30798
|
+
cacheParams.type = type;
|
|
30799
|
+
if (parent)
|
|
30800
|
+
cacheParams.parent = parent;
|
|
30801
|
+
if (status !== "active")
|
|
30802
|
+
cacheParams.status = status;
|
|
30803
|
+
const cacheKey = makeCacheKey24(namespace_collection, cacheParams);
|
|
30804
|
+
logger38.log({
|
|
30805
|
+
level: "info",
|
|
30806
|
+
message: `Cache key for getAll offices: ${cacheKey}`
|
|
30807
|
+
});
|
|
30808
|
+
try {
|
|
30809
|
+
const cached = await getCache(cacheKey);
|
|
30810
|
+
if (cached) {
|
|
30811
|
+
logger38.log({
|
|
30812
|
+
level: "info",
|
|
30813
|
+
message: `Cache hit for getAll offices: ${cacheKey}`
|
|
30814
|
+
});
|
|
30815
|
+
return cached;
|
|
30816
|
+
}
|
|
30817
|
+
const items = await collection.aggregate([
|
|
30818
|
+
{ $match: query },
|
|
30819
|
+
{ $sort: sort },
|
|
30820
|
+
{ $skip: page * limit },
|
|
30821
|
+
{ $limit: limit }
|
|
30822
|
+
]).toArray();
|
|
30823
|
+
const length = await collection.countDocuments(query);
|
|
30824
|
+
const data = paginate20(items, page, limit, length);
|
|
30825
|
+
setCache(cacheKey, data, 600).then(() => {
|
|
30826
|
+
logger38.log({
|
|
30827
|
+
level: "info",
|
|
30828
|
+
message: `Cache set for getAll offices: ${cacheKey}`
|
|
30829
|
+
});
|
|
30830
|
+
}).catch((err) => {
|
|
30831
|
+
logger38.log({
|
|
30832
|
+
level: "error",
|
|
30833
|
+
message: `Failed to set cache for getAll offices: ${err.message}`
|
|
30834
|
+
});
|
|
30835
|
+
});
|
|
30836
|
+
return data;
|
|
30837
|
+
} catch (error) {
|
|
30838
|
+
logger38.log({ level: "error", message: `${error}` });
|
|
30839
|
+
throw error;
|
|
30840
|
+
}
|
|
30841
|
+
}
|
|
30842
|
+
async function getById(_id) {
|
|
30843
|
+
try {
|
|
30844
|
+
_id = new ObjectId49(_id);
|
|
30845
|
+
} catch (error) {
|
|
30846
|
+
throw new BadRequestError79("Invalid ID.");
|
|
30847
|
+
}
|
|
30848
|
+
const cacheKey = makeCacheKey24(namespace_collection, { _id: String(_id) });
|
|
30849
|
+
try {
|
|
30850
|
+
const cached = await getCache(cacheKey);
|
|
30851
|
+
if (cached) {
|
|
30852
|
+
logger38.log({
|
|
30853
|
+
level: "info",
|
|
30854
|
+
message: `Cache hit for getById office: ${cacheKey}`
|
|
30855
|
+
});
|
|
30856
|
+
return cached;
|
|
30857
|
+
}
|
|
30858
|
+
const result = await collection.findOne({
|
|
30859
|
+
_id
|
|
30860
|
+
});
|
|
30861
|
+
setCache(cacheKey, result, 300).then(() => {
|
|
30862
|
+
logger38.log({
|
|
30863
|
+
level: "info",
|
|
30864
|
+
message: `Cache set for office by id: ${cacheKey}`
|
|
30865
|
+
});
|
|
30866
|
+
}).catch((err) => {
|
|
30867
|
+
logger38.log({
|
|
30868
|
+
level: "error",
|
|
30869
|
+
message: `Failed to set cache for office by id: ${err.message}`
|
|
30870
|
+
});
|
|
30871
|
+
});
|
|
30872
|
+
return result;
|
|
30873
|
+
} catch (error) {
|
|
30874
|
+
if (error instanceof AppError19) {
|
|
30875
|
+
throw error;
|
|
30876
|
+
} else {
|
|
30877
|
+
throw new InternalServerError29("Failed to get office.");
|
|
30878
|
+
}
|
|
30879
|
+
}
|
|
30880
|
+
}
|
|
30881
|
+
async function deleteById(_id, session) {
|
|
30882
|
+
try {
|
|
30883
|
+
_id = new ObjectId49(_id);
|
|
30884
|
+
} catch (error) {
|
|
30885
|
+
throw new BadRequestError79("Invalid ID.");
|
|
30886
|
+
}
|
|
30887
|
+
try {
|
|
30888
|
+
const res = await collection.updateOne(
|
|
30889
|
+
{ _id },
|
|
30890
|
+
{ $set: { status: "deleted", deletedAt: /* @__PURE__ */ new Date() } }
|
|
30891
|
+
);
|
|
30892
|
+
delCachedData();
|
|
30893
|
+
return res;
|
|
30894
|
+
} catch (error) {
|
|
30895
|
+
logger38.log({
|
|
30896
|
+
level: "error",
|
|
30897
|
+
message: error.message
|
|
30898
|
+
});
|
|
30899
|
+
if (error instanceof AppError19) {
|
|
30900
|
+
throw error;
|
|
30901
|
+
} else {
|
|
30902
|
+
throw new InternalServerError29("Failed to delete office.");
|
|
30903
|
+
}
|
|
30904
|
+
}
|
|
30905
|
+
}
|
|
30906
|
+
function delCachedData() {
|
|
30907
|
+
delNamespace().then(() => {
|
|
30908
|
+
logger38.log({
|
|
30909
|
+
level: "info",
|
|
30910
|
+
message: `Cache namespace cleared for ${namespace_collection}`
|
|
30911
|
+
});
|
|
30912
|
+
}).catch((err) => {
|
|
30913
|
+
logger38.log({
|
|
30914
|
+
level: "error",
|
|
30915
|
+
message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
|
|
30916
|
+
});
|
|
30917
|
+
});
|
|
30918
|
+
}
|
|
30919
|
+
return {
|
|
30920
|
+
createIndexes,
|
|
30921
|
+
add,
|
|
30922
|
+
getAll,
|
|
30923
|
+
getById,
|
|
30924
|
+
updateById,
|
|
30925
|
+
deleteById,
|
|
30926
|
+
delCachedData
|
|
30927
|
+
};
|
|
30928
|
+
}
|
|
30929
|
+
|
|
30930
|
+
// src/services/office.service.ts
|
|
30931
|
+
import { BadRequestError as BadRequestError80, useAtlas as useAtlas41, logger as logger39 } from "@eeplatform/nodejs-utils";
|
|
30932
|
+
import * as XLSX3 from "xlsx";
|
|
30933
|
+
import * as Papa3 from "papaparse";
|
|
30934
|
+
function useOfficeService() {
|
|
30935
|
+
const { add: addOffice, delCachedData } = useOfficeRepo();
|
|
30936
|
+
async function addBulk(file) {
|
|
30937
|
+
logger39.log({
|
|
30938
|
+
level: "info",
|
|
30939
|
+
message: `Starting office bulk upload. File: ${file.originalname}, Size: ${file.size} bytes`
|
|
30940
|
+
});
|
|
30941
|
+
const MAX_SIZE = 16 * 1024 * 1024;
|
|
30942
|
+
let offices = [];
|
|
30943
|
+
let totalSize = 0;
|
|
30944
|
+
let validatedOffices = [];
|
|
30945
|
+
if (!file.buffer) {
|
|
30946
|
+
throw new BadRequestError80("File buffer is empty or corrupted");
|
|
30947
|
+
}
|
|
30948
|
+
try {
|
|
30949
|
+
const fileExtension = file.originalname.split(".").pop()?.toLowerCase();
|
|
30950
|
+
if (fileExtension === "csv") {
|
|
30951
|
+
const csvData = file.buffer.toString("utf-8");
|
|
30952
|
+
totalSize = Buffer.byteLength(csvData, "utf8");
|
|
30953
|
+
if (totalSize > MAX_SIZE) {
|
|
30954
|
+
throw new BadRequestError80(
|
|
30955
|
+
`File size exceeds limit. Maximum allowed: ${MAX_SIZE / 1024 / 1024}MB, Received: ${(totalSize / 1024 / 1024).toFixed(2)}MB`
|
|
30956
|
+
);
|
|
30957
|
+
}
|
|
30958
|
+
const parseResult = Papa3.parse(csvData, {
|
|
30959
|
+
header: true,
|
|
30960
|
+
skipEmptyLines: true,
|
|
30961
|
+
transformHeader: (header) => {
|
|
30962
|
+
return header.toLowerCase().replace(/\s+/g, "").replace(/[^\w]/g, "");
|
|
30963
|
+
}
|
|
30964
|
+
});
|
|
30965
|
+
if (parseResult.errors.length > 0) {
|
|
30966
|
+
throw new BadRequestError80(
|
|
30967
|
+
`CSV parsing errors: ${parseResult.errors.map((e) => e.message).join(", ")}`
|
|
30968
|
+
);
|
|
30969
|
+
}
|
|
30970
|
+
offices = parseResult.data || [];
|
|
30971
|
+
} else if (["xlsx", "xls"].includes(fileExtension || "")) {
|
|
30972
|
+
totalSize = file.buffer.length;
|
|
30973
|
+
if (totalSize > MAX_SIZE) {
|
|
30974
|
+
throw new BadRequestError80(
|
|
30975
|
+
`File size exceeds limit. Maximum allowed: ${MAX_SIZE / 1024 / 1024}MB, Received: ${(totalSize / 1024 / 1024).toFixed(2)}MB`
|
|
30976
|
+
);
|
|
30977
|
+
}
|
|
30978
|
+
const workbook = XLSX3.read(file.buffer, { type: "buffer" });
|
|
30979
|
+
const sheetName = workbook.SheetNames[0];
|
|
30980
|
+
if (!sheetName) {
|
|
30981
|
+
throw new BadRequestError80("Excel file contains no sheets");
|
|
30982
|
+
}
|
|
30983
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
30984
|
+
offices = XLSX3.utils.sheet_to_json(worksheet, {
|
|
30985
|
+
header: 1,
|
|
30986
|
+
defval: ""
|
|
30987
|
+
});
|
|
30988
|
+
if (offices.length > 0) {
|
|
30989
|
+
const headers = offices[0];
|
|
30990
|
+
offices = offices.slice(1).map((row) => {
|
|
30991
|
+
const obj = {};
|
|
30992
|
+
headers.forEach((header, index) => {
|
|
30993
|
+
obj[header.trim()] = row[index] || "";
|
|
30994
|
+
});
|
|
30995
|
+
return obj;
|
|
30996
|
+
});
|
|
30997
|
+
}
|
|
30998
|
+
} else {
|
|
30999
|
+
throw new BadRequestError80(
|
|
31000
|
+
"Unsupported file format. Please upload CSV, XLS, or XLSX files."
|
|
31001
|
+
);
|
|
31002
|
+
}
|
|
31003
|
+
if (!offices || offices.length === 0) {
|
|
31004
|
+
throw new BadRequestError80("File is empty or contains no valid data");
|
|
31005
|
+
}
|
|
31006
|
+
const results = {
|
|
31007
|
+
total: offices.length,
|
|
31008
|
+
successful: 0,
|
|
31009
|
+
failed: 0,
|
|
31010
|
+
errors: []
|
|
31011
|
+
};
|
|
31012
|
+
logger39.log({
|
|
31013
|
+
level: "info",
|
|
31014
|
+
message: `Processing ${offices.length} offices from file`
|
|
31015
|
+
});
|
|
31016
|
+
for (let i = 0; i < offices.length; i++) {
|
|
31017
|
+
const officeData = offices[i];
|
|
31018
|
+
try {
|
|
31019
|
+
const cleanOffice = {
|
|
31020
|
+
name: String(officeData.name || "").trim(),
|
|
31021
|
+
code: String(officeData.code || "").trim(),
|
|
31022
|
+
type: String(officeData.type || "").trim(),
|
|
31023
|
+
path: String(officeData.path || "").trim(),
|
|
31024
|
+
status: String(officeData.status || "active").trim()
|
|
31025
|
+
};
|
|
31026
|
+
if (officeData.parent && String(officeData.parent).trim()) {
|
|
31027
|
+
cleanOffice.parent = String(officeData.parent).trim();
|
|
31028
|
+
}
|
|
31029
|
+
const { error } = schemaOffice.validate(cleanOffice);
|
|
31030
|
+
if (error) {
|
|
31031
|
+
results.errors.push(`Row ${i + 1}: ${error.message}`);
|
|
31032
|
+
results.failed++;
|
|
31033
|
+
continue;
|
|
31034
|
+
}
|
|
31035
|
+
validatedOffices.push(cleanOffice);
|
|
31036
|
+
} catch (error) {
|
|
31037
|
+
results.errors.push(`Row ${i + 1}: ${error.message}`);
|
|
31038
|
+
results.failed++;
|
|
31039
|
+
}
|
|
31040
|
+
}
|
|
31041
|
+
if (validatedOffices.length === 0) {
|
|
31042
|
+
throw new BadRequestError80(
|
|
31043
|
+
"No valid offices found in file. Please check the format and data."
|
|
31044
|
+
);
|
|
31045
|
+
}
|
|
31046
|
+
const db = useAtlas41.getDb();
|
|
31047
|
+
if (!db) {
|
|
31048
|
+
throw new Error("Database connection not available");
|
|
31049
|
+
}
|
|
31050
|
+
const session = db.client.startSession();
|
|
31051
|
+
try {
|
|
31052
|
+
await session.withTransaction(async () => {
|
|
31053
|
+
const batchSize = 100;
|
|
31054
|
+
for (let i = 0; i < validatedOffices.length; i += batchSize) {
|
|
31055
|
+
const batch = validatedOffices.slice(i, i + batchSize);
|
|
31056
|
+
for (const office of batch) {
|
|
31057
|
+
try {
|
|
31058
|
+
await addOffice(office, session, false);
|
|
31059
|
+
results.successful++;
|
|
31060
|
+
} catch (error) {
|
|
31061
|
+
results.failed++;
|
|
31062
|
+
results.errors.push(
|
|
31063
|
+
`Failed to insert office "${office.name}": ${error.message}`
|
|
31064
|
+
);
|
|
31065
|
+
logger39.log({
|
|
31066
|
+
level: "error",
|
|
31067
|
+
message: `Failed to insert office: ${error.message}`
|
|
31068
|
+
});
|
|
31069
|
+
}
|
|
31070
|
+
}
|
|
31071
|
+
}
|
|
31072
|
+
});
|
|
31073
|
+
delCachedData();
|
|
31074
|
+
logger39.log({
|
|
31075
|
+
level: "info",
|
|
31076
|
+
message: `Bulk upload completed. Successful: ${results.successful}, Failed: ${results.failed}`
|
|
31077
|
+
});
|
|
31078
|
+
return results;
|
|
31079
|
+
} catch (error) {
|
|
31080
|
+
logger39.log({
|
|
31081
|
+
level: "error",
|
|
31082
|
+
message: `Transaction failed: ${error.message}`
|
|
31083
|
+
});
|
|
31084
|
+
throw new BadRequestError80(`Bulk upload failed: ${error.message}`);
|
|
31085
|
+
} finally {
|
|
31086
|
+
await session.endSession();
|
|
31087
|
+
}
|
|
31088
|
+
} catch (error) {
|
|
31089
|
+
logger39.log({
|
|
31090
|
+
level: "error",
|
|
31091
|
+
message: `Bulk office upload failed: ${error.message}`
|
|
31092
|
+
});
|
|
31093
|
+
if (error instanceof BadRequestError80) {
|
|
31094
|
+
throw error;
|
|
31095
|
+
}
|
|
31096
|
+
throw new BadRequestError80(`File processing failed: ${error.message}`);
|
|
31097
|
+
}
|
|
31098
|
+
}
|
|
31099
|
+
return {
|
|
31100
|
+
addBulk
|
|
31101
|
+
};
|
|
31102
|
+
}
|
|
31103
|
+
|
|
31104
|
+
// src/controllers/office.controller.ts
|
|
31105
|
+
import { BadRequestError as BadRequestError81 } from "@eeplatform/nodejs-utils";
|
|
31106
|
+
import Joi44 from "joi";
|
|
31107
|
+
function useOfficeController() {
|
|
31108
|
+
const {
|
|
31109
|
+
add: _add,
|
|
31110
|
+
getAll: _getAll,
|
|
31111
|
+
getById: _getById,
|
|
31112
|
+
updateById: _updateByIdById,
|
|
31113
|
+
deleteById: _deleteByIdById
|
|
31114
|
+
} = useOfficeRepo();
|
|
31115
|
+
const { addBulk: _addBulk } = useOfficeService();
|
|
31116
|
+
async function add(req, res, next) {
|
|
31117
|
+
const value = req.body;
|
|
31118
|
+
const validation = Joi44.object({
|
|
31119
|
+
name: Joi44.string().required(),
|
|
31120
|
+
code: Joi44.string().required(),
|
|
31121
|
+
type: Joi44.string().required(),
|
|
31122
|
+
path: Joi44.string().required(),
|
|
31123
|
+
parent: Joi44.string().hex().optional().allow(null, ""),
|
|
31124
|
+
status: Joi44.string().optional().allow(null, "")
|
|
31125
|
+
});
|
|
31126
|
+
const { error } = validation.validate(value);
|
|
31127
|
+
if (error) {
|
|
31128
|
+
next(new BadRequestError81(error.message));
|
|
31129
|
+
return;
|
|
31130
|
+
}
|
|
31131
|
+
try {
|
|
31132
|
+
const id = await _add(value);
|
|
31133
|
+
res.json({ message: "Office created successfully", id });
|
|
31134
|
+
return;
|
|
31135
|
+
} catch (error2) {
|
|
31136
|
+
next(error2);
|
|
31137
|
+
}
|
|
31138
|
+
}
|
|
31139
|
+
async function getAll(req, res, next) {
|
|
31140
|
+
const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
|
|
31141
|
+
const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
|
|
31142
|
+
const search = req.query.search ?? "";
|
|
31143
|
+
const type = req.query.type ?? "";
|
|
31144
|
+
const parent = req.query.parent ?? "";
|
|
31145
|
+
const status = req.query.status ?? "active";
|
|
31146
|
+
const isPageNumber = isFinite(page);
|
|
31147
|
+
if (!isPageNumber) {
|
|
31148
|
+
next(new BadRequestError81("Invalid page number."));
|
|
31149
|
+
return;
|
|
31150
|
+
}
|
|
31151
|
+
const isLimitNumber = isFinite(limit);
|
|
31152
|
+
if (!isLimitNumber) {
|
|
31153
|
+
next(new BadRequestError81("Invalid limit number."));
|
|
31154
|
+
return;
|
|
31155
|
+
}
|
|
31156
|
+
const validation = Joi44.object({
|
|
31157
|
+
page: Joi44.number().min(1).optional().allow("", null),
|
|
31158
|
+
limit: Joi44.number().min(1).optional().allow("", null),
|
|
31159
|
+
search: Joi44.string().optional().allow("", null),
|
|
31160
|
+
type: Joi44.string().optional().allow("", null),
|
|
31161
|
+
parent: Joi44.string().optional().allow("", null),
|
|
31162
|
+
status: Joi44.string().optional().allow("", null)
|
|
31163
|
+
});
|
|
31164
|
+
const { error } = validation.validate({
|
|
31165
|
+
page,
|
|
31166
|
+
limit,
|
|
31167
|
+
search,
|
|
31168
|
+
type,
|
|
31169
|
+
parent,
|
|
31170
|
+
status
|
|
31171
|
+
});
|
|
31172
|
+
if (error) {
|
|
31173
|
+
next(new BadRequestError81(error.message));
|
|
31174
|
+
return;
|
|
31175
|
+
}
|
|
31176
|
+
try {
|
|
31177
|
+
const offices = await _getAll({
|
|
31178
|
+
search,
|
|
31179
|
+
page,
|
|
31180
|
+
limit,
|
|
31181
|
+
type,
|
|
31182
|
+
parent,
|
|
31183
|
+
status
|
|
31184
|
+
});
|
|
31185
|
+
res.json(offices);
|
|
31186
|
+
return;
|
|
31187
|
+
} catch (error2) {
|
|
31188
|
+
next(error2);
|
|
31189
|
+
}
|
|
31190
|
+
}
|
|
31191
|
+
async function getById(req, res, next) {
|
|
31192
|
+
const id = req.params.id;
|
|
31193
|
+
const validation = Joi44.object({
|
|
31194
|
+
id: Joi44.string().hex().required()
|
|
31195
|
+
});
|
|
31196
|
+
const { error } = validation.validate({ id });
|
|
31197
|
+
if (error) {
|
|
31198
|
+
next(new BadRequestError81(error.message));
|
|
31199
|
+
return;
|
|
31200
|
+
}
|
|
31201
|
+
try {
|
|
31202
|
+
const office = await _getById(id);
|
|
31203
|
+
if (!office) {
|
|
31204
|
+
next(new BadRequestError81("Office not found."));
|
|
31205
|
+
return;
|
|
31206
|
+
}
|
|
31207
|
+
res.json(office);
|
|
31208
|
+
return;
|
|
31209
|
+
} catch (error2) {
|
|
31210
|
+
next(error2);
|
|
31211
|
+
}
|
|
31212
|
+
}
|
|
31213
|
+
async function updateById(req, res, next) {
|
|
31214
|
+
const id = req.params.id;
|
|
31215
|
+
const value = req.body;
|
|
31216
|
+
const validation = Joi44.object({
|
|
31217
|
+
id: Joi44.string().hex().required(),
|
|
31218
|
+
name: Joi44.string().optional(),
|
|
31219
|
+
code: Joi44.string().optional(),
|
|
31220
|
+
type: Joi44.string().optional(),
|
|
31221
|
+
parent: Joi44.string().hex().optional().allow(null, ""),
|
|
31222
|
+
path: Joi44.string().optional(),
|
|
31223
|
+
status: Joi44.string().optional()
|
|
31224
|
+
});
|
|
31225
|
+
const { error } = validation.validate({ id, ...value });
|
|
31226
|
+
if (error) {
|
|
31227
|
+
next(new BadRequestError81(error.message));
|
|
31228
|
+
return;
|
|
31229
|
+
}
|
|
31230
|
+
try {
|
|
31231
|
+
const result = await _updateByIdById(id, value);
|
|
31232
|
+
if (result.matchedCount === 0) {
|
|
31233
|
+
next(new BadRequestError81("Office not found."));
|
|
31234
|
+
return;
|
|
31235
|
+
}
|
|
31236
|
+
res.json({ message: "Office updated successfully" });
|
|
31237
|
+
return;
|
|
31238
|
+
} catch (error2) {
|
|
31239
|
+
next(error2);
|
|
31240
|
+
}
|
|
31241
|
+
}
|
|
31242
|
+
async function deleteById(req, res, next) {
|
|
31243
|
+
const id = req.params.id;
|
|
31244
|
+
const validation = Joi44.object({
|
|
31245
|
+
id: Joi44.string().hex().required()
|
|
31246
|
+
});
|
|
31247
|
+
const { error } = validation.validate({ id });
|
|
31248
|
+
if (error) {
|
|
31249
|
+
next(new BadRequestError81(error.message));
|
|
31250
|
+
return;
|
|
31251
|
+
}
|
|
31252
|
+
try {
|
|
31253
|
+
const result = await _deleteByIdById(id);
|
|
31254
|
+
if (result.matchedCount === 0) {
|
|
31255
|
+
next(new BadRequestError81("Office not found."));
|
|
31256
|
+
return;
|
|
31257
|
+
}
|
|
31258
|
+
res.json({ message: "Office deleted successfully" });
|
|
31259
|
+
return;
|
|
31260
|
+
} catch (error2) {
|
|
31261
|
+
next(error2);
|
|
31262
|
+
}
|
|
31263
|
+
}
|
|
31264
|
+
async function bulkAddOffices(req, res, next) {
|
|
31265
|
+
if (!req.file) {
|
|
31266
|
+
res.status(400).send("File is required!");
|
|
31267
|
+
return;
|
|
31268
|
+
}
|
|
31269
|
+
try {
|
|
31270
|
+
const result = await _addBulk(req.file);
|
|
31271
|
+
res.status(201).json(result);
|
|
31272
|
+
return;
|
|
31273
|
+
} catch (error) {
|
|
31274
|
+
next(error);
|
|
31275
|
+
return;
|
|
31276
|
+
}
|
|
31277
|
+
}
|
|
31278
|
+
return {
|
|
31279
|
+
add,
|
|
31280
|
+
getAll,
|
|
31281
|
+
getById,
|
|
31282
|
+
updateById,
|
|
31283
|
+
deleteById,
|
|
31284
|
+
bulkAddOffices
|
|
31285
|
+
};
|
|
31286
|
+
}
|
|
31287
|
+
|
|
31288
|
+
// src/models/curriculum.model.ts
|
|
31289
|
+
import { BadRequestError as BadRequestError82, logger as logger40 } from "@eeplatform/nodejs-utils";
|
|
31290
|
+
import Joi45 from "joi";
|
|
31291
|
+
import { ObjectId as ObjectId50 } from "mongodb";
|
|
31292
|
+
var schemaCurriculum = Joi45.object({
|
|
31293
|
+
_id: Joi45.string().hex().optional(),
|
|
31294
|
+
school: Joi45.string().hex().required(),
|
|
31295
|
+
code: Joi45.string().required(),
|
|
31296
|
+
educationLevel: Joi45.string().required(),
|
|
31297
|
+
gradeLevel: Joi45.string().required(),
|
|
31298
|
+
subjectCode: Joi45.string().required(),
|
|
31299
|
+
subjectName: Joi45.string().required(),
|
|
31300
|
+
subjectType: Joi45.string().required(),
|
|
31301
|
+
sessionFrequency: Joi45.number().integer().min(0).required(),
|
|
31302
|
+
sessionDuration: Joi45.number().integer().min(0).required(),
|
|
31303
|
+
totalMinutesPerWeek: Joi45.number().integer().min(0).required(),
|
|
31304
|
+
curriculumMemoRef: Joi45.string().optional().allow("", null),
|
|
31305
|
+
status: Joi45.string().optional().allow("", null),
|
|
31306
|
+
createdAt: Joi45.date().optional().allow("", null),
|
|
31307
|
+
updatedAt: Joi45.date().optional().allow("", null),
|
|
31308
|
+
deletedAt: Joi45.date().optional().allow("", null),
|
|
31309
|
+
createdBy: Joi45.string().optional().allow("", null),
|
|
31310
|
+
updatedBy: Joi45.string().optional().allow("", null),
|
|
31311
|
+
deletedBy: Joi45.string().optional().allow("", null)
|
|
31312
|
+
});
|
|
31313
|
+
function MCurriculum(value) {
|
|
31314
|
+
const { error } = schemaCurriculum.validate(value);
|
|
31315
|
+
if (error) {
|
|
31316
|
+
logger40.info(`Curriculum Model: ${error.message}`);
|
|
31317
|
+
throw new BadRequestError82(error.message);
|
|
31318
|
+
}
|
|
31319
|
+
if (value._id && typeof value._id === "string") {
|
|
31320
|
+
try {
|
|
31321
|
+
value._id = new ObjectId50(value._id);
|
|
31322
|
+
} catch (error2) {
|
|
31323
|
+
throw new BadRequestError82("Invalid _id format");
|
|
31324
|
+
}
|
|
31325
|
+
}
|
|
31326
|
+
return {
|
|
31327
|
+
_id: value._id ?? void 0,
|
|
31328
|
+
school: value.school ?? "",
|
|
31329
|
+
code: value.code ?? "",
|
|
31330
|
+
educationLevel: value.educationLevel ?? "",
|
|
31331
|
+
gradeLevel: value.gradeLevel ?? "",
|
|
31332
|
+
subjectCode: value.subjectCode ?? "",
|
|
31333
|
+
subjectName: value.subjectName ?? "",
|
|
31334
|
+
subjectType: value.subjectType ?? "",
|
|
31335
|
+
sessionFrequency: value.sessionFrequency ?? 0,
|
|
31336
|
+
sessionDuration: value.sessionDuration ?? 0,
|
|
31337
|
+
totalMinutesPerWeek: value.totalMinutesPerWeek ?? 0,
|
|
31338
|
+
curriculumMemoRef: value.curriculumMemoRef ?? "",
|
|
31339
|
+
status: value.status ?? "active",
|
|
31340
|
+
createdAt: value.createdAt ?? /* @__PURE__ */ new Date(),
|
|
31341
|
+
updatedAt: value.updatedAt ?? "",
|
|
31342
|
+
deletedAt: value.deletedAt ?? "",
|
|
31343
|
+
createdBy: value.createdBy ?? "",
|
|
31344
|
+
updatedBy: value.updatedBy ?? "",
|
|
31345
|
+
deletedBy: value.deletedBy ?? ""
|
|
31346
|
+
};
|
|
31347
|
+
}
|
|
31348
|
+
|
|
31349
|
+
// src/repositories/curriculum.repository.ts
|
|
31350
|
+
import {
|
|
31351
|
+
AppError as AppError20,
|
|
31352
|
+
BadRequestError as BadRequestError83,
|
|
31353
|
+
InternalServerError as InternalServerError30,
|
|
31354
|
+
logger as logger41,
|
|
31355
|
+
makeCacheKey as makeCacheKey25,
|
|
31356
|
+
paginate as paginate21,
|
|
31357
|
+
useAtlas as useAtlas42,
|
|
31358
|
+
useCache as useCache26
|
|
31359
|
+
} from "@eeplatform/nodejs-utils";
|
|
31360
|
+
import { ObjectId as ObjectId51 } from "mongodb";
|
|
31361
|
+
function useCurriculumRepo() {
|
|
31362
|
+
const db = useAtlas42.getDb();
|
|
31363
|
+
if (!db) {
|
|
31364
|
+
throw new Error("Unable to connect to server.");
|
|
31365
|
+
}
|
|
31366
|
+
const namespace_collection = "school.curriculums";
|
|
31367
|
+
const collection = db.collection(namespace_collection);
|
|
31368
|
+
const { getCache, setCache, delNamespace } = useCache26(namespace_collection);
|
|
31369
|
+
async function createIndexes() {
|
|
31370
|
+
try {
|
|
31371
|
+
await collection.createIndexes([
|
|
31372
|
+
{ key: { code: 1 }, unique: true, name: "unique_code_index" },
|
|
31373
|
+
{ key: { educationLevel: 1 } },
|
|
31374
|
+
{ key: { gradeLevel: 1 } },
|
|
31375
|
+
{ key: { subjectCode: 1 } },
|
|
31376
|
+
{ key: { status: 1 } }
|
|
31377
|
+
]);
|
|
31378
|
+
} catch (error) {
|
|
31379
|
+
throw new Error("Failed to create index on curriculums.");
|
|
31380
|
+
}
|
|
31381
|
+
}
|
|
31382
|
+
async function add(value, session) {
|
|
31383
|
+
try {
|
|
31384
|
+
value = MCurriculum(value);
|
|
31385
|
+
const res = await collection.insertOne(value, { session });
|
|
31386
|
+
delCachedData();
|
|
31387
|
+
return res.insertedId;
|
|
31388
|
+
} catch (error) {
|
|
31389
|
+
logger41.log({
|
|
31390
|
+
level: "error",
|
|
31391
|
+
message: error.message
|
|
31392
|
+
});
|
|
31393
|
+
if (error instanceof AppError20) {
|
|
31394
|
+
throw error;
|
|
31395
|
+
} else {
|
|
31396
|
+
const isDuplicated = error.message.includes("duplicate");
|
|
31397
|
+
if (isDuplicated) {
|
|
31398
|
+
throw new BadRequestError83("Curriculum already exists.");
|
|
31399
|
+
}
|
|
31400
|
+
throw new Error("Failed to create curriculum.");
|
|
31401
|
+
}
|
|
31402
|
+
}
|
|
31403
|
+
}
|
|
31404
|
+
async function updateById(_id, value, session) {
|
|
31405
|
+
try {
|
|
31406
|
+
_id = new ObjectId51(_id);
|
|
31407
|
+
} catch (error) {
|
|
31408
|
+
throw new BadRequestError83("Invalid ID.");
|
|
31409
|
+
}
|
|
31410
|
+
try {
|
|
31411
|
+
const res = await collection.updateOne(
|
|
31412
|
+
{ _id },
|
|
31413
|
+
{ $set: { ...value, updatedAt: /* @__PURE__ */ new Date() } },
|
|
31414
|
+
{ session }
|
|
31415
|
+
);
|
|
31416
|
+
delCachedData();
|
|
31417
|
+
return res;
|
|
31418
|
+
} catch (error) {
|
|
31419
|
+
logger41.log({
|
|
31420
|
+
level: "error",
|
|
31421
|
+
message: error.message
|
|
31422
|
+
});
|
|
31423
|
+
if (error instanceof AppError20) {
|
|
31424
|
+
throw error;
|
|
31425
|
+
} else {
|
|
31426
|
+
throw new Error("Failed to update curriculum.");
|
|
31427
|
+
}
|
|
31428
|
+
}
|
|
31429
|
+
}
|
|
31430
|
+
async function getAll({
|
|
31431
|
+
search = "",
|
|
31432
|
+
page = 1,
|
|
31433
|
+
limit = 10,
|
|
31434
|
+
sort = {},
|
|
31435
|
+
educationLevel = "",
|
|
31436
|
+
gradeLevel = "",
|
|
31437
|
+
subjectCode = "",
|
|
31438
|
+
status = "active"
|
|
31439
|
+
} = {}) {
|
|
31440
|
+
page = page > 0 ? page - 1 : 0;
|
|
31441
|
+
const query = {
|
|
31442
|
+
status
|
|
31443
|
+
};
|
|
31444
|
+
sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
|
|
31445
|
+
if (search) {
|
|
31446
|
+
query.$or = [
|
|
31447
|
+
{ code: { $regex: search, $options: "i" } },
|
|
31448
|
+
{ subjectName: { $regex: search, $options: "i" } },
|
|
31449
|
+
{ subjectCode: { $regex: search, $options: "i" } }
|
|
31450
|
+
];
|
|
31451
|
+
}
|
|
31452
|
+
if (educationLevel) {
|
|
31453
|
+
query.educationLevel = educationLevel;
|
|
31454
|
+
}
|
|
31455
|
+
if (gradeLevel) {
|
|
31456
|
+
query.gradeLevel = gradeLevel;
|
|
31457
|
+
}
|
|
31458
|
+
if (subjectCode) {
|
|
31459
|
+
query.subjectCode = subjectCode;
|
|
31460
|
+
}
|
|
31461
|
+
const cacheParams = {
|
|
31462
|
+
page,
|
|
31463
|
+
limit,
|
|
31464
|
+
sort: JSON.stringify(sort)
|
|
31465
|
+
};
|
|
31466
|
+
if (search)
|
|
31467
|
+
cacheParams.search = search;
|
|
31468
|
+
if (educationLevel)
|
|
31469
|
+
cacheParams.educationLevel = educationLevel;
|
|
31470
|
+
if (gradeLevel)
|
|
31471
|
+
cacheParams.gradeLevel = gradeLevel;
|
|
31472
|
+
if (subjectCode)
|
|
31473
|
+
cacheParams.subjectCode = subjectCode;
|
|
31474
|
+
if (status !== "active")
|
|
31475
|
+
cacheParams.status = status;
|
|
31476
|
+
const cacheKey = makeCacheKey25(namespace_collection, cacheParams);
|
|
31477
|
+
logger41.log({
|
|
31478
|
+
level: "info",
|
|
31479
|
+
message: `Cache key for getAll curriculums: ${cacheKey}`
|
|
31480
|
+
});
|
|
31481
|
+
try {
|
|
31482
|
+
const cached = await getCache(cacheKey);
|
|
31483
|
+
if (cached) {
|
|
31484
|
+
logger41.log({
|
|
31485
|
+
level: "info",
|
|
31486
|
+
message: `Cache hit for getAll curriculums: ${cacheKey}`
|
|
31487
|
+
});
|
|
31488
|
+
return cached;
|
|
31489
|
+
}
|
|
31490
|
+
const items = await collection.aggregate([
|
|
31491
|
+
{ $match: query },
|
|
31492
|
+
{ $sort: sort },
|
|
31493
|
+
{ $skip: page * limit },
|
|
31494
|
+
{ $limit: limit }
|
|
31495
|
+
]).toArray();
|
|
31496
|
+
const length = await collection.countDocuments(query);
|
|
31497
|
+
const data = paginate21(items, page, limit, length);
|
|
31498
|
+
setCache(cacheKey, data, 600).then(() => {
|
|
31499
|
+
logger41.log({
|
|
31500
|
+
level: "info",
|
|
31501
|
+
message: `Cache set for getAll curriculums: ${cacheKey}`
|
|
31502
|
+
});
|
|
31503
|
+
}).catch((err) => {
|
|
31504
|
+
logger41.log({
|
|
31505
|
+
level: "error",
|
|
31506
|
+
message: `Failed to set cache for getAll curriculums: ${err.message}`
|
|
31507
|
+
});
|
|
31508
|
+
});
|
|
31509
|
+
return data;
|
|
31510
|
+
} catch (error) {
|
|
31511
|
+
logger41.log({ level: "error", message: `${error}` });
|
|
31512
|
+
throw error;
|
|
31513
|
+
}
|
|
31514
|
+
}
|
|
31515
|
+
async function getById(_id) {
|
|
31516
|
+
try {
|
|
31517
|
+
_id = new ObjectId51(_id);
|
|
31518
|
+
} catch (error) {
|
|
31519
|
+
throw new BadRequestError83("Invalid ID.");
|
|
31520
|
+
}
|
|
31521
|
+
const cacheKey = makeCacheKey25(namespace_collection, { _id: String(_id) });
|
|
31522
|
+
try {
|
|
31523
|
+
const cached = await getCache(cacheKey);
|
|
31524
|
+
if (cached) {
|
|
31525
|
+
logger41.log({
|
|
31526
|
+
level: "info",
|
|
31527
|
+
message: `Cache hit for getById curriculum: ${cacheKey}`
|
|
31528
|
+
});
|
|
31529
|
+
return cached;
|
|
31530
|
+
}
|
|
31531
|
+
const result = await collection.findOne({
|
|
31532
|
+
_id
|
|
31533
|
+
});
|
|
31534
|
+
setCache(cacheKey, result, 300).then(() => {
|
|
31535
|
+
logger41.log({
|
|
31536
|
+
level: "info",
|
|
31537
|
+
message: `Cache set for curriculum by id: ${cacheKey}`
|
|
31538
|
+
});
|
|
31539
|
+
}).catch((err) => {
|
|
31540
|
+
logger41.log({
|
|
31541
|
+
level: "error",
|
|
31542
|
+
message: `Failed to set cache for curriculum by id: ${err.message}`
|
|
31543
|
+
});
|
|
31544
|
+
});
|
|
31545
|
+
return result;
|
|
31546
|
+
} catch (error) {
|
|
31547
|
+
if (error instanceof AppError20) {
|
|
31548
|
+
throw error;
|
|
31549
|
+
} else {
|
|
31550
|
+
throw new InternalServerError30("Failed to get curriculum.");
|
|
31551
|
+
}
|
|
31552
|
+
}
|
|
31553
|
+
}
|
|
31554
|
+
async function deleteById(_id, session) {
|
|
31555
|
+
try {
|
|
31556
|
+
_id = new ObjectId51(_id);
|
|
31557
|
+
} catch (error) {
|
|
31558
|
+
throw new BadRequestError83("Invalid ID.");
|
|
31559
|
+
}
|
|
31560
|
+
try {
|
|
31561
|
+
const res = await collection.updateOne(
|
|
31562
|
+
{ _id },
|
|
31563
|
+
{ $set: { status: "deleted", deletedAt: /* @__PURE__ */ new Date() } }
|
|
31564
|
+
);
|
|
31565
|
+
delCachedData();
|
|
31566
|
+
return res;
|
|
31567
|
+
} catch (error) {
|
|
31568
|
+
logger41.log({
|
|
31569
|
+
level: "error",
|
|
31570
|
+
message: error.message
|
|
31571
|
+
});
|
|
31572
|
+
if (error instanceof AppError20) {
|
|
31573
|
+
throw error;
|
|
31574
|
+
} else {
|
|
31575
|
+
throw new InternalServerError30("Failed to delete curriculum.");
|
|
31576
|
+
}
|
|
31577
|
+
}
|
|
31578
|
+
}
|
|
31579
|
+
function delCachedData() {
|
|
31580
|
+
delNamespace().then(() => {
|
|
31581
|
+
logger41.log({
|
|
31582
|
+
level: "info",
|
|
31583
|
+
message: `Cache namespace cleared for ${namespace_collection}`
|
|
31584
|
+
});
|
|
31585
|
+
}).catch((err) => {
|
|
31586
|
+
logger41.log({
|
|
31587
|
+
level: "error",
|
|
31588
|
+
message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
|
|
31589
|
+
});
|
|
31590
|
+
});
|
|
31591
|
+
}
|
|
31592
|
+
return {
|
|
31593
|
+
createIndexes,
|
|
31594
|
+
add,
|
|
31595
|
+
getAll,
|
|
31596
|
+
getById,
|
|
31597
|
+
updateById,
|
|
31598
|
+
deleteById
|
|
31599
|
+
};
|
|
31600
|
+
}
|
|
31601
|
+
|
|
31602
|
+
// src/controllers/curriculum.controller.ts
|
|
31603
|
+
import { BadRequestError as BadRequestError84, logger as logger42 } from "@eeplatform/nodejs-utils";
|
|
31604
|
+
import Joi46 from "joi";
|
|
31605
|
+
function useCurriculumController() {
|
|
31606
|
+
const {
|
|
31607
|
+
getAll: _getAll,
|
|
31608
|
+
getById: _getById,
|
|
31609
|
+
add: _add,
|
|
31610
|
+
updateById: _updateById,
|
|
31611
|
+
deleteById: _deleteById
|
|
31612
|
+
} = useCurriculumRepo();
|
|
31613
|
+
async function add(req, res, next) {
|
|
31614
|
+
const value = req.body;
|
|
31615
|
+
const validation = Joi46.object({
|
|
31616
|
+
code: Joi46.string().required(),
|
|
31617
|
+
school: Joi46.string().hex().required(),
|
|
31618
|
+
educationLevel: Joi46.string().required(),
|
|
31619
|
+
gradeLevel: Joi46.string().required(),
|
|
31620
|
+
subjectCode: Joi46.string().required(),
|
|
31621
|
+
subjectName: Joi46.string().required(),
|
|
31622
|
+
subjectType: Joi46.string().required(),
|
|
31623
|
+
sessionFrequency: Joi46.number().integer().min(0).required(),
|
|
31624
|
+
sessionDuration: Joi46.number().integer().min(0).required(),
|
|
31625
|
+
totalMinutesPerWeek: Joi46.number().integer().min(0).required(),
|
|
31626
|
+
curriculumMemoRef: Joi46.string().optional().allow("", null),
|
|
31627
|
+
status: Joi46.string().optional().allow("", null)
|
|
31628
|
+
});
|
|
31629
|
+
const { error } = validation.validate(value);
|
|
31630
|
+
if (error) {
|
|
31631
|
+
next(new BadRequestError84(error.message));
|
|
31632
|
+
logger42.info(`Controller: ${error.message}`);
|
|
31633
|
+
return;
|
|
31634
|
+
}
|
|
31635
|
+
try {
|
|
31636
|
+
const result = await _add(value);
|
|
31637
|
+
res.json(result);
|
|
31638
|
+
return;
|
|
31639
|
+
} catch (error2) {
|
|
31640
|
+
next(error2);
|
|
31641
|
+
}
|
|
31642
|
+
}
|
|
31643
|
+
async function updateById(req, res, next) {
|
|
31644
|
+
const value = req.body;
|
|
31645
|
+
const id = req.params.id ?? "";
|
|
31646
|
+
const validation = Joi46.object({
|
|
31647
|
+
id: Joi46.string().hex().required(),
|
|
31648
|
+
value: Joi46.object({
|
|
31649
|
+
code: Joi46.string().optional(),
|
|
31650
|
+
educationLevel: Joi46.string().optional(),
|
|
31651
|
+
gradeLevel: Joi46.string().optional(),
|
|
31652
|
+
subjectCode: Joi46.string().optional(),
|
|
31653
|
+
subjectName: Joi46.string().optional(),
|
|
31654
|
+
subjectType: Joi46.string().optional(),
|
|
31655
|
+
sessionFrequency: Joi46.number().integer().min(0).optional(),
|
|
31656
|
+
sessionDuration: Joi46.number().integer().min(0).optional(),
|
|
31657
|
+
totalMinutesPerWeek: Joi46.number().integer().min(0).optional(),
|
|
31658
|
+
curriculumMemoRef: Joi46.string().optional().allow("", null)
|
|
31659
|
+
}).min(1)
|
|
31660
|
+
});
|
|
31661
|
+
const { error } = validation.validate({ id, value });
|
|
31662
|
+
if (error) {
|
|
31663
|
+
next(new BadRequestError84(error.message));
|
|
31664
|
+
logger42.info(`Controller: ${error.message}`);
|
|
31665
|
+
return;
|
|
31666
|
+
}
|
|
31667
|
+
try {
|
|
31668
|
+
const result = await _updateById(id, value);
|
|
31669
|
+
res.json(result);
|
|
31670
|
+
return;
|
|
31671
|
+
} catch (error2) {
|
|
31672
|
+
next(error2);
|
|
31673
|
+
}
|
|
31674
|
+
}
|
|
31675
|
+
async function getAll(req, res, next) {
|
|
31676
|
+
const query = req.query;
|
|
31677
|
+
const validation = Joi46.object({
|
|
31678
|
+
page: Joi46.number().min(1).optional().allow("", null),
|
|
31679
|
+
limit: Joi46.number().min(1).optional().allow("", null),
|
|
31680
|
+
search: Joi46.string().optional().allow("", null),
|
|
31681
|
+
educationLevel: Joi46.string().optional().allow("", null),
|
|
31682
|
+
gradeLevel: Joi46.string().optional().allow("", null),
|
|
31683
|
+
subjectCode: Joi46.string().optional().allow("", null),
|
|
31684
|
+
status: Joi46.string().optional().allow("", null)
|
|
31685
|
+
});
|
|
31686
|
+
const { error } = validation.validate(query);
|
|
31687
|
+
if (error) {
|
|
31688
|
+
next(new BadRequestError84(error.message));
|
|
31689
|
+
return;
|
|
31690
|
+
}
|
|
31691
|
+
const page = parseInt(req.query.page) ?? 1;
|
|
31692
|
+
let limit = parseInt(req.query.limit) ?? 20;
|
|
31693
|
+
limit = isNaN(limit) ? 20 : limit;
|
|
31694
|
+
const sort = req.query.sort ? String(req.query.sort).split(",") : "";
|
|
31695
|
+
const sortOrder = req.query.sortOrder ? String(req.query.sortOrder).split(",") : "";
|
|
31696
|
+
const sortObj = {};
|
|
31697
|
+
if (sort && Array.isArray(sort) && sort.length && sortOrder && Array.isArray(sortOrder) && sortOrder.length) {
|
|
31698
|
+
sort.forEach((field, index) => {
|
|
31699
|
+
sortObj[field] = sortOrder[index] === "desc" ? -1 : 1;
|
|
31700
|
+
});
|
|
31701
|
+
}
|
|
31702
|
+
const status = req.query.status ?? "active";
|
|
31703
|
+
const educationLevel = req.query.educationLevel ?? "";
|
|
31704
|
+
const gradeLevel = req.query.gradeLevel ?? "";
|
|
31705
|
+
const subjectCode = req.query.subjectCode ?? "";
|
|
31706
|
+
const search = req.query.search ?? "";
|
|
31707
|
+
try {
|
|
31708
|
+
const curriculums = await _getAll({
|
|
31709
|
+
page,
|
|
31710
|
+
limit,
|
|
31711
|
+
sort: sortObj,
|
|
31712
|
+
status,
|
|
31713
|
+
educationLevel,
|
|
31714
|
+
gradeLevel,
|
|
31715
|
+
subjectCode,
|
|
31716
|
+
search
|
|
31717
|
+
});
|
|
31718
|
+
res.json(curriculums);
|
|
31719
|
+
return;
|
|
31720
|
+
} catch (error2) {
|
|
31721
|
+
next(error2);
|
|
31722
|
+
}
|
|
31723
|
+
}
|
|
31724
|
+
async function getById(req, res, next) {
|
|
31725
|
+
const id = req.params.id;
|
|
31726
|
+
const validation = Joi46.object({
|
|
31727
|
+
id: Joi46.string().hex().required()
|
|
31728
|
+
});
|
|
31729
|
+
const { error } = validation.validate({ id });
|
|
31730
|
+
if (error) {
|
|
31731
|
+
next(new BadRequestError84(error.message));
|
|
31732
|
+
return;
|
|
31733
|
+
}
|
|
31734
|
+
try {
|
|
31735
|
+
const curriculum = await _getById(id);
|
|
31736
|
+
res.json({
|
|
31737
|
+
message: "Successfully retrieved curriculum.",
|
|
31738
|
+
data: { curriculum }
|
|
31739
|
+
});
|
|
31740
|
+
return;
|
|
31741
|
+
} catch (error2) {
|
|
31742
|
+
next(error2);
|
|
31743
|
+
}
|
|
31744
|
+
}
|
|
31745
|
+
async function deleteById(req, res, next) {
|
|
31746
|
+
const id = req.params.id;
|
|
31747
|
+
const validation = Joi46.object({
|
|
31748
|
+
id: Joi46.string().hex().required()
|
|
31749
|
+
});
|
|
31750
|
+
const { error } = validation.validate({ id });
|
|
31751
|
+
if (error) {
|
|
31752
|
+
next(new BadRequestError84(error.message));
|
|
31753
|
+
return;
|
|
31754
|
+
}
|
|
31755
|
+
try {
|
|
31756
|
+
const result = await _deleteById(id);
|
|
31757
|
+
res.json({
|
|
31758
|
+
message: "Successfully deleted curriculum.",
|
|
31759
|
+
data: result
|
|
31760
|
+
});
|
|
31761
|
+
return;
|
|
31762
|
+
} catch (error2) {
|
|
31763
|
+
next(error2);
|
|
31764
|
+
}
|
|
31765
|
+
}
|
|
31766
|
+
return {
|
|
31767
|
+
add,
|
|
31768
|
+
getAll,
|
|
31769
|
+
getById,
|
|
31770
|
+
updateById,
|
|
31771
|
+
deleteById
|
|
31772
|
+
};
|
|
31773
|
+
}
|
|
30618
31774
|
export {
|
|
30619
31775
|
ACCESS_TOKEN_EXPIRY,
|
|
30620
31776
|
ACCESS_TOKEN_SECRET,
|
|
@@ -30636,12 +31792,14 @@ export {
|
|
|
30636
31792
|
MAsset,
|
|
30637
31793
|
MBuilding,
|
|
30638
31794
|
MBuildingUnit,
|
|
31795
|
+
MCurriculum,
|
|
30639
31796
|
MDivision,
|
|
30640
31797
|
MEntity,
|
|
30641
31798
|
MFile,
|
|
30642
31799
|
MMember,
|
|
30643
31800
|
MONGO_DB,
|
|
30644
31801
|
MONGO_URI,
|
|
31802
|
+
MOffice,
|
|
30645
31803
|
MOrder,
|
|
30646
31804
|
MOrg,
|
|
30647
31805
|
MPaymentMethod,
|
|
@@ -30682,7 +31840,9 @@ export {
|
|
|
30682
31840
|
schemaAssetUpdateOption,
|
|
30683
31841
|
schemaBuilding,
|
|
30684
31842
|
schemaBuildingUnit,
|
|
31843
|
+
schemaCurriculum,
|
|
30685
31844
|
schemaDivision,
|
|
31845
|
+
schemaOffice,
|
|
30686
31846
|
schemaPlantilla,
|
|
30687
31847
|
schemaRegion,
|
|
30688
31848
|
schemaSchool,
|
|
@@ -30700,6 +31860,8 @@ export {
|
|
|
30700
31860
|
useBuildingUnitRepo,
|
|
30701
31861
|
useCounterModel,
|
|
30702
31862
|
useCounterRepo,
|
|
31863
|
+
useCurriculumController,
|
|
31864
|
+
useCurriculumRepo,
|
|
30703
31865
|
useDivisionController,
|
|
30704
31866
|
useDivisionRepo,
|
|
30705
31867
|
useDivisionService,
|
|
@@ -30715,6 +31877,9 @@ export {
|
|
|
30715
31877
|
useInvoiceService,
|
|
30716
31878
|
useMemberController,
|
|
30717
31879
|
useMemberRepo,
|
|
31880
|
+
useOfficeController,
|
|
31881
|
+
useOfficeRepo,
|
|
31882
|
+
useOfficeService,
|
|
30718
31883
|
useOrderController,
|
|
30719
31884
|
useOrderRepo,
|
|
30720
31885
|
useOrgController,
|