@goweekdays/core 2.12.3 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -11073,6 +11073,24 @@ function useOrgController() {
11073
11073
  companySearch: _companySearch
11074
11074
  } = useOrgRepo();
11075
11075
  const { addPilot: _addPilot } = useOrgService();
11076
+ async function validOrg(req, res, next) {
11077
+ const org = req.params.org ?? "";
11078
+ const { error } = Joi49.string().hex().required().validate(org);
11079
+ if (error) {
11080
+ next(new BadRequestError53("Invalid organization ID."));
11081
+ return;
11082
+ }
11083
+ try {
11084
+ const organization = await _getById(org);
11085
+ if (!organization) {
11086
+ next(new BadRequestError53("Organization not found."));
11087
+ return;
11088
+ }
11089
+ next();
11090
+ } catch (error2) {
11091
+ next(error2);
11092
+ }
11093
+ }
11076
11094
  async function add(req, res, next) {
11077
11095
  const value = req.body;
11078
11096
  const { error } = schemaOrgAdd.validate(value);
@@ -11246,6 +11264,7 @@ function useOrgController() {
11246
11264
  }
11247
11265
  }
11248
11266
  return {
11267
+ validOrg,
11249
11268
  add,
11250
11269
  getOrgsByUserId,
11251
11270
  getByName,
@@ -13798,10 +13817,10 @@ function useCounterRepo() {
13798
13817
  throw new Error("Failed to create index.");
13799
13818
  }
13800
13819
  }
13801
- async function add(type) {
13820
+ async function add(type, session) {
13802
13821
  try {
13803
13822
  const value = createCounter({ type });
13804
- await collection.insertOne(value);
13823
+ await collection.insertOne(value, { session });
13805
13824
  delCachedData();
13806
13825
  } catch (error) {
13807
13826
  throw new Error("Failed to add counter.");
@@ -13811,7 +13830,7 @@ function useCounterRepo() {
13811
13830
  try {
13812
13831
  const res = await collection.updateOne(
13813
13832
  { type },
13814
- { $inc: { count: 1 } },
13833
+ { $inc: { count: 1 }, $set: { updatedAt: /* @__PURE__ */ new Date() } },
13815
13834
  { session }
13816
13835
  );
13817
13836
  delCachedData();
@@ -17045,6 +17064,2349 @@ function useJobSummaryCtrl() {
17045
17064
  getByOrg
17046
17065
  };
17047
17066
  }
17067
+
17068
+ // src/resources/finance-tax/tax.model.ts
17069
+ import { BadRequestError as BadRequestError80 } from "@goweekdays/utils";
17070
+ import Joi69 from "joi";
17071
+ import { ObjectId as ObjectId40 } from "mongodb";
17072
+ var taxDirections = ["input", "output", "withholding"];
17073
+ var schemaTax = Joi69.object({
17074
+ org: Joi69.string().hex().required(),
17075
+ name: Joi69.string().required(),
17076
+ rate: Joi69.number().required(),
17077
+ direction: Joi69.string().valid(...taxDirections).required()
17078
+ });
17079
+ var schemaTaxUpdate = Joi69.object({
17080
+ name: Joi69.string().required(),
17081
+ rate: Joi69.number().required(),
17082
+ direction: Joi69.string().valid(...taxDirections).required()
17083
+ });
17084
+ function modelTax(data) {
17085
+ const { error } = schemaTax.validate(data);
17086
+ if (error) {
17087
+ throw new BadRequestError80(`Invalid tax data: ${error.message}`);
17088
+ }
17089
+ try {
17090
+ data.org = new ObjectId40(data.org);
17091
+ } catch (error2) {
17092
+ throw new BadRequestError80(`Invalid org ID: ${data.org}`);
17093
+ }
17094
+ return {
17095
+ _id: data._id,
17096
+ org: data.org,
17097
+ name: data.name,
17098
+ rate: data.rate,
17099
+ direction: data.direction,
17100
+ status: data.status ?? "active",
17101
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
17102
+ updatedAt: data.updatedAt ?? ""
17103
+ };
17104
+ }
17105
+
17106
+ // src/resources/finance-tax/tax.repository.ts
17107
+ import {
17108
+ AppError as AppError39,
17109
+ BadRequestError as BadRequestError81,
17110
+ useAtlas as useAtlas36,
17111
+ useCache as useCache27,
17112
+ makeCacheKey as makeCacheKey24,
17113
+ paginate as paginate21,
17114
+ logger as logger41,
17115
+ InternalServerError as InternalServerError36
17116
+ } from "@goweekdays/utils";
17117
+ import { ObjectId as ObjectId41 } from "mongodb";
17118
+ import Joi70 from "joi";
17119
+ function useTaxRepo() {
17120
+ const db = useAtlas36.getDb();
17121
+ if (!db) {
17122
+ throw new BadRequestError81("Unable to connect to server.");
17123
+ }
17124
+ const namespace_collection = "finance.taxes";
17125
+ const collection = db.collection(namespace_collection);
17126
+ const { getCache, setCache, delNamespace } = useCache27(namespace_collection);
17127
+ function delCachedData() {
17128
+ delNamespace().then(() => {
17129
+ logger41.log({
17130
+ level: "info",
17131
+ message: `Cache namespace cleared for ${namespace_collection}`
17132
+ });
17133
+ }).catch((err) => {
17134
+ logger41.log({
17135
+ level: "error",
17136
+ message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
17137
+ });
17138
+ });
17139
+ }
17140
+ async function createIndexes() {
17141
+ try {
17142
+ await collection.createIndexes([
17143
+ { key: { name: 1 } },
17144
+ { key: { org: 1 } },
17145
+ {
17146
+ key: { name: 1, org: 1 },
17147
+ partialFilterExpression: { status: "active" },
17148
+ unique: true,
17149
+ name: "unique_name_per_org"
17150
+ }
17151
+ ]);
17152
+ } catch (error) {
17153
+ throw new Error("Failed to create index on tax.");
17154
+ }
17155
+ }
17156
+ async function add(value, session) {
17157
+ try {
17158
+ value = modelTax(value);
17159
+ const res = await collection.insertOne(value, { session });
17160
+ delCachedData();
17161
+ return res.insertedId;
17162
+ } catch (error) {
17163
+ logger41.log({
17164
+ level: "error",
17165
+ message: error.message
17166
+ });
17167
+ throw new BadRequestError81(`Failed to create tax: ${error.message}`);
17168
+ }
17169
+ }
17170
+ async function getAll(options) {
17171
+ options.page = options.page && options.page > 0 ? options.page - 1 : 0;
17172
+ options.status = options.status ?? "active";
17173
+ options.limit = options.limit ?? 10;
17174
+ const query = { status: options.status };
17175
+ try {
17176
+ query.org = new ObjectId41(options.org);
17177
+ } catch (error) {
17178
+ throw new BadRequestError81("Invalid organization ID.");
17179
+ }
17180
+ if (options.search) {
17181
+ query.$text = { $search: options.search };
17182
+ }
17183
+ const cacheKey = makeCacheKey24(namespace_collection, {
17184
+ search: options.search,
17185
+ page: options.page,
17186
+ limit: options.limit,
17187
+ status: options.status
17188
+ });
17189
+ logger41.log({
17190
+ level: "info",
17191
+ message: `Cache key for getAll taxes: ${cacheKey}`
17192
+ });
17193
+ try {
17194
+ const cached = await getCache(cacheKey);
17195
+ if (cached) {
17196
+ logger41.log({
17197
+ level: "info",
17198
+ message: `Cache hit for getAll taxes: ${cacheKey}`
17199
+ });
17200
+ return cached;
17201
+ }
17202
+ const items = await collection.aggregate([
17203
+ { $match: query },
17204
+ { $skip: options.page * options.limit },
17205
+ { $limit: options.limit },
17206
+ {
17207
+ $project: {
17208
+ _id: 1,
17209
+ org: 1,
17210
+ name: 1,
17211
+ rate: 1,
17212
+ direction: 1,
17213
+ status: 1,
17214
+ createdAt: 1
17215
+ }
17216
+ }
17217
+ ]).toArray();
17218
+ const length = await collection.countDocuments(query);
17219
+ const data = paginate21(items, options.page, options.limit, length);
17220
+ setCache(cacheKey, data, 600).then(() => {
17221
+ logger41.log({
17222
+ level: "info",
17223
+ message: `Cache set for getAll taxes: ${cacheKey}`
17224
+ });
17225
+ }).catch((err) => {
17226
+ logger41.log({
17227
+ level: "error",
17228
+ message: `Failed to set cache for getAll taxes: ${err.message}`
17229
+ });
17230
+ });
17231
+ return data;
17232
+ } catch (error) {
17233
+ logger41.log({ level: "error", message: `${error}` });
17234
+ throw error;
17235
+ }
17236
+ }
17237
+ async function getTaxesByOrg({ search = "", page = 1, limit = 10, org = "", status: status2 = "active" } = {}) {
17238
+ page = page > 0 ? page - 1 : 0;
17239
+ try {
17240
+ org = new ObjectId41(org);
17241
+ } catch (error) {
17242
+ throw new BadRequestError81("Invalid organization ID.");
17243
+ }
17244
+ const query = { org, status: status2 };
17245
+ const cacheKeyOptions = {
17246
+ org: String(org),
17247
+ status: status2,
17248
+ limit,
17249
+ page
17250
+ };
17251
+ if (search) {
17252
+ cacheKeyOptions.search = search;
17253
+ query.$text = { $search: search };
17254
+ }
17255
+ try {
17256
+ const cacheKey = makeCacheKey24(namespace_collection, cacheKeyOptions);
17257
+ const cached = await getCache(
17258
+ cacheKey
17259
+ );
17260
+ if (cached) {
17261
+ logger41.log({
17262
+ level: "info",
17263
+ message: `Cache hit for getTaxesByOrg : ${cacheKey}`
17264
+ });
17265
+ return cached;
17266
+ }
17267
+ const items = await collection.aggregate([
17268
+ { $match: query },
17269
+ { $skip: page * limit },
17270
+ { $limit: limit },
17271
+ {
17272
+ $project: {
17273
+ _id: 1,
17274
+ name: 1,
17275
+ rate: 1,
17276
+ direction: 1,
17277
+ status: 1,
17278
+ createdAt: 1
17279
+ }
17280
+ }
17281
+ ]).toArray();
17282
+ const length = await collection.countDocuments(query);
17283
+ const data = paginate21(items, page, limit, length);
17284
+ setCache(cacheKey, data, 300).then(() => {
17285
+ logger41.log({
17286
+ level: "info",
17287
+ message: `Cache set for getTaxesByOrg: ${cacheKey}`
17288
+ });
17289
+ }).catch((err) => {
17290
+ logger41.log({
17291
+ level: "error",
17292
+ message: `Failed to set cache for getTaxesByOrg: ${err.message}`
17293
+ });
17294
+ });
17295
+ return data;
17296
+ } catch (error) {
17297
+ throw new InternalServerError36(
17298
+ "Internal server error, failed to retrieve taxes."
17299
+ );
17300
+ }
17301
+ }
17302
+ async function getById(_id) {
17303
+ try {
17304
+ _id = new ObjectId41(_id);
17305
+ } catch (error) {
17306
+ throw new BadRequestError81("Invalid ID.");
17307
+ }
17308
+ const cacheKey = makeCacheKey24(namespace_collection, { _id: String(_id) });
17309
+ try {
17310
+ const cached = await getCache(cacheKey);
17311
+ if (cached) {
17312
+ logger41.log({
17313
+ level: "info",
17314
+ message: `Cache hit for getById tax: ${cacheKey}`
17315
+ });
17316
+ return cached;
17317
+ }
17318
+ const result = await collection.findOne({ _id });
17319
+ if (!result) {
17320
+ throw new BadRequestError81("Tax not found.");
17321
+ }
17322
+ setCache(cacheKey, result, 300).then(() => {
17323
+ logger41.log({
17324
+ level: "info",
17325
+ message: `Cache set for tax by id: ${cacheKey}`
17326
+ });
17327
+ }).catch((err) => {
17328
+ logger41.log({
17329
+ level: "error",
17330
+ message: `Failed to set cache for tax by id: ${err.message}`
17331
+ });
17332
+ });
17333
+ return result;
17334
+ } catch (error) {
17335
+ if (error instanceof AppError39) {
17336
+ throw error;
17337
+ } else {
17338
+ throw new InternalServerError36("Failed to get tax.");
17339
+ }
17340
+ }
17341
+ }
17342
+ async function updateById(_id, options) {
17343
+ const { error: errorId } = Joi70.string().hex().required().validate(String(_id));
17344
+ if (errorId) {
17345
+ throw new BadRequestError81("Invalid Tax ID.");
17346
+ }
17347
+ const { error } = schemaTaxUpdate.validate(options);
17348
+ if (error) {
17349
+ throw new BadRequestError81(`Invalid tax update data: ${error.message}`);
17350
+ }
17351
+ try {
17352
+ _id = new ObjectId41(_id);
17353
+ } catch (error2) {
17354
+ throw new BadRequestError81("Invalid Tax ID.");
17355
+ }
17356
+ try {
17357
+ await collection.updateOne(
17358
+ { _id },
17359
+ { $set: { ...options, updatedAt: /* @__PURE__ */ new Date() } }
17360
+ );
17361
+ delCachedData();
17362
+ return "Successfully updated tax.";
17363
+ } catch (error2) {
17364
+ throw new InternalServerError36("Failed to update tax.");
17365
+ }
17366
+ }
17367
+ async function deleteById(_id, session) {
17368
+ try {
17369
+ _id = new ObjectId41(_id);
17370
+ } catch (error) {
17371
+ throw new BadRequestError81("Invalid ID.");
17372
+ }
17373
+ try {
17374
+ await collection.updateOne(
17375
+ { _id },
17376
+ {
17377
+ $set: {
17378
+ status: "deleted",
17379
+ updatedAt: /* @__PURE__ */ new Date(),
17380
+ deletedAt: /* @__PURE__ */ new Date()
17381
+ }
17382
+ },
17383
+ { session }
17384
+ );
17385
+ delCachedData();
17386
+ return "Successfully deleted tax.";
17387
+ } catch (error) {
17388
+ throw new InternalServerError36("Failed to delete tax.");
17389
+ }
17390
+ }
17391
+ async function updateStatusById(_id, status2) {
17392
+ try {
17393
+ _id = new ObjectId41(_id);
17394
+ } catch (error) {
17395
+ throw new BadRequestError81("Invalid ID.");
17396
+ }
17397
+ try {
17398
+ const result = await collection.updateOne(
17399
+ { _id },
17400
+ {
17401
+ $set: {
17402
+ status: status2,
17403
+ updatedAt: /* @__PURE__ */ new Date()
17404
+ }
17405
+ }
17406
+ );
17407
+ if (result.matchedCount === 0) {
17408
+ throw new BadRequestError81("Tax not found.");
17409
+ }
17410
+ delCachedData();
17411
+ return "Successfully updated tax status.";
17412
+ } catch (error) {
17413
+ if (error instanceof AppError39) {
17414
+ throw error;
17415
+ }
17416
+ throw new InternalServerError36("Failed to update tax status.");
17417
+ }
17418
+ }
17419
+ return {
17420
+ createIndexes,
17421
+ add,
17422
+ getAll,
17423
+ getTaxesByOrg,
17424
+ getById,
17425
+ updateById,
17426
+ deleteById,
17427
+ updateStatusById
17428
+ };
17429
+ }
17430
+
17431
+ // src/resources/finance-tax/tax.controller.ts
17432
+ import Joi71 from "joi";
17433
+ import { BadRequestError as BadRequestError82, logger as logger42 } from "@goweekdays/utils";
17434
+ function useTaxController() {
17435
+ const {
17436
+ getAll: _getAll,
17437
+ getTaxesByOrg: _getTaxesByOrg,
17438
+ getById: _getById,
17439
+ updateById: _updateById,
17440
+ deleteById: _deleteById,
17441
+ updateStatusById: _updateStatusById,
17442
+ add: _add
17443
+ } = useTaxRepo();
17444
+ async function add(req, res, next) {
17445
+ const value = req.body;
17446
+ const { error } = schemaTax.validate(value);
17447
+ if (error) {
17448
+ next(new BadRequestError82(error.message));
17449
+ logger42.info(`Controller: ${error.message}`);
17450
+ return;
17451
+ }
17452
+ try {
17453
+ const message = await _add(value);
17454
+ res.json({ message });
17455
+ return;
17456
+ } catch (error2) {
17457
+ next(error2);
17458
+ }
17459
+ }
17460
+ async function getAll(req, res, next) {
17461
+ const query = req.query;
17462
+ const validation = Joi71.object({
17463
+ org: Joi71.string().hex().required(),
17464
+ page: Joi71.number().min(1).optional().allow("", null),
17465
+ limit: Joi71.number().min(1).optional().allow("", null),
17466
+ search: Joi71.string().optional().allow("", null),
17467
+ status: Joi71.string().optional()
17468
+ });
17469
+ const org = req.params.org ?? "";
17470
+ const { error } = validation.validate({ ...query, org });
17471
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
17472
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
17473
+ const search = req.query.search ?? "";
17474
+ const status2 = req.query.status ?? "active";
17475
+ const isPageNumber = isFinite(page);
17476
+ if (!isPageNumber) {
17477
+ next(new BadRequestError82("Invalid page number."));
17478
+ return;
17479
+ }
17480
+ const isLimitNumber = isFinite(limit);
17481
+ if (!isLimitNumber) {
17482
+ next(new BadRequestError82("Invalid limit number."));
17483
+ return;
17484
+ }
17485
+ if (error) {
17486
+ next(new BadRequestError82(error.message));
17487
+ return;
17488
+ }
17489
+ try {
17490
+ const taxes = await _getAll({ org, page, limit, search, status: status2 });
17491
+ res.json(taxes);
17492
+ return;
17493
+ } catch (error2) {
17494
+ next(error2);
17495
+ }
17496
+ }
17497
+ async function getTaxesByOrg(req, res, next) {
17498
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
17499
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
17500
+ const search = req.query.search ?? "";
17501
+ const status2 = req.query.status ?? "active";
17502
+ const org = req.params.org ?? "";
17503
+ const isPageNumber = isFinite(page);
17504
+ if (!isPageNumber) {
17505
+ next(new BadRequestError82("Invalid page number."));
17506
+ return;
17507
+ }
17508
+ const isLimitNumber = isFinite(limit);
17509
+ if (!isLimitNumber) {
17510
+ next(new BadRequestError82("Invalid limit number."));
17511
+ return;
17512
+ }
17513
+ const validation = Joi71.object({
17514
+ org: Joi71.string().hex().required(),
17515
+ page: Joi71.number().min(1).optional().allow("", null),
17516
+ limit: Joi71.number().min(1).optional().allow("", null),
17517
+ search: Joi71.string().optional().allow("", null),
17518
+ status: Joi71.string().optional()
17519
+ });
17520
+ const { error } = validation.validate({ org, page, limit, search, status: status2 });
17521
+ if (error) {
17522
+ next(new BadRequestError82(error.message));
17523
+ return;
17524
+ }
17525
+ try {
17526
+ const taxes = await _getTaxesByOrg({ org, page, limit, search, status: status2 });
17527
+ res.json(taxes);
17528
+ return;
17529
+ } catch (error2) {
17530
+ next(error2);
17531
+ }
17532
+ }
17533
+ async function getById(req, res, next) {
17534
+ const id = req.params.id;
17535
+ const validation = Joi71.object({
17536
+ id: Joi71.string().hex().required()
17537
+ });
17538
+ const { error } = validation.validate({ id });
17539
+ if (error) {
17540
+ next(new BadRequestError82(error.message));
17541
+ return;
17542
+ }
17543
+ try {
17544
+ const tax = await _getById(id);
17545
+ res.json(tax);
17546
+ return;
17547
+ } catch (error2) {
17548
+ next(error2);
17549
+ }
17550
+ }
17551
+ async function updateById(req, res, next) {
17552
+ const _id = req.params.id ?? "";
17553
+ const { error: errorId } = Joi71.string().hex().required().validate(_id);
17554
+ if (errorId) {
17555
+ next(new BadRequestError82("Invalid Tax ID."));
17556
+ return;
17557
+ }
17558
+ const payload = req.body;
17559
+ const { error } = schemaTaxUpdate.validate(payload);
17560
+ if (error) {
17561
+ next(new BadRequestError82(`Invalid tax update data: ${error.message}`));
17562
+ return;
17563
+ }
17564
+ try {
17565
+ const message = await _updateById(_id, payload);
17566
+ res.json({ message });
17567
+ return;
17568
+ } catch (error2) {
17569
+ next(error2);
17570
+ }
17571
+ }
17572
+ async function deleteById(req, res, next) {
17573
+ const id = req.params.id;
17574
+ if (!id) {
17575
+ next(new BadRequestError82("Tax ID is required."));
17576
+ return;
17577
+ }
17578
+ try {
17579
+ const message = await _deleteById(id);
17580
+ res.json(message);
17581
+ return;
17582
+ } catch (error) {
17583
+ next(error);
17584
+ }
17585
+ }
17586
+ async function updateStatusById(req, res, next) {
17587
+ const id = req.params.id;
17588
+ const status2 = req.params.status;
17589
+ const validation = Joi71.object({
17590
+ id: Joi71.string().hex().required(),
17591
+ status: Joi71.string().required()
17592
+ });
17593
+ const { error } = validation.validate({ id, status: status2 });
17594
+ if (error) {
17595
+ next(new BadRequestError82(error.message));
17596
+ return;
17597
+ }
17598
+ try {
17599
+ const message = await _updateStatusById(id, status2);
17600
+ res.json({ message });
17601
+ return;
17602
+ } catch (error2) {
17603
+ next(error2);
17604
+ }
17605
+ }
17606
+ return {
17607
+ add,
17608
+ getAll,
17609
+ getTaxesByOrg,
17610
+ getById,
17611
+ updateById,
17612
+ deleteById,
17613
+ updateStatusById
17614
+ };
17615
+ }
17616
+
17617
+ // src/resources/finance-account/chart-of-account.model.ts
17618
+ import Joi72 from "joi";
17619
+ import { ObjectId as ObjectId42 } from "mongodb";
17620
+ var chartOfAccountTypes = [
17621
+ "asset",
17622
+ "liability",
17623
+ "equity",
17624
+ "income",
17625
+ "expense"
17626
+ ];
17627
+ var chartOfAccountNormalBalances = [
17628
+ "debit",
17629
+ "credit"
17630
+ ];
17631
+ var schemaChartOfAccount = {
17632
+ type: Joi72.string().valid(...chartOfAccountTypes).required(),
17633
+ normalBalance: Joi72.string().valid(...chartOfAccountNormalBalances).required(),
17634
+ parent: Joi72.string().hex().optional().allow("", null),
17635
+ name: Joi72.string().required(),
17636
+ code: Joi72.string().required(),
17637
+ tax: Joi72.string().hex().optional().allow("", null)
17638
+ };
17639
+ var schemaChartOfAccountBase = Joi72.object({
17640
+ ...schemaChartOfAccount,
17641
+ org: Joi72.string().hex().required(),
17642
+ isContra: Joi72.boolean().required()
17643
+ });
17644
+ var schemaChartOfAccountStd = Joi72.object({
17645
+ ...schemaChartOfAccount,
17646
+ org: Joi72.string().hex().required(),
17647
+ isContra: Joi72.boolean().required(),
17648
+ path: Joi72.string().required(),
17649
+ parentName: Joi72.string().optional().allow("", null)
17650
+ });
17651
+ var schemaChartOfAccountUpdate = Joi72.object({
17652
+ ...schemaChartOfAccount,
17653
+ isContra: Joi72.boolean().required()
17654
+ });
17655
+ function modelChartOfAccount(data) {
17656
+ const { error } = schemaChartOfAccountStd.validate(data);
17657
+ if (error) {
17658
+ throw new Error(`Invalid chart of account data: ${error.message}`);
17659
+ }
17660
+ try {
17661
+ data.org = new ObjectId42(data.org);
17662
+ } catch (error2) {
17663
+ throw new Error(`Invalid org ID: ${data.org}`);
17664
+ }
17665
+ if (data.parent) {
17666
+ try {
17667
+ data.parent = new ObjectId42(data.parent);
17668
+ } catch (error2) {
17669
+ throw new Error(`Invalid parent ID: ${data.parent}`);
17670
+ }
17671
+ }
17672
+ if (data.tax) {
17673
+ try {
17674
+ data.tax = new ObjectId42(data.tax);
17675
+ } catch (error2) {
17676
+ throw new Error(`Invalid tax ID: ${data.tax}`);
17677
+ }
17678
+ }
17679
+ return {
17680
+ _id: data._id,
17681
+ org: data.org,
17682
+ type: data.type,
17683
+ normalBalance: data.normalBalance,
17684
+ parent: data.parent,
17685
+ parentName: data.parentName ?? "",
17686
+ path: data.path,
17687
+ name: data.name,
17688
+ code: data.code,
17689
+ tax: data.tax,
17690
+ isContra: data.isContra,
17691
+ status: data.status ?? "active",
17692
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
17693
+ updatedAt: data.updatedAt ?? ""
17694
+ };
17695
+ }
17696
+
17697
+ // src/resources/finance-account/chart-of-account.repository.ts
17698
+ import {
17699
+ AppError as AppError40,
17700
+ BadRequestError as BadRequestError83,
17701
+ useAtlas as useAtlas37,
17702
+ useCache as useCache28,
17703
+ makeCacheKey as makeCacheKey25,
17704
+ paginate as paginate22,
17705
+ logger as logger43,
17706
+ InternalServerError as InternalServerError37
17707
+ } from "@goweekdays/utils";
17708
+ import { ObjectId as ObjectId43 } from "mongodb";
17709
+ import Joi73 from "joi";
17710
+ function useChartOfAccountRepo() {
17711
+ const db = useAtlas37.getDb();
17712
+ if (!db) {
17713
+ throw new BadRequestError83("Unable to connect to server.");
17714
+ }
17715
+ const namespace_collection = "finance.accounts";
17716
+ const collection = db.collection(namespace_collection);
17717
+ const { getCache, setCache, delNamespace } = useCache28(namespace_collection);
17718
+ function delCachedData() {
17719
+ delNamespace().then(() => {
17720
+ logger43.log({
17721
+ level: "info",
17722
+ message: `Cache namespace cleared for ${namespace_collection}`
17723
+ });
17724
+ }).catch((err) => {
17725
+ logger43.log({
17726
+ level: "error",
17727
+ message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
17728
+ });
17729
+ });
17730
+ }
17731
+ async function createIndexes() {
17732
+ try {
17733
+ await collection.createIndexes([
17734
+ { key: { name: 1 } },
17735
+ { key: { org: 1 } },
17736
+ { key: { code: 1 } },
17737
+ { key: { path: 1 } },
17738
+ { key: { name: 1, org: 1 }, unique: true, name: "unique_name_per_org" },
17739
+ { key: { code: 1, org: 1 }, unique: true, name: "unique_code_per_org" }
17740
+ ]);
17741
+ } catch (error) {
17742
+ throw new Error("Failed to create index on chart of account.");
17743
+ }
17744
+ }
17745
+ async function add(value, session) {
17746
+ try {
17747
+ value = modelChartOfAccount(value);
17748
+ const res = await collection.insertOne(value, { session });
17749
+ delCachedData();
17750
+ return res.insertedId;
17751
+ } catch (error) {
17752
+ logger43.log({
17753
+ level: "error",
17754
+ message: error.message
17755
+ });
17756
+ throw new BadRequestError83(
17757
+ `Failed to create chart of account: ${error.message}`
17758
+ );
17759
+ }
17760
+ }
17761
+ async function getAll(options) {
17762
+ const validation = Joi73.object({
17763
+ org: Joi73.string().hex().required(),
17764
+ page: Joi73.number().min(1).optional().allow("", null),
17765
+ limit: Joi73.number().min(1).optional().allow("", null),
17766
+ search: Joi73.string().optional().allow("", null),
17767
+ status: Joi73.string().optional().allow("", null)
17768
+ });
17769
+ const { error } = validation.validate(options);
17770
+ if (error) {
17771
+ throw new BadRequestError83(error.message);
17772
+ }
17773
+ options.page = options.page && options.page > 0 ? options.page - 1 : 0;
17774
+ options.status = options.status ?? "active";
17775
+ options.limit = options.limit ?? 10;
17776
+ const query = { status: options.status };
17777
+ try {
17778
+ query.org = new ObjectId43(options.org);
17779
+ } catch (error2) {
17780
+ throw new BadRequestError83("Invalid organization ID.");
17781
+ }
17782
+ if (options.search) {
17783
+ query.$text = { $search: options.search };
17784
+ }
17785
+ const cacheKey = makeCacheKey25(namespace_collection, {
17786
+ search: options.search,
17787
+ page: options.page,
17788
+ limit: options.limit,
17789
+ status: options.status
17790
+ });
17791
+ logger43.log({
17792
+ level: "info",
17793
+ message: `Cache key for getAll chart of accounts: ${cacheKey}`
17794
+ });
17795
+ try {
17796
+ const cached = await getCache(cacheKey);
17797
+ if (cached) {
17798
+ logger43.log({
17799
+ level: "info",
17800
+ message: `Cache hit for getAll chart of accounts: ${cacheKey}`
17801
+ });
17802
+ return cached;
17803
+ }
17804
+ const items = await collection.aggregate([
17805
+ { $match: query },
17806
+ { $sort: { path: 1 } },
17807
+ { $skip: options.page * options.limit },
17808
+ { $limit: options.limit },
17809
+ {
17810
+ $project: {
17811
+ _id: 1,
17812
+ type: 1,
17813
+ normalBalance: 1,
17814
+ parent: 1,
17815
+ parentName: 1,
17816
+ path: 1,
17817
+ name: 1,
17818
+ code: 1,
17819
+ tax: 1,
17820
+ status: 1
17821
+ }
17822
+ }
17823
+ ]).toArray();
17824
+ const length = await collection.countDocuments(query);
17825
+ const data = paginate22(items, options.page, options.limit, length);
17826
+ setCache(cacheKey, data, 600).then(() => {
17827
+ logger43.log({
17828
+ level: "info",
17829
+ message: `Cache set for getAll chart of accounts: ${cacheKey}`
17830
+ });
17831
+ }).catch((err) => {
17832
+ logger43.log({
17833
+ level: "error",
17834
+ message: `Failed to set cache for getAll chart of accounts: ${err.message}`
17835
+ });
17836
+ });
17837
+ return data;
17838
+ } catch (error2) {
17839
+ logger43.log({ level: "error", message: `${error2}` });
17840
+ throw error2;
17841
+ }
17842
+ }
17843
+ async function getByOrg({ search = "", page = 1, limit = 10, org = "", status: status2 = "active" } = {}) {
17844
+ page = page > 0 ? page - 1 : 0;
17845
+ try {
17846
+ org = new ObjectId43(org);
17847
+ } catch (error) {
17848
+ throw new BadRequestError83("Invalid organization ID.");
17849
+ }
17850
+ const query = { org, status: status2 };
17851
+ const cacheKeyOptions = {
17852
+ org: String(org),
17853
+ status: status2,
17854
+ limit,
17855
+ page
17856
+ };
17857
+ if (search) {
17858
+ cacheKeyOptions.search = search;
17859
+ query.$text = { $search: search };
17860
+ }
17861
+ try {
17862
+ const cacheKey = makeCacheKey25(namespace_collection, cacheKeyOptions);
17863
+ const cached = await getCache(
17864
+ cacheKey
17865
+ );
17866
+ if (cached) {
17867
+ logger43.log({
17868
+ level: "info",
17869
+ message: `Cache hit for getByOrg: ${cacheKey}`
17870
+ });
17871
+ return cached;
17872
+ }
17873
+ const items = await collection.aggregate([
17874
+ { $match: query },
17875
+ { $skip: page * limit },
17876
+ { $limit: limit },
17877
+ {
17878
+ $project: {
17879
+ _id: 1,
17880
+ org: 1,
17881
+ type: 1,
17882
+ normalBalance: 1,
17883
+ parent: 1,
17884
+ name: 1,
17885
+ code: 1,
17886
+ tax: 1,
17887
+ status: 1,
17888
+ createdAt: 1,
17889
+ updatedAt: 1
17890
+ }
17891
+ }
17892
+ ]).toArray();
17893
+ const length = await collection.countDocuments(query);
17894
+ const data = paginate22(items, page, limit, length);
17895
+ setCache(cacheKey, data, 300).then(() => {
17896
+ logger43.log({
17897
+ level: "info",
17898
+ message: `Cache set for getByOrg: ${cacheKey}`
17899
+ });
17900
+ }).catch((err) => {
17901
+ logger43.log({
17902
+ level: "error",
17903
+ message: `Failed to set cache for getByOrg: ${err.message}`
17904
+ });
17905
+ });
17906
+ return data;
17907
+ } catch (error) {
17908
+ throw new InternalServerError37(
17909
+ "Internal server error, failed to retrieve chart of accounts."
17910
+ );
17911
+ }
17912
+ }
17913
+ async function getById(_id) {
17914
+ try {
17915
+ _id = new ObjectId43(_id);
17916
+ } catch (error) {
17917
+ throw new BadRequestError83("Invalid ID.");
17918
+ }
17919
+ const cacheKey = makeCacheKey25(namespace_collection, { _id: String(_id) });
17920
+ try {
17921
+ const cached = await getCache(cacheKey);
17922
+ if (cached) {
17923
+ logger43.log({
17924
+ level: "info",
17925
+ message: `Cache hit for getById chart of account: ${cacheKey}`
17926
+ });
17927
+ return cached;
17928
+ }
17929
+ const result = await collection.findOne({ _id });
17930
+ if (!result) {
17931
+ throw new BadRequestError83("Chart of account not found.");
17932
+ }
17933
+ setCache(cacheKey, result, 300).then(() => {
17934
+ logger43.log({
17935
+ level: "info",
17936
+ message: `Cache set for chart of account by id: ${cacheKey}`
17937
+ });
17938
+ }).catch((err) => {
17939
+ logger43.log({
17940
+ level: "error",
17941
+ message: `Failed to set cache for chart of account by id: ${err.message}`
17942
+ });
17943
+ });
17944
+ return result;
17945
+ } catch (error) {
17946
+ if (error instanceof AppError40) {
17947
+ throw error;
17948
+ } else {
17949
+ throw new InternalServerError37("Failed to get chart of account.");
17950
+ }
17951
+ }
17952
+ }
17953
+ async function updateById(_id, options, session) {
17954
+ try {
17955
+ _id = new ObjectId43(_id);
17956
+ } catch (error) {
17957
+ throw new BadRequestError83("Invalid Chart of Account ID.");
17958
+ }
17959
+ try {
17960
+ await collection.updateOne(
17961
+ { _id },
17962
+ { $set: { ...options, updatedAt: /* @__PURE__ */ new Date() } }
17963
+ );
17964
+ delCachedData();
17965
+ return "Successfully updated chart of account.";
17966
+ } catch (error) {
17967
+ throw new InternalServerError37("Failed to update chart of account.");
17968
+ }
17969
+ }
17970
+ async function deleteById(_id, session) {
17971
+ try {
17972
+ _id = new ObjectId43(_id);
17973
+ } catch (error) {
17974
+ throw new BadRequestError83("Invalid ID.");
17975
+ }
17976
+ try {
17977
+ await collection.updateOne(
17978
+ { _id },
17979
+ {
17980
+ $set: {
17981
+ status: "archived",
17982
+ updatedAt: /* @__PURE__ */ new Date(),
17983
+ deletedAt: /* @__PURE__ */ new Date()
17984
+ }
17985
+ },
17986
+ { session }
17987
+ );
17988
+ delCachedData();
17989
+ return "Successfully archived chart of account.";
17990
+ } catch (error) {
17991
+ throw new InternalServerError37("Failed to archive chart of account.");
17992
+ }
17993
+ }
17994
+ async function updateStatusById(_id, status2) {
17995
+ try {
17996
+ _id = new ObjectId43(_id);
17997
+ } catch (error) {
17998
+ throw new BadRequestError83("Invalid ID.");
17999
+ }
18000
+ try {
18001
+ const result = await collection.updateOne(
18002
+ { _id },
18003
+ {
18004
+ $set: {
18005
+ status: status2,
18006
+ updatedAt: /* @__PURE__ */ new Date()
18007
+ }
18008
+ }
18009
+ );
18010
+ if (result.matchedCount === 0) {
18011
+ throw new BadRequestError83("Chart of account not found.");
18012
+ }
18013
+ delCachedData();
18014
+ return "Successfully updated chart of account status.";
18015
+ } catch (error) {
18016
+ if (error instanceof AppError40) {
18017
+ throw error;
18018
+ }
18019
+ throw new InternalServerError37(
18020
+ "Failed to update chart of account status."
18021
+ );
18022
+ }
18023
+ }
18024
+ async function countByPath(path) {
18025
+ const { error } = Joi73.string().required().validate(path);
18026
+ if (error) {
18027
+ throw new BadRequestError83("Path is required and must be a string.");
18028
+ }
18029
+ try {
18030
+ const count = await collection.countDocuments({
18031
+ path: { $regex: `^${path}(-|$)` }
18032
+ });
18033
+ return count;
18034
+ } catch (error2) {
18035
+ throw new InternalServerError37(
18036
+ "Failed to count chart of accounts by path."
18037
+ );
18038
+ }
18039
+ }
18040
+ return {
18041
+ createIndexes,
18042
+ add,
18043
+ getAll,
18044
+ getByOrg,
18045
+ getById,
18046
+ updateById,
18047
+ deleteById,
18048
+ updateStatusById,
18049
+ countByPath
18050
+ };
18051
+ }
18052
+
18053
+ // src/resources/finance-account/chart-of-account.controller.ts
18054
+ import Joi75 from "joi";
18055
+ import { BadRequestError as BadRequestError85, logger as logger44 } from "@goweekdays/utils";
18056
+
18057
+ // src/resources/finance-account/chart-of-account.service.ts
18058
+ import { BadRequestError as BadRequestError84, useAtlas as useAtlas38 } from "@goweekdays/utils";
18059
+ import Joi74 from "joi";
18060
+ function useChartOfAccountService() {
18061
+ const {
18062
+ add: _add,
18063
+ getById: _getById,
18064
+ updateById: _updateById,
18065
+ deleteById: _deleteById,
18066
+ countByPath
18067
+ } = useChartOfAccountRepo();
18068
+ async function add(value) {
18069
+ const { error } = schemaChartOfAccountBase.validate(value);
18070
+ if (error) {
18071
+ throw new BadRequestError84(error.message);
18072
+ }
18073
+ const session = useAtlas38.getClient()?.startSession();
18074
+ if (!session) {
18075
+ throw new Error("Failed to start database session.");
18076
+ }
18077
+ try {
18078
+ session.startTransaction();
18079
+ const data = {
18080
+ org: value.org,
18081
+ name: value.name,
18082
+ code: value.code,
18083
+ type: value.type,
18084
+ normalBalance: value.normalBalance,
18085
+ parent: value.parent,
18086
+ path: value.code,
18087
+ tax: value.tax,
18088
+ isContra: value.isContra
18089
+ };
18090
+ let parentAccount = null;
18091
+ if (value.parent) {
18092
+ parentAccount = await _getById(value.parent);
18093
+ if (!parentAccount) {
18094
+ throw new BadRequestError84("Parent account not found.");
18095
+ }
18096
+ data.path = `${parentAccount.path}-${data.code}`;
18097
+ data.parentName = parentAccount.name;
18098
+ data.type = parentAccount.type;
18099
+ data.normalBalance = parentAccount.normalBalance;
18100
+ }
18101
+ const message = await _add(data, session);
18102
+ await session.commitTransaction();
18103
+ return message;
18104
+ } catch (error2) {
18105
+ await session.abortTransaction();
18106
+ throw error2;
18107
+ } finally {
18108
+ session.endSession();
18109
+ }
18110
+ }
18111
+ async function updateById(id, value) {
18112
+ const { error: errorId } = Joi74.string().hex().required().validate(id);
18113
+ if (errorId) {
18114
+ throw new BadRequestError84("Invalid Chart of Account ID.");
18115
+ }
18116
+ const { error } = schemaChartOfAccountUpdate.validate(value);
18117
+ if (error) {
18118
+ throw new BadRequestError84(
18119
+ `Invalid chart of account update data: ${error.message}`
18120
+ );
18121
+ }
18122
+ const session = useAtlas38.getClient()?.startSession();
18123
+ if (!session) {
18124
+ throw new Error("Failed to start database session.");
18125
+ }
18126
+ try {
18127
+ session.startTransaction();
18128
+ const existingAccount = await _getById(id);
18129
+ if (!existingAccount) {
18130
+ throw new BadRequestError84("Chart of Account not found.");
18131
+ }
18132
+ const updatedData = {
18133
+ org: existingAccount.org,
18134
+ name: value.name,
18135
+ code: value.code,
18136
+ type: value.type,
18137
+ normalBalance: value.normalBalance,
18138
+ parent: value.parent ?? "",
18139
+ parentName: "",
18140
+ tax: value.tax,
18141
+ path: existingAccount.path,
18142
+ isContra: value.isContra
18143
+ };
18144
+ if (value.parent) {
18145
+ const parentAccount = await _getById(value.parent);
18146
+ if (!parentAccount) {
18147
+ throw new BadRequestError84("Parent account not found.");
18148
+ }
18149
+ updatedData.path = `${parentAccount.path}-${value.code}`;
18150
+ updatedData.parentName = parentAccount.name;
18151
+ updatedData.type = parentAccount.type;
18152
+ updatedData.normalBalance = parentAccount.normalBalance;
18153
+ } else {
18154
+ updatedData.path = value.code;
18155
+ updatedData.parentName = "";
18156
+ }
18157
+ const message = await _updateById(id, updatedData, session);
18158
+ await session.commitTransaction();
18159
+ return message;
18160
+ } catch (error2) {
18161
+ await session.abortTransaction();
18162
+ throw error2;
18163
+ } finally {
18164
+ session.endSession();
18165
+ }
18166
+ }
18167
+ async function deleteById(id) {
18168
+ const { error } = Joi74.string().hex().required().validate(id);
18169
+ if (error) {
18170
+ throw new BadRequestError84("Invalid Chart of Account ID.");
18171
+ }
18172
+ const session = useAtlas38.getClient()?.startSession();
18173
+ if (!session) {
18174
+ throw new Error("Failed to start database session.");
18175
+ }
18176
+ try {
18177
+ session.startTransaction();
18178
+ const existingAccount = await _getById(id);
18179
+ if (!existingAccount) {
18180
+ throw new BadRequestError84("Chart of Account not found.");
18181
+ }
18182
+ const childCount = await countByPath(existingAccount.path);
18183
+ if (childCount > 1) {
18184
+ throw new BadRequestError84(
18185
+ "Cannot delete account with child accounts. Please delete child accounts first."
18186
+ );
18187
+ }
18188
+ const message = await _deleteById(id, session);
18189
+ await session.commitTransaction();
18190
+ return message;
18191
+ } catch (error2) {
18192
+ await session.abortTransaction();
18193
+ throw error2;
18194
+ } finally {
18195
+ session.endSession();
18196
+ }
18197
+ }
18198
+ return {
18199
+ add,
18200
+ updateById,
18201
+ deleteById
18202
+ };
18203
+ }
18204
+
18205
+ // src/resources/finance-account/chart-of-account.controller.ts
18206
+ function useChartOfAccountController() {
18207
+ const {
18208
+ getAll: _getAll,
18209
+ getByOrg: _getByOrg,
18210
+ getById: _getById,
18211
+ updateStatusById: _updateStatusById
18212
+ } = useChartOfAccountRepo();
18213
+ const {
18214
+ add: _add,
18215
+ updateById: _updateById,
18216
+ deleteById: _deleteById
18217
+ } = useChartOfAccountService();
18218
+ async function add(req, res, next) {
18219
+ const value = req.body;
18220
+ const { error } = schemaChartOfAccountBase.validate(value);
18221
+ if (error) {
18222
+ next(new BadRequestError85(error.message));
18223
+ logger44.info(`Controller: ${error.message}`);
18224
+ return;
18225
+ }
18226
+ try {
18227
+ const message = await _add(value);
18228
+ res.json({ message });
18229
+ return;
18230
+ } catch (error2) {
18231
+ next(error2);
18232
+ }
18233
+ }
18234
+ async function getAll(req, res, next) {
18235
+ const query = req.query;
18236
+ const validation = Joi75.object({
18237
+ org: Joi75.string().hex().required(),
18238
+ page: Joi75.number().min(1).optional().allow("", null),
18239
+ limit: Joi75.number().min(1).optional().allow("", null),
18240
+ search: Joi75.string().optional().allow("", null),
18241
+ status: Joi75.string().optional().allow("", null)
18242
+ });
18243
+ const org = req.params.org ?? "";
18244
+ const { error } = validation.validate({ ...query, org });
18245
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
18246
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
18247
+ const search = req.query.search ?? "";
18248
+ const status2 = req.query.status ?? "active";
18249
+ const isPageNumber = isFinite(page);
18250
+ if (!isPageNumber) {
18251
+ next(new BadRequestError85("Invalid page number."));
18252
+ return;
18253
+ }
18254
+ const isLimitNumber = isFinite(limit);
18255
+ if (!isLimitNumber) {
18256
+ next(new BadRequestError85("Invalid limit number."));
18257
+ return;
18258
+ }
18259
+ if (error) {
18260
+ next(new BadRequestError85(error.message));
18261
+ return;
18262
+ }
18263
+ try {
18264
+ const accounts = await _getAll({ org, page, limit, search, status: status2 });
18265
+ res.json(accounts);
18266
+ return;
18267
+ } catch (error2) {
18268
+ next(error2);
18269
+ }
18270
+ }
18271
+ async function getByOrg(req, res, next) {
18272
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
18273
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
18274
+ const search = req.query.search ?? "";
18275
+ const status2 = req.query.status ?? "active";
18276
+ const org = req.params.org ?? "";
18277
+ const isPageNumber = isFinite(page);
18278
+ if (!isPageNumber) {
18279
+ next(new BadRequestError85("Invalid page number."));
18280
+ return;
18281
+ }
18282
+ const isLimitNumber = isFinite(limit);
18283
+ if (!isLimitNumber) {
18284
+ next(new BadRequestError85("Invalid limit number."));
18285
+ return;
18286
+ }
18287
+ const validation = Joi75.object({
18288
+ org: Joi75.string().hex().required(),
18289
+ page: Joi75.number().min(1).optional().allow("", null),
18290
+ limit: Joi75.number().min(1).optional().allow("", null),
18291
+ search: Joi75.string().optional().allow("", null),
18292
+ status: Joi75.string().optional()
18293
+ });
18294
+ const { error } = validation.validate({ org, page, limit, search, status: status2 });
18295
+ if (error) {
18296
+ next(new BadRequestError85(error.message));
18297
+ return;
18298
+ }
18299
+ try {
18300
+ const accounts = await _getByOrg({ org, page, limit, search, status: status2 });
18301
+ res.json(accounts);
18302
+ return;
18303
+ } catch (error2) {
18304
+ next(error2);
18305
+ }
18306
+ }
18307
+ async function getById(req, res, next) {
18308
+ const id = req.params.id;
18309
+ const validation = Joi75.object({
18310
+ id: Joi75.string().hex().required()
18311
+ });
18312
+ const { error } = validation.validate({ id });
18313
+ if (error) {
18314
+ next(new BadRequestError85(error.message));
18315
+ return;
18316
+ }
18317
+ try {
18318
+ const account = await _getById(id);
18319
+ res.json(account);
18320
+ return;
18321
+ } catch (error2) {
18322
+ next(error2);
18323
+ }
18324
+ }
18325
+ async function updateById(req, res, next) {
18326
+ const _id = req.params.id;
18327
+ const { error: errorId } = Joi75.string().hex().required().validate(_id);
18328
+ if (errorId) {
18329
+ next(new BadRequestError85("Invalid Chart of Account ID."));
18330
+ return;
18331
+ }
18332
+ const payload = req.body;
18333
+ const { error } = schemaChartOfAccountUpdate.validate(payload);
18334
+ if (error) {
18335
+ next(new BadRequestError85(error.message));
18336
+ return;
18337
+ }
18338
+ try {
18339
+ const message = await _updateById(_id, payload);
18340
+ res.json({ message });
18341
+ return;
18342
+ } catch (error2) {
18343
+ next(error2);
18344
+ }
18345
+ }
18346
+ async function deleteById(req, res, next) {
18347
+ const id = req.params.id;
18348
+ if (!id) {
18349
+ next(new BadRequestError85("Chart of Account ID is required."));
18350
+ return;
18351
+ }
18352
+ try {
18353
+ const message = await _deleteById(id);
18354
+ res.json(message);
18355
+ return;
18356
+ } catch (error) {
18357
+ next(error);
18358
+ }
18359
+ }
18360
+ async function updateStatusById(req, res, next) {
18361
+ const id = req.params.id;
18362
+ const status2 = req.params.status;
18363
+ const validation = Joi75.object({
18364
+ id: Joi75.string().hex().required(),
18365
+ status: Joi75.string().required()
18366
+ });
18367
+ const { error } = validation.validate({ id, status: status2 });
18368
+ if (error) {
18369
+ next(new BadRequestError85(error.message));
18370
+ return;
18371
+ }
18372
+ try {
18373
+ const message = await _updateStatusById(id, status2);
18374
+ res.json({ message });
18375
+ return;
18376
+ } catch (error2) {
18377
+ next(error2);
18378
+ }
18379
+ }
18380
+ return {
18381
+ add,
18382
+ getAll,
18383
+ getByOrg,
18384
+ getById,
18385
+ updateById,
18386
+ deleteById,
18387
+ updateStatusById
18388
+ };
18389
+ }
18390
+
18391
+ // src/resources/finance-journal/finance.journal.entry.model.ts
18392
+ import { BadRequestError as BadRequestError86 } from "@goweekdays/utils";
18393
+ import Joi76 from "joi";
18394
+ import { ObjectId as ObjectId44 } from "mongodb";
18395
+ var journalEntryStatuses = [
18396
+ "draft",
18397
+ "posted",
18398
+ "voided"
18399
+ ];
18400
+ var journalEntryTypes = [
18401
+ "general",
18402
+ "sales",
18403
+ "purchases",
18404
+ "cash-receipts",
18405
+ "cash-disbursements"
18406
+ ];
18407
+ var schemaJournalEntryBase = {
18408
+ org: Joi76.string().hex().required(),
18409
+ book: Joi76.string().valid(...journalEntryTypes).required(),
18410
+ date: Joi76.date().required(),
18411
+ type: Joi76.string().required(),
18412
+ description: Joi76.string().required(),
18413
+ createdBy: Joi76.string().hex().required(),
18414
+ metadata: Joi76.object({
18415
+ customerId: Joi76.string().hex().optional().allow("", null),
18416
+ supplierId: Joi76.string().hex().optional().allow("", null),
18417
+ itemId: Joi76.string().hex().optional().allow("", null),
18418
+ assetId: Joi76.string().hex().optional().allow("", null),
18419
+ purchaseOrderId: Joi76.string().hex().optional().allow("", null),
18420
+ salesOrderId: Joi76.string().hex().optional().allow("", null)
18421
+ }).optional()
18422
+ };
18423
+ var schemaJournalEntryCtrl = Joi76.object(schemaJournalEntryBase);
18424
+ var schemaJournalEntryRepo = Joi76.object({
18425
+ ...schemaJournalEntryBase,
18426
+ id: Joi76.string().required(),
18427
+ createdByName: Joi76.string().required(),
18428
+ status: Joi76.string().valid(...journalEntryStatuses).required()
18429
+ });
18430
+ var schemaJournalEntryGetAll = Joi76.object({
18431
+ org: Joi76.string().hex().required(),
18432
+ page: Joi76.number().optional().allow("", null),
18433
+ limit: Joi76.number().max(100).optional().allow("", null),
18434
+ book: Joi76.string().required().allow("", null),
18435
+ type: Joi76.string().optional().allow("", null),
18436
+ search: Joi76.string().optional().allow("", null),
18437
+ status: Joi76.string().optional().allow("", null)
18438
+ });
18439
+ function modelJournalEntry(data) {
18440
+ const { error } = schemaJournalEntryRepo.validate(data);
18441
+ if (error) {
18442
+ throw new Error(`Invalid journal entry data: ${error.message}`);
18443
+ }
18444
+ try {
18445
+ data.org = new ObjectId44(data.org);
18446
+ } catch (error2) {
18447
+ throw new BadRequestError86(`Invalid org ID: ${data.org}`);
18448
+ }
18449
+ try {
18450
+ data.createdBy = new ObjectId44(data.createdBy);
18451
+ } catch (error2) {
18452
+ throw new BadRequestError86(`Invalid createdBy ID: ${data.createdBy}`);
18453
+ }
18454
+ const date = /* @__PURE__ */ new Date();
18455
+ if (data.metadata) {
18456
+ if (data.metadata.customerId) {
18457
+ try {
18458
+ data.metadata.customerId = new ObjectId44(data.metadata.customerId);
18459
+ } catch (error2) {
18460
+ throw new BadRequestError86(
18461
+ `Invalid customer ID: ${data.metadata.customerId}`
18462
+ );
18463
+ }
18464
+ }
18465
+ if (data.metadata.supplierId) {
18466
+ try {
18467
+ data.metadata.supplierId = new ObjectId44(data.metadata.supplierId);
18468
+ } catch (error2) {
18469
+ throw new BadRequestError86(
18470
+ `Invalid supplier ID: ${data.metadata.supplierId}`
18471
+ );
18472
+ }
18473
+ }
18474
+ if (data.metadata.itemId) {
18475
+ try {
18476
+ data.metadata.itemId = new ObjectId44(data.metadata.itemId);
18477
+ } catch (error2) {
18478
+ throw new BadRequestError86(`Invalid item ID: ${data.metadata.itemId}`);
18479
+ }
18480
+ }
18481
+ if (data.metadata.assetId) {
18482
+ try {
18483
+ data.metadata.assetId = new ObjectId44(data.metadata.assetId);
18484
+ } catch (error2) {
18485
+ throw new BadRequestError86(`Invalid asset ID: ${data.metadata.assetId}`);
18486
+ }
18487
+ }
18488
+ if (data.metadata.purchaseOrderId) {
18489
+ try {
18490
+ data.metadata.purchaseOrderId = new ObjectId44(
18491
+ data.metadata.purchaseOrderId
18492
+ );
18493
+ } catch (error2) {
18494
+ throw new BadRequestError86(
18495
+ `Invalid purchase order ID: ${data.metadata.purchaseOrderId}`
18496
+ );
18497
+ }
18498
+ }
18499
+ if (data.metadata.salesOrderId) {
18500
+ try {
18501
+ data.metadata.salesOrderId = new ObjectId44(data.metadata.salesOrderId);
18502
+ } catch (error2) {
18503
+ throw new BadRequestError86(
18504
+ `Invalid sales order ID: ${data.metadata.salesOrderId}`
18505
+ );
18506
+ }
18507
+ }
18508
+ }
18509
+ return {
18510
+ _id: data._id,
18511
+ id: data.id,
18512
+ org: data.org,
18513
+ type: data.type,
18514
+ date,
18515
+ book: data.book,
18516
+ description: data.description,
18517
+ metadata: data.metadata ?? {
18518
+ customerId: "",
18519
+ supplierId: "",
18520
+ itemId: "",
18521
+ assetId: "",
18522
+ purchaseOrderId: "",
18523
+ salesOrderId: ""
18524
+ },
18525
+ status: data.status,
18526
+ postedAt: data.postedAt ?? "",
18527
+ createdBy: data.createdBy,
18528
+ createdByName: data.createdByName,
18529
+ createdAt: data.createdAt ?? date,
18530
+ updatedAt: data.updatedAt ?? ""
18531
+ };
18532
+ }
18533
+
18534
+ // src/resources/finance-journal/finance.journal.entry.repository.ts
18535
+ import {
18536
+ AppError as AppError41,
18537
+ BadRequestError as BadRequestError87,
18538
+ useAtlas as useAtlas39,
18539
+ useCache as useCache29,
18540
+ makeCacheKey as makeCacheKey26,
18541
+ paginate as paginate23,
18542
+ logger as logger45,
18543
+ InternalServerError as InternalServerError38
18544
+ } from "@goweekdays/utils";
18545
+ import { ObjectId as ObjectId45 } from "mongodb";
18546
+ import Joi77 from "joi";
18547
+ function useJournalEntryRepo() {
18548
+ const db = useAtlas39.getDb();
18549
+ if (!db) {
18550
+ throw new BadRequestError87("Unable to connect to server.");
18551
+ }
18552
+ const namespace_collection = "finance.journal.entries";
18553
+ const collection = db.collection(namespace_collection);
18554
+ const { getCache, setCache, delNamespace } = useCache29(namespace_collection);
18555
+ function delCachedData() {
18556
+ delNamespace().then(() => {
18557
+ logger45.log({
18558
+ level: "info",
18559
+ message: `Cache namespace cleared for ${namespace_collection}`
18560
+ });
18561
+ }).catch((err) => {
18562
+ logger45.log({
18563
+ level: "error",
18564
+ message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
18565
+ });
18566
+ });
18567
+ }
18568
+ async function add(value, session) {
18569
+ try {
18570
+ value = modelJournalEntry(value);
18571
+ const res = await collection.insertOne(value, { session });
18572
+ delCachedData();
18573
+ return res.insertedId;
18574
+ } catch (error) {
18575
+ logger45.log({
18576
+ level: "error",
18577
+ message: error.message
18578
+ });
18579
+ throw new BadRequestError87(
18580
+ `Failed to create journal entry: ${error.message}`
18581
+ );
18582
+ }
18583
+ }
18584
+ async function getAll(options) {
18585
+ options.page = options.page && options.page > 0 ? options.page - 1 : 0;
18586
+ options.status = options.status ?? "posted";
18587
+ options.limit = options.limit ?? 10;
18588
+ options.book = options.book ?? "general";
18589
+ const { error } = schemaJournalEntryGetAll.validate(options);
18590
+ if (error) {
18591
+ throw new BadRequestError87(`Invalid query parameters: ${error.message}`);
18592
+ }
18593
+ const query = {
18594
+ status: options.status,
18595
+ book: options.book
18596
+ };
18597
+ const cacheKeyOptions = {
18598
+ page: options.page,
18599
+ limit: options.limit,
18600
+ book: options.book,
18601
+ status: options.status
18602
+ };
18603
+ try {
18604
+ query.org = new ObjectId45(options.org);
18605
+ cacheKeyOptions.org = String(options.org);
18606
+ } catch (error2) {
18607
+ throw new BadRequestError87("Invalid organization ID.");
18608
+ }
18609
+ if (options.search) {
18610
+ query.$text = { $search: options.search };
18611
+ cacheKeyOptions.search = options.search;
18612
+ }
18613
+ if (options.type) {
18614
+ query.type = options.type;
18615
+ cacheKeyOptions.type = options.type;
18616
+ }
18617
+ const cacheKey = makeCacheKey26(namespace_collection, cacheKeyOptions);
18618
+ logger45.log({
18619
+ level: "info",
18620
+ message: `Cache key for getAll journal entries: ${cacheKey}`
18621
+ });
18622
+ try {
18623
+ const cached = await getCache(cacheKey);
18624
+ if (cached) {
18625
+ logger45.log({
18626
+ level: "info",
18627
+ message: `Cache hit for getAll journal entries: ${cacheKey}`
18628
+ });
18629
+ return cached;
18630
+ }
18631
+ const items = await collection.aggregate([
18632
+ { $match: query },
18633
+ { $skip: options.page * options.limit },
18634
+ { $limit: options.limit },
18635
+ {
18636
+ $lookup: {
18637
+ from: "finance.journal.lines",
18638
+ let: { journalId: "$_id" },
18639
+ pipeline: [
18640
+ {
18641
+ $match: {
18642
+ $expr: { $eq: ["$journalEntry", "$$journalId"] }
18643
+ }
18644
+ },
18645
+ {
18646
+ $sort: { debit: -1 }
18647
+ }
18648
+ ],
18649
+ as: "lines"
18650
+ }
18651
+ }
18652
+ ]).toArray();
18653
+ const length = await collection.countDocuments(query);
18654
+ const data = paginate23(items, options.page, options.limit, length);
18655
+ setCache(cacheKey, data, 600).then(() => {
18656
+ logger45.log({
18657
+ level: "info",
18658
+ message: `Cache set for getAll journal entries: ${cacheKey}`
18659
+ });
18660
+ }).catch((err) => {
18661
+ logger45.log({
18662
+ level: "error",
18663
+ message: `Failed to set cache for getAll journal entries: ${err.message}`
18664
+ });
18665
+ });
18666
+ return data;
18667
+ } catch (error2) {
18668
+ logger45.log({ level: "error", message: `${error2}` });
18669
+ throw error2;
18670
+ }
18671
+ }
18672
+ async function getById(_id) {
18673
+ try {
18674
+ _id = new ObjectId45(_id);
18675
+ } catch (error) {
18676
+ throw new BadRequestError87("Invalid ID.");
18677
+ }
18678
+ const cacheKey = makeCacheKey26(namespace_collection, { _id: String(_id) });
18679
+ try {
18680
+ const cached = await getCache(cacheKey);
18681
+ if (cached) {
18682
+ logger45.log({
18683
+ level: "info",
18684
+ message: `Cache hit for getById journal entry: ${cacheKey}`
18685
+ });
18686
+ return cached;
18687
+ }
18688
+ const result = await collection.findOne({ _id });
18689
+ if (!result) {
18690
+ throw new BadRequestError87("Journal entry not found.");
18691
+ }
18692
+ setCache(cacheKey, result, 300).then(() => {
18693
+ logger45.log({
18694
+ level: "info",
18695
+ message: `Cache set for journal entry by id: ${cacheKey}`
18696
+ });
18697
+ }).catch((err) => {
18698
+ logger45.log({
18699
+ level: "error",
18700
+ message: `Failed to set cache for journal entry by id: ${err.message}`
18701
+ });
18702
+ });
18703
+ return result;
18704
+ } catch (error) {
18705
+ if (error instanceof AppError41) {
18706
+ throw error;
18707
+ } else {
18708
+ throw new InternalServerError38("Failed to get journal entry.");
18709
+ }
18710
+ }
18711
+ }
18712
+ async function updateById(_id, options) {
18713
+ const { error: errorId } = Joi77.string().hex().required().validate(String(_id));
18714
+ if (errorId) {
18715
+ throw new BadRequestError87("Invalid Journal Entry ID.");
18716
+ }
18717
+ const { error } = schemaJournalEntryRepo.validate(options);
18718
+ if (error) {
18719
+ throw new BadRequestError87(
18720
+ `Invalid journal entry update data: ${error.message}`
18721
+ );
18722
+ }
18723
+ try {
18724
+ _id = new ObjectId45(_id);
18725
+ } catch (error2) {
18726
+ throw new BadRequestError87("Invalid Journal Entry ID.");
18727
+ }
18728
+ try {
18729
+ await collection.updateOne(
18730
+ { _id },
18731
+ { $set: { ...options, updatedAt: /* @__PURE__ */ new Date() } }
18732
+ );
18733
+ delCachedData();
18734
+ return "Successfully updated journal entry.";
18735
+ } catch (error2) {
18736
+ throw new InternalServerError38("Failed to update journal entry.");
18737
+ }
18738
+ }
18739
+ async function deleteById(_id, session) {
18740
+ try {
18741
+ _id = new ObjectId45(_id);
18742
+ } catch (error) {
18743
+ throw new BadRequestError87("Invalid ID.");
18744
+ }
18745
+ try {
18746
+ await collection.updateOne(
18747
+ { _id },
18748
+ {
18749
+ $set: {
18750
+ status: "voided",
18751
+ updatedAt: /* @__PURE__ */ new Date()
18752
+ }
18753
+ },
18754
+ { session }
18755
+ );
18756
+ delCachedData();
18757
+ return "Successfully voided journal entry.";
18758
+ } catch (error) {
18759
+ throw new InternalServerError38("Failed to void journal entry.");
18760
+ }
18761
+ }
18762
+ async function updateStatusById(_id, status2) {
18763
+ try {
18764
+ _id = new ObjectId45(_id);
18765
+ } catch (error) {
18766
+ throw new BadRequestError87("Invalid ID.");
18767
+ }
18768
+ try {
18769
+ const result = await collection.updateOne(
18770
+ { _id },
18771
+ {
18772
+ $set: {
18773
+ status: status2,
18774
+ updatedAt: /* @__PURE__ */ new Date()
18775
+ }
18776
+ }
18777
+ );
18778
+ if (result.matchedCount === 0) {
18779
+ throw new BadRequestError87("Journal entry not found.");
18780
+ }
18781
+ delCachedData();
18782
+ return "Successfully updated journal entry status.";
18783
+ } catch (error) {
18784
+ if (error instanceof AppError41) {
18785
+ throw error;
18786
+ }
18787
+ throw new InternalServerError38("Failed to update journal entry status.");
18788
+ }
18789
+ }
18790
+ return {
18791
+ add,
18792
+ getAll,
18793
+ getById,
18794
+ updateById,
18795
+ deleteById,
18796
+ updateStatusById
18797
+ };
18798
+ }
18799
+
18800
+ // src/resources/finance-journal/finance.journal.entry.service.ts
18801
+ import Joi80 from "joi";
18802
+
18803
+ // src/resources/finance-journal/finance.journal.line.model.ts
18804
+ import { BadRequestError as BadRequestError88 } from "@goweekdays/utils";
18805
+ import Joi78 from "joi";
18806
+ import { ObjectId as ObjectId46 } from "mongodb";
18807
+ var schemaJournalLineBase = {
18808
+ org: Joi78.string().hex().required(),
18809
+ account: Joi78.string().hex().required(),
18810
+ debit: Joi78.number().min(0).required(),
18811
+ credit: Joi78.number().min(0).required()
18812
+ };
18813
+ var schemaJournalLineCtrl = Joi78.object(schemaJournalLineBase);
18814
+ var schemaJournalLineRepo = Joi78.object({
18815
+ ...schemaJournalLineBase,
18816
+ journalEntry: Joi78.string().hex().required(),
18817
+ accountName: Joi78.string().required()
18818
+ });
18819
+ function modelJournalLine(data) {
18820
+ const { error } = schemaJournalLineRepo.validate(data);
18821
+ if (error) {
18822
+ throw new BadRequestError88(`Invalid journal line data: ${error.message}`);
18823
+ }
18824
+ try {
18825
+ data.org = new ObjectId46(data.org);
18826
+ } catch (error2) {
18827
+ throw new BadRequestError88(`Invalid org ID: ${data.org}`);
18828
+ }
18829
+ try {
18830
+ data.journalEntry = new ObjectId46(data.journalEntry);
18831
+ } catch (error2) {
18832
+ throw new BadRequestError88(`Invalid journal entry ID: ${data.journalEntry}`);
18833
+ }
18834
+ try {
18835
+ data.account = new ObjectId46(data.account);
18836
+ } catch (error2) {
18837
+ throw new BadRequestError88(`Invalid account ID: ${data.account}`);
18838
+ }
18839
+ return {
18840
+ _id: data._id,
18841
+ org: data.org,
18842
+ journalEntry: data.journalEntry,
18843
+ account: data.account,
18844
+ accountName: data.accountName,
18845
+ debit: data.debit,
18846
+ credit: data.credit,
18847
+ postReference: data.postReference ?? "",
18848
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
18849
+ updatedAt: data.updatedAt ?? ""
18850
+ };
18851
+ }
18852
+
18853
+ // src/resources/finance-journal/finance.journal.line.repository.ts
18854
+ import {
18855
+ AppError as AppError42,
18856
+ BadRequestError as BadRequestError89,
18857
+ useAtlas as useAtlas40,
18858
+ useCache as useCache30,
18859
+ makeCacheKey as makeCacheKey27,
18860
+ paginate as paginate24,
18861
+ logger as logger46,
18862
+ InternalServerError as InternalServerError39
18863
+ } from "@goweekdays/utils";
18864
+ import { ObjectId as ObjectId47 } from "mongodb";
18865
+ import Joi79 from "joi";
18866
+ function useJournalLineRepo() {
18867
+ const db = useAtlas40.getDb();
18868
+ if (!db) {
18869
+ throw new BadRequestError89("Unable to connect to server.");
18870
+ }
18871
+ const namespace_collection = "finance.journal.lines";
18872
+ const collection = db.collection(namespace_collection);
18873
+ const { getCache, setCache, delNamespace } = useCache30(namespace_collection);
18874
+ function delCachedData() {
18875
+ delNamespace().then(() => {
18876
+ logger46.log({
18877
+ level: "info",
18878
+ message: `Cache namespace cleared for ${namespace_collection}`
18879
+ });
18880
+ }).catch((err) => {
18881
+ logger46.log({
18882
+ level: "error",
18883
+ message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
18884
+ });
18885
+ });
18886
+ }
18887
+ async function add(value, session) {
18888
+ try {
18889
+ value = modelJournalLine(value);
18890
+ const res = await collection.insertOne(value, { session });
18891
+ delCachedData();
18892
+ return res.insertedId;
18893
+ } catch (error) {
18894
+ logger46.log({
18895
+ level: "error",
18896
+ message: error.message
18897
+ });
18898
+ throw new BadRequestError89(
18899
+ `Failed to create journal line: ${error.message}`
18900
+ );
18901
+ }
18902
+ }
18903
+ async function getAll(options) {
18904
+ options.page = options.page && options.page > 0 ? options.page - 1 : 0;
18905
+ options.limit = options.limit ?? 10;
18906
+ const query = {};
18907
+ try {
18908
+ query.org = new ObjectId47(options.org);
18909
+ } catch (error) {
18910
+ throw new BadRequestError89("Invalid organization ID.");
18911
+ }
18912
+ if (options.journalEntry) {
18913
+ try {
18914
+ query.journalEntry = new ObjectId47(options.journalEntry);
18915
+ } catch (error) {
18916
+ throw new BadRequestError89("Invalid journal entry ID.");
18917
+ }
18918
+ }
18919
+ const cacheKey = makeCacheKey27(namespace_collection, {
18920
+ org: String(options.org),
18921
+ journalEntry: String(options.journalEntry ?? ""),
18922
+ page: options.page,
18923
+ limit: options.limit
18924
+ });
18925
+ logger46.log({
18926
+ level: "info",
18927
+ message: `Cache key for getAll journal lines: ${cacheKey}`
18928
+ });
18929
+ try {
18930
+ const cached = await getCache(cacheKey);
18931
+ if (cached) {
18932
+ logger46.log({
18933
+ level: "info",
18934
+ message: `Cache hit for getAll journal lines: ${cacheKey}`
18935
+ });
18936
+ return cached;
18937
+ }
18938
+ const items = await collection.aggregate([
18939
+ { $match: query },
18940
+ { $skip: options.page * options.limit },
18941
+ { $limit: options.limit },
18942
+ {
18943
+ $project: {
18944
+ _id: 1,
18945
+ org: 1,
18946
+ journalEntry: 1,
18947
+ account: 1,
18948
+ debit: 1,
18949
+ credit: 1,
18950
+ description: 1,
18951
+ createdAt: 1
18952
+ }
18953
+ }
18954
+ ]).toArray();
18955
+ const length = await collection.countDocuments(query);
18956
+ const data = paginate24(items, options.page, options.limit, length);
18957
+ setCache(cacheKey, data, 600).then(() => {
18958
+ logger46.log({
18959
+ level: "info",
18960
+ message: `Cache set for getAll journal lines: ${cacheKey}`
18961
+ });
18962
+ }).catch((err) => {
18963
+ logger46.log({
18964
+ level: "error",
18965
+ message: `Failed to set cache for getAll journal lines: ${err.message}`
18966
+ });
18967
+ });
18968
+ return data;
18969
+ } catch (error) {
18970
+ logger46.log({ level: "error", message: `${error}` });
18971
+ throw error;
18972
+ }
18973
+ }
18974
+ async function getById(_id) {
18975
+ try {
18976
+ _id = new ObjectId47(_id);
18977
+ } catch (error) {
18978
+ throw new BadRequestError89("Invalid ID.");
18979
+ }
18980
+ const cacheKey = makeCacheKey27(namespace_collection, { _id: String(_id) });
18981
+ try {
18982
+ const cached = await getCache(cacheKey);
18983
+ if (cached) {
18984
+ logger46.log({
18985
+ level: "info",
18986
+ message: `Cache hit for getById journal line: ${cacheKey}`
18987
+ });
18988
+ return cached;
18989
+ }
18990
+ const result = await collection.findOne({ _id });
18991
+ if (!result) {
18992
+ throw new BadRequestError89("Journal line not found.");
18993
+ }
18994
+ setCache(cacheKey, result, 300).then(() => {
18995
+ logger46.log({
18996
+ level: "info",
18997
+ message: `Cache set for journal line by id: ${cacheKey}`
18998
+ });
18999
+ }).catch((err) => {
19000
+ logger46.log({
19001
+ level: "error",
19002
+ message: `Failed to set cache for journal line by id: ${err.message}`
19003
+ });
19004
+ });
19005
+ return result;
19006
+ } catch (error) {
19007
+ if (error instanceof AppError42) {
19008
+ throw error;
19009
+ } else {
19010
+ throw new InternalServerError39("Failed to get journal line.");
19011
+ }
19012
+ }
19013
+ }
19014
+ async function updateById(_id, options) {
19015
+ const { error: errorId } = Joi79.string().hex().required().validate(String(_id));
19016
+ if (errorId) {
19017
+ throw new BadRequestError89("Invalid Journal Line ID.");
19018
+ }
19019
+ const { error } = schemaJournalLineRepo.validate(options);
19020
+ if (error) {
19021
+ throw new BadRequestError89(
19022
+ `Invalid journal line update data: ${error.message}`
19023
+ );
19024
+ }
19025
+ try {
19026
+ _id = new ObjectId47(_id);
19027
+ } catch (error2) {
19028
+ throw new BadRequestError89("Invalid Journal Line ID.");
19029
+ }
19030
+ try {
19031
+ await collection.updateOne(
19032
+ { _id },
19033
+ { $set: { ...options, updatedAt: /* @__PURE__ */ new Date() } }
19034
+ );
19035
+ delCachedData();
19036
+ return "Successfully updated journal line.";
19037
+ } catch (error2) {
19038
+ throw new InternalServerError39("Failed to update journal line.");
19039
+ }
19040
+ }
19041
+ async function deleteById(_id, session) {
19042
+ try {
19043
+ _id = new ObjectId47(_id);
19044
+ } catch (error) {
19045
+ throw new BadRequestError89("Invalid ID.");
19046
+ }
19047
+ try {
19048
+ await collection.deleteOne({ _id }, { session });
19049
+ delCachedData();
19050
+ return "Successfully deleted journal line.";
19051
+ } catch (error) {
19052
+ throw new InternalServerError39("Failed to delete journal line.");
19053
+ }
19054
+ }
19055
+ return {
19056
+ add,
19057
+ getAll,
19058
+ getById,
19059
+ updateById,
19060
+ deleteById
19061
+ };
19062
+ }
19063
+
19064
+ // src/resources/finance-journal/finance.journal.entry.service.ts
19065
+ import {
19066
+ AppError as AppError43,
19067
+ BadRequestError as BadRequestError90,
19068
+ InternalServerError as InternalServerError40,
19069
+ NotFoundError as NotFoundError6,
19070
+ useAtlas as useAtlas41
19071
+ } from "@goweekdays/utils";
19072
+ function useJournalEntryService() {
19073
+ const { add: _add } = useJournalEntryRepo();
19074
+ const { add: addLine } = useJournalLineRepo();
19075
+ const { getUserById } = useUserRepo();
19076
+ const { getById: getAccountById } = useChartOfAccountRepo();
19077
+ const {
19078
+ getByType: getCounterByType,
19079
+ add: addCounter,
19080
+ incrementByType
19081
+ } = useCounterRepo();
19082
+ async function add(entry, lines) {
19083
+ const { error } = Joi80.object({
19084
+ journalEntry: schemaJournalEntryCtrl.required(),
19085
+ journalLines: Joi80.array().items(schemaJournalLineCtrl).min(1).required()
19086
+ }).validate({ journalEntry: entry, journalLines: lines });
19087
+ if (error) {
19088
+ throw new BadRequestError90(`Invalid journal entry data: ${error.message}`);
19089
+ }
19090
+ const session = useAtlas41.getClient()?.startSession();
19091
+ if (!session) {
19092
+ throw new BadRequestError90("Unable to start database session.");
19093
+ }
19094
+ try {
19095
+ session.startTransaction();
19096
+ const counterName = `${entry.org.toString()}.journal.entry.${entry.book}`;
19097
+ const counter = await getCounterByType(counterName);
19098
+ if (!counter) {
19099
+ await addCounter(counterName, session);
19100
+ }
19101
+ const entryNumber = counter ? counter.count + 1 : 1;
19102
+ entry.id = entryNumber.toString().padStart(6, "0");
19103
+ await incrementByType(counterName, session);
19104
+ const totalDebit = lines.reduce((sum, line) => sum + line.debit, 0);
19105
+ const totalCredit = lines.reduce((sum, line) => sum + line.credit, 0);
19106
+ if (totalDebit !== totalCredit) {
19107
+ throw new BadRequestError90(
19108
+ `Total debit (${totalDebit}) must equal total credit (${totalCredit}).`
19109
+ );
19110
+ }
19111
+ const user = await getUserById(entry.createdBy);
19112
+ if (!user) {
19113
+ throw new NotFoundError6("User not found.");
19114
+ }
19115
+ entry.createdByName = `${user.firstName} ${user.lastName}`;
19116
+ entry.status = "draft";
19117
+ const entryId = await _add(entry, session);
19118
+ for (const line of lines) {
19119
+ line.journalEntry = entryId.toString();
19120
+ const account = await getAccountById(String(line.account));
19121
+ if (!account) {
19122
+ throw new NotFoundError6(`Account not found: ${line.account}`);
19123
+ }
19124
+ line.accountName = account.name;
19125
+ await addLine(line, session);
19126
+ }
19127
+ await session.commitTransaction();
19128
+ return "Journal entry created successfully.";
19129
+ } catch (error2) {
19130
+ await session.abortTransaction();
19131
+ if (error2 instanceof AppError43) {
19132
+ throw error2;
19133
+ }
19134
+ throw new InternalServerError40(
19135
+ `Failed to create journal entry: ${error2.message}`
19136
+ );
19137
+ } finally {
19138
+ session.endSession();
19139
+ }
19140
+ }
19141
+ return {
19142
+ add
19143
+ };
19144
+ }
19145
+
19146
+ // src/resources/finance-journal/finance.journal.entry.controller.ts
19147
+ import Joi81 from "joi";
19148
+ import { BadRequestError as BadRequestError91, logger as logger47 } from "@goweekdays/utils";
19149
+ function useJournalEntryController() {
19150
+ const {
19151
+ getAll: _getAll,
19152
+ getById: _getById,
19153
+ updateById: _updateById,
19154
+ deleteById: _deleteById,
19155
+ updateStatusById: _updateStatusById
19156
+ } = useJournalEntryRepo();
19157
+ const { add: _add } = useJournalEntryService();
19158
+ async function add(req, res, next) {
19159
+ const value = req.body;
19160
+ const { error } = Joi81.object({
19161
+ journalEntry: schemaJournalEntryCtrl.required(),
19162
+ journalLines: Joi81.array().items(schemaJournalLineCtrl).min(1).required()
19163
+ }).validate(value);
19164
+ if (error) {
19165
+ next(new BadRequestError91(error.message));
19166
+ logger47.info(`Controller: ${error.message}`);
19167
+ return;
19168
+ }
19169
+ try {
19170
+ const message = await _add(value.journalEntry, value.journalLines);
19171
+ res.json({ message });
19172
+ return;
19173
+ } catch (error2) {
19174
+ next(error2);
19175
+ }
19176
+ }
19177
+ async function getAll(req, res, next) {
19178
+ const query = req.query;
19179
+ const org = req.params.org ?? "";
19180
+ const { error } = schemaJournalEntryGetAll.validate({ ...query, org });
19181
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
19182
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
19183
+ const book = req.query.book ?? "general";
19184
+ const type = req.query.type ?? "";
19185
+ const search = req.query.search ?? "";
19186
+ const status2 = req.query.status ?? "posted";
19187
+ if (!isFinite(page)) {
19188
+ next(new BadRequestError91("Invalid page number."));
19189
+ return;
19190
+ }
19191
+ if (!isFinite(limit)) {
19192
+ next(new BadRequestError91("Invalid limit number."));
19193
+ return;
19194
+ }
19195
+ if (error) {
19196
+ next(new BadRequestError91(error.message));
19197
+ return;
19198
+ }
19199
+ try {
19200
+ const entries = await _getAll({
19201
+ org,
19202
+ page,
19203
+ limit,
19204
+ book,
19205
+ type,
19206
+ search,
19207
+ status: status2
19208
+ });
19209
+ res.json(entries);
19210
+ return;
19211
+ } catch (error2) {
19212
+ next(error2);
19213
+ }
19214
+ }
19215
+ async function getById(req, res, next) {
19216
+ const id = req.params.id;
19217
+ const validation = Joi81.object({
19218
+ id: Joi81.string().hex().required()
19219
+ });
19220
+ const { error } = validation.validate({ id });
19221
+ if (error) {
19222
+ next(new BadRequestError91(error.message));
19223
+ return;
19224
+ }
19225
+ try {
19226
+ const entry = await _getById(id);
19227
+ res.json(entry);
19228
+ return;
19229
+ } catch (error2) {
19230
+ next(error2);
19231
+ }
19232
+ }
19233
+ async function updateById(req, res, next) {
19234
+ const _id = req.params.id ?? "";
19235
+ const { error: errorId } = Joi81.string().hex().required().validate(_id);
19236
+ if (errorId) {
19237
+ next(new BadRequestError91("Invalid Journal Entry ID."));
19238
+ return;
19239
+ }
19240
+ const payload = req.body;
19241
+ const { error } = schemaJournalEntryCtrl.validate(payload);
19242
+ if (error) {
19243
+ next(
19244
+ new BadRequestError91(
19245
+ `Invalid journal entry update data: ${error.message}`
19246
+ )
19247
+ );
19248
+ return;
19249
+ }
19250
+ try {
19251
+ const message = await _updateById(_id, payload);
19252
+ res.json({ message });
19253
+ return;
19254
+ } catch (error2) {
19255
+ next(error2);
19256
+ }
19257
+ }
19258
+ async function deleteById(req, res, next) {
19259
+ const id = req.params.id;
19260
+ if (!id) {
19261
+ next(new BadRequestError91("Journal Entry ID is required."));
19262
+ return;
19263
+ }
19264
+ try {
19265
+ const message = await _deleteById(id);
19266
+ res.json(message);
19267
+ return;
19268
+ } catch (error) {
19269
+ next(error);
19270
+ }
19271
+ }
19272
+ async function updateStatusById(req, res, next) {
19273
+ const id = req.params.id;
19274
+ const status2 = req.params.status;
19275
+ const validation = Joi81.object({
19276
+ id: Joi81.string().hex().required(),
19277
+ status: Joi81.string().required()
19278
+ });
19279
+ const { error } = validation.validate({ id, status: status2 });
19280
+ if (error) {
19281
+ next(new BadRequestError91(error.message));
19282
+ return;
19283
+ }
19284
+ try {
19285
+ const message = await _updateStatusById(id, status2);
19286
+ res.json({ message });
19287
+ return;
19288
+ } catch (error2) {
19289
+ next(error2);
19290
+ }
19291
+ }
19292
+ return {
19293
+ add,
19294
+ getAll,
19295
+ getById,
19296
+ updateById,
19297
+ deleteById,
19298
+ updateStatusById
19299
+ };
19300
+ }
19301
+
19302
+ // src/resources/finance-journal/finance.journal.line.controller.ts
19303
+ import Joi82 from "joi";
19304
+ import { BadRequestError as BadRequestError92 } from "@goweekdays/utils";
19305
+ function useJournalLineController() {
19306
+ const {
19307
+ getAll: _getAll,
19308
+ getById: _getById,
19309
+ updateById: _updateById,
19310
+ deleteById: _deleteById,
19311
+ add: _add
19312
+ } = useJournalLineRepo();
19313
+ async function getAll(req, res, next) {
19314
+ const query = req.query;
19315
+ const validation = Joi82.object({
19316
+ org: Joi82.string().hex().required(),
19317
+ journalEntry: Joi82.string().hex().optional(),
19318
+ page: Joi82.number().min(1).optional().allow("", null),
19319
+ limit: Joi82.number().min(1).optional().allow("", null)
19320
+ });
19321
+ const org = req.params.org ?? "";
19322
+ const journalEntry = req.query.journalEntry;
19323
+ const { error } = validation.validate({ ...query, org, journalEntry });
19324
+ const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
19325
+ const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
19326
+ if (!isFinite(page)) {
19327
+ next(new BadRequestError92("Invalid page number."));
19328
+ return;
19329
+ }
19330
+ if (!isFinite(limit)) {
19331
+ next(new BadRequestError92("Invalid limit number."));
19332
+ return;
19333
+ }
19334
+ if (error) {
19335
+ next(new BadRequestError92(error.message));
19336
+ return;
19337
+ }
19338
+ try {
19339
+ const lines = await _getAll({ org, journalEntry, page, limit });
19340
+ res.json(lines);
19341
+ return;
19342
+ } catch (error2) {
19343
+ next(error2);
19344
+ }
19345
+ }
19346
+ async function getById(req, res, next) {
19347
+ const id = req.params.id;
19348
+ const validation = Joi82.object({
19349
+ id: Joi82.string().hex().required()
19350
+ });
19351
+ const { error } = validation.validate({ id });
19352
+ if (error) {
19353
+ next(new BadRequestError92(error.message));
19354
+ return;
19355
+ }
19356
+ try {
19357
+ const line = await _getById(id);
19358
+ res.json(line);
19359
+ return;
19360
+ } catch (error2) {
19361
+ next(error2);
19362
+ }
19363
+ }
19364
+ async function updateById(req, res, next) {
19365
+ const _id = req.params.id ?? "";
19366
+ const { error: errorId } = Joi82.string().hex().required().validate(_id);
19367
+ if (errorId) {
19368
+ next(new BadRequestError92("Invalid Journal Line ID."));
19369
+ return;
19370
+ }
19371
+ const payload = req.body;
19372
+ const { error } = schemaJournalLineCtrl.validate(payload);
19373
+ if (error) {
19374
+ next(
19375
+ new BadRequestError92(
19376
+ `Invalid journal line update data: ${error.message}`
19377
+ )
19378
+ );
19379
+ return;
19380
+ }
19381
+ try {
19382
+ const message = await _updateById(_id, payload);
19383
+ res.json({ message });
19384
+ return;
19385
+ } catch (error2) {
19386
+ next(error2);
19387
+ }
19388
+ }
19389
+ async function deleteById(req, res, next) {
19390
+ const id = req.params.id;
19391
+ if (!id) {
19392
+ next(new BadRequestError92("Journal Line ID is required."));
19393
+ return;
19394
+ }
19395
+ try {
19396
+ const message = await _deleteById(id);
19397
+ res.json(message);
19398
+ return;
19399
+ } catch (error) {
19400
+ next(error);
19401
+ }
19402
+ }
19403
+ return {
19404
+ getAll,
19405
+ getById,
19406
+ updateById,
19407
+ deleteById
19408
+ };
19409
+ }
17048
19410
  export {
17049
19411
  ACCESS_TOKEN_EXPIRY,
17050
19412
  ACCESS_TOKEN_SECRET,
@@ -17087,12 +19449,17 @@ export {
17087
19449
  VERIFICATION_USER_INVITE_DURATION,
17088
19450
  XENDIT_BASE_URL,
17089
19451
  XENDIT_SECRET_KEY,
19452
+ chartOfAccountNormalBalances,
19453
+ chartOfAccountTypes,
17090
19454
  currencies,
17091
19455
  isDev,
17092
19456
  jobApplicationStatuses,
19457
+ journalEntryStatuses,
19458
+ journalEntryTypes,
17093
19459
  ledgerBillStatuses,
17094
19460
  ledgerBillTypes,
17095
19461
  modelApp,
19462
+ modelChartOfAccount,
17096
19463
  modelJobApplication,
17097
19464
  modelJobPost,
17098
19465
  modelJobProfile,
@@ -17104,6 +19471,8 @@ export {
17104
19471
  modelJobStatusSetup,
17105
19472
  modelJobStatusTrans,
17106
19473
  modelJobSummary,
19474
+ modelJournalEntry,
19475
+ modelJournalLine,
17107
19476
  modelLedgerBill,
17108
19477
  modelMember,
17109
19478
  modelOption,
@@ -17115,6 +19484,7 @@ export {
17115
19484
  modelRole,
17116
19485
  modelSubscription,
17117
19486
  modelSubscriptionTransaction,
19487
+ modelTax,
17118
19488
  modelUser,
17119
19489
  modelVerification,
17120
19490
  schemaApp,
@@ -17123,6 +19493,9 @@ export {
17123
19493
  schemaBuilding,
17124
19494
  schemaBuildingUnit,
17125
19495
  schemaCertification,
19496
+ schemaChartOfAccountBase,
19497
+ schemaChartOfAccountStd,
19498
+ schemaChartOfAccountUpdate,
17126
19499
  schemaEducation,
17127
19500
  schemaGroup,
17128
19501
  schemaInviteMember,
@@ -17149,6 +19522,11 @@ export {
17149
19522
  schemaJobStatusTrans,
17150
19523
  schemaJobSummary,
17151
19524
  schemaJobSummaryInc,
19525
+ schemaJournalEntryCtrl,
19526
+ schemaJournalEntryGetAll,
19527
+ schemaJournalEntryRepo,
19528
+ schemaJournalLineCtrl,
19529
+ schemaJournalLineRepo,
17152
19530
  schemaLanguage,
17153
19531
  schemaLedgerBill,
17154
19532
  schemaLedgerBillingSummary,
@@ -17179,11 +19557,14 @@ export {
17179
19557
  schemaSubscriptionSeats,
17180
19558
  schemaSubscriptionTransaction,
17181
19559
  schemaSubscriptionUpdate,
19560
+ schemaTax,
19561
+ schemaTaxUpdate,
17182
19562
  schemaUpdateOptions,
17183
19563
  schemaUser,
17184
19564
  schemaVerification,
17185
19565
  schemaVerificationOrgInvite,
17186
19566
  schemaWorkExp,
19567
+ taxDirections,
17187
19568
  transactionSchema,
17188
19569
  useAppController,
17189
19570
  useAppRepo,
@@ -17196,6 +19577,8 @@ export {
17196
19577
  useBuildingUnitController,
17197
19578
  useBuildingUnitRepo,
17198
19579
  useBuildingUnitService,
19580
+ useChartOfAccountController,
19581
+ useChartOfAccountRepo,
17199
19582
  useCounterModel,
17200
19583
  useCounterRepo,
17201
19584
  useFileController,
@@ -17214,6 +19597,11 @@ export {
17214
19597
  useJobSummaryCtrl,
17215
19598
  useJobSummaryRepo,
17216
19599
  useJobSummarySvc,
19600
+ useJournalEntryController,
19601
+ useJournalEntryRepo,
19602
+ useJournalEntryService,
19603
+ useJournalLineController,
19604
+ useJournalLineRepo,
17217
19605
  useLedgerBillingController,
17218
19606
  useLedgerBillingRepo,
17219
19607
  useMemberController,
@@ -17244,6 +19632,8 @@ export {
17244
19632
  useSubscriptionService,
17245
19633
  useSubscriptionTransactionController,
17246
19634
  useSubscriptionTransactionRepo,
19635
+ useTaxController,
19636
+ useTaxRepo,
17247
19637
  useUserController,
17248
19638
  useUserRepo,
17249
19639
  useUserService,