@tomei/finance 0.3.28 → 0.3.30

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.
@@ -1,1081 +1,1156 @@
1
- import axios from 'axios';
2
- import * as cuid from 'cuid';
3
- import {
4
- HashTable,
5
- LoginUserBase,
6
- ObjectBase,
7
- RecordNotFoundError,
8
- } from '@tomei/general';
9
- import Account from '../account/account';
10
- import JournalEntry from '../journal-entry/journal-entry';
11
- import FinanceCustomerBase from '../customer/customer';
12
- import Document from '../document/document';
13
- import { IAccountSystem } from '../interfaces';
14
- import { FinanceCompanyRepository } from './finance-company.repository';
15
- import { FinanceCustomerRepository } from '../customer/finance-customer.repository';
16
- import { LedgerTransactionRepository } from '../ledger-transaction/ledger-transaction.repository';
17
- import { DocType, PaymentStatus, TransactionTypeOptions } from '../enum';
18
- import PaymentMethodType from '../payment-method-type/payment-method-type';
19
- import { PaymentRepository } from '../payment/payment.repository';
20
- import { PaymentItemRepository } from '../payment-item/payment-item.repository';
21
- import Payment from '../payment/payment';
22
- import { DocumentRepository } from '../document/document.repository';
23
- import { DocumentItemRepository } from '../document/document-item.repository';
24
- import { getConfig } from '../config';
25
-
26
- const config = getConfig();
27
-
28
- export default class FinanceCompany extends ObjectBase {
29
- private _CompanyId = 'New';
30
- private _CompSystemCode = '';
31
- private _CompSystemRefId = '';
32
- private _AccSystemCode = '';
33
- private static _htFinanceCompanyIds = new HashTable();
34
- private static _htFinanceCompanies = new HashTable();
35
- private static _financeCompanyRepository = new FinanceCompanyRepository();
36
- private static _PaymentRepository = new PaymentRepository();
37
- private static _PaymentItemRepository = new PaymentItemRepository();
38
- private static _DocumentRepository = new DocumentRepository();
39
- private static _DocumentItemRepository = new DocumentItemRepository();
40
- private static _FinanceCustomerRepository = new FinanceCustomerRepository();
41
- private static _LedgerTransactionRepository =
42
- new LedgerTransactionRepository();
43
- private _AccountingSystem: IAccountSystem;
44
-
45
- get CompSystemCode(): string {
46
- return this._CompSystemCode;
47
- }
48
-
49
- private set CompSystemCode(code: string) {
50
- this._CompSystemCode = code;
51
- }
52
-
53
- get CompSystemRefId() {
54
- return this._CompSystemRefId;
55
- }
56
-
57
- set CompSystemRefId(id: string) {
58
- this._CompSystemRefId = id;
59
- }
60
-
61
- get AccSystemCode() {
62
- return this._AccSystemCode;
63
- }
64
-
65
- private set AccSystemCode(code: string) {
66
- this._AccSystemCode = code;
67
- }
68
-
69
- get CompanyId() {
70
- return this._CompanyId;
71
- }
72
-
73
- get ObjectId() {
74
- return this._CompanyId;
75
- }
76
-
77
- private set ObjectId(id: string) {
78
- this._CompanyId = id;
79
- }
80
-
81
- get ObjectName() {
82
- return `${this.CompSystemCode}-${this.CompSystemRefId}-${this.AccSystemCode}`;
83
- }
84
-
85
- get TableName() {
86
- return 'finance_Company';
87
- }
88
-
89
- get AccountingSystem(): IAccountSystem {
90
- return this._AccountingSystem;
91
- }
92
-
93
- set AccountingSystem(system: IAccountSystem) {
94
- this._AccountingSystem = system;
95
- }
96
-
97
- constructor(
98
- compSystemCode: string,
99
- compSystemRefId: string,
100
- accSystemCode: string,
101
- ) {
102
- super();
103
- this.CompSystemCode = compSystemCode;
104
- this.CompSystemRefId = compSystemRefId;
105
- this.AccSystemCode = accSystemCode;
106
- }
107
-
108
- static async getFinanceCompanyId(
109
- compSystemCode: string,
110
- compSystemRefId: string,
111
- accSystemCode: string,
112
- ): Promise<string> {
113
- let sCompanyId = '';
114
- /*Assemble the hashtable key*/
115
- const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`;
116
- /*Check if the FinanceCompany has previously being loaded*/
117
- if (!FinanceCompany._htFinanceCompanyIds.get(sKey)) {
118
- /*Instantiate a new FinanceCompany*/
119
- const financeCompany = new FinanceCompany(
120
- compSystemCode,
121
- compSystemRefId,
122
- accSystemCode,
123
- );
124
-
125
- /*Retrieve the finance company from finance_Company table using compSystemCode,
126
- * CompSystemRefId and accSystemCode */
127
- const company = await FinanceCompany._financeCompanyRepository.findOne({
128
- where: {
129
- CompSystemCode: compSystemCode,
130
- CompSystemRefId: compSystemRefId,
131
- AccSystemCode: accSystemCode,
132
- },
133
- });
134
-
135
- /*Retrieve and store the companyId from the result*/
136
- financeCompany.ObjectId = company.CompanyId;
137
- sCompanyId = financeCompany.ObjectId;
138
-
139
- /*Add the details into the hashtable*/
140
- FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId);
141
- FinanceCompany._htFinanceCompanies.add(sCompanyId, financeCompany);
142
- }
143
-
144
- if (typeof FinanceCompany._htFinanceCompanyIds.get(sKey) === 'string') {
145
- sCompanyId = FinanceCompany._htFinanceCompanyIds.get(sKey);
146
- }
147
-
148
- return sCompanyId;
149
- }
150
-
151
- static async getFinanceCompany(companyId: string): Promise<FinanceCompany> {
152
- /*Check if the finance company is previously be loaded*/
153
- if (!FinanceCompany._htFinanceCompanies.get(companyId)) {
154
- /*Retrieve the finance company from finance_Company table using compSystemCode,
155
- * CompSystemRefId and accSystemCode */
156
- const company = await FinanceCompany._financeCompanyRepository.findOne({
157
- where: { CompanyId: companyId },
158
- });
159
-
160
- if (!company) {
161
- throw Error('No finance company found. Please create first.');
162
- }
163
-
164
- /*Using the result returned, instantiate a new FinanceCompany*/
165
- const compSystemCode = company.CompSystemCode;
166
- const compSystemRefId = company.CompSystemRefId;
167
- const accSystemCode = company.AccSystemCode;
168
-
169
- const financeCompany = new FinanceCompany(
170
- compSystemCode,
171
- compSystemRefId,
172
- accSystemCode,
173
- );
174
-
175
- /*Retrieve and store the CompanyId from the result*/
176
- financeCompany.ObjectId = company.CompanyId;
177
- financeCompany._CompanyId = company.CompanyId;
178
-
179
- /*Add the details into the hashtable*/
180
- const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`;
181
- FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId);
182
- FinanceCompany._htFinanceCompanies.add(
183
- financeCompany.ObjectId,
184
- financeCompany,
185
- );
186
- }
187
- // tslint:disable-next-line:no-console
188
- console.log('return from hash table');
189
- return FinanceCompany._htFinanceCompanies.get(companyId);
190
- }
191
-
192
- static async createFinanceCompany(
193
- dbTransaction: any,
194
- loginUser: LoginUserBase,
195
- compSystemCode: string,
196
- compSystemRefId: string,
197
- accSystemCode: string,
198
- ) {
199
- /*Instantiate a new FinanceCompany*/
200
- const financeCompany = new FinanceCompany(
201
- compSystemCode,
202
- compSystemRefId,
203
- accSystemCode,
204
- );
205
-
206
- /*Validating if the finance company already exists in finance_Company*/
207
- const company = await FinanceCompany._financeCompanyRepository.findOne({
208
- where: {
209
- CompSystemCode: compSystemCode,
210
- CompSystemRefId: compSystemRefId,
211
- AccSystemCode: accSystemCode,
212
- },
213
- });
214
-
215
- if (company) {
216
- throw Error(
217
- 'There is already another Finance Company with the compSystemCode, CompSystemRefId and accSystemCode specified.',
218
- );
219
- }
220
-
221
- /*Generating the companyId*/
222
- financeCompany.ObjectId = cuid();
223
-
224
- /*Save the FinanceCompany to the finance_Company table*/
225
- await FinanceCompany._financeCompanyRepository.create(
226
- {
227
- CompanyId: financeCompany.CompanyId,
228
- CompSystemCode: financeCompany.CompSystemCode,
229
- CompSystemRefId: financeCompany.CompSystemRefId,
230
- AccSystemCode: financeCompany.AccSystemCode,
231
- },
232
- {
233
- transaction: dbTransaction,
234
- },
235
- );
236
-
237
- /*Add the details to hashtable*/
238
- const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`;
239
- FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId);
240
- FinanceCompany._htFinanceCompanies.add(
241
- financeCompany.ObjectId,
242
- financeCompany,
243
- );
244
- }
245
-
246
- async createCustomer(
247
- dbTransaction: any,
248
- custSystemCode: string,
249
- custSystemRefId: string,
250
- customer: FinanceCustomerBase,
251
- ) {
252
- try {
253
- if (!customer.CustSystemCode || !customer.CustSystemRefId) {
254
- throw new Error(
255
- 'CustSystemCode and CustomerRefId are required fields.',
256
- );
257
- }
258
-
259
- const financeCustomerData =
260
- await FinanceCompany._FinanceCustomerRepository.findOne({
261
- where: {
262
- CompanyId: this._CompanyId,
263
- CustSystemCode: custSystemCode,
264
- CustSystemRefId: custSystemRefId,
265
- },
266
- });
267
-
268
- if (financeCustomerData) {
269
- throw new Error('Customer already created previously.');
270
- }
271
-
272
- // Retrieve the type of accounting system, API Key, and API secret based on SysCode from the Config.ts
273
- const accSystemRefId = await this.AccountingSystem.createCustomer({
274
- customer,
275
- });
276
-
277
- const newCustomer = await customer.save(
278
- accSystemRefId,
279
- custSystemCode,
280
- custSystemRefId,
281
- dbTransaction,
282
- );
283
-
284
- const payload = {
285
- Action: 'Create',
286
- Activity: 'Finance Customer Created',
287
- Description: `Customer (ID: ${newCustomer.CustomerId}) has been created for ${this.AccSystemCode}`,
288
- EntityType: 'finance_Customer',
289
- EntityValueBefore: JSON.stringify({}),
290
- EntityValueAfter: JSON.stringify(newCustomer),
291
- PerformedById: 'test',
292
- PerformedAt: new Date(),
293
- EntityId: newCustomer.CustomerId,
294
- };
295
-
296
- // await axios.post(`${config.commonApiUrl}/activity-histories`, payload);
297
- await axios.post(
298
- `${process.env.COMMON_API_URL}/activity-histories`,
299
- payload,
300
- );
301
-
302
- return customer;
303
- } catch (error) {
304
- throw error;
305
- }
306
- }
307
-
308
- async postJournal(dbTransaction: any, journalEntry: JournalEntry) {
309
- const debitTransactions = await journalEntry.DebitTransactions;
310
- const creditTransactions = await journalEntry.CreditTransactions;
311
- try {
312
- if (creditTransactions.length < 1 || debitTransactions?.length < 1) {
313
- throw new Error(
314
- 'There should be at least 1 debit ledger transaction and 1 credit ledger transaction in the journal entry',
315
- );
316
- }
317
-
318
- const totalCreditAmount = creditTransactions.reduce(
319
- (accumulator, currentValue) => accumulator + currentValue.CreditAmount,
320
- 0,
321
- );
322
- const totalDebitAmount = debitTransactions.reduce(
323
- (accumulator, currentValue) => accumulator + currentValue.DebitAmount,
324
- 0,
325
- );
326
-
327
- if (totalCreditAmount !== totalDebitAmount) {
328
- throw new Error(
329
- 'Credit ledger transaction and debit ledger transaction should the same amount',
330
- );
331
- }
332
-
333
- const newJournalEntry = await journalEntry.save('test', dbTransaction);
334
-
335
- const allLedgerTransactions =
336
- await FinanceCompany._LedgerTransactionRepository.findAll({});
337
-
338
- for (const ledgerTransaction of allLedgerTransactions) {
339
- ledgerTransaction.JournalEntryId = newJournalEntry.JournalEntryId;
340
- await ledgerTransaction.save();
341
- }
342
-
343
- await this.AccountingSystem.postJournalEntry(newJournalEntry);
344
-
345
- const payload = {
346
- Action: 'Create',
347
- Activity: 'Post Journal Entry',
348
- Description: `Journal Entry (ID: ${newJournalEntry.JournalEntryId}) has been created`,
349
- EntityType: 'JournalEntry',
350
- EntityValueBefore: JSON.stringify({}),
351
- EntityValueAfter: JSON.stringify(newJournalEntry),
352
- PerformedById: 'test',
353
- PerformedAt: new Date(),
354
- EntityId: newJournalEntry.JournalEntryId,
355
- };
356
-
357
- // await axios.post(`${config.commonApiUrl}/activity-histories`, payload);
358
- await axios.post(
359
- `${process.env.COMMON_API_URL}/activity-histories`,
360
- payload,
361
- );
362
- } catch (error) {
363
- throw error;
364
- }
365
- }
366
-
367
- async createAccount(dbTransaction: any, account: Account) {
368
- try {
369
- account.validateAccountValue();
370
-
371
- let createAccountPayload: any = {
372
- Name: account.Name,
373
- AcctNum: account.AccountNo,
374
- AccountType: account.AccountType,
375
- AccountSubType: account.AccountSubtype,
376
- };
377
-
378
- if (account.isParentAccountExists()) {
379
- createAccountPayload = {
380
- ...createAccountPayload,
381
- CurrencyRef: 'MYR',
382
- ParentRef: account.ParentAccountNo,
383
- SubAccount: true,
384
- };
385
- }
386
-
387
- const accSystemAccountId = await this.AccountingSystem.createAccount(
388
- createAccountPayload,
389
- );
390
-
391
- const newAccount = await account.save(
392
- accSystemAccountId,
393
- 'test',
394
- dbTransaction,
395
- );
396
-
397
- const payload = {
398
- Action: 'Create',
399
- Activity: 'Account Created',
400
- Description: `Account (ID: ${newAccount.AccountNo}) has been created`,
401
- EntityType: 'Account',
402
- EntityValueBefore: JSON.stringify({}),
403
- EntityValueAfter: JSON.stringify(newAccount),
404
- PerformedById: 'test',
405
- PerformedAt: new Date(),
406
- EntityId: newAccount.AccountNo,
407
- };
408
-
409
- // await axios.post(`${config.commonApiUrl}/activity-histories`, payload);
410
- await axios.post(
411
- `${process.env.COMMON_API_URL}/activity-histories`,
412
- payload,
413
- );
414
-
415
- return account;
416
- } catch (error) {
417
- throw error;
418
- }
419
- }
420
-
421
- /**
422
- * Issue an invoice
423
- *
424
- * @param dbTransaction - The database transaction to be used
425
- * @param loginUser - The user issuing the invoice
426
- * @param invoice - The document containing the invoice details
427
- * @param customer - The customer to be issued the invoice
428
- * @param dtAccountNo - The account number of the Account Receivable (AR) account to debit. If not provided, will debit the customer's default AR account
429
- *
430
- * @returns {Document} - Document object representing the full details of the invoice issued
431
- */
432
- async issueInvoice(
433
- /*todo: loginUser & customer is NOT supposed to be optional */
434
- dbTransaction: any,
435
- invoice: Document,
436
- loginUser?: LoginUserBase,
437
- customer?: FinanceCustomerBase,
438
- dtAccountNo?: string,
439
- ): Promise<Document> {
440
- try {
441
- /*Check if the invoice number already exists (should be unique)*/
442
- const duplicateInvoice = await FinanceCompany._DocumentRepository.findOne(
443
- {
444
- where: {
445
- DocNo: invoice.DocNo,
446
- },
447
- transaction: dbTransaction,
448
- },
449
- );
450
-
451
- if (duplicateInvoice) {
452
- throw new Error('Invoice number already exists');
453
- }
454
-
455
- const documentItems = await invoice.DocumentItems;
456
-
457
- /*Check if the document has at least 1 document item*/
458
- if (!documentItems.length) {
459
- throw new Error('Document must have at least 1 document item');
460
- }
461
-
462
- /*Check if each document item has CtAccountNo provided*/
463
- for (const invoiceItem of documentItems) {
464
- if (!invoiceItem.CtAccountNo) {
465
- throw new Error(
466
- 'Each document item should have CtAccountNo provided',
467
- );
468
- }
469
- }
470
-
471
- /*Set up the document type*/
472
- invoice.DocType = DocType.INVOICE;
473
-
474
- /*Saving the document and document items to the database*/
475
- await FinanceCompany._DocumentRepository.create(
476
- {
477
- DocNo: invoice.DocNo,
478
- DocType: invoice.DocType,
479
- DocDate: invoice.DocDate,
480
- CompanyId: invoice.CompanyId,
481
- Currency: invoice.Currency,
482
- Amount: invoice.Amount,
483
- Description: invoice.Description,
484
- Status: invoice.Status,
485
- IssuedById: invoice.IssuedById,
486
- IssuedToId: invoice.IssuedToId,
487
- IssuedToType: invoice.IssuedToType,
488
- CreatedById: invoice.CreatedById,
489
- CreatedAt: new Date(),
490
- UpdatedById: invoice.UpdatedById,
491
- UpdatedAt: new Date(),
492
- DocPDFFileMediaId: invoice.DocPDFFileMediaId,
493
- DocHTMLFileMediaId: invoice.DocHTMLFileMediaId,
494
- AccSystemRefId: invoice.AccSystemRefId,
495
- PostedToAccSystemYN: invoice.PostedToAccSystemYN,
496
- PostedById: invoice.PostedById,
497
- PostedDateTime: new Date(),
498
- UseAccSystemDocYN: invoice.UseAccSystemDocYN,
499
- },
500
- {
501
- transaction: dbTransaction,
502
- },
503
- );
504
-
505
- // Q: IS THIS ONE BELOW NECESSARY? WHY WOULD WE CREATE THE SAME DOCUMENT ITEM
506
-
507
- // documentItems.forEach(async (documentItem) => {
508
- // await FinanceCompany._DocumentItemRepository.create(
509
- // {
510
- // DocumentItemId: cuid(),
511
- // DocNo: documentItem.DocNo,
512
- // Name: documentItem.Name,
513
- // NameBM: documentItem.NameBM,
514
- // Description: documentItem.Description,
515
- // ItemId: documentItem.ItemId,
516
- // ItemType: documentItem.ItemType,
517
- // ItemSKU: documentItem.ItemSKU,
518
- // ItemSerialNo: documentItem.ItemSerialNo,
519
- // Currency: documentItem.Currency,
520
- // UnitPrice: documentItem.UnitPrice,
521
- // Quantity: documentItem.Quantity,
522
- // QuantityUOM: documentItem.QuantityUOM,
523
- // Amount: documentItem.Amount,
524
- // TaxCode: documentItem.TaxCode,
525
- // TaxAmount: documentItem.TaxAmount,
526
- // TaxRate: documentItem.TaxRate,
527
- // TaxInclusiveYN: documentItem.TaxInclusiveYN,
528
- // DtAccountNo: documentItem.DtAccountNo,
529
- // CtAccountNo: documentItem.CtAccountNo,
530
- // },
531
- // {
532
- // transaction: dbTransaction,
533
- // },
534
- // );
535
- // });
536
-
537
- /*Generating the invoice*/
538
- if (invoice.UseAccSystemDocYN === 'Y') {
539
- /*todo: Posting to accounting system to generate invoice*/
540
- await this.AccountingSystem.createInvoice(invoice);
541
- } else {
542
- /*todo: check config file to see which invoice template is to be used for specific project*/
543
-
544
- /*Generating invoice based on template*/
545
- invoice.generateInvoice(invoice.IssuedById);
546
- }
547
-
548
- const journalEntry = new JournalEntry(dbTransaction);
549
- const transactionDate = new Date();
550
-
551
- const ledgerTransaction = await journalEntry.newLedgerTransaction(
552
- TransactionTypeOptions.DEBIT,
553
- );
554
-
555
- if (dtAccountNo) {
556
- /*Transacting using AR account provided*/
557
- ledgerTransaction.AccountNo = dtAccountNo;
558
- } else {
559
- /*Transacting based on default customer AR account*/
560
- ledgerTransaction.AccountNo = customer.CustSystemCode + '-AR';
561
- }
562
-
563
- ledgerTransaction.Currency = invoice.Currency;
564
- ledgerTransaction.DebitAmount = invoice.Amount;
565
- ledgerTransaction.Date = transactionDate;
566
- ledgerTransaction.Name = invoice.DocNo;
567
-
568
- for (const invoiceItem of documentItems) {
569
- const itemLedger = await journalEntry.newLedgerTransaction(
570
- TransactionTypeOptions.CREDIT,
571
- );
572
- itemLedger.AccountNo = invoiceItem.CtAccountNo;
573
- itemLedger.Currency = invoiceItem.Currency;
574
- itemLedger.CreditAmount = invoiceItem.Amount;
575
- itemLedger.Date = transactionDate;
576
- itemLedger.Name = invoiceItem.Name;
577
- }
578
-
579
- this.postJournal(dbTransaction, journalEntry);
580
-
581
- /*todo: uncomment below later*/
582
-
583
- const payload = {
584
- Action: 'Create',
585
- Activity: 'Issuing an Invoice',
586
- Description: `Ledger Transaction (ID: ${ledgerTransaction.TransactionId}) has been created`,
587
- EntityType: 'LedgerTransaction',
588
- EntityValueBefore: JSON.stringify({}),
589
- EntityValueAfter: JSON.stringify(ledgerTransaction),
590
- PerformedById: 'test',
591
- PerformedAt: new Date(),
592
- EntityId: ledgerTransaction.TransactionId,
593
- };
594
-
595
- await axios.post(
596
- `${process.env.COMMON_API_URL}/activity-histories`,
597
- payload,
598
- );
599
-
600
- return invoice;
601
- } catch (err) {
602
- // tslint:disable-next-line:no-console
603
- console.log('Issue invoice err: ', err);
604
- throw err;
605
- }
606
- }
607
-
608
- /**
609
- * Issue a debit note
610
- *
611
- * @param dbTransaction - The database transaction to be used
612
- * @param loginUser - The user issuing the invoice
613
- * @param invoice - The document containing the invoice details
614
- * @param customer - The customer to be issued the invoice
615
- * @param dtAccountNo - The account number of the Account Receivable (AR) account to debit. If not provided, will debit the customer's default AR account
616
- *
617
- * @returns {Document} - Document object representing the full details of the invoice issued
618
- */
619
- async issueDebitNote(
620
- dbTransaction: any,
621
- loginUser: LoginUserBase,
622
- invoice: Document,
623
- customer: FinanceCustomerBase,
624
- dtAccountNo?: string,
625
- ): Promise<Document> {
626
- try {
627
- /*Check if the invoice number already exists (should be unique)*/
628
- const duplicateInvoice = await FinanceCompany._DocumentRepository.findOne(
629
- {
630
- where: {
631
- DocNo: invoice.DocNo,
632
- },
633
- transaction: dbTransaction,
634
- },
635
- );
636
-
637
- if (duplicateInvoice) {
638
- throw new Error('Invoice number already exists');
639
- }
640
-
641
- const documentItems = await invoice.DocumentItems;
642
-
643
- /*Check if the document has at least 1 document item*/
644
- if (!documentItems.length) {
645
- throw new Error('Document must have at least 1 document item');
646
- }
647
-
648
- /*Check if each document item has CtAccountNo provided*/
649
- for (const invoiceItem of documentItems) {
650
- if (!invoiceItem.CtAccountNo) {
651
- throw new Error(
652
- 'Each document item should have CtAccountNo provided',
653
- );
654
- }
655
- }
656
-
657
- /*Set up the document type*/
658
- invoice.DocType = DocType.DEBIT_NOTE;
659
-
660
- /*Saving the document and document items to the database*/
661
- await FinanceCompany._DocumentRepository.create(
662
- {
663
- DocNo: invoice.DocNo,
664
- DocType: invoice.DocType,
665
- DocDate: invoice.DocDate,
666
- CompanyId: invoice.CompanyId,
667
- Currency: invoice.Currency,
668
- Amount: invoice.Amount,
669
- Description: invoice.Description,
670
- Status: invoice.Status,
671
- IssuedById: loginUser.ObjectId,
672
- IssuedToId: customer.ObjectId,
673
- IssuedToType: invoice.IssuedToType,
674
- CreatedById: loginUser.ObjectId,
675
- CreatedAt: new Date(),
676
- UpdatedById: loginUser.ObjectId,
677
- UpdatedAt: new Date(),
678
- DocPDFFileMediaId: invoice.DocPDFFileMediaId,
679
- DocHTMLFileMediaId: invoice.DocHTMLFileMediaId,
680
- AccSystemRefId: invoice.AccSystemRefId,
681
- PostedToAccSystemYN: invoice.PostedToAccSystemYN,
682
- PostedById: invoice.PostedById,
683
- PostedDateTime: new Date(),
684
- UseAccSystemDocYN: invoice.UseAccSystemDocYN,
685
- },
686
- {
687
- transaction: dbTransaction,
688
- },
689
- );
690
-
691
- documentItems.forEach(async (documentItem) => {
692
- await FinanceCompany._DocumentItemRepository.create(
693
- {
694
- DocumentItemId: cuid(),
695
- DocNo: documentItem.DocNo,
696
- Name: documentItem.Name,
697
- NameBM: documentItem.NameBM,
698
- Description: documentItem.Description,
699
- ItemId: documentItem.ItemId,
700
- ItemType: documentItem.ItemType,
701
- ItemSKU: documentItem.ItemSKU,
702
- ItemSerialNo: documentItem.ItemSerialNo,
703
- Currency: documentItem.Currency,
704
- UnitPrice: documentItem.UnitPrice,
705
- Quantity: documentItem.Quantity,
706
- QuantityUOM: documentItem.QuantityUOM,
707
- Amount: documentItem.Amount,
708
- TaxCode: documentItem.TaxCode,
709
- TaxAmount: documentItem.TaxAmount,
710
- TaxRate: documentItem.TaxRate,
711
- TaxInclusiveYN: documentItem.TaxInclusiveYN,
712
- DtAccountNo: documentItem.DtAccountNo,
713
- CtAccountNo: documentItem.CtAccountNo,
714
- },
715
- {
716
- transaction: dbTransaction,
717
- },
718
- );
719
- });
720
-
721
- /*Generating the invoice*/
722
- if (invoice.UseAccSystemDocYN === 'Y') {
723
- /*todo: Posting to accounting system to generate invoice*/
724
- await this.AccountingSystem.createInvoice(invoice);
725
- } else {
726
- /*todo: check config file to see which invoice template is to be used for specific project*/
727
-
728
- /*Generating invoice based on template*/
729
- invoice.generateInvoice(loginUser.IDNo, customer);
730
- }
731
-
732
- const journalEntry = new JournalEntry(dbTransaction);
733
- const transactionDate = new Date();
734
-
735
- const debitTransaction = await journalEntry.newLedgerTransaction(
736
- TransactionTypeOptions.DEBIT,
737
- );
738
-
739
- if (dtAccountNo) {
740
- /*Transacting using AR account provided*/
741
- debitTransaction.AccountNo = dtAccountNo;
742
- } else {
743
- /*Transacting based on default customer AR account*/
744
- debitTransaction.AccountNo = customer.CustSystemCode + '-AR';
745
- }
746
-
747
- debitTransaction.Currency = invoice.Currency;
748
- debitTransaction.DebitAmount = invoice.Amount;
749
- debitTransaction.Date = transactionDate;
750
- debitTransaction.Name = invoice.DocNo;
751
-
752
- for (const invoiceItem of documentItems) {
753
- const itemLedger = await journalEntry.newLedgerTransaction(
754
- TransactionTypeOptions.CREDIT,
755
- );
756
- itemLedger.AccountNo = invoiceItem.CtAccountNo;
757
- itemLedger.Currency = invoiceItem.Currency;
758
- itemLedger.CreditAmount = invoiceItem.Amount;
759
- itemLedger.Date = transactionDate;
760
- itemLedger.Name = invoiceItem.Name;
761
- }
762
-
763
- this.postJournal(dbTransaction, journalEntry);
764
-
765
- const payload = {
766
- Action: 'Create',
767
- Activity: 'Issuing a Debit Note',
768
- Description: `Debit Transaction (ID: ${debitTransaction.TransactionId}) has been created`,
769
- EntityType: 'DebitTransaction',
770
- EntityValueBefore: JSON.stringify({}),
771
- EntityValueAfter: JSON.stringify(debitTransaction),
772
- PerformedById: 'test',
773
- PerformedAt: new Date(),
774
- EntityId: debitTransaction.TransactionId,
775
- };
776
-
777
- await axios.post(`${config.commonApiUrl}/activity-histories`, payload);
778
-
779
- return invoice;
780
- } catch (err) {
781
- // tslint:disable-next-line:no-console
782
- console.log('Issue debit note err: ', err);
783
- throw err;
784
- }
785
- }
786
-
787
- /**
788
- * Issue a credit note
789
- *
790
- * @param dbTransaction - The database transaction to be used
791
- * @param loginUser - The user issuing the credit note
792
- * @param creditNote - The document containing the credit note details
793
- * @param customer - The customer to be issued the credit note
794
- * @param ctAccountNo - The account number of the Account Payable (AP) account to debit. If not provided, will debit the customer's default AR account
795
- *
796
- * @returns {Document} - Document object representing the full details of the invoice issued
797
- */
798
- async issueCreditNote(
799
- dbTransaction: any,
800
- loginUser: LoginUserBase,
801
- creditNote: Document,
802
- customer: FinanceCustomerBase,
803
- ctAccountNo?: string,
804
- ): Promise<Document> {
805
- try {
806
- /*Check if the invoice number already exists (should be unique)*/
807
- const duplicateCreditNote =
808
- await FinanceCompany._DocumentRepository.findOne({
809
- where: {
810
- DocNo: creditNote.DocNo,
811
- },
812
- transaction: dbTransaction,
813
- });
814
-
815
- if (duplicateCreditNote) {
816
- throw new Error('Invoice number already exists');
817
- }
818
-
819
- const documentItems = await creditNote.DocumentItems;
820
-
821
- /*Check if the document has at least 1 document item*/
822
- if (!documentItems.length) {
823
- throw new Error('Document must have at least 1 document item');
824
- }
825
-
826
- /*Check if each document item has CtAccountNo provided*/
827
- for (const invoiceItem of documentItems) {
828
- if (!invoiceItem.CtAccountNo) {
829
- throw new Error(
830
- 'Each document item should have CtAccountNo provided',
831
- );
832
- }
833
- }
834
-
835
- /*Set up the document type*/
836
- creditNote.DocType = DocType.DEBIT_NOTE;
837
-
838
- /*Saving the document and document items to the database*/
839
- await FinanceCompany._DocumentRepository.create(
840
- {
841
- DocNo: creditNote.DocNo,
842
- DocType: creditNote.DocType,
843
- DocDate: creditNote.DocDate,
844
- CompanyId: creditNote.CompanyId,
845
- Currency: creditNote.Currency,
846
- Amount: creditNote.Amount,
847
- Description: creditNote.Description,
848
- Status: creditNote.Status,
849
- IssuedById: loginUser.ObjectId,
850
- IssuedToId: customer.ObjectId,
851
- IssuedToType: creditNote.IssuedToType,
852
- CreatedById: loginUser.ObjectId,
853
- CreatedAt: new Date(),
854
- UpdatedById: loginUser.ObjectId,
855
- UpdatedAt: new Date(),
856
- DocPDFFileMediaId: creditNote.DocPDFFileMediaId,
857
- DocHTMLFileMediaId: creditNote.DocHTMLFileMediaId,
858
- AccSystemRefId: creditNote.AccSystemRefId,
859
- PostedToAccSystemYN: creditNote.PostedToAccSystemYN,
860
- PostedById: creditNote.PostedById,
861
- PostedDateTime: new Date(),
862
- UseAccSystemDocYN: creditNote.UseAccSystemDocYN,
863
- },
864
- {
865
- transaction: dbTransaction,
866
- },
867
- );
868
-
869
- documentItems.forEach(async (documentItem) => {
870
- await FinanceCompany._DocumentItemRepository.create(
871
- {
872
- DocumentItemId: documentItem.DocumentItemId,
873
- DocNo: documentItem.DocNo,
874
- Name: documentItem.Name,
875
- NameBM: documentItem.NameBM,
876
- Description: documentItem.Description,
877
- ItemId: documentItem.ItemId,
878
- ItemType: documentItem.ItemType,
879
- ItemSKU: documentItem.ItemSKU,
880
- ItemSerialNo: documentItem.ItemSerialNo,
881
- Currency: documentItem.Currency,
882
- UnitPrice: documentItem.UnitPrice,
883
- Quantity: documentItem.Quantity,
884
- QuantityUOM: documentItem.QuantityUOM,
885
- Amount: documentItem.Amount,
886
- TaxCode: documentItem.TaxCode,
887
- TaxAmount: documentItem.TaxAmount,
888
- TaxRate: documentItem.TaxRate,
889
- TaxInclusiveYN: documentItem.TaxInclusiveYN,
890
- DtAccountNo: documentItem.DtAccountNo,
891
- CtAccountNo: documentItem.CtAccountNo,
892
- },
893
- {
894
- transaction: dbTransaction,
895
- },
896
- );
897
- });
898
-
899
- /*Generating the credit note*/
900
- if (creditNote.UseAccSystemDocYN === 'Y') {
901
- /*todo: Posting to accounting system to generate creditNote*/
902
- await this.AccountingSystem.createCreditNote(creditNote);
903
- } else {
904
- /*todo: check config file to see which invoice template is to be used for specific project*/
905
-
906
- /*Generating credit note based on template*/
907
- creditNote.generateCreditNote(loginUser.IDNo, customer);
908
- }
909
-
910
- const journalEntry = new JournalEntry(dbTransaction);
911
- const transactionDate = new Date();
912
-
913
- const creditTransaction = await journalEntry.newLedgerTransaction(
914
- TransactionTypeOptions.CREDIT,
915
- );
916
-
917
- if (ctAccountNo) {
918
- /*Transacting using AR account provided*/
919
- creditTransaction.AccountNo = ctAccountNo;
920
- } else {
921
- /*Transacting based on default customer AR account*/
922
- creditTransaction.AccountNo = customer.CustSystemCode + '-AP';
923
- }
924
-
925
- creditTransaction.Currency = creditNote.Currency;
926
- creditTransaction.CreditAmount = creditNote.Amount;
927
- creditTransaction.Date = transactionDate;
928
- creditTransaction.Name = creditNote.DocNo;
929
-
930
- for (const invoiceItem of documentItems) {
931
- const itemLedger = await journalEntry.newLedgerTransaction(
932
- TransactionTypeOptions.DEBIT,
933
- );
934
- itemLedger.AccountNo = invoiceItem.CtAccountNo;
935
- itemLedger.Currency = invoiceItem.Currency;
936
- itemLedger.DebitAmount = invoiceItem.Amount;
937
- itemLedger.Date = transactionDate;
938
- itemLedger.Name = invoiceItem.Name;
939
- }
940
-
941
- this.postJournal(dbTransaction, journalEntry);
942
-
943
- const payload = {
944
- Action: 'Create',
945
- Activity: 'Issuing a Credit Note Transaction',
946
- Description: `Credit Transaction (ID: ${creditTransaction.TransactionId}) has been created`,
947
- EntityType: 'CreditTransaction',
948
- EntityValueBefore: JSON.stringify({}),
949
- EntityValueAfter: JSON.stringify(creditTransaction),
950
- PerformedById: 'test',
951
- PerformedAt: transactionDate,
952
- EntityId: creditTransaction.TransactionId,
953
- };
954
-
955
- await axios.post(`${config.commonApiUrl}/activity-histories`, payload);
956
-
957
- return creditNote;
958
- } catch (err) {
959
- // tslint:disable-next-line:no-console
960
- console.log('Issue credit note err: ', err);
961
- throw err;
962
- }
963
- }
964
-
965
- /**
966
- * Register a payment collected
967
- *
968
- * @param dbTransaction - The database transaction to be used
969
- * @param loginUser - The user collecting the payment
970
- * @param payment - The payment object containing payment details
971
- * @param customer - The customer making the payment
972
- * @param ctAccountNo - The customer's Account Receivable (AR) account to credit
973
- *
974
- * @returns {Payment} - Payment object representing the full detals of the payment collection recorded
975
- */
976
- async collectPayment(
977
- dbTransaction: any,
978
- loginUser: LoginUserBase,
979
- payment: Payment,
980
- customer: FinanceCustomerBase,
981
- ctAccountNo?: string,
982
- ): Promise<Payment> {
983
- let paymentMethodType: PaymentMethodType;
984
- try {
985
- paymentMethodType = new PaymentMethodType(
986
- dbTransaction,
987
- payment.MethodTypeId,
988
- );
989
-
990
- const paymentItems = await payment.PaymentItems;
991
- if (paymentItems.length < 1) {
992
- throw new Error(
993
- 'Atleast one payment item is required to identify what payment is being paid for',
994
- );
995
- }
996
-
997
- payment.PaymentId = cuid();
998
-
999
- await FinanceCompany._PaymentRepository.create(
1000
- {
1001
- PaymentId: payment.PaymentId,
1002
- PaymentType: payment.PaymentType,
1003
- PaymentDate: payment.PaymentDate,
1004
- CompanyId: this.CompanyId,
1005
- MethodTypeId: payment.MethodTypeId,
1006
- Currency: payment.Currency,
1007
- Amount: payment.Amount,
1008
- Status: PaymentStatus.SUCCESSFUL,
1009
- TransactionId: payment.TransactionId,
1010
- TransactionApprovalCode: payment.TransactionApprovalCode,
1011
- TransactionAccountName: payment.TransactionAccountName,
1012
- TransactionAccountNo: payment.TransactionAccountNo,
1013
- ReceivedBy: payment.ReceivedBy,
1014
- PostedToAccSystemYN: 'N',
1015
- UpdatedAt: new Date(),
1016
- UpdatedBy: loginUser.ObjectId,
1017
- CreatedAt: new Date(),
1018
- CreatedBy: loginUser.ObjectId,
1019
- },
1020
- { transaction: dbTransaction },
1021
- );
1022
-
1023
- paymentItems.forEach(async (paymentItem) => {
1024
- await FinanceCompany._PaymentItemRepository.create(
1025
- {
1026
- PaymentId: payment.PaymentId,
1027
- PayForObjectId: paymentItem.PayForObjectId,
1028
- PayForObjectType: paymentItem.PayForObjectType,
1029
- Currency: paymentItem.Currency,
1030
- Amount: paymentItem.Amount,
1031
- },
1032
- { transaction: dbTransaction },
1033
- );
1034
- });
1035
-
1036
- const journalEntry = new JournalEntry(dbTransaction);
1037
- //Temporary fix to successfully save journal entry in PostJournal
1038
- journalEntry.init({
1039
- CompanyId: this.CompanyId,
1040
- Name: 'Collect-payments for ' + payment.PaymentId,
1041
- });
1042
- const transactionDate = new Date();
1043
-
1044
- const debitLT = await journalEntry.newLedgerTransaction(
1045
- TransactionTypeOptions.DEBIT,
1046
- );
1047
- debitLT.AccountNo = paymentMethodType.AccountNo;
1048
- debitLT.Currency = payment.Currency;
1049
- debitLT.DebitAmount = payment.Amount;
1050
- debitLT.Date = transactionDate;
1051
- debitLT.Name = customer.FullName;
1052
-
1053
- const creditLT = await journalEntry.newLedgerTransaction(
1054
- TransactionTypeOptions.CREDIT,
1055
- );
1056
-
1057
- if (ctAccountNo) {
1058
- creditLT.AccountNo = ctAccountNo;
1059
- } else {
1060
- // const config = getConfig();
1061
- creditLT.AccountNo = customer.CustSystemCode + '-AR';
1062
- }
1063
- creditLT.Currency = payment.Currency;
1064
- creditLT.CreditAmount = payment.Amount;
1065
- creditLT.Date = transactionDate;
1066
- creditLT.Name = paymentMethodType.Name;
1067
-
1068
- await this.postJournal(dbTransaction, journalEntry);
1069
-
1070
- /*todo: saving a record into the activity history table*/
1071
-
1072
- return payment;
1073
- } catch (error) {
1074
- if (error instanceof RecordNotFoundError) {
1075
- throw new Error('Invalid PaymentMethodType id');
1076
- } else {
1077
- throw error;
1078
- }
1079
- }
1080
- }
1081
- }
1
+ import axios from 'axios';
2
+ import * as cuid from 'cuid';
3
+ import {
4
+ HashTable,
5
+ LoginUserBase,
6
+ ObjectBase,
7
+ RecordNotFoundError,
8
+ } from '@tomei/general';
9
+ import Account from '../account/account';
10
+ import JournalEntry from '../journal-entry/journal-entry';
11
+ import FinanceCustomerBase from '../customer/customer';
12
+ import Document from '../document/document';
13
+ import { IAccountSystem } from '../interfaces';
14
+ import { FinanceCompanyRepository } from './finance-company.repository';
15
+ import { FinanceCustomerRepository } from '../customer/finance-customer.repository';
16
+ import { LedgerTransactionRepository } from '../ledger-transaction/ledger-transaction.repository';
17
+ import { DocType, PaymentStatus, TransactionTypeOptions } from '../enum';
18
+ import PaymentMethodType from '../payment-method-type/payment-method-type';
19
+ import { PaymentRepository } from '../payment/payment.repository';
20
+ import { PaymentItemRepository } from '../payment-item/payment-item.repository';
21
+ import Payment from '../payment/payment';
22
+ import { DocumentRepository } from '../document/document.repository';
23
+ import { DocumentItemRepository } from '../document/document-item.repository';
24
+ import { PaymentMethodRepository } from '../payment-method/payment-method.repository';
25
+ import { PaymentMethodTypeRepository } from '../payment-method-type/payment-method-type.repository';
26
+ import PaymentMethod from '../payment-method/payment-method';
27
+
28
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
29
+ const { getConfig } = require('../config');
30
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
31
+ const config = getConfig();
32
+
33
+ export default class FinanceCompany extends ObjectBase {
34
+ private _CompanyId = 'New';
35
+ private _CompSystemCode = '';
36
+ private _CompSystemRefId = '';
37
+ private _AccSystemCode = '';
38
+ private static _htFinanceCompanyIds = new HashTable();
39
+ private static _htFinanceCompanies = new HashTable();
40
+ private static _financeCompanyRepository = new FinanceCompanyRepository();
41
+ private static _PaymentRepository = new PaymentRepository();
42
+ private static _PaymentItemRepository = new PaymentItemRepository();
43
+ private static _PaymentMethodRepository = new PaymentMethodRepository();
44
+ private static _PaymentMethodTypeRepository =
45
+ new PaymentMethodTypeRepository();
46
+ private static _DocumentRepository = new DocumentRepository();
47
+ private static _DocumentItemRepository = new DocumentItemRepository();
48
+ private static _FinanceCustomerRepository = new FinanceCustomerRepository();
49
+ private static _LedgerTransactionRepository =
50
+ new LedgerTransactionRepository();
51
+ private _AccountingSystem: IAccountSystem;
52
+
53
+ private _PaymentMethods = null;
54
+
55
+ get CompSystemCode(): string {
56
+ return this._CompSystemCode;
57
+ }
58
+
59
+ private set CompSystemCode(code: string) {
60
+ this._CompSystemCode = code;
61
+ }
62
+
63
+ get CompSystemRefId() {
64
+ return this._CompSystemRefId;
65
+ }
66
+
67
+ set CompSystemRefId(id: string) {
68
+ this._CompSystemRefId = id;
69
+ }
70
+
71
+ get AccSystemCode() {
72
+ return this._AccSystemCode;
73
+ }
74
+
75
+ private set AccSystemCode(code: string) {
76
+ this._AccSystemCode = code;
77
+ }
78
+
79
+ get CompanyId() {
80
+ return this._CompanyId;
81
+ }
82
+
83
+ get ObjectId() {
84
+ return this._CompanyId;
85
+ }
86
+
87
+ private set ObjectId(id: string) {
88
+ this._CompanyId = id;
89
+ }
90
+
91
+ get ObjectName() {
92
+ return `${this.CompSystemCode}-${this.CompSystemRefId}-${this.AccSystemCode}`;
93
+ }
94
+
95
+ get TableName() {
96
+ return 'finance_Company';
97
+ }
98
+
99
+ get AccountingSystem(): IAccountSystem {
100
+ return this._AccountingSystem;
101
+ }
102
+
103
+ set AccountingSystem(system: IAccountSystem) {
104
+ this._AccountingSystem = system;
105
+ }
106
+
107
+ constructor(
108
+ compSystemCode: string,
109
+ compSystemRefId: string,
110
+ accSystemCode: string,
111
+ ) {
112
+ super();
113
+ this.CompSystemCode = compSystemCode;
114
+ this.CompSystemRefId = compSystemRefId;
115
+ this.AccSystemCode = accSystemCode;
116
+ }
117
+
118
+ static async getFinanceCompanyId(
119
+ compSystemCode: string,
120
+ compSystemRefId: string,
121
+ accSystemCode: string,
122
+ ): Promise<string> {
123
+ let sCompanyId = '';
124
+ /*Assemble the hashtable key*/
125
+ const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`;
126
+ /*Check if the FinanceCompany has previously being loaded*/
127
+ if (!FinanceCompany._htFinanceCompanyIds.get(sKey)) {
128
+ /*Instantiate a new FinanceCompany*/
129
+ const financeCompany = new FinanceCompany(
130
+ compSystemCode,
131
+ compSystemRefId,
132
+ accSystemCode,
133
+ );
134
+
135
+ /*Retrieve the finance company from finance_Company table using compSystemCode,
136
+ * CompSystemRefId and accSystemCode */
137
+ const company = await FinanceCompany._financeCompanyRepository.findOne({
138
+ where: {
139
+ CompSystemCode: compSystemCode,
140
+ CompSystemRefId: compSystemRefId,
141
+ AccSystemCode: accSystemCode,
142
+ },
143
+ });
144
+
145
+ /*Retrieve and store the companyId from the result*/
146
+ financeCompany.ObjectId = company.CompanyId;
147
+ sCompanyId = financeCompany.ObjectId;
148
+
149
+ /*Add the details into the hashtable*/
150
+ FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId);
151
+ FinanceCompany._htFinanceCompanies.add(sCompanyId, financeCompany);
152
+ }
153
+
154
+ if (typeof FinanceCompany._htFinanceCompanyIds.get(sKey) === 'string') {
155
+ sCompanyId = FinanceCompany._htFinanceCompanyIds.get(sKey);
156
+ }
157
+
158
+ return sCompanyId;
159
+ }
160
+
161
+ static async getFinanceCompany(companyId: string): Promise<FinanceCompany> {
162
+ /*Check if the finance company is previously be loaded*/
163
+ if (!FinanceCompany._htFinanceCompanies.get(companyId)) {
164
+ /*Retrieve the finance company from finance_Company table using compSystemCode,
165
+ * CompSystemRefId and accSystemCode */
166
+ const company = await FinanceCompany._financeCompanyRepository.findOne({
167
+ where: { CompanyId: companyId },
168
+ });
169
+
170
+ if (!company) {
171
+ throw Error('No finance company found. Please create first.');
172
+ }
173
+
174
+ /*Using the result returned, instantiate a new FinanceCompany*/
175
+ const compSystemCode = company.CompSystemCode;
176
+ const compSystemRefId = company.CompSystemRefId;
177
+ const accSystemCode = company.AccSystemCode;
178
+
179
+ const financeCompany = new FinanceCompany(
180
+ compSystemCode,
181
+ compSystemRefId,
182
+ accSystemCode,
183
+ );
184
+
185
+ /*Retrieve and store the CompanyId from the result*/
186
+ financeCompany.ObjectId = company.CompanyId;
187
+ financeCompany._CompanyId = company.CompanyId;
188
+
189
+ /*Add the details into the hashtable*/
190
+ const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`;
191
+ FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId);
192
+ FinanceCompany._htFinanceCompanies.add(
193
+ financeCompany.ObjectId,
194
+ financeCompany,
195
+ );
196
+ }
197
+ // tslint:disable-next-line:no-console
198
+ console.log('return from hash table');
199
+ return FinanceCompany._htFinanceCompanies.get(companyId);
200
+ }
201
+
202
+ static async createFinanceCompany(
203
+ dbTransaction: any,
204
+ loginUser: LoginUserBase,
205
+ compSystemCode: string,
206
+ compSystemRefId: string,
207
+ accSystemCode: string,
208
+ ) {
209
+ /*Instantiate a new FinanceCompany*/
210
+ const financeCompany = new FinanceCompany(
211
+ compSystemCode,
212
+ compSystemRefId,
213
+ accSystemCode,
214
+ );
215
+
216
+ /*Validating if the finance company already exists in finance_Company*/
217
+ const company = await FinanceCompany._financeCompanyRepository.findOne({
218
+ where: {
219
+ CompSystemCode: compSystemCode,
220
+ CompSystemRefId: compSystemRefId,
221
+ AccSystemCode: accSystemCode,
222
+ },
223
+ });
224
+
225
+ if (company) {
226
+ throw Error(
227
+ 'There is already another Finance Company with the compSystemCode, CompSystemRefId and accSystemCode specified.',
228
+ );
229
+ }
230
+
231
+ /*Generating the companyId*/
232
+ financeCompany.ObjectId = cuid();
233
+
234
+ /*Save the FinanceCompany to the finance_Company table*/
235
+ await FinanceCompany._financeCompanyRepository.create(
236
+ {
237
+ CompanyId: financeCompany.CompanyId,
238
+ CompSystemCode: financeCompany.CompSystemCode,
239
+ CompSystemRefId: financeCompany.CompSystemRefId,
240
+ AccSystemCode: financeCompany.AccSystemCode,
241
+ },
242
+ {
243
+ transaction: dbTransaction,
244
+ },
245
+ );
246
+
247
+ /*Add the details to hashtable*/
248
+ const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`;
249
+ FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId);
250
+ FinanceCompany._htFinanceCompanies.add(
251
+ financeCompany.ObjectId,
252
+ financeCompany,
253
+ );
254
+ }
255
+
256
+ async createCustomer(
257
+ dbTransaction: any,
258
+ custSystemCode: string,
259
+ custSystemRefId: string,
260
+ customer: FinanceCustomerBase,
261
+ ) {
262
+ try {
263
+ if (!customer.CustSystemCode || !customer.CustSystemRefId) {
264
+ throw new Error(
265
+ 'CustSystemCode and CustomerRefId are required fields.',
266
+ );
267
+ }
268
+
269
+ const financeCustomerData =
270
+ await FinanceCompany._FinanceCustomerRepository.findOne({
271
+ where: {
272
+ CompanyId: this._CompanyId,
273
+ CustSystemCode: custSystemCode,
274
+ CustSystemRefId: custSystemRefId,
275
+ },
276
+ });
277
+
278
+ if (financeCustomerData) {
279
+ throw new Error('Customer already created previously.');
280
+ }
281
+
282
+ // Retrieve the type of accounting system, API Key, and API secret based on SysCode from the Config.ts
283
+ const customerId = await this.AccountingSystem.createCustomer({
284
+ customer,
285
+ });
286
+
287
+ const newCustomer = await customer.save(
288
+ customerId,
289
+ custSystemCode,
290
+ custSystemRefId,
291
+ dbTransaction,
292
+ );
293
+
294
+ const payload = {
295
+ Action: 'Create',
296
+ Activity: 'Finance Customer Created',
297
+ Description: `Customer (ID: ${newCustomer.CustomerId}) has been created for ${this.AccSystemCode}`,
298
+ EntityType: 'finance_Customer',
299
+ EntityValueBefore: JSON.stringify({}),
300
+ EntityValueAfter: JSON.stringify(newCustomer),
301
+ PerformedById: 'test',
302
+ PerformedAt: new Date(),
303
+ EntityId: newCustomer.CustomerId,
304
+ };
305
+
306
+ await axios.post(
307
+ `${process.env.COMMON_API_URL}/activity-histories`,
308
+ payload,
309
+ );
310
+
311
+ return customer;
312
+ } catch (error) {
313
+ throw error;
314
+ }
315
+ }
316
+
317
+ async postJournal(dbTransaction: any, journalEntry: JournalEntry) {
318
+ const debitTransactions = await journalEntry.DebitTransactions;
319
+ const creditTransactions = await journalEntry.CreditTransactions;
320
+ try {
321
+ if (creditTransactions.length < 1 || debitTransactions?.length < 1) {
322
+ throw new Error(
323
+ 'There should be at least 1 debit ledger transaction and 1 credit ledger transaction in the journal entry',
324
+ );
325
+ }
326
+
327
+ const totalCreditAmount = creditTransactions.reduce(
328
+ (accumulator, currentValue) => accumulator + currentValue.CreditAmount,
329
+ 0,
330
+ );
331
+ const totalDebitAmount = debitTransactions.reduce(
332
+ (accumulator, currentValue) => accumulator + currentValue.DebitAmount,
333
+ 0,
334
+ );
335
+
336
+ if (totalCreditAmount !== totalDebitAmount) {
337
+ throw new Error(
338
+ 'Credit ledger transaction and debit ledger transaction should the same amount',
339
+ );
340
+ }
341
+
342
+ const newJournalEntry = await journalEntry.save('test', dbTransaction);
343
+
344
+ const allLedgerTransactions =
345
+ await FinanceCompany._LedgerTransactionRepository.findAll({});
346
+
347
+ for (const ledgerTransaction of allLedgerTransactions) {
348
+ ledgerTransaction.JournalEntryId = newJournalEntry.JournalEntryId;
349
+ await ledgerTransaction.save();
350
+ }
351
+
352
+ await this.AccountingSystem.postJournalEntry(newJournalEntry);
353
+
354
+ const payload = {
355
+ Action: 'Create',
356
+ Activity: 'Post Journal Entry',
357
+ Description: `Journal Entry (ID: ${newJournalEntry.JournalEntryId}) has been created`,
358
+ EntityType: 'JournalEntry',
359
+ EntityValueBefore: JSON.stringify({}),
360
+ EntityValueAfter: JSON.stringify(newJournalEntry),
361
+ PerformedById: 'test',
362
+ PerformedAt: new Date(),
363
+ EntityId: newJournalEntry.JournalEntryId,
364
+ };
365
+
366
+ await axios.post(
367
+ `${process.env.COMMON_API_URL}/activity-histories`,
368
+ payload,
369
+ );
370
+ } catch (error) {
371
+ throw error;
372
+ }
373
+ }
374
+
375
+ async createAccount(dbTransaction: any, account: Account) {
376
+ try {
377
+ account.validateAccountValue();
378
+
379
+ let createAccountPayload: any = {
380
+ Name: account.Name,
381
+ AcctNum: account.AccountNo,
382
+ AccountType: account.AccountType,
383
+ AccountSubType: account.AccountSubtype,
384
+ };
385
+
386
+ if (account.isParentAccountExists()) {
387
+ createAccountPayload = {
388
+ ...createAccountPayload,
389
+ CurrencyRef: 'MYR',
390
+ ParentRef: account.ParentAccountNo,
391
+ SubAccount: true,
392
+ };
393
+ }
394
+
395
+ const accSystemAccountId = await this.AccountingSystem.createAccount(
396
+ createAccountPayload,
397
+ );
398
+
399
+ const newAccount = await account.save(
400
+ accSystemAccountId,
401
+ 'test',
402
+ dbTransaction,
403
+ );
404
+
405
+ const payload = {
406
+ Action: 'Create',
407
+ Activity: 'Account Created',
408
+ Description: `Account (ID: ${newAccount.AccountNo}) has been created`,
409
+ EntityType: 'Account',
410
+ EntityValueBefore: JSON.stringify({}),
411
+ EntityValueAfter: JSON.stringify(newAccount),
412
+ PerformedById: 'test',
413
+ PerformedAt: new Date(),
414
+ EntityId: newAccount.AccountNo,
415
+ };
416
+
417
+ await axios.post(
418
+ `${process.env.COMMON_API_URL}/activity-histories`,
419
+ payload,
420
+ );
421
+
422
+ return account;
423
+ } catch (error) {
424
+ throw error;
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Issue an invoice
430
+ *
431
+ * @param dbTransaction - The database transaction to be used
432
+ * @param loginUser - The user issuing the invoice
433
+ * @param invoice - The document containing the invoice details
434
+ * @param customer - The customer to be issued the invoice
435
+ * @param dtAccountNo - The account number of the Account Receivable (AR) account to debit. If not provided, will debit the customer's default AR account
436
+ *
437
+ * @returns {Document} - Document object representing the full details of the invoice issued
438
+ */
439
+ async issueInvoice(
440
+ /*todo: loginUser & customer is NOT supposed to be optional */
441
+ dbTransaction: any,
442
+ invoice: Document,
443
+ loginUser?: LoginUserBase,
444
+ customer?: FinanceCustomerBase,
445
+ dtAccountNo?: string,
446
+ ): Promise<Document> {
447
+ try {
448
+ /*Check if the invoice number already exists (should be unique)*/
449
+ const duplicateInvoice = await FinanceCompany._DocumentRepository.findOne(
450
+ {
451
+ where: {
452
+ DocNo: invoice.DocNo,
453
+ },
454
+ transaction: dbTransaction,
455
+ },
456
+ );
457
+
458
+ if (duplicateInvoice) {
459
+ throw new Error('Invoice number already exists');
460
+ }
461
+
462
+ const documentItems = await invoice.DocumentItems;
463
+
464
+ /*Check if the document has at least 1 document item*/
465
+ if (!documentItems.length) {
466
+ throw new Error('Document must have at least 1 document item');
467
+ }
468
+
469
+ /*Check if each document item has CtAccountNo provided*/
470
+ for (const invoiceItem of documentItems) {
471
+ if (!invoiceItem.CtAccountNo) {
472
+ throw new Error(
473
+ 'Each document item should have CtAccountNo provided',
474
+ );
475
+ }
476
+ }
477
+
478
+ /*Set up the document type*/
479
+ invoice.DocType = DocType.INVOICE;
480
+
481
+ /*Saving the document and document items to the database*/
482
+ await FinanceCompany._DocumentRepository.create(
483
+ {
484
+ DocNo: invoice.DocNo,
485
+ DocType: invoice.DocType,
486
+ DocDate: invoice.DocDate,
487
+ CompanyId: invoice.CompanyId,
488
+ Currency: invoice.Currency,
489
+ Amount: invoice.Amount,
490
+ Description: invoice.Description,
491
+ Status: invoice.Status,
492
+ IssuedById: invoice.IssuedById,
493
+ IssuedToId: invoice.IssuedToId,
494
+ IssuedToType: invoice.IssuedToType,
495
+ CreatedById: invoice.CreatedById,
496
+ CreatedAt: new Date(),
497
+ UpdatedById: invoice.UpdatedById,
498
+ UpdatedAt: new Date(),
499
+ DocPDFFileMediaId: invoice.DocPDFFileMediaId,
500
+ DocHTMLFileMediaId: invoice.DocHTMLFileMediaId,
501
+ AccSystemRefId: invoice.AccSystemRefId,
502
+ PostedToAccSystemYN: invoice.PostedToAccSystemYN,
503
+ PostedById: invoice.PostedById,
504
+ PostedDateTime: new Date(),
505
+ UseAccSystemDocYN: invoice.UseAccSystemDocYN,
506
+ },
507
+ {
508
+ transaction: dbTransaction,
509
+ },
510
+ );
511
+
512
+ // Q: IS THIS ONE BELOW NECESSARY? WHY WOULD WE CREATE THE SAME DOCUMENT ITEM
513
+
514
+ // documentItems.forEach(async (documentItem) => {
515
+ // await FinanceCompany._DocumentItemRepository.create(
516
+ // {
517
+ // DocumentItemId: cuid(),
518
+ // DocNo: documentItem.DocNo,
519
+ // Name: documentItem.Name,
520
+ // NameBM: documentItem.NameBM,
521
+ // Description: documentItem.Description,
522
+ // ItemId: documentItem.ItemId,
523
+ // ItemType: documentItem.ItemType,
524
+ // ItemSKU: documentItem.ItemSKU,
525
+ // ItemSerialNo: documentItem.ItemSerialNo,
526
+ // Currency: documentItem.Currency,
527
+ // UnitPrice: documentItem.UnitPrice,
528
+ // Quantity: documentItem.Quantity,
529
+ // QuantityUOM: documentItem.QuantityUOM,
530
+ // Amount: documentItem.Amount,
531
+ // TaxCode: documentItem.TaxCode,
532
+ // TaxAmount: documentItem.TaxAmount,
533
+ // TaxRate: documentItem.TaxRate,
534
+ // TaxInclusiveYN: documentItem.TaxInclusiveYN,
535
+ // DtAccountNo: documentItem.DtAccountNo,
536
+ // CtAccountNo: documentItem.CtAccountNo,
537
+ // },
538
+ // {
539
+ // transaction: dbTransaction,
540
+ // },
541
+ // );
542
+ // });
543
+
544
+ /*Generating the invoice*/
545
+ if (invoice.UseAccSystemDocYN === 'Y') {
546
+ /*todo: Posting to accounting system to generate invoice*/
547
+ await this.AccountingSystem.createInvoice(invoice);
548
+ } else {
549
+ /*todo: check config file to see which invoice template is to be used for specific project*/
550
+
551
+ /*Generating invoice based on template*/
552
+ invoice.generateInvoice(invoice.IssuedById);
553
+ }
554
+
555
+ const journalEntry = new JournalEntry(dbTransaction);
556
+ const transactionDate = new Date();
557
+
558
+ const ledgerTransaction = await journalEntry.newLedgerTransaction(
559
+ TransactionTypeOptions.DEBIT,
560
+ );
561
+
562
+ if (dtAccountNo) {
563
+ /*Transacting using AR account provided*/
564
+ ledgerTransaction.AccountNo = dtAccountNo;
565
+ } else {
566
+ /*Transacting based on default customer AR account*/
567
+ ledgerTransaction.AccountNo = customer.CustSystemCode + '-AR';
568
+ }
569
+
570
+ ledgerTransaction.Currency = invoice.Currency;
571
+ ledgerTransaction.DebitAmount = invoice.Amount;
572
+ ledgerTransaction.Date = transactionDate;
573
+ ledgerTransaction.Name = invoice.DocNo;
574
+
575
+ for (const invoiceItem of documentItems) {
576
+ const itemLedger = await journalEntry.newLedgerTransaction(
577
+ TransactionTypeOptions.CREDIT,
578
+ );
579
+ itemLedger.AccountNo = invoiceItem.CtAccountNo;
580
+ itemLedger.Currency = invoiceItem.Currency;
581
+ itemLedger.CreditAmount = invoiceItem.Amount;
582
+ itemLedger.Date = transactionDate;
583
+ itemLedger.Name = invoiceItem.Name;
584
+ }
585
+
586
+ this.postJournal(dbTransaction, journalEntry);
587
+
588
+ /*todo: uncomment below later*/
589
+
590
+ // const payload = {
591
+ // Action: 'Create',
592
+ // Activity: 'Issuing an Invoice',
593
+ // Description: `Ledger Transaction (ID: ${ledgerTransaction.TransactionId}) has been created`,
594
+ // EntityType: 'LedgerTransaction',
595
+ // EntityValueBefore: JSON.stringify({}),
596
+ // EntityValueAfter: JSON.stringify(ledgerTransaction),
597
+ // PerformedById: 'test',
598
+ // PerformedAt: new Date(),
599
+ // EntityId: ledgerTransaction.TransactionId,
600
+ // };
601
+
602
+ // await axios.post(
603
+ // `${process.env.COMMON_API_URL}/activity-histories`,
604
+ // payload,
605
+ // );
606
+
607
+ return invoice;
608
+ } catch (err) {
609
+ // tslint:disable-next-line:no-console
610
+ console.log('Issue invoice err: ', err);
611
+ throw err;
612
+ }
613
+ }
614
+
615
+ /**
616
+ * Issue a debit note
617
+ *
618
+ * @param dbTransaction - The database transaction to be used
619
+ * @param loginUser - The user issuing the invoice
620
+ * @param invoice - The document containing the invoice details
621
+ * @param customer - The customer to be issued the invoice
622
+ * @param dtAccountNo - The account number of the Account Receivable (AR) account to debit. If not provided, will debit the customer's default AR account
623
+ *
624
+ * @returns {Document} - Document object representing the full details of the invoice issued
625
+ */
626
+ async issueDebitNote(
627
+ dbTransaction: any,
628
+ loginUser: LoginUserBase,
629
+ invoice: Document,
630
+ customer: FinanceCustomerBase,
631
+ dtAccountNo?: string,
632
+ ): Promise<Document> {
633
+ try {
634
+ /*Check if the invoice number already exists (should be unique)*/
635
+ const duplicateInvoice = await FinanceCompany._DocumentRepository.findOne(
636
+ {
637
+ where: {
638
+ DocNo: invoice.DocNo,
639
+ },
640
+ transaction: dbTransaction,
641
+ },
642
+ );
643
+
644
+ if (duplicateInvoice) {
645
+ throw new Error('Invoice number already exists');
646
+ }
647
+
648
+ const documentItems = await invoice.DocumentItems;
649
+
650
+ /*Check if the document has at least 1 document item*/
651
+ if (!documentItems.length) {
652
+ throw new Error('Document must have at least 1 document item');
653
+ }
654
+
655
+ /*Check if each document item has CtAccountNo provided*/
656
+ for (const invoiceItem of documentItems) {
657
+ if (!invoiceItem.CtAccountNo) {
658
+ throw new Error(
659
+ 'Each document item should have CtAccountNo provided',
660
+ );
661
+ }
662
+ }
663
+
664
+ /*Set up the document type*/
665
+ invoice.DocType = DocType.DEBIT_NOTE;
666
+
667
+ /*Saving the document and document items to the database*/
668
+ await FinanceCompany._DocumentRepository.create(
669
+ {
670
+ DocNo: invoice.DocNo,
671
+ DocType: invoice.DocType,
672
+ DocDate: invoice.DocDate,
673
+ CompanyId: invoice.CompanyId,
674
+ Currency: invoice.Currency,
675
+ Amount: invoice.Amount,
676
+ Description: invoice.Description,
677
+ Status: invoice.Status,
678
+ IssuedById: loginUser.ObjectId,
679
+ IssuedToId: customer.ObjectId,
680
+ IssuedToType: invoice.IssuedToType,
681
+ CreatedById: loginUser.ObjectId,
682
+ CreatedAt: new Date(),
683
+ UpdatedById: loginUser.ObjectId,
684
+ UpdatedAt: new Date(),
685
+ DocPDFFileMediaId: invoice.DocPDFFileMediaId,
686
+ DocHTMLFileMediaId: invoice.DocHTMLFileMediaId,
687
+ AccSystemRefId: invoice.AccSystemRefId,
688
+ PostedToAccSystemYN: invoice.PostedToAccSystemYN,
689
+ PostedById: invoice.PostedById,
690
+ PostedDateTime: new Date(),
691
+ UseAccSystemDocYN: invoice.UseAccSystemDocYN,
692
+ },
693
+ {
694
+ transaction: dbTransaction,
695
+ },
696
+ );
697
+
698
+ documentItems.forEach(async (documentItem) => {
699
+ await FinanceCompany._DocumentItemRepository.create(
700
+ {
701
+ DocumentItemId: cuid(),
702
+ DocNo: documentItem.DocNo,
703
+ Name: documentItem.Name,
704
+ NameBM: documentItem.NameBM,
705
+ Description: documentItem.Description,
706
+ ItemId: documentItem.ItemId,
707
+ ItemType: documentItem.ItemType,
708
+ ItemSKU: documentItem.ItemSKU,
709
+ ItemSerialNo: documentItem.ItemSerialNo,
710
+ Currency: documentItem.Currency,
711
+ UnitPrice: documentItem.UnitPrice,
712
+ Quantity: documentItem.Quantity,
713
+ QuantityUOM: documentItem.QuantityUOM,
714
+ Amount: documentItem.Amount,
715
+ TaxCode: documentItem.TaxCode,
716
+ TaxAmount: documentItem.TaxAmount,
717
+ TaxRate: documentItem.TaxRate,
718
+ TaxInclusiveYN: documentItem.TaxInclusiveYN,
719
+ DtAccountNo: documentItem.DtAccountNo,
720
+ CtAccountNo: documentItem.CtAccountNo,
721
+ },
722
+ {
723
+ transaction: dbTransaction,
724
+ },
725
+ );
726
+ });
727
+
728
+ /*Generating the invoice*/
729
+ if (invoice.UseAccSystemDocYN === 'Y') {
730
+ /*todo: Posting to accounting system to generate invoice*/
731
+ await this.AccountingSystem.createInvoice(invoice);
732
+ } else {
733
+ /*todo: check config file to see which invoice template is to be used for specific project*/
734
+
735
+ /*Generating invoice based on template*/
736
+ invoice.generateInvoice(loginUser.IDNo, customer);
737
+ }
738
+
739
+ const journalEntry = new JournalEntry(dbTransaction);
740
+ const transactionDate = new Date();
741
+
742
+ const debitTransaction = await journalEntry.newLedgerTransaction(
743
+ TransactionTypeOptions.DEBIT,
744
+ );
745
+
746
+ if (dtAccountNo) {
747
+ /*Transacting using AR account provided*/
748
+ debitTransaction.AccountNo = dtAccountNo;
749
+ } else {
750
+ /*Transacting based on default customer AR account*/
751
+ debitTransaction.AccountNo = customer.CustSystemCode + '-AR';
752
+ }
753
+
754
+ debitTransaction.Currency = invoice.Currency;
755
+ debitTransaction.DebitAmount = invoice.Amount;
756
+ debitTransaction.Date = transactionDate;
757
+ debitTransaction.Name = invoice.DocNo;
758
+
759
+ for (const invoiceItem of documentItems) {
760
+ const itemLedger = await journalEntry.newLedgerTransaction(
761
+ TransactionTypeOptions.CREDIT,
762
+ );
763
+ itemLedger.AccountNo = invoiceItem.CtAccountNo;
764
+ itemLedger.Currency = invoiceItem.Currency;
765
+ itemLedger.CreditAmount = invoiceItem.Amount;
766
+ itemLedger.Date = transactionDate;
767
+ itemLedger.Name = invoiceItem.Name;
768
+ }
769
+
770
+ this.postJournal(dbTransaction, journalEntry);
771
+
772
+ const payload = {
773
+ Action: 'Create',
774
+ Activity: 'Issuing a Debit Note',
775
+ Description: `Debit Transaction (ID: ${debitTransaction.TransactionId}) has been created`,
776
+ EntityType: 'DebitTransaction',
777
+ EntityValueBefore: JSON.stringify({}),
778
+ EntityValueAfter: JSON.stringify(debitTransaction),
779
+ PerformedById: 'test',
780
+ PerformedAt: new Date(),
781
+ EntityId: debitTransaction.TransactionId,
782
+ };
783
+
784
+ await axios.post(
785
+ `${process.env.COMMON_API_URL}/activity-histories`,
786
+ payload,
787
+ );
788
+
789
+ return invoice;
790
+ } catch (err) {
791
+ // tslint:disable-next-line:no-console
792
+ console.log('Issue debit note err: ', err);
793
+ throw err;
794
+ }
795
+ }
796
+
797
+ /**
798
+ * Issue a credit note
799
+ *
800
+ * @param dbTransaction - The database transaction to be used
801
+ * @param loginUser - The user issuing the credit note
802
+ * @param creditNote - The document containing the credit note details
803
+ * @param customer - The customer to be issued the credit note
804
+ * @param ctAccountNo - The account number of the Account Payable (AP) account to debit. If not provided, will debit the customer's default AR account
805
+ *
806
+ * @returns {Document} - Document object representing the full details of the invoice issued
807
+ */
808
+ async issueCreditNote(
809
+ dbTransaction: any,
810
+ loginUser: LoginUserBase,
811
+ creditNote: Document,
812
+ customer: FinanceCustomerBase,
813
+ ctAccountNo?: string,
814
+ ): Promise<Document> {
815
+ try {
816
+ /*Check if the invoice number already exists (should be unique)*/
817
+ const duplicateCreditNote =
818
+ await FinanceCompany._DocumentRepository.findOne({
819
+ where: {
820
+ DocNo: creditNote.DocNo,
821
+ },
822
+ transaction: dbTransaction,
823
+ });
824
+
825
+ if (duplicateCreditNote) {
826
+ throw new Error('Invoice number already exists');
827
+ }
828
+
829
+ const documentItems = await creditNote.DocumentItems;
830
+
831
+ /*Check if the document has at least 1 document item*/
832
+ if (!documentItems.length) {
833
+ throw new Error('Document must have at least 1 document item');
834
+ }
835
+
836
+ /*Check if each document item has CtAccountNo provided*/
837
+ for (const invoiceItem of documentItems) {
838
+ if (!invoiceItem.CtAccountNo) {
839
+ throw new Error(
840
+ 'Each document item should have CtAccountNo provided',
841
+ );
842
+ }
843
+ }
844
+
845
+ /*Set up the document type*/
846
+ creditNote.DocType = DocType.DEBIT_NOTE;
847
+
848
+ /*Saving the document and document items to the database*/
849
+ await FinanceCompany._DocumentRepository.create(
850
+ {
851
+ DocNo: creditNote.DocNo,
852
+ DocType: creditNote.DocType,
853
+ DocDate: creditNote.DocDate,
854
+ CompanyId: creditNote.CompanyId,
855
+ Currency: creditNote.Currency,
856
+ Amount: creditNote.Amount,
857
+ Description: creditNote.Description,
858
+ Status: creditNote.Status,
859
+ IssuedById: loginUser.ObjectId,
860
+ IssuedToId: customer.ObjectId,
861
+ IssuedToType: creditNote.IssuedToType,
862
+ CreatedById: loginUser.ObjectId,
863
+ CreatedAt: new Date(),
864
+ UpdatedById: loginUser.ObjectId,
865
+ UpdatedAt: new Date(),
866
+ DocPDFFileMediaId: creditNote.DocPDFFileMediaId,
867
+ DocHTMLFileMediaId: creditNote.DocHTMLFileMediaId,
868
+ AccSystemRefId: creditNote.AccSystemRefId,
869
+ PostedToAccSystemYN: creditNote.PostedToAccSystemYN,
870
+ PostedById: creditNote.PostedById,
871
+ PostedDateTime: new Date(),
872
+ UseAccSystemDocYN: creditNote.UseAccSystemDocYN,
873
+ },
874
+ {
875
+ transaction: dbTransaction,
876
+ },
877
+ );
878
+
879
+ documentItems.forEach(async (documentItem) => {
880
+ await FinanceCompany._DocumentItemRepository.create(
881
+ {
882
+ DocumentItemId: documentItem.DocumentItemId,
883
+ DocNo: documentItem.DocNo,
884
+ Name: documentItem.Name,
885
+ NameBM: documentItem.NameBM,
886
+ Description: documentItem.Description,
887
+ ItemId: documentItem.ItemId,
888
+ ItemType: documentItem.ItemType,
889
+ ItemSKU: documentItem.ItemSKU,
890
+ ItemSerialNo: documentItem.ItemSerialNo,
891
+ Currency: documentItem.Currency,
892
+ UnitPrice: documentItem.UnitPrice,
893
+ Quantity: documentItem.Quantity,
894
+ QuantityUOM: documentItem.QuantityUOM,
895
+ Amount: documentItem.Amount,
896
+ TaxCode: documentItem.TaxCode,
897
+ TaxAmount: documentItem.TaxAmount,
898
+ TaxRate: documentItem.TaxRate,
899
+ TaxInclusiveYN: documentItem.TaxInclusiveYN,
900
+ DtAccountNo: documentItem.DtAccountNo,
901
+ CtAccountNo: documentItem.CtAccountNo,
902
+ },
903
+ {
904
+ transaction: dbTransaction,
905
+ },
906
+ );
907
+ });
908
+
909
+ /*Generating the credit note*/
910
+ if (creditNote.UseAccSystemDocYN === 'Y') {
911
+ /*todo: Posting to accounting system to generate creditNote*/
912
+ await this.AccountingSystem.createCreditNote(creditNote);
913
+ } else {
914
+ /*todo: check config file to see which invoice template is to be used for specific project*/
915
+
916
+ /*Generating credit note based on template*/
917
+ creditNote.generateCreditNote(loginUser.IDNo, customer);
918
+ }
919
+
920
+ const journalEntry = new JournalEntry(dbTransaction);
921
+ const transactionDate = new Date();
922
+
923
+ const creditTransaction = await journalEntry.newLedgerTransaction(
924
+ TransactionTypeOptions.CREDIT,
925
+ );
926
+
927
+ if (ctAccountNo) {
928
+ /*Transacting using AR account provided*/
929
+ creditTransaction.AccountNo = ctAccountNo;
930
+ } else {
931
+ /*Transacting based on default customer AR account*/
932
+ creditTransaction.AccountNo = customer.CustSystemCode + '-AP';
933
+ }
934
+
935
+ creditTransaction.Currency = creditNote.Currency;
936
+ creditTransaction.CreditAmount = creditNote.Amount;
937
+ creditTransaction.Date = transactionDate;
938
+ creditTransaction.Name = creditNote.DocNo;
939
+
940
+ for (const invoiceItem of documentItems) {
941
+ const itemLedger = await journalEntry.newLedgerTransaction(
942
+ TransactionTypeOptions.DEBIT,
943
+ );
944
+ itemLedger.AccountNo = invoiceItem.CtAccountNo;
945
+ itemLedger.Currency = invoiceItem.Currency;
946
+ itemLedger.DebitAmount = invoiceItem.Amount;
947
+ itemLedger.Date = transactionDate;
948
+ itemLedger.Name = invoiceItem.Name;
949
+ }
950
+
951
+ this.postJournal(dbTransaction, journalEntry);
952
+
953
+ const payload = {
954
+ Action: 'Create',
955
+ Activity: 'Issuing a Credit Note Transaction',
956
+ Description: `Credit Transaction (ID: ${creditTransaction.TransactionId}) has been created`,
957
+ EntityType: 'CreditTransaction',
958
+ EntityValueBefore: JSON.stringify({}),
959
+ EntityValueAfter: JSON.stringify(creditTransaction),
960
+ PerformedById: 'test',
961
+ PerformedAt: transactionDate,
962
+ EntityId: creditTransaction.TransactionId,
963
+ };
964
+
965
+ await axios.post(
966
+ `${process.env.COMMON_API_URL}/activity-histories`,
967
+ payload,
968
+ );
969
+
970
+ return creditNote;
971
+ } catch (err) {
972
+ // tslint:disable-next-line:no-console
973
+ console.log('Issue credit note err: ', err);
974
+ throw err;
975
+ }
976
+ }
977
+
978
+ /**
979
+ * Register a payment collected
980
+ *
981
+ * @param dbTransaction - The database transaction to be used
982
+ * @param loginUser - The user collecting the payment
983
+ * @param payment - The payment object containing payment details
984
+ * @param customer - The customer making the payment
985
+ * @param ctAccountNo - The customer's Account Receivable (AR) account to credit
986
+ *
987
+ * @returns {Payment} - Payment object representing the full detals of the payment collection recorded
988
+ */
989
+ async collectPayment(
990
+ dbTransaction: any,
991
+ loginUser: LoginUserBase,
992
+ payment: Payment,
993
+ customer: FinanceCustomerBase,
994
+ ctAccountNo?: string,
995
+ ): Promise<Payment> {
996
+ let paymentMethodType: PaymentMethodType;
997
+ try {
998
+ paymentMethodType = new PaymentMethodType(
999
+ dbTransaction,
1000
+ payment.MethodTypeId,
1001
+ );
1002
+
1003
+ const paymentItems = await payment.PaymentItems;
1004
+ if (paymentItems.length < 1) {
1005
+ throw new Error(
1006
+ 'Atleast one payment item is required to identify what payment is being paid for',
1007
+ );
1008
+ }
1009
+
1010
+ payment.PaymentId = cuid();
1011
+
1012
+ await FinanceCompany._PaymentRepository.create(
1013
+ {
1014
+ PaymentId: payment.PaymentId,
1015
+ PaymentType: payment.PaymentType,
1016
+ PaymentDate: payment.PaymentDate,
1017
+ CompanyId: this.CompanyId,
1018
+ MethodTypeId: payment.MethodTypeId,
1019
+ Currency: payment.Currency,
1020
+ Amount: payment.Amount,
1021
+ Status: PaymentStatus.SUCCESSFUL,
1022
+ TransactionId: payment.TransactionId,
1023
+ TransactionApprovalCode: payment.TransactionApprovalCode,
1024
+ TransactionAccountName: payment.TransactionAccountName,
1025
+ TransactionAccountNo: payment.TransactionAccountNo,
1026
+ ReceivedBy: payment.ReceivedBy,
1027
+ PostedToAccSystemYN: 'N',
1028
+ UpdatedAt: new Date(),
1029
+ UpdatedBy: loginUser.ObjectId,
1030
+ CreatedAt: new Date(),
1031
+ CreatedBy: loginUser.ObjectId,
1032
+ },
1033
+ { transaction: dbTransaction },
1034
+ );
1035
+
1036
+ paymentItems.forEach(async (paymentItem) => {
1037
+ await FinanceCompany._PaymentItemRepository.create(
1038
+ {
1039
+ PaymentId: payment.PaymentId,
1040
+ PayForObjectId: paymentItem.PayForObjectId,
1041
+ PayForObjectType: paymentItem.PayForObjectType,
1042
+ Currency: paymentItem.Currency,
1043
+ Amount: paymentItem.Amount,
1044
+ },
1045
+ { transaction: dbTransaction },
1046
+ );
1047
+ });
1048
+
1049
+ const journalEntry = new JournalEntry(dbTransaction);
1050
+ //Temporary fix to successfully save journal entry in PostJournal
1051
+ journalEntry.init({
1052
+ CompanyId: this.CompanyId,
1053
+ Name: 'Collect-payments for ' + payment.PaymentId,
1054
+ });
1055
+ const transactionDate = new Date();
1056
+
1057
+ const debitLT = await journalEntry.newLedgerTransaction(
1058
+ TransactionTypeOptions.DEBIT,
1059
+ );
1060
+ debitLT.AccountNo = paymentMethodType.AccountNo;
1061
+ debitLT.Currency = payment.Currency;
1062
+ debitLT.DebitAmount = payment.Amount;
1063
+ debitLT.Date = transactionDate;
1064
+ debitLT.Name = customer.FullName;
1065
+
1066
+ const creditLT = await journalEntry.newLedgerTransaction(
1067
+ TransactionTypeOptions.CREDIT,
1068
+ );
1069
+
1070
+ if (ctAccountNo) {
1071
+ creditLT.AccountNo = ctAccountNo;
1072
+ } else {
1073
+ // const config = getConfig();
1074
+ creditLT.AccountNo = customer.CustSystemCode + '-AR';
1075
+ }
1076
+ creditLT.Currency = payment.Currency;
1077
+ creditLT.CreditAmount = payment.Amount;
1078
+ creditLT.Date = transactionDate;
1079
+ creditLT.Name = paymentMethodType.Name;
1080
+
1081
+ await this.postJournal(dbTransaction, journalEntry);
1082
+
1083
+ /*todo: saving a record into the activity history table*/
1084
+
1085
+ return payment;
1086
+ } catch (error) {
1087
+ if (error instanceof RecordNotFoundError) {
1088
+ throw new Error('Invalid PaymentMethodType id');
1089
+ } else {
1090
+ throw error;
1091
+ }
1092
+ }
1093
+ }
1094
+
1095
+ get PaymentMethods() {
1096
+ if (this._PaymentMethods === null || this._PaymentMethods.length === 0) {
1097
+ this.LoadPaymentMethods();
1098
+ }
1099
+ return this._PaymentMethods;
1100
+ }
1101
+
1102
+ /**
1103
+ * Method to load / reload the payment methods based on the configuration file
1104
+ */
1105
+ async LoadPaymentMethods(): Promise<void> {
1106
+ /*Retrieve the payment method from the config file */
1107
+ const { companyId, paymentMethods } = config.systemConfig['EZC'];
1108
+ const paymentMethod = new PaymentMethod();
1109
+
1110
+ for (const method in paymentMethods) {
1111
+ const paymentMethodData =
1112
+ await FinanceCompany._PaymentMethodRepository.findOne({
1113
+ where: {
1114
+ MethodId: paymentMethods[method].id,
1115
+ Name: paymentMethods[method].name,
1116
+ },
1117
+ });
1118
+
1119
+ if (!paymentMethodData) {
1120
+ const newPaymentMethod =
1121
+ await FinanceCompany._PaymentMethodRepository.create({
1122
+ MethodId: paymentMethods[method].id,
1123
+ Name: paymentMethods[method].name,
1124
+ CompanyId: companyId,
1125
+ });
1126
+
1127
+ await this._PaymentMethods.push(newPaymentMethod);
1128
+ }
1129
+
1130
+ await this._PaymentMethods.push(paymentMethodData);
1131
+
1132
+ this._PaymentMethods.forEach(async (paymentMethodData) => {
1133
+ const paymentMethodTypeData =
1134
+ await FinanceCompany._PaymentMethodTypeRepository.findOne({
1135
+ where: {
1136
+ MethodId: paymentMethodData.MethodId,
1137
+ },
1138
+ });
1139
+
1140
+ if (!paymentMethodTypeData) {
1141
+ const configPaymentMethodTypes = paymentMethods[method]?.types;
1142
+
1143
+ for (const methodType in configPaymentMethodTypes) {
1144
+ const paymentMethodTypePayload = {
1145
+ MethodId: paymentMethodTypeData.MethodId,
1146
+ MethodTypeId: configPaymentMethodTypes[methodType].id,
1147
+ Name: configPaymentMethodTypes[methodType].name,
1148
+ };
1149
+
1150
+ await paymentMethod.newPaymentMethodType(paymentMethodTypePayload);
1151
+ }
1152
+ }
1153
+ });
1154
+ }
1155
+ }
1156
+ }