Files
bigcapital/server/src/services/Accounting/JournalCommands.ts
a.bouhuolia 2d6d642f3b feat(reports): inventory valuation report.
feat(reports): sales by items report.
feat(reports): purchases by items report.
2021-03-29 10:51:24 +02:00

478 lines
13 KiB
TypeScript

import moment from 'moment';
import {
IBill,
IManualJournalEntry,
ISaleReceipt,
ISystemUser,
} from 'interfaces';
import JournalPoster from './JournalPoster';
import JournalEntry from './JournalEntry';
import {
IManualJournal,
IExpense,
IExpenseCategory,
IItem,
ISaleInvoice,
IInventoryLotCost,
IItemEntry,
} from 'interfaces';
import { increment } from 'utils';
export default class JournalCommands {
journal: JournalPoster;
models: any;
repositories: any;
/**
* Constructor method.
* @param {JournalPoster} journal -
*/
constructor(journal: JournalPoster) {
this.journal = journal;
this.repositories = this.journal.repositories;
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,
referenceNumber: bill.referenceNo,
transactionNumber: bill.billNumber,
createdAt: bill.createdAt,
};
// 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,
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
: entry.costAccountId,
index: index + 2,
itemId: entry.itemId,
itemQuantity: entry.quantity,
});
this.journal.debit(debitEntry);
});
}
/**
* Customer opening balance journals.
* @param {number} customerId
* @param {number} openingBalance
*/
async customerOpeningBalance(
customerId: number,
openingBalance: number,
openingBalanceAt: Date | string,
userId: number
) {
const { accountRepository } = this.repositories;
const incomeAccount = await accountRepository.findOne({
slug: 'other-income',
});
const receivableAccount = await accountRepository.findOne({
slug: 'accounts-receivable',
});
const commonEntry = {
referenceType: 'CustomerOpeningBalance',
referenceId: customerId,
date: openingBalanceAt,
userId,
};
const debitEntry = new JournalEntry({
...commonEntry,
credit: 0,
debit: openingBalance,
account: receivableAccount.id,
contactId: customerId,
index: 1,
});
const creditEntry = new JournalEntry({
...commonEntry,
credit: openingBalance,
debit: 0,
account: incomeAccount.id,
index: 2,
});
this.journal.debit(debitEntry);
this.journal.credit(creditEntry);
}
/**
* Vendor opening balance journals
* @param {number} vendorId
* @param {number} openingBalance
* @param {Date|string} openingBalanceAt
* @param {number} authorizedUserId
*/
async vendorOpeningBalance(
vendorId: number,
openingBalance: number,
openingBalanceAt: Date | string,
authorizedUserId: ISystemUser
) {
const { accountRepository } = this.repositories;
const payableAccount = await accountRepository.findOne({
slug: 'accounts-payable',
});
const otherCost = await accountRepository.findOne({
slug: 'other-expenses',
});
const commonEntry = {
referenceType: 'VendorOpeningBalance',
referenceId: vendorId,
date: openingBalanceAt,
userId: authorizedUserId,
};
const creditEntry = new JournalEntry({
...commonEntry,
account: payableAccount.id,
credit: openingBalance,
debit: 0,
index: 1,
contactId: vendorId,
});
const debitEntry = new JournalEntry({
...commonEntry,
account: otherCost.id,
debit: openingBalance,
credit: 0,
index: 2,
});
this.journal.debit(debitEntry);
this.journal.credit(creditEntry);
}
/**
* Writes journal entries of expense model object.
* @param {IExpense} expense
*/
expense(expense: IExpense, userId: number) {
const mixinEntry = {
referenceType: 'Expense',
referenceId: expense.id,
date: expense.paymentDate,
userId,
};
const paymentJournalEntry = new JournalEntry({
credit: expense.totalAmount,
account: expense.paymentAccountId,
index: 1,
...mixinEntry,
});
this.journal.credit(paymentJournalEntry);
expense.categories.forEach((category: IExpenseCategory, index) => {
const expenseJournalEntry = new JournalEntry({
account: category.expenseAccountId,
debit: category.amount,
note: category.description,
...mixinEntry,
index: index + 2,
});
this.journal.debit(expenseJournalEntry);
});
}
/**
* Reverts the jouranl entries.
* @param {number|number[]} referenceId - Reference id.
* @param {string} referenceType - Reference type.
*/
async revertJournalEntries(
referenceId: number | number[],
referenceType: string
) {
const { AccountTransaction } = this.models;
const transactions = await AccountTransaction.query()
.where('reference_type', referenceType)
.whereIn(
'reference_id',
Array.isArray(referenceId) ? referenceId : [referenceId]
)
.withGraphFetched('account');
this.journal.fromTransactions(transactions);
this.journal.removeEntries();
}
/**
* Reverts the sale invoice cost journal entries.
* @param {Date|string} startingDate
* @return {Promise<void>}
*/
async revertInventoryCostJournalEntries(
startingDate: Date | string
): Promise<void> {
const { transactionsRepository } = this.repositories;
const transactions = await transactionsRepository.journal({
fromDate: startingDate,
referenceType: ['SaleInvoice'],
indexGroup: 20
});
this.journal.fromTransactions(transactions);
this.journal.removeEntries();
}
/**
* Reverts sale invoice the income journal entries.
* @param {number} saleInvoiceId
*/
async revertInvoiceIncomeEntries(saleInvoiceId: number) {
const { transactionsRepository } = this.repositories;
const transactions = await transactionsRepository.journal({
referenceType: ['SaleInvoice'],
referenceId: [saleInvoiceId],
});
this.journal.fromTransactions(transactions);
this.journal.removeEntries();
}
/**
* Writes journal entries from manual journal model object.
* @param {IManualJournal} manualJournalObj
* @param {number} manualJournalId
*/
async manualJournal(manualJournalObj: IManualJournal) {
const commonEntry = {
transaction_number: manualJournalObj.journalNumber,
reference_number: manualJournalObj.reference,
createdAt: manualJournalObj.createdAt,
};
manualJournalObj.entries.forEach((entry: IManualJournalEntry) => {
const jouranlEntry = new JournalEntry({
...commonEntry,
debit: entry.debit,
credit: entry.credit,
account: entry.accountId,
referenceType: 'Journal',
referenceId: manualJournalObj.id,
contactId: entry.contactId,
note: entry.note,
date: manualJournalObj.date,
userId: manualJournalObj.userId,
index: entry.index,
});
if (entry.debit) {
this.journal.debit(jouranlEntry);
} else {
this.journal.credit(jouranlEntry);
}
});
}
/**
* Writes journal entries for given sale invoice.
* -------
* - Cost of goods sold -> Debit -> YYYY
* - Inventory assets -> Credit -> YYYY
* --------
* @param {ISaleInvoice} saleInvoice
* @param {JournalPoster} journal
*/
saleInvoiceInventoryCost(
inventoryCostLots: IInventoryLotCost &
{ item: IItem; itemEntry: IItemEntry }[]
) {
const getIndexIncrement = increment(0);
inventoryCostLots.forEach(
(
inventoryCostLot: IInventoryLotCost & {
item: IItem;
itemEntry: IItemEntry;
}
) => {
const commonEntry = {
referenceType: inventoryCostLot.transactionType,
referenceId: inventoryCostLot.transactionId,
date: inventoryCostLot.date,
indexGroup: 20,
createdAt: inventoryCostLot.createdAt,
};
// XXX Debit - Cost account.
const costEntry = new JournalEntry({
...commonEntry,
debit: inventoryCostLot.cost,
account: inventoryCostLot.itemEntry.costAccountId,
itemId: inventoryCostLot.itemId,
index: getIndexIncrement(),
});
// XXX Credit - Inventory account.
const inventoryEntry = new JournalEntry({
...commonEntry,
credit: inventoryCostLot.cost,
account: inventoryCostLot.item.inventoryAccountId,
itemId: inventoryCostLot.itemId,
index: getIndexIncrement(),
});
this.journal.credit(inventoryEntry);
this.journal.debit(costEntry);
}
);
}
/**
* Writes the sale invoice income journal entries.
* -----
* - Receivable accounts -> Debit -> XXXX
* - Income -> Credit -> XXXX
*
* @param {ISaleInvoice} saleInvoice
* @param {number} receivableAccountsId
* @param {number} authorizedUserId
*/
async saleInvoiceIncomeEntries(
saleInvoice: ISaleInvoice & {
entries: IItemEntry & { item: IItem };
},
receivableAccountId: number
): Promise<void> {
const commonEntry = {
referenceType: 'SaleInvoice',
referenceId: saleInvoice.id,
date: saleInvoice.invoiceDate,
userId: saleInvoice.userId,
transactionNumber: saleInvoice.invoiceNo,
referenceNumber: saleInvoice.referenceNo,
createdAt: saleInvoice.createdAt,
indexGroup: 10,
};
// XXX Debit - Receivable account.
const receivableEntry = new JournalEntry({
...commonEntry,
debit: saleInvoice.balance,
account: receivableAccountId,
contactId: saleInvoice.customerId,
index: 1,
});
this.journal.debit(receivableEntry);
saleInvoice.entries.forEach((entry: IItemEntry, index: number) => {
const income: number = entry.quantity * entry.rate;
// XXX Credit - Income account.
const incomeEntry = new JournalEntry({
...commonEntry,
credit: income,
account: entry.sellAccountId,
note: entry.description,
index: index + 2,
itemId: entry.itemId,
itemQuantity: entry.quantity,
});
this.journal.credit(incomeEntry);
});
}
/**
* Writes the sale invoice income journal entries.
* -----
* - Deposit account -> Debit -> XXXX
* - Income -> Credit -> XXXX
*
* @param {ISaleInvoice} saleInvoice
* @param {number} receivableAccountsId
* @param {number} authorizedUserId
*/
async saleReceiptIncomeEntries(
saleReceipt: ISaleReceipt & {
entries: IItemEntry & { item: IItem };
}
): Promise<void> {
const commonEntry = {
referenceType: 'SaleReceipt',
referenceId: saleReceipt.id,
date: saleReceipt.receiptDate,
userId: saleReceipt.userId,
transactionNumber: saleReceipt.receiptNumber,
referenceNumber: saleReceipt.referenceNo,
createdAt: saleReceipt.createdAt,
};
// XXX Debit - Deposit account.
const depositEntry = new JournalEntry({
...commonEntry,
debit: saleReceipt.amount,
account: saleReceipt.depositAccountId,
index: 1,
});
this.journal.debit(depositEntry);
saleReceipt.entries.forEach(
(entry: IItemEntry & { item: IItem }, index: number) => {
const income: number = entry.quantity * entry.rate;
// XXX Credit - Income account.
const incomeEntry = new JournalEntry({
...commonEntry,
credit: income,
account: entry.item.sellAccountId,
note: entry.description,
index: index + 2,
itemId: entry.itemId,
itemQuantity: entry.quantity,
});
this.journal.credit(incomeEntry);
}
);
}
}