@eeplatform/core 1.4.5 → 1.5.1
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 +12 -0
- package/dist/index.d.ts +200 -3
- package/dist/index.js +1770 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1784 -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,1758 @@ 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
|
+
effectiveSchoolYear: Joi45.string().required(),
|
|
31297
|
+
maxTeachingHoursPerDay: Joi45.number().integer().min(1).required(),
|
|
31298
|
+
subjects: Joi45.array().items(
|
|
31299
|
+
Joi45.object({
|
|
31300
|
+
educationLevel: Joi45.string().required(),
|
|
31301
|
+
gradeLevel: Joi45.string().required(),
|
|
31302
|
+
subjectCode: Joi45.string().required(),
|
|
31303
|
+
subjectName: Joi45.string().required(),
|
|
31304
|
+
subjectType: Joi45.string().required(),
|
|
31305
|
+
sessionFrequency: Joi45.number().integer().min(0).required(),
|
|
31306
|
+
sessionDuration: Joi45.number().integer().min(0).required(),
|
|
31307
|
+
totalMinutesPerWeek: Joi45.number().integer().min(0).required()
|
|
31308
|
+
})
|
|
31309
|
+
),
|
|
31310
|
+
curriculumMemoRef: Joi45.string().optional().allow("", null),
|
|
31311
|
+
status: Joi45.string().optional().allow("", null),
|
|
31312
|
+
createdAt: Joi45.date().optional().allow("", null),
|
|
31313
|
+
updatedAt: Joi45.date().optional().allow("", null),
|
|
31314
|
+
deletedAt: Joi45.date().optional().allow("", null),
|
|
31315
|
+
createdBy: Joi45.string().optional().allow("", null),
|
|
31316
|
+
updatedBy: Joi45.string().optional().allow("", null),
|
|
31317
|
+
deletedBy: Joi45.string().optional().allow("", null)
|
|
31318
|
+
});
|
|
31319
|
+
function MCurriculum(value) {
|
|
31320
|
+
const { error } = schemaCurriculum.validate(value);
|
|
31321
|
+
if (error) {
|
|
31322
|
+
logger40.info(`Curriculum Model: ${error.message}`);
|
|
31323
|
+
throw new BadRequestError82(error.message);
|
|
31324
|
+
}
|
|
31325
|
+
if (value._id && typeof value._id === "string") {
|
|
31326
|
+
try {
|
|
31327
|
+
value._id = new ObjectId50(value._id);
|
|
31328
|
+
} catch (error2) {
|
|
31329
|
+
throw new BadRequestError82("Invalid _id format");
|
|
31330
|
+
}
|
|
31331
|
+
}
|
|
31332
|
+
return {
|
|
31333
|
+
_id: value._id ?? void 0,
|
|
31334
|
+
school: value.school ?? "",
|
|
31335
|
+
code: value.code ?? "",
|
|
31336
|
+
effectiveSchoolYear: value.effectiveSchoolYear ?? "",
|
|
31337
|
+
maxTeachingHoursPerDay: value.maxTeachingHoursPerDay ?? 0,
|
|
31338
|
+
subjects: value.subjects ?? [],
|
|
31339
|
+
curriculumMemoRef: value.curriculumMemoRef ?? "",
|
|
31340
|
+
status: value.status ?? "active",
|
|
31341
|
+
createdAt: value.createdAt ?? /* @__PURE__ */ new Date(),
|
|
31342
|
+
updatedAt: value.updatedAt ?? "",
|
|
31343
|
+
deletedAt: value.deletedAt ?? "",
|
|
31344
|
+
createdBy: value.createdBy ?? "",
|
|
31345
|
+
updatedBy: value.updatedBy ?? "",
|
|
31346
|
+
deletedBy: value.deletedBy ?? ""
|
|
31347
|
+
};
|
|
31348
|
+
}
|
|
31349
|
+
|
|
31350
|
+
// src/repositories/curriculum.repository.ts
|
|
31351
|
+
import {
|
|
31352
|
+
AppError as AppError20,
|
|
31353
|
+
BadRequestError as BadRequestError83,
|
|
31354
|
+
InternalServerError as InternalServerError30,
|
|
31355
|
+
logger as logger41,
|
|
31356
|
+
makeCacheKey as makeCacheKey25,
|
|
31357
|
+
paginate as paginate21,
|
|
31358
|
+
useAtlas as useAtlas42,
|
|
31359
|
+
useCache as useCache26
|
|
31360
|
+
} from "@eeplatform/nodejs-utils";
|
|
31361
|
+
import { ObjectId as ObjectId51 } from "mongodb";
|
|
31362
|
+
function useCurriculumRepo() {
|
|
31363
|
+
const db = useAtlas42.getDb();
|
|
31364
|
+
if (!db) {
|
|
31365
|
+
throw new Error("Unable to connect to server.");
|
|
31366
|
+
}
|
|
31367
|
+
const namespace_collection = "school.curriculums";
|
|
31368
|
+
const collection = db.collection(namespace_collection);
|
|
31369
|
+
const { getCache, setCache, delNamespace } = useCache26(namespace_collection);
|
|
31370
|
+
async function createIndexes() {
|
|
31371
|
+
try {
|
|
31372
|
+
await collection.createIndexes([
|
|
31373
|
+
{
|
|
31374
|
+
key: {
|
|
31375
|
+
school: 1,
|
|
31376
|
+
code: 1,
|
|
31377
|
+
status: 1
|
|
31378
|
+
},
|
|
31379
|
+
unique: true,
|
|
31380
|
+
partialFilterExpression: { status: "active" },
|
|
31381
|
+
name: "unique_code_index"
|
|
31382
|
+
},
|
|
31383
|
+
{ key: { status: 1 } }
|
|
31384
|
+
]);
|
|
31385
|
+
} catch (error) {
|
|
31386
|
+
throw new Error("Failed to create index on curriculums.");
|
|
31387
|
+
}
|
|
31388
|
+
}
|
|
31389
|
+
async function add(value, session) {
|
|
31390
|
+
try {
|
|
31391
|
+
value = MCurriculum(value);
|
|
31392
|
+
const res = await collection.insertOne(value, { session });
|
|
31393
|
+
delCachedData();
|
|
31394
|
+
return res.insertedId;
|
|
31395
|
+
} catch (error) {
|
|
31396
|
+
logger41.log({
|
|
31397
|
+
level: "error",
|
|
31398
|
+
message: error.message
|
|
31399
|
+
});
|
|
31400
|
+
if (error instanceof AppError20) {
|
|
31401
|
+
throw error;
|
|
31402
|
+
} else {
|
|
31403
|
+
const isDuplicated = error.message.includes("duplicate");
|
|
31404
|
+
if (isDuplicated) {
|
|
31405
|
+
throw new BadRequestError83("Curriculum already exists.");
|
|
31406
|
+
}
|
|
31407
|
+
throw new Error("Failed to create curriculum.");
|
|
31408
|
+
}
|
|
31409
|
+
}
|
|
31410
|
+
}
|
|
31411
|
+
async function updateById(_id, value, session) {
|
|
31412
|
+
try {
|
|
31413
|
+
_id = new ObjectId51(_id);
|
|
31414
|
+
} catch (error) {
|
|
31415
|
+
throw new BadRequestError83("Invalid ID.");
|
|
31416
|
+
}
|
|
31417
|
+
try {
|
|
31418
|
+
const res = await collection.updateOne(
|
|
31419
|
+
{ _id },
|
|
31420
|
+
{ $set: { ...value, updatedAt: /* @__PURE__ */ new Date() } },
|
|
31421
|
+
{ session }
|
|
31422
|
+
);
|
|
31423
|
+
delCachedData();
|
|
31424
|
+
return res;
|
|
31425
|
+
} catch (error) {
|
|
31426
|
+
logger41.log({
|
|
31427
|
+
level: "error",
|
|
31428
|
+
message: error.message
|
|
31429
|
+
});
|
|
31430
|
+
if (error instanceof AppError20) {
|
|
31431
|
+
throw error;
|
|
31432
|
+
} else {
|
|
31433
|
+
throw new Error("Failed to update curriculum.");
|
|
31434
|
+
}
|
|
31435
|
+
}
|
|
31436
|
+
}
|
|
31437
|
+
async function getAll({
|
|
31438
|
+
search = "",
|
|
31439
|
+
page = 1,
|
|
31440
|
+
limit = 10,
|
|
31441
|
+
sort = {},
|
|
31442
|
+
status = "active"
|
|
31443
|
+
} = {}) {
|
|
31444
|
+
page = page > 0 ? page - 1 : 0;
|
|
31445
|
+
const query = {
|
|
31446
|
+
status
|
|
31447
|
+
};
|
|
31448
|
+
sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
|
|
31449
|
+
if (search) {
|
|
31450
|
+
query.$or = [
|
|
31451
|
+
{ code: { $regex: search, $options: "i" } },
|
|
31452
|
+
{ subjectName: { $regex: search, $options: "i" } },
|
|
31453
|
+
{ subjectCode: { $regex: search, $options: "i" } }
|
|
31454
|
+
];
|
|
31455
|
+
}
|
|
31456
|
+
const cacheParams = {
|
|
31457
|
+
page,
|
|
31458
|
+
limit,
|
|
31459
|
+
sort: JSON.stringify(sort)
|
|
31460
|
+
};
|
|
31461
|
+
if (search)
|
|
31462
|
+
cacheParams.search = search;
|
|
31463
|
+
if (status !== "active")
|
|
31464
|
+
cacheParams.status = status;
|
|
31465
|
+
const cacheKey = makeCacheKey25(namespace_collection, cacheParams);
|
|
31466
|
+
logger41.log({
|
|
31467
|
+
level: "info",
|
|
31468
|
+
message: `Cache key for getAll curriculums: ${cacheKey}`
|
|
31469
|
+
});
|
|
31470
|
+
try {
|
|
31471
|
+
const cached = await getCache(cacheKey);
|
|
31472
|
+
if (cached) {
|
|
31473
|
+
logger41.log({
|
|
31474
|
+
level: "info",
|
|
31475
|
+
message: `Cache hit for getAll curriculums: ${cacheKey}`
|
|
31476
|
+
});
|
|
31477
|
+
return cached;
|
|
31478
|
+
}
|
|
31479
|
+
const items = await collection.aggregate([
|
|
31480
|
+
{ $match: query },
|
|
31481
|
+
{ $sort: sort },
|
|
31482
|
+
{ $skip: page * limit },
|
|
31483
|
+
{ $limit: limit },
|
|
31484
|
+
{
|
|
31485
|
+
$project: {
|
|
31486
|
+
_id: 1,
|
|
31487
|
+
school: 1,
|
|
31488
|
+
code: 1,
|
|
31489
|
+
effectiveSchoolYear: 1,
|
|
31490
|
+
maxTeachingHoursPerDay: 1,
|
|
31491
|
+
curriculumMemoRef: 1,
|
|
31492
|
+
status: 1,
|
|
31493
|
+
createdAt: 1,
|
|
31494
|
+
updatedAt: 1
|
|
31495
|
+
}
|
|
31496
|
+
}
|
|
31497
|
+
]).toArray();
|
|
31498
|
+
const length = await collection.countDocuments(query);
|
|
31499
|
+
const data = paginate21(items, page, limit, length);
|
|
31500
|
+
setCache(cacheKey, data, 600).then(() => {
|
|
31501
|
+
logger41.log({
|
|
31502
|
+
level: "info",
|
|
31503
|
+
message: `Cache set for getAll curriculums: ${cacheKey}`
|
|
31504
|
+
});
|
|
31505
|
+
}).catch((err) => {
|
|
31506
|
+
logger41.log({
|
|
31507
|
+
level: "error",
|
|
31508
|
+
message: `Failed to set cache for getAll curriculums: ${err.message}`
|
|
31509
|
+
});
|
|
31510
|
+
});
|
|
31511
|
+
return data;
|
|
31512
|
+
} catch (error) {
|
|
31513
|
+
logger41.log({ level: "error", message: `${error}` });
|
|
31514
|
+
throw error;
|
|
31515
|
+
}
|
|
31516
|
+
}
|
|
31517
|
+
async function getById(_id) {
|
|
31518
|
+
try {
|
|
31519
|
+
_id = new ObjectId51(_id);
|
|
31520
|
+
} catch (error) {
|
|
31521
|
+
throw new BadRequestError83("Invalid ID.");
|
|
31522
|
+
}
|
|
31523
|
+
const cacheKey = makeCacheKey25(namespace_collection, { _id: String(_id) });
|
|
31524
|
+
try {
|
|
31525
|
+
const cached = await getCache(cacheKey);
|
|
31526
|
+
if (cached) {
|
|
31527
|
+
logger41.log({
|
|
31528
|
+
level: "info",
|
|
31529
|
+
message: `Cache hit for getById curriculum: ${cacheKey}`
|
|
31530
|
+
});
|
|
31531
|
+
return cached;
|
|
31532
|
+
}
|
|
31533
|
+
const result = await collection.findOne({
|
|
31534
|
+
_id
|
|
31535
|
+
});
|
|
31536
|
+
setCache(cacheKey, result, 300).then(() => {
|
|
31537
|
+
logger41.log({
|
|
31538
|
+
level: "info",
|
|
31539
|
+
message: `Cache set for curriculum by id: ${cacheKey}`
|
|
31540
|
+
});
|
|
31541
|
+
}).catch((err) => {
|
|
31542
|
+
logger41.log({
|
|
31543
|
+
level: "error",
|
|
31544
|
+
message: `Failed to set cache for curriculum by id: ${err.message}`
|
|
31545
|
+
});
|
|
31546
|
+
});
|
|
31547
|
+
return result;
|
|
31548
|
+
} catch (error) {
|
|
31549
|
+
if (error instanceof AppError20) {
|
|
31550
|
+
throw error;
|
|
31551
|
+
} else {
|
|
31552
|
+
throw new InternalServerError30("Failed to get curriculum.");
|
|
31553
|
+
}
|
|
31554
|
+
}
|
|
31555
|
+
}
|
|
31556
|
+
async function deleteById(_id, session) {
|
|
31557
|
+
try {
|
|
31558
|
+
_id = new ObjectId51(_id);
|
|
31559
|
+
} catch (error) {
|
|
31560
|
+
throw new BadRequestError83("Invalid ID.");
|
|
31561
|
+
}
|
|
31562
|
+
try {
|
|
31563
|
+
const res = await collection.updateOne(
|
|
31564
|
+
{ _id },
|
|
31565
|
+
{ $set: { status: "deleted", deletedAt: /* @__PURE__ */ new Date() } }
|
|
31566
|
+
);
|
|
31567
|
+
delCachedData();
|
|
31568
|
+
return res;
|
|
31569
|
+
} catch (error) {
|
|
31570
|
+
logger41.log({
|
|
31571
|
+
level: "error",
|
|
31572
|
+
message: error.message
|
|
31573
|
+
});
|
|
31574
|
+
if (error instanceof AppError20) {
|
|
31575
|
+
throw error;
|
|
31576
|
+
} else {
|
|
31577
|
+
throw new InternalServerError30("Failed to delete curriculum.");
|
|
31578
|
+
}
|
|
31579
|
+
}
|
|
31580
|
+
}
|
|
31581
|
+
function delCachedData() {
|
|
31582
|
+
delNamespace().then(() => {
|
|
31583
|
+
logger41.log({
|
|
31584
|
+
level: "info",
|
|
31585
|
+
message: `Cache namespace cleared for ${namespace_collection}`
|
|
31586
|
+
});
|
|
31587
|
+
}).catch((err) => {
|
|
31588
|
+
logger41.log({
|
|
31589
|
+
level: "error",
|
|
31590
|
+
message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
|
|
31591
|
+
});
|
|
31592
|
+
});
|
|
31593
|
+
}
|
|
31594
|
+
return {
|
|
31595
|
+
createIndexes,
|
|
31596
|
+
add,
|
|
31597
|
+
getAll,
|
|
31598
|
+
getById,
|
|
31599
|
+
updateById,
|
|
31600
|
+
deleteById
|
|
31601
|
+
};
|
|
31602
|
+
}
|
|
31603
|
+
|
|
31604
|
+
// src/controllers/curriculum.controller.ts
|
|
31605
|
+
import { BadRequestError as BadRequestError84, logger as logger42 } from "@eeplatform/nodejs-utils";
|
|
31606
|
+
import Joi46 from "joi";
|
|
31607
|
+
function useCurriculumController() {
|
|
31608
|
+
const {
|
|
31609
|
+
getAll: _getAll,
|
|
31610
|
+
getById: _getById,
|
|
31611
|
+
add: _add,
|
|
31612
|
+
updateById: _updateById,
|
|
31613
|
+
deleteById: _deleteById
|
|
31614
|
+
} = useCurriculumRepo();
|
|
31615
|
+
async function add(req, res, next) {
|
|
31616
|
+
const value = req.body;
|
|
31617
|
+
const { error } = schemaCurriculum.validate(value);
|
|
31618
|
+
if (error) {
|
|
31619
|
+
next(new BadRequestError84(error.message));
|
|
31620
|
+
logger42.info(`Controller: ${error.message}`);
|
|
31621
|
+
return;
|
|
31622
|
+
}
|
|
31623
|
+
try {
|
|
31624
|
+
const result = await _add(value);
|
|
31625
|
+
res.json(result);
|
|
31626
|
+
return;
|
|
31627
|
+
} catch (error2) {
|
|
31628
|
+
next(error2);
|
|
31629
|
+
}
|
|
31630
|
+
}
|
|
31631
|
+
async function updateById(req, res, next) {
|
|
31632
|
+
const value = req.body;
|
|
31633
|
+
const id = req.params.id ?? "";
|
|
31634
|
+
const validation = Joi46.object({
|
|
31635
|
+
id: Joi46.string().hex().required(),
|
|
31636
|
+
value: Joi46.object({
|
|
31637
|
+
code: Joi46.string().optional(),
|
|
31638
|
+
educationLevel: Joi46.string().optional(),
|
|
31639
|
+
gradeLevel: Joi46.string().optional(),
|
|
31640
|
+
subjectCode: Joi46.string().optional(),
|
|
31641
|
+
subjectName: Joi46.string().optional(),
|
|
31642
|
+
subjectType: Joi46.string().optional(),
|
|
31643
|
+
sessionFrequency: Joi46.number().integer().min(0).optional(),
|
|
31644
|
+
sessionDuration: Joi46.number().integer().min(0).optional(),
|
|
31645
|
+
totalMinutesPerWeek: Joi46.number().integer().min(0).optional(),
|
|
31646
|
+
curriculumMemoRef: Joi46.string().optional().allow("", null)
|
|
31647
|
+
}).min(1)
|
|
31648
|
+
});
|
|
31649
|
+
const { error } = validation.validate({ id, value });
|
|
31650
|
+
if (error) {
|
|
31651
|
+
next(new BadRequestError84(error.message));
|
|
31652
|
+
logger42.info(`Controller: ${error.message}`);
|
|
31653
|
+
return;
|
|
31654
|
+
}
|
|
31655
|
+
try {
|
|
31656
|
+
const result = await _updateById(id, value);
|
|
31657
|
+
res.json(result);
|
|
31658
|
+
return;
|
|
31659
|
+
} catch (error2) {
|
|
31660
|
+
next(error2);
|
|
31661
|
+
}
|
|
31662
|
+
}
|
|
31663
|
+
async function getAll(req, res, next) {
|
|
31664
|
+
const query = req.query;
|
|
31665
|
+
const validation = Joi46.object({
|
|
31666
|
+
page: Joi46.number().min(1).optional().allow("", null),
|
|
31667
|
+
limit: Joi46.number().min(1).optional().allow("", null),
|
|
31668
|
+
search: Joi46.string().optional().allow("", null),
|
|
31669
|
+
status: Joi46.string().optional().allow("", null)
|
|
31670
|
+
});
|
|
31671
|
+
const { error } = validation.validate(query);
|
|
31672
|
+
if (error) {
|
|
31673
|
+
next(new BadRequestError84(error.message));
|
|
31674
|
+
return;
|
|
31675
|
+
}
|
|
31676
|
+
const page = parseInt(req.query.page) ?? 1;
|
|
31677
|
+
let limit = parseInt(req.query.limit) ?? 20;
|
|
31678
|
+
limit = isNaN(limit) ? 20 : limit;
|
|
31679
|
+
const sort = req.query.sort ? String(req.query.sort).split(",") : "";
|
|
31680
|
+
const sortOrder = req.query.sortOrder ? String(req.query.sortOrder).split(",") : "";
|
|
31681
|
+
const sortObj = {};
|
|
31682
|
+
if (sort && Array.isArray(sort) && sort.length && sortOrder && Array.isArray(sortOrder) && sortOrder.length) {
|
|
31683
|
+
sort.forEach((field, index) => {
|
|
31684
|
+
sortObj[field] = sortOrder[index] === "desc" ? -1 : 1;
|
|
31685
|
+
});
|
|
31686
|
+
}
|
|
31687
|
+
const status = req.query.status ?? "active";
|
|
31688
|
+
const search = req.query.search ?? "";
|
|
31689
|
+
try {
|
|
31690
|
+
const curriculums = await _getAll({
|
|
31691
|
+
page,
|
|
31692
|
+
limit,
|
|
31693
|
+
sort: sortObj,
|
|
31694
|
+
status,
|
|
31695
|
+
search
|
|
31696
|
+
});
|
|
31697
|
+
res.json(curriculums);
|
|
31698
|
+
return;
|
|
31699
|
+
} catch (error2) {
|
|
31700
|
+
next(error2);
|
|
31701
|
+
}
|
|
31702
|
+
}
|
|
31703
|
+
async function getById(req, res, next) {
|
|
31704
|
+
const id = req.params.id;
|
|
31705
|
+
const validation = Joi46.object({
|
|
31706
|
+
id: Joi46.string().hex().required()
|
|
31707
|
+
});
|
|
31708
|
+
const { error } = validation.validate({ id });
|
|
31709
|
+
if (error) {
|
|
31710
|
+
next(new BadRequestError84(error.message));
|
|
31711
|
+
return;
|
|
31712
|
+
}
|
|
31713
|
+
try {
|
|
31714
|
+
const curriculum = await _getById(id);
|
|
31715
|
+
res.json({
|
|
31716
|
+
message: "Successfully retrieved curriculum.",
|
|
31717
|
+
data: { curriculum }
|
|
31718
|
+
});
|
|
31719
|
+
return;
|
|
31720
|
+
} catch (error2) {
|
|
31721
|
+
next(error2);
|
|
31722
|
+
}
|
|
31723
|
+
}
|
|
31724
|
+
async function deleteById(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 result = await _deleteById(id);
|
|
31736
|
+
res.json({
|
|
31737
|
+
message: "Successfully deleted curriculum.",
|
|
31738
|
+
data: result
|
|
31739
|
+
});
|
|
31740
|
+
return;
|
|
31741
|
+
} catch (error2) {
|
|
31742
|
+
next(error2);
|
|
31743
|
+
}
|
|
31744
|
+
}
|
|
31745
|
+
return {
|
|
31746
|
+
add,
|
|
31747
|
+
getAll,
|
|
31748
|
+
getById,
|
|
31749
|
+
updateById,
|
|
31750
|
+
deleteById
|
|
31751
|
+
};
|
|
31752
|
+
}
|
|
31753
|
+
|
|
31754
|
+
// src/models/grade-level.model.ts
|
|
31755
|
+
import { BadRequestError as BadRequestError85, logger as logger43 } from "@eeplatform/nodejs-utils";
|
|
31756
|
+
import Joi47 from "joi";
|
|
31757
|
+
import { ObjectId as ObjectId52 } from "mongodb";
|
|
31758
|
+
var schemaGradeLevel = Joi47.object({
|
|
31759
|
+
_id: Joi47.string().hex().optional(),
|
|
31760
|
+
school: Joi47.string().hex().optional(),
|
|
31761
|
+
educationLevel: Joi47.string().required(),
|
|
31762
|
+
gradeLevel: Joi47.string().required(),
|
|
31763
|
+
teachingStyle: Joi47.string().required(),
|
|
31764
|
+
defaultStartTime: Joi47.string().optional().allow("", null),
|
|
31765
|
+
defaultEndTime: Joi47.string().optional().allow("", null),
|
|
31766
|
+
status: Joi47.string().optional().allow("", null),
|
|
31767
|
+
createdAt: Joi47.date().optional().allow("", null),
|
|
31768
|
+
updatedAt: Joi47.date().optional().allow("", null),
|
|
31769
|
+
deletedAt: Joi47.date().optional().allow("", null),
|
|
31770
|
+
createdBy: Joi47.string().optional().allow("", null),
|
|
31771
|
+
updatedBy: Joi47.string().optional().allow("", null),
|
|
31772
|
+
deletedBy: Joi47.string().optional().allow("", null)
|
|
31773
|
+
});
|
|
31774
|
+
function MGradeLevel(value) {
|
|
31775
|
+
const { error } = schemaGradeLevel.validate(value);
|
|
31776
|
+
if (error) {
|
|
31777
|
+
logger43.info(`Grade Level Model: ${error.message}`);
|
|
31778
|
+
throw new BadRequestError85(error.message);
|
|
31779
|
+
}
|
|
31780
|
+
if (value._id && typeof value._id === "string") {
|
|
31781
|
+
try {
|
|
31782
|
+
value._id = new ObjectId52(value._id);
|
|
31783
|
+
} catch (error2) {
|
|
31784
|
+
throw new BadRequestError85("Invalid _id format");
|
|
31785
|
+
}
|
|
31786
|
+
}
|
|
31787
|
+
if (value.school && typeof value.school === "string") {
|
|
31788
|
+
try {
|
|
31789
|
+
value.school = new ObjectId52(value.school);
|
|
31790
|
+
} catch (error2) {
|
|
31791
|
+
throw new BadRequestError85("Invalid school format");
|
|
31792
|
+
}
|
|
31793
|
+
}
|
|
31794
|
+
return {
|
|
31795
|
+
_id: value._id ?? void 0,
|
|
31796
|
+
school: value.school ?? void 0,
|
|
31797
|
+
educationLevel: value.educationLevel ?? "",
|
|
31798
|
+
gradeLevel: value.gradeLevel ?? "",
|
|
31799
|
+
teachingStyle: value.teachingStyle ?? "",
|
|
31800
|
+
defaultStartTime: value.defaultStartTime ?? "",
|
|
31801
|
+
defaultEndTime: value.defaultEndTime ?? "",
|
|
31802
|
+
status: value.status ?? "active",
|
|
31803
|
+
createdAt: value.createdAt ?? /* @__PURE__ */ new Date(),
|
|
31804
|
+
updatedAt: value.updatedAt ?? "",
|
|
31805
|
+
deletedAt: value.deletedAt ?? "",
|
|
31806
|
+
createdBy: value.createdBy ?? "",
|
|
31807
|
+
updatedBy: value.updatedBy ?? "",
|
|
31808
|
+
deletedBy: value.deletedBy ?? ""
|
|
31809
|
+
};
|
|
31810
|
+
}
|
|
31811
|
+
|
|
31812
|
+
// src/repositories/grade-level.repository.ts
|
|
31813
|
+
import {
|
|
31814
|
+
AppError as AppError21,
|
|
31815
|
+
BadRequestError as BadRequestError86,
|
|
31816
|
+
InternalServerError as InternalServerError31,
|
|
31817
|
+
logger as logger44,
|
|
31818
|
+
makeCacheKey as makeCacheKey26,
|
|
31819
|
+
paginate as paginate22,
|
|
31820
|
+
useAtlas as useAtlas43,
|
|
31821
|
+
useCache as useCache27
|
|
31822
|
+
} from "@eeplatform/nodejs-utils";
|
|
31823
|
+
import { ObjectId as ObjectId53 } from "mongodb";
|
|
31824
|
+
function useGradeLevelRepo() {
|
|
31825
|
+
const db = useAtlas43.getDb();
|
|
31826
|
+
if (!db) {
|
|
31827
|
+
throw new Error("Unable to connect to server.");
|
|
31828
|
+
}
|
|
31829
|
+
const namespace_collection = "school.grade-levels";
|
|
31830
|
+
const collection = db.collection(namespace_collection);
|
|
31831
|
+
const { getCache, setCache, delNamespace } = useCache27(namespace_collection);
|
|
31832
|
+
async function createIndexes() {
|
|
31833
|
+
try {
|
|
31834
|
+
await collection.createIndexes([
|
|
31835
|
+
{ key: { educationLevel: 1 } },
|
|
31836
|
+
{ key: { gradeLevel: 1 } },
|
|
31837
|
+
{ key: { teachingStyle: 1 } },
|
|
31838
|
+
{ key: { school: 1 } },
|
|
31839
|
+
{ key: { status: 1 } },
|
|
31840
|
+
{
|
|
31841
|
+
key: { educationLevel: 1, gradeLevel: 1, school: 1 },
|
|
31842
|
+
unique: true,
|
|
31843
|
+
partialFilterExpression: { status: { $in: ["active", "suspended"] } },
|
|
31844
|
+
name: "unique_grade_level_per_school_index"
|
|
31845
|
+
}
|
|
31846
|
+
]);
|
|
31847
|
+
} catch (error) {
|
|
31848
|
+
throw new Error("Failed to create index on grade levels.");
|
|
31849
|
+
}
|
|
31850
|
+
}
|
|
31851
|
+
async function add(value, session) {
|
|
31852
|
+
try {
|
|
31853
|
+
value = MGradeLevel(value);
|
|
31854
|
+
const res = await collection.insertOne(value, { session });
|
|
31855
|
+
delCachedData();
|
|
31856
|
+
return res.insertedId;
|
|
31857
|
+
} catch (error) {
|
|
31858
|
+
logger44.log({
|
|
31859
|
+
level: "error",
|
|
31860
|
+
message: error.message
|
|
31861
|
+
});
|
|
31862
|
+
if (error instanceof AppError21) {
|
|
31863
|
+
throw error;
|
|
31864
|
+
} else {
|
|
31865
|
+
const isDuplicated = error.message.includes("duplicate");
|
|
31866
|
+
if (isDuplicated) {
|
|
31867
|
+
throw new BadRequestError86(
|
|
31868
|
+
"Grade level already exists for this education level and school."
|
|
31869
|
+
);
|
|
31870
|
+
}
|
|
31871
|
+
throw new Error("Failed to create grade level.");
|
|
31872
|
+
}
|
|
31873
|
+
}
|
|
31874
|
+
}
|
|
31875
|
+
async function updateById(_id, value, session) {
|
|
31876
|
+
try {
|
|
31877
|
+
_id = new ObjectId53(_id);
|
|
31878
|
+
} catch (error) {
|
|
31879
|
+
throw new BadRequestError86("Invalid ID.");
|
|
31880
|
+
}
|
|
31881
|
+
if (value.school && typeof value.school === "string") {
|
|
31882
|
+
try {
|
|
31883
|
+
value.school = new ObjectId53(value.school);
|
|
31884
|
+
} catch (error) {
|
|
31885
|
+
throw new BadRequestError86("Invalid school ID format.");
|
|
31886
|
+
}
|
|
31887
|
+
}
|
|
31888
|
+
try {
|
|
31889
|
+
const res = await collection.updateOne(
|
|
31890
|
+
{ _id },
|
|
31891
|
+
{ $set: { ...value, updatedAt: /* @__PURE__ */ new Date() } },
|
|
31892
|
+
{ session }
|
|
31893
|
+
);
|
|
31894
|
+
delCachedData();
|
|
31895
|
+
return res;
|
|
31896
|
+
} catch (error) {
|
|
31897
|
+
logger44.log({
|
|
31898
|
+
level: "error",
|
|
31899
|
+
message: error.message
|
|
31900
|
+
});
|
|
31901
|
+
if (error instanceof AppError21) {
|
|
31902
|
+
throw error;
|
|
31903
|
+
} else {
|
|
31904
|
+
const isDuplicated = error.message.includes("duplicate");
|
|
31905
|
+
if (isDuplicated) {
|
|
31906
|
+
throw new BadRequestError86(
|
|
31907
|
+
"Grade level already exists for this education level and school."
|
|
31908
|
+
);
|
|
31909
|
+
}
|
|
31910
|
+
throw new Error("Failed to update grade level.");
|
|
31911
|
+
}
|
|
31912
|
+
}
|
|
31913
|
+
}
|
|
31914
|
+
async function getAll({
|
|
31915
|
+
search = "",
|
|
31916
|
+
page = 1,
|
|
31917
|
+
limit = 10,
|
|
31918
|
+
sort = {},
|
|
31919
|
+
educationLevel = "",
|
|
31920
|
+
gradeLevel = "",
|
|
31921
|
+
teachingStyle = "",
|
|
31922
|
+
school = "",
|
|
31923
|
+
status = "active"
|
|
31924
|
+
} = {}) {
|
|
31925
|
+
page = page > 0 ? page - 1 : 0;
|
|
31926
|
+
const query = {
|
|
31927
|
+
status
|
|
31928
|
+
};
|
|
31929
|
+
sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
|
|
31930
|
+
if (search) {
|
|
31931
|
+
query.$or = [
|
|
31932
|
+
{ educationLevel: { $regex: search, $options: "i" } },
|
|
31933
|
+
{ gradeLevel: { $regex: search, $options: "i" } },
|
|
31934
|
+
{ teachingStyle: { $regex: search, $options: "i" } }
|
|
31935
|
+
];
|
|
31936
|
+
}
|
|
31937
|
+
if (educationLevel) {
|
|
31938
|
+
query.educationLevel = educationLevel;
|
|
31939
|
+
}
|
|
31940
|
+
if (gradeLevel) {
|
|
31941
|
+
query.gradeLevel = gradeLevel;
|
|
31942
|
+
}
|
|
31943
|
+
if (teachingStyle) {
|
|
31944
|
+
query.teachingStyle = teachingStyle;
|
|
31945
|
+
}
|
|
31946
|
+
if (school) {
|
|
31947
|
+
try {
|
|
31948
|
+
query.school = new ObjectId53(school);
|
|
31949
|
+
} catch (error) {
|
|
31950
|
+
throw new BadRequestError86("Invalid school ID format.");
|
|
31951
|
+
}
|
|
31952
|
+
}
|
|
31953
|
+
const cacheParams = {
|
|
31954
|
+
page,
|
|
31955
|
+
limit,
|
|
31956
|
+
sort: JSON.stringify(sort)
|
|
31957
|
+
};
|
|
31958
|
+
if (search)
|
|
31959
|
+
cacheParams.search = search;
|
|
31960
|
+
if (educationLevel)
|
|
31961
|
+
cacheParams.educationLevel = educationLevel;
|
|
31962
|
+
if (gradeLevel)
|
|
31963
|
+
cacheParams.gradeLevel = gradeLevel;
|
|
31964
|
+
if (teachingStyle)
|
|
31965
|
+
cacheParams.teachingStyle = teachingStyle;
|
|
31966
|
+
if (school)
|
|
31967
|
+
cacheParams.school = school;
|
|
31968
|
+
if (status !== "active")
|
|
31969
|
+
cacheParams.status = status;
|
|
31970
|
+
const cacheKey = makeCacheKey26(namespace_collection, cacheParams);
|
|
31971
|
+
logger44.log({
|
|
31972
|
+
level: "info",
|
|
31973
|
+
message: `Cache key for getAll grade levels: ${cacheKey}`
|
|
31974
|
+
});
|
|
31975
|
+
try {
|
|
31976
|
+
const cached = await getCache(cacheKey);
|
|
31977
|
+
if (cached) {
|
|
31978
|
+
logger44.log({
|
|
31979
|
+
level: "info",
|
|
31980
|
+
message: `Cache hit for getAll grade levels: ${cacheKey}`
|
|
31981
|
+
});
|
|
31982
|
+
return cached;
|
|
31983
|
+
}
|
|
31984
|
+
const items = await collection.aggregate([
|
|
31985
|
+
{ $match: query },
|
|
31986
|
+
{ $sort: sort },
|
|
31987
|
+
{ $skip: page * limit },
|
|
31988
|
+
{ $limit: limit },
|
|
31989
|
+
{
|
|
31990
|
+
$lookup: {
|
|
31991
|
+
from: "school.schools",
|
|
31992
|
+
localField: "school",
|
|
31993
|
+
foreignField: "_id",
|
|
31994
|
+
as: "schoolDetails"
|
|
31995
|
+
}
|
|
31996
|
+
},
|
|
31997
|
+
{
|
|
31998
|
+
$addFields: {
|
|
31999
|
+
schoolName: { $arrayElemAt: ["$schoolDetails.name", 0] }
|
|
32000
|
+
}
|
|
32001
|
+
},
|
|
32002
|
+
{
|
|
32003
|
+
$project: {
|
|
32004
|
+
schoolDetails: 0
|
|
32005
|
+
}
|
|
32006
|
+
}
|
|
32007
|
+
]).toArray();
|
|
32008
|
+
const length = await collection.countDocuments(query);
|
|
32009
|
+
const data = paginate22(items, page, limit, length);
|
|
32010
|
+
setCache(cacheKey, data, 600).then(() => {
|
|
32011
|
+
logger44.log({
|
|
32012
|
+
level: "info",
|
|
32013
|
+
message: `Cache set for getAll grade levels: ${cacheKey}`
|
|
32014
|
+
});
|
|
32015
|
+
}).catch((err) => {
|
|
32016
|
+
logger44.log({
|
|
32017
|
+
level: "error",
|
|
32018
|
+
message: `Failed to set cache for getAll grade levels: ${err.message}`
|
|
32019
|
+
});
|
|
32020
|
+
});
|
|
32021
|
+
return data;
|
|
32022
|
+
} catch (error) {
|
|
32023
|
+
logger44.log({ level: "error", message: `${error}` });
|
|
32024
|
+
throw error;
|
|
32025
|
+
}
|
|
32026
|
+
}
|
|
32027
|
+
async function getById(_id) {
|
|
32028
|
+
try {
|
|
32029
|
+
_id = new ObjectId53(_id);
|
|
32030
|
+
} catch (error) {
|
|
32031
|
+
throw new BadRequestError86("Invalid ID.");
|
|
32032
|
+
}
|
|
32033
|
+
const cacheKey = makeCacheKey26(namespace_collection, { _id: String(_id) });
|
|
32034
|
+
try {
|
|
32035
|
+
const cached = await getCache(cacheKey);
|
|
32036
|
+
if (cached) {
|
|
32037
|
+
logger44.log({
|
|
32038
|
+
level: "info",
|
|
32039
|
+
message: `Cache hit for getById grade level: ${cacheKey}`
|
|
32040
|
+
});
|
|
32041
|
+
return cached;
|
|
32042
|
+
}
|
|
32043
|
+
const result = await collection.aggregate([
|
|
32044
|
+
{ $match: { _id } },
|
|
32045
|
+
{
|
|
32046
|
+
$lookup: {
|
|
32047
|
+
from: "school.schools",
|
|
32048
|
+
localField: "school",
|
|
32049
|
+
foreignField: "_id",
|
|
32050
|
+
as: "schoolDetails"
|
|
32051
|
+
}
|
|
32052
|
+
},
|
|
32053
|
+
{
|
|
32054
|
+
$addFields: {
|
|
32055
|
+
schoolName: { $arrayElemAt: ["$schoolDetails.name", 0] }
|
|
32056
|
+
}
|
|
32057
|
+
},
|
|
32058
|
+
{
|
|
32059
|
+
$project: {
|
|
32060
|
+
schoolDetails: 0
|
|
32061
|
+
}
|
|
32062
|
+
}
|
|
32063
|
+
]).toArray();
|
|
32064
|
+
const gradeLevel = result[0] || null;
|
|
32065
|
+
setCache(cacheKey, gradeLevel, 300).then(() => {
|
|
32066
|
+
logger44.log({
|
|
32067
|
+
level: "info",
|
|
32068
|
+
message: `Cache set for grade level by id: ${cacheKey}`
|
|
32069
|
+
});
|
|
32070
|
+
}).catch((err) => {
|
|
32071
|
+
logger44.log({
|
|
32072
|
+
level: "error",
|
|
32073
|
+
message: `Failed to set cache for grade level by id: ${err.message}`
|
|
32074
|
+
});
|
|
32075
|
+
});
|
|
32076
|
+
return gradeLevel;
|
|
32077
|
+
} catch (error) {
|
|
32078
|
+
if (error instanceof AppError21) {
|
|
32079
|
+
throw error;
|
|
32080
|
+
} else {
|
|
32081
|
+
throw new InternalServerError31("Failed to get grade level.");
|
|
32082
|
+
}
|
|
32083
|
+
}
|
|
32084
|
+
}
|
|
32085
|
+
async function deleteById(_id, session) {
|
|
32086
|
+
try {
|
|
32087
|
+
_id = new ObjectId53(_id);
|
|
32088
|
+
} catch (error) {
|
|
32089
|
+
throw new BadRequestError86("Invalid ID.");
|
|
32090
|
+
}
|
|
32091
|
+
try {
|
|
32092
|
+
const res = await collection.updateOne(
|
|
32093
|
+
{ _id },
|
|
32094
|
+
{ $set: { status: "deleted", deletedAt: /* @__PURE__ */ new Date() } },
|
|
32095
|
+
{ session }
|
|
32096
|
+
);
|
|
32097
|
+
delCachedData();
|
|
32098
|
+
return res;
|
|
32099
|
+
} catch (error) {
|
|
32100
|
+
logger44.log({
|
|
32101
|
+
level: "error",
|
|
32102
|
+
message: error.message
|
|
32103
|
+
});
|
|
32104
|
+
if (error instanceof AppError21) {
|
|
32105
|
+
throw error;
|
|
32106
|
+
} else {
|
|
32107
|
+
throw new InternalServerError31("Failed to delete grade level.");
|
|
32108
|
+
}
|
|
32109
|
+
}
|
|
32110
|
+
}
|
|
32111
|
+
async function getByEducationLevel(educationLevel, school) {
|
|
32112
|
+
const query = {
|
|
32113
|
+
educationLevel,
|
|
32114
|
+
status: "active"
|
|
32115
|
+
};
|
|
32116
|
+
if (school) {
|
|
32117
|
+
try {
|
|
32118
|
+
query.school = new ObjectId53(school);
|
|
32119
|
+
} catch (error) {
|
|
32120
|
+
throw new BadRequestError86("Invalid school ID format.");
|
|
32121
|
+
}
|
|
32122
|
+
}
|
|
32123
|
+
const cacheKey = makeCacheKey26(namespace_collection, {
|
|
32124
|
+
educationLevel,
|
|
32125
|
+
school: school || "all",
|
|
32126
|
+
method: "getByEducationLevel"
|
|
32127
|
+
});
|
|
32128
|
+
try {
|
|
32129
|
+
const cached = await getCache(cacheKey);
|
|
32130
|
+
if (cached) {
|
|
32131
|
+
logger44.log({
|
|
32132
|
+
level: "info",
|
|
32133
|
+
message: `Cache hit for getByEducationLevel: ${cacheKey}`
|
|
32134
|
+
});
|
|
32135
|
+
return cached;
|
|
32136
|
+
}
|
|
32137
|
+
const result = await collection.find(query).sort({ gradeLevel: 1 }).toArray();
|
|
32138
|
+
setCache(cacheKey, result, 300).then(() => {
|
|
32139
|
+
logger44.log({
|
|
32140
|
+
level: "info",
|
|
32141
|
+
message: `Cache set for getByEducationLevel: ${cacheKey}`
|
|
32142
|
+
});
|
|
32143
|
+
}).catch((err) => {
|
|
32144
|
+
logger44.log({
|
|
32145
|
+
level: "error",
|
|
32146
|
+
message: `Failed to set cache for getByEducationLevel: ${err.message}`
|
|
32147
|
+
});
|
|
32148
|
+
});
|
|
32149
|
+
return result;
|
|
32150
|
+
} catch (error) {
|
|
32151
|
+
logger44.log({ level: "error", message: `${error}` });
|
|
32152
|
+
throw error;
|
|
32153
|
+
}
|
|
32154
|
+
}
|
|
32155
|
+
function delCachedData() {
|
|
32156
|
+
delNamespace().then(() => {
|
|
32157
|
+
logger44.log({
|
|
32158
|
+
level: "info",
|
|
32159
|
+
message: `Cache namespace cleared for ${namespace_collection}`
|
|
32160
|
+
});
|
|
32161
|
+
}).catch((err) => {
|
|
32162
|
+
logger44.log({
|
|
32163
|
+
level: "error",
|
|
32164
|
+
message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
|
|
32165
|
+
});
|
|
32166
|
+
});
|
|
32167
|
+
}
|
|
32168
|
+
return {
|
|
32169
|
+
createIndexes,
|
|
32170
|
+
add,
|
|
32171
|
+
getAll,
|
|
32172
|
+
getById,
|
|
32173
|
+
updateById,
|
|
32174
|
+
deleteById,
|
|
32175
|
+
getByEducationLevel
|
|
32176
|
+
};
|
|
32177
|
+
}
|
|
32178
|
+
|
|
32179
|
+
// src/controllers/grade-level.controller.ts
|
|
32180
|
+
import { BadRequestError as BadRequestError87, logger as logger45 } from "@eeplatform/nodejs-utils";
|
|
32181
|
+
import Joi48 from "joi";
|
|
32182
|
+
function useGradeLevelController() {
|
|
32183
|
+
const {
|
|
32184
|
+
getAll: _getAll,
|
|
32185
|
+
getById: _getById,
|
|
32186
|
+
add: _add,
|
|
32187
|
+
updateById: _updateById,
|
|
32188
|
+
deleteById: _deleteById,
|
|
32189
|
+
getByEducationLevel: _getByEducationLevel
|
|
32190
|
+
} = useGradeLevelRepo();
|
|
32191
|
+
async function add(req, res, next) {
|
|
32192
|
+
const value = req.body;
|
|
32193
|
+
const validation = Joi48.object({
|
|
32194
|
+
school: Joi48.string().hex().optional(),
|
|
32195
|
+
educationLevel: Joi48.string().required(),
|
|
32196
|
+
gradeLevel: Joi48.string().required(),
|
|
32197
|
+
teachingStyle: Joi48.string().required(),
|
|
32198
|
+
defaultStartTime: Joi48.string().optional().allow("", null),
|
|
32199
|
+
defaultEndTime: Joi48.string().optional().allow("", null),
|
|
32200
|
+
status: Joi48.string().optional().allow("", null)
|
|
32201
|
+
});
|
|
32202
|
+
const { error } = validation.validate(value);
|
|
32203
|
+
if (error) {
|
|
32204
|
+
next(new BadRequestError87(error.message));
|
|
32205
|
+
logger45.info(`Controller: ${error.message}`);
|
|
32206
|
+
return;
|
|
32207
|
+
}
|
|
32208
|
+
try {
|
|
32209
|
+
const result = await _add(value);
|
|
32210
|
+
res.json({
|
|
32211
|
+
message: "Successfully created grade level.",
|
|
32212
|
+
data: { _id: result }
|
|
32213
|
+
});
|
|
32214
|
+
return;
|
|
32215
|
+
} catch (error2) {
|
|
32216
|
+
next(error2);
|
|
32217
|
+
}
|
|
32218
|
+
}
|
|
32219
|
+
async function updateById(req, res, next) {
|
|
32220
|
+
const value = req.body;
|
|
32221
|
+
const id = req.params.id ?? "";
|
|
32222
|
+
const validation = Joi48.object({
|
|
32223
|
+
id: Joi48.string().hex().required(),
|
|
32224
|
+
value: Joi48.object({
|
|
32225
|
+
school: Joi48.string().hex().optional(),
|
|
32226
|
+
educationLevel: Joi48.string().optional(),
|
|
32227
|
+
gradeLevel: Joi48.string().optional(),
|
|
32228
|
+
teachingStyle: Joi48.string().optional(),
|
|
32229
|
+
maxTeachingHoursPerDay: Joi48.number().integer().min(0).optional(),
|
|
32230
|
+
maxTeachingHoursPerWeek: Joi48.number().integer().min(0).optional(),
|
|
32231
|
+
defaultStartTime: Joi48.string().optional().allow("", null),
|
|
32232
|
+
defaultEndTime: Joi48.string().optional().allow("", null)
|
|
32233
|
+
}).min(1)
|
|
32234
|
+
});
|
|
32235
|
+
const { error } = validation.validate({ id, value });
|
|
32236
|
+
if (error) {
|
|
32237
|
+
next(new BadRequestError87(error.message));
|
|
32238
|
+
logger45.info(`Controller: ${error.message}`);
|
|
32239
|
+
return;
|
|
32240
|
+
}
|
|
32241
|
+
try {
|
|
32242
|
+
const result = await _updateById(id, value);
|
|
32243
|
+
res.json({
|
|
32244
|
+
message: "Successfully updated grade level.",
|
|
32245
|
+
data: result
|
|
32246
|
+
});
|
|
32247
|
+
return;
|
|
32248
|
+
} catch (error2) {
|
|
32249
|
+
next(error2);
|
|
32250
|
+
}
|
|
32251
|
+
}
|
|
32252
|
+
async function getAll(req, res, next) {
|
|
32253
|
+
const query = req.query;
|
|
32254
|
+
const validation = Joi48.object({
|
|
32255
|
+
page: Joi48.number().min(1).optional().allow("", null),
|
|
32256
|
+
limit: Joi48.number().min(1).optional().allow("", null),
|
|
32257
|
+
search: Joi48.string().optional().allow("", null),
|
|
32258
|
+
educationLevel: Joi48.string().optional().allow("", null),
|
|
32259
|
+
gradeLevel: Joi48.string().optional().allow("", null),
|
|
32260
|
+
teachingStyle: Joi48.string().optional().allow("", null),
|
|
32261
|
+
school: Joi48.string().hex().optional().allow("", null),
|
|
32262
|
+
status: Joi48.string().optional().allow("", null)
|
|
32263
|
+
});
|
|
32264
|
+
const { error } = validation.validate(query);
|
|
32265
|
+
if (error) {
|
|
32266
|
+
next(new BadRequestError87(error.message));
|
|
32267
|
+
return;
|
|
32268
|
+
}
|
|
32269
|
+
const page = parseInt(req.query.page) ?? 1;
|
|
32270
|
+
let limit = parseInt(req.query.limit) ?? 20;
|
|
32271
|
+
limit = isNaN(limit) ? 20 : limit;
|
|
32272
|
+
const sort = req.query.sort ? String(req.query.sort).split(",") : "";
|
|
32273
|
+
const sortOrder = req.query.sortOrder ? String(req.query.sortOrder).split(",") : "";
|
|
32274
|
+
const sortObj = {};
|
|
32275
|
+
if (sort && Array.isArray(sort) && sort.length && sortOrder && Array.isArray(sortOrder) && sortOrder.length) {
|
|
32276
|
+
sort.forEach((field, index) => {
|
|
32277
|
+
sortObj[field] = sortOrder[index] === "desc" ? -1 : 1;
|
|
32278
|
+
});
|
|
32279
|
+
}
|
|
32280
|
+
const status = req.query.status ?? "active";
|
|
32281
|
+
const educationLevel = req.query.educationLevel ?? "";
|
|
32282
|
+
const gradeLevel = req.query.gradeLevel ?? "";
|
|
32283
|
+
const teachingStyle = req.query.teachingStyle ?? "";
|
|
32284
|
+
const school = req.query.school ?? "";
|
|
32285
|
+
const search = req.query.search ?? "";
|
|
32286
|
+
try {
|
|
32287
|
+
const gradeLevels = await _getAll({
|
|
32288
|
+
page,
|
|
32289
|
+
limit,
|
|
32290
|
+
sort: sortObj,
|
|
32291
|
+
status,
|
|
32292
|
+
educationLevel,
|
|
32293
|
+
gradeLevel,
|
|
32294
|
+
teachingStyle,
|
|
32295
|
+
school,
|
|
32296
|
+
search
|
|
32297
|
+
});
|
|
32298
|
+
res.json(gradeLevels);
|
|
32299
|
+
return;
|
|
32300
|
+
} catch (error2) {
|
|
32301
|
+
next(error2);
|
|
32302
|
+
}
|
|
32303
|
+
}
|
|
32304
|
+
async function getById(req, res, next) {
|
|
32305
|
+
const id = req.params.id;
|
|
32306
|
+
const validation = Joi48.object({
|
|
32307
|
+
id: Joi48.string().hex().required()
|
|
32308
|
+
});
|
|
32309
|
+
const { error } = validation.validate({ id });
|
|
32310
|
+
if (error) {
|
|
32311
|
+
next(new BadRequestError87(error.message));
|
|
32312
|
+
return;
|
|
32313
|
+
}
|
|
32314
|
+
try {
|
|
32315
|
+
const gradeLevel = await _getById(id);
|
|
32316
|
+
res.json({
|
|
32317
|
+
message: "Successfully retrieved grade level.",
|
|
32318
|
+
data: { gradeLevel }
|
|
32319
|
+
});
|
|
32320
|
+
return;
|
|
32321
|
+
} catch (error2) {
|
|
32322
|
+
next(error2);
|
|
32323
|
+
}
|
|
32324
|
+
}
|
|
32325
|
+
async function deleteById(req, res, next) {
|
|
32326
|
+
const id = req.params.id;
|
|
32327
|
+
const validation = Joi48.object({
|
|
32328
|
+
id: Joi48.string().hex().required()
|
|
32329
|
+
});
|
|
32330
|
+
const { error } = validation.validate({ id });
|
|
32331
|
+
if (error) {
|
|
32332
|
+
next(new BadRequestError87(error.message));
|
|
32333
|
+
return;
|
|
32334
|
+
}
|
|
32335
|
+
try {
|
|
32336
|
+
const result = await _deleteById(id);
|
|
32337
|
+
res.json({
|
|
32338
|
+
message: "Successfully deleted grade level.",
|
|
32339
|
+
data: result
|
|
32340
|
+
});
|
|
32341
|
+
return;
|
|
32342
|
+
} catch (error2) {
|
|
32343
|
+
next(error2);
|
|
32344
|
+
}
|
|
32345
|
+
}
|
|
32346
|
+
async function getByEducationLevel(req, res, next) {
|
|
32347
|
+
const educationLevel = req.params.educationLevel;
|
|
32348
|
+
const school = req.query.school;
|
|
32349
|
+
const validation = Joi48.object({
|
|
32350
|
+
educationLevel: Joi48.string().required(),
|
|
32351
|
+
school: Joi48.string().hex().optional().allow("", null)
|
|
32352
|
+
});
|
|
32353
|
+
const { error } = validation.validate({ educationLevel, school });
|
|
32354
|
+
if (error) {
|
|
32355
|
+
next(new BadRequestError87(error.message));
|
|
32356
|
+
return;
|
|
32357
|
+
}
|
|
32358
|
+
try {
|
|
32359
|
+
const gradeLevels = await _getByEducationLevel(educationLevel, school);
|
|
32360
|
+
res.json({
|
|
32361
|
+
message: "Successfully retrieved grade levels by education level.",
|
|
32362
|
+
data: { gradeLevels }
|
|
32363
|
+
});
|
|
32364
|
+
return;
|
|
32365
|
+
} catch (error2) {
|
|
32366
|
+
next(error2);
|
|
32367
|
+
}
|
|
32368
|
+
}
|
|
32369
|
+
return {
|
|
32370
|
+
add,
|
|
32371
|
+
getAll,
|
|
32372
|
+
getById,
|
|
32373
|
+
updateById,
|
|
32374
|
+
deleteById,
|
|
32375
|
+
getByEducationLevel
|
|
32376
|
+
};
|
|
32377
|
+
}
|
|
30618
32378
|
export {
|
|
30619
32379
|
ACCESS_TOKEN_EXPIRY,
|
|
30620
32380
|
ACCESS_TOKEN_SECRET,
|
|
@@ -30636,12 +32396,15 @@ export {
|
|
|
30636
32396
|
MAsset,
|
|
30637
32397
|
MBuilding,
|
|
30638
32398
|
MBuildingUnit,
|
|
32399
|
+
MCurriculum,
|
|
30639
32400
|
MDivision,
|
|
30640
32401
|
MEntity,
|
|
30641
32402
|
MFile,
|
|
32403
|
+
MGradeLevel,
|
|
30642
32404
|
MMember,
|
|
30643
32405
|
MONGO_DB,
|
|
30644
32406
|
MONGO_URI,
|
|
32407
|
+
MOffice,
|
|
30645
32408
|
MOrder,
|
|
30646
32409
|
MOrg,
|
|
30647
32410
|
MPaymentMethod,
|
|
@@ -30682,7 +32445,10 @@ export {
|
|
|
30682
32445
|
schemaAssetUpdateOption,
|
|
30683
32446
|
schemaBuilding,
|
|
30684
32447
|
schemaBuildingUnit,
|
|
32448
|
+
schemaCurriculum,
|
|
30685
32449
|
schemaDivision,
|
|
32450
|
+
schemaGradeLevel,
|
|
32451
|
+
schemaOffice,
|
|
30686
32452
|
schemaPlantilla,
|
|
30687
32453
|
schemaRegion,
|
|
30688
32454
|
schemaSchool,
|
|
@@ -30700,6 +32466,8 @@ export {
|
|
|
30700
32466
|
useBuildingUnitRepo,
|
|
30701
32467
|
useCounterModel,
|
|
30702
32468
|
useCounterRepo,
|
|
32469
|
+
useCurriculumController,
|
|
32470
|
+
useCurriculumRepo,
|
|
30703
32471
|
useDivisionController,
|
|
30704
32472
|
useDivisionRepo,
|
|
30705
32473
|
useDivisionService,
|
|
@@ -30709,12 +32477,17 @@ export {
|
|
|
30709
32477
|
useFileRepo,
|
|
30710
32478
|
useFileService,
|
|
30711
32479
|
useGitHubService,
|
|
32480
|
+
useGradeLevelController,
|
|
32481
|
+
useGradeLevelRepo,
|
|
30712
32482
|
useInvoiceController,
|
|
30713
32483
|
useInvoiceModel,
|
|
30714
32484
|
useInvoiceRepo,
|
|
30715
32485
|
useInvoiceService,
|
|
30716
32486
|
useMemberController,
|
|
30717
32487
|
useMemberRepo,
|
|
32488
|
+
useOfficeController,
|
|
32489
|
+
useOfficeRepo,
|
|
32490
|
+
useOfficeService,
|
|
30718
32491
|
useOrderController,
|
|
30719
32492
|
useOrderRepo,
|
|
30720
32493
|
useOrgController,
|