mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
358 lines
10 KiB
JavaScript
358 lines
10 KiB
JavaScript
import { omit, sumBy, pick } from 'lodash';
|
|
import moment from 'moment';
|
|
import {
|
|
Account,
|
|
Bill,
|
|
Vendor,
|
|
ItemEntry,
|
|
Item,
|
|
AccountTransaction,
|
|
} from '@/models';
|
|
import JournalPoster from '@/services/Accounting/JournalPoster';
|
|
import JournalEntry from '@/services/Accounting/JournalEntry';
|
|
import AccountsService from '@/services/Accounts/AccountsService';
|
|
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
|
import InventoryService from '@/services/Inventory/Inventory';
|
|
import HasItemsEntries from '@/services/Sales/HasItemsEntries';
|
|
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
|
|
import { formatDateFields } from '@/utils';
|
|
|
|
/**
|
|
* Vendor bills services.
|
|
* @service
|
|
*/
|
|
export default class BillsService extends SalesInvoicesCost {
|
|
/**
|
|
* Creates a new bill and stored it to the storage.
|
|
*
|
|
* Precedures.
|
|
* ----
|
|
* - Insert bill transactions to the storage.
|
|
* - Insert bill entries to the storage.
|
|
* - Increment the given vendor id.
|
|
* - Record bill journal transactions on the given accounts.
|
|
* - Record bill items inventory transactions.
|
|
* ----
|
|
* @param {IBill} bill -
|
|
* @return {void}
|
|
*/
|
|
static async createBill(billDTO) {
|
|
const invLotNumber = await InventoryService.nextLotNumber();
|
|
const bill = {
|
|
...formatDateFields(billDTO, ['bill_date', 'due_date']),
|
|
amount: sumBy(billDTO.entries, 'amount'),
|
|
invLotNumber: billDTO.invLotNumber || invLotNumber
|
|
};
|
|
const saveEntriesOpers = [];
|
|
|
|
const storedBill = await Bill.tenant()
|
|
.query()
|
|
.insert({
|
|
...omit(bill, ['entries']),
|
|
});
|
|
bill.entries.forEach((entry) => {
|
|
const oper = ItemEntry.tenant()
|
|
.query()
|
|
.insertAndFetch({
|
|
reference_type: 'Bill',
|
|
reference_id: storedBill.id,
|
|
...omit(entry, ['amount']),
|
|
}).then((itemEntry) => {
|
|
entry.id = itemEntry.id;
|
|
});
|
|
saveEntriesOpers.push(oper);
|
|
});
|
|
// Await save all bill entries operations.
|
|
await Promise.all([...saveEntriesOpers]);
|
|
|
|
// Increments vendor balance.
|
|
const incrementOper = Vendor.changeBalance(bill.vendor_id, bill.amount);
|
|
|
|
// Rewrite the inventory transactions for inventory items.
|
|
const writeInvTransactionsOper = this.recordInventoryTransactions(
|
|
bill, storedBill.id
|
|
);
|
|
// Writes the journal entries for the given bill transaction.
|
|
const writeJEntriesOper = this.recordJournalTransactions({
|
|
id: storedBill.id, ...bill,
|
|
});
|
|
await Promise.all([
|
|
incrementOper,
|
|
writeInvTransactionsOper,
|
|
writeJEntriesOper,
|
|
]);
|
|
// Schedule bill re-compute based on the item cost
|
|
// method and starting date.
|
|
await this.scheduleComputeBillItemsCost(bill);
|
|
|
|
return storedBill;
|
|
}
|
|
|
|
/**
|
|
* Edits details of the given bill id with associated entries.
|
|
*
|
|
* Precedures:
|
|
* -------
|
|
* - Update the bill transaction on the storage.
|
|
* - Update the bill entries on the storage and insert the not have id and delete
|
|
* once that not presented.
|
|
* - Increment the diff amount on the given vendor id.
|
|
* - Re-write the inventory transactions.
|
|
* - Re-write the bill journal transactions.
|
|
*
|
|
* @param {Integer} billId - The given bill id.
|
|
* @param {IBill} bill - The given new bill details.
|
|
*/
|
|
static async editBill(billId, billDTO) {
|
|
const oldBill = await Bill.tenant().query().findById(billId);
|
|
const bill = {
|
|
...formatDateFields(billDTO, ['bill_date', 'due_date']),
|
|
amount: sumBy(billDTO.entries, 'amount'),
|
|
invLotNumber: oldBill.invLotNumber,
|
|
};
|
|
// Update the bill transaction.
|
|
const updatedBill = await Bill.tenant()
|
|
.query()
|
|
.where('id', billId)
|
|
.update({
|
|
...omit(bill, ['entries', 'invLotNumber'])
|
|
});
|
|
// Old stored entries.
|
|
const storedEntries = await ItemEntry.tenant()
|
|
.query()
|
|
.where('reference_id', billId)
|
|
.where('reference_type', 'Bill');
|
|
|
|
// Patch the bill entries.
|
|
const patchEntriesOper = HasItemsEntries.patchItemsEntries(
|
|
bill.entries, storedEntries, 'Bill', billId,
|
|
);
|
|
// Changes the diff vendor balance between old and new amount.
|
|
const changeVendorBalanceOper = Vendor.changeDiffBalance(
|
|
bill.vendor_id,
|
|
oldBill.vendorId,
|
|
bill.amount,
|
|
oldBill.amount,
|
|
);
|
|
// Re-write the inventory transactions for inventory items.
|
|
const writeInvTransactionsOper = this.recordInventoryTransactions(bill, billId, true);
|
|
|
|
// Writes the journal entries for the given bill transaction.
|
|
const writeJEntriesOper = this.recordJournalTransactions({
|
|
id: billId,
|
|
...bill,
|
|
}, billId);
|
|
|
|
await Promise.all([
|
|
patchEntriesOper,
|
|
changeVendorBalanceOper,
|
|
writeInvTransactionsOper,
|
|
writeJEntriesOper,
|
|
]);
|
|
// Schedule sale invoice re-compute based on the item cost
|
|
// method and starting date.
|
|
await this.scheduleComputeBillItemsCost(bill);
|
|
}
|
|
|
|
/**
|
|
* Deletes the bill with associated entries.
|
|
* @param {Integer} billId
|
|
* @return {void}
|
|
*/
|
|
static async deleteBill(billId) {
|
|
const bill = await Bill.tenant().query()
|
|
.where('id', billId)
|
|
.withGraphFetched('entries')
|
|
.first();
|
|
|
|
// Delete all associated bill entries.
|
|
const deleteBillEntriesOper = ItemEntry.tenant()
|
|
.query()
|
|
.where('reference_type', 'Bill')
|
|
.where('reference_id', billId)
|
|
.delete();
|
|
|
|
// Delete the bill transaction.
|
|
const deleteBillOper = Bill.tenant().query().where('id', billId).delete();
|
|
|
|
// Delete associated bill journal transactions.
|
|
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
|
|
billId,
|
|
'Bill'
|
|
);
|
|
// Delete bill associated inventory transactions.
|
|
const deleteInventoryTransOper = InventoryService.deleteInventoryTransactions(
|
|
billId, 'Bill'
|
|
);
|
|
// Revert vendor balance.
|
|
const revertVendorBalance = Vendor.changeBalance(bill.vendorId, bill.amount * -1);
|
|
|
|
await Promise.all([
|
|
deleteBillOper,
|
|
deleteBillEntriesOper,
|
|
deleteTransactionsOper,
|
|
deleteInventoryTransOper,
|
|
revertVendorBalance,
|
|
]);
|
|
// Schedule sale invoice re-compute based on the item cost
|
|
// method and starting date.
|
|
await this.scheduleComputeBillItemsCost(bill);
|
|
}
|
|
|
|
/**
|
|
* Records the inventory transactions from the given bill input.
|
|
* @param {Bill} bill
|
|
* @param {number} billId
|
|
*/
|
|
static recordInventoryTransactions(bill, billId, override) {
|
|
const inventoryTransactions = bill.entries
|
|
.map((entry) => ({
|
|
...pick(entry, ['item_id', 'quantity', 'rate']),
|
|
lotNumber: bill.invLotNumber,
|
|
transactionType: 'Bill',
|
|
transactionId: billId,
|
|
direction: 'IN',
|
|
date: bill.bill_date,
|
|
entryId: entry.id,
|
|
}));
|
|
|
|
return InventoryService.recordInventoryTransactions(
|
|
inventoryTransactions, override
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Records the bill journal transactions.
|
|
* @async
|
|
* @param {IBill} bill
|
|
* @param {Integer} billId
|
|
*/
|
|
static async recordJournalTransactions(bill, billId) {
|
|
const entriesItemsIds = bill.entries.map((entry) => entry.item_id);
|
|
const payableTotal = sumBy(bill.entries, 'amount');
|
|
const formattedDate = moment(bill.bill_date).format('YYYY-MM-DD');
|
|
|
|
const storedItems = await Item.tenant()
|
|
.query()
|
|
.whereIn('id', entriesItemsIds);
|
|
|
|
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
|
|
const payableAccount = await AccountsService.getAccountByType('accounts_payable');
|
|
|
|
const accountsDepGraph = await Account.tenant().depGraph().query();
|
|
const journal = new JournalPoster(accountsDepGraph);
|
|
|
|
const commonJournalMeta = {
|
|
debit: 0,
|
|
credit: 0,
|
|
referenceId: bill.id,
|
|
referenceType: 'Bill',
|
|
date: formattedDate,
|
|
accural: true,
|
|
};
|
|
if (billId) {
|
|
const transactions = await AccountTransaction.tenant()
|
|
.query()
|
|
.whereIn('reference_type', ['Bill'])
|
|
.whereIn('reference_id', [billId])
|
|
.withGraphFetched('account.type');
|
|
|
|
journal.loadEntries(transactions);
|
|
journal.removeEntries();
|
|
}
|
|
const payableEntry = new JournalEntry({
|
|
...commonJournalMeta,
|
|
credit: payableTotal,
|
|
account: payableAccount.id,
|
|
contactId: bill.vendor_id,
|
|
contactType: 'Vendor',
|
|
});
|
|
journal.credit(payableEntry);
|
|
|
|
bill.entries.forEach((entry) => {
|
|
const item = storedItemsMap.get(entry.item_id);
|
|
|
|
const debitEntry = new JournalEntry({
|
|
...commonJournalMeta,
|
|
debit: entry.amount,
|
|
account:
|
|
['inventory'].indexOf(item.type) !== -1
|
|
? item.inventoryAccountId
|
|
: item.costAccountId,
|
|
});
|
|
journal.debit(debitEntry);
|
|
});
|
|
return Promise.all([
|
|
journal.deleteEntries(),
|
|
journal.saveEntries(),
|
|
journal.saveBalance(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Detarmines whether the bill exists on the storage.
|
|
* @param {Integer} billId
|
|
* @return {Boolean}
|
|
*/
|
|
static async isBillExists(billId) {
|
|
const foundBills = await Bill.tenant().query().where('id', billId);
|
|
return foundBills.length > 0;
|
|
}
|
|
|
|
/**
|
|
* Detarmines whether the given bills exist on the storage in bulk.
|
|
* @param {Array} billsIds
|
|
* @return {Boolean}
|
|
*/
|
|
static async isBillsExist(billsIds) {
|
|
const bills = await Bill.tenant().query().whereIn('id', billsIds);
|
|
return bills.length > 0;
|
|
}
|
|
|
|
/**
|
|
* Detarmines whether the given bill id exists on the storage.
|
|
* @param {Integer} billNumber
|
|
*/
|
|
static async isBillNoExists(billNumber) {
|
|
const foundBills = await Bill.tenant()
|
|
.query()
|
|
.where('bill_number', billNumber);
|
|
return foundBills.length > 0;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the given bill details with associated items entries.
|
|
* @param {Integer} billId -
|
|
* @returns {Promise}
|
|
*/
|
|
static getBill(billId) {
|
|
return Bill.tenant().query().where('id', billId).first();
|
|
}
|
|
|
|
/**
|
|
* Retrieve the given bill details with associated items entries.
|
|
* @param {Integer} billId -
|
|
* @returns {Promise}
|
|
*/
|
|
static getBillWithMetadata(billId) {
|
|
return Bill.tenant()
|
|
.query()
|
|
.where('id', billId)
|
|
.withGraphFetched('vendor')
|
|
.withGraphFetched('entries')
|
|
.first();
|
|
}
|
|
|
|
/**
|
|
* Schedules compute bill items cost based on each item cost method.
|
|
* @param {IBill} bill
|
|
* @return {Promise}
|
|
*/
|
|
static scheduleComputeBillItemsCost(bill) {
|
|
return this.scheduleComputeItemsCost(
|
|
bill.entries.map((e) => e.item_id),
|
|
bill.bill_date,
|
|
);
|
|
}
|
|
}
|