mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-23 00:00:31 +00:00
fix: sync contacts balance with journal entries.
fix: edit invoice amount that has payment transactions.
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"agenda": "^3.1.0",
|
"agenda": "^3.1.0",
|
||||||
"agendash": "^1.0.0",
|
"agendash": "^1.0.0",
|
||||||
"app-root-path": "^3.0.0",
|
"app-root-path": "^3.0.0",
|
||||||
|
"async": "^3.2.0",
|
||||||
"axios": "^0.20.0",
|
"axios": "^0.20.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { mapKeysDeep } from 'utils'
|
|||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
|
|
||||||
export default class BaseController {
|
export default class BaseController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts plain object keys to cameCase style.
|
* Converts plain object keys to cameCase style.
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
@@ -75,6 +74,10 @@ export default class BaseController {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async middleware.
|
||||||
|
* @param {function} callback
|
||||||
|
*/
|
||||||
asyncMiddleware(callback) {
|
asyncMiddleware(callback) {
|
||||||
return asyncMiddleware(callback);
|
return asyncMiddleware(callback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
manualJournalId
|
manualJournalId
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
manual_journal: manualJournal
|
manual_journal: manualJournal,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -449,14 +449,25 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (error.errorType === 'CONTACTS_SHOULD_ASSIGN_WITH_VALID_ACCOUNT') {
|
||||||
|
return res.boom.badRequest('', {
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
type: 'CONTACTS_SHOULD_ASSIGN_WITH_VALID_ACCOUNT',
|
||||||
|
code: 700,
|
||||||
|
meta: this.transfromToResponse(error.payload),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
if (error.errorType === 'contacts_not_found') {
|
if (error.errorType === 'contacts_not_found') {
|
||||||
return res.boom.badRequest('', {
|
return res.boom.badRequest('', {
|
||||||
errors: [{ type: 'CONTACTS_NOT_FOUND', code: 700 }],
|
errors: [{ type: 'CONTACTS_NOT_FOUND', code: 800 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (error.errorType === 'MANUAL_JOURNAL_ALREADY_PUBLISHED') {
|
if (error.errorType === 'MANUAL_JOURNAL_ALREADY_PUBLISHED') {
|
||||||
return res.boom.badRequest('', {
|
return res.boom.badRequest('', {
|
||||||
errors: [{ type: 'MANUAL_JOURNAL_ALREADY_PUBLISHED', code: 800 }],
|
errors: [{ type: 'MANUAL_JOURNAL_ALREADY_PUBLISHED', code: 900 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,8 +106,14 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
check('entries.*.quantity').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(),
|
check('entries.*.discount')
|
||||||
check('entries.*.description').optional({ nullable: true }).trim().escape(),
|
.optional({ nullable: true })
|
||||||
|
.isNumeric()
|
||||||
|
.toFloat(),
|
||||||
|
check('entries.*.description')
|
||||||
|
.optional({ nullable: true })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +236,11 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Deletes the sale invoice with associated entries and journal transaction.
|
// Deletes the sale invoice with associated entries and journal transaction.
|
||||||
await this.saleInvoiceService.deleteSaleInvoice(tenantId, saleInvoiceId, user);
|
await this.saleInvoiceService.deleteSaleInvoice(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: saleInvoiceId,
|
id: saleInvoiceId,
|
||||||
@@ -402,15 +412,23 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
}
|
}
|
||||||
if (error.errorType === 'SALE_ESTIMATE_NOT_FOUND') {
|
if (error.errorType === 'SALE_ESTIMATE_NOT_FOUND') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
errors: [
|
errors: [{ type: 'FROM_SALE_ESTIMATE_NOT_FOUND', code: 1200 }],
|
||||||
{ type: 'FROM_SALE_ESTIMATE_NOT_FOUND', code: 1200 },
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (error.errorType === 'SALE_ESTIMATE_CONVERTED_TO_INVOICE') {
|
if (error.errorType === 'SALE_ESTIMATE_CONVERTED_TO_INVOICE') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
errors: [
|
errors: [
|
||||||
{ type: 'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE', code: 1300 },
|
{
|
||||||
|
type: 'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE',
|
||||||
|
code: 1300,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [
|
||||||
|
{ type: 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT', code: 1400 },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default class LicensesController extends BaseController {
|
|||||||
challenge: true,
|
challenge: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/generate',
|
'/generate',
|
||||||
this.generateLicenseSchema,
|
this.generateLicenseSchema,
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ import 'subscribers/manualJournals';
|
|||||||
import 'subscribers/expenses';
|
import 'subscribers/expenses';
|
||||||
|
|
||||||
import 'subscribers/Bills';
|
import 'subscribers/Bills';
|
||||||
import 'subscribers/Bills/SyncVendorsBalances';
|
|
||||||
import 'subscribers/Bills/WriteJournalEntries';
|
import 'subscribers/Bills/WriteJournalEntries';
|
||||||
import 'subscribers/Bills/WriteInventoryTransactions';
|
import 'subscribers/Bills/WriteInventoryTransactions';
|
||||||
|
|
||||||
import 'subscribers/SaleInvoices';
|
import 'subscribers/SaleInvoices';
|
||||||
import 'subscribers/SaleInvoices/SyncCustomersBalance';
|
|
||||||
import 'subscribers/SaleInvoices/WriteInventoryTransactions';
|
import 'subscribers/SaleInvoices/WriteInventoryTransactions';
|
||||||
import 'subscribers/SaleInvoices/WriteJournalEntries';
|
import 'subscribers/SaleInvoices/WriteJournalEntries';
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,25 @@ export default class Contact extends TenantModel {
|
|||||||
* Defined virtual attributes.
|
* Defined virtual attributes.
|
||||||
*/
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return ['closingBalance'];
|
return ['contactNormal', 'closingBalance'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the contact normal by the given contact type.
|
||||||
|
*/
|
||||||
|
static getContactNormalByType(contactType) {
|
||||||
|
const types = {
|
||||||
|
'vendor': 'credit',
|
||||||
|
'customer': 'debit',
|
||||||
|
};
|
||||||
|
return types[contactType];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the contact noraml;
|
||||||
|
*/
|
||||||
|
get contactNormal() {
|
||||||
|
return Contact.getContactNormalByType(this.contactService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default class Customer extends TenantModel {
|
|||||||
* Defined virtual attributes.
|
* Defined virtual attributes.
|
||||||
*/
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return ['closingBalance'];
|
return ['closingBalance', 'contactNormal'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,6 +51,13 @@ export default class Customer extends TenantModel {
|
|||||||
return this.openingBalance + this.balance;
|
return this.openingBalance + this.balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the contact noraml;
|
||||||
|
*/
|
||||||
|
get contactNormal() {
|
||||||
|
return 'debit';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model modifiers.
|
* Model modifiers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default class Vendor extends TenantModel {
|
|||||||
* Defined virtual attributes.
|
* Defined virtual attributes.
|
||||||
*/
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return ['closingBalance'];
|
return ['closingBalance', 'contactNormal'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +50,13 @@ export default class Vendor extends TenantModel {
|
|||||||
return this.openingBalance + this.balance;
|
return this.openingBalance + this.balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the contact noraml;
|
||||||
|
*/
|
||||||
|
get contactNormal() {
|
||||||
|
return 'debit';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model modifiers.
|
* Model modifiers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
|
|
||||||
export default class JournalCommands {
|
export default class JournalCommands {
|
||||||
journal: JournalPoster;
|
journal: JournalPoster;
|
||||||
|
|
||||||
models: any;
|
models: any;
|
||||||
repositories: any;
|
repositories: any;
|
||||||
|
|
||||||
@@ -77,7 +76,6 @@ export default class JournalCommands {
|
|||||||
credit: bill.amount,
|
credit: bill.amount,
|
||||||
account: payableAccount.id,
|
account: payableAccount.id,
|
||||||
contactId: bill.vendorId,
|
contactId: bill.vendorId,
|
||||||
contactType: 'Vendor',
|
|
||||||
index: 1,
|
index: 1,
|
||||||
});
|
});
|
||||||
this.journal.credit(payableEntry);
|
this.journal.credit(payableEntry);
|
||||||
@@ -113,8 +111,8 @@ export default class JournalCommands {
|
|||||||
) {
|
) {
|
||||||
const { accountRepository } = this.repositories;
|
const { accountRepository } = this.repositories;
|
||||||
|
|
||||||
const openingBalanceAccount = await accountRepository.findOne({
|
const incomeAccount = await accountRepository.findOne({
|
||||||
slug: 'opening-balance',
|
slug: 'other-income',
|
||||||
});
|
});
|
||||||
const receivableAccount = await accountRepository.findOne({
|
const receivableAccount = await accountRepository.findOne({
|
||||||
slug: 'accounts-receivable',
|
slug: 'accounts-receivable',
|
||||||
@@ -123,8 +121,6 @@ export default class JournalCommands {
|
|||||||
const commonEntry = {
|
const commonEntry = {
|
||||||
referenceType: 'CustomerOpeningBalance',
|
referenceType: 'CustomerOpeningBalance',
|
||||||
referenceId: customerId,
|
referenceId: customerId,
|
||||||
contactType: 'Customer',
|
|
||||||
contactId: customerId,
|
|
||||||
date: openingBalanceAt,
|
date: openingBalanceAt,
|
||||||
userId,
|
userId,
|
||||||
};
|
};
|
||||||
@@ -133,13 +129,14 @@ export default class JournalCommands {
|
|||||||
credit: 0,
|
credit: 0,
|
||||||
debit: openingBalance,
|
debit: openingBalance,
|
||||||
account: receivableAccount.id,
|
account: receivableAccount.id,
|
||||||
|
contactId: customerId,
|
||||||
index: 1,
|
index: 1,
|
||||||
});
|
});
|
||||||
const creditEntry = new JournalEntry({
|
const creditEntry = new JournalEntry({
|
||||||
...commonEntry,
|
...commonEntry,
|
||||||
credit: openingBalance,
|
credit: openingBalance,
|
||||||
debit: 0,
|
debit: 0,
|
||||||
account: openingBalanceAccount.id,
|
account: incomeAccount.id,
|
||||||
index: 2,
|
index: 2,
|
||||||
});
|
});
|
||||||
this.journal.debit(debitEntry);
|
this.journal.debit(debitEntry);
|
||||||
@@ -171,8 +168,6 @@ export default class JournalCommands {
|
|||||||
const commonEntry = {
|
const commonEntry = {
|
||||||
referenceType: 'VendorOpeningBalance',
|
referenceType: 'VendorOpeningBalance',
|
||||||
referenceId: vendorId,
|
referenceId: vendorId,
|
||||||
contactType: 'Vendor',
|
|
||||||
contactId: vendorId,
|
|
||||||
date: openingBalanceAt,
|
date: openingBalanceAt,
|
||||||
userId: authorizedUserId,
|
userId: authorizedUserId,
|
||||||
};
|
};
|
||||||
@@ -182,6 +177,7 @@ export default class JournalCommands {
|
|||||||
credit: openingBalance,
|
credit: openingBalance,
|
||||||
debit: 0,
|
debit: 0,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
contactId: vendorId,
|
||||||
});
|
});
|
||||||
const debitEntry = new JournalEntry({
|
const debitEntry = new JournalEntry({
|
||||||
...commonEntry,
|
...commonEntry,
|
||||||
@@ -297,7 +293,6 @@ export default class JournalCommands {
|
|||||||
account: entry.accountId,
|
account: entry.accountId,
|
||||||
referenceType: 'Journal',
|
referenceType: 'Journal',
|
||||||
referenceId: manualJournalObj.id,
|
referenceId: manualJournalObj.id,
|
||||||
contactType: entry.contactType,
|
|
||||||
contactId: entry.contactId,
|
contactId: entry.contactId,
|
||||||
note: entry.note,
|
note: entry.note,
|
||||||
date: manualJournalObj.date,
|
date: manualJournalObj.date,
|
||||||
@@ -379,6 +374,7 @@ export default class JournalCommands {
|
|||||||
...commonEntry,
|
...commonEntry,
|
||||||
debit: saleInvoice.balance,
|
debit: saleInvoice.balance,
|
||||||
account: receivableAccountId,
|
account: receivableAccountId,
|
||||||
|
contactId: saleInvoice.customerId,
|
||||||
index: 1,
|
index: 1,
|
||||||
});
|
});
|
||||||
this.journal.debit(receivableEntry);
|
this.journal.debit(receivableEntry);
|
||||||
|
|||||||
74
server/src/services/Accounting/JournalContacts.ts
Normal file
74
server/src/services/Accounting/JournalContacts.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import async from 'async';
|
||||||
|
|
||||||
|
export default class JournalContacts {
|
||||||
|
saveContactBalanceQueue: any;
|
||||||
|
contactsBalanceTable: {
|
||||||
|
[key: number]: { credit: number; debit: number };
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
constructor(journal) {
|
||||||
|
this.journal = journal;
|
||||||
|
this.saveContactBalanceQueue = async.queue(
|
||||||
|
this.saveContactBalanceChangeTask.bind(this),
|
||||||
|
10
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets the contact balance change.
|
||||||
|
*/
|
||||||
|
private getContactsBalanceChanges(entry) {
|
||||||
|
if (!entry.contactId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const change = {
|
||||||
|
debit: entry.debit,
|
||||||
|
credit: entry.credit,
|
||||||
|
};
|
||||||
|
if (!this.contactsBalanceTable[entry.contactId]) {
|
||||||
|
this.contactsBalanceTable[entry.contactId] = { credit: 0, debit: 0 };
|
||||||
|
}
|
||||||
|
if (change.credit) {
|
||||||
|
this.contactsBalanceTable[entry.contactId].credit += change.credit;
|
||||||
|
}
|
||||||
|
if (change.debit) {
|
||||||
|
this.contactsBalanceTable[entry.contactId].debit += change.debit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save contacts balance change.
|
||||||
|
*/
|
||||||
|
saveContactsBalance() {
|
||||||
|
const balanceChanges = Object.entries(
|
||||||
|
this.contactsBalanceTable
|
||||||
|
).map(([contactId, { credit, debit }]) => ({ contactId, credit, debit }));
|
||||||
|
|
||||||
|
return this.saveContactBalanceQueue.pushAsync(balanceChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves contact balance change task.
|
||||||
|
* @param {number} contactId - Contact id.
|
||||||
|
* @param {number} credit - Credit amount.
|
||||||
|
* @param {number} debit - Debit amount.
|
||||||
|
*/
|
||||||
|
async saveContactBalanceChangeTask({ contactId, credit, debit }, callback) {
|
||||||
|
const { contactRepository } = this.repositories;
|
||||||
|
|
||||||
|
const contact = await contactRepository.findOneById(contactId);
|
||||||
|
let balanceChange = 0;
|
||||||
|
|
||||||
|
if (contact.contactNormal === 'credit') {
|
||||||
|
balanceChange += credit - debit;
|
||||||
|
} else {
|
||||||
|
balanceChange += debit - credit;
|
||||||
|
}
|
||||||
|
// Contact change balance.
|
||||||
|
await contactRepository.changeNumber(
|
||||||
|
{ id: contactId },
|
||||||
|
'balance',
|
||||||
|
balanceChange
|
||||||
|
);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,4 @@ export default class JournalFinancial {
|
|||||||
this.journal = journal;
|
this.journal = journal;
|
||||||
this.accountsDepGraph = this.journal.accountsDepGraph;
|
this.accountsDepGraph = this.journal.accountsDepGraph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { omit, get } from 'lodash';
|
import { omit, get, chain } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import async from 'async';
|
||||||
import JournalEntry from 'services/Accounting/JournalEntry';
|
import JournalEntry from 'services/Accounting/JournalEntry';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +12,19 @@ import {
|
|||||||
TEntryType,
|
TEntryType,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
|
|
||||||
|
const CONTACTS_CONFIG = [
|
||||||
|
{
|
||||||
|
accountBySlug: 'accounts-receivable',
|
||||||
|
contactService: 'customer',
|
||||||
|
assignRequired: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accountBySlug: 'accounts-payable',
|
||||||
|
contactService: 'vendor',
|
||||||
|
assignRequired: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default class JournalPoster implements IJournalPoster {
|
export default class JournalPoster implements IJournalPoster {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
@@ -24,6 +38,10 @@ export default class JournalPoster implements IJournalPoster {
|
|||||||
accountsDepGraph: IAccountsChange;
|
accountsDepGraph: IAccountsChange;
|
||||||
|
|
||||||
accountsBalanceTable: { [key: number]: number } = {};
|
accountsBalanceTable: { [key: number]: number } = {};
|
||||||
|
contactsBalanceTable: {
|
||||||
|
[key: number]: { credit: number; debit: number }[];
|
||||||
|
} = {};
|
||||||
|
saveContactBalanceQueue: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Journal poster constructor.
|
* Journal poster constructor.
|
||||||
@@ -39,6 +57,10 @@ export default class JournalPoster implements IJournalPoster {
|
|||||||
if (accountsGraph) {
|
if (accountsGraph) {
|
||||||
this.accountsDepGraph = accountsGraph;
|
this.accountsDepGraph = accountsGraph;
|
||||||
}
|
}
|
||||||
|
this.saveContactBalanceQueue = async.queue(
|
||||||
|
this.saveContactBalanceChangeTask.bind(this),
|
||||||
|
10
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +91,7 @@ export default class JournalPoster implements IJournalPoster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Detarmines the ledger is empty.
|
||||||
*/
|
*/
|
||||||
public isEmpty() {
|
public isEmpty() {
|
||||||
return this.entries.length === 0;
|
return this.entries.length === 0;
|
||||||
@@ -85,6 +107,7 @@ export default class JournalPoster implements IJournalPoster {
|
|||||||
}
|
}
|
||||||
this.entries.push(entryModel.entry);
|
this.entries.push(entryModel.entry);
|
||||||
this.setAccountBalanceChange(entryModel.entry);
|
this.setAccountBalanceChange(entryModel.entry);
|
||||||
|
this.setContactBalanceChange(entryModel.entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,6 +120,83 @@ export default class JournalPoster implements IJournalPoster {
|
|||||||
}
|
}
|
||||||
this.entries.push(entryModel.entry);
|
this.entries.push(entryModel.entry);
|
||||||
this.setAccountBalanceChange(entryModel.entry);
|
this.setAccountBalanceChange(entryModel.entry);
|
||||||
|
this.setContactBalanceChange(entryModel.entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the contact balance change.
|
||||||
|
*/
|
||||||
|
private setContactBalanceChange(entry) {
|
||||||
|
if (!entry.contactId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const change = {
|
||||||
|
debit: entry.debit || 0,
|
||||||
|
credit: entry.credit || 0,
|
||||||
|
account: entry.account,
|
||||||
|
};
|
||||||
|
if (!this.contactsBalanceTable[entry.contactId]) {
|
||||||
|
this.contactsBalanceTable[entry.contactId] = [];
|
||||||
|
}
|
||||||
|
this.contactsBalanceTable[entry.contactId].push(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save contacts balance change.
|
||||||
|
*/
|
||||||
|
async saveContactsBalance() {
|
||||||
|
await this.initAccountsDepGraph();
|
||||||
|
|
||||||
|
const balanceChanges = Object.entries(this.contactsBalanceTable).map(
|
||||||
|
([contactId, entries]) => ({
|
||||||
|
contactId,
|
||||||
|
entries: entries.filter((entry) => {
|
||||||
|
const account = this.accountsDepGraph.getNodeData(entry.account);
|
||||||
|
|
||||||
|
return (
|
||||||
|
account &&
|
||||||
|
CONTACTS_CONFIG.some((config) => {
|
||||||
|
return config.accountBySlug === account.slug;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const balanceEntries = chain(balanceChanges)
|
||||||
|
.map((change) => change.entries.map(entry => ({
|
||||||
|
...entry,
|
||||||
|
contactId: change.contactId
|
||||||
|
})))
|
||||||
|
.flatten()
|
||||||
|
.value();
|
||||||
|
|
||||||
|
return this.saveContactBalanceQueue.pushAsync(balanceEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves contact balance change task.
|
||||||
|
* @param {number} contactId - Contact id.
|
||||||
|
* @param {number} credit - Credit amount.
|
||||||
|
* @param {number} debit - Debit amount.
|
||||||
|
*/
|
||||||
|
async saveContactBalanceChangeTask({ contactId, credit, debit }) {
|
||||||
|
const { contactRepository } = this.repositories;
|
||||||
|
|
||||||
|
const contact = await contactRepository.findOneById(contactId);
|
||||||
|
let balanceChange = 0;
|
||||||
|
|
||||||
|
if (contact.contactNormal === 'credit') {
|
||||||
|
balanceChange += credit - debit;
|
||||||
|
} else {
|
||||||
|
balanceChange += debit - credit;
|
||||||
|
}
|
||||||
|
// Contact change balance.
|
||||||
|
await contactRepository.changeNumber(
|
||||||
|
{ id: contactId },
|
||||||
|
'balance',
|
||||||
|
balanceChange
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -321,7 +421,8 @@ export default class JournalPoster implements IJournalPoster {
|
|||||||
entry.credit = -1 * entry.credit;
|
entry.credit = -1 * entry.credit;
|
||||||
entry.debit = -1 * entry.debit;
|
entry.debit = -1 * entry.debit;
|
||||||
|
|
||||||
this.setAccountBalanceChange(entry, entry.accountNormal);
|
this.setAccountBalanceChange(entry);
|
||||||
|
this.setContactBalanceChange(entry);
|
||||||
});
|
});
|
||||||
this.deletedEntriesIds.push(...removeEntries.map((entry) => entry.id));
|
this.deletedEntriesIds.push(...removeEntries.map((entry) => entry.id));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,10 @@ export default class ContactsService {
|
|||||||
journal.fromTransactions(contactsTransactions);
|
journal.fromTransactions(contactsTransactions);
|
||||||
journal.removeEntries();
|
journal.removeEntries();
|
||||||
|
|
||||||
await Promise.all([journal.saveBalance(), journal.deleteEntries()]);
|
await Promise.all([
|
||||||
|
journal.saveBalance(),
|
||||||
|
journal.deleteEntries(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -268,7 +268,11 @@ export default class CustomersService {
|
|||||||
openingBalanceAt,
|
openingBalanceAt,
|
||||||
authorizedUserId
|
authorizedUserId
|
||||||
);
|
);
|
||||||
await Promise.all([journal.saveBalance(), journal.saveEntries()]);
|
await Promise.all([
|
||||||
|
journal.saveBalance(),
|
||||||
|
journal.saveEntries(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -192,7 +192,11 @@ export default class VendorsService {
|
|||||||
openingBalanceAt,
|
openingBalanceAt,
|
||||||
user
|
user
|
||||||
);
|
);
|
||||||
await Promise.all([journal.saveBalance(), journal.saveEntries()]);
|
await Promise.all([
|
||||||
|
journal.saveBalance(),
|
||||||
|
journal.saveEntries(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,29 +10,12 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
|||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import InventoryService from 'services/Inventory/Inventory';
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
import { ACCOUNT_PARENT_TYPE, ACCOUNT_ROOT_TYPE, ACCOUNT_TYPE } from 'data/AccountTypes'
|
import {
|
||||||
|
ACCOUNT_PARENT_TYPE,
|
||||||
const ERRORS = {
|
ACCOUNT_ROOT_TYPE,
|
||||||
NOT_FOUND: 'NOT_FOUND',
|
ACCOUNT_TYPE,
|
||||||
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
} from 'data/AccountTypes';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
ITEM_NAME_EXISTS: 'ITEM_NAME_EXISTS',
|
|
||||||
ITEM_CATEOGRY_NOT_FOUND: 'ITEM_CATEOGRY_NOT_FOUND',
|
|
||||||
COST_ACCOUNT_NOT_COGS: 'COST_ACCOUNT_NOT_COGS',
|
|
||||||
COST_ACCOUNT_NOT_FOUMD: 'COST_ACCOUNT_NOT_FOUMD',
|
|
||||||
SELL_ACCOUNT_NOT_FOUND: 'SELL_ACCOUNT_NOT_FOUND',
|
|
||||||
SELL_ACCOUNT_NOT_INCOME: 'SELL_ACCOUNT_NOT_INCOME',
|
|
||||||
|
|
||||||
INVENTORY_ACCOUNT_NOT_FOUND: 'INVENTORY_ACCOUNT_NOT_FOUND',
|
|
||||||
INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY',
|
|
||||||
|
|
||||||
ITEMS_HAVE_ASSOCIATED_TRANSACTIONS: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS',
|
|
||||||
ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
|
||||||
|
|
||||||
ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT:
|
|
||||||
'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
|
||||||
};
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ItemsService implements IItemsService {
|
export default class ItemsService implements IItemsService {
|
||||||
@Inject()
|
@Inject()
|
||||||
@@ -149,9 +132,7 @@ export default class ItemsService implements IItemsService {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
sellAccountId: number
|
sellAccountId: number
|
||||||
) {
|
) {
|
||||||
const {
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
accountRepository,
|
|
||||||
} = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
this.logger.info('[items] validate sell account existance.', {
|
this.logger.info('[items] validate sell account existance.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -166,7 +147,6 @@ export default class ItemsService implements IItemsService {
|
|||||||
sellAccountId,
|
sellAccountId,
|
||||||
});
|
});
|
||||||
throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_FOUND);
|
throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_FOUND);
|
||||||
|
|
||||||
} else if (!foundAccount.isParentType(ACCOUNT_ROOT_TYPE.INCOME)) {
|
} else if (!foundAccount.isParentType(ACCOUNT_ROOT_TYPE.INCOME)) {
|
||||||
this.logger.info('[items] sell account not income type.', {
|
this.logger.info('[items] sell account not income type.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -185,9 +165,7 @@ export default class ItemsService implements IItemsService {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
inventoryAccountId: number
|
inventoryAccountId: number
|
||||||
) {
|
) {
|
||||||
const {
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
accountRepository,
|
|
||||||
} = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
this.logger.info('[items] validate inventory account existance.', {
|
this.logger.info('[items] validate inventory account existance.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -203,7 +181,6 @@ export default class ItemsService implements IItemsService {
|
|||||||
inventoryAccountId,
|
inventoryAccountId,
|
||||||
});
|
});
|
||||||
throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_FOUND);
|
throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_FOUND);
|
||||||
|
|
||||||
} else if (!foundAccount.isAccountType(ACCOUNT_TYPE.INVENTORY)) {
|
} else if (!foundAccount.isAccountType(ACCOUNT_TYPE.INVENTORY)) {
|
||||||
this.logger.info('[items] inventory account not inventory type.', {
|
this.logger.info('[items] inventory account not inventory type.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
21
server/src/services/Items/constants.ts
Normal file
21
server/src/services/Items/constants.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
export const ERRORS = {
|
||||||
|
NOT_FOUND: 'NOT_FOUND',
|
||||||
|
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
||||||
|
|
||||||
|
ITEM_NAME_EXISTS: 'ITEM_NAME_EXISTS',
|
||||||
|
ITEM_CATEOGRY_NOT_FOUND: 'ITEM_CATEOGRY_NOT_FOUND',
|
||||||
|
COST_ACCOUNT_NOT_COGS: 'COST_ACCOUNT_NOT_COGS',
|
||||||
|
COST_ACCOUNT_NOT_FOUMD: 'COST_ACCOUNT_NOT_FOUMD',
|
||||||
|
SELL_ACCOUNT_NOT_FOUND: 'SELL_ACCOUNT_NOT_FOUND',
|
||||||
|
SELL_ACCOUNT_NOT_INCOME: 'SELL_ACCOUNT_NOT_INCOME',
|
||||||
|
|
||||||
|
INVENTORY_ACCOUNT_NOT_FOUND: 'INVENTORY_ACCOUNT_NOT_FOUND',
|
||||||
|
INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY',
|
||||||
|
|
||||||
|
ITEMS_HAVE_ASSOCIATED_TRANSACTIONS: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS',
|
||||||
|
ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
||||||
|
|
||||||
|
ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT:
|
||||||
|
'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
ISystemUser,
|
ISystemUser,
|
||||||
IManualJournal,
|
IManualJournal,
|
||||||
IPaginationMeta,
|
IPaginationMeta,
|
||||||
|
IManualJournalEntry,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
@@ -20,30 +21,7 @@ import {
|
|||||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||||
import JournalCommands from 'services/Accounting/JournalCommands';
|
import JournalCommands from 'services/Accounting/JournalCommands';
|
||||||
import JournalPosterService from 'services/Sales/JournalPosterService';
|
import JournalPosterService from 'services/Sales/JournalPosterService';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
const ERRORS = {
|
|
||||||
NOT_FOUND: 'manual_journal_not_found',
|
|
||||||
CREDIT_DEBIT_NOT_EQUAL_ZERO: 'credit_debit_not_equal_zero',
|
|
||||||
CREDIT_DEBIT_NOT_EQUAL: 'credit_debit_not_equal',
|
|
||||||
ACCCOUNTS_IDS_NOT_FOUND: 'acccounts_ids_not_found',
|
|
||||||
JOURNAL_NUMBER_EXISTS: 'journal_number_exists',
|
|
||||||
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
|
||||||
CONTACTS_NOT_FOUND: 'contacts_not_found',
|
|
||||||
MANUAL_JOURNAL_ALREADY_PUBLISHED: 'MANUAL_JOURNAL_ALREADY_PUBLISHED',
|
|
||||||
};
|
|
||||||
|
|
||||||
const CONTACTS_CONFIG = [
|
|
||||||
{
|
|
||||||
accountBySlug: 'accounts-receivable',
|
|
||||||
contactService: 'customer',
|
|
||||||
assignRequired: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accountBySlug: 'accounts-payable',
|
|
||||||
contactService: 'vendor',
|
|
||||||
assignRequired: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ManualJournalsService implements IManualJournalsService {
|
export default class ManualJournalsService implements IManualJournalsService {
|
||||||
@@ -159,8 +137,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId);
|
const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId);
|
||||||
|
|
||||||
const accounts = await Account.query()
|
const accounts = await Account.query().whereIn('id', manualAccountsIds);
|
||||||
.whereIn('id', manualAccountsIds);
|
|
||||||
|
|
||||||
const storedAccountsIds = accounts.map((account) => account.id);
|
const storedAccountsIds = accounts.map((account) => account.id);
|
||||||
|
|
||||||
@@ -203,29 +180,37 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
* @param {string} accountBySlug
|
* @param {string} accountBySlug
|
||||||
* @param {string} contactType
|
* @param {string} contactType
|
||||||
*/
|
*/
|
||||||
private async validateAccountsWithContactType(
|
private async validateAccountWithContactType(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
manualJournalDTO: IManualJournalDTO,
|
entriesDTO: IManualJournalEntry[],
|
||||||
accountBySlug: string,
|
|
||||||
contactType: string,
|
|
||||||
contactRequired: boolean = true
|
|
||||||
): Promise<void> {
|
|
||||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
const payableAccount = await accountRepository.findOne({
|
|
||||||
slug: accountBySlug,
|
|
||||||
});
|
|
||||||
|
|
||||||
const entriesHasNoVendorContact = manualJournalDTO.entries.filter(
|
accountBySlug: string,
|
||||||
(e) =>
|
contactType: string
|
||||||
e.accountId === payableAccount.id &&
|
): Promise<void> {
|
||||||
((!e.contactId && contactRequired) || e.contactType !== contactType)
|
const { Account, Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Retrieve account meta by the given account slug.
|
||||||
|
const account = await Account.query().findOne('slug', accountBySlug);
|
||||||
|
|
||||||
|
// Filter all entries of the given account.
|
||||||
|
const accountEntries = entriesDTO.filter(
|
||||||
|
(entry) => entry.accountId === account.id
|
||||||
);
|
);
|
||||||
if (entriesHasNoVendorContact.length > 0) {
|
// Can't continue if there is no entry that associate to the given account.
|
||||||
const indexes = entriesHasNoVendorContact.map((e) => e.index);
|
if (accountEntries.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Filter entries that have no contact type or not equal the valid type.
|
||||||
|
const entriesNoContact = accountEntries.filter(
|
||||||
|
(entry) => !entry.contactType || entry.contactType !== contactType
|
||||||
|
);
|
||||||
|
// Throw error in case one of entries that has invalid contact type.
|
||||||
|
if (entriesNoContact.length > 0) {
|
||||||
|
const indexes = entriesNoContact.map(e => e.index);
|
||||||
|
|
||||||
throw new ServiceError(ERRORS.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT, '', {
|
throw new ServiceError(ERRORS.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT, '', {
|
||||||
|
accountSlug: accountBySlug,
|
||||||
contactType,
|
contactType,
|
||||||
accountBySlug,
|
|
||||||
indexes,
|
indexes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -238,19 +223,22 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
*/
|
*/
|
||||||
private async dynamicValidateAccountsWithContactType(
|
private async dynamicValidateAccountsWithContactType(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
manualJournalDTO: IManualJournalDTO
|
entriesDTO: IManualJournalEntry[]
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return Promise.all(
|
return Promise.all([
|
||||||
CONTACTS_CONFIG.map(({ accountBySlug, contactService, assignRequired }) =>
|
this.validateAccountWithContactType(
|
||||||
this.validateAccountsWithContactType(
|
|
||||||
tenantId,
|
tenantId,
|
||||||
manualJournalDTO,
|
entriesDTO,
|
||||||
accountBySlug,
|
'accounts-receivable',
|
||||||
contactService,
|
'customer'
|
||||||
assignRequired
|
),
|
||||||
)
|
this.validateAccountWithContactType(
|
||||||
)
|
tenantId,
|
||||||
);
|
entriesDTO,
|
||||||
|
'accounts-payable',
|
||||||
|
'vendor'
|
||||||
|
),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -273,9 +261,9 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
const entriesContactsIds = entriesContactPairs.map(
|
const entriesContactsIds = entriesContactPairs.map(
|
||||||
(entry) => entry.contactId
|
(entry) => entry.contactId
|
||||||
);
|
);
|
||||||
|
|
||||||
// Retrieve all stored contacts on the storage from contacts entries.
|
// Retrieve all stored contacts on the storage from contacts entries.
|
||||||
const storedContacts = await contactRepository.findByIds(
|
const storedContacts = await contactRepository.findWhereIn(
|
||||||
|
'id',
|
||||||
entriesContactsIds
|
entriesContactsIds
|
||||||
);
|
);
|
||||||
// Converts the stored contacts to map with id as key and entry as value.
|
// Converts the stored contacts to map with id as key and entry as value.
|
||||||
@@ -287,8 +275,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
entriesContactPairs.forEach((contactEntry) => {
|
entriesContactPairs.forEach((contactEntry) => {
|
||||||
const storedContact = storedContactsMap.get(contactEntry.contactId);
|
const storedContact = storedContactsMap.get(contactEntry.contactId);
|
||||||
|
|
||||||
// in case the contact id not found or contact type no equals pushed to
|
// in case the contact id not found.
|
||||||
// not found contacts.
|
|
||||||
if (
|
if (
|
||||||
!storedContact ||
|
!storedContact ||
|
||||||
storedContact.contactService !== contactEntry.contactType
|
storedContact.contactService !== contactEntry.contactType
|
||||||
@@ -378,7 +365,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
// Validate accounts with contact type from the given config.
|
// Validate accounts with contact type from the given config.
|
||||||
await this.dynamicValidateAccountsWithContactType(
|
await this.dynamicValidateAccountsWithContactType(
|
||||||
tenantId,
|
tenantId,
|
||||||
manualJournalDTO
|
manualJournalDTO.entries
|
||||||
);
|
);
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
'[manual_journal] trying to save manual journal to the storage.',
|
'[manual_journal] trying to save manual journal to the storage.',
|
||||||
@@ -446,7 +433,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
// Validate accounts with contact type from the given config.
|
// Validate accounts with contact type from the given config.
|
||||||
await this.dynamicValidateAccountsWithContactType(
|
await this.dynamicValidateAccountsWithContactType(
|
||||||
tenantId,
|
tenantId,
|
||||||
manualJournalDTO
|
manualJournalDTO.entries
|
||||||
);
|
);
|
||||||
// Transform manual journal DTO to model.
|
// Transform manual journal DTO to model.
|
||||||
const manualJournalObj = this.transformEditDTOToModel(
|
const manualJournalObj = this.transformEditDTOToModel(
|
||||||
@@ -525,7 +512,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
manualJournalsIds: number[]
|
manualJournalsIds: number[]
|
||||||
): Promise<{
|
): Promise<{
|
||||||
oldManualJournals: IManualJournal[]
|
oldManualJournals: IManualJournal[];
|
||||||
}> {
|
}> {
|
||||||
const { ManualJournal, ManualJournalEntry } = this.tenancy.models(tenantId);
|
const { ManualJournal, ManualJournalEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -817,6 +804,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
journal.saveBalance(),
|
journal.saveBalance(),
|
||||||
journal.deleteEntries(),
|
journal.deleteEntries(),
|
||||||
journal.saveEntries(),
|
journal.saveEntries(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
]);
|
]);
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
'[manual_journal] the journal entries saved successfully.',
|
'[manual_journal] the journal entries saved successfully.',
|
||||||
|
|||||||
24
server/src/services/ManualJournals/constants.ts
Normal file
24
server/src/services/ManualJournals/constants.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export const ERRORS = {
|
||||||
|
NOT_FOUND: 'manual_journal_not_found',
|
||||||
|
CREDIT_DEBIT_NOT_EQUAL_ZERO: 'credit_debit_not_equal_zero',
|
||||||
|
CREDIT_DEBIT_NOT_EQUAL: 'credit_debit_not_equal',
|
||||||
|
ACCCOUNTS_IDS_NOT_FOUND: 'acccounts_ids_not_found',
|
||||||
|
JOURNAL_NUMBER_EXISTS: 'journal_number_exists',
|
||||||
|
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
||||||
|
CONTACTS_NOT_FOUND: 'contacts_not_found',
|
||||||
|
ENTRIES_CONTACTS_NOT_FOUND: 'ENTRIES_CONTACTS_NOT_FOUND',
|
||||||
|
MANUAL_JOURNAL_ALREADY_PUBLISHED: 'MANUAL_JOURNAL_ALREADY_PUBLISHED',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONTACTS_CONFIG = [
|
||||||
|
{
|
||||||
|
accountBySlug: 'accounts-receivable',
|
||||||
|
contactService: 'customer',
|
||||||
|
assignRequired: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accountBySlug: 'accounts-payable',
|
||||||
|
contactService: 'vendor',
|
||||||
|
assignRequired: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -27,7 +27,6 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
|||||||
import { entriesAmountDiff, formatDateFields } from 'utils';
|
import { entriesAmountDiff, formatDateFields } from 'utils';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import { ACCOUNT_PARENT_TYPE } from 'data/AccountTypes';
|
import { ACCOUNT_PARENT_TYPE } from 'data/AccountTypes';
|
||||||
import PayableAgingSummaryService from 'services/FinancialStatements/AgingSummary/APAgingSummaryService';
|
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
BILL_VENDOR_NOT_FOUND: 'VENDOR_NOT_FOUND',
|
BILL_VENDOR_NOT_FOUND: 'VENDOR_NOT_FOUND',
|
||||||
@@ -452,6 +451,7 @@ export default class BillPaymentsService {
|
|||||||
.delete();
|
.delete();
|
||||||
await BillPayment.query().where('id', billPaymentId).delete();
|
await BillPayment.query().where('id', billPaymentId).delete();
|
||||||
|
|
||||||
|
// Triggers `onBillPaymentDeleted` event.
|
||||||
await this.eventDispatcher.dispatch(events.billPayment.onDeleted, {
|
await this.eventDispatcher.dispatch(events.billPayment.onDeleted, {
|
||||||
tenantId,
|
tenantId,
|
||||||
billPaymentId,
|
billPaymentId,
|
||||||
@@ -490,7 +490,8 @@ export default class BillPaymentsService {
|
|||||||
*/
|
*/
|
||||||
public async recordJournalEntries(
|
public async recordJournalEntries(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
billPayment: IBillPayment
|
billPayment: IBillPayment,
|
||||||
|
override: boolean = false,
|
||||||
) {
|
) {
|
||||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
@@ -498,7 +499,7 @@ export default class BillPaymentsService {
|
|||||||
const paymentAmount = sumBy(billPayment.entries, 'paymentAmount');
|
const paymentAmount = sumBy(billPayment.entries, 'paymentAmount');
|
||||||
const formattedDate = moment(billPayment.paymentDate).format('YYYY-MM-DD');
|
const formattedDate = moment(billPayment.paymentDate).format('YYYY-MM-DD');
|
||||||
|
|
||||||
// Retrieve AP account from the storage.
|
// Retrieve A/P account from the storage.
|
||||||
const payableAccount = await accountRepository.findOne({
|
const payableAccount = await accountRepository.findOne({
|
||||||
slug: 'accounts-payable',
|
slug: 'accounts-payable',
|
||||||
});
|
});
|
||||||
@@ -511,26 +512,27 @@ export default class BillPaymentsService {
|
|||||||
referenceType: 'BillPayment',
|
referenceType: 'BillPayment',
|
||||||
date: formattedDate,
|
date: formattedDate,
|
||||||
};
|
};
|
||||||
if (billPayment.id) {
|
if (override) {
|
||||||
const transactions = await AccountTransaction.query()
|
const transactions = await AccountTransaction.query()
|
||||||
.whereIn('reference_type', ['BillPayment'])
|
.whereIn('reference_type', ['BillPayment'])
|
||||||
.where('reference_id', billPayment.id)
|
.where('reference_id', billPayment.id)
|
||||||
.withGraphFetched('account');
|
.withGraphFetched('account');
|
||||||
|
|
||||||
journal.loadEntries(transactions);
|
journal.fromTransactions(transactions);
|
||||||
journal.removeEntries();
|
journal.removeEntries();
|
||||||
}
|
}
|
||||||
const debitReceivable = new JournalEntry({
|
const debitReceivable = new JournalEntry({
|
||||||
...commonJournal,
|
...commonJournal,
|
||||||
debit: paymentAmount,
|
debit: paymentAmount,
|
||||||
contactType: 'Vendor',
|
|
||||||
contactId: billPayment.vendorId,
|
contactId: billPayment.vendorId,
|
||||||
account: payableAccount.id,
|
account: payableAccount.id,
|
||||||
|
index: 1,
|
||||||
});
|
});
|
||||||
const creditPaymentAccount = new JournalEntry({
|
const creditPaymentAccount = new JournalEntry({
|
||||||
...commonJournal,
|
...commonJournal,
|
||||||
credit: paymentAmount,
|
credit: paymentAmount,
|
||||||
account: billPayment.paymentAccountId,
|
account: billPayment.paymentAccountId,
|
||||||
|
index: 2,
|
||||||
});
|
});
|
||||||
journal.debit(debitReceivable);
|
journal.debit(debitReceivable);
|
||||||
journal.credit(creditPaymentAccount);
|
journal.credit(creditPaymentAccount);
|
||||||
@@ -539,6 +541,7 @@ export default class BillPaymentsService {
|
|||||||
journal.deleteEntries(),
|
journal.deleteEntries(),
|
||||||
journal.saveEntries(),
|
journal.saveEntries(),
|
||||||
journal.saveBalance(),
|
journal.saveBalance(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,7 +557,11 @@ export default class BillPaymentsService {
|
|||||||
|
|
||||||
await journalCommands.revertJournalEntries(billPaymentId, 'BillPayment');
|
await journalCommands.revertJournalEntries(billPaymentId, 'BillPayment');
|
||||||
|
|
||||||
return Promise.all([journal.saveBalance(), journal.deleteEntries()]);
|
return Promise.all([
|
||||||
|
journal.saveBalance(),
|
||||||
|
journal.deleteEntries(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -527,6 +527,7 @@ export default class BillsService extends SalesInvoicesCost {
|
|||||||
journal.deleteEntries(),
|
journal.deleteEntries(),
|
||||||
journal.saveEntries(),
|
journal.saveEntries(),
|
||||||
journal.saveBalance(),
|
journal.saveBalance(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ export default class JournalPosterService {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
journal.deleteEntries(),
|
journal.deleteEntries(),
|
||||||
journal.saveBalance()
|
journal.saveBalance(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
IPaymentReceiveEntryDTO,
|
IPaymentReceiveEntryDTO,
|
||||||
IPaymentReceivesFilter,
|
IPaymentReceivesFilter,
|
||||||
ISaleInvoice,
|
ISaleInvoice,
|
||||||
ISystemService,
|
|
||||||
ISystemUser,
|
ISystemUser,
|
||||||
IPaymentReceivePageEntry,
|
IPaymentReceivePageEntry,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
@@ -496,7 +495,7 @@ export default class PaymentReceiveService {
|
|||||||
paymentReceiveId: number,
|
paymentReceiveId: number,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser
|
||||||
): Promise<{
|
): Promise<{
|
||||||
paymentReceive: Omit<IPaymentReceive, "entries">;
|
paymentReceive: Omit<IPaymentReceive, 'entries'>;
|
||||||
entries: IPaymentReceivePageEntry[];
|
entries: IPaymentReceivePageEntry[];
|
||||||
}> {
|
}> {
|
||||||
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
|
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
@@ -665,7 +664,6 @@ export default class PaymentReceiveService {
|
|||||||
const creditReceivable = new JournalEntry({
|
const creditReceivable = new JournalEntry({
|
||||||
...commonJournal,
|
...commonJournal,
|
||||||
credit: paymentAmount,
|
credit: paymentAmount,
|
||||||
contactType: 'Customer',
|
|
||||||
contactId: paymentReceive.customerId,
|
contactId: paymentReceive.customerId,
|
||||||
account: receivableAccount.id,
|
account: receivableAccount.id,
|
||||||
index: 1,
|
index: 1,
|
||||||
@@ -683,6 +681,7 @@ export default class PaymentReceiveService {
|
|||||||
journal.deleteEntries(),
|
journal.deleteEntries(),
|
||||||
journal.saveEntries(),
|
journal.saveEntries(),
|
||||||
journal.saveBalance(),
|
journal.saveBalance(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,7 +704,11 @@ export default class PaymentReceiveService {
|
|||||||
|
|
||||||
await commands.revertJournalEntries(paymentReceiveId, 'PaymentReceive');
|
await commands.revertJournalEntries(paymentReceiveId, 'PaymentReceive');
|
||||||
|
|
||||||
await Promise.all([journal.saveBalance(), journal.deleteEntries()]);
|
await Promise.all([
|
||||||
|
journal.saveBalance(),
|
||||||
|
journal.deleteEntries(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
|||||||
import CustomersService from 'services/Contacts/CustomersService';
|
import CustomersService from 'services/Contacts/CustomersService';
|
||||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||||
import JournalPosterService from './JournalPosterService';
|
import JournalPosterService from './JournalPosterService';
|
||||||
|
import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
||||||
@@ -37,6 +38,8 @@ const ERRORS = {
|
|||||||
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
||||||
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
||||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
|
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
|
||||||
|
INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT:
|
||||||
|
'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT',
|
||||||
INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES:
|
INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES:
|
||||||
'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES',
|
'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES',
|
||||||
};
|
};
|
||||||
@@ -130,6 +133,20 @@ export default class SaleInvoicesService {
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the invoice amount is bigger than payment amount before edit the invoice.
|
||||||
|
* @param {number} saleInvoiceAmount
|
||||||
|
* @param {number} paymentAmount
|
||||||
|
*/
|
||||||
|
validateInvoiceAmountBiggerPaymentAmount(
|
||||||
|
saleInvoiceAmount: number,
|
||||||
|
paymentAmount: number
|
||||||
|
) {
|
||||||
|
if (saleInvoiceAmount < paymentAmount) {
|
||||||
|
throw new ServiceError(ERRORS.INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate whether sale invoice exists on the storage.
|
* Validate whether sale invoice exists on the storage.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -267,7 +284,7 @@ export default class SaleInvoicesService {
|
|||||||
saleInvoiceDTO: any,
|
saleInvoiceDTO: any,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser
|
||||||
): Promise<ISaleInvoice> {
|
): Promise<ISaleInvoice> {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(
|
const oldSaleInvoice = await this.getInvoiceOrThrowError(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -309,12 +326,16 @@ export default class SaleInvoicesService {
|
|||||||
'SaleInvoice',
|
'SaleInvoice',
|
||||||
saleInvoiceDTO.entries
|
saleInvoiceDTO.entries
|
||||||
);
|
);
|
||||||
|
// Validate the invoice amount is not smaller than the invoice payment amount.
|
||||||
|
this.validateInvoiceAmountBiggerPaymentAmount(
|
||||||
|
saleInvoiceObj.balance,
|
||||||
|
oldSaleInvoice.paymentAmount
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] trying to update sale invoice.');
|
this.logger.info('[sale_invoice] trying to update sale invoice.');
|
||||||
const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch(
|
const saleInvoice: ISaleInvoice = await saleInvoiceRepository.update(
|
||||||
{
|
{ ...omit(saleInvoiceObj, ['paymentAmount']) },
|
||||||
id: saleInvoiceId,
|
{ id: saleInvoiceId }
|
||||||
...saleInvoiceObj,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
// Triggers `onSaleInvoiceEdited` event.
|
// Triggers `onSaleInvoiceEdited` event.
|
||||||
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
|
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
|
||||||
@@ -470,6 +491,7 @@ export default class SaleInvoicesService {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
journal.deleteEntries(),
|
journal.deleteEntries(),
|
||||||
journal.saveBalance(),
|
journal.saveBalance(),
|
||||||
|
journal.saveContactsBalance(),
|
||||||
journal.saveEntries(),
|
journal.saveEntries(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
import { Container } from 'typedi';
|
|
||||||
import { EventSubscriber, On } from 'event-dispatch';
|
|
||||||
import events from 'subscribers/events';
|
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
|
||||||
|
|
||||||
@EventSubscriber()
|
|
||||||
export default class BillSubscriber {
|
|
||||||
tenancy: TenancyService;
|
|
||||||
logger: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.tenancy = Container.get(TenancyService);
|
|
||||||
this.logger = Container.get('logger');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles vendor balance increment once bill created.
|
|
||||||
*/
|
|
||||||
@On(events.bill.onCreated)
|
|
||||||
async handleVendorBalanceIncrement({ tenantId, billId, bill }) {
|
|
||||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
// Increments vendor balance.
|
|
||||||
this.logger.info('[bill] trying to increment vendor balance.', {
|
|
||||||
tenantId,
|
|
||||||
billId,
|
|
||||||
});
|
|
||||||
await vendorRepository.changeBalance(bill.vendorId, bill.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles vendor balance decrement once bill deleted.
|
|
||||||
*/
|
|
||||||
@On(events.bill.onDeleted)
|
|
||||||
async handleVendorBalanceDecrement({ tenantId, billId, oldBill }) {
|
|
||||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
// Decrements vendor balance.
|
|
||||||
this.logger.info('[bill] trying to decrement vendor balance.', {
|
|
||||||
tenantId,
|
|
||||||
billId,
|
|
||||||
});
|
|
||||||
await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles vendor balance difference change.
|
|
||||||
*/
|
|
||||||
@On(events.bill.onEdited)
|
|
||||||
async handleVendorBalanceDiffChange({ tenantId, billId, oldBill, bill }) {
|
|
||||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
// Changes the diff vendor balance between old and new amount.
|
|
||||||
this.logger.info('[bill[ change vendor the different balance.', {
|
|
||||||
tenantId,
|
|
||||||
billId,
|
|
||||||
});
|
|
||||||
await vendorRepository.changeDiffBalance(
|
|
||||||
bill.vendorId,
|
|
||||||
bill.amount,
|
|
||||||
oldBill.amount,
|
|
||||||
oldBill.vendorId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import { Container } from 'typedi';
|
|
||||||
import { On, EventSubscriber } from 'event-dispatch';
|
|
||||||
import events from 'subscribers/events';
|
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
|
||||||
|
|
||||||
@EventSubscriber()
|
|
||||||
export default class SaleInvoiceSubscriber {
|
|
||||||
logger: any;
|
|
||||||
tenancy: TenancyService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.logger = Container.get('logger');
|
|
||||||
this.tenancy = Container.get(TenancyService);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles customer balance increment once sale invoice created.
|
|
||||||
*/
|
|
||||||
@On(events.saleInvoice.onCreated)
|
|
||||||
public async handleCustomerBalanceIncrement({
|
|
||||||
tenantId,
|
|
||||||
saleInvoice,
|
|
||||||
saleInvoiceId,
|
|
||||||
}) {
|
|
||||||
const { customerRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] trying to increment customer balance.', {
|
|
||||||
tenantId,
|
|
||||||
});
|
|
||||||
await customerRepository.changeBalance(
|
|
||||||
saleInvoice.customerId,
|
|
||||||
saleInvoice.balance
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles customer balance diff balnace change once sale invoice edited.
|
|
||||||
*/
|
|
||||||
@On(events.saleInvoice.onEdited)
|
|
||||||
public async onSaleInvoiceEdited({
|
|
||||||
tenantId,
|
|
||||||
saleInvoice,
|
|
||||||
oldSaleInvoice,
|
|
||||||
saleInvoiceId,
|
|
||||||
}) {
|
|
||||||
const { customerRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] trying to change diff customer balance.', {
|
|
||||||
tenantId,
|
|
||||||
});
|
|
||||||
await customerRepository.changeDiffBalance(
|
|
||||||
saleInvoice.customerId,
|
|
||||||
saleInvoice.balance,
|
|
||||||
oldSaleInvoice.balance,
|
|
||||||
oldSaleInvoice.customerId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles customer balance decrement once sale invoice deleted.
|
|
||||||
*/
|
|
||||||
@On(events.saleInvoice.onDeleted)
|
|
||||||
public async handleCustomerBalanceDecrement({
|
|
||||||
tenantId,
|
|
||||||
saleInvoiceId,
|
|
||||||
oldSaleInvoice,
|
|
||||||
}) {
|
|
||||||
const { customerRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] trying to decrement customer balance.', {
|
|
||||||
tenantId,
|
|
||||||
});
|
|
||||||
await customerRepository.changeBalance(
|
|
||||||
oldSaleInvoice.customerId,
|
|
||||||
oldSaleInvoice.balance * -1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,12 +21,20 @@ export default class PaymentMadesSubscriber {
|
|||||||
*/
|
*/
|
||||||
@On(events.billPayment.onCreated)
|
@On(events.billPayment.onCreated)
|
||||||
@On(events.billPayment.onEdited)
|
@On(events.billPayment.onEdited)
|
||||||
async handleBillsIncrementPaymentAmount({ tenantId, billPayment, oldBillPayment, billPaymentId }) {
|
async handleBillsIncrementPaymentAmount({
|
||||||
this.logger.info('[payment_made] trying to change bills payment amount.', { tenantId, billPaymentId });
|
tenantId,
|
||||||
|
billPayment,
|
||||||
|
oldBillPayment,
|
||||||
|
billPaymentId,
|
||||||
|
}) {
|
||||||
|
this.logger.info('[payment_made] trying to change bills payment amount.', {
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
});
|
||||||
this.billPaymentsService.saveChangeBillsPaymentAmount(
|
this.billPaymentsService.saveChangeBillsPaymentAmount(
|
||||||
tenantId,
|
tenantId,
|
||||||
billPayment.entries,
|
billPayment.entries,
|
||||||
oldBillPayment?.entries || null,
|
oldBillPayment?.entries || null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,27 +42,19 @@ export default class PaymentMadesSubscriber {
|
|||||||
* Handle revert bill payment amount once bill payment deleted.
|
* Handle revert bill payment amount once bill payment deleted.
|
||||||
*/
|
*/
|
||||||
@On(events.billPayment.onDeleted)
|
@On(events.billPayment.onDeleted)
|
||||||
handleBillDecrementPaymentAmount({ tenantId, billPaymentId, oldBillPayment }) {
|
handleBillDecrementPaymentAmount({
|
||||||
this.logger.info('[payment_made] tring to revert bill payment amount.', { tenantId, billPaymentId });
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
oldBillPayment,
|
||||||
|
}) {
|
||||||
|
this.logger.info('[payment_made] tring to revert bill payment amount.', {
|
||||||
|
tenantId,
|
||||||
|
billPaymentId,
|
||||||
|
});
|
||||||
this.billPaymentsService.saveChangeBillsPaymentAmount(
|
this.billPaymentsService.saveChangeBillsPaymentAmount(
|
||||||
tenantId,
|
tenantId,
|
||||||
oldBillPayment.entries.map((entry) => ({ ...entry, paymentAmount: 0 })),
|
oldBillPayment.entries.map((entry) => ({ ...entry, paymentAmount: 0 })),
|
||||||
oldBillPayment.entries,
|
oldBillPayment.entries
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle vendor balance increment once payment made created.
|
|
||||||
*/
|
|
||||||
@On(events.billPayment.onCreated)
|
|
||||||
async handleVendorIncrement({ tenantId, billPayment, billPaymentId }) {
|
|
||||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
// Increment the vendor balance after bills payments.
|
|
||||||
this.logger.info('[bill_payment] trying to increment vendor balance.', { tenantId });
|
|
||||||
await vendorRepository.changeBalance(
|
|
||||||
billPayment.vendorId,
|
|
||||||
billPayment.amount * -1,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,20 +65,30 @@ export default class PaymentMadesSubscriber {
|
|||||||
async handleWriteJournalEntries({ tenantId, billPayment }) {
|
async handleWriteJournalEntries({ tenantId, billPayment }) {
|
||||||
// Records the journal transactions after bills payment
|
// Records the journal transactions after bills payment
|
||||||
// and change diff acoount balance.
|
// and change diff acoount balance.
|
||||||
this.logger.info('[bill_payment] trying to write journal entries.', { tenantId, billPaymentId: billPayment.id });
|
this.logger.info('[bill_payment] trying to write journal entries.', {
|
||||||
|
tenantId,
|
||||||
|
billPaymentId: billPayment.id,
|
||||||
|
});
|
||||||
await this.billPaymentsService.recordJournalEntries(tenantId, billPayment);
|
await this.billPaymentsService.recordJournalEntries(tenantId, billPayment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrements the vendor balance once bill payment deleted.
|
* Handle bill payment re-writing journal entries once the payment transaction be edited.
|
||||||
*/
|
*/
|
||||||
@On(events.billPayment.onDeleted)
|
@On(events.billPayment.onEdited)
|
||||||
async handleVendorDecrement({ tenantId, paymentMadeId, oldPaymentMade }) {
|
async handleRewriteJournalEntriesOncePaymentEdited({
|
||||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
tenantId,
|
||||||
|
billPayment,
|
||||||
await vendorRepository.changeBalance(
|
}) {
|
||||||
oldPaymentMade.vendorId,
|
// Records the journal transactions after bills payment be edited.
|
||||||
oldPaymentMade.amount,
|
this.logger.info('[bill_payment] trying to rewrite journal entries.', {
|
||||||
|
tenantId,
|
||||||
|
billPaymentId: billPayment.id,
|
||||||
|
});
|
||||||
|
await this.billPaymentsService.recordJournalEntries(
|
||||||
|
tenantId,
|
||||||
|
billPayment,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,24 +98,8 @@ export default class PaymentMadesSubscriber {
|
|||||||
@On(events.billPayment.onDeleted)
|
@On(events.billPayment.onDeleted)
|
||||||
async handleRevertJournalEntries({ tenantId, billPaymentId }) {
|
async handleRevertJournalEntries({ tenantId, billPaymentId }) {
|
||||||
await this.billPaymentsService.revertJournalEntries(
|
await this.billPaymentsService.revertJournalEntries(
|
||||||
tenantId, billPaymentId,
|
tenantId,
|
||||||
);
|
billPaymentId
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the vendor balance different between old and new once
|
|
||||||
* bill payment edited.
|
|
||||||
*/
|
|
||||||
@On(events.billPayment.onEdited)
|
|
||||||
async handleVendorChangeDiffBalance({ tenantId, paymentMadeId, billPayment, oldBillPayment }) {
|
|
||||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
// Change the different vendor balance between the new and old one.
|
|
||||||
await vendorRepository.changeDiffBalance(
|
|
||||||
billPayment.vendorId,
|
|
||||||
oldBillPayment.vendorId,
|
|
||||||
billPayment.amount * -1,
|
|
||||||
oldBillPayment.amount * -1,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,61 +39,6 @@ export default class PaymentReceivesSubscriber {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle customer balance decrement once payment receive created.
|
|
||||||
*/
|
|
||||||
@On(events.paymentReceive.onCreated)
|
|
||||||
async handleCustomerBalanceDecrement({
|
|
||||||
tenantId,
|
|
||||||
paymentReceiveId,
|
|
||||||
paymentReceive,
|
|
||||||
}) {
|
|
||||||
const { customerRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
this.logger.info('[payment_receive] trying to decrement customer balance.');
|
|
||||||
await customerRepository.changeBalance(
|
|
||||||
paymentReceive.customerId,
|
|
||||||
paymentReceive.amount * -1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle customer balance increment once payment receive deleted.
|
|
||||||
*/
|
|
||||||
@On(events.paymentReceive.onDeleted)
|
|
||||||
async handleCustomerBalanceIncrement({
|
|
||||||
tenantId,
|
|
||||||
paymentReceiveId,
|
|
||||||
oldPaymentReceive,
|
|
||||||
}) {
|
|
||||||
const { customerRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
this.logger.info('[payment_receive] trying to increment customer balance.');
|
|
||||||
await customerRepository.changeBalance(
|
|
||||||
oldPaymentReceive.customerId,
|
|
||||||
oldPaymentReceive.amount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles customer balance diff balance change once payment receive edited.
|
|
||||||
*/
|
|
||||||
@On(events.paymentReceive.onEdited)
|
|
||||||
async handleCustomerBalanceDiffChange({
|
|
||||||
tenantId,
|
|
||||||
paymentReceiveId,
|
|
||||||
paymentReceive,
|
|
||||||
oldPaymentReceive,
|
|
||||||
}) {
|
|
||||||
const { customerRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
await customerRepository.changeDiffBalance(
|
|
||||||
paymentReceive.customerId,
|
|
||||||
paymentReceive.amount * -1,
|
|
||||||
oldPaymentReceive.amount * -1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle journal entries writing once the payment receive edited.
|
* Handle journal entries writing once the payment receive edited.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user