fix: sync contacts balance with journal entries.

fix: edit invoice amount that has payment transactions.
This commit is contained in:
a.bouhuolia
2021-03-02 11:22:44 +02:00
parent b98d18f189
commit d51d9a5038
29 changed files with 477 additions and 401 deletions

View File

@@ -17,7 +17,6 @@ import {
export default class JournalCommands {
journal: JournalPoster;
models: any;
repositories: any;
@@ -77,7 +76,6 @@ export default class JournalCommands {
credit: bill.amount,
account: payableAccount.id,
contactId: bill.vendorId,
contactType: 'Vendor',
index: 1,
});
this.journal.credit(payableEntry);
@@ -113,8 +111,8 @@ export default class JournalCommands {
) {
const { accountRepository } = this.repositories;
const openingBalanceAccount = await accountRepository.findOne({
slug: 'opening-balance',
const incomeAccount = await accountRepository.findOne({
slug: 'other-income',
});
const receivableAccount = await accountRepository.findOne({
slug: 'accounts-receivable',
@@ -123,8 +121,6 @@ export default class JournalCommands {
const commonEntry = {
referenceType: 'CustomerOpeningBalance',
referenceId: customerId,
contactType: 'Customer',
contactId: customerId,
date: openingBalanceAt,
userId,
};
@@ -133,13 +129,14 @@ export default class JournalCommands {
credit: 0,
debit: openingBalance,
account: receivableAccount.id,
contactId: customerId,
index: 1,
});
const creditEntry = new JournalEntry({
...commonEntry,
credit: openingBalance,
debit: 0,
account: openingBalanceAccount.id,
account: incomeAccount.id,
index: 2,
});
this.journal.debit(debitEntry);
@@ -171,8 +168,6 @@ export default class JournalCommands {
const commonEntry = {
referenceType: 'VendorOpeningBalance',
referenceId: vendorId,
contactType: 'Vendor',
contactId: vendorId,
date: openingBalanceAt,
userId: authorizedUserId,
};
@@ -182,6 +177,7 @@ export default class JournalCommands {
credit: openingBalance,
debit: 0,
index: 1,
contactId: vendorId,
});
const debitEntry = new JournalEntry({
...commonEntry,
@@ -297,7 +293,6 @@ export default class JournalCommands {
account: entry.accountId,
referenceType: 'Journal',
referenceId: manualJournalObj.id,
contactType: entry.contactType,
contactId: entry.contactId,
note: entry.note,
date: manualJournalObj.date,
@@ -379,6 +374,7 @@ export default class JournalCommands {
...commonEntry,
debit: saleInvoice.balance,
account: receivableAccountId,
contactId: saleInvoice.customerId,
index: 1,
});
this.journal.debit(receivableEntry);

View 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();
}
}

View File

@@ -14,6 +14,4 @@ export default class JournalFinancial {
this.journal = journal;
this.accountsDepGraph = this.journal.accountsDepGraph;
}
}

View File

@@ -1,6 +1,7 @@
import { omit, get } from 'lodash';
import { omit, get, chain } from 'lodash';
import moment from 'moment';
import { Container } from 'typedi';
import async from 'async';
import JournalEntry from 'services/Accounting/JournalEntry';
import TenancyService from 'services/Tenancy/TenancyService';
import {
@@ -11,6 +12,19 @@ import {
TEntryType,
} 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 {
tenantId: number;
tenancy: TenancyService;
@@ -24,6 +38,10 @@ export default class JournalPoster implements IJournalPoster {
accountsDepGraph: IAccountsChange;
accountsBalanceTable: { [key: number]: number } = {};
contactsBalanceTable: {
[key: number]: { credit: number; debit: number }[];
} = {};
saveContactBalanceQueue: any;
/**
* Journal poster constructor.
@@ -39,6 +57,10 @@ export default class JournalPoster implements IJournalPoster {
if (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() {
return this.entries.length === 0;
@@ -85,6 +107,7 @@ export default class JournalPoster implements IJournalPoster {
}
this.entries.push(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.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.debit = -1 * entry.debit;
this.setAccountBalanceChange(entry, entry.accountNormal);
this.setAccountBalanceChange(entry);
this.setContactBalanceChange(entry);
});
this.deletedEntriesIds.push(...removeEntries.map((entry) => entry.id));
}