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