@goweekdays/core 2.15.8 → 2.15.10

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
@@ -22578,19 +22578,36 @@ var schemaAssetItemCreate = import_joi96.default.object({
22578
22578
  }, "subcategory-requires-category");
22579
22579
  var schemaAssetItemUpdate = import_joi96.default.object({
22580
22580
  name: import_joi96.default.string().optional(),
22581
+ sku: import_joi96.default.string().optional().allow("", null),
22582
+ price: import_joi96.default.number().min(0).optional().allow("", null),
22583
+ unit: import_joi96.default.string().optional(),
22581
22584
  description: import_joi96.default.string().optional().allow("", null),
22582
22585
  assetCategory: import_joi96.default.string().valid(...assetItemCategories).optional(),
22583
- assetClass: import_joi96.default.string().valid(...assetItemClasses).optional(),
22586
+ assetClass: import_joi96.default.string().valid(...assetItemClasses).optional().allow("", null),
22587
+ trackingType: import_joi96.default.string().valid(...assetItemTrackingTypes).optional(),
22588
+ departmentId: import_joi96.default.string().hex().length(24).optional(),
22589
+ departmentName: import_joi96.default.string().optional(),
22590
+ categoryId: import_joi96.default.string().hex().length(24).optional().allow("", null),
22591
+ categoryName: import_joi96.default.string().optional().allow("", null),
22592
+ subcategoryId: import_joi96.default.string().hex().length(24).optional().allow("", null),
22593
+ subcategoryName: import_joi96.default.string().optional().allow("", null),
22594
+ categoryPath: import_joi96.default.string().optional().allow("", null),
22584
22595
  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
22596
  tags: import_joi96.default.array().items(
22588
22597
  import_joi96.default.object({
22589
22598
  id: import_joi96.default.string().required(),
22590
22599
  name: import_joi96.default.string().required()
22591
22600
  })
22592
22601
  ).optional(),
22593
- quantityOnHand: import_joi96.default.number().min(0).optional()
22602
+ isPublic: import_joi96.default.boolean().optional(),
22603
+ updatedAt: import_joi96.default.date().optional()
22604
+ }).custom((value, helpers) => {
22605
+ if (value.subcategoryId && !value.categoryId) {
22606
+ return helpers.error("any.custom", {
22607
+ message: "categoryId is required when subcategoryId is provided."
22608
+ });
22609
+ }
22610
+ return value;
22594
22611
  });
22595
22612
  function modelAssetItem(data) {
22596
22613
  const { error } = schemaAssetItem.validate(data);
@@ -22638,10 +22655,12 @@ function modelAssetItem(data) {
22638
22655
  "Marketplace price is required for items marked as public."
22639
22656
  );
22640
22657
  }
22658
+ data.normalizedName = data.name.trim().toLowerCase();
22641
22659
  return {
22642
22660
  _id: data._id,
22643
22661
  orgId: data.orgId,
22644
22662
  name: data.name,
22663
+ normalizedName: data.normalizedName,
22645
22664
  description: data.description ?? "",
22646
22665
  assetCategory: data.assetCategory,
22647
22666
  assetClass: data.assetClass,
@@ -22690,7 +22709,17 @@ function useAssetItemRepo() {
22690
22709
  { key: { purpose: 1 } },
22691
22710
  { key: { trackingType: 1 } },
22692
22711
  { key: { status: 1 } },
22693
- { key: { name: "text" } }
22712
+ { key: { name: "text" } },
22713
+ { key: { public: 1 } },
22714
+ {
22715
+ key: {
22716
+ orgId: 1,
22717
+ normalizedName: 1,
22718
+ purpose: 1,
22719
+ itemRefId: 1
22720
+ },
22721
+ unique: true
22722
+ }
22694
22723
  ]);
22695
22724
  } catch (error) {
22696
22725
  throw new import_utils116.BadRequestError("Failed to create asset item indexes.");
@@ -22726,10 +22755,14 @@ function useAssetItemRepo() {
22726
22755
  categoryId = "",
22727
22756
  subcategoryId = "",
22728
22757
  brand = "",
22729
- tags = []
22758
+ tags = [],
22759
+ isPublic = false
22730
22760
  } = {}) {
22731
22761
  page = page > 0 ? page - 1 : 0;
22732
22762
  const query = { status: status2 };
22763
+ if (isPublic) {
22764
+ query.isPublic = true;
22765
+ }
22733
22766
  if (search) {
22734
22767
  query.$text = { $search: search };
22735
22768
  }
@@ -22770,7 +22803,8 @@ function useAssetItemRepo() {
22770
22803
  subcategoryId,
22771
22804
  brand,
22772
22805
  tags: tags.join(","),
22773
- tag: "getAll"
22806
+ tag: "getAll",
22807
+ public: isPublic ? "true" : "false"
22774
22808
  });
22775
22809
  try {
22776
22810
  const cached = await repo.getCache(cacheKey);
@@ -22815,10 +22849,16 @@ function useAssetItemRepo() {
22815
22849
  }
22816
22850
  }
22817
22851
  async function updateById(id, value) {
22852
+ const { error, value: validatedValue } = schemaAssetItemUpdate.validate(value);
22853
+ if (error) {
22854
+ throw new import_utils116.BadRequestError(
22855
+ `Invalid asset item update data: ${error.message}`
22856
+ );
22857
+ }
22818
22858
  try {
22819
22859
  const result = await repo.collection.findOneAndUpdate(
22820
22860
  { _id: new import_mongodb59.ObjectId(id) },
22821
- { $set: { ...value, updatedAt: /* @__PURE__ */ new Date() } },
22861
+ { $set: { ...validatedValue, updatedAt: /* @__PURE__ */ new Date() } },
22822
22862
  { returnDocument: "after" }
22823
22863
  );
22824
22864
  if (!result) {
@@ -22826,9 +22866,9 @@ function useAssetItemRepo() {
22826
22866
  }
22827
22867
  repo.delCachedData();
22828
22868
  return result;
22829
- } catch (error) {
22830
- if (error instanceof import_utils116.AppError)
22831
- throw error;
22869
+ } catch (error2) {
22870
+ if (error2 instanceof import_utils116.AppError)
22871
+ throw error2;
22832
22872
  throw new import_utils116.InternalServerError("Failed to update asset item.");
22833
22873
  }
22834
22874
  }
@@ -22883,7 +22923,7 @@ function useAssetItemRepo() {
22883
22923
  }
22884
22924
 
22885
22925
  // src/resources/asset-item/asset.item.service.ts
22886
- var import_utils120 = require("@goweekdays/utils");
22926
+ var import_utils126 = require("@goweekdays/utils");
22887
22927
 
22888
22928
  // src/resources/tag/tag.repository.ts
22889
22929
  var import_utils117 = require("@goweekdays/utils");
@@ -23543,246 +23583,187 @@ function useCategoryRepo() {
23543
23583
  };
23544
23584
  }
23545
23585
 
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
+ // src/resources/asset-item-movement/stock.movement.model.ts
23587
+ var import_joi100 = __toESM(require("joi"));
23588
+ var import_mongodb64 = require("mongodb");
23589
+ var stockMovementTypes = [
23590
+ "in",
23591
+ "out",
23592
+ "transfer",
23593
+ "adjustment",
23594
+ "conversion"
23595
+ ];
23596
+ var stockMovementReferenceTypes = [
23597
+ "purchase",
23598
+ "sale",
23599
+ "transfer",
23600
+ "manual",
23601
+ "conversion"
23602
+ ];
23603
+ var schemaStockMovement = import_joi100.default.object({
23604
+ itemId: import_joi100.default.string().hex().length(24).required(),
23605
+ type: import_joi100.default.string().valid(...stockMovementTypes).required(),
23606
+ quantity: import_joi100.default.number().positive().required(),
23607
+ unitCost: import_joi100.default.number().min(0).optional().allow(null),
23608
+ totalCost: import_joi100.default.number().min(0).optional().allow(null),
23609
+ reference: import_joi100.default.object({
23610
+ type: import_joi100.default.string().valid(...stockMovementReferenceTypes).required(),
23611
+ id: import_joi100.default.string().optional().allow("", null)
23612
+ }).optional().allow(null),
23613
+ fromLocationId: import_joi100.default.string().optional().allow("", null),
23614
+ toLocationId: import_joi100.default.string().optional().allow("", null),
23615
+ fromItemId: import_joi100.default.string().hex().length(24).optional().allow("", null),
23616
+ toItemId: import_joi100.default.string().hex().length(24).optional().allow("", null),
23617
+ reason: import_joi100.default.string().optional().allow("", null)
23618
+ });
23619
+ function modelStockMovement(data) {
23620
+ const { error } = schemaStockMovement.validate(data);
23621
+ if (error) {
23622
+ throw new Error(`Invalid stock movement data: ${error.message}`);
23586
23623
  }
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
- }
23624
+ if (data.itemId && typeof data.itemId === "string") {
23594
23625
  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();
23626
+ data.itemId = new import_mongodb64.ObjectId(data.itemId);
23627
+ } catch {
23628
+ throw new Error(`Invalid itemId format: ${data.itemId}`);
23632
23629
  }
23633
23630
  }
23634
23631
  return {
23635
- add
23632
+ _id: data._id,
23633
+ itemId: data.itemId,
23634
+ type: data.type,
23635
+ quantity: data.quantity,
23636
+ unitCost: data.unitCost ?? 0,
23637
+ totalCost: data.totalCost ?? 0,
23638
+ reference: data.reference ?? void 0,
23639
+ fromLocationId: data.fromLocationId ?? "",
23640
+ toLocationId: data.toLocationId ?? "",
23641
+ fromItemId: data.fromItemId ?? "",
23642
+ toItemId: data.toItemId ?? "",
23643
+ reason: data.reason ?? "",
23644
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date()
23636
23645
  };
23637
23646
  }
23638
23647
 
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) {
23648
+ // src/resources/asset-item-movement/stock.movement.repository.ts
23649
+ var import_utils120 = require("@goweekdays/utils");
23650
+ var import_mongodb65 = require("mongodb");
23651
+ function useStockMovementRepo() {
23652
+ const namespace_collection = "stock.movements";
23653
+ const repo = (0, import_utils120.useRepo)(namespace_collection);
23654
+ async function createIndexes() {
23663
23655
  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
- });
23656
+ await repo.collection.createIndexes([
23657
+ { key: { itemId: 1 } },
23658
+ { key: { createdAt: -1 } },
23659
+ { key: { itemId: 1, createdAt: -1 } },
23660
+ { key: { type: 1 } }
23661
+ ]);
23674
23662
  } catch (error) {
23675
- next(error);
23663
+ throw new import_utils120.BadRequestError("Failed to create stock movement indexes.");
23676
23664
  }
23677
23665
  }
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
- }
23666
+ async function add(value, session) {
23705
23667
  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);
23668
+ value = modelStockMovement(value);
23669
+ const res = await repo.collection.insertOne(value, { session });
23670
+ repo.delCachedData();
23671
+ return res.insertedId;
23672
+ } catch (error) {
23673
+ import_utils120.logger.log({ level: "error", message: error.message });
23674
+ throw new import_utils120.InternalServerError("Failed to create stock movement.");
23723
23675
  }
23724
23676
  }
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;
23677
+ async function getByItemId({
23678
+ itemId,
23679
+ type = "",
23680
+ page = 1,
23681
+ limit = 10
23682
+ }) {
23683
+ page = page > 0 ? page - 1 : 0;
23684
+ const query = {
23685
+ itemId: new import_mongodb65.ObjectId(itemId)
23686
+ };
23687
+ if (type) {
23688
+ query.type = type;
23730
23689
  }
23731
- try {
23732
- const assetItem = await repo.getById(req.params.id);
23733
- res.json(assetItem);
23734
- } catch (error2) {
23735
- next(error2);
23690
+ const cacheKey = (0, import_utils120.makeCacheKey)(namespace_collection, {
23691
+ itemId,
23692
+ type,
23693
+ page,
23694
+ limit,
23695
+ tag: "byItemId"
23696
+ });
23697
+ try {
23698
+ const cached = await repo.getCache(cacheKey);
23699
+ if (cached)
23700
+ return cached;
23701
+ const items = await repo.collection.aggregate([
23702
+ { $match: query },
23703
+ { $sort: { createdAt: -1 } },
23704
+ { $skip: page * limit },
23705
+ { $limit: limit }
23706
+ ]).toArray();
23707
+ const length = await repo.collection.countDocuments(query);
23708
+ const data = (0, import_utils120.paginate)(items, page, limit, length);
23709
+ repo.setCache(cacheKey, data, 600);
23710
+ return data;
23711
+ } catch (error) {
23712
+ import_utils120.logger.log({ level: "error", message: `${error}` });
23713
+ throw error;
23736
23714
  }
23737
23715
  }
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
- }
23716
+ async function getById(id) {
23717
+ const cacheKey = (0, import_utils120.makeCacheKey)(namespace_collection, {
23718
+ id,
23719
+ tag: "by-id"
23720
+ });
23749
23721
  try {
23750
- const updatedAssetItem = await repo.updateById(req.params.id, req.body);
23751
- res.json({
23752
- message: "Asset item updated successfully.",
23753
- assetItem: updatedAssetItem
23722
+ const cached = await repo.getCache(cacheKey);
23723
+ if (cached)
23724
+ return cached;
23725
+ const result = await repo.collection.findOne({
23726
+ _id: new import_mongodb65.ObjectId(id)
23754
23727
  });
23728
+ if (!result) {
23729
+ throw new import_utils120.BadRequestError("Stock movement not found.");
23730
+ }
23731
+ repo.setCache(cacheKey, result, 300);
23732
+ return result;
23755
23733
  } catch (error) {
23756
- next(error);
23734
+ if (error instanceof import_utils120.AppError)
23735
+ throw error;
23736
+ throw new import_utils120.InternalServerError("Failed to get stock movement.");
23757
23737
  }
23758
23738
  }
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
- }
23739
+ async function countByItemId(itemId) {
23765
23740
  try {
23766
- await repo.deleteById(req.params.id);
23767
- res.json({
23768
- message: "Asset item deleted successfully."
23741
+ return await repo.collection.countDocuments({
23742
+ itemId: new import_mongodb65.ObjectId(itemId)
23769
23743
  });
23770
- } catch (error2) {
23771
- next(error2);
23744
+ } catch (error) {
23745
+ throw new import_utils120.InternalServerError("Failed to count stock movements.");
23772
23746
  }
23773
23747
  }
23774
23748
  return {
23749
+ createIndexes,
23775
23750
  add,
23776
- getAll,
23751
+ getByItemId,
23777
23752
  getById,
23778
- updateById,
23779
- deleteById
23753
+ countByItemId
23780
23754
  };
23781
23755
  }
23782
23756
 
23757
+ // src/resources/asset-item-movement/stock.movement.service.ts
23758
+ var import_utils122 = require("@goweekdays/utils");
23759
+ var import_utils123 = require("@goweekdays/utils");
23760
+
23761
+ // src/resources/asset-item-unit/asset.unit.repository.ts
23762
+ var import_utils121 = require("@goweekdays/utils");
23763
+
23783
23764
  // src/resources/asset-item-unit/asset.unit.model.ts
23784
23765
  var import_joi101 = __toESM(require("joi"));
23785
- var import_mongodb64 = require("mongodb");
23766
+ var import_mongodb66 = require("mongodb");
23786
23767
  var assetUnitStatuses = [
23787
23768
  "available",
23788
23769
  "assigned",
@@ -23812,7 +23793,7 @@ function modelAssetUnit(data) {
23812
23793
  }
23813
23794
  if (data.itemId && typeof data.itemId === "string") {
23814
23795
  try {
23815
- data.itemId = new import_mongodb64.ObjectId(data.itemId);
23796
+ data.itemId = new import_mongodb66.ObjectId(data.itemId);
23816
23797
  } catch {
23817
23798
  throw new Error(`Invalid itemId format: ${data.itemId}`);
23818
23799
  }
@@ -23831,11 +23812,10 @@ function modelAssetUnit(data) {
23831
23812
  }
23832
23813
 
23833
23814
  // src/resources/asset-item-unit/asset.unit.repository.ts
23834
- var import_utils122 = require("@goweekdays/utils");
23835
- var import_mongodb65 = require("mongodb");
23815
+ var import_mongodb67 = require("mongodb");
23836
23816
  function useAssetUnitRepo() {
23837
23817
  const namespace_collection = "asset.units";
23838
- const repo = (0, import_utils122.useRepo)(namespace_collection);
23818
+ const repo = (0, import_utils121.useRepo)(namespace_collection);
23839
23819
  async function createIndexes() {
23840
23820
  try {
23841
23821
  await repo.collection.createIndexes([
@@ -23845,7 +23825,7 @@ function useAssetUnitRepo() {
23845
23825
  { key: { itemId: 1, status: 1 } }
23846
23826
  ]);
23847
23827
  } catch (error) {
23848
- throw new import_utils122.BadRequestError("Failed to create asset unit indexes.");
23828
+ throw new import_utils121.BadRequestError("Failed to create asset unit indexes.");
23849
23829
  }
23850
23830
  }
23851
23831
  async function add(value, session) {
@@ -23855,8 +23835,8 @@ function useAssetUnitRepo() {
23855
23835
  repo.delCachedData();
23856
23836
  return res.insertedId;
23857
23837
  } catch (error) {
23858
- import_utils122.logger.log({ level: "error", message: error.message });
23859
- throw new import_utils122.InternalServerError("Failed to create asset unit.");
23838
+ import_utils121.logger.log({ level: "error", message: error.message });
23839
+ throw new import_utils121.InternalServerError("Failed to create asset unit.");
23860
23840
  }
23861
23841
  }
23862
23842
  async function getByItemId({
@@ -23867,12 +23847,12 @@ function useAssetUnitRepo() {
23867
23847
  }) {
23868
23848
  page = page > 0 ? page - 1 : 0;
23869
23849
  const query = {
23870
- itemId: new import_mongodb65.ObjectId(itemId)
23850
+ itemId: new import_mongodb67.ObjectId(itemId)
23871
23851
  };
23872
23852
  if (status2) {
23873
23853
  query.status = status2;
23874
23854
  }
23875
- const cacheKey = (0, import_utils122.makeCacheKey)(namespace_collection, {
23855
+ const cacheKey = (0, import_utils121.makeCacheKey)(namespace_collection, {
23876
23856
  itemId,
23877
23857
  status: status2,
23878
23858
  page,
@@ -23890,16 +23870,16 @@ function useAssetUnitRepo() {
23890
23870
  { $limit: limit }
23891
23871
  ]).toArray();
23892
23872
  const length = await repo.collection.countDocuments(query);
23893
- const data = (0, import_utils122.paginate)(items, page, limit, length);
23873
+ const data = (0, import_utils121.paginate)(items, page, limit, length);
23894
23874
  repo.setCache(cacheKey, data, 600);
23895
23875
  return data;
23896
23876
  } catch (error) {
23897
- import_utils122.logger.log({ level: "error", message: `${error}` });
23877
+ import_utils121.logger.log({ level: "error", message: `${error}` });
23898
23878
  throw error;
23899
23879
  }
23900
23880
  }
23901
23881
  async function getById(id) {
23902
- const cacheKey = (0, import_utils122.makeCacheKey)(namespace_collection, {
23882
+ const cacheKey = (0, import_utils121.makeCacheKey)(namespace_collection, {
23903
23883
  id,
23904
23884
  tag: "by-id"
23905
23885
  });
@@ -23908,45 +23888,54 @@ function useAssetUnitRepo() {
23908
23888
  if (cached)
23909
23889
  return cached;
23910
23890
  const result = await repo.collection.findOne({
23911
- _id: new import_mongodb65.ObjectId(id)
23891
+ _id: new import_mongodb67.ObjectId(id)
23912
23892
  });
23913
23893
  if (!result) {
23914
- throw new import_utils122.BadRequestError("Asset unit not found.");
23894
+ throw new import_utils121.BadRequestError("Asset unit not found.");
23915
23895
  }
23916
23896
  repo.setCache(cacheKey, result, 300);
23917
23897
  return result;
23918
23898
  } catch (error) {
23919
- if (error instanceof import_utils122.AppError)
23899
+ if (error instanceof import_utils121.AppError)
23920
23900
  throw error;
23921
- throw new import_utils122.InternalServerError("Failed to get asset unit.");
23901
+ throw new import_utils121.InternalServerError("Failed to get asset unit.");
23922
23902
  }
23923
23903
  }
23924
23904
  async function updateById(id, value, session) {
23925
23905
  try {
23926
23906
  const result = await repo.collection.findOneAndUpdate(
23927
- { _id: new import_mongodb65.ObjectId(id) },
23907
+ { _id: new import_mongodb67.ObjectId(id) },
23928
23908
  { $set: { ...value, updatedAt: /* @__PURE__ */ new Date() } },
23929
23909
  { returnDocument: "after", session }
23930
23910
  );
23931
23911
  if (!result) {
23932
- throw new import_utils122.BadRequestError("Asset unit not found.");
23912
+ throw new import_utils121.BadRequestError("Asset unit not found.");
23933
23913
  }
23934
23914
  repo.delCachedData();
23935
23915
  return result;
23936
23916
  } catch (error) {
23937
- if (error instanceof import_utils122.AppError)
23917
+ if (error instanceof import_utils121.AppError)
23938
23918
  throw error;
23939
- throw new import_utils122.InternalServerError("Failed to update asset unit.");
23919
+ throw new import_utils121.InternalServerError("Failed to update asset unit.");
23920
+ }
23921
+ }
23922
+ async function countByItemId(itemId) {
23923
+ try {
23924
+ return await repo.collection.countDocuments({
23925
+ itemId: new import_mongodb67.ObjectId(itemId)
23926
+ });
23927
+ } catch (error) {
23928
+ throw new import_utils121.InternalServerError("Failed to count asset units.");
23940
23929
  }
23941
23930
  }
23942
23931
  async function countByItemIdAndStatus(itemId, status2) {
23943
23932
  try {
23944
23933
  return await repo.collection.countDocuments({
23945
- itemId: new import_mongodb65.ObjectId(itemId),
23934
+ itemId: new import_mongodb67.ObjectId(itemId),
23946
23935
  status: status2
23947
23936
  });
23948
23937
  } catch (error) {
23949
- throw new import_utils122.InternalServerError("Failed to count asset units.");
23938
+ throw new import_utils121.InternalServerError("Failed to count asset units.");
23950
23939
  }
23951
23940
  }
23952
23941
  return {
@@ -23955,671 +23944,816 @@ function useAssetUnitRepo() {
23955
23944
  getByItemId,
23956
23945
  getById,
23957
23946
  updateById,
23947
+ countByItemId,
23958
23948
  countByItemIdAndStatus
23959
23949
  };
23960
23950
  }
23961
23951
 
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) {
23952
+ // src/resources/asset-item-movement/stock.movement.service.ts
23953
+ function useStockMovementService() {
23954
+ const movementRepo = useStockMovementRepo();
23955
+ const assetItemRepo = useAssetItemRepo();
23956
+ const assetUnitRepo = useAssetUnitRepo();
23957
+ async function stockIn(value, units) {
23958
+ const item = await assetItemRepo.getById(String(value.itemId));
23959
+ const session = import_utils123.useAtlas.getClient()?.startSession();
23960
+ if (!session)
23961
+ throw new import_utils122.BadRequestError("Failed to start session.");
23977
23962
  try {
23978
- const { error } = schemaAssetUnit.validate(req.body);
23979
- if (error) {
23980
- next(new import_utils123.BadRequestError(error.message));
23981
- return;
23963
+ session.startTransaction();
23964
+ value.type = "in";
23965
+ const movementId = await movementRepo.add(value, session);
23966
+ if (item.trackingType === "individual" && units && units.length > 0) {
23967
+ for (const unit of units) {
23968
+ await assetUnitRepo.add(
23969
+ {
23970
+ itemId: value.itemId,
23971
+ serialNumber: unit.serialNumber,
23972
+ plateNumber: unit.plateNumber,
23973
+ status: "available",
23974
+ locationId: value.toLocationId
23975
+ },
23976
+ session
23977
+ );
23978
+ }
23982
23979
  }
23983
- const unitId = await repo.add(req.body);
23984
- res.status(201).json({
23985
- message: "Asset unit created successfully.",
23986
- unitId
23987
- });
23980
+ await assetItemRepo.incrementQuantity(
23981
+ String(value.itemId),
23982
+ value.quantity,
23983
+ session
23984
+ );
23985
+ await session.commitTransaction();
23986
+ return movementId;
23988
23987
  } catch (error) {
23989
- next(error);
23988
+ await session.abortTransaction();
23989
+ throw error;
23990
+ } finally {
23991
+ await session.endSession();
23990
23992
  }
23991
23993
  }
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;
23994
+ async function stockOut(value, unitIds) {
23995
+ const item = await assetItemRepo.getById(String(value.itemId));
23996
+ if (item.trackingType === "quantity" && (item.quantityOnHand ?? 0) < value.quantity) {
23997
+ throw new import_utils122.BadRequestError("Insufficient stock.");
23997
23998
  }
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;
23999
+ const session = import_utils123.useAtlas.getClient()?.startSession();
24000
+ if (!session)
24001
+ throw new import_utils122.BadRequestError("Failed to start session.");
24002
24002
  try {
24003
- const results = await repo.getByItemId({ itemId, status: status2, page, limit });
24004
- res.json(results);
24005
- } catch (error2) {
24006
- next(error2);
24003
+ session.startTransaction();
24004
+ value.type = "out";
24005
+ const movementId = await movementRepo.add(value, session);
24006
+ if (item.trackingType === "individual" && unitIds && unitIds.length > 0) {
24007
+ for (const unitId of unitIds) {
24008
+ await assetUnitRepo.updateById(
24009
+ unitId,
24010
+ { status: "assigned", assignedTo: value.reference?.id },
24011
+ session
24012
+ );
24013
+ }
24014
+ }
24015
+ await assetItemRepo.incrementQuantity(
24016
+ String(value.itemId),
24017
+ -value.quantity,
24018
+ session
24019
+ );
24020
+ await session.commitTransaction();
24021
+ return movementId;
24022
+ } catch (error) {
24023
+ await session.abortTransaction();
24024
+ throw error;
24025
+ } finally {
24026
+ await session.endSession();
24007
24027
  }
24008
24028
  }
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;
24029
+ async function transfer(value, unitIds) {
24030
+ if (!value.fromLocationId || !value.toLocationId) {
24031
+ throw new import_utils122.BadRequestError(
24032
+ "Transfer requires both fromLocationId and toLocationId."
24033
+ );
24014
24034
  }
24035
+ const item = await assetItemRepo.getById(String(value.itemId));
24036
+ const session = import_utils123.useAtlas.getClient()?.startSession();
24037
+ if (!session)
24038
+ throw new import_utils122.BadRequestError("Failed to start session.");
24015
24039
  try {
24016
- const unit = await repo.getById(req.params.id);
24017
- res.json(unit);
24018
- } catch (error2) {
24019
- next(error2);
24040
+ session.startTransaction();
24041
+ value.type = "transfer";
24042
+ const movementId = await movementRepo.add(value, session);
24043
+ if (item.trackingType === "individual" && unitIds && unitIds.length > 0) {
24044
+ for (const unitId of unitIds) {
24045
+ await assetUnitRepo.updateById(
24046
+ unitId,
24047
+ { locationId: value.toLocationId },
24048
+ session
24049
+ );
24050
+ }
24051
+ }
24052
+ await session.commitTransaction();
24053
+ return movementId;
24054
+ } catch (error) {
24055
+ await session.abortTransaction();
24056
+ throw error;
24057
+ } finally {
24058
+ await session.endSession();
24020
24059
  }
24021
24060
  }
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;
24061
+ async function adjustment(value) {
24062
+ if (!value.reason) {
24063
+ throw new import_utils122.BadRequestError("Adjustment requires a reason.");
24032
24064
  }
24065
+ const session = import_utils123.useAtlas.getClient()?.startSession();
24066
+ if (!session)
24067
+ throw new import_utils122.BadRequestError("Failed to start session.");
24033
24068
  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
- });
24069
+ session.startTransaction();
24070
+ value.type = "adjustment";
24071
+ const movementId = await movementRepo.add(value, session);
24072
+ await assetItemRepo.incrementQuantity(
24073
+ String(value.itemId),
24074
+ value.quantity,
24075
+ session
24076
+ );
24077
+ await session.commitTransaction();
24078
+ return movementId;
24039
24079
  } catch (error) {
24040
- next(error);
24080
+ await session.abortTransaction();
24081
+ throw error;
24082
+ } finally {
24083
+ await session.endSession();
24041
24084
  }
24042
24085
  }
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") {
24086
+ async function conversion(newItem, quantity, unitIds) {
24087
+ const sourceItem = await assetItemRepo.getById(String(newItem.itemRefId));
24088
+ if (!newItem.itemRefId) {
24089
+ throw new import_utils122.BadRequestError("Conversion requires itemRefId.");
24090
+ }
24091
+ if (sourceItem.trackingType === "quantity" && (sourceItem.quantityOnHand ?? 0) < quantity) {
24092
+ throw new import_utils122.BadRequestError("Insufficient stock on source item.");
24093
+ }
24094
+ const session = import_utils123.useAtlas.getClient()?.startSession();
24095
+ if (!session)
24096
+ throw new import_utils122.BadRequestError("Failed to start session.");
24090
24097
  try {
24091
- data.itemId = new import_mongodb66.ObjectId(data.itemId);
24092
- } catch {
24093
- throw new Error(`Invalid itemId format: ${data.itemId}`);
24098
+ session.startTransaction();
24099
+ const newItemId = await assetItemRepo.add(newItem, session);
24100
+ const movementData = {
24101
+ itemId: newItem.itemRefId,
24102
+ type: "conversion",
24103
+ quantity,
24104
+ fromItemId: String(newItem.itemRefId),
24105
+ toItemId: String(newItemId),
24106
+ reference: { type: "conversion", id: String(newItemId) }
24107
+ };
24108
+ const movementId = await movementRepo.add(movementData, session);
24109
+ await assetItemRepo.incrementQuantity(
24110
+ String(newItem.itemRefId),
24111
+ -quantity,
24112
+ session
24113
+ );
24114
+ await assetItemRepo.incrementQuantity(
24115
+ String(newItemId),
24116
+ quantity,
24117
+ session
24118
+ );
24119
+ if (sourceItem.trackingType === "individual" && unitIds && unitIds.length > 0) {
24120
+ for (const unitId of unitIds) {
24121
+ await assetUnitRepo.updateById(
24122
+ unitId,
24123
+ { itemId: String(newItemId) },
24124
+ session
24125
+ );
24126
+ }
24127
+ }
24128
+ await session.commitTransaction();
24129
+ return { movementId, newItemId };
24130
+ } catch (error) {
24131
+ await session.abortTransaction();
24132
+ throw error;
24133
+ } finally {
24134
+ await session.endSession();
24094
24135
  }
24095
24136
  }
24096
24137
  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()
24138
+ stockIn,
24139
+ stockOut,
24140
+ transfer,
24141
+ adjustment,
24142
+ conversion
24110
24143
  };
24111
24144
  }
24112
24145
 
24113
- // src/resources/asset-item-movement/stock.movement.repository.ts
24146
+ // src/resources/asset-item-movement/stock.movement.controller.ts
24147
+ var import_joi102 = __toESM(require("joi"));
24114
24148
  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() {
24149
+ var querySchema = import_joi102.default.object({
24150
+ itemId: import_joi102.default.string().hex().length(24).required(),
24151
+ type: import_joi102.default.string().valid(...stockMovementTypes).optional().allow("", null),
24152
+ page: import_joi102.default.number().min(1).optional().allow("", null),
24153
+ limit: import_joi102.default.number().min(1).optional().allow("", null)
24154
+ });
24155
+ var idParamSchema2 = import_joi102.default.object({
24156
+ id: import_joi102.default.string().hex().length(24).required()
24157
+ });
24158
+ var stockInSchema = import_joi102.default.object({
24159
+ itemId: import_joi102.default.string().hex().length(24).required(),
24160
+ quantity: import_joi102.default.number().positive().required(),
24161
+ unitCost: import_joi102.default.number().min(0).optional().allow(null),
24162
+ totalCost: import_joi102.default.number().min(0).optional().allow(null),
24163
+ reference: import_joi102.default.object({
24164
+ type: import_joi102.default.string().valid("purchase", "sale", "transfer", "manual", "conversion").required(),
24165
+ id: import_joi102.default.string().optional().allow("", null)
24166
+ }).optional().allow(null),
24167
+ toLocationId: import_joi102.default.string().optional().allow("", null),
24168
+ units: import_joi102.default.array().items(
24169
+ import_joi102.default.object({
24170
+ serialNumber: import_joi102.default.string().optional().allow("", null),
24171
+ plateNumber: import_joi102.default.string().optional().allow("", null)
24172
+ })
24173
+ ).optional()
24174
+ });
24175
+ var stockOutSchema = import_joi102.default.object({
24176
+ itemId: import_joi102.default.string().hex().length(24).required(),
24177
+ quantity: import_joi102.default.number().positive().required(),
24178
+ unitCost: import_joi102.default.number().min(0).optional().allow(null),
24179
+ totalCost: import_joi102.default.number().min(0).optional().allow(null),
24180
+ reference: import_joi102.default.object({
24181
+ type: import_joi102.default.string().valid("purchase", "sale", "transfer", "manual", "conversion").required(),
24182
+ id: import_joi102.default.string().optional().allow("", null)
24183
+ }).optional().allow(null),
24184
+ fromLocationId: import_joi102.default.string().optional().allow("", null),
24185
+ unitIds: import_joi102.default.array().items(import_joi102.default.string().hex().length(24)).optional()
24186
+ });
24187
+ var transferSchema = import_joi102.default.object({
24188
+ itemId: import_joi102.default.string().hex().length(24).required(),
24189
+ quantity: import_joi102.default.number().positive().required(),
24190
+ fromLocationId: import_joi102.default.string().required(),
24191
+ toLocationId: import_joi102.default.string().required(),
24192
+ reference: import_joi102.default.object({
24193
+ type: import_joi102.default.string().valid("purchase", "sale", "transfer", "manual", "conversion").required(),
24194
+ id: import_joi102.default.string().optional().allow("", null)
24195
+ }).optional().allow(null),
24196
+ unitIds: import_joi102.default.array().items(import_joi102.default.string().hex().length(24)).optional()
24197
+ });
24198
+ var adjustmentSchema = import_joi102.default.object({
24199
+ itemId: import_joi102.default.string().hex().length(24).required(),
24200
+ quantity: import_joi102.default.number().required(),
24201
+ reason: import_joi102.default.string().required(),
24202
+ reference: import_joi102.default.object({
24203
+ type: import_joi102.default.string().valid("purchase", "sale", "transfer", "manual", "conversion").required(),
24204
+ id: import_joi102.default.string().optional().allow("", null)
24205
+ }).optional().allow(null)
24206
+ });
24207
+ var conversionSchema = import_joi102.default.object({
24208
+ quantity: import_joi102.default.number().positive().required(),
24209
+ newItem: import_joi102.default.object({
24210
+ name: import_joi102.default.string().required(),
24211
+ description: import_joi102.default.string().optional().allow("", null),
24212
+ assetCategory: import_joi102.default.string().valid(...assetItemCategories).required(),
24213
+ assetClass: import_joi102.default.string().valid(...assetItemClasses).required(),
24214
+ trackingType: import_joi102.default.string().valid(...assetItemTrackingTypes).required(),
24215
+ purpose: import_joi102.default.string().valid(...assetItemPurposes).required(),
24216
+ itemRefId: import_joi102.default.string().hex().length(24).required(),
24217
+ remarks: import_joi102.default.string().optional().allow("", null),
24218
+ departmentId: import_joi102.default.string().required(),
24219
+ departmentName: import_joi102.default.string().required(),
24220
+ categoryId: import_joi102.default.string().required(),
24221
+ categoryName: import_joi102.default.string().required(),
24222
+ subcategoryId: import_joi102.default.string().required(),
24223
+ subcategoryName: import_joi102.default.string().required(),
24224
+ categoryPath: import_joi102.default.string().required(),
24225
+ brand: import_joi102.default.string().optional().allow("", null),
24226
+ tags: import_joi102.default.array().items(
24227
+ import_joi102.default.object({
24228
+ id: import_joi102.default.string().required(),
24229
+ name: import_joi102.default.string().required()
24230
+ })
24231
+ ).optional().default([])
24232
+ }).required(),
24233
+ unitIds: import_joi102.default.array().items(import_joi102.default.string().hex().length(24)).optional()
24234
+ });
24235
+ function useStockMovementController() {
24236
+ const repo = useStockMovementRepo();
24237
+ const service = useStockMovementService();
24238
+ async function stockIn(req, res, next) {
24120
24239
  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
- ]);
24240
+ const { error } = stockInSchema.validate(req.body);
24241
+ if (error) {
24242
+ next(new import_utils124.BadRequestError(error.message));
24243
+ return;
24244
+ }
24245
+ const { units, ...movement } = req.body;
24246
+ const movementId = await service.stockIn(movement, units);
24247
+ res.status(201).json({
24248
+ message: "Stock in recorded successfully.",
24249
+ movementId
24250
+ });
24127
24251
  } catch (error) {
24128
- throw new import_utils124.BadRequestError("Failed to create stock movement indexes.");
24252
+ next(error);
24129
24253
  }
24130
24254
  }
24131
- async function add(value, session) {
24255
+ async function stockOut(req, res, next) {
24132
24256
  try {
24133
- value = modelStockMovement(value);
24134
- const res = await repo.collection.insertOne(value, { session });
24135
- repo.delCachedData();
24136
- return res.insertedId;
24257
+ const { error } = stockOutSchema.validate(req.body);
24258
+ if (error) {
24259
+ next(new import_utils124.BadRequestError(error.message));
24260
+ return;
24261
+ }
24262
+ const { unitIds, ...movement } = req.body;
24263
+ const movementId = await service.stockOut(movement, unitIds);
24264
+ res.status(201).json({
24265
+ message: "Stock out recorded successfully.",
24266
+ movementId
24267
+ });
24137
24268
  } catch (error) {
24138
- import_utils124.logger.log({ level: "error", message: error.message });
24139
- throw new import_utils124.InternalServerError("Failed to create stock movement.");
24269
+ next(error);
24140
24270
  }
24141
24271
  }
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
- });
24272
+ async function transfer(req, res, next) {
24162
24273
  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;
24274
+ const { error } = transferSchema.validate(req.body);
24275
+ if (error) {
24276
+ next(new import_utils124.BadRequestError(error.message));
24277
+ return;
24278
+ }
24279
+ const { unitIds, ...movement } = req.body;
24280
+ const movementId = await service.transfer(movement, unitIds);
24281
+ res.status(201).json({
24282
+ message: "Transfer recorded successfully.",
24283
+ movementId
24284
+ });
24176
24285
  } catch (error) {
24177
- import_utils124.logger.log({ level: "error", message: `${error}` });
24178
- throw error;
24286
+ next(error);
24179
24287
  }
24180
24288
  }
24181
- async function getById(id) {
24182
- const cacheKey = (0, import_utils124.makeCacheKey)(namespace_collection, {
24183
- id,
24184
- tag: "by-id"
24185
- });
24289
+ async function adjustment(req, res, next) {
24186
24290
  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)
24291
+ const { error } = adjustmentSchema.validate(req.body);
24292
+ if (error) {
24293
+ next(new import_utils124.BadRequestError(error.message));
24294
+ return;
24295
+ }
24296
+ const movementId = await service.adjustment(req.body);
24297
+ res.status(201).json({
24298
+ message: "Adjustment recorded successfully.",
24299
+ movementId
24192
24300
  });
24193
- if (!result) {
24194
- throw new import_utils124.BadRequestError("Stock movement not found.");
24301
+ } catch (error) {
24302
+ next(error);
24303
+ }
24304
+ }
24305
+ async function getByItemId(req, res, next) {
24306
+ const { error } = querySchema.validate(req.query);
24307
+ if (error) {
24308
+ next(new import_utils124.BadRequestError(error.message));
24309
+ return;
24310
+ }
24311
+ const itemId = req.query.itemId;
24312
+ const type = req.query.type ?? "";
24313
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
24314
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
24315
+ try {
24316
+ const results = await repo.getByItemId({ itemId, type, page, limit });
24317
+ res.json(results);
24318
+ } catch (error2) {
24319
+ next(error2);
24320
+ }
24321
+ }
24322
+ async function getById(req, res, next) {
24323
+ const { error } = idParamSchema2.validate(req.params);
24324
+ if (error) {
24325
+ next(new import_utils124.BadRequestError(error.message));
24326
+ return;
24327
+ }
24328
+ try {
24329
+ const movement = await repo.getById(req.params.id);
24330
+ res.json(movement);
24331
+ } catch (error2) {
24332
+ next(error2);
24333
+ }
24334
+ }
24335
+ async function conversion(req, res, next) {
24336
+ try {
24337
+ const { error } = conversionSchema.validate(req.body);
24338
+ if (error) {
24339
+ next(new import_utils124.BadRequestError(error.message));
24340
+ return;
24195
24341
  }
24196
- repo.setCache(cacheKey, result, 300);
24197
- return result;
24342
+ const { quantity, newItem, unitIds } = req.body;
24343
+ const result = await service.conversion(newItem, quantity, unitIds);
24344
+ res.status(201).json({
24345
+ message: "Conversion recorded successfully.",
24346
+ movementId: result.movementId,
24347
+ newItemId: result.newItemId
24348
+ });
24198
24349
  } catch (error) {
24199
- if (error instanceof import_utils124.AppError)
24200
- throw error;
24201
- throw new import_utils124.InternalServerError("Failed to get stock movement.");
24350
+ next(error);
24202
24351
  }
24203
24352
  }
24204
24353
  return {
24205
- createIndexes,
24206
- add,
24354
+ stockIn,
24355
+ stockOut,
24356
+ transfer,
24357
+ adjustment,
24358
+ conversion,
24207
24359
  getByItemId,
24208
24360
  getById
24209
24361
  };
24210
24362
  }
24211
24363
 
24212
- // src/resources/asset-item-movement/stock.movement.service.ts
24364
+ // src/resources/asset-item-unit/asset.unit.controller.ts
24365
+ var import_joi103 = __toESM(require("joi"));
24213
24366
  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.");
24367
+ var querySchema2 = import_joi103.default.object({
24368
+ itemId: import_joi103.default.string().hex().length(24).required(),
24369
+ status: import_joi103.default.string().valid(...assetUnitStatuses).optional().allow("", null),
24370
+ page: import_joi103.default.number().min(1).optional().allow("", null),
24371
+ limit: import_joi103.default.number().min(1).optional().allow("", null)
24372
+ });
24373
+ var idParamSchema3 = import_joi103.default.object({
24374
+ id: import_joi103.default.string().hex().length(24).required()
24375
+ });
24376
+ function useAssetUnitController() {
24377
+ const repo = useAssetUnitRepo();
24378
+ async function add(req, res, next) {
24224
24379
  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
- }
24380
+ const { error } = schemaAssetUnit.validate(req.body);
24381
+ if (error) {
24382
+ next(new import_utils125.BadRequestError(error.message));
24383
+ return;
24241
24384
  }
24242
- await assetItemRepo.incrementQuantity(
24243
- String(value.itemId),
24244
- value.quantity,
24245
- session
24246
- );
24247
- await session.commitTransaction();
24248
- return movementId;
24385
+ const unitId = await repo.add(req.body);
24386
+ res.status(201).json({
24387
+ message: "Asset unit created successfully.",
24388
+ unitId
24389
+ });
24249
24390
  } catch (error) {
24250
- await session.abortTransaction();
24251
- throw error;
24252
- } finally {
24253
- await session.endSession();
24391
+ next(error);
24254
24392
  }
24255
24393
  }
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.");
24394
+ async function getByItemId(req, res, next) {
24395
+ const { error } = querySchema2.validate(req.query);
24396
+ if (error) {
24397
+ next(new import_utils125.BadRequestError(error.message));
24398
+ return;
24260
24399
  }
24261
- const session = import_utils126.useAtlas.getClient()?.startSession();
24262
- if (!session)
24263
- throw new import_utils125.BadRequestError("Failed to start session.");
24400
+ const itemId = req.query.itemId;
24401
+ const status2 = req.query.status ?? "";
24402
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
24403
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
24264
24404
  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();
24405
+ const results = await repo.getByItemId({ itemId, status: status2, page, limit });
24406
+ res.json(results);
24407
+ } catch (error2) {
24408
+ next(error2);
24289
24409
  }
24290
24410
  }
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
- );
24411
+ async function getById(req, res, next) {
24412
+ const { error } = idParamSchema3.validate(req.params);
24413
+ if (error) {
24414
+ next(new import_utils125.BadRequestError(error.message));
24415
+ return;
24296
24416
  }
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
24417
  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;
24418
+ const unit = await repo.getById(req.params.id);
24419
+ res.json(unit);
24420
+ } catch (error2) {
24421
+ next(error2);
24422
+ }
24423
+ }
24424
+ async function updateById(req, res, next) {
24425
+ const { error: paramError } = idParamSchema3.validate(req.params);
24426
+ if (paramError) {
24427
+ next(new import_utils125.BadRequestError(paramError.message));
24428
+ return;
24429
+ }
24430
+ const { error: bodyError } = schemaAssetUnitUpdate.validate(req.body);
24431
+ if (bodyError) {
24432
+ next(new import_utils125.BadRequestError(bodyError.message));
24433
+ return;
24434
+ }
24435
+ try {
24436
+ const updatedUnit = await repo.updateById(req.params.id, req.body);
24437
+ res.json({
24438
+ message: "Asset unit updated successfully.",
24439
+ unit: updatedUnit
24440
+ });
24316
24441
  } catch (error) {
24317
- await session.abortTransaction();
24318
- throw error;
24319
- } finally {
24320
- await session.endSession();
24442
+ next(error);
24321
24443
  }
24322
24444
  }
24323
- async function adjustment(value) {
24324
- if (!value.reason) {
24325
- throw new import_utils125.BadRequestError("Adjustment requires a reason.");
24445
+ return {
24446
+ add,
24447
+ getByItemId,
24448
+ getById,
24449
+ updateById
24450
+ };
24451
+ }
24452
+
24453
+ // src/resources/asset-item/asset.item.service.ts
24454
+ function normalizeTagName(name) {
24455
+ return name.trim().toLowerCase().replace(/\s+/g, "-");
24456
+ }
24457
+ function useAssetItemService() {
24458
+ const assetItemRepo = useAssetItemRepo();
24459
+ const tagRepo = useTagRepo();
24460
+ const categoryRepo = useCategoryRepo();
24461
+ const stockMovementRepo = useStockMovementRepo();
24462
+ const assetUnitRepo = useAssetUnitRepo();
24463
+ async function resolveTags(tagNames, orgId, categoryPath, session) {
24464
+ const resolvedTags = [];
24465
+ for (const tag of tagNames) {
24466
+ const normalizedName = normalizeTagName(tag.name);
24467
+ const existing = await tagRepo.findByNormalizedNameAndOrg(
24468
+ normalizedName,
24469
+ orgId
24470
+ );
24471
+ if (existing && existing._id) {
24472
+ await tagRepo.incrementUsageCount(existing._id, 1, session);
24473
+ resolvedTags.push({
24474
+ id: String(existing._id),
24475
+ name: existing.name
24476
+ });
24477
+ } else {
24478
+ const tagData = {
24479
+ name: tag.name.trim(),
24480
+ normalizedName,
24481
+ type: "private",
24482
+ orgId,
24483
+ categoryPath,
24484
+ status: "active",
24485
+ usageCount: 1
24486
+ };
24487
+ const tagId = await tagRepo.add(tagData, session);
24488
+ resolvedTags.push({
24489
+ id: String(tagId),
24490
+ name: tag.name.trim()
24491
+ });
24492
+ }
24326
24493
  }
24494
+ return resolvedTags;
24495
+ }
24496
+ async function add(value) {
24327
24497
  const session = import_utils126.useAtlas.getClient()?.startSession();
24328
- if (!session)
24329
- throw new import_utils125.BadRequestError("Failed to start session.");
24498
+ if (!session) {
24499
+ throw new import_utils126.InternalServerError(
24500
+ "Unable to start session for asset item service."
24501
+ );
24502
+ }
24330
24503
  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
24504
+ await session.startTransaction();
24505
+ const department = await categoryRepo.getById(value.departmentId);
24506
+ value.departmentName = department.displayName;
24507
+ if (value.categoryId) {
24508
+ const category = await categoryRepo.getById(value.categoryId);
24509
+ value.categoryName = category.displayName;
24510
+ }
24511
+ if (value.subcategoryId) {
24512
+ const subcategory = await categoryRepo.getById(value.subcategoryId);
24513
+ value.subcategoryName = subcategory.displayName;
24514
+ }
24515
+ const categoryPath = buildCategoryPath(
24516
+ value.departmentName,
24517
+ value.categoryName ?? "",
24518
+ value.subcategoryName ?? ""
24338
24519
  );
24520
+ value.categoryPath = categoryPath;
24521
+ const resolvedTags = value.tags.length ? await resolveTags(
24522
+ value.tags,
24523
+ value.orgId,
24524
+ value.categoryPath,
24525
+ session
24526
+ ) : [];
24527
+ const assetItem = {
24528
+ ...value,
24529
+ tags: resolvedTags
24530
+ };
24531
+ const assetItemId = await assetItemRepo.add(assetItem, session);
24339
24532
  await session.commitTransaction();
24340
- return movementId;
24533
+ return assetItemId;
24341
24534
  } catch (error) {
24342
24535
  await session.abortTransaction();
24343
- throw error;
24536
+ if (error instanceof import_utils126.AppError)
24537
+ throw error;
24538
+ throw new import_utils126.InternalServerError("Failed to create asset item.");
24344
24539
  } finally {
24345
- await session.endSession();
24540
+ session.endSession();
24346
24541
  }
24347
24542
  }
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.");
24543
+ async function makePublic(id, value) {
24544
+ try {
24545
+ await assetItemRepo.updateById(id, {
24546
+ isPublic: value,
24547
+ updatedAt: /* @__PURE__ */ new Date()
24548
+ });
24549
+ return "Asset item visibility updated successfully.";
24550
+ } catch (error) {
24551
+ if (error instanceof import_utils126.AppError)
24552
+ throw error;
24553
+ throw new import_utils126.InternalServerError("Failed to update asset item visibility.");
24352
24554
  }
24353
- if (sourceItem.trackingType === "quantity" && (sourceItem.quantityOnHand ?? 0) < quantity) {
24354
- throw new import_utils125.BadRequestError("Insufficient stock on source item.");
24555
+ }
24556
+ async function deleteById(id) {
24557
+ const [movementCount, unitCount] = await Promise.all([
24558
+ stockMovementRepo.countByItemId(id),
24559
+ assetUnitRepo.countByItemId(id)
24560
+ ]);
24561
+ if (movementCount > 0 || unitCount > 0) {
24562
+ throw new import_utils126.BadRequestError(
24563
+ "Cannot delete asset item because it has associated movement or unit records."
24564
+ );
24355
24565
  }
24356
- const session = import_utils126.useAtlas.getClient()?.startSession();
24357
- if (!session)
24358
- throw new import_utils125.BadRequestError("Failed to start session.");
24359
24566
  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 };
24567
+ await assetItemRepo.deleteById(id);
24568
+ return "Asset item deleted successfully.";
24392
24569
  } catch (error) {
24393
- await session.abortTransaction();
24394
- throw error;
24395
- } finally {
24396
- await session.endSession();
24570
+ if (error instanceof import_utils126.AppError)
24571
+ throw error;
24572
+ throw new import_utils126.InternalServerError("Failed to delete asset item.");
24397
24573
  }
24398
24574
  }
24399
24575
  return {
24400
- stockIn,
24401
- stockOut,
24402
- transfer,
24403
- adjustment,
24404
- conversion
24576
+ add,
24577
+ makePublic,
24578
+ deleteById
24405
24579
  };
24406
24580
  }
24407
24581
 
24408
- // src/resources/asset-item-movement/stock.movement.controller.ts
24582
+ // src/resources/asset-item/asset.item.controller.ts
24409
24583
  var import_joi104 = __toESM(require("joi"));
24410
24584
  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),
24585
+ var paginationSchema3 = import_joi104.default.object({
24414
24586
  page: import_joi104.default.number().min(1).optional().allow("", null),
24415
- limit: import_joi104.default.number().min(1).optional().allow("", null)
24587
+ limit: import_joi104.default.number().min(1).optional().allow("", null),
24588
+ search: import_joi104.default.string().optional().allow("", null),
24589
+ status: import_joi104.default.string().valid(...assetItemStatuses).optional().allow("", null),
24590
+ assetCategory: import_joi104.default.string().valid(...assetItemCategories).optional().allow("", null),
24591
+ trackingType: import_joi104.default.string().valid(...assetItemTrackingTypes).optional().allow("", null),
24592
+ purpose: import_joi104.default.string().valid(...assetItemPurposes).optional().allow("", null),
24593
+ departmentId: import_joi104.default.string().optional().allow("", null),
24594
+ categoryId: import_joi104.default.string().optional().allow("", null),
24595
+ subcategoryId: import_joi104.default.string().optional().allow("", null),
24596
+ brand: import_joi104.default.string().optional().allow("", null),
24597
+ tags: import_joi104.default.string().optional().allow("", null),
24598
+ public: import_joi104.default.boolean().optional().allow("", null)
24416
24599
  });
24417
24600
  var idParamSchema4 = import_joi104.default.object({
24418
24601
  id: import_joi104.default.string().hex().length(24).required()
24419
24602
  });
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) {
24603
+ function useAssetItemController() {
24604
+ const repo = useAssetItemRepo();
24605
+ const service = useAssetItemService();
24606
+ async function add(req, res, next) {
24501
24607
  try {
24502
- const { error } = stockInSchema.validate(req.body);
24608
+ const { error, value } = schemaAssetItemCreate.validate(req.body);
24503
24609
  if (error) {
24504
24610
  next(new import_utils127.BadRequestError(error.message));
24505
24611
  return;
24506
24612
  }
24507
- const { units, ...movement } = req.body;
24508
- const movementId = await service.stockIn(movement, units);
24613
+ const assetItemId = await service.add(value);
24509
24614
  res.status(201).json({
24510
- message: "Stock in recorded successfully.",
24511
- movementId
24615
+ message: "Asset item created successfully.",
24616
+ assetItemId
24512
24617
  });
24513
24618
  } catch (error) {
24514
24619
  next(error);
24515
24620
  }
24516
24621
  }
24517
- async function stockOut(req, res, next) {
24622
+ async function getAll(req, res, next) {
24623
+ const { error } = paginationSchema3.validate(req.query);
24624
+ if (error) {
24625
+ next(new import_utils127.BadRequestError(error.message));
24626
+ return;
24627
+ }
24628
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
24629
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
24630
+ const search = req.query.search ?? "";
24631
+ const status2 = req.query.status ?? "active";
24632
+ const assetCategory = req.query.assetCategory ?? "";
24633
+ const trackingType = req.query.trackingType ?? "";
24634
+ const purpose = req.query.purpose ?? "";
24635
+ const departmentId = req.query.departmentId ?? "";
24636
+ const categoryId = req.query.categoryId ?? "";
24637
+ const subcategoryId = req.query.subcategoryId ?? "";
24638
+ const brand = req.query.brand ?? "";
24639
+ const tagsParam = req.query.tags ?? "";
24640
+ const tags = tagsParam ? tagsParam.split(",").filter(Boolean) : [];
24641
+ const isPublic = req.query.public === "true";
24642
+ if (!isFinite(page)) {
24643
+ next(new import_utils127.BadRequestError("Invalid page number."));
24644
+ return;
24645
+ }
24646
+ if (!isFinite(limit)) {
24647
+ next(new import_utils127.BadRequestError("Invalid limit number."));
24648
+ return;
24649
+ }
24518
24650
  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
24651
+ const results = await repo.getAll({
24652
+ page,
24653
+ limit,
24654
+ search,
24655
+ status: status2,
24656
+ assetCategory,
24657
+ trackingType,
24658
+ purpose,
24659
+ departmentId,
24660
+ categoryId,
24661
+ subcategoryId,
24662
+ brand,
24663
+ tags,
24664
+ isPublic
24529
24665
  });
24530
- } catch (error) {
24531
- next(error);
24666
+ res.json(results);
24667
+ } catch (error2) {
24668
+ next(error2);
24532
24669
  }
24533
24670
  }
24534
- async function transfer(req, res, next) {
24671
+ async function getById(req, res, next) {
24672
+ const { error } = idParamSchema4.validate(req.params);
24673
+ if (error) {
24674
+ next(new import_utils127.BadRequestError(error.message));
24675
+ return;
24676
+ }
24535
24677
  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);
24678
+ const assetItem = await repo.getById(req.params.id);
24679
+ res.json(assetItem);
24680
+ } catch (error2) {
24681
+ next(error2);
24549
24682
  }
24550
24683
  }
24551
- async function adjustment(req, res, next) {
24684
+ async function updateById(req, res, next) {
24685
+ const { error: paramError } = import_joi104.default.object({
24686
+ id: import_joi104.default.string().hex().length(24).required(),
24687
+ orgId: import_joi104.default.string().hex().required()
24688
+ }).validate(req.params);
24689
+ if (paramError) {
24690
+ next(new import_utils127.BadRequestError(paramError.message));
24691
+ return;
24692
+ }
24693
+ const { error: bodyError } = schemaAssetItemUpdate.validate(req.body);
24694
+ if (bodyError) {
24695
+ next(new import_utils127.BadRequestError(bodyError.message));
24696
+ return;
24697
+ }
24552
24698
  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
24699
+ const updatedAssetItem = await repo.updateById(req.params.id, req.body);
24700
+ res.json({
24701
+ message: "Asset item updated successfully.",
24702
+ assetItem: updatedAssetItem
24562
24703
  });
24563
24704
  } catch (error) {
24564
24705
  next(error);
24565
24706
  }
24566
24707
  }
24567
- async function getByItemId(req, res, next) {
24568
- const { error } = querySchema2.validate(req.query);
24708
+ async function makePublic(req, res, next) {
24709
+ const { error, value } = import_joi104.default.object({
24710
+ orgId: import_joi104.default.string().hex().required(),
24711
+ id: import_joi104.default.string().hex().length(24).required(),
24712
+ isPublic: import_joi104.default.boolean().required()
24713
+ }).validate({ ...req.params, ...req.body });
24569
24714
  if (error) {
24570
24715
  next(new import_utils127.BadRequestError(error.message));
24571
24716
  return;
24572
24717
  }
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
24718
  try {
24578
- const results = await repo.getByItemId({ itemId, type, page, limit });
24579
- res.json(results);
24719
+ const message = await service.makePublic(value.id, value.isPublic);
24720
+ res.json({
24721
+ message
24722
+ });
24723
+ return;
24580
24724
  } catch (error2) {
24581
- next(error2);
24725
+ if (error2 instanceof import_utils127.AppError) {
24726
+ next(error2);
24727
+ return;
24728
+ }
24729
+ next(new import_utils127.InternalServerError("Failed to update asset item visibility."));
24582
24730
  }
24583
24731
  }
24584
- async function getById(req, res, next) {
24585
- const { error } = idParamSchema4.validate(req.params);
24732
+ async function deleteById(req, res, next) {
24733
+ const { error } = import_joi104.default.object({
24734
+ id: import_joi104.default.string().hex().length(24).required(),
24735
+ orgId: import_joi104.default.string().hex().required()
24736
+ }).validate(req.params);
24586
24737
  if (error) {
24587
24738
  next(new import_utils127.BadRequestError(error.message));
24588
24739
  return;
24589
24740
  }
24590
24741
  try {
24591
- const movement = await repo.getById(req.params.id);
24592
- res.json(movement);
24742
+ await service.deleteById(req.params.id);
24743
+ res.json({
24744
+ message: "Asset item deleted successfully."
24745
+ });
24593
24746
  } catch (error2) {
24594
24747
  next(error2);
24595
24748
  }
24596
24749
  }
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
24750
  return {
24616
- stockIn,
24617
- stockOut,
24618
- transfer,
24619
- adjustment,
24620
- conversion,
24621
- getByItemId,
24622
- getById
24751
+ add,
24752
+ getAll,
24753
+ getById,
24754
+ updateById,
24755
+ makePublic,
24756
+ deleteById
24623
24757
  };
24624
24758
  }
24625
24759