UNPKG

@tomei/finance

Version:

NestJS package for finance module

1,005 lines (1,004 loc) 93.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const axios_1 = __importDefault(require("axios")); const general_1 = require("@tomei/general"); const account_1 = __importDefault(require("../account/account")); const journal_entry_1 = __importDefault(require("../journal-entry/journal-entry")); const document_1 = __importDefault(require("../document/document")); const finance_company_repository_1 = require("./finance-company.repository"); const finance_customer_repository_1 = require("../customer/finance-customer.repository"); const ledger_transaction_repository_1 = require("../ledger-transaction/ledger-transaction.repository"); const enum_1 = require("../enum"); const payment_method_type_1 = __importDefault(require("../payment-method-type/payment-method-type")); const payment_repository_1 = require("../payment/payment.repository"); const payment_item_repository_1 = require("../payment-item/payment-item.repository"); const document_repository_1 = require("../document/document.repository"); const document_item_repository_1 = require("../document/document-item.repository"); const payment_method_repository_1 = require("../payment-method/payment-method.repository"); const payment_method_type_repository_1 = require("../payment-method-type/payment-method-type.repository"); const payment_method_1 = __importDefault(require("../payment-method/payment-method")); const account_repository_1 = require("../account/account.repository"); const payment_paid_with_repository_1 = require("../payment-paid-with/payment-paid-with.repository"); const document_item_1 = __importDefault(require("../document/document-item")); const typeof_1 = require("../helpers/typeof"); const activity_history_1 = require("@tomei/activity-history"); const config_1 = require("@tomei/config"); const tax_repository_1 = require("../tax/tax.repository"); const tax_1 = require("../tax/tax"); const collect_payment_type_1 = require("../enum/collect-payment-type"); const tag_1 = require("../tag/tag"); const tag_repository_1 = require("../tag/tag.repository"); const tag_group_repository_1 = require("../tag-group/tag-group.repository"); const tag_group_1 = require("../tag-group/tag-group"); const document_tag_repository_1 = require("../document-tag/document-tag.repository"); const document_tag_1 = require("../document-tag/document-tag"); const payment_method_type_entity_1 = __importDefault(require("../models/payment-method-type.entity")); class FinanceCompany extends general_1.ObjectBase { get ObjectType() { return this._ObjectType; } get CompSystemCode() { return this._CompSystemCode; } set CompSystemCode(code) { this._CompSystemCode = code; } get CompSystemRefId() { return this._CompSystemRefId; } set CompSystemRefId(id) { this._CompSystemRefId = id; } get AccSystemCode() { return this._AccSystemCode; } get AccSystemRefId() { return this._AccSystemRefId; } set AccSystemRefId(id) { this._AccSystemRefId = id; } get PostedToAccSystemYN() { return this._PostedToAccSystemYN; } set PostedToAccSystemYN(value) { this._PostedToAccSystemYN = value; } get PostedById() { return this._PostedById; } set PostedById(id) { this._PostedById = id; } get PostedDateTime() { return this._PostedDateTime; } set PostedDateTime(date) { this._PostedDateTime = date; } set AccSystemCode(code) { this._AccSystemCode = code; } get CompanyId() { return this._CompanyId; } get ObjectId() { return this._CompanyId; } set ObjectId(id) { this._CompanyId = id; } get ObjectName() { return `${this.CompSystemCode}-${this.CompSystemRefId}-${this.AccSystemCode}`; } get TableName() { return 'finance_Company'; } get AccountingSystem() { return this._AccountingSystem; } set AccountingSystem(system) { this._AccountingSystem = system; } constructor(compSystemCode, compSystemRefId, accSystemCode) { super(); this._CompanyId = 'New'; this._CompSystemCode = ''; this._CompSystemRefId = ''; this._AccSystemCode = 'REF'; this._AccSystemRefId = 'REF'; this._PostedToAccSystemYN = 'N'; this._PostedById = null; this._PostedDateTime = null; this._ObjectType = 'FinanceCompany'; this._PaymentMethods = []; this._Taxes = []; this.CompSystemCode = compSystemCode; this.CompSystemRefId = compSystemRefId; this.AccSystemCode = accSystemCode || 'REF'; this.AccSystemRefId = 'REF'; this.PostedToAccSystemYN = 'N'; } static async getFinanceCompanyId(compSystemCode, compSystemRefId, accSystemCode, dbTransaction) { let sCompanyId = ''; const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`; if (!FinanceCompany._htFinanceCompanyIds.get(sKey)) { const financeCompany = new FinanceCompany(compSystemCode, compSystemRefId, accSystemCode); const company = await FinanceCompany._financeCompanyRepository.findOne({ where: { CompSystemCode: compSystemCode, CompSystemRefId: compSystemRefId, AccSystemCode: accSystemCode, }, transaction: dbTransaction, }); financeCompany.ObjectId = company.CompanyId; sCompanyId = financeCompany.ObjectId; FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId); FinanceCompany._htFinanceCompanies.add(sCompanyId, financeCompany); } if (typeof FinanceCompany._htFinanceCompanyIds.get(sKey) === 'string') { sCompanyId = FinanceCompany._htFinanceCompanyIds.get(sKey); } return sCompanyId; } static async getFinanceCompany(companyId, dbTransaction) { if (!FinanceCompany._htFinanceCompanies.get(companyId)) { const company = await FinanceCompany._financeCompanyRepository.findOne({ where: { CompanyId: companyId }, transaction: dbTransaction, }); if (!company) { throw Error('No finance company found. Please create first.'); } const compSystemCode = company.CompSystemCode; const compSystemRefId = company.CompSystemRefId; const accSystemCode = company.AccSystemCode; const financeCompany = new FinanceCompany(compSystemCode, compSystemRefId, accSystemCode); financeCompany.ObjectId = company.CompanyId; financeCompany._CompanyId = company.CompanyId; const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`; FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId); FinanceCompany._htFinanceCompanies.add(financeCompany.ObjectId, financeCompany); } return FinanceCompany._htFinanceCompanies.get(companyId); } static async createFinanceCompany(dbTransaction, loginUser, companyId, compSystemCode, compSystemRefId, accSystemCode, accSystemRefId, postedToAccSystemYN) { const financeCompany = new FinanceCompany(compSystemCode, compSystemRefId, accSystemCode); const company = await FinanceCompany._financeCompanyRepository.findOne({ where: { CompSystemCode: compSystemCode, CompSystemRefId: compSystemRefId, AccSystemCode: accSystemCode, }, }); if (company) { throw Error('There is already another Finance Company with the compSystemCode, CompSystemRefId and accSystemCode specified.'); } financeCompany.ObjectId = companyId; financeCompany.PostedById = loginUser?.ObjectId || null; financeCompany.PostedDateTime = new Date(); if (postedToAccSystemYN === 'Y' && !financeCompany.PostedById) { throw Error('Cannot post to the accounting system because no user is specified.'); } await FinanceCompany._financeCompanyRepository.create({ CompanyId: financeCompany.CompanyId, CompSystemCode: financeCompany.CompSystemCode, CompSystemRefId: financeCompany.CompSystemRefId, AccSystemCode: financeCompany.AccSystemCode, AccSystemRefId: postedToAccSystemYN === 'Y' ? accSystemRefId : financeCompany.AccSystemRefId, PostedToAccSystemYN: postedToAccSystemYN || financeCompany.PostedToAccSystemYN, PostedById: postedToAccSystemYN === 'Y' ? financeCompany.PostedById : null, PostedDateTime: postedToAccSystemYN === 'Y' ? financeCompany.PostedDateTime : null, }, { transaction: dbTransaction, }); const sKey = `${compSystemCode}-${compSystemRefId}-${accSystemCode}`; FinanceCompany._htFinanceCompanyIds.add(sKey, financeCompany.ObjectId); FinanceCompany._htFinanceCompanies.add(financeCompany.ObjectId, financeCompany); console.log('return from hash table'); return FinanceCompany._htFinanceCompanies.get(companyId); } static async findAccount(loginUser, dbTransaction, accountNo) { try { const systemCode = config_1.ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges(systemCode, 'FINANCECOMPANY_VIEW_ACCOUNT'); if (!isPrivileged) { throw new general_1.ClassError('FinanceCompany', 'findAccountErrMsg0X', 'User not privileged to view finance company account'); } const record = await FinanceCompany._AccountRepository.findOne({ where: { AccountNo: accountNo, }, transaction: dbTransaction, }); if (record) { const account = new account_1.default(dbTransaction); account.init(record.get({ plain: true })); return account; } return undefined; } catch (error) { throw error; } } async createCustomer(dbTransaction, custSystemCode, custSystemRefId, customer, loginUser) { try { if (!custSystemCode || !custSystemRefId) { throw new Error('CustSystemCode and CustomerRefId are required fields.'); } const financeCustomerData = await FinanceCompany._FinanceCustomerRepository.findOne({ where: { CompanyId: this._CompanyId, CustSystemCode: custSystemCode, CustSystemRefId: custSystemRefId, }, }); let AccCustomerRefId = 'REF'; if (!customer.AccSystemRefId || customer.AccSystemRefId == 'REF') { if (this.AccountingSystem) { AccCustomerRefId = await this.AccountingSystem.createCustomer(customer, dbTransaction); customer.PostedById = loginUser.ObjectId; customer.PostedDateTime = new Date(); customer.PostedToAccSystemYN = 'Y'; } } customer.CompanyId = this._CompanyId; let customerRecord; let isUpdated = false; if (financeCustomerData) { customerRecord = await customer.update(AccCustomerRefId ? AccCustomerRefId : undefined, dbTransaction); isUpdated = true; } else { customerRecord = await customer.save(AccCustomerRefId, custSystemCode, custSystemRefId, dbTransaction); isUpdated = false; } const activity = new activity_history_1.Activity(); activity.ActivityId = this._createId().toUpperCase(); if (isUpdated) { activity.Action = activity_history_1.ActionEnum.UPDATE; activity.Description = 'Update Finance Customer'; } else { activity.Action = activity_history_1.ActionEnum.CREATE; activity.Description = 'Add Finance Customer'; } activity.EntityType = 'FinanceCustomer'; activity.EntityId = customerRecord.CustomerId; activity.EntityValueBefore = JSON.stringify({}); activity.EntityValueAfter = JSON.stringify({ CustomerId: customerRecord.CustomerId, CustSystemCode: customerRecord.CustSystemCode, CustSystemRefId: customerRecord.CustSystemRefId, Name: customerRecord.Name, Description: customerRecord.Description, CreatedAt: customerRecord.CreatedAt, CreatedById: customerRecord.CreatedById, UpdatedAt: customerRecord.UpdatedAt, UpdatedById: customerRecord.UpdatedById, AccSystemRefId: customerRecord.AccSystemRefId, PostedToAccSystemYN: customerRecord.PostedToAccSystemYN, PostedById: customerRecord.PostedById, PostedDateTime: customerRecord.PostedDateTime, }); await activity.create(loginUser.ObjectId, dbTransaction); return customer; } catch (error) { throw error; } } async postJournal(dbTransaction, journalEntry, loginUser) { const debitTransactions = await journalEntry.DebitTransactions; const creditTransactions = await journalEntry.CreditTransactions; try { if (creditTransactions.length < 1 || debitTransactions?.length < 1) { throw new Error('There should be at least 1 debit ledger transaction and 1 credit ledger transaction in the journal entry'); } let totalCreditAmount = creditTransactions.reduce((accumulator, currentValue) => accumulator + currentValue.CreditAmount, 0); let totalDebitAmount = debitTransactions.reduce((accumulator, currentValue) => accumulator + currentValue.DebitAmount, 0); console.log('totalCreditAmount: ', totalCreditAmount); console.log('totalDebitAmount: ', totalDebitAmount); if (typeof totalCreditAmount === 'string') { totalCreditAmount = parseFloat(totalCreditAmount); } if (typeof totalDebitAmount === 'string') { totalDebitAmount = parseFloat(totalDebitAmount); } if (totalCreditAmount.toFixed(2) !== totalDebitAmount.toFixed(2)) { throw new Error('Credit ledger transaction and debit ledger transaction should the same amount'); } const newJournalEntry = await journalEntry.save(loginUser.ObjectId, dbTransaction); for (const ledgerTransaction of debitTransactions) { ledgerTransaction.JournalEntryId = newJournalEntry.JournalEntryId; await ledgerTransaction.save(dbTransaction); } for (const ledgerTransaction of creditTransactions) { ledgerTransaction.JournalEntryId = newJournalEntry.JournalEntryId; await ledgerTransaction.save(dbTransaction); } const payload = { Action: 'Create', Activity: 'Post Journal Entry', Description: `Journal Entry (ID: ${newJournalEntry.JournalEntryId}) has been created`, EntityType: 'JournalEntry', EntityValueBefore: JSON.stringify({}), EntityValueAfter: JSON.stringify(newJournalEntry), PerformedById: loginUser.ObjectId, PerformedAt: new Date(), EntityId: newJournalEntry.JournalEntryId, }; await axios_1.default.post(`${process.env.COMMON_API_URL}/activity-histories`, payload); } catch (error) { throw error; } } async createAccount(dbTransaction, account, loginUser) { try { let accSystemRefId; if (!account.AccountType) { throw new Error('AccountType is required.'); } const accountData = await FinanceCompany._AccountRepository.findByPk(account.AccountNo, { transaction: dbTransaction, }); if (accountData) { account.AccountNo = account.AccountNo ? account.AccountNo : accountData.AccountNo; account.CompanyId = account.CompanyId ? account.CompanyId : accountData.CompanyId; account.ParentAccountNo = account.ParentAccountNo ? account.ParentAccountNo : accountData.ParentAccountNo; account.Name = account.Name ? account.Name : accountData.Name; account.Description = account.Description ? account.Description : accountData.Description; account.AccountType = account.AccountType ? account.AccountType : accountData.AccountType; account.AccountSubtype = account.AccountSubtype ? account.AccountSubtype : accountData.AccountSubType; account.OwnerId = account.OwnerId ? account.OwnerId : accountData.OwnerId; account.OwnerType = account.OwnerType ? account.OwnerType : accountData.OwnerType; account.RelatedObjectId = account.RelatedObjectId ? account.RelatedObjectId : accountData.RelatedObjectId; account.RelatedObjectType = account.RelatedObjectType ? account.RelatedObjectType : accountData.RelatedObjectType; account.CreatedById = account.CreatedById ? account.CreatedById : accountData.CreatedById; account.CreatedAt = account.CreatedAt ? account.CreatedAt : accountData.CreatedAt; account.UpdatedById = account.UpdatedById ? account.UpdatedById : accountData.UpdatedById; account.UpdatedAt = account.UpdatedAt ? account.UpdatedAt : accountData.UpdatedAt; account.AccSystemRefId = account.AccSystemRefId && account.AccSystemRefId !== 'REF' ? account.AccSystemRefId : accountData.AccSystemRefId; account.PostedToAccSystemYN = account.PostedToAccSystemYN !== 'N' ? account.PostedToAccSystemYN : accountData.PostedToAccSystemYN; account.PostedById = account.PostedById ? account.PostedById : accountData.PostedById; account.PostedDateTime = account.PostedDateTime ? account.PostedDateTime : accountData.PostedDateTime; } let createAccountPayload = { Name: account.Name, AcctNum: account.AccountNo, AccountType: account.AccountType, AccountSubType: account.AccountSubtype, }; if (this.AccountingSystem) { accSystemRefId = await this.AccountingSystem.createAccount(account, dbTransaction); } if (account.isParentAccountExists()) { createAccountPayload = { ...createAccountPayload, CurrencyRef: 'MYR', ParentRef: account.ParentAccountNo, SubAccount: true, }; } console.log('Finance Company Create Account: Before accSystemAccountId Create'); console.log('Finance Company Create Account: After accSystemAccountId Create'); console.log('Finance Company Create Account: Before new Account Create'); if (account.AccSystemRefId && account.AccSystemRefId !== 'REF') { account.PostedToAccSystemYN = 'Y'; account.PostedById = loginUser.ObjectId; account.PostedDateTime = new Date(); } const newAccount = await account.save(this.CompanyId, account.AccSystemRefId || 'REF', loginUser.ObjectId, dbTransaction); console.log('Finance Company Create Account: After new Account Create'); const activity = new activity_history_1.Activity(); activity.ActivityId = this._createId().toUpperCase(); if (accountData) { activity.Action = activity_history_1.ActionEnum.UPDATE; activity.Description = 'Update Finance Account'; } else { activity.Action = activity_history_1.ActionEnum.CREATE; activity.Description = 'Add Finance Account'; } activity.EntityType = 'Account'; activity.EntityId = account.AccountNo; activity.EntityValueBefore = JSON.stringify({}); activity.EntityValueAfter = JSON.stringify({ AccountNo: newAccount.AccountNo, Name: newAccount.Name, Description: newAccount.Description, AccountType: newAccount.AccountType, AccountSubtype: newAccount.AccountSubtype, CreatedAt: newAccount.CreatedAt, CreatedById: newAccount.CreatedById, UpdatedAt: newAccount.UpdatedAt, UpdatedById: newAccount.UpdatedById, AccSystemRefId: newAccount.AccSystemRefId, PostedToAccSystemYN: newAccount.PostedToAccSystemYN, PostedById: newAccount.PostedById, PostedDateTime: newAccount.PostedDateTime, }); await activity.create(loginUser.ObjectId, dbTransaction); return newAccount; } catch (error) { throw error; } } async issueInvoice(dbTransaction, invoice, loginUser, customer, dtAccountNo, tags, discount) { try { const duplicateInvoice = await FinanceCompany._DocumentRepository.findOne({ where: { DocNo: invoice.DocNo, }, transaction: dbTransaction, }); if (duplicateInvoice) { throw new Error('Invoice number already exists'); } const documentItems = await invoice.getDocumentItems(dbTransaction); if (!documentItems.length) { throw new Error('Document must have at least 1 document item'); } for (const invoiceItem of documentItems) { if (!invoiceItem.CtAccountNo) { throw new Error('Each document item should have CtAccountNo provided'); } } invoice.DocType = enum_1.DocType.INVOICE; let invoiceMedia; if (invoice.UseAccSystemDocYN === 'Y') { let payload = { customer: customer, invoice: invoice, paymentMode: 'credit', discount: discount, }; if (tags) { payload = { ...payload, tagIds: tags.map((tag) => tag.AccSystemRefId), }; } const accInvoiceRefIdDetail = await this.AccountingSystem.createInvoice(payload, dbTransaction); invoice.DocNo = accInvoiceRefIdDetail.invoiceNo; invoice.AccSystemRefId = accInvoiceRefIdDetail.invoiceRefId; invoice.PostedToAccSystemYN = 'Y'; invoice.PostedById = loginUser.ObjectId; invoice.PostedDateTime = new Date(); for (const documentItem of documentItems) { const accItem = accInvoiceRefIdDetail.invoiceItems.find((item) => item.name === documentItem.Name); if (accItem) { documentItem.DocNo = accInvoiceRefIdDetail.invoiceNo; documentItem.AccSystemRefId = accItem.itemRefId; documentItem.PostedToAccSystemYN = 'Y'; documentItem.PostedById = loginUser.ObjectId; documentItem.PostedDateTime = new Date(); } } } else { invoiceMedia = await invoice.generateInvoice(invoice.IssuedById, customer, dbTransaction); } await FinanceCompany._DocumentRepository.create({ DocNo: invoice.DocNo, DocType: invoice.DocType, DocDate: invoice.DocDate, CompanyId: invoice.CompanyId, Currency: invoice.Currency, AmountBeforeDiscount: invoice.AmountBeforeDiscount, Amount: invoice.Amount, DiscountType: invoice.DiscountType, DiscountValue: invoice.DiscountValue, Description: invoice.Description, Status: invoice.Status, IssuedById: invoice.IssuedById, IssuedToId: invoice.IssuedToId, IssuedToType: invoice.IssuedToType, RelatedObjectId: invoice.RelatedObjectId, RelatedObjectType: invoice.RelatedObjectType, CreatedById: invoice.CreatedById, CreatedAt: new Date(), UpdatedById: invoice.UpdatedById, UpdatedAt: new Date(), DocPDFFileMediaId: invoice.UseAccSystemDocYN == 'N' ? invoiceMedia.PDFMedia.MediaId : null, DocHTMLFileMediaId: invoice.UseAccSystemDocYN == 'N' ? invoiceMedia.HTMLMedia.MediaId : null, AccSystemRefId: invoice.AccSystemRefId, PostedToAccSystemYN: invoice.PostedToAccSystemYN, PostedById: invoice.UseAccSystemDocYN == 'Y' ? invoice.PostedById : null, PostedDateTime: new Date(), UseAccSystemDocYN: invoice.UseAccSystemDocYN, }, { transaction: dbTransaction, }); for (const documentItem of documentItems) { await FinanceCompany._DocumentItemRepository.create({ DocumentItemId: this._createId().toUpperCase(), DocNo: invoice.DocNo, Name: documentItem.Name, NameBM: documentItem.NameBM, Description: documentItem.Description, ItemId: documentItem.ItemId, ItemType: documentItem.ItemType, ItemSKU: documentItem.ItemSKU, ItemSerialNo: documentItem.ItemSerialNo, Currency: documentItem.Currency, UnitPrice: documentItem.UnitPrice, Quantity: documentItem.Quantity, QuantityUOM: documentItem.QuantityUOM, AmountBeforeDiscount: documentItem.AmountBeforeDiscount, Amount: documentItem.Amount, DiscountType: documentItem.DiscountType, DiscountValue: documentItem.DiscountValue, TaxCode: documentItem.TaxCode, TaxAmount: documentItem.TaxAmount, TaxRate: documentItem.TaxRate, TaxInclusiveYN: documentItem.TaxInclusiveYN, DtAccountNo: documentItem.DtAccountNo ? documentItem.DtAccountNo : null, CtAccountNo: documentItem.CtAccountNo ? documentItem.CtAccountNo : null, AccSystemRefId: documentItem.AccSystemRefId, PostedToAccSystemYN: documentItem.PostedToAccSystemYN, PostedById: documentItem.PostedById, PostedDateTime: documentItem.PostedDateTime, }, { transaction: dbTransaction, }); } if (tags) { const documentTagRepo = new document_tag_repository_1.DocumentTagRepository(); for (const tag of tags) { const documentTag = await document_tag_1.DocumentTag.init(dbTransaction, documentTagRepo); documentTag.DocNo = invoice.DocNo; documentTag.TagId = tag.TagId; await documentTag.create(loginUser, dbTransaction); } } const transactionDate = new Date(); const htCreditAccountAmount = new general_1.HashTable(); const htCreditAccountCurrency = new general_1.HashTable(); const htCreditAccountPurpose = new general_1.HashTable(); documentItems.forEach((invoiceItem) => { if (!htCreditAccountAmount.exists(invoiceItem.CtAccountNo)) { htCreditAccountAmount.add(invoiceItem.CtAccountNo, invoiceItem.Amount); htCreditAccountCurrency.add(invoiceItem.CtAccountNo, invoiceItem.Currency); htCreditAccountPurpose.add(invoiceItem.CtAccountNo, invoiceItem.Name); } else { const d = htCreditAccountAmount.get(invoiceItem.CtAccountNo); htCreditAccountAmount.add(invoiceItem.CtAccountNo, d + invoiceItem.Amount); } }); const savedItems = htCreditAccountAmount.list(); for (const item of savedItems) { const journalEntry = new journal_entry_1.default(dbTransaction); journalEntry.init({ CompanyId: this.CompanyId, Name: 'Issue Invoice ' + invoice.DocNo, }); const creditAmount = item.value[1]; const currency = htCreditAccountCurrency.get(item.value[0]); const purpose = htCreditAccountPurpose.get(item.value[0]); const dt = await journalEntry.newLedgerTransaction(enum_1.TransactionTypeOptions.DEBIT); if (dtAccountNo) { dt.AccountNo = dtAccountNo; } else { const arAccount = await customer.getAccountReceivable(); dt.AccountNo = arAccount.AccountNo; } dt.Currency = currency ? currency : 'MYR'; dt.DebitAmount = creditAmount ? creditAmount : 0.0; dt.Date = transactionDate; dt.Description = `${purpose}`; dt.Name = `${purpose}`; dt.RelatedDocNo = invoice.DocNo; dt.RelatedObjectId = invoice.RelatedObjectId; dt.RelatedObjectType = invoice.RelatedObjectType; const ct = await journalEntry.newLedgerTransaction(enum_1.TransactionTypeOptions.CREDIT); ct.AccountNo = item.value[0]; ct.Currency = currency ? currency : 'MYR'; ct.CreditAmount = creditAmount ? creditAmount : 0.0; ct.Date = transactionDate; ct.Description = customer.FullName; ct.Name = customer.FullName; ct.RelatedDocNo = invoice.DocNo; ct.RelatedObjectId = invoice.RelatedObjectId; ct.RelatedObjectType = invoice.RelatedObjectType; await this.postJournal(dbTransaction, journalEntry, loginUser); } return invoice; } catch (err) { console.log('Issue invoice err: ', err); throw err; } } static async findCustomer(custSystemRefId, dbTransaction) { const data = await FinanceCompany._FinanceCustomerRepository.findOne({ where: { CustSystemRefId: custSystemRefId, }, transaction: dbTransaction, }); return data; } async issueDebitNote(dbTransaction, loginUser, invoice, customer, dtAccountNo) { try { const duplicateInvoice = await FinanceCompany._DocumentRepository.findOne({ where: { DocNo: invoice.DocNo, }, transaction: dbTransaction, }); if (duplicateInvoice) { throw new Error('Invoice number already exists'); } const documentItems = await invoice.getDocumentItems(dbTransaction); if (!documentItems.length) { throw new Error('Document must have at least 1 document item'); } for (const invoiceItem of documentItems) { if (!invoiceItem.CtAccountNo) { throw new Error('Each document item should have CtAccountNo provided'); } } invoice.DocType = enum_1.DocType.DEBIT_NOTE; if (invoice.UseAccSystemDocYN === 'Y') { const accInvoiceRefIDetail = await this.AccountingSystem.createInvoice({ customer, invoice, paymentMode: 'credit', }, dbTransaction); invoice.AccSystemRefId = accInvoiceRefIDetail.invoiceRefId; invoice.PostedToAccSystemYN = 'Y'; invoice.PostedById = loginUser.ObjectId; invoice.PostedDateTime = new Date(); for (const documentItem of documentItems) { const accItem = accInvoiceRefIDetail.invoiceItems.find((item) => item.name === documentItem.Name); if (accItem) { documentItem.AccSystemRefId = accItem.itemRefId; documentItem.PostedToAccSystemYN = 'Y'; documentItem.PostedById = loginUser.ObjectId; documentItem.PostedDateTime = new Date(); } } } else { invoice.generateInvoice(loginUser.IDNo, customer, dbTransaction); } await FinanceCompany._DocumentRepository.create({ DocNo: invoice.DocNo, DocType: invoice.DocType, DocDate: invoice.DocDate, CompanyId: invoice.CompanyId, Currency: invoice.Currency, AmountBeforeDiscount: invoice.AmountBeforeDiscount, Amount: invoice.Amount, DiscountType: invoice.DiscountType, DiscountValue: invoice.DiscountValue, Description: invoice.Description, Status: invoice.Status, IssuedById: loginUser.ObjectId, IssuedToId: customer.ObjectId, IssuedToType: invoice.IssuedToType, RelatedObjectId: invoice.RelatedObjectId, RelatedObjectType: invoice.RelatedObjectType, CreatedById: loginUser.ObjectId, CreatedAt: new Date(), UpdatedById: loginUser.ObjectId, UpdatedAt: new Date(), DocPDFFileMediaId: invoice.DocPDFFileMediaId, DocHTMLFileMediaId: invoice.DocHTMLFileMediaId, AccSystemRefId: invoice.AccSystemRefId, PostedToAccSystemYN: invoice.PostedToAccSystemYN, PostedById: invoice.PostedToAccSystemYN == 'Y' ? invoice.PostedById : null, PostedDateTime: new Date(), UseAccSystemDocYN: invoice.UseAccSystemDocYN, }, { transaction: dbTransaction, }); documentItems.forEach(async (documentItem) => { await FinanceCompany._DocumentItemRepository.create({ DocumentItemId: this._createId().toUpperCase(), DocNo: documentItem.DocNo, Name: documentItem.Name, NameBM: documentItem.NameBM, Description: documentItem.Description, ItemId: documentItem.ItemId, ItemType: documentItem.ItemType, ItemSKU: documentItem.ItemSKU, ItemSerialNo: documentItem.ItemSerialNo, Currency: documentItem.Currency, UnitPrice: documentItem.UnitPrice, Quantity: documentItem.Quantity, QuantityUOM: documentItem.QuantityUOM, AmountBeforeDiscount: documentItem.AmountBeforeDiscount, Amount: documentItem.Amount, DiscountType: documentItem.DiscountType, DiscountValue: documentItem.DiscountValue, TaxCode: documentItem.TaxCode, TaxAmount: documentItem.TaxAmount, TaxRate: documentItem.TaxRate, TaxInclusiveYN: documentItem.TaxInclusiveYN, DtAccountNo: documentItem.DtAccountNo, CtAccountNo: documentItem.CtAccountNo, AccSystemRefId: documentItem.AccSystemRefId, PostedToAccSystemYN: documentItem.PostedToAccSystemYN, PostedById: documentItem.PostedById, PostedDateTime: documentItem.PostedDateTime, }, { transaction: dbTransaction, }); }); const transactionDate = new Date(); const htCreditAccountAmount = new general_1.HashTable(); const htCreditAccountCurrency = new general_1.HashTable(); const htCreditAccountPurpose = new general_1.HashTable(); documentItems.forEach((invoiceItem) => { if (!htCreditAccountAmount.exists(invoiceItem.CtAccountNo)) { htCreditAccountAmount.add(invoiceItem.CtAccountNo, invoiceItem.Amount); htCreditAccountCurrency.add(invoiceItem.CtAccountNo, invoiceItem.Currency); htCreditAccountPurpose.add(invoiceItem.CtAccountNo, invoiceItem.Name); } else { const d = htCreditAccountAmount.get(invoiceItem.CtAccountNo); htCreditAccountAmount.add(invoiceItem.CtAccountNo, d + invoiceItem.Amount); } }); const savedItems = htCreditAccountAmount.list(); for (const item of savedItems) { const journalEntry = new journal_entry_1.default(dbTransaction); journalEntry.init({ CompanyId: this.CompanyId, Name: 'issue Invoice ' + invoice.DocNo, }); const creditAmount = item.value[1]; const currency = htCreditAccountCurrency.get(item.value[0]); const purpose = htCreditAccountPurpose.get(item.value[0]); const dt = await journalEntry.newLedgerTransaction(enum_1.TransactionTypeOptions.DEBIT); if (dtAccountNo) { dt.AccountNo = dtAccountNo; } else { const arAccount = await customer.getAccountReceivable(); dt.AccountNo = arAccount.AccountNo; } dt.Currency = currency ? currency : 'MYR'; dt.DebitAmount = creditAmount ? creditAmount : 0.0; dt.Date = transactionDate; dt.Description = `${purpose}`; dt.Name = `${purpose}`; dt.RelatedDocNo = invoice.DocNo; dt.RelatedObjectId = invoice.RelatedObjectId; dt.RelatedObjectType = invoice.RelatedObjectType; const ct = await journalEntry.newLedgerTransaction(enum_1.TransactionTypeOptions.CREDIT); ct.AccountNo = item.value[0]; ct.Currency = currency ? currency : 'MYR'; ct.CreditAmount = creditAmount ? creditAmount : 0.0; ct.Date = transactionDate; ct.Description = customer.FullName; ct.Name = customer.FullName; ct.RelatedDocNo = invoice.DocNo; ct.RelatedObjectId = invoice.RelatedObjectId; ct.RelatedObjectType = invoice.RelatedObjectType; await this.postJournal(dbTransaction, journalEntry, loginUser); } return invoice; } catch (err) { console.log('Issue debit note err: ', err); throw err; } } async issueCreditNote(dbTransaction, loginUser, creditNote, customer, ctAccountNo) { try { const duplicateCreditNote = await FinanceCompany._DocumentRepository.findOne({ where: { DocNo: creditNote.DocNo, }, transaction: dbTransaction, }); if (duplicateCreditNote) { throw new Error('Invoice number already exists'); } const documentItems = await creditNote.getDocumentItems(dbTransaction); if (!documentItems.length) { throw new Error('Document must have at least 1 document item'); } for (const invoiceItem of documentItems) { if (!invoiceItem.DtAccountNo) { throw new Error('Each document item should have DtAccountNo provided'); } const actualDocument = await document_1.default.initDocument(dbTransaction, invoiceItem.ItemId); if (actualDocument.IssuedToId !== creditNote.IssuedToId) { throw new general_1.ClassError('FinanceCompany', 'FinanceCompanyErrMsgOX', 'To issue credit note, all invoices must belong to same customer.'); } } if (creditNote.UseAccSystemDocYN === 'Y' && this.AccountingSystem) { const creditNoteItems = await this.getAccSystemCreditNoteItems(dbTransaction, documentItems); const payload = { customer: { customerId: customer.AccSystemRefId, name: customer.FullName, email: customer.Email, contactNo: customer.ContactNo, }, referenceInvoices: [], issueDate: creditNote.DocDate.toISOString(), currency: creditNote.Currency, reason: creditNote.Description, items: creditNoteItems, createdBy: { userId: loginUser.ObjectId, name: '', }, }; const postedCreditNote = await this.AccountingSystem.createCreditNote(payload, dbTransaction); creditNote.DocNo = postedCreditNote.creditNoteNo; creditNote.AccSystemRefId = postedCreditNote.creditNoteRefId; creditNote.PostedToAccSystemYN = 'Y'; creditNote.PostedById = loginUser.ObjectId; creditNote.PostedDateTime = new Date(); } else { creditNote.generateCreditNote(loginUser.IDNo, customer); } creditNote.DocType = enum_1.DocType.CREDIT_NOTE; creditNote.Status = enum_1.DocumentStatus.SETTLED; const creditNotedDocumentPayload = { DocNo: creditNote.DocNo, DocType: creditNote.DocType, DocDate: creditNote.DocDate, CompanyId: creditNote.CompanyId, Currency: creditNote.Currency, AmountBeforeDiscount: creditNote.AmountBeforeDiscount, Amount: creditNote.Amount, DiscountType: creditNote.DiscountType, DiscountValue: creditNote.DiscountValue, Description: creditNote.Description, Status: creditNote.Status, IssuedById: creditNote.IssuedById ? creditNote.IssuedById : loginUser.ObjectId, IssuedToId: creditNote.IssuedToId ? creditNote.IssuedToId : customer.CustomerId, IssuedToType: creditNote.IssuedToType ? creditNote.IssuedToType : 'FinanceCustomer', RelatedObjectId: creditNote.RelatedObjectId, RelatedObjectType: creditNote.RelatedObjectType, CreatedById: loginUser.ObjectId, CreatedAt: new Date(), UpdatedById: loginUser.ObjectId, UpdatedAt: new Date(), DocPDFFileMediaId: creditNote.DocPDFFileMediaId, DocHTMLFileMediaId: creditNote.DocHTMLFileMediaId, AccSystemRefId: creditNote.AccSystemRefId, PostedToAccSystemYN: creditNote.PostedToAccSystemYN, PostedById: creditNote.PostedToAccSystemYN == 'Y' ? creditNote.PostedById : null, PostedDateTime: creditNote.PostedToAccSystemYN == 'Y' ? new Date() : null, UseAccSystemDocYN: creditNote.UseAccSystemDocYN, }; await FinanceCompany._DocumentRepository.create(creditNotedDocumentPayload, { transaction: dbTransaction, }); const activity = new activity_history_1.Activity(); activity.ActivityId = activity.createId(); activity.Action = activity_history_1.ActionEnum.CREATE; activity.Description = 'Create Credit Note ' + creditNote.DocNo; activity.EntityType = 'Document'; activity.EntityId = creditNote.DocNo; activity.EntityValueBefore = JSON.stringify({}); activity.EntityValueAfter = JSON.stringify(creditNotedDocumentPayload); await activity.create(loginUser.ObjectId, dbTransaction); for (const docItem of documentItems) { const documentItemPayload = { DocumentItemId: this._createId().toUpperCase(), DocNo: creditNote.DocNo, Name: docItem.Name, NameBM: docItem.NameBM, Description: docItem.Description, ItemId: docItem.ItemId, ItemType: docItem.ItemType, ItemSKU: docItem.ItemSKU, ItemSerialNo: docItem.ItemSerialNo, Currency: docItem.Currency, UnitPrice: docItem.UnitPrice, Quantity: docItem.Quantity, QuantityUOM: docItem.QuantityUOM, AmountBeforeDiscount: docItem.AmountBeforeDiscount, Amount: docItem.Amount, DiscountType: docItem.DiscountType, DiscountValue: docItem.DiscountValue, TaxCode: docItem.TaxCode, TaxAmount: docItem.TaxAmount, TaxRate: docItem.TaxRate, TaxInclusiveYN: docItem.TaxInclusiveYN, DtAccountNo: docItem.DtAccountNo ? docItem.DtAccountNo : null, CtAccountNo: docItem.CtAccountNo ? docItem.CtAccountNo : null, AccSystemRefId: docItem.AccSystemRefId, PostedToAccSystemYN: docItem.PostedToAccSystemYN, PostedById: docItem.PostedById, PostedDateTime: docItem.PostedDateTime, }; await FinanceCompany._DocumentItemRepository.create(documentItemPayload, { transaction: dbTransaction, }); const activity = new activity_history_1.Activity(); activity.ActivityId = activity.createId(); activity.Action = activity_history_1.ActionEnum.CREATE; activity.Description = 'Create Document Item for Credit Note ' + creditNote.DocNo; activity.EntityType = 'DocumentItem'; activity.EntityId = documentItemPayload.DocumentItemId; activity.EntityValueBefore = JSON.stringify({}); activity.EntityValueAfter = JSON.stringify(documentItemPayload); await activity.create(loginUser.ObjectId, dbTransaction); await document_1.default.settleByCreditNote(loginUser, dbTransaction, docItem.ItemId, docItem.Amount); } const journalEntry = new journal_entry_1.default(dbTransaction); journalEntry.init({ CompanyId: this.CompanyId, Name: 'Issue Credit Note ' + creditNote.DocNo, }); const transactionDate = new Date(); const creditTransaction = await journalEntry.newLedgerTransaction(enum_1.TransactionTypeOptions.CREDIT); if (ctAccountNo) { creditTransaction.AccountNo = ctAccountNo; } else {