fix: writing the bill journal entries.

This commit is contained in:
a.bouhuolia
2020-12-19 12:08:42 +02:00
parent 112eaec488
commit 920875d7d9
10 changed files with 170 additions and 92 deletions

View File

@@ -197,8 +197,8 @@ export default class BillsController extends BaseController {
* @param {Response} res
*/
async editBill(req: Request, res: Response, next: NextFunction) {
const { id: billId, user } = req.params;
const { tenantId } = req;
const { id: billId } = req.params;
const { tenantId, user } = req;
const billDTO: IBillEditDTO = this.matchedBodyData(req);
try {

View File

@@ -46,6 +46,7 @@ export interface IBill {
openedAt: Date | string,
entries: IItemEntry[],
userId: number,
};
export interface IBillsFilter extends IDynamicListFilterDTO {

View File

@@ -11,6 +11,8 @@ interface IJournalTransactionsFilter {
toAmount: number,
contactsIds?: number[],
contactType?: string,
referenceType?: string[],
referenceId?: number[],
};
export default class AccountTransactionsRepository extends TenantRepository {
@@ -42,6 +44,12 @@ export default class AccountTransactionsRepository extends TenantRepository {
if (filter.contactType) {
query.where('contact_type', filter.contactType);
}
if (filter.referenceType && filter.referenceType.length > 0) {
query.whereIn('reference_type', filter.referenceType);
}
if (filter.referenceId && filter.referenceId.length > 0) {
query.whereIn('reference_id', filter.referenceId);
}
});
});
}

View File

@@ -177,7 +177,20 @@ export default class CachableRepository extends EntityRepository{
*
* @param {string|number[]} values -
*/
async deleteWhereIn(values: string | number[]) {
async deleteWhereIn(field: string, values: string | number[]) {
const result = await super.deleteWhereIn(field, values);
// Flushes the repository cache after delete operation.
this.flushCache();
return result;
}
/**
*
* @param {string|number[]} values
*/
async deleteWhereIdIn(values: string | number[]) {
const result = await super.deleteWhereIdIn(values);
// Flushes the repository cache after delete operation.

View File

@@ -175,7 +175,7 @@ export default class EntityRepository {
}
/**
*
* Deletes the given entries in the array on the specific field.
* @param {string} field -
* @param {number|string} values -
*/

View File

@@ -1,4 +1,6 @@
import { sumBy, chain } from 'lodash';
import moment from 'moment';
import { IBill } from 'interfaces';
import JournalPoster from "./JournalPoster";
import JournalEntry from "./JournalEntry";
import { AccountTransaction } from 'models';
@@ -7,6 +9,7 @@ import {
IManualJournal,
IExpense,
IExpenseCategory,
IItem,
} from 'interfaces';
interface IInventoryCostEntity {
@@ -57,6 +60,68 @@ export default class JournalCommands{
this.models = this.journal.models;
}
/**
* Records the bill journal entries.
* @param {IBill} bill
* @param {boolean} override - Override the old bill entries.
*/
async bill(bill: IBill, override: boolean = false): Promise<void> {
const { transactionsRepository, accountRepository } = this.repositories;
const { Item, ItemEntry } = this.models;
const entriesItemsIds = bill.entries.map((entry) => entry.itemId);
// Retrieve the bill transaction items.
const storedItems = await Item.query().whereIn('id', entriesItemsIds);
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
const payableAccount = await accountRepository.findOne({ slug: 'accounts-payable' });
const formattedDate = moment(bill.billDate).format('YYYY-MM-DD');
const commonJournalMeta = {
debit: 0,
credit: 0,
referenceId: bill.id,
referenceType: 'Bill',
date: formattedDate,
userId: bill.userId,
};
// Overrides the old bill entries.
if (override) {
const entries = await transactionsRepository.journal({
referenceType: ['Bill'],
referenceId: [bill.id],
});
this.journal.fromTransactions(entries);
this.journal.removeEntries();
}
const payableEntry = new JournalEntry({
...commonJournalMeta,
credit: bill.amount,
account: payableAccount.id,
contactId: bill.vendorId,
contactType: 'Vendor',
index: 1,
});
this.journal.credit(payableEntry);
bill.entries.forEach((entry, index) => {
const item: IItem = storedItemsMap.get(entry.itemId);
const amount = ItemEntry.calcAmount(entry);
const debitEntry = new JournalEntry({
...commonJournalMeta,
debit: amount,
account:
['inventory'].indexOf(item.type) !== -1
? item.inventoryAccountId
: item.costAccountId,
index: index + 2,
});
this.journal.debit(debitEntry);
});
}
/**
* Customer opening balance journals.
* @param {number} customerId

View File

@@ -21,7 +21,7 @@ export default class JournalPoster implements IJournalPoster {
deletedEntriesIds: number[] = [];
entries: IJournalEntry[] = [];
balancesChange: IAccountsChange = {};
accountsDepGraph: IAccountsChange = {};
accountsDepGraph: IAccountsChange;
accountsBalanceTable: { [key: number]: number; } = {};
@@ -250,12 +250,12 @@ export default class JournalPoster implements IJournalPoster {
* @returns {Promise<void>}
*/
public async saveEntries() {
const { AccountTransaction } = this.models;
const { transactionsRepository } = this.repositories;
const saveOperations: Promise<void>[] = [];
this.entries.forEach((entry) => {
const oper = AccountTransaction.query()
.insert({
const oper = transactionsRepository
.create({
accountId: entry.account,
...omit(entry, ['account']),
});
@@ -309,12 +309,10 @@ export default class JournalPoster implements IJournalPoster {
* @return {Promise<void>}
*/
public async deleteEntries() {
const { AccountTransaction } = this.models;
const { transactionsRepository } = this.repositories;
if (this.deletedEntriesIds.length > 0) {
await AccountTransaction.query()
.whereIn('id', this.deletedEntriesIds)
.delete();
await transactionsRepository.deleteWhereIdIn(this.deletedEntriesIds);
}
}
@@ -332,7 +330,6 @@ export default class JournalPoster implements IJournalPoster {
});
}
/**
* Calculates the entries balance change.
* @public

View File

@@ -27,6 +27,7 @@ import {
import { ServiceError } from 'exceptions';
import ItemsService from 'services/Items/ItemsService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import JournalCommands from 'services/Accounting/JournalCommands';
const ERRORS = {
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
@@ -139,8 +140,8 @@ export default class BillsService extends SalesInvoicesCost {
private async billDTOToModel(
tenantId: number,
billDTO: IBillDTO | IBillEditDTO,
oldBill?: IBill,
authorizedUser: ISystemUser,
oldBill?: IBill,
) {
const { ItemEntry } = this.tenancy.models(tenantId);
let invLotNumber = oldBill?.invLotNumber;
@@ -156,7 +157,7 @@ export default class BillsService extends SalesInvoicesCost {
return {
...formatDateFields(
omit(billDTO, ['open']),
omit(billDTO, ['open', 'entries']),
['billDate', 'dueDate']
),
amount,
@@ -165,7 +166,6 @@ export default class BillsService extends SalesInvoicesCost {
reference_type: 'Bill',
...omit(entry, ['amount', 'id']),
})),
// Avoid rewrite the open date in edit mode when already opened.
...(billDTO.open && (!oldBill?.openedAt)) && ({
openedAt: moment().toMySqlDateTime(),
@@ -197,7 +197,7 @@ export default class BillsService extends SalesInvoicesCost {
const { Bill } = this.tenancy.models(tenantId);
this.logger.info('[bill] trying to create a new bill', { tenantId, billDTO });
const billObj = await this.billDTOToModel(tenantId, billDTO, null, authorizedUser);
const billObj = await this.billDTOToModel(tenantId, billDTO, authorizedUser, null);
// Retrieve vendor or throw not found service error.
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
@@ -253,7 +253,7 @@ export default class BillsService extends SalesInvoicesCost {
const oldBill = await this.getBillOrThrowError(tenantId, billId);
// Transforms the bill DTO object to model object.
const billObj = await this.billDTOToModel(tenantId, billDTO, oldBill, authorizedUser);
const billObj = await this.billDTOToModel(tenantId, billDTO, authorizedUser, oldBill);
// Retrieve vendor details or throw not found service error.
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
@@ -342,62 +342,16 @@ export default class BillsService extends SalesInvoicesCost {
* @param {IBill} bill
* @param {Integer} billId
*/
public async recordJournalTransactions(tenantId: number, bill: IBill, billId?: number) {
const { AccountTransaction, Item, ItemEntry } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId);
const entriesItemsIds = bill.entries.map((entry) => entry.itemId);
const formattedDate = moment(bill.billDate).format('YYYY-MM-DD');
const storedItems = await Item.query().whereIn('id', entriesItemsIds);
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
const payableAccount = await accountRepository.find({ slug: 'accounts-payable' });
public async recordJournalTransactions(
tenantId: number,
bill: IBill,
override: boolean = false,
) {
const journal = new JournalPoster(tenantId);
const journalCommands = new JournalCommands(journal);
const commonJournalMeta = {
debit: 0,
credit: 0,
referenceId: bill.id,
referenceType: 'Bill',
date: formattedDate,
userId: bill.userId,
};
if (billId) {
const transactions = await AccountTransaction.query()
.whereIn('reference_type', ['Bill'])
.whereIn('reference_id', [billId])
.withGraphFetched('account.type');
await journalCommands.bill(bill, override)
journal.loadEntries(transactions);
journal.removeEntries();
}
const payableEntry = new JournalEntry({
...commonJournalMeta,
credit: bill.amount,
account: payableAccount.id,
contactId: bill.vendorId,
contactType: 'Vendor',
index: 1,
});
journal.credit(payableEntry);
bill.entries.forEach((entry, index) => {
const item: IItem = storedItemsMap.get(entry.itemId);
const amount = ItemEntry.calcAmount(entry);
const debitEntry = new JournalEntry({
...commonJournalMeta,
debit: amount,
account:
['inventory'].indexOf(item.type) !== -1
? item.inventoryAccountId
: item.costAccountId,
index: index + 2,
});
journal.debit(debitEntry);
});
return Promise.all([
journal.deleteEntries(),
journal.saveEntries(),

View File

@@ -37,11 +37,20 @@ export default class BillSubscriber {
* Handles writing journal entries once bill created.
*/
@On(events.bill.onCreated)
@On(events.bill.onEdited)
async handlerWriteJournalEntries({ tenantId, billId, bill }) {
async handlerWriteJournalEntriesOnCreate({ tenantId, bill }) {
// Writes the journal entries for the given bill transaction.
this.logger.info('[bill] writing bill journal entries.', { tenantId });
await this.billsService.recordJournalTransactions(tenantId, bill, billId);
await this.billsService.recordJournalTransactions(tenantId, bill);
}
/**
* Handles the overwriting journal entries once bill edited.
*/
@On(events.bill.onEdited)
async handleOverwriteJournalEntriesOnEdit({ tenantId, bill }) {
// Overwrite the journal entries for the given bill transaction.
this.logger.info('[bill] overwriting bill journal entries.', { tenantId });
await this.billsService.recordJournalTransactions(tenantId, bill, true);
}
/**

View File

@@ -1,9 +1,10 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from "event-dispatch";
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import SettingsService from 'services/Settings/SettingsService';
import SaleEstimateService from 'services/Sales/SalesEstimate';
@EventSubscriber()
export default class SaleInvoiceSubscriber {
logger: any;
@@ -22,23 +23,36 @@ export default class SaleInvoiceSubscriber {
* Handles customer balance increment once sale invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleCustomerBalanceIncrement({ tenantId, saleInvoice, saleInvoiceId }) {
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);
this.logger.info('[sale_invoice] trying to increment customer balance.', {
tenantId,
});
await customerRepository.changeBalance(
saleInvoice.customerId,
saleInvoice.balance
);
}
/**
*
* Marks the sale estimate as converted from the sale invoice once created.
*/
@On(events.saleInvoice.onCreated)
public async handleMarkEstimateConvert({ tenantId, saleInvoice, saleInvoiceId }) {
if (saleInvoice.fromEstiamteId) {
public async handleMarkEstimateConvert({
tenantId,
saleInvoice,
saleInvoiceId,
}) {
if (saleInvoice.fromEstimateId) {
this.saleEstimatesService.convertEstimateToInvoice(
tenantId,
saleInvoice.fromEstiamteId,
saleInvoiceId,
saleInvoiceId
);
}
}
@@ -47,29 +61,42 @@ export default class SaleInvoiceSubscriber {
* Handles customer balance diff balnace change once sale invoice edited.
*/
@On(events.saleInvoice.onEdited)
public async onSaleInvoiceEdited({ tenantId, saleInvoice, oldSaleInvoice, saleInvoiceId }) {
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 });
this.logger.info('[sale_invoice] trying to change diff customer balance.', {
tenantId,
});
await customerRepository.changeDiffBalance(
saleInvoice.customerId,
saleInvoice.balance,
oldSaleInvoice.balance,
oldSaleInvoice.customerId,
)
oldSaleInvoice.customerId
);
}
/**
* Handles customer balance decrement once sale invoice deleted.
*/
@On(events.saleInvoice.onDeleted)
public async handleCustomerBalanceDecrement({ tenantId, saleInvoiceId, oldSaleInvoice }) {
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(
this.logger.info('[sale_invoice] trying to decrement customer balance.', {
tenantId,
});
await customerRepository.changeBalance(
oldSaleInvoice.customerId,
oldSaleInvoice.balance * -1,
oldSaleInvoice.balance * -1
);
}
@@ -77,10 +104,14 @@ export default class SaleInvoiceSubscriber {
* Handles sale invoice next number increment once invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleInvoiceNextNumberIncrement({ tenantId, saleInvoiceId, saleInvoice }) {
public async handleInvoiceNextNumberIncrement({
tenantId,
saleInvoiceId,
saleInvoice,
}) {
await this.settingsService.incrementNextNumber(tenantId, {
key: 'next_number',
group: 'sales_invoices',
});
}
}
}