@goweekdays/core 2.15.6 → 2.15.8

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.js CHANGED
@@ -72,6 +72,14 @@ __export(src_exports, {
72
72
  VERIFICATION_USER_INVITE_DURATION: () => VERIFICATION_USER_INVITE_DURATION,
73
73
  XENDIT_BASE_URL: () => XENDIT_BASE_URL,
74
74
  XENDIT_SECRET_KEY: () => XENDIT_SECRET_KEY,
75
+ assetItemCategories: () => assetItemCategories,
76
+ assetItemClasses: () => assetItemClasses,
77
+ assetItemPurposes: () => assetItemPurposes,
78
+ assetItemStatuses: () => assetItemStatuses,
79
+ assetItemTrackingTypes: () => assetItemTrackingTypes,
80
+ assetUnitStatuses: () => assetUnitStatuses,
81
+ buildCategoryPath: () => buildCategoryPath,
82
+ categoryLevels: () => categoryLevels,
75
83
  chartOfAccountControlTypes: () => chartOfAccountControlTypes,
76
84
  chartOfAccountNormalBalances: () => chartOfAccountNormalBalances,
77
85
  chartOfAccountStatuses: () => chartOfAccountStatuses,
@@ -86,7 +94,10 @@ __export(src_exports, {
86
94
  ledgerBillTypes: () => ledgerBillTypes,
87
95
  modelAccountBalance: () => modelAccountBalance,
88
96
  modelApp: () => modelApp,
97
+ modelAssetItem: () => modelAssetItem,
98
+ modelAssetUnit: () => modelAssetUnit,
89
99
  modelBusinessProfile: () => modelBusinessProfile,
100
+ modelCategoryNode: () => modelCategoryNode,
90
101
  modelChartOfAccount: () => modelChartOfAccount,
91
102
  modelCustomer: () => modelCustomer,
92
103
  modelJobApplication: () => modelJobApplication,
@@ -112,14 +123,22 @@ __export(src_exports, {
112
123
  modelPlan: () => modelPlan,
113
124
  modelPromo: () => modelPromo,
114
125
  modelRole: () => modelRole,
126
+ modelStockMovement: () => modelStockMovement,
115
127
  modelSubscription: () => modelSubscription,
116
128
  modelSubscriptionTransaction: () => modelSubscriptionTransaction,
129
+ modelTag: () => modelTag,
117
130
  modelTax: () => modelTax,
118
131
  modelUser: () => modelUser,
119
132
  modelVerification: () => modelVerification,
133
+ normalizeName: () => normalizeName,
120
134
  schemaAccountBalance: () => schemaAccountBalance,
121
135
  schemaApp: () => schemaApp,
122
136
  schemaAppUpdate: () => schemaAppUpdate,
137
+ schemaAssetItem: () => schemaAssetItem,
138
+ schemaAssetItemCreate: () => schemaAssetItemCreate,
139
+ schemaAssetItemUpdate: () => schemaAssetItemUpdate,
140
+ schemaAssetUnit: () => schemaAssetUnit,
141
+ schemaAssetUnitUpdate: () => schemaAssetUnitUpdate,
123
142
  schemaAward: () => schemaAward,
124
143
  schemaBuilding: () => schemaBuilding,
125
144
  schemaBuildingUnit: () => schemaBuildingUnit,
@@ -131,6 +150,10 @@ __export(src_exports, {
131
150
  schemaBusinessProfileRegisteredAddress: () => schemaBusinessProfileRegisteredAddress,
132
151
  schemaBusinessProfileTIN: () => schemaBusinessProfileTIN,
133
152
  schemaBusinessProfileTradeName: () => schemaBusinessProfileTradeName,
153
+ schemaCategoryGetAll: () => schemaCategoryGetAll,
154
+ schemaCategoryNodeCreate: () => schemaCategoryNodeCreate,
155
+ schemaCategoryNodeStd: () => schemaCategoryNodeStd,
156
+ schemaCategoryNodeUpdate: () => schemaCategoryNodeUpdate,
134
157
  schemaCertification: () => schemaCertification,
135
158
  schemaChartOfAccountBase: () => schemaChartOfAccountBase,
136
159
  schemaChartOfAccountStd: () => schemaChartOfAccountStd,
@@ -200,6 +223,7 @@ __export(src_exports, {
200
223
  schemaRole: () => schemaRole,
201
224
  schemaRoleUpdate: () => schemaRoleUpdate,
202
225
  schemaSkill: () => schemaSkill,
226
+ schemaStockMovement: () => schemaStockMovement,
203
227
  schemaSubscribe: () => schemaSubscribe,
204
228
  schemaSubscription: () => schemaSubscription,
205
229
  schemaSubscriptionCompute: () => schemaSubscriptionCompute,
@@ -207,6 +231,9 @@ __export(src_exports, {
207
231
  schemaSubscriptionSeats: () => schemaSubscriptionSeats,
208
232
  schemaSubscriptionTransaction: () => schemaSubscriptionTransaction,
209
233
  schemaSubscriptionUpdate: () => schemaSubscriptionUpdate,
234
+ schemaTagCreate: () => schemaTagCreate,
235
+ schemaTagStd: () => schemaTagStd,
236
+ schemaTagUpdate: () => schemaTagUpdate,
210
237
  schemaTax: () => schemaTax,
211
238
  schemaTaxUpdate: () => schemaTaxUpdate,
212
239
  schemaUpdateOptions: () => schemaUpdateOptions,
@@ -214,6 +241,8 @@ __export(src_exports, {
214
241
  schemaVerification: () => schemaVerification,
215
242
  schemaVerificationOrgInvite: () => schemaVerificationOrgInvite,
216
243
  schemaWorkExp: () => schemaWorkExp,
244
+ stockMovementReferenceTypes: () => stockMovementReferenceTypes,
245
+ stockMovementTypes: () => stockMovementTypes,
217
246
  taxDirections: () => taxDirections,
218
247
  taxTypes: () => taxTypes,
219
248
  transactionSchema: () => transactionSchema,
@@ -221,6 +250,11 @@ __export(src_exports, {
221
250
  useAppController: () => useAppController,
222
251
  useAppRepo: () => useAppRepo,
223
252
  useAppService: () => useAppService,
253
+ useAssetItemController: () => useAssetItemController,
254
+ useAssetItemRepo: () => useAssetItemRepo,
255
+ useAssetItemService: () => useAssetItemService,
256
+ useAssetUnitController: () => useAssetUnitController,
257
+ useAssetUnitRepo: () => useAssetUnitRepo,
224
258
  useAuthController: () => useAuthController,
225
259
  useAuthService: () => useAuthService,
226
260
  useBuildingController: () => useBuildingController,
@@ -231,6 +265,9 @@ __export(src_exports, {
231
265
  useBuildingUnitService: () => useBuildingUnitService,
232
266
  useBusinessProfileCtrl: () => useBusinessProfileCtrl,
233
267
  useBusinessProfileRepo: () => useBusinessProfileRepo,
268
+ useCategoryController: () => useCategoryController,
269
+ useCategoryRepo: () => useCategoryRepo,
270
+ useCategoryService: () => useCategoryService,
234
271
  useChartOfAccountController: () => useChartOfAccountController,
235
272
  useChartOfAccountRepo: () => useChartOfAccountRepo,
236
273
  useCounterModel: () => useCounterModel,
@@ -286,11 +323,17 @@ __export(src_exports, {
286
323
  useRoleController: () => useRoleController,
287
324
  useRoleRepo: () => useRoleRepo,
288
325
  useRoleService: () => useRoleService,
326
+ useStockMovementController: () => useStockMovementController,
327
+ useStockMovementRepo: () => useStockMovementRepo,
328
+ useStockMovementService: () => useStockMovementService,
289
329
  useSubscriptionController: () => useSubscriptionController,
290
330
  useSubscriptionRepo: () => useSubscriptionRepo,
291
331
  useSubscriptionService: () => useSubscriptionService,
292
332
  useSubscriptionTransactionController: () => useSubscriptionTransactionController,
293
333
  useSubscriptionTransactionRepo: () => useSubscriptionTransactionRepo,
334
+ useTagController: () => useTagController,
335
+ useTagRepo: () => useTagRepo,
336
+ useTagService: () => useTagService,
294
337
  useTaxController: () => useTaxController,
295
338
  useTaxRepo: () => useTaxRepo,
296
339
  useUserController: () => useUserController,
@@ -3671,6 +3714,11 @@ function useAppService() {
3671
3714
  name: "Finance",
3672
3715
  description: "Finance and billing services."
3673
3716
  },
3717
+ {
3718
+ code: "assets",
3719
+ name: "Assets",
3720
+ description: "Asset management and tracking."
3721
+ },
3674
3722
  {
3675
3723
  code: "marketplace",
3676
3724
  name: "Marketplace",
@@ -22459,6 +22507,2523 @@ function useJournalLineController() {
22459
22507
  getById
22460
22508
  };
22461
22509
  }
22510
+
22511
+ // src/resources/asset-item/asset.item.model.ts
22512
+ var import_utils115 = require("@goweekdays/utils");
22513
+ var import_joi96 = __toESM(require("joi"));
22514
+ var import_mongodb58 = require("mongodb");
22515
+ var assetItemCategories = [
22516
+ "supply",
22517
+ "tool",
22518
+ "vehicle",
22519
+ "facility",
22520
+ "equipment"
22521
+ ];
22522
+ var assetItemClasses = [
22523
+ "consumable",
22524
+ "semi_expendable",
22525
+ "ppe"
22526
+ ];
22527
+ var assetItemPurposes = ["internal", "for-sale", "for-rent"];
22528
+ var assetItemTrackingTypes = ["quantity", "individual"];
22529
+ var assetItemStatuses = ["active", "archived"];
22530
+ var schemaAssetItemBase = {
22531
+ orgId: import_joi96.default.string().hex().length(24).required(),
22532
+ name: import_joi96.default.string().required(),
22533
+ sku: import_joi96.default.string().optional().allow("", null).default(""),
22534
+ price: import_joi96.default.number().min(0).optional().allow("", null).default(0),
22535
+ unit: import_joi96.default.string().required(),
22536
+ description: import_joi96.default.string().optional().allow("", null),
22537
+ assetCategory: import_joi96.default.string().valid(...assetItemCategories).required(),
22538
+ assetClass: import_joi96.default.string().valid(...assetItemClasses).optional().allow("", null),
22539
+ trackingType: import_joi96.default.string().valid(...assetItemTrackingTypes).required(),
22540
+ purpose: import_joi96.default.string().valid(...assetItemPurposes).required(),
22541
+ itemRefId: import_joi96.default.string().hex().length(24).optional().allow("", null),
22542
+ remarks: import_joi96.default.string().optional().allow("", null),
22543
+ departmentId: import_joi96.default.string().hex().length(24).required(),
22544
+ categoryId: import_joi96.default.string().hex().length(24).optional().allow("", null),
22545
+ subcategoryId: import_joi96.default.string().hex().length(24).optional().allow("", null),
22546
+ categoryPath: import_joi96.default.string().optional().allow("", null),
22547
+ brand: import_joi96.default.string().optional().allow("", null),
22548
+ tags: import_joi96.default.array().items(
22549
+ import_joi96.default.object({
22550
+ id: import_joi96.default.string().required(),
22551
+ name: import_joi96.default.string().required()
22552
+ })
22553
+ ).optional().default([]),
22554
+ quantityOnHand: import_joi96.default.number().min(0).optional().default(0)
22555
+ };
22556
+ var schemaAssetItem = import_joi96.default.object({
22557
+ ...schemaAssetItemBase,
22558
+ departmentName: import_joi96.default.string().required(),
22559
+ categoryName: import_joi96.default.string().optional().allow("", null),
22560
+ subcategoryName: import_joi96.default.string().optional().allow("", null),
22561
+ status: import_joi96.default.string().valid(...assetItemStatuses).optional().allow("", null)
22562
+ });
22563
+ var schemaAssetItemCreate = import_joi96.default.object({
22564
+ ...schemaAssetItemBase,
22565
+ tags: import_joi96.default.array().items(
22566
+ import_joi96.default.object({
22567
+ id: import_joi96.default.string().required(),
22568
+ name: import_joi96.default.string().required()
22569
+ })
22570
+ ).optional().default([])
22571
+ }).custom((value, helpers) => {
22572
+ if (value.subcategoryId && !value.categoryId) {
22573
+ return helpers.error("any.custom", {
22574
+ message: "categoryId is required when subcategoryId is provided."
22575
+ });
22576
+ }
22577
+ return value;
22578
+ }, "subcategory-requires-category");
22579
+ var schemaAssetItemUpdate = import_joi96.default.object({
22580
+ name: import_joi96.default.string().optional(),
22581
+ description: import_joi96.default.string().optional().allow("", null),
22582
+ assetCategory: import_joi96.default.string().valid(...assetItemCategories).optional(),
22583
+ assetClass: import_joi96.default.string().valid(...assetItemClasses).optional(),
22584
+ brand: import_joi96.default.string().optional().allow("", null),
22585
+ sku: import_joi96.default.string().optional().allow("", null),
22586
+ price: import_joi96.default.number().min(0).optional().allow("", null),
22587
+ tags: import_joi96.default.array().items(
22588
+ import_joi96.default.object({
22589
+ id: import_joi96.default.string().required(),
22590
+ name: import_joi96.default.string().required()
22591
+ })
22592
+ ).optional(),
22593
+ quantityOnHand: import_joi96.default.number().min(0).optional()
22594
+ });
22595
+ function modelAssetItem(data) {
22596
+ const { error } = schemaAssetItem.validate(data);
22597
+ if (error) {
22598
+ throw new import_utils115.BadRequestError(`Invalid asset item data: ${error.message}`);
22599
+ }
22600
+ try {
22601
+ data.orgId = new import_mongodb58.ObjectId(data.orgId);
22602
+ } catch (error2) {
22603
+ throw new import_utils115.BadRequestError("Invalid orgId format.");
22604
+ }
22605
+ try {
22606
+ data.departmentId = new import_mongodb58.ObjectId(data.departmentId);
22607
+ } catch (error2) {
22608
+ throw new import_utils115.BadRequestError("Invalid departmentId format.");
22609
+ }
22610
+ if (data.categoryId) {
22611
+ try {
22612
+ data.categoryId = new import_mongodb58.ObjectId(data.categoryId);
22613
+ } catch (error2) {
22614
+ throw new import_utils115.BadRequestError("Invalid categoryId format.");
22615
+ }
22616
+ }
22617
+ if (data.subcategoryId) {
22618
+ try {
22619
+ data.subcategoryId = new import_mongodb58.ObjectId(data.subcategoryId);
22620
+ } catch (error2) {
22621
+ throw new import_utils115.BadRequestError("Invalid subcategoryId format.");
22622
+ }
22623
+ }
22624
+ if (data.itemRefId) {
22625
+ try {
22626
+ data.itemRefId = new import_mongodb58.ObjectId(data.itemRefId);
22627
+ } catch (error2) {
22628
+ throw new import_utils115.BadRequestError("Invalid itemRefId format.");
22629
+ }
22630
+ }
22631
+ if (data.purpose === "for-sale" && (data.price === void 0 || data.price === null || data.price === 0)) {
22632
+ throw new import_utils115.BadRequestError(
22633
+ "Price must be greater than 0 for items marked as 'for-sale'."
22634
+ );
22635
+ }
22636
+ if (data.isPublic && (data.marketplacePrice === void 0 || data.marketplacePrice === null)) {
22637
+ throw new import_utils115.BadRequestError(
22638
+ "Marketplace price is required for items marked as public."
22639
+ );
22640
+ }
22641
+ return {
22642
+ _id: data._id,
22643
+ orgId: data.orgId,
22644
+ name: data.name,
22645
+ description: data.description ?? "",
22646
+ assetCategory: data.assetCategory,
22647
+ assetClass: data.assetClass,
22648
+ unit: data.unit ?? "",
22649
+ price: data.price ?? 0,
22650
+ marketplacePrice: data.marketplacePrice ?? 0,
22651
+ isPublic: data.isPublic ?? false,
22652
+ sku: data.sku ?? "",
22653
+ brand: data.brand ?? "",
22654
+ trackingType: data.trackingType,
22655
+ purpose: data.purpose,
22656
+ itemRefId: data.itemRefId ?? "",
22657
+ remarks: data.remarks ?? "",
22658
+ departmentId: data.departmentId,
22659
+ departmentName: data.departmentName,
22660
+ categoryId: data.categoryId ?? "",
22661
+ categoryName: data.categoryName ?? "",
22662
+ subcategoryId: data.subcategoryId ?? "",
22663
+ subcategoryName: data.subcategoryName ?? "",
22664
+ categoryPath: data.categoryPath ?? "",
22665
+ tags: data.tags ?? [],
22666
+ quantityOnHand: data.quantityOnHand ?? 0,
22667
+ status: data.status ?? "active",
22668
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
22669
+ updatedAt: data.updatedAt ?? "",
22670
+ deletedAt: data.deletedAt ?? ""
22671
+ };
22672
+ }
22673
+
22674
+ // src/resources/asset-item/asset.item.repository.ts
22675
+ var import_utils116 = require("@goweekdays/utils");
22676
+ var import_mongodb59 = require("mongodb");
22677
+ function useAssetItemRepo() {
22678
+ const namespace_collection = "asset.items";
22679
+ const repo = (0, import_utils116.useRepo)(namespace_collection);
22680
+ async function createIndexes() {
22681
+ try {
22682
+ await repo.collection.createIndexes([
22683
+ { key: { categoryPath: 1 } },
22684
+ { key: { departmentId: 1 } },
22685
+ { key: { categoryId: 1 } },
22686
+ { key: { subcategoryId: 1 } },
22687
+ { key: { brand: 1 } },
22688
+ { key: { "tags.name": 1 } },
22689
+ { key: { assetCategory: 1 } },
22690
+ { key: { purpose: 1 } },
22691
+ { key: { trackingType: 1 } },
22692
+ { key: { status: 1 } },
22693
+ { key: { name: "text" } }
22694
+ ]);
22695
+ } catch (error) {
22696
+ throw new import_utils116.BadRequestError("Failed to create asset item indexes.");
22697
+ }
22698
+ }
22699
+ async function add(value, session) {
22700
+ try {
22701
+ value = modelAssetItem(value);
22702
+ const res = await repo.collection.insertOne(value, { session });
22703
+ repo.delCachedData();
22704
+ return res.insertedId;
22705
+ } catch (error) {
22706
+ if (error instanceof import_utils116.AppError) {
22707
+ throw error;
22708
+ }
22709
+ import_utils116.logger.log({ level: "error", message: error.message });
22710
+ const isDuplicated = error.message.includes("duplicate");
22711
+ if (isDuplicated) {
22712
+ throw new import_utils116.BadRequestError("Asset item name already exists.");
22713
+ }
22714
+ throw new import_utils116.InternalServerError("Failed to create asset item.");
22715
+ }
22716
+ }
22717
+ async function getAll({
22718
+ search = "",
22719
+ page = 1,
22720
+ limit = 10,
22721
+ status: status2 = "active",
22722
+ assetCategory = "",
22723
+ trackingType = "",
22724
+ purpose = "",
22725
+ departmentId = "",
22726
+ categoryId = "",
22727
+ subcategoryId = "",
22728
+ brand = "",
22729
+ tags = []
22730
+ } = {}) {
22731
+ page = page > 0 ? page - 1 : 0;
22732
+ const query = { status: status2 };
22733
+ if (search) {
22734
+ query.$text = { $search: search };
22735
+ }
22736
+ if (assetCategory) {
22737
+ query.assetCategory = assetCategory;
22738
+ }
22739
+ if (trackingType) {
22740
+ query.trackingType = trackingType;
22741
+ }
22742
+ if (purpose) {
22743
+ query.purpose = purpose;
22744
+ }
22745
+ if (departmentId) {
22746
+ query.departmentId = departmentId;
22747
+ }
22748
+ if (categoryId) {
22749
+ query.categoryId = categoryId;
22750
+ }
22751
+ if (subcategoryId) {
22752
+ query.subcategoryId = subcategoryId;
22753
+ }
22754
+ if (brand) {
22755
+ query.brand = brand;
22756
+ }
22757
+ if (tags.length > 0) {
22758
+ query["tags.name"] = { $all: tags };
22759
+ }
22760
+ const cacheKey = (0, import_utils116.makeCacheKey)(namespace_collection, {
22761
+ search,
22762
+ page,
22763
+ limit,
22764
+ status: status2,
22765
+ assetCategory,
22766
+ trackingType,
22767
+ purpose,
22768
+ departmentId,
22769
+ categoryId,
22770
+ subcategoryId,
22771
+ brand,
22772
+ tags: tags.join(","),
22773
+ tag: "getAll"
22774
+ });
22775
+ try {
22776
+ const cached = await repo.getCache(cacheKey);
22777
+ if (cached)
22778
+ return cached;
22779
+ const items = await repo.collection.aggregate([
22780
+ { $match: query },
22781
+ { $sort: { _id: -1 } },
22782
+ { $skip: page * limit },
22783
+ { $limit: limit }
22784
+ ]).toArray();
22785
+ const length = await repo.collection.countDocuments(query);
22786
+ const data = (0, import_utils116.paginate)(items, page, limit, length);
22787
+ repo.setCache(cacheKey, data, 600);
22788
+ return data;
22789
+ } catch (error) {
22790
+ import_utils116.logger.log({ level: "error", message: `${error}` });
22791
+ throw error;
22792
+ }
22793
+ }
22794
+ async function getById(id) {
22795
+ const cacheKey = (0, import_utils116.makeCacheKey)(namespace_collection, {
22796
+ id,
22797
+ tag: "by-id"
22798
+ });
22799
+ try {
22800
+ const cached = await repo.getCache(cacheKey);
22801
+ if (cached)
22802
+ return cached;
22803
+ const result = await repo.collection.findOne({
22804
+ _id: new import_mongodb59.ObjectId(id)
22805
+ });
22806
+ if (!result) {
22807
+ throw new import_utils116.BadRequestError("Asset item not found.");
22808
+ }
22809
+ repo.setCache(cacheKey, result, 300);
22810
+ return result;
22811
+ } catch (error) {
22812
+ if (error instanceof import_utils116.AppError)
22813
+ throw error;
22814
+ throw new import_utils116.InternalServerError("Failed to get asset item.");
22815
+ }
22816
+ }
22817
+ async function updateById(id, value) {
22818
+ try {
22819
+ const result = await repo.collection.findOneAndUpdate(
22820
+ { _id: new import_mongodb59.ObjectId(id) },
22821
+ { $set: { ...value, updatedAt: /* @__PURE__ */ new Date() } },
22822
+ { returnDocument: "after" }
22823
+ );
22824
+ if (!result) {
22825
+ throw new import_utils116.BadRequestError("Asset item not found.");
22826
+ }
22827
+ repo.delCachedData();
22828
+ return result;
22829
+ } catch (error) {
22830
+ if (error instanceof import_utils116.AppError)
22831
+ throw error;
22832
+ throw new import_utils116.InternalServerError("Failed to update asset item.");
22833
+ }
22834
+ }
22835
+ async function incrementQuantity(id, amount, session) {
22836
+ try {
22837
+ const result = await repo.collection.findOneAndUpdate(
22838
+ { _id: new import_mongodb59.ObjectId(id) },
22839
+ {
22840
+ $inc: { quantityOnHand: amount },
22841
+ $set: { updatedAt: /* @__PURE__ */ new Date() }
22842
+ },
22843
+ { returnDocument: "after", session }
22844
+ );
22845
+ if (!result) {
22846
+ throw new import_utils116.BadRequestError("Asset item not found.");
22847
+ }
22848
+ repo.delCachedData();
22849
+ return result;
22850
+ } catch (error) {
22851
+ if (error instanceof import_utils116.AppError)
22852
+ throw error;
22853
+ throw new import_utils116.InternalServerError("Failed to update asset item quantity.");
22854
+ }
22855
+ }
22856
+ async function deleteById(id) {
22857
+ try {
22858
+ const result = await repo.collection.findOneAndUpdate(
22859
+ { _id: new import_mongodb59.ObjectId(id) },
22860
+ { $set: { deletedAt: /* @__PURE__ */ new Date(), status: "archived" } },
22861
+ { returnDocument: "after" }
22862
+ );
22863
+ if (!result) {
22864
+ throw new import_utils116.BadRequestError("Asset item not found.");
22865
+ }
22866
+ repo.delCachedData();
22867
+ return result;
22868
+ } catch (error) {
22869
+ if (error instanceof import_utils116.AppError)
22870
+ throw error;
22871
+ throw new import_utils116.InternalServerError("Failed to delete asset item.");
22872
+ }
22873
+ }
22874
+ return {
22875
+ createIndexes,
22876
+ add,
22877
+ getAll,
22878
+ getById,
22879
+ updateById,
22880
+ incrementQuantity,
22881
+ deleteById
22882
+ };
22883
+ }
22884
+
22885
+ // src/resources/asset-item/asset.item.service.ts
22886
+ var import_utils120 = require("@goweekdays/utils");
22887
+
22888
+ // src/resources/tag/tag.repository.ts
22889
+ var import_utils117 = require("@goweekdays/utils");
22890
+
22891
+ // src/resources/tag/tag.model.ts
22892
+ var import_joi97 = __toESM(require("joi"));
22893
+ var import_mongodb60 = require("mongodb");
22894
+ var schemaTagFields = {
22895
+ name: import_joi97.default.string().required(),
22896
+ type: import_joi97.default.string().valid("public", "private").required(),
22897
+ orgId: import_joi97.default.string().hex().optional().allow("", null),
22898
+ categoryPath: import_joi97.default.string().optional().allow("", null),
22899
+ status: import_joi97.default.string().valid("active", "pending").optional()
22900
+ };
22901
+ var schemaTagCreate = import_joi97.default.object(schemaTagFields);
22902
+ var schemaTagStd = import_joi97.default.object({
22903
+ ...schemaTagFields,
22904
+ normalizedName: import_joi97.default.string().required(),
22905
+ usageCount: import_joi97.default.number().integer().min(0).optional()
22906
+ });
22907
+ var schemaTagUpdate = import_joi97.default.object({
22908
+ name: import_joi97.default.string().optional(),
22909
+ type: import_joi97.default.string().valid("public", "private").optional(),
22910
+ orgId: import_joi97.default.string().hex().optional().allow("", null),
22911
+ categoryPath: import_joi97.default.string().optional().allow("", null),
22912
+ status: import_joi97.default.string().valid("active", "pending").optional()
22913
+ }).min(1);
22914
+ function modelTag(data) {
22915
+ const { error } = schemaTagStd.validate(data, { allowUnknown: false });
22916
+ if (error) {
22917
+ throw new Error(`Invalid tag data: ${error.message}`);
22918
+ }
22919
+ if (data.orgId) {
22920
+ try {
22921
+ data.orgId = new import_mongodb60.ObjectId(data.orgId);
22922
+ } catch (error2) {
22923
+ throw new Error(`Invalid orgId: ${data.orgId}`);
22924
+ }
22925
+ }
22926
+ return {
22927
+ _id: data._id,
22928
+ name: data.name,
22929
+ normalizedName: data.normalizedName,
22930
+ type: data.type,
22931
+ orgId: data.orgId ?? "",
22932
+ categoryPath: data.categoryPath ?? "",
22933
+ status: data.status ?? "pending",
22934
+ usageCount: data.usageCount ?? 0,
22935
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
22936
+ updatedAt: data.updatedAt ?? ""
22937
+ };
22938
+ }
22939
+
22940
+ // src/resources/tag/tag.repository.ts
22941
+ var import_mongodb61 = require("mongodb");
22942
+ var import_joi98 = __toESM(require("joi"));
22943
+ function useTagRepo() {
22944
+ const namespace_collection = "tags";
22945
+ const repo = (0, import_utils117.useRepo)(namespace_collection);
22946
+ async function createIndexes() {
22947
+ try {
22948
+ await repo.collection.createIndexes([
22949
+ { key: { name: 1 } },
22950
+ { key: { normalizedName: 1 } },
22951
+ { key: { type: 1 } },
22952
+ { key: { orgId: 1 } },
22953
+ { key: { status: 1 } },
22954
+ { key: { categoryPath: 1 } },
22955
+ {
22956
+ key: { normalizedName: 1, orgId: 1 },
22957
+ unique: true,
22958
+ name: "unique_normalized_name_per_org"
22959
+ }
22960
+ ]);
22961
+ } catch (error) {
22962
+ throw new Error("Failed to create index on tags.");
22963
+ }
22964
+ }
22965
+ async function add(value, session) {
22966
+ try {
22967
+ value = modelTag(value);
22968
+ const res = await repo.collection.insertOne(value, { session });
22969
+ repo.delCachedData();
22970
+ return res.insertedId;
22971
+ } catch (error) {
22972
+ import_utils117.logger.log({
22973
+ level: "error",
22974
+ message: error.message
22975
+ });
22976
+ const isDuplicate = /normalizedName|unique/i.test(error.message);
22977
+ if (isDuplicate) {
22978
+ throw new import_utils117.BadRequestError("A tag with this name already exists.");
22979
+ }
22980
+ throw new import_utils117.BadRequestError(`Failed to create tag: ${error.message}`);
22981
+ }
22982
+ }
22983
+ async function getAll(options) {
22984
+ const validation = import_joi98.default.object({
22985
+ page: import_joi98.default.number().min(1).optional().allow("", null),
22986
+ limit: import_joi98.default.number().min(1).optional().allow("", null),
22987
+ search: import_joi98.default.string().optional().allow("", null),
22988
+ type: import_joi98.default.string().valid("public", "private").optional().allow("", null),
22989
+ orgId: import_joi98.default.string().hex().optional().allow("", null),
22990
+ status: import_joi98.default.string().valid("active", "pending").optional().allow("", null),
22991
+ categoryPath: import_joi98.default.string().optional().allow("", null)
22992
+ });
22993
+ const { error } = validation.validate(options);
22994
+ if (error) {
22995
+ throw new import_utils117.BadRequestError(error.message);
22996
+ }
22997
+ options.page = options.page && options.page > 0 ? options.page - 1 : 0;
22998
+ options.limit = options.limit ?? 10;
22999
+ const query = {};
23000
+ const cacheKeyOptions = {
23001
+ limit: options.limit,
23002
+ page: options.page
23003
+ };
23004
+ if (options.search) {
23005
+ query.name = { $regex: options.search, $options: "i" };
23006
+ cacheKeyOptions.search = options.search;
23007
+ }
23008
+ if (options.type) {
23009
+ query.type = options.type;
23010
+ cacheKeyOptions.type = options.type;
23011
+ }
23012
+ if (options.orgId) {
23013
+ query.orgId = new import_mongodb61.ObjectId(options.orgId);
23014
+ cacheKeyOptions.orgId = options.orgId;
23015
+ }
23016
+ if (options.status) {
23017
+ query.status = options.status;
23018
+ cacheKeyOptions.status = options.status;
23019
+ }
23020
+ if (options.categoryPath) {
23021
+ query.categoryPath = options.categoryPath;
23022
+ cacheKeyOptions.categoryPath = options.categoryPath;
23023
+ }
23024
+ const cacheKey = (0, import_utils117.makeCacheKey)(namespace_collection, cacheKeyOptions);
23025
+ try {
23026
+ const cached = await repo.getCache(cacheKey);
23027
+ if (cached) {
23028
+ return cached;
23029
+ }
23030
+ const items = await repo.collection.aggregate([
23031
+ { $match: query },
23032
+ { $sort: { name: 1 } },
23033
+ { $skip: options.page * options.limit },
23034
+ { $limit: options.limit },
23035
+ {
23036
+ $project: {
23037
+ _id: 1,
23038
+ name: 1,
23039
+ normalizedName: 1,
23040
+ type: 1,
23041
+ orgId: 1,
23042
+ categoryPath: 1,
23043
+ status: 1,
23044
+ usageCount: 1
23045
+ }
23046
+ }
23047
+ ]).toArray();
23048
+ const length = await repo.collection.countDocuments(query);
23049
+ const data = (0, import_utils117.paginate)(items, options.page, options.limit, length);
23050
+ repo.setCache(cacheKey, data, 600).then(() => {
23051
+ import_utils117.logger.log({
23052
+ level: "info",
23053
+ message: `Cache set for getAll tags: ${cacheKey}`
23054
+ });
23055
+ }).catch((err) => {
23056
+ import_utils117.logger.log({
23057
+ level: "error",
23058
+ message: `Failed to set cache for getAll tags: ${err.message}`
23059
+ });
23060
+ });
23061
+ return data;
23062
+ } catch (error2) {
23063
+ import_utils117.logger.log({ level: "error", message: `${error2}` });
23064
+ throw error2;
23065
+ }
23066
+ }
23067
+ async function getById(_id) {
23068
+ try {
23069
+ _id = new import_mongodb61.ObjectId(_id);
23070
+ } catch (error) {
23071
+ throw new import_utils117.BadRequestError("Invalid ID.");
23072
+ }
23073
+ const cacheKey = (0, import_utils117.makeCacheKey)(namespace_collection, { _id: String(_id) });
23074
+ try {
23075
+ const cached = await repo.getCache(cacheKey);
23076
+ if (cached) {
23077
+ return cached;
23078
+ }
23079
+ const result = await repo.collection.findOne({ _id });
23080
+ if (!result) {
23081
+ throw new import_utils117.BadRequestError("Tag not found.");
23082
+ }
23083
+ repo.setCache(cacheKey, result, 300).then(() => {
23084
+ import_utils117.logger.log({
23085
+ level: "info",
23086
+ message: `Cache set for tag by id: ${cacheKey}`
23087
+ });
23088
+ }).catch((err) => {
23089
+ import_utils117.logger.log({
23090
+ level: "error",
23091
+ message: `Failed to set cache for tag by id: ${err.message}`
23092
+ });
23093
+ });
23094
+ return result;
23095
+ } catch (error) {
23096
+ if (error instanceof import_utils117.AppError) {
23097
+ throw error;
23098
+ } else {
23099
+ throw new import_utils117.InternalServerError("Failed to get tag.");
23100
+ }
23101
+ }
23102
+ }
23103
+ async function updateById(_id, options, session) {
23104
+ try {
23105
+ _id = new import_mongodb61.ObjectId(_id);
23106
+ } catch (error) {
23107
+ throw new import_utils117.BadRequestError("Invalid Tag ID.");
23108
+ }
23109
+ try {
23110
+ await repo.collection.updateOne(
23111
+ { _id },
23112
+ { $set: { ...options, updatedAt: /* @__PURE__ */ new Date() } },
23113
+ { session }
23114
+ );
23115
+ repo.delCachedData();
23116
+ return "Successfully updated tag.";
23117
+ } catch (error) {
23118
+ const isDuplicate = /normalizedName|unique/i.test(error.message);
23119
+ if (isDuplicate) {
23120
+ throw new import_utils117.BadRequestError("A tag with this name already exists.");
23121
+ }
23122
+ throw new import_utils117.InternalServerError("Failed to update tag.");
23123
+ }
23124
+ }
23125
+ async function deleteById(_id, session) {
23126
+ try {
23127
+ _id = new import_mongodb61.ObjectId(_id);
23128
+ } catch (error) {
23129
+ throw new import_utils117.BadRequestError("Invalid ID.");
23130
+ }
23131
+ try {
23132
+ await repo.collection.deleteOne({ _id }, { session });
23133
+ repo.delCachedData();
23134
+ return "Successfully deleted tag.";
23135
+ } catch (error) {
23136
+ throw new import_utils117.InternalServerError("Failed to delete tag.");
23137
+ }
23138
+ }
23139
+ async function findByNormalizedNameAndOrg(normalizedName, orgId) {
23140
+ try {
23141
+ const query = { normalizedName };
23142
+ query.orgId = new import_mongodb61.ObjectId(orgId);
23143
+ return await repo.collection.findOne(query);
23144
+ } catch (error) {
23145
+ if (error instanceof import_utils117.AppError)
23146
+ throw error;
23147
+ throw new import_utils117.InternalServerError("Failed to find tag.");
23148
+ }
23149
+ }
23150
+ async function incrementUsageCount(_id, amount = 1, session) {
23151
+ try {
23152
+ _id = new import_mongodb61.ObjectId(_id);
23153
+ } catch (error) {
23154
+ throw new import_utils117.BadRequestError("Invalid ID.");
23155
+ }
23156
+ try {
23157
+ await repo.collection.updateOne(
23158
+ { _id },
23159
+ { $inc: { usageCount: amount } },
23160
+ { session }
23161
+ );
23162
+ repo.delCachedData();
23163
+ } catch (error) {
23164
+ throw new import_utils117.InternalServerError("Failed to update tag usage count.");
23165
+ }
23166
+ }
23167
+ return {
23168
+ createIndexes,
23169
+ add,
23170
+ getAll,
23171
+ getById,
23172
+ findByNormalizedNameAndOrg,
23173
+ updateById,
23174
+ deleteById,
23175
+ incrementUsageCount
23176
+ };
23177
+ }
23178
+
23179
+ // src/resources/asset-item-category/category.repository.ts
23180
+ var import_utils119 = require("@goweekdays/utils");
23181
+
23182
+ // src/resources/asset-item-category/category.model.ts
23183
+ var import_joi99 = __toESM(require("joi"));
23184
+ var import_mongodb62 = require("mongodb");
23185
+ var import_utils118 = require("@goweekdays/utils");
23186
+ var categoryLevels = [
23187
+ "department",
23188
+ "category",
23189
+ "subcategory"
23190
+ ];
23191
+ var schemaCategoryNodeFields = {
23192
+ orgId: import_joi99.default.string().hex().length(24).optional().allow("", null),
23193
+ level: import_joi99.default.string().valid(...categoryLevels).required(),
23194
+ type: import_joi99.default.string().valid("public", "private").optional().default("public"),
23195
+ name: import_joi99.default.string().required(),
23196
+ displayName: import_joi99.default.string().required(),
23197
+ parentId: import_joi99.default.string().hex().length(24).optional().allow("", null),
23198
+ path: import_joi99.default.string().optional().allow("", null),
23199
+ isActive: import_joi99.default.boolean().optional().default(true)
23200
+ };
23201
+ var schemaCategoryNodeCreate = import_joi99.default.object({
23202
+ displayName: import_joi99.default.string().required(),
23203
+ parentId: import_joi99.default.string().hex().length(24).optional().allow("", null),
23204
+ orgId: import_joi99.default.string().hex().length(24).optional().allow("", null)
23205
+ });
23206
+ var schemaCategoryNodeUpdate = import_joi99.default.object({
23207
+ displayName: import_joi99.default.string().optional()
23208
+ });
23209
+ var schemaCategoryNodeStd = import_joi99.default.object(schemaCategoryNodeFields);
23210
+ var schemaCategoryGetAll = import_joi99.default.object({
23211
+ page: import_joi99.default.number().integer().min(1).default(1),
23212
+ limit: import_joi99.default.number().integer().min(1).max(100).default(20),
23213
+ org: import_joi99.default.string().hex().length(24).optional().allow("", null).default(""),
23214
+ parent: import_joi99.default.string().hex().length(24).optional().allow("", null).default(""),
23215
+ level: import_joi99.default.string().valid(...categoryLevels, "").optional().default(""),
23216
+ type: import_joi99.default.string().valid("public", "private", "").optional().default(""),
23217
+ search: import_joi99.default.string().optional().allow("", null).default(""),
23218
+ status: import_joi99.default.string().optional().allow("", null).default("active")
23219
+ });
23220
+ function normalizeName(displayName) {
23221
+ return displayName.trim().toLowerCase().replace(/\s+/g, "_");
23222
+ }
23223
+ function buildCategoryPath(departmentName, categoryName, subcategoryName) {
23224
+ return [departmentName, categoryName, subcategoryName].filter(Boolean).join(".").replace(/ /g, "_").toLowerCase();
23225
+ }
23226
+ function modelCategoryNode(data) {
23227
+ const { error } = schemaCategoryNodeStd.validate(data);
23228
+ if (error) {
23229
+ throw new import_utils118.BadRequestError(`Invalid category node data: ${error.message}`);
23230
+ }
23231
+ if (data.orgId) {
23232
+ try {
23233
+ data.orgId = new import_mongodb62.ObjectId(data.orgId);
23234
+ } catch {
23235
+ throw new import_utils118.BadRequestError(`Invalid orgId: ${data.orgId}`);
23236
+ }
23237
+ }
23238
+ if (data.parentId) {
23239
+ try {
23240
+ data.parentId = new import_mongodb62.ObjectId(data.parentId);
23241
+ } catch {
23242
+ throw new import_utils118.BadRequestError(`Invalid parentId: ${data.parentId}`);
23243
+ }
23244
+ }
23245
+ return {
23246
+ _id: data._id,
23247
+ orgId: data.orgId ?? "",
23248
+ level: data.level,
23249
+ type: data.type ?? "public",
23250
+ name: data.name,
23251
+ displayName: data.displayName,
23252
+ parentId: data.parentId ?? "",
23253
+ path: data.path ?? "",
23254
+ status: data.status ?? "active",
23255
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
23256
+ updatedAt: data.updatedAt ?? ""
23257
+ };
23258
+ }
23259
+
23260
+ // src/resources/asset-item-category/category.repository.ts
23261
+ var import_mongodb63 = require("mongodb");
23262
+ function useCategoryRepo() {
23263
+ const namespace_collection = "categories";
23264
+ const repo = (0, import_utils119.useRepo)(namespace_collection);
23265
+ async function createIndexes() {
23266
+ try {
23267
+ await repo.collection.createIndexes([
23268
+ { key: { level: 1 } },
23269
+ { key: { parentId: 1 } },
23270
+ {
23271
+ key: { name: "text" },
23272
+ name: "text_name"
23273
+ },
23274
+ {
23275
+ key: { name: 1, parentId: 1 },
23276
+ unique: true,
23277
+ name: "unique_name_per_parent"
23278
+ },
23279
+ { key: { path: 1 } },
23280
+ { key: { isActive: 1 } }
23281
+ ]);
23282
+ } catch (error) {
23283
+ throw new import_utils119.InternalServerError("Failed to create category indexes.");
23284
+ }
23285
+ }
23286
+ async function add(value, session) {
23287
+ try {
23288
+ value = modelCategoryNode(value);
23289
+ const res = await repo.collection.insertOne(value, { session });
23290
+ repo.delCachedData();
23291
+ return res.insertedId;
23292
+ } catch (error) {
23293
+ import_utils119.logger.log({ level: "error", message: error.message });
23294
+ const isDuplicated = error.message.includes("duplicate");
23295
+ if (isDuplicated) {
23296
+ throw new import_utils119.BadRequestError(
23297
+ "Category name already exists under this parent."
23298
+ );
23299
+ }
23300
+ throw new import_utils119.InternalServerError("Failed to create category node.");
23301
+ }
23302
+ }
23303
+ async function getAll({
23304
+ page = 1,
23305
+ limit = 20,
23306
+ org = "",
23307
+ parent = "",
23308
+ level = "",
23309
+ type = "",
23310
+ search = "",
23311
+ status: status2 = "active"
23312
+ } = {}) {
23313
+ page = page > 0 ? page - 1 : 0;
23314
+ const query = { status: status2 };
23315
+ if (org) {
23316
+ try {
23317
+ query.orgId = new import_mongodb63.ObjectId(org);
23318
+ } catch (error) {
23319
+ throw new import_utils119.BadRequestError("Invalid org ID.");
23320
+ }
23321
+ }
23322
+ if (level) {
23323
+ query.level = level;
23324
+ }
23325
+ if (type) {
23326
+ query.type = type;
23327
+ }
23328
+ if (parent) {
23329
+ try {
23330
+ query.parentId = new import_mongodb63.ObjectId(parent);
23331
+ } catch (error) {
23332
+ throw new import_utils119.BadRequestError("Invalid parentId.");
23333
+ }
23334
+ }
23335
+ const cacheKeyOptions = {
23336
+ page,
23337
+ limit,
23338
+ org,
23339
+ parent,
23340
+ level,
23341
+ type,
23342
+ status: status2,
23343
+ tag: "all"
23344
+ };
23345
+ if (search) {
23346
+ query.$text = { $search: `"${search}"` };
23347
+ cacheKeyOptions.search = search;
23348
+ }
23349
+ const cacheKey = (0, import_utils119.makeCacheKey)(namespace_collection, cacheKeyOptions);
23350
+ import_utils119.logger.log({
23351
+ level: "info",
23352
+ message: `Cache key for getAll categories: ${cacheKey}`
23353
+ });
23354
+ try {
23355
+ const cached = await repo.getCache(cacheKey);
23356
+ if (cached) {
23357
+ import_utils119.logger.log({
23358
+ level: "info",
23359
+ message: `Cache hit for getAll categories: ${cacheKey}`
23360
+ });
23361
+ return cached;
23362
+ }
23363
+ const items = await repo.collection.aggregate([
23364
+ { $match: query },
23365
+ { $skip: page * limit },
23366
+ { $limit: limit }
23367
+ ]).toArray();
23368
+ const length = await repo.collection.countDocuments(query);
23369
+ const data = (0, import_utils119.paginate)(items, page, limit, length);
23370
+ repo.setCache(cacheKey, data, 600).then(() => {
23371
+ import_utils119.logger.log({
23372
+ level: "info",
23373
+ message: `Cache set for getAll categories: ${cacheKey}`
23374
+ });
23375
+ }).catch((err) => {
23376
+ import_utils119.logger.log({
23377
+ level: "error",
23378
+ message: `Failed to set cache for getAll categories: ${err.message}`
23379
+ });
23380
+ });
23381
+ return data;
23382
+ } catch (error) {
23383
+ import_utils119.logger.log({ level: "error", message: `${error}` });
23384
+ throw new import_utils119.InternalServerError("Failed to get categories.");
23385
+ }
23386
+ }
23387
+ async function getById(_id) {
23388
+ try {
23389
+ _id = new import_mongodb63.ObjectId(_id);
23390
+ } catch {
23391
+ throw new import_utils119.BadRequestError("Invalid category ID.");
23392
+ }
23393
+ const cacheKey = (0, import_utils119.makeCacheKey)(namespace_collection, {
23394
+ _id: String(_id),
23395
+ tag: "by-id"
23396
+ });
23397
+ try {
23398
+ const cached = await repo.getCache(cacheKey);
23399
+ if (cached)
23400
+ return cached;
23401
+ const result = await repo.collection.findOne({ _id });
23402
+ if (!result) {
23403
+ throw new import_utils119.BadRequestError("Category node not found.");
23404
+ }
23405
+ repo.setCache(cacheKey, result, 300).catch((err) => {
23406
+ import_utils119.logger.log({
23407
+ level: "error",
23408
+ message: `Failed to set cache for category by id: ${err.message}`
23409
+ });
23410
+ });
23411
+ return result;
23412
+ } catch (error) {
23413
+ if (error instanceof import_utils119.AppError)
23414
+ throw error;
23415
+ throw new import_utils119.InternalServerError("Failed to get category node.");
23416
+ }
23417
+ }
23418
+ async function getByNameParent(name, { parentId = "", orgId = "" } = {}) {
23419
+ const query = {
23420
+ $text: { $search: `"${name.trim()}"` }
23421
+ };
23422
+ if (parentId) {
23423
+ try {
23424
+ query.parentId = new import_mongodb63.ObjectId(parentId);
23425
+ } catch (error) {
23426
+ throw new import_utils119.BadRequestError("Invalid parentId.");
23427
+ }
23428
+ }
23429
+ if (orgId) {
23430
+ try {
23431
+ query.orgId = new import_mongodb63.ObjectId(orgId);
23432
+ } catch (error) {
23433
+ throw new import_utils119.BadRequestError("Invalid orgId.");
23434
+ }
23435
+ }
23436
+ try {
23437
+ const cacheKey = (0, import_utils119.makeCacheKey)(namespace_collection, {
23438
+ name,
23439
+ parentId: parentId ?? "null",
23440
+ orgId: orgId ?? "null",
23441
+ tag: "by-name-parent"
23442
+ });
23443
+ import_utils119.logger.log({
23444
+ level: "info",
23445
+ message: `Cache key for getByNameParentId: ${cacheKey}`
23446
+ });
23447
+ const cached = await repo.getCache(cacheKey);
23448
+ if (cached) {
23449
+ import_utils119.logger.log({
23450
+ level: "info",
23451
+ message: `Cache hit for getByNameParentId: ${cacheKey}`
23452
+ });
23453
+ return cached;
23454
+ }
23455
+ const result = await repo.collection.findOne(query);
23456
+ if (result) {
23457
+ repo.setCache(cacheKey, result, 300).catch((err) => {
23458
+ import_utils119.logger.log({
23459
+ level: "error",
23460
+ message: `Failed to set cache for category by name and parentId: ${err.message}`
23461
+ });
23462
+ });
23463
+ }
23464
+ return result;
23465
+ } catch (error) {
23466
+ console.log(error);
23467
+ throw new import_utils119.InternalServerError("Failed to get category node by name.");
23468
+ }
23469
+ }
23470
+ async function updateById(_id, update, session) {
23471
+ try {
23472
+ _id = new import_mongodb63.ObjectId(_id);
23473
+ } catch {
23474
+ throw new import_utils119.BadRequestError("Invalid category ID.");
23475
+ }
23476
+ try {
23477
+ const res = await repo.collection.updateOne(
23478
+ { _id },
23479
+ { $set: update },
23480
+ { session }
23481
+ );
23482
+ if (res.matchedCount === 0) {
23483
+ throw new import_utils119.BadRequestError("Category node not found.");
23484
+ }
23485
+ repo.delCachedData();
23486
+ } catch (error) {
23487
+ if (error instanceof import_utils119.AppError)
23488
+ throw error;
23489
+ import_utils119.logger.log({ level: "error", message: error.message });
23490
+ const isDuplicated = error.message.includes("duplicate");
23491
+ if (isDuplicated) {
23492
+ throw new import_utils119.BadRequestError(
23493
+ "Category name already exists under this parent."
23494
+ );
23495
+ }
23496
+ throw new import_utils119.InternalServerError("Failed to update category node.");
23497
+ }
23498
+ }
23499
+ async function hasChildren(_id) {
23500
+ try {
23501
+ _id = new import_mongodb63.ObjectId(_id);
23502
+ } catch {
23503
+ throw new import_utils119.BadRequestError("Invalid category ID.");
23504
+ }
23505
+ try {
23506
+ const count = await repo.collection.countDocuments({ parentId: _id });
23507
+ return count > 0;
23508
+ } catch (error) {
23509
+ throw new import_utils119.InternalServerError("Failed to check for child categories.");
23510
+ }
23511
+ }
23512
+ async function softDeleteById(_id, session) {
23513
+ try {
23514
+ _id = new import_mongodb63.ObjectId(_id);
23515
+ } catch {
23516
+ throw new import_utils119.BadRequestError("Invalid category ID.");
23517
+ }
23518
+ try {
23519
+ const res = await repo.collection.updateOne(
23520
+ { _id },
23521
+ { $set: { status: "deleted", updatedAt: /* @__PURE__ */ new Date() } },
23522
+ { session }
23523
+ );
23524
+ if (res.matchedCount === 0) {
23525
+ throw new import_utils119.BadRequestError("Category node not found.");
23526
+ }
23527
+ repo.delCachedData();
23528
+ } catch (error) {
23529
+ if (error instanceof import_utils119.AppError)
23530
+ throw error;
23531
+ throw new import_utils119.InternalServerError("Failed to delete category node.");
23532
+ }
23533
+ }
23534
+ return {
23535
+ createIndexes,
23536
+ add,
23537
+ getAll,
23538
+ getById,
23539
+ getByNameParent,
23540
+ updateById,
23541
+ hasChildren,
23542
+ softDeleteById
23543
+ };
23544
+ }
23545
+
23546
+ // src/resources/asset-item/asset.item.service.ts
23547
+ function normalizeTagName(name) {
23548
+ return name.trim().toLowerCase().replace(/\s+/g, "-");
23549
+ }
23550
+ function useAssetItemService() {
23551
+ const assetItemRepo = useAssetItemRepo();
23552
+ const tagRepo = useTagRepo();
23553
+ const categoryRepo = useCategoryRepo();
23554
+ async function resolveTags(tagNames, orgId, categoryPath, session) {
23555
+ const resolvedTags = [];
23556
+ for (const tag of tagNames) {
23557
+ const normalizedName = normalizeTagName(tag.name);
23558
+ const existing = await tagRepo.findByNormalizedNameAndOrg(
23559
+ normalizedName,
23560
+ orgId
23561
+ );
23562
+ if (existing && existing._id) {
23563
+ await tagRepo.incrementUsageCount(existing._id, 1, session);
23564
+ resolvedTags.push({
23565
+ id: String(existing._id),
23566
+ name: existing.name
23567
+ });
23568
+ } else {
23569
+ const tagData = {
23570
+ name: tag.name.trim(),
23571
+ normalizedName,
23572
+ type: "private",
23573
+ orgId,
23574
+ categoryPath,
23575
+ status: "active",
23576
+ usageCount: 1
23577
+ };
23578
+ const tagId = await tagRepo.add(tagData, session);
23579
+ resolvedTags.push({
23580
+ id: String(tagId),
23581
+ name: tag.name.trim()
23582
+ });
23583
+ }
23584
+ }
23585
+ return resolvedTags;
23586
+ }
23587
+ async function add(value) {
23588
+ const session = import_utils120.useAtlas.getClient()?.startSession();
23589
+ if (!session) {
23590
+ throw new import_utils120.InternalServerError(
23591
+ "Unable to start session for asset item service."
23592
+ );
23593
+ }
23594
+ try {
23595
+ await session.startTransaction();
23596
+ const department = await categoryRepo.getById(value.departmentId);
23597
+ value.departmentName = department.displayName;
23598
+ if (value.categoryId) {
23599
+ const category = await categoryRepo.getById(value.categoryId);
23600
+ value.categoryName = category.displayName;
23601
+ }
23602
+ if (value.subcategoryId) {
23603
+ const subcategory = await categoryRepo.getById(value.subcategoryId);
23604
+ value.subcategoryName = subcategory.displayName;
23605
+ }
23606
+ const categoryPath = buildCategoryPath(
23607
+ value.departmentName,
23608
+ value.categoryName ?? "",
23609
+ value.subcategoryName ?? ""
23610
+ );
23611
+ value.categoryPath = categoryPath;
23612
+ const resolvedTags = value.tags.length ? await resolveTags(
23613
+ value.tags,
23614
+ value.orgId,
23615
+ value.categoryPath,
23616
+ session
23617
+ ) : [];
23618
+ const assetItem = {
23619
+ ...value,
23620
+ tags: resolvedTags
23621
+ };
23622
+ const assetItemId = await assetItemRepo.add(assetItem, session);
23623
+ await session.commitTransaction();
23624
+ return assetItemId;
23625
+ } catch (error) {
23626
+ await session.abortTransaction();
23627
+ if (error instanceof import_utils120.AppError)
23628
+ throw error;
23629
+ throw new import_utils120.InternalServerError("Failed to create asset item.");
23630
+ } finally {
23631
+ session.endSession();
23632
+ }
23633
+ }
23634
+ return {
23635
+ add
23636
+ };
23637
+ }
23638
+
23639
+ // src/resources/asset-item/asset.item.controller.ts
23640
+ var import_joi100 = __toESM(require("joi"));
23641
+ var import_utils121 = require("@goweekdays/utils");
23642
+ var paginationSchema3 = import_joi100.default.object({
23643
+ page: import_joi100.default.number().min(1).optional().allow("", null),
23644
+ limit: import_joi100.default.number().min(1).optional().allow("", null),
23645
+ search: import_joi100.default.string().optional().allow("", null),
23646
+ status: import_joi100.default.string().valid(...assetItemStatuses).optional().allow("", null),
23647
+ assetCategory: import_joi100.default.string().valid(...assetItemCategories).optional().allow("", null),
23648
+ trackingType: import_joi100.default.string().valid(...assetItemTrackingTypes).optional().allow("", null),
23649
+ purpose: import_joi100.default.string().valid(...assetItemPurposes).optional().allow("", null),
23650
+ departmentId: import_joi100.default.string().optional().allow("", null),
23651
+ categoryId: import_joi100.default.string().optional().allow("", null),
23652
+ subcategoryId: import_joi100.default.string().optional().allow("", null),
23653
+ brand: import_joi100.default.string().optional().allow("", null),
23654
+ tags: import_joi100.default.string().optional().allow("", null)
23655
+ });
23656
+ var idParamSchema2 = import_joi100.default.object({
23657
+ id: import_joi100.default.string().hex().length(24).required()
23658
+ });
23659
+ function useAssetItemController() {
23660
+ const repo = useAssetItemRepo();
23661
+ const service = useAssetItemService();
23662
+ async function add(req, res, next) {
23663
+ try {
23664
+ const { error, value } = schemaAssetItemCreate.validate(req.body);
23665
+ if (error) {
23666
+ next(new import_utils121.BadRequestError(error.message));
23667
+ return;
23668
+ }
23669
+ const assetItemId = await service.add(value);
23670
+ res.status(201).json({
23671
+ message: "Asset item created successfully.",
23672
+ assetItemId
23673
+ });
23674
+ } catch (error) {
23675
+ next(error);
23676
+ }
23677
+ }
23678
+ async function getAll(req, res, next) {
23679
+ const { error } = paginationSchema3.validate(req.query);
23680
+ if (error) {
23681
+ next(new import_utils121.BadRequestError(error.message));
23682
+ return;
23683
+ }
23684
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
23685
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
23686
+ const search = req.query.search ?? "";
23687
+ const status2 = req.query.status ?? "active";
23688
+ const assetCategory = req.query.assetCategory ?? "";
23689
+ const trackingType = req.query.trackingType ?? "";
23690
+ const purpose = req.query.purpose ?? "";
23691
+ const departmentId = req.query.departmentId ?? "";
23692
+ const categoryId = req.query.categoryId ?? "";
23693
+ const subcategoryId = req.query.subcategoryId ?? "";
23694
+ const brand = req.query.brand ?? "";
23695
+ const tagsParam = req.query.tags ?? "";
23696
+ const tags = tagsParam ? tagsParam.split(",").filter(Boolean) : [];
23697
+ if (!isFinite(page)) {
23698
+ next(new import_utils121.BadRequestError("Invalid page number."));
23699
+ return;
23700
+ }
23701
+ if (!isFinite(limit)) {
23702
+ next(new import_utils121.BadRequestError("Invalid limit number."));
23703
+ return;
23704
+ }
23705
+ try {
23706
+ const results = await repo.getAll({
23707
+ page,
23708
+ limit,
23709
+ search,
23710
+ status: status2,
23711
+ assetCategory,
23712
+ trackingType,
23713
+ purpose,
23714
+ departmentId,
23715
+ categoryId,
23716
+ subcategoryId,
23717
+ brand,
23718
+ tags
23719
+ });
23720
+ res.json(results);
23721
+ } catch (error2) {
23722
+ next(error2);
23723
+ }
23724
+ }
23725
+ async function getById(req, res, next) {
23726
+ const { error } = idParamSchema2.validate(req.params);
23727
+ if (error) {
23728
+ next(new import_utils121.BadRequestError(error.message));
23729
+ return;
23730
+ }
23731
+ try {
23732
+ const assetItem = await repo.getById(req.params.id);
23733
+ res.json(assetItem);
23734
+ } catch (error2) {
23735
+ next(error2);
23736
+ }
23737
+ }
23738
+ async function updateById(req, res, next) {
23739
+ const { error: paramError } = idParamSchema2.validate(req.params);
23740
+ if (paramError) {
23741
+ next(new import_utils121.BadRequestError(paramError.message));
23742
+ return;
23743
+ }
23744
+ const { error: bodyError } = schemaAssetItemUpdate.validate(req.body);
23745
+ if (bodyError) {
23746
+ next(new import_utils121.BadRequestError(bodyError.message));
23747
+ return;
23748
+ }
23749
+ try {
23750
+ const updatedAssetItem = await repo.updateById(req.params.id, req.body);
23751
+ res.json({
23752
+ message: "Asset item updated successfully.",
23753
+ assetItem: updatedAssetItem
23754
+ });
23755
+ } catch (error) {
23756
+ next(error);
23757
+ }
23758
+ }
23759
+ async function deleteById(req, res, next) {
23760
+ const { error } = idParamSchema2.validate(req.params);
23761
+ if (error) {
23762
+ next(new import_utils121.BadRequestError(error.message));
23763
+ return;
23764
+ }
23765
+ try {
23766
+ await repo.deleteById(req.params.id);
23767
+ res.json({
23768
+ message: "Asset item deleted successfully."
23769
+ });
23770
+ } catch (error2) {
23771
+ next(error2);
23772
+ }
23773
+ }
23774
+ return {
23775
+ add,
23776
+ getAll,
23777
+ getById,
23778
+ updateById,
23779
+ deleteById
23780
+ };
23781
+ }
23782
+
23783
+ // src/resources/asset-item-unit/asset.unit.model.ts
23784
+ var import_joi101 = __toESM(require("joi"));
23785
+ var import_mongodb64 = require("mongodb");
23786
+ var assetUnitStatuses = [
23787
+ "available",
23788
+ "assigned",
23789
+ "maintenance",
23790
+ "disposed"
23791
+ ];
23792
+ var schemaAssetUnitBase = {
23793
+ itemId: import_joi101.default.string().hex().length(24).required(),
23794
+ serialNumber: import_joi101.default.string().optional().allow("", null),
23795
+ plateNumber: import_joi101.default.string().optional().allow("", null),
23796
+ status: import_joi101.default.string().valid(...assetUnitStatuses).required(),
23797
+ locationId: import_joi101.default.string().optional().allow("", null),
23798
+ assignedTo: import_joi101.default.string().optional().allow("", null)
23799
+ };
23800
+ var schemaAssetUnit = import_joi101.default.object(schemaAssetUnitBase);
23801
+ var schemaAssetUnitUpdate = import_joi101.default.object({
23802
+ serialNumber: import_joi101.default.string().optional().allow("", null),
23803
+ plateNumber: import_joi101.default.string().optional().allow("", null),
23804
+ status: import_joi101.default.string().valid(...assetUnitStatuses).optional(),
23805
+ locationId: import_joi101.default.string().optional().allow("", null),
23806
+ assignedTo: import_joi101.default.string().optional().allow("", null)
23807
+ });
23808
+ function modelAssetUnit(data) {
23809
+ const { error } = schemaAssetUnit.validate(data);
23810
+ if (error) {
23811
+ throw new Error(`Invalid asset unit data: ${error.message}`);
23812
+ }
23813
+ if (data.itemId && typeof data.itemId === "string") {
23814
+ try {
23815
+ data.itemId = new import_mongodb64.ObjectId(data.itemId);
23816
+ } catch {
23817
+ throw new Error(`Invalid itemId format: ${data.itemId}`);
23818
+ }
23819
+ }
23820
+ return {
23821
+ _id: data._id,
23822
+ itemId: data.itemId,
23823
+ serialNumber: data.serialNumber ?? "",
23824
+ plateNumber: data.plateNumber ?? "",
23825
+ status: data.status,
23826
+ locationId: data.locationId ?? "",
23827
+ assignedTo: data.assignedTo ?? "",
23828
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
23829
+ updatedAt: data.updatedAt ?? ""
23830
+ };
23831
+ }
23832
+
23833
+ // src/resources/asset-item-unit/asset.unit.repository.ts
23834
+ var import_utils122 = require("@goweekdays/utils");
23835
+ var import_mongodb65 = require("mongodb");
23836
+ function useAssetUnitRepo() {
23837
+ const namespace_collection = "asset.units";
23838
+ const repo = (0, import_utils122.useRepo)(namespace_collection);
23839
+ async function createIndexes() {
23840
+ try {
23841
+ await repo.collection.createIndexes([
23842
+ { key: { itemId: 1 } },
23843
+ { key: { status: 1 } },
23844
+ { key: { locationId: 1 } },
23845
+ { key: { itemId: 1, status: 1 } }
23846
+ ]);
23847
+ } catch (error) {
23848
+ throw new import_utils122.BadRequestError("Failed to create asset unit indexes.");
23849
+ }
23850
+ }
23851
+ async function add(value, session) {
23852
+ try {
23853
+ value = modelAssetUnit(value);
23854
+ const res = await repo.collection.insertOne(value, { session });
23855
+ repo.delCachedData();
23856
+ return res.insertedId;
23857
+ } catch (error) {
23858
+ import_utils122.logger.log({ level: "error", message: error.message });
23859
+ throw new import_utils122.InternalServerError("Failed to create asset unit.");
23860
+ }
23861
+ }
23862
+ async function getByItemId({
23863
+ itemId,
23864
+ status: status2 = "",
23865
+ page = 1,
23866
+ limit = 10
23867
+ }) {
23868
+ page = page > 0 ? page - 1 : 0;
23869
+ const query = {
23870
+ itemId: new import_mongodb65.ObjectId(itemId)
23871
+ };
23872
+ if (status2) {
23873
+ query.status = status2;
23874
+ }
23875
+ const cacheKey = (0, import_utils122.makeCacheKey)(namespace_collection, {
23876
+ itemId,
23877
+ status: status2,
23878
+ page,
23879
+ limit,
23880
+ tag: "byItemId"
23881
+ });
23882
+ try {
23883
+ const cached = await repo.getCache(cacheKey);
23884
+ if (cached)
23885
+ return cached;
23886
+ const items = await repo.collection.aggregate([
23887
+ { $match: query },
23888
+ { $sort: { _id: -1 } },
23889
+ { $skip: page * limit },
23890
+ { $limit: limit }
23891
+ ]).toArray();
23892
+ const length = await repo.collection.countDocuments(query);
23893
+ const data = (0, import_utils122.paginate)(items, page, limit, length);
23894
+ repo.setCache(cacheKey, data, 600);
23895
+ return data;
23896
+ } catch (error) {
23897
+ import_utils122.logger.log({ level: "error", message: `${error}` });
23898
+ throw error;
23899
+ }
23900
+ }
23901
+ async function getById(id) {
23902
+ const cacheKey = (0, import_utils122.makeCacheKey)(namespace_collection, {
23903
+ id,
23904
+ tag: "by-id"
23905
+ });
23906
+ try {
23907
+ const cached = await repo.getCache(cacheKey);
23908
+ if (cached)
23909
+ return cached;
23910
+ const result = await repo.collection.findOne({
23911
+ _id: new import_mongodb65.ObjectId(id)
23912
+ });
23913
+ if (!result) {
23914
+ throw new import_utils122.BadRequestError("Asset unit not found.");
23915
+ }
23916
+ repo.setCache(cacheKey, result, 300);
23917
+ return result;
23918
+ } catch (error) {
23919
+ if (error instanceof import_utils122.AppError)
23920
+ throw error;
23921
+ throw new import_utils122.InternalServerError("Failed to get asset unit.");
23922
+ }
23923
+ }
23924
+ async function updateById(id, value, session) {
23925
+ try {
23926
+ const result = await repo.collection.findOneAndUpdate(
23927
+ { _id: new import_mongodb65.ObjectId(id) },
23928
+ { $set: { ...value, updatedAt: /* @__PURE__ */ new Date() } },
23929
+ { returnDocument: "after", session }
23930
+ );
23931
+ if (!result) {
23932
+ throw new import_utils122.BadRequestError("Asset unit not found.");
23933
+ }
23934
+ repo.delCachedData();
23935
+ return result;
23936
+ } catch (error) {
23937
+ if (error instanceof import_utils122.AppError)
23938
+ throw error;
23939
+ throw new import_utils122.InternalServerError("Failed to update asset unit.");
23940
+ }
23941
+ }
23942
+ async function countByItemIdAndStatus(itemId, status2) {
23943
+ try {
23944
+ return await repo.collection.countDocuments({
23945
+ itemId: new import_mongodb65.ObjectId(itemId),
23946
+ status: status2
23947
+ });
23948
+ } catch (error) {
23949
+ throw new import_utils122.InternalServerError("Failed to count asset units.");
23950
+ }
23951
+ }
23952
+ return {
23953
+ createIndexes,
23954
+ add,
23955
+ getByItemId,
23956
+ getById,
23957
+ updateById,
23958
+ countByItemIdAndStatus
23959
+ };
23960
+ }
23961
+
23962
+ // src/resources/asset-item-unit/asset.unit.controller.ts
23963
+ var import_joi102 = __toESM(require("joi"));
23964
+ var import_utils123 = require("@goweekdays/utils");
23965
+ var querySchema = import_joi102.default.object({
23966
+ itemId: import_joi102.default.string().hex().length(24).required(),
23967
+ status: import_joi102.default.string().valid(...assetUnitStatuses).optional().allow("", null),
23968
+ page: import_joi102.default.number().min(1).optional().allow("", null),
23969
+ limit: import_joi102.default.number().min(1).optional().allow("", null)
23970
+ });
23971
+ var idParamSchema3 = import_joi102.default.object({
23972
+ id: import_joi102.default.string().hex().length(24).required()
23973
+ });
23974
+ function useAssetUnitController() {
23975
+ const repo = useAssetUnitRepo();
23976
+ async function add(req, res, next) {
23977
+ try {
23978
+ const { error } = schemaAssetUnit.validate(req.body);
23979
+ if (error) {
23980
+ next(new import_utils123.BadRequestError(error.message));
23981
+ return;
23982
+ }
23983
+ const unitId = await repo.add(req.body);
23984
+ res.status(201).json({
23985
+ message: "Asset unit created successfully.",
23986
+ unitId
23987
+ });
23988
+ } catch (error) {
23989
+ next(error);
23990
+ }
23991
+ }
23992
+ async function getByItemId(req, res, next) {
23993
+ const { error } = querySchema.validate(req.query);
23994
+ if (error) {
23995
+ next(new import_utils123.BadRequestError(error.message));
23996
+ return;
23997
+ }
23998
+ const itemId = req.query.itemId;
23999
+ const status2 = req.query.status ?? "";
24000
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
24001
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
24002
+ try {
24003
+ const results = await repo.getByItemId({ itemId, status: status2, page, limit });
24004
+ res.json(results);
24005
+ } catch (error2) {
24006
+ next(error2);
24007
+ }
24008
+ }
24009
+ async function getById(req, res, next) {
24010
+ const { error } = idParamSchema3.validate(req.params);
24011
+ if (error) {
24012
+ next(new import_utils123.BadRequestError(error.message));
24013
+ return;
24014
+ }
24015
+ try {
24016
+ const unit = await repo.getById(req.params.id);
24017
+ res.json(unit);
24018
+ } catch (error2) {
24019
+ next(error2);
24020
+ }
24021
+ }
24022
+ async function updateById(req, res, next) {
24023
+ const { error: paramError } = idParamSchema3.validate(req.params);
24024
+ if (paramError) {
24025
+ next(new import_utils123.BadRequestError(paramError.message));
24026
+ return;
24027
+ }
24028
+ const { error: bodyError } = schemaAssetUnitUpdate.validate(req.body);
24029
+ if (bodyError) {
24030
+ next(new import_utils123.BadRequestError(bodyError.message));
24031
+ return;
24032
+ }
24033
+ try {
24034
+ const updatedUnit = await repo.updateById(req.params.id, req.body);
24035
+ res.json({
24036
+ message: "Asset unit updated successfully.",
24037
+ unit: updatedUnit
24038
+ });
24039
+ } catch (error) {
24040
+ next(error);
24041
+ }
24042
+ }
24043
+ return {
24044
+ add,
24045
+ getByItemId,
24046
+ getById,
24047
+ updateById
24048
+ };
24049
+ }
24050
+
24051
+ // src/resources/asset-item-movement/stock.movement.model.ts
24052
+ var import_joi103 = __toESM(require("joi"));
24053
+ var import_mongodb66 = require("mongodb");
24054
+ var stockMovementTypes = [
24055
+ "in",
24056
+ "out",
24057
+ "transfer",
24058
+ "adjustment",
24059
+ "conversion"
24060
+ ];
24061
+ var stockMovementReferenceTypes = [
24062
+ "purchase",
24063
+ "sale",
24064
+ "transfer",
24065
+ "manual",
24066
+ "conversion"
24067
+ ];
24068
+ var schemaStockMovement = import_joi103.default.object({
24069
+ itemId: import_joi103.default.string().hex().length(24).required(),
24070
+ type: import_joi103.default.string().valid(...stockMovementTypes).required(),
24071
+ quantity: import_joi103.default.number().positive().required(),
24072
+ unitCost: import_joi103.default.number().min(0).optional().allow(null),
24073
+ totalCost: import_joi103.default.number().min(0).optional().allow(null),
24074
+ reference: import_joi103.default.object({
24075
+ type: import_joi103.default.string().valid(...stockMovementReferenceTypes).required(),
24076
+ id: import_joi103.default.string().optional().allow("", null)
24077
+ }).optional().allow(null),
24078
+ fromLocationId: import_joi103.default.string().optional().allow("", null),
24079
+ toLocationId: import_joi103.default.string().optional().allow("", null),
24080
+ fromItemId: import_joi103.default.string().hex().length(24).optional().allow("", null),
24081
+ toItemId: import_joi103.default.string().hex().length(24).optional().allow("", null),
24082
+ reason: import_joi103.default.string().optional().allow("", null)
24083
+ });
24084
+ function modelStockMovement(data) {
24085
+ const { error } = schemaStockMovement.validate(data);
24086
+ if (error) {
24087
+ throw new Error(`Invalid stock movement data: ${error.message}`);
24088
+ }
24089
+ if (data.itemId && typeof data.itemId === "string") {
24090
+ try {
24091
+ data.itemId = new import_mongodb66.ObjectId(data.itemId);
24092
+ } catch {
24093
+ throw new Error(`Invalid itemId format: ${data.itemId}`);
24094
+ }
24095
+ }
24096
+ return {
24097
+ _id: data._id,
24098
+ itemId: data.itemId,
24099
+ type: data.type,
24100
+ quantity: data.quantity,
24101
+ unitCost: data.unitCost ?? 0,
24102
+ totalCost: data.totalCost ?? 0,
24103
+ reference: data.reference ?? void 0,
24104
+ fromLocationId: data.fromLocationId ?? "",
24105
+ toLocationId: data.toLocationId ?? "",
24106
+ fromItemId: data.fromItemId ?? "",
24107
+ toItemId: data.toItemId ?? "",
24108
+ reason: data.reason ?? "",
24109
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date()
24110
+ };
24111
+ }
24112
+
24113
+ // src/resources/asset-item-movement/stock.movement.repository.ts
24114
+ var import_utils124 = require("@goweekdays/utils");
24115
+ var import_mongodb67 = require("mongodb");
24116
+ function useStockMovementRepo() {
24117
+ const namespace_collection = "stock.movements";
24118
+ const repo = (0, import_utils124.useRepo)(namespace_collection);
24119
+ async function createIndexes() {
24120
+ try {
24121
+ await repo.collection.createIndexes([
24122
+ { key: { itemId: 1 } },
24123
+ { key: { createdAt: -1 } },
24124
+ { key: { itemId: 1, createdAt: -1 } },
24125
+ { key: { type: 1 } }
24126
+ ]);
24127
+ } catch (error) {
24128
+ throw new import_utils124.BadRequestError("Failed to create stock movement indexes.");
24129
+ }
24130
+ }
24131
+ async function add(value, session) {
24132
+ try {
24133
+ value = modelStockMovement(value);
24134
+ const res = await repo.collection.insertOne(value, { session });
24135
+ repo.delCachedData();
24136
+ return res.insertedId;
24137
+ } catch (error) {
24138
+ import_utils124.logger.log({ level: "error", message: error.message });
24139
+ throw new import_utils124.InternalServerError("Failed to create stock movement.");
24140
+ }
24141
+ }
24142
+ async function getByItemId({
24143
+ itemId,
24144
+ type = "",
24145
+ page = 1,
24146
+ limit = 10
24147
+ }) {
24148
+ page = page > 0 ? page - 1 : 0;
24149
+ const query = {
24150
+ itemId: new import_mongodb67.ObjectId(itemId)
24151
+ };
24152
+ if (type) {
24153
+ query.type = type;
24154
+ }
24155
+ const cacheKey = (0, import_utils124.makeCacheKey)(namespace_collection, {
24156
+ itemId,
24157
+ type,
24158
+ page,
24159
+ limit,
24160
+ tag: "byItemId"
24161
+ });
24162
+ try {
24163
+ const cached = await repo.getCache(cacheKey);
24164
+ if (cached)
24165
+ return cached;
24166
+ const items = await repo.collection.aggregate([
24167
+ { $match: query },
24168
+ { $sort: { createdAt: -1 } },
24169
+ { $skip: page * limit },
24170
+ { $limit: limit }
24171
+ ]).toArray();
24172
+ const length = await repo.collection.countDocuments(query);
24173
+ const data = (0, import_utils124.paginate)(items, page, limit, length);
24174
+ repo.setCache(cacheKey, data, 600);
24175
+ return data;
24176
+ } catch (error) {
24177
+ import_utils124.logger.log({ level: "error", message: `${error}` });
24178
+ throw error;
24179
+ }
24180
+ }
24181
+ async function getById(id) {
24182
+ const cacheKey = (0, import_utils124.makeCacheKey)(namespace_collection, {
24183
+ id,
24184
+ tag: "by-id"
24185
+ });
24186
+ try {
24187
+ const cached = await repo.getCache(cacheKey);
24188
+ if (cached)
24189
+ return cached;
24190
+ const result = await repo.collection.findOne({
24191
+ _id: new import_mongodb67.ObjectId(id)
24192
+ });
24193
+ if (!result) {
24194
+ throw new import_utils124.BadRequestError("Stock movement not found.");
24195
+ }
24196
+ repo.setCache(cacheKey, result, 300);
24197
+ return result;
24198
+ } catch (error) {
24199
+ if (error instanceof import_utils124.AppError)
24200
+ throw error;
24201
+ throw new import_utils124.InternalServerError("Failed to get stock movement.");
24202
+ }
24203
+ }
24204
+ return {
24205
+ createIndexes,
24206
+ add,
24207
+ getByItemId,
24208
+ getById
24209
+ };
24210
+ }
24211
+
24212
+ // src/resources/asset-item-movement/stock.movement.service.ts
24213
+ var import_utils125 = require("@goweekdays/utils");
24214
+ var import_utils126 = require("@goweekdays/utils");
24215
+ function useStockMovementService() {
24216
+ const movementRepo = useStockMovementRepo();
24217
+ const assetItemRepo = useAssetItemRepo();
24218
+ const assetUnitRepo = useAssetUnitRepo();
24219
+ async function stockIn(value, units) {
24220
+ const item = await assetItemRepo.getById(String(value.itemId));
24221
+ const session = import_utils126.useAtlas.getClient()?.startSession();
24222
+ if (!session)
24223
+ throw new import_utils125.BadRequestError("Failed to start session.");
24224
+ try {
24225
+ session.startTransaction();
24226
+ value.type = "in";
24227
+ const movementId = await movementRepo.add(value, session);
24228
+ if (item.trackingType === "individual" && units && units.length > 0) {
24229
+ for (const unit of units) {
24230
+ await assetUnitRepo.add(
24231
+ {
24232
+ itemId: value.itemId,
24233
+ serialNumber: unit.serialNumber,
24234
+ plateNumber: unit.plateNumber,
24235
+ status: "available",
24236
+ locationId: value.toLocationId
24237
+ },
24238
+ session
24239
+ );
24240
+ }
24241
+ }
24242
+ await assetItemRepo.incrementQuantity(
24243
+ String(value.itemId),
24244
+ value.quantity,
24245
+ session
24246
+ );
24247
+ await session.commitTransaction();
24248
+ return movementId;
24249
+ } catch (error) {
24250
+ await session.abortTransaction();
24251
+ throw error;
24252
+ } finally {
24253
+ await session.endSession();
24254
+ }
24255
+ }
24256
+ async function stockOut(value, unitIds) {
24257
+ const item = await assetItemRepo.getById(String(value.itemId));
24258
+ if (item.trackingType === "quantity" && (item.quantityOnHand ?? 0) < value.quantity) {
24259
+ throw new import_utils125.BadRequestError("Insufficient stock.");
24260
+ }
24261
+ const session = import_utils126.useAtlas.getClient()?.startSession();
24262
+ if (!session)
24263
+ throw new import_utils125.BadRequestError("Failed to start session.");
24264
+ try {
24265
+ session.startTransaction();
24266
+ value.type = "out";
24267
+ const movementId = await movementRepo.add(value, session);
24268
+ if (item.trackingType === "individual" && unitIds && unitIds.length > 0) {
24269
+ for (const unitId of unitIds) {
24270
+ await assetUnitRepo.updateById(
24271
+ unitId,
24272
+ { status: "assigned", assignedTo: value.reference?.id },
24273
+ session
24274
+ );
24275
+ }
24276
+ }
24277
+ await assetItemRepo.incrementQuantity(
24278
+ String(value.itemId),
24279
+ -value.quantity,
24280
+ session
24281
+ );
24282
+ await session.commitTransaction();
24283
+ return movementId;
24284
+ } catch (error) {
24285
+ await session.abortTransaction();
24286
+ throw error;
24287
+ } finally {
24288
+ await session.endSession();
24289
+ }
24290
+ }
24291
+ async function transfer(value, unitIds) {
24292
+ if (!value.fromLocationId || !value.toLocationId) {
24293
+ throw new import_utils125.BadRequestError(
24294
+ "Transfer requires both fromLocationId and toLocationId."
24295
+ );
24296
+ }
24297
+ const item = await assetItemRepo.getById(String(value.itemId));
24298
+ const session = import_utils126.useAtlas.getClient()?.startSession();
24299
+ if (!session)
24300
+ throw new import_utils125.BadRequestError("Failed to start session.");
24301
+ try {
24302
+ session.startTransaction();
24303
+ value.type = "transfer";
24304
+ const movementId = await movementRepo.add(value, session);
24305
+ if (item.trackingType === "individual" && unitIds && unitIds.length > 0) {
24306
+ for (const unitId of unitIds) {
24307
+ await assetUnitRepo.updateById(
24308
+ unitId,
24309
+ { locationId: value.toLocationId },
24310
+ session
24311
+ );
24312
+ }
24313
+ }
24314
+ await session.commitTransaction();
24315
+ return movementId;
24316
+ } catch (error) {
24317
+ await session.abortTransaction();
24318
+ throw error;
24319
+ } finally {
24320
+ await session.endSession();
24321
+ }
24322
+ }
24323
+ async function adjustment(value) {
24324
+ if (!value.reason) {
24325
+ throw new import_utils125.BadRequestError("Adjustment requires a reason.");
24326
+ }
24327
+ const session = import_utils126.useAtlas.getClient()?.startSession();
24328
+ if (!session)
24329
+ throw new import_utils125.BadRequestError("Failed to start session.");
24330
+ try {
24331
+ session.startTransaction();
24332
+ value.type = "adjustment";
24333
+ const movementId = await movementRepo.add(value, session);
24334
+ await assetItemRepo.incrementQuantity(
24335
+ String(value.itemId),
24336
+ value.quantity,
24337
+ session
24338
+ );
24339
+ await session.commitTransaction();
24340
+ return movementId;
24341
+ } catch (error) {
24342
+ await session.abortTransaction();
24343
+ throw error;
24344
+ } finally {
24345
+ await session.endSession();
24346
+ }
24347
+ }
24348
+ async function conversion(newItem, quantity, unitIds) {
24349
+ const sourceItem = await assetItemRepo.getById(String(newItem.itemRefId));
24350
+ if (!newItem.itemRefId) {
24351
+ throw new import_utils125.BadRequestError("Conversion requires itemRefId.");
24352
+ }
24353
+ if (sourceItem.trackingType === "quantity" && (sourceItem.quantityOnHand ?? 0) < quantity) {
24354
+ throw new import_utils125.BadRequestError("Insufficient stock on source item.");
24355
+ }
24356
+ const session = import_utils126.useAtlas.getClient()?.startSession();
24357
+ if (!session)
24358
+ throw new import_utils125.BadRequestError("Failed to start session.");
24359
+ try {
24360
+ session.startTransaction();
24361
+ const newItemId = await assetItemRepo.add(newItem, session);
24362
+ const movementData = {
24363
+ itemId: newItem.itemRefId,
24364
+ type: "conversion",
24365
+ quantity,
24366
+ fromItemId: String(newItem.itemRefId),
24367
+ toItemId: String(newItemId),
24368
+ reference: { type: "conversion", id: String(newItemId) }
24369
+ };
24370
+ const movementId = await movementRepo.add(movementData, session);
24371
+ await assetItemRepo.incrementQuantity(
24372
+ String(newItem.itemRefId),
24373
+ -quantity,
24374
+ session
24375
+ );
24376
+ await assetItemRepo.incrementQuantity(
24377
+ String(newItemId),
24378
+ quantity,
24379
+ session
24380
+ );
24381
+ if (sourceItem.trackingType === "individual" && unitIds && unitIds.length > 0) {
24382
+ for (const unitId of unitIds) {
24383
+ await assetUnitRepo.updateById(
24384
+ unitId,
24385
+ { itemId: String(newItemId) },
24386
+ session
24387
+ );
24388
+ }
24389
+ }
24390
+ await session.commitTransaction();
24391
+ return { movementId, newItemId };
24392
+ } catch (error) {
24393
+ await session.abortTransaction();
24394
+ throw error;
24395
+ } finally {
24396
+ await session.endSession();
24397
+ }
24398
+ }
24399
+ return {
24400
+ stockIn,
24401
+ stockOut,
24402
+ transfer,
24403
+ adjustment,
24404
+ conversion
24405
+ };
24406
+ }
24407
+
24408
+ // src/resources/asset-item-movement/stock.movement.controller.ts
24409
+ var import_joi104 = __toESM(require("joi"));
24410
+ var import_utils127 = require("@goweekdays/utils");
24411
+ var querySchema2 = import_joi104.default.object({
24412
+ itemId: import_joi104.default.string().hex().length(24).required(),
24413
+ type: import_joi104.default.string().valid(...stockMovementTypes).optional().allow("", null),
24414
+ page: import_joi104.default.number().min(1).optional().allow("", null),
24415
+ limit: import_joi104.default.number().min(1).optional().allow("", null)
24416
+ });
24417
+ var idParamSchema4 = import_joi104.default.object({
24418
+ id: import_joi104.default.string().hex().length(24).required()
24419
+ });
24420
+ var stockInSchema = import_joi104.default.object({
24421
+ itemId: import_joi104.default.string().hex().length(24).required(),
24422
+ quantity: import_joi104.default.number().positive().required(),
24423
+ unitCost: import_joi104.default.number().min(0).optional().allow(null),
24424
+ totalCost: import_joi104.default.number().min(0).optional().allow(null),
24425
+ reference: import_joi104.default.object({
24426
+ type: import_joi104.default.string().valid("purchase", "sale", "transfer", "manual", "conversion").required(),
24427
+ id: import_joi104.default.string().optional().allow("", null)
24428
+ }).optional().allow(null),
24429
+ toLocationId: import_joi104.default.string().optional().allow("", null),
24430
+ units: import_joi104.default.array().items(
24431
+ import_joi104.default.object({
24432
+ serialNumber: import_joi104.default.string().optional().allow("", null),
24433
+ plateNumber: import_joi104.default.string().optional().allow("", null)
24434
+ })
24435
+ ).optional()
24436
+ });
24437
+ var stockOutSchema = import_joi104.default.object({
24438
+ itemId: import_joi104.default.string().hex().length(24).required(),
24439
+ quantity: import_joi104.default.number().positive().required(),
24440
+ unitCost: import_joi104.default.number().min(0).optional().allow(null),
24441
+ totalCost: import_joi104.default.number().min(0).optional().allow(null),
24442
+ reference: import_joi104.default.object({
24443
+ type: import_joi104.default.string().valid("purchase", "sale", "transfer", "manual", "conversion").required(),
24444
+ id: import_joi104.default.string().optional().allow("", null)
24445
+ }).optional().allow(null),
24446
+ fromLocationId: import_joi104.default.string().optional().allow("", null),
24447
+ unitIds: import_joi104.default.array().items(import_joi104.default.string().hex().length(24)).optional()
24448
+ });
24449
+ var transferSchema = import_joi104.default.object({
24450
+ itemId: import_joi104.default.string().hex().length(24).required(),
24451
+ quantity: import_joi104.default.number().positive().required(),
24452
+ fromLocationId: import_joi104.default.string().required(),
24453
+ toLocationId: import_joi104.default.string().required(),
24454
+ reference: import_joi104.default.object({
24455
+ type: import_joi104.default.string().valid("purchase", "sale", "transfer", "manual", "conversion").required(),
24456
+ id: import_joi104.default.string().optional().allow("", null)
24457
+ }).optional().allow(null),
24458
+ unitIds: import_joi104.default.array().items(import_joi104.default.string().hex().length(24)).optional()
24459
+ });
24460
+ var adjustmentSchema = import_joi104.default.object({
24461
+ itemId: import_joi104.default.string().hex().length(24).required(),
24462
+ quantity: import_joi104.default.number().required(),
24463
+ reason: import_joi104.default.string().required(),
24464
+ reference: import_joi104.default.object({
24465
+ type: import_joi104.default.string().valid("purchase", "sale", "transfer", "manual", "conversion").required(),
24466
+ id: import_joi104.default.string().optional().allow("", null)
24467
+ }).optional().allow(null)
24468
+ });
24469
+ var conversionSchema = import_joi104.default.object({
24470
+ quantity: import_joi104.default.number().positive().required(),
24471
+ newItem: import_joi104.default.object({
24472
+ name: import_joi104.default.string().required(),
24473
+ description: import_joi104.default.string().optional().allow("", null),
24474
+ assetCategory: import_joi104.default.string().valid(...assetItemCategories).required(),
24475
+ assetClass: import_joi104.default.string().valid(...assetItemClasses).required(),
24476
+ trackingType: import_joi104.default.string().valid(...assetItemTrackingTypes).required(),
24477
+ purpose: import_joi104.default.string().valid(...assetItemPurposes).required(),
24478
+ itemRefId: import_joi104.default.string().hex().length(24).required(),
24479
+ remarks: import_joi104.default.string().optional().allow("", null),
24480
+ departmentId: import_joi104.default.string().required(),
24481
+ departmentName: import_joi104.default.string().required(),
24482
+ categoryId: import_joi104.default.string().required(),
24483
+ categoryName: import_joi104.default.string().required(),
24484
+ subcategoryId: import_joi104.default.string().required(),
24485
+ subcategoryName: import_joi104.default.string().required(),
24486
+ categoryPath: import_joi104.default.string().required(),
24487
+ brand: import_joi104.default.string().optional().allow("", null),
24488
+ tags: import_joi104.default.array().items(
24489
+ import_joi104.default.object({
24490
+ id: import_joi104.default.string().required(),
24491
+ name: import_joi104.default.string().required()
24492
+ })
24493
+ ).optional().default([])
24494
+ }).required(),
24495
+ unitIds: import_joi104.default.array().items(import_joi104.default.string().hex().length(24)).optional()
24496
+ });
24497
+ function useStockMovementController() {
24498
+ const repo = useStockMovementRepo();
24499
+ const service = useStockMovementService();
24500
+ async function stockIn(req, res, next) {
24501
+ try {
24502
+ const { error } = stockInSchema.validate(req.body);
24503
+ if (error) {
24504
+ next(new import_utils127.BadRequestError(error.message));
24505
+ return;
24506
+ }
24507
+ const { units, ...movement } = req.body;
24508
+ const movementId = await service.stockIn(movement, units);
24509
+ res.status(201).json({
24510
+ message: "Stock in recorded successfully.",
24511
+ movementId
24512
+ });
24513
+ } catch (error) {
24514
+ next(error);
24515
+ }
24516
+ }
24517
+ async function stockOut(req, res, next) {
24518
+ try {
24519
+ const { error } = stockOutSchema.validate(req.body);
24520
+ if (error) {
24521
+ next(new import_utils127.BadRequestError(error.message));
24522
+ return;
24523
+ }
24524
+ const { unitIds, ...movement } = req.body;
24525
+ const movementId = await service.stockOut(movement, unitIds);
24526
+ res.status(201).json({
24527
+ message: "Stock out recorded successfully.",
24528
+ movementId
24529
+ });
24530
+ } catch (error) {
24531
+ next(error);
24532
+ }
24533
+ }
24534
+ async function transfer(req, res, next) {
24535
+ try {
24536
+ const { error } = transferSchema.validate(req.body);
24537
+ if (error) {
24538
+ next(new import_utils127.BadRequestError(error.message));
24539
+ return;
24540
+ }
24541
+ const { unitIds, ...movement } = req.body;
24542
+ const movementId = await service.transfer(movement, unitIds);
24543
+ res.status(201).json({
24544
+ message: "Transfer recorded successfully.",
24545
+ movementId
24546
+ });
24547
+ } catch (error) {
24548
+ next(error);
24549
+ }
24550
+ }
24551
+ async function adjustment(req, res, next) {
24552
+ try {
24553
+ const { error } = adjustmentSchema.validate(req.body);
24554
+ if (error) {
24555
+ next(new import_utils127.BadRequestError(error.message));
24556
+ return;
24557
+ }
24558
+ const movementId = await service.adjustment(req.body);
24559
+ res.status(201).json({
24560
+ message: "Adjustment recorded successfully.",
24561
+ movementId
24562
+ });
24563
+ } catch (error) {
24564
+ next(error);
24565
+ }
24566
+ }
24567
+ async function getByItemId(req, res, next) {
24568
+ const { error } = querySchema2.validate(req.query);
24569
+ if (error) {
24570
+ next(new import_utils127.BadRequestError(error.message));
24571
+ return;
24572
+ }
24573
+ const itemId = req.query.itemId;
24574
+ const type = req.query.type ?? "";
24575
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
24576
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
24577
+ try {
24578
+ const results = await repo.getByItemId({ itemId, type, page, limit });
24579
+ res.json(results);
24580
+ } catch (error2) {
24581
+ next(error2);
24582
+ }
24583
+ }
24584
+ async function getById(req, res, next) {
24585
+ const { error } = idParamSchema4.validate(req.params);
24586
+ if (error) {
24587
+ next(new import_utils127.BadRequestError(error.message));
24588
+ return;
24589
+ }
24590
+ try {
24591
+ const movement = await repo.getById(req.params.id);
24592
+ res.json(movement);
24593
+ } catch (error2) {
24594
+ next(error2);
24595
+ }
24596
+ }
24597
+ async function conversion(req, res, next) {
24598
+ try {
24599
+ const { error } = conversionSchema.validate(req.body);
24600
+ if (error) {
24601
+ next(new import_utils127.BadRequestError(error.message));
24602
+ return;
24603
+ }
24604
+ const { quantity, newItem, unitIds } = req.body;
24605
+ const result = await service.conversion(newItem, quantity, unitIds);
24606
+ res.status(201).json({
24607
+ message: "Conversion recorded successfully.",
24608
+ movementId: result.movementId,
24609
+ newItemId: result.newItemId
24610
+ });
24611
+ } catch (error) {
24612
+ next(error);
24613
+ }
24614
+ }
24615
+ return {
24616
+ stockIn,
24617
+ stockOut,
24618
+ transfer,
24619
+ adjustment,
24620
+ conversion,
24621
+ getByItemId,
24622
+ getById
24623
+ };
24624
+ }
24625
+
24626
+ // src/resources/asset-item-category/category.service.ts
24627
+ var import_utils128 = require("@goweekdays/utils");
24628
+ function useCategoryService() {
24629
+ const repo = useCategoryRepo();
24630
+ async function add(value) {
24631
+ const { error } = schemaCategoryNodeCreate.validate(value);
24632
+ if (error) {
24633
+ throw new import_utils128.BadRequestError(error.message);
24634
+ }
24635
+ const name = normalizeName(value.displayName);
24636
+ let level;
24637
+ let path = "";
24638
+ if (!value.parentId) {
24639
+ level = "department";
24640
+ const existing = await repo.getByNameParent(name, { orgId: value.orgId });
24641
+ if (existing) {
24642
+ throw new import_utils128.BadRequestError(
24643
+ `Department "${value.displayName}" already exists.`
24644
+ );
24645
+ }
24646
+ } else {
24647
+ const parent = await repo.getById(value.parentId);
24648
+ if (parent && parent.level === "department") {
24649
+ level = "category";
24650
+ } else if (parent && parent.level === "category") {
24651
+ level = "subcategory";
24652
+ } else {
24653
+ throw new import_utils128.BadRequestError(
24654
+ "Cannot create a child under a subcategory. Maximum depth is 3 levels."
24655
+ );
24656
+ }
24657
+ const existing = await repo.getByNameParent(name, {
24658
+ parentId: value.parentId,
24659
+ orgId: value.orgId
24660
+ });
24661
+ if (existing) {
24662
+ throw new import_utils128.BadRequestError(
24663
+ `"${value.displayName}" already exists under this parent.`
24664
+ );
24665
+ }
24666
+ if (level === "subcategory") {
24667
+ const department = await repo.getById(parent.parentId);
24668
+ path = buildCategoryPath(department.name, parent.name, name);
24669
+ }
24670
+ }
24671
+ const data = {
24672
+ orgId: value.orgId ?? "",
24673
+ level,
24674
+ type: value.orgId ? "private" : "public",
24675
+ name,
24676
+ displayName: value.displayName.trim(),
24677
+ parentId: value.parentId ?? "",
24678
+ path
24679
+ };
24680
+ return await repo.add(data);
24681
+ }
24682
+ async function updateById(_id, value) {
24683
+ const { error } = schemaCategoryNodeUpdate.validate(value);
24684
+ if (error) {
24685
+ throw new import_utils128.BadRequestError(error.message);
24686
+ }
24687
+ const update = {
24688
+ displayName: "",
24689
+ name: "",
24690
+ updatedAt: /* @__PURE__ */ new Date()
24691
+ };
24692
+ if (value.displayName) {
24693
+ update.displayName = value.displayName.trim();
24694
+ update.name = normalizeName(value.displayName);
24695
+ }
24696
+ await repo.updateById(_id, update);
24697
+ }
24698
+ async function deleteById(_id) {
24699
+ const hasChildren = await repo.hasChildren(_id);
24700
+ if (hasChildren) {
24701
+ throw new import_utils128.BadRequestError(
24702
+ "Cannot delete a category that has child categories. Remove or reassign children first."
24703
+ );
24704
+ }
24705
+ await repo.softDeleteById(_id);
24706
+ }
24707
+ return {
24708
+ add,
24709
+ updateById,
24710
+ deleteById
24711
+ };
24712
+ }
24713
+
24714
+ // src/resources/asset-item-category/category.controller.ts
24715
+ var import_joi105 = __toESM(require("joi"));
24716
+ var import_utils129 = require("@goweekdays/utils");
24717
+ function useCategoryController() {
24718
+ const repo = useCategoryRepo();
24719
+ const service = useCategoryService();
24720
+ async function add(req, res, next) {
24721
+ const orgId = req.params.org ?? "";
24722
+ const { error, value } = schemaCategoryNodeCreate.validate({
24723
+ ...req.body,
24724
+ orgId
24725
+ });
24726
+ if (error) {
24727
+ next(new import_utils129.BadRequestError(error.message));
24728
+ return;
24729
+ }
24730
+ try {
24731
+ const id = await service.add(value);
24732
+ res.status(201).json({ message: "Category created successfully.", id });
24733
+ } catch (error2) {
24734
+ next(error2);
24735
+ }
24736
+ }
24737
+ async function getAll(req, res, next) {
24738
+ const org = req.params.org ?? "";
24739
+ const { error, value } = schemaCategoryGetAll.validate({
24740
+ ...req.query,
24741
+ org
24742
+ });
24743
+ if (error) {
24744
+ next(new import_utils129.BadRequestError(error.message));
24745
+ return;
24746
+ }
24747
+ try {
24748
+ const data = await repo.getAll(value);
24749
+ res.json(data);
24750
+ } catch (error2) {
24751
+ next(error2);
24752
+ }
24753
+ }
24754
+ async function getById(req, res, next) {
24755
+ const _id = req.params.id;
24756
+ const { error: errorId } = import_joi105.default.string().hex().length(24).required().validate(_id);
24757
+ if (errorId) {
24758
+ next(new import_utils129.BadRequestError("Invalid category ID."));
24759
+ return;
24760
+ }
24761
+ try {
24762
+ const category = await repo.getById(_id);
24763
+ if (!category) {
24764
+ next(new import_utils129.BadRequestError("Category not found."));
24765
+ return;
24766
+ }
24767
+ res.json(category);
24768
+ } catch (error) {
24769
+ next(error);
24770
+ }
24771
+ }
24772
+ async function updateById(req, res, next) {
24773
+ const _id = req.params.id;
24774
+ const { error: errorId } = import_joi105.default.string().hex().length(24).required().validate(_id);
24775
+ if (errorId) {
24776
+ next(new import_utils129.BadRequestError("Invalid category ID."));
24777
+ return;
24778
+ }
24779
+ const { error, value } = schemaCategoryNodeUpdate.validate(req.body);
24780
+ if (error) {
24781
+ next(new import_utils129.BadRequestError(error.message));
24782
+ return;
24783
+ }
24784
+ const { displayName } = value;
24785
+ try {
24786
+ await service.updateById(_id, { displayName });
24787
+ res.json({ message: "Category updated successfully." });
24788
+ } catch (error2) {
24789
+ next(error2);
24790
+ }
24791
+ }
24792
+ async function deleteById(req, res, next) {
24793
+ const _id = req.params.id;
24794
+ const { error: errorId } = import_joi105.default.string().hex().length(24).required().validate(_id);
24795
+ if (errorId) {
24796
+ next(new import_utils129.BadRequestError("Invalid category ID."));
24797
+ return;
24798
+ }
24799
+ try {
24800
+ await service.deleteById(_id);
24801
+ res.json({ message: "Category deleted successfully." });
24802
+ } catch (error) {
24803
+ next(error);
24804
+ }
24805
+ }
24806
+ return {
24807
+ add,
24808
+ getAll,
24809
+ getById,
24810
+ updateById,
24811
+ deleteById
24812
+ };
24813
+ }
24814
+
24815
+ // src/resources/tag/tag.service.ts
24816
+ var import_utils130 = require("@goweekdays/utils");
24817
+ var import_joi106 = __toESM(require("joi"));
24818
+ function normalizeTagName2(name) {
24819
+ return name.trim().toLowerCase().replace(/\s+/g, "-");
24820
+ }
24821
+ function useTagService() {
24822
+ const {
24823
+ add: _add,
24824
+ getById: _getById,
24825
+ updateById: _updateById,
24826
+ deleteById: _deleteById
24827
+ } = useTagRepo();
24828
+ async function add(value) {
24829
+ const { error } = schemaTagCreate.validate(value);
24830
+ if (error) {
24831
+ throw new import_utils130.BadRequestError(error.message);
24832
+ }
24833
+ const normalizedName = normalizeTagName2(value.name);
24834
+ const data = {
24835
+ name: value.name,
24836
+ normalizedName,
24837
+ type: value.type,
24838
+ orgId: value.orgId ?? "",
24839
+ categoryPath: value.categoryPath ?? "",
24840
+ status: "pending",
24841
+ usageCount: 0
24842
+ };
24843
+ try {
24844
+ const id = await _add(data);
24845
+ return id;
24846
+ } catch (error2) {
24847
+ if (error2 instanceof import_utils130.AppError)
24848
+ throw error2;
24849
+ throw new import_utils130.BadRequestError("Failed to add tag.");
24850
+ }
24851
+ }
24852
+ async function updateById(id, value) {
24853
+ const { error: errorId } = import_joi106.default.string().hex().required().validate(id);
24854
+ if (errorId) {
24855
+ throw new import_utils130.BadRequestError("Invalid Tag ID.");
24856
+ }
24857
+ const { error } = schemaTagUpdate.validate(value);
24858
+ if (error) {
24859
+ throw new import_utils130.BadRequestError(`Invalid tag update data: ${error.message}`);
24860
+ }
24861
+ const existingTag = await _getById(id);
24862
+ if (!existingTag) {
24863
+ throw new import_utils130.BadRequestError("Tag not found.");
24864
+ }
24865
+ const updatedData = { ...value };
24866
+ if (value.name) {
24867
+ updatedData.normalizedName = normalizeTagName2(value.name);
24868
+ }
24869
+ const message = await _updateById(id, updatedData);
24870
+ return message;
24871
+ }
24872
+ async function deleteById(id) {
24873
+ const { error } = import_joi106.default.string().hex().required().validate(id);
24874
+ if (error) {
24875
+ throw new import_utils130.BadRequestError("Invalid Tag ID.");
24876
+ }
24877
+ const existingTag = await _getById(id);
24878
+ if (!existingTag) {
24879
+ throw new import_utils130.BadRequestError("Tag not found.");
24880
+ }
24881
+ const message = await _deleteById(id);
24882
+ return message;
24883
+ }
24884
+ return {
24885
+ add,
24886
+ updateById,
24887
+ deleteById
24888
+ };
24889
+ }
24890
+
24891
+ // src/resources/tag/tag.controller.ts
24892
+ var import_joi107 = __toESM(require("joi"));
24893
+ var import_utils131 = require("@goweekdays/utils");
24894
+ function useTagController() {
24895
+ const { getAll: _getAll, getById: _getById } = useTagRepo();
24896
+ const {
24897
+ add: _add,
24898
+ updateById: _updateById,
24899
+ deleteById: _deleteById
24900
+ } = useTagService();
24901
+ async function add(req, res, next) {
24902
+ const value = req.body;
24903
+ const { error } = schemaTagCreate.validate(value);
24904
+ if (error) {
24905
+ next(new import_utils131.BadRequestError(error.message));
24906
+ import_utils131.logger.info(`Controller: ${error.message}`);
24907
+ return;
24908
+ }
24909
+ try {
24910
+ const message = await _add(value);
24911
+ res.json({ message });
24912
+ return;
24913
+ } catch (error2) {
24914
+ next(error2);
24915
+ }
24916
+ }
24917
+ async function getAll(req, res, next) {
24918
+ const query = req.query;
24919
+ const validation = import_joi107.default.object({
24920
+ page: import_joi107.default.number().min(1).optional().allow("", null),
24921
+ limit: import_joi107.default.number().min(1).optional().allow("", null),
24922
+ search: import_joi107.default.string().optional().allow("", null),
24923
+ type: import_joi107.default.string().valid("public", "private").optional().allow("", null),
24924
+ orgId: import_joi107.default.string().hex().optional().allow("", null),
24925
+ status: import_joi107.default.string().valid("active", "pending").optional().allow("", null),
24926
+ categoryPath: import_joi107.default.string().optional().allow("", null)
24927
+ });
24928
+ const { error } = validation.validate(query);
24929
+ if (error) {
24930
+ next(new import_utils131.BadRequestError(error.message));
24931
+ return;
24932
+ }
24933
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
24934
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
24935
+ const search = req.query.search ?? "";
24936
+ const type = req.query.type ?? "";
24937
+ const orgId = req.query.orgId ?? "";
24938
+ const status2 = req.query.status ?? "";
24939
+ const categoryPath = req.query.categoryPath ?? "";
24940
+ const isPageNumber = isFinite(page);
24941
+ if (!isPageNumber) {
24942
+ next(new import_utils131.BadRequestError("Invalid page number."));
24943
+ return;
24944
+ }
24945
+ const isLimitNumber = isFinite(limit);
24946
+ if (!isLimitNumber) {
24947
+ next(new import_utils131.BadRequestError("Invalid limit number."));
24948
+ return;
24949
+ }
24950
+ try {
24951
+ const tags = await _getAll({
24952
+ page,
24953
+ limit,
24954
+ search,
24955
+ type,
24956
+ orgId,
24957
+ status: status2,
24958
+ categoryPath
24959
+ });
24960
+ res.json(tags);
24961
+ return;
24962
+ } catch (error2) {
24963
+ next(error2);
24964
+ }
24965
+ }
24966
+ async function getById(req, res, next) {
24967
+ const id = req.params.id;
24968
+ const validation = import_joi107.default.object({
24969
+ id: import_joi107.default.string().hex().required()
24970
+ });
24971
+ const { error } = validation.validate({ id });
24972
+ if (error) {
24973
+ next(new import_utils131.BadRequestError(error.message));
24974
+ return;
24975
+ }
24976
+ try {
24977
+ const tag = await _getById(id);
24978
+ res.json(tag);
24979
+ return;
24980
+ } catch (error2) {
24981
+ next(error2);
24982
+ }
24983
+ }
24984
+ async function updateById(req, res, next) {
24985
+ const _id = req.params.id;
24986
+ const { error: errorId } = import_joi107.default.string().hex().required().validate(_id);
24987
+ if (errorId) {
24988
+ next(new import_utils131.BadRequestError("Invalid Tag ID."));
24989
+ return;
24990
+ }
24991
+ const payload = req.body;
24992
+ const { error } = schemaTagUpdate.validate(payload);
24993
+ if (error) {
24994
+ next(new import_utils131.BadRequestError(error.message));
24995
+ return;
24996
+ }
24997
+ try {
24998
+ const message = await _updateById(_id, payload);
24999
+ res.json({ message });
25000
+ return;
25001
+ } catch (error2) {
25002
+ next(error2);
25003
+ }
25004
+ }
25005
+ async function deleteById(req, res, next) {
25006
+ const id = req.params.id;
25007
+ if (!id) {
25008
+ next(new import_utils131.BadRequestError("Tag ID is required."));
25009
+ return;
25010
+ }
25011
+ try {
25012
+ const message = await _deleteById(id);
25013
+ res.json(message);
25014
+ return;
25015
+ } catch (error) {
25016
+ next(error);
25017
+ }
25018
+ }
25019
+ return {
25020
+ add,
25021
+ getAll,
25022
+ getById,
25023
+ updateById,
25024
+ deleteById
25025
+ };
25026
+ }
22462
25027
  // Annotate the CommonJS export names for ESM import in node:
22463
25028
  0 && (module.exports = {
22464
25029
  ACCESS_TOKEN_EXPIRY,
@@ -22503,6 +25068,14 @@ function useJournalLineController() {
22503
25068
  VERIFICATION_USER_INVITE_DURATION,
22504
25069
  XENDIT_BASE_URL,
22505
25070
  XENDIT_SECRET_KEY,
25071
+ assetItemCategories,
25072
+ assetItemClasses,
25073
+ assetItemPurposes,
25074
+ assetItemStatuses,
25075
+ assetItemTrackingTypes,
25076
+ assetUnitStatuses,
25077
+ buildCategoryPath,
25078
+ categoryLevels,
22506
25079
  chartOfAccountControlTypes,
22507
25080
  chartOfAccountNormalBalances,
22508
25081
  chartOfAccountStatuses,
@@ -22517,7 +25090,10 @@ function useJournalLineController() {
22517
25090
  ledgerBillTypes,
22518
25091
  modelAccountBalance,
22519
25092
  modelApp,
25093
+ modelAssetItem,
25094
+ modelAssetUnit,
22520
25095
  modelBusinessProfile,
25096
+ modelCategoryNode,
22521
25097
  modelChartOfAccount,
22522
25098
  modelCustomer,
22523
25099
  modelJobApplication,
@@ -22543,14 +25119,22 @@ function useJournalLineController() {
22543
25119
  modelPlan,
22544
25120
  modelPromo,
22545
25121
  modelRole,
25122
+ modelStockMovement,
22546
25123
  modelSubscription,
22547
25124
  modelSubscriptionTransaction,
25125
+ modelTag,
22548
25126
  modelTax,
22549
25127
  modelUser,
22550
25128
  modelVerification,
25129
+ normalizeName,
22551
25130
  schemaAccountBalance,
22552
25131
  schemaApp,
22553
25132
  schemaAppUpdate,
25133
+ schemaAssetItem,
25134
+ schemaAssetItemCreate,
25135
+ schemaAssetItemUpdate,
25136
+ schemaAssetUnit,
25137
+ schemaAssetUnitUpdate,
22554
25138
  schemaAward,
22555
25139
  schemaBuilding,
22556
25140
  schemaBuildingUnit,
@@ -22562,6 +25146,10 @@ function useJournalLineController() {
22562
25146
  schemaBusinessProfileRegisteredAddress,
22563
25147
  schemaBusinessProfileTIN,
22564
25148
  schemaBusinessProfileTradeName,
25149
+ schemaCategoryGetAll,
25150
+ schemaCategoryNodeCreate,
25151
+ schemaCategoryNodeStd,
25152
+ schemaCategoryNodeUpdate,
22565
25153
  schemaCertification,
22566
25154
  schemaChartOfAccountBase,
22567
25155
  schemaChartOfAccountStd,
@@ -22631,6 +25219,7 @@ function useJournalLineController() {
22631
25219
  schemaRole,
22632
25220
  schemaRoleUpdate,
22633
25221
  schemaSkill,
25222
+ schemaStockMovement,
22634
25223
  schemaSubscribe,
22635
25224
  schemaSubscription,
22636
25225
  schemaSubscriptionCompute,
@@ -22638,6 +25227,9 @@ function useJournalLineController() {
22638
25227
  schemaSubscriptionSeats,
22639
25228
  schemaSubscriptionTransaction,
22640
25229
  schemaSubscriptionUpdate,
25230
+ schemaTagCreate,
25231
+ schemaTagStd,
25232
+ schemaTagUpdate,
22641
25233
  schemaTax,
22642
25234
  schemaTaxUpdate,
22643
25235
  schemaUpdateOptions,
@@ -22645,6 +25237,8 @@ function useJournalLineController() {
22645
25237
  schemaVerification,
22646
25238
  schemaVerificationOrgInvite,
22647
25239
  schemaWorkExp,
25240
+ stockMovementReferenceTypes,
25241
+ stockMovementTypes,
22648
25242
  taxDirections,
22649
25243
  taxTypes,
22650
25244
  transactionSchema,
@@ -22652,6 +25246,11 @@ function useJournalLineController() {
22652
25246
  useAppController,
22653
25247
  useAppRepo,
22654
25248
  useAppService,
25249
+ useAssetItemController,
25250
+ useAssetItemRepo,
25251
+ useAssetItemService,
25252
+ useAssetUnitController,
25253
+ useAssetUnitRepo,
22655
25254
  useAuthController,
22656
25255
  useAuthService,
22657
25256
  useBuildingController,
@@ -22662,6 +25261,9 @@ function useJournalLineController() {
22662
25261
  useBuildingUnitService,
22663
25262
  useBusinessProfileCtrl,
22664
25263
  useBusinessProfileRepo,
25264
+ useCategoryController,
25265
+ useCategoryRepo,
25266
+ useCategoryService,
22665
25267
  useChartOfAccountController,
22666
25268
  useChartOfAccountRepo,
22667
25269
  useCounterModel,
@@ -22717,11 +25319,17 @@ function useJournalLineController() {
22717
25319
  useRoleController,
22718
25320
  useRoleRepo,
22719
25321
  useRoleService,
25322
+ useStockMovementController,
25323
+ useStockMovementRepo,
25324
+ useStockMovementService,
22720
25325
  useSubscriptionController,
22721
25326
  useSubscriptionRepo,
22722
25327
  useSubscriptionService,
22723
25328
  useSubscriptionTransactionController,
22724
25329
  useSubscriptionTransactionRepo,
25330
+ useTagController,
25331
+ useTagRepo,
25332
+ useTagService,
22725
25333
  useTaxController,
22726
25334
  useTaxRepo,
22727
25335
  useUserController,