@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/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 parse3(val);
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 parse3(str) {
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({ ...value.building }, session);
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
- step: Joi41.number().optional().allow(null, 0),
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
- incumbent: data.incumbent ?? "",
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
- step: parseInt(plantillaData.step || "1") || 1,
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,