mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
- feat: Sales estimates.
- feat: Sales invoices. - feat: Sales payment receives. - feat: Purchases bills. - feat: Purchases bills payments that made to the vendors.
This commit is contained in:
@@ -1,9 +1,24 @@
|
||||
import { Account } from '@/models';
|
||||
import { Account, AccountType } from '@/models';
|
||||
|
||||
export default class AccountsService {
|
||||
|
||||
static async isAccountExists(accountId) {
|
||||
const foundAccounts = await Account.tenant().query().where('id', accountId);
|
||||
return foundAccounts.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
static async getAccountByType(accountTypeKey) {
|
||||
const accountType = await AccountType.tenant()
|
||||
.query()
|
||||
.where('key', accountTypeKey)
|
||||
.first();
|
||||
|
||||
const account = await Account.tenant()
|
||||
.query()
|
||||
.where('account_type_id', accountType.id)
|
||||
.first();
|
||||
|
||||
console.log(account);
|
||||
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
16
server/src/services/Inventory/Inventory.js
Normal file
16
server/src/services/Inventory/Inventory.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { InventoryTransaction } from "../../models";
|
||||
|
||||
|
||||
export default class InventoryService {
|
||||
|
||||
async isInventoryPurchaseSold(transactionType, transactionId) {
|
||||
|
||||
}
|
||||
|
||||
static deleteTransactions(transactionId, transactionType) {
|
||||
return InventoryTransaction.tenant().query()
|
||||
.where('transaction_type', transactionType)
|
||||
.where('transaction_id', transactionId)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,244 @@
|
||||
import { omit } from "lodash";
|
||||
import { BillPayment } from '@/models';
|
||||
|
||||
export default class BillPaymentsService {
|
||||
import express from 'express';
|
||||
import { omit } from 'lodash';
|
||||
import { check, query, validationResult, param } from 'express-validator';
|
||||
import { BillPayment, BillPaymentEntry, Vendor } from '@/models';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import ServiceItemsEntries from '../Sales/ServiceItemsEntries';
|
||||
import AccountsService from '../Accounts/AccountsService';
|
||||
import JournalPoster from '../Accounting/JournalPoster';
|
||||
import JournalEntry from '../Accounting/JournalEntry';
|
||||
|
||||
export default class BillPaymentsService {
|
||||
/**
|
||||
* Creates a new bill payment transcations and store it to the storage
|
||||
* with associated bills entries and journal transactions.
|
||||
*
|
||||
* Precedures
|
||||
* ------
|
||||
* - Records the bill payment transaction.
|
||||
* - Records the bill payment associated entries.
|
||||
* - Increment the payment amount of the given vendor bills.
|
||||
* - Decrement the vendor balance.
|
||||
* - Records payment journal entries.
|
||||
*
|
||||
* @param {IBillPayment} billPayment
|
||||
*/
|
||||
static async createBillPayment(billPayment) {
|
||||
const storedBillPayment = await BillPayment.tenant().query().insert({
|
||||
...omit(billPayment, ['entries']),
|
||||
const amount = sumBy(billPayment.entries, 'paymentAmount');
|
||||
const storedBillPayment = await BillPayment.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
amount,
|
||||
...omit(billPayment, ['entries']),
|
||||
});
|
||||
const storeOpers = [];
|
||||
|
||||
billPayment.entries.forEach((entry) => {
|
||||
const oper = BillPaymentEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
bill_payment_id: storedBillPayment.id,
|
||||
...entry,
|
||||
});
|
||||
// Increment the bill payment amount.
|
||||
const billOper = BillPayment.changePaymentAmount(
|
||||
entry.billId,
|
||||
entry.paymentAmount
|
||||
);
|
||||
storeOpers.push(billOper);
|
||||
storeOpers.push(oper);
|
||||
});
|
||||
|
||||
// Decrement the vendor balance after bills payments.
|
||||
const vendorDecrementOper = Vendor.changeBalanace(
|
||||
billPayment.vendor_id,
|
||||
amount * -1
|
||||
);
|
||||
// Records the journal transactions after bills payment
|
||||
// and change diff acoount balance.
|
||||
const recordJournalTransaction = this.recordPaymentReceiveJournalEntries({
|
||||
id: storedBillPayment.id,
|
||||
...billPayment,
|
||||
});
|
||||
await Promise.all([
|
||||
...storeOpers,
|
||||
recordJournalTransaction,
|
||||
vendorDecrementOper,
|
||||
]);
|
||||
return storedBillPayment;
|
||||
}
|
||||
|
||||
editBillPayment(billPaymentId, billPayment) {
|
||||
/**
|
||||
* Edits the details of the given bill payment.
|
||||
*
|
||||
* Preceducres.
|
||||
* -------
|
||||
* - Update the bill payment transaction.
|
||||
* - Insert the new bill payment entries that have no ids.
|
||||
* - Update the bill paymeny entries that have ids.
|
||||
* - Delete the bill payment entries that not presented.
|
||||
* - Re-insert the journal transactions and update the diff accounts balance.
|
||||
* - Update the diff vendor balance.
|
||||
* - Update the diff bill payment amount.
|
||||
*
|
||||
* @param {Integer} billPaymentId
|
||||
* @param {IBillPayment} billPayment
|
||||
* @param {IBillPayment} oldBillPayment
|
||||
*/
|
||||
static async editBillPayment(billPaymentId, billPayment, oldBillPayment) {
|
||||
const amount = sumBy(bilPayment.entries, 'payment_amount');
|
||||
const updateBillPayment = await BillPayment.tenant()
|
||||
.query()
|
||||
.where('id', billPaymentId)
|
||||
.update({
|
||||
amount,
|
||||
...omit(billPayment, ['entries']),
|
||||
});
|
||||
const opers = [];
|
||||
const entriesHasIds = billpayment.entries.filter((i) => i.id);
|
||||
const entriesHasNoIds = billPayment.entries.filter((e) => !e.id);
|
||||
|
||||
const entriesIds = entriesHasIds.map((e) => e.id);
|
||||
|
||||
const entriesIdsShouldDelete = ServiceItemsEntries.entriesShouldDeleted(
|
||||
oldBillPayment.entries,
|
||||
entriesHasIds
|
||||
);
|
||||
if (entriesIdsShouldDelete.length > 0) {
|
||||
const deleteOper = BillPaymentEntry.tenant()
|
||||
.query()
|
||||
.bulkDelete(entriesIdsShouldDelete);
|
||||
opers.push(deleteOper);
|
||||
}
|
||||
// Entries that should be update to the storage.
|
||||
if (entriesHasIds.length > 0) {
|
||||
const updateOper = BillPaymentEntry.tenant()
|
||||
.query()
|
||||
.bulkUpdate(entriesHasIds, { where: 'id' });
|
||||
opers.push(updateOper);
|
||||
}
|
||||
// Entries that should be inserted to the storage.
|
||||
if (entriesHasNoIds.length > 0) {
|
||||
const insertOper = BillPaymentEntry.tenant()
|
||||
.query()
|
||||
.bulkInsert(
|
||||
entriesHasNoIds.map((e) => ({ ...e, bill_payment_id: billPaymentId }))
|
||||
);
|
||||
opers.push(insertOper);
|
||||
}
|
||||
// Records the journal transactions after bills payment and change
|
||||
// different acoount balance.
|
||||
const recordJournalTransaction = this.recordPaymentReceiveJournalEntries({
|
||||
id: storedBillPayment.id,
|
||||
...billPayment,
|
||||
});
|
||||
// Change the different vendor balance between the new and old one.
|
||||
const changeDiffBalance = Vendor.changeDiffBalance(
|
||||
billPayment.vendor_id,
|
||||
oldBillPayment.vendor_id,
|
||||
billPayment.amount,
|
||||
oldBillPayment.amount
|
||||
);
|
||||
await Promise.all([
|
||||
...opers,
|
||||
recordJournalTransaction,
|
||||
changeDiffBalance,
|
||||
]);
|
||||
}
|
||||
|
||||
static async isBillPaymentExists(billPaymentId) {
|
||||
const foundBillPayments = await BillPayment.tenant().query().where('id', billPaymentId);
|
||||
return foundBillPayments.lengh > 0;
|
||||
/**
|
||||
* Deletes the bill payment and associated transactions.
|
||||
* @param {Integer} billPaymentId -
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async deleteBillPayment(billPaymentId) {
|
||||
const billPayment = await BillPayment.tenant().query().where('id', billPaymentId).first();
|
||||
|
||||
await BillPayment.tenant().query().where('id', billPaymentId).delete();
|
||||
await BillPaymentEntry.tenant()
|
||||
.query()
|
||||
.where('bill_payment_id', billPaymentId)
|
||||
.delete();
|
||||
|
||||
const deleteTransactionsOper = this.deleteJournalTransactions(
|
||||
billPaymentId,
|
||||
'BillPayment'
|
||||
);
|
||||
const revertVendorBalance = Vendor.changeBalanace(
|
||||
billpayment.vendor_id,
|
||||
billPayment.amount * -1,
|
||||
);
|
||||
return Promise.all([
|
||||
deleteTransactionsOper,
|
||||
revertVendorBalance,
|
||||
]);
|
||||
}
|
||||
|
||||
static async isBillPaymentNumberExists(billPaymentNumber) {
|
||||
const foundPayments = await Bill.tenant().query().where('bill_payment_number', billPaymentNumber);
|
||||
return foundPayments.length > 0;
|
||||
/**
|
||||
* Records bill payment receive journal transactions.
|
||||
* @param {BillPayment} billPayment
|
||||
* @param {Integer} billPaymentId
|
||||
*/
|
||||
static async recordPaymentReceiveJournalEntries(billPayment) {
|
||||
const paymentAmount = sumBy(billPayment.entries, 'payment_amount');
|
||||
const formattedDate = moment(billPayment.payment_date).format('YYYY-MM-DD');
|
||||
const payableAccount = await AccountsService.getAccountByType(
|
||||
'accounts_payable'
|
||||
);
|
||||
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
const journal = new JournalPoster(accountsDepGraph);
|
||||
|
||||
const commonJournal = {
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
referenceId: billPayment.id,
|
||||
referenceType: 'BillPayment',
|
||||
date: formattedDate,
|
||||
};
|
||||
if (billPayment.id) {
|
||||
const transactions = await AccountTransaction.tenant()
|
||||
.query()
|
||||
.whereIn('reference_type', ['BillPayment'])
|
||||
.where('reference_id', billPayment.id)
|
||||
.withGraphFetched('account.type');
|
||||
|
||||
journal.loadEntries(transactions);
|
||||
journal.removeEntries();
|
||||
}
|
||||
const debitReceivable = new JournalEntry({
|
||||
...commonJournal,
|
||||
debit: paymentAmount,
|
||||
contactType: 'Vendor',
|
||||
contactId: billpayment.vendor_id,
|
||||
account: payableAccount.id,
|
||||
});
|
||||
const creditPaymentAccount = new JournalEntry({
|
||||
...commonJournal,
|
||||
credit: paymentAmount,
|
||||
account: billPayment.payment_account_id,
|
||||
});
|
||||
journal.debit(debitReceivable);
|
||||
journal.credit(creditPaymentAccount);
|
||||
|
||||
await Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveEntries(),
|
||||
journal.saveBalance(),
|
||||
]);
|
||||
}
|
||||
|
||||
isBillPaymentsExist(billPaymentIds) {
|
||||
static async getBillPayment(billPaymentId) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the bill payment exists on the storage.
|
||||
* @param {Integer} billPaymentId
|
||||
*/
|
||||
static async isBillPaymentExists(billPaymentId) {
|
||||
const billPayment = await BillPayment.tenant().query()
|
||||
.where('id', billPaymentId)
|
||||
.first();
|
||||
return billPayment.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +1,316 @@
|
||||
import { omit } from 'lodash';
|
||||
import { Bill, BillPayment } from '@/models';
|
||||
import { Item } from '@/models';
|
||||
import { Account } from '../../models';
|
||||
import JournalPoster from '../Accounting/JournalPoster';
|
||||
import { omit, sumBy, difference } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
Bill,
|
||||
Vendor,
|
||||
InventoryTransaction,
|
||||
ItemEntry,
|
||||
Item,
|
||||
Account,
|
||||
} 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 '../Inventory/Inventory';
|
||||
import { AccountTransaction } from '../../models';
|
||||
|
||||
/**
|
||||
* Vendor bills services.
|
||||
*/
|
||||
export default class BillsService {
|
||||
/**
|
||||
* 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(bill) {
|
||||
const storedBill = await Bill.tenant().query().insert({
|
||||
...omit(bill, ['entries']),
|
||||
const amount = sumBy(bill.entries, 'amount');
|
||||
const saveEntriesOpers = [];
|
||||
|
||||
const storedBill = await Bill.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
amount,
|
||||
...omit(bill, ['entries']),
|
||||
});
|
||||
bill.entries.forEach((entry) => {
|
||||
const oper = ItemEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
reference_type: 'Bill',
|
||||
reference_id: storedBill.id,
|
||||
...omit(entry, ['amount']),
|
||||
});
|
||||
saveEntriesOpers.push(oper);
|
||||
});
|
||||
// Increment vendor balance.
|
||||
const incrementOper = Vendor.changeBalance(bill.vendor_id, amount);
|
||||
|
||||
await Promise.all([
|
||||
...saveEntriesOpers,
|
||||
incrementOper,
|
||||
this.recordInventoryTransactions(bill, storedBill.id),
|
||||
this.recordJournalTransactions({ ...bill, id: storedBill.id }),
|
||||
]);
|
||||
return storedBill;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch items entries to the storage.
|
||||
*
|
||||
* @param {Array} newEntries
|
||||
* @param {Array} oldEntries
|
||||
* @param {String} referenceType
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async patchItemsEntries(newEntries, oldEntries, referenceType, billId) {
|
||||
const entriesHasIds = newEntries.filter((entry) => entry.id);
|
||||
const entriesHasNoIds = newEntries.filter((entry) => !entry.id);
|
||||
|
||||
const entriesIds = entriesHasIds.map(entry => entry.id);
|
||||
|
||||
const oldEntriesIds = oldEntries.map((e) => e.id);
|
||||
const opers = [];
|
||||
|
||||
const entriesIdsShouldDelete = difference(
|
||||
oldEntriesIds,
|
||||
entriesIds,
|
||||
);
|
||||
if (entriesIdsShouldDelete.length > 0) {
|
||||
const deleteOper = ItemEntry.tenant()
|
||||
.query()
|
||||
.whereIn('id', entriesIdsShouldDelete)
|
||||
.delete();
|
||||
opers.push(deleteOper);
|
||||
}
|
||||
entriesHasIds.forEach((entry) => {
|
||||
const updateOper = ItemEntry.tenant()
|
||||
.query()
|
||||
.where('id', entry.id)
|
||||
.update({
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(updateOper);
|
||||
});
|
||||
entriesHasNoIds.forEach((entry) => {
|
||||
const insertOper = ItemEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
reference_id: billId,
|
||||
reference_type: referenceType,
|
||||
...omit(entry, ['id', 'amount']),
|
||||
});
|
||||
opers.push(insertOper);
|
||||
});
|
||||
return Promise.all([...opers]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits details of the given bill id with associated entries.
|
||||
* @param {Integer} billId
|
||||
* @param {IBill} bill
|
||||
*
|
||||
* 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
|
||||
* @param {IBill} bill
|
||||
*/
|
||||
static async editBill(billId, bill) {
|
||||
const updatedBill = await Bill.tenant().query().insert({
|
||||
...omit(bill, ['entries']),
|
||||
const amount = sumBy(bill.entries, 'amount');
|
||||
|
||||
// Update the bill transaction.
|
||||
const updatedBill = await Bill.tenant()
|
||||
.query()
|
||||
.where('id', billId)
|
||||
.update({
|
||||
amount,
|
||||
...omit(bill, ['entries'])
|
||||
});
|
||||
|
||||
// Old stored entries.
|
||||
const storedEntries = await ItemEntry.tenant()
|
||||
.query()
|
||||
.where('reference_id', billId)
|
||||
.where('reference_type', 'Bill');
|
||||
|
||||
// Patch the bill entries.
|
||||
const patchEntriesOper = this.patchItemsEntries(bill.entries, storedEntries, 'Bill', billId);
|
||||
|
||||
// Record bill journal transactions.
|
||||
const recordTransactionsOper = this.recordJournalTransactions(bill, billId);
|
||||
|
||||
await Promise.all([
|
||||
patchEntriesOper,
|
||||
recordTransactionsOper,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records inventory transactions.
|
||||
* @param {IBill} bill -
|
||||
* @return {void}
|
||||
*/
|
||||
static async recordInventoryTransactions(bill, billId) {
|
||||
const storeInventoryTransactions = [];
|
||||
const entriesItemsIds = bill.entries.map((e) => e.item_id);
|
||||
const inventoryItems = await Item.tenant()
|
||||
.query()
|
||||
.whereIn('id', entriesItemsIds)
|
||||
.where('type', 'inventory');
|
||||
|
||||
const inventoryItemsIds = inventoryItems.map((i) => i.id);
|
||||
const inventoryEntries = bill.entries.filter(
|
||||
(entry) => inventoryItemsIds.indexOf(entry.item_id) !== -1
|
||||
);
|
||||
inventoryEntries.forEach((entry) => {
|
||||
const oper = InventoryTransaction.tenant().query().insert({
|
||||
direction: 'IN',
|
||||
date: bill.bill_date,
|
||||
|
||||
item_id: entry.item_id,
|
||||
quantity: entry.quantity,
|
||||
rate: entry.rate,
|
||||
remaining: entry.quantity,
|
||||
|
||||
transaction_type: 'Bill',
|
||||
transaction_id: billId,
|
||||
});
|
||||
storeInventoryTransactions.push(oper);
|
||||
});
|
||||
return Promise.all([...storeInventoryTransactions]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the bill journal transactions.
|
||||
* @param {IBill} bill
|
||||
* @async
|
||||
* @param {IBill} bill
|
||||
* @param {Integer} billId
|
||||
*/
|
||||
async recordJournalTransactions(bill) {
|
||||
const entriesItemsIds = bill.entries.map(entry => entry.item_id);
|
||||
const payableTotal = sumBy(bill, 'entries.total');
|
||||
const storedItems = await Item.tenant().query().whereIn('id', entriesItemsIds);
|
||||
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 payableAccount = await Account.tenant().query();
|
||||
const formattedDate = moment(saleInvoice.invoice_date).format('YYYY-MM-DD');
|
||||
const storedItems = await Item.tenant()
|
||||
.query()
|
||||
.whereIn('id', entriesItemsIds);
|
||||
|
||||
const accountsDepGraph = await Account.depGraph().query().remember();
|
||||
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
|
||||
const payableAccount = await AccountsService.getAccountByType(
|
||||
'accounts_payable'
|
||||
);
|
||||
if (!payableAccount) {
|
||||
throw new Error('New payable account on the storage.');
|
||||
}
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
const journal = new JournalPoster(accountsDepGraph);
|
||||
|
||||
const commonJournalMeta = {
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
referenceId: bill.id,
|
||||
referenceId: billId,
|
||||
referenceType: 'Bill',
|
||||
date: formattedDate,
|
||||
accural: true,
|
||||
};
|
||||
const payableEntry = await JournalEntry({
|
||||
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,
|
||||
contactId: bill.vendorId,
|
||||
account: payableAccount.id,
|
||||
contactId: bill.vendor_id,
|
||||
contactType: 'Vendor',
|
||||
});
|
||||
journal.credit(payableEntry);
|
||||
|
||||
bill.entries.forEach((item) => {
|
||||
if (['inventory'].indexOf(item.type) !== -1) {
|
||||
const inventoryEntry = new JournalEntry({
|
||||
...commonJournalMeta,
|
||||
account: item.inventoryAccountId,
|
||||
});
|
||||
journal.debit(inventoryEntry);
|
||||
} else {
|
||||
const costEntry = new JournalEntry({
|
||||
...commonJournalMeta,
|
||||
account: item.costAccountId,
|
||||
});
|
||||
journal.debit(costEntry);
|
||||
}
|
||||
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);
|
||||
});
|
||||
await Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveEntries(),
|
||||
journal.saveBalance(),
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the bill with associated entries.
|
||||
* @param {Integer} billId
|
||||
* @param {Integer} billId
|
||||
* @return {void}
|
||||
*/
|
||||
static async deleteBill(billId) {
|
||||
await BillPayment.tenant().query().where('id', billId);
|
||||
const bill = await Bill.tenant().query().where('id', billId).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.deleteTransactions(
|
||||
billId,
|
||||
'Bill'
|
||||
);
|
||||
// Revert vendor balance.
|
||||
const revertVendorBalance = Vendor.changeBalance(billId, bill.amount * -1);
|
||||
|
||||
await Promise.all([
|
||||
deleteBillOper,
|
||||
deleteBillEntriesOper,
|
||||
deleteTransactionsOper,
|
||||
deleteInventoryTransOper,
|
||||
revertVendorBalance,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the bill exists on the storage.
|
||||
* @param {Integer} billId
|
||||
* @param {Integer} billId
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static async isBillExists(billId) {
|
||||
@@ -100,15 +320,31 @@ export default class BillsService {
|
||||
|
||||
/**
|
||||
* Detarmines whether the given bills exist on the storage in bulk.
|
||||
* @param {Array} billsIds
|
||||
* @param {Array} billsIds
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isBillsExist(billsIds) {
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ export default class JournalPosterService {
|
||||
/**
|
||||
* Deletes the journal transactions that associated to the given reference id.
|
||||
*/
|
||||
static async deleteJournalTransactions(referenceId) {
|
||||
static async deleteJournalTransactions(referenceId, referenceType) {
|
||||
const transactions = await AccountTransaction.tenant()
|
||||
.query()
|
||||
.whereIn('reference_type', ['SaleInvoice'])
|
||||
.whereIn('reference_type', [referenceType])
|
||||
.where('reference_id', referenceId)
|
||||
.withGraphFetched('account.type');
|
||||
|
||||
@@ -21,5 +21,4 @@ export default class JournalPosterService {
|
||||
|
||||
await Promise.all([journal.deleteEntries(), journal.saveBalance()]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,116 +1,371 @@
|
||||
import { omit } from 'lodash';
|
||||
import { PaymentReceive, PaymentReceiveEntry } from '@/models';
|
||||
import { omit, sumBy, mapValues, groupBy, chain } from 'lodash';
|
||||
import moment, { updateLocale } from 'moment';
|
||||
import {
|
||||
AccountTransaction,
|
||||
PaymentReceive,
|
||||
PaymentReceiveEntry,
|
||||
SaleInvoice,
|
||||
Customer,
|
||||
Account,
|
||||
} from '@/models';
|
||||
import AccountsService from '@/services/Accounts/AccountsService';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import JournalEntry from '@/services/Accounting/JournalEntry';
|
||||
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
||||
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
|
||||
import PaymentReceiveEntryRepository from '@/repositories/PaymentReceiveEntryRepository';
|
||||
import CustomerRepository from '@/repositories/CustomerRepository';
|
||||
|
||||
export default class PaymentReceiveService extends JournalPosterService {
|
||||
/**
|
||||
* Creates a new payment receive and store it to the storage
|
||||
* with associated invoices payment and journal transactions.
|
||||
* @async
|
||||
* @param {IPaymentReceive} paymentReceive
|
||||
* @param {IPaymentReceive} paymentReceive
|
||||
*/
|
||||
static async createPaymentReceive(paymentReceive) {
|
||||
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
|
||||
const storedPaymentReceive = await PaymentReceive.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
amount: paymentAmount,
|
||||
...omit(paymentReceive, ['entries']),
|
||||
});
|
||||
const storeOpers = [];
|
||||
|
||||
paymentReceive.entries.forEach((invoice) => {
|
||||
const oper = PaymentReceiveEntry.tenant().query().insert({
|
||||
payment_receive_id: storedPaymentReceive.id,
|
||||
...invoice,
|
||||
});
|
||||
storeOpers.push(oper);
|
||||
});
|
||||
await Promise.all([ ...storeOpers ]);
|
||||
paymentReceive.entries.forEach((entry) => {
|
||||
const oper = PaymentReceiveEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
payment_receive_id: storedPaymentReceive.id,
|
||||
...entry,
|
||||
});
|
||||
// Increment the invoice payment amount.
|
||||
const invoice = SaleInvoice.tenant()
|
||||
.query()
|
||||
.where('id', entry.invoice_id)
|
||||
.increment('payment_amount', entry.payment_amount);
|
||||
|
||||
storeOpers.push(oper);
|
||||
storeOpers.push(invoice);
|
||||
});
|
||||
const customerIncrementOper = Customer.decrementBalance(
|
||||
paymentReceive.customer_id,
|
||||
paymentAmount
|
||||
);
|
||||
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries({
|
||||
id: storedPaymentReceive.id,
|
||||
...paymentReceive,
|
||||
});
|
||||
await Promise.all([
|
||||
...storeOpers,
|
||||
customerIncrementOper,
|
||||
recordJournalTransactions,
|
||||
]);
|
||||
return storedPaymentReceive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details the given payment receive with associated entries.
|
||||
* ------
|
||||
* - Update the payment receive transactions.
|
||||
* - Insert the new payment receive entries.
|
||||
* - Update the given payment receive entries.
|
||||
* - Delete the not presented payment receive entries.
|
||||
* - Re-insert the journal transactions and update the different accounts balance.
|
||||
* - Update the different customer balances.
|
||||
* - Update the different invoice payment amount.
|
||||
* @async
|
||||
* @param {Integer} paymentReceiveId
|
||||
* @param {IPaymentReceive} paymentReceive
|
||||
* @param {Integer} paymentReceiveId
|
||||
* @param {IPaymentReceive} paymentReceive
|
||||
* @param {IPaymentReceive} oldPaymentReceive
|
||||
*/
|
||||
static async editPaymentReceive(paymentReceiveId, paymentReceive) {
|
||||
const updatePaymentReceive = await PaymentReceive.tenant().query()
|
||||
static async editPaymentReceive(
|
||||
paymentReceiveId,
|
||||
paymentReceive,
|
||||
oldPaymentReceive
|
||||
) {
|
||||
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
|
||||
// Update the payment receive transaction.
|
||||
const updatePaymentReceive = await PaymentReceive.tenant()
|
||||
.query()
|
||||
.where('id', paymentReceiveId)
|
||||
.update({
|
||||
amount: paymentAmount,
|
||||
...omit(paymentReceive, ['entries']),
|
||||
});
|
||||
const storedEntries = await PaymentReceiveEntry.tenant().query()
|
||||
.where('payment_receive_id', paymentReceiveId);
|
||||
|
||||
const entriesIds = paymentReceive.entries.filter(i => i.id);
|
||||
const opers = [];
|
||||
const entriesIds = paymentReceive.entries.filter((i) => i.id);
|
||||
const entriesShouldInsert = paymentReceive.entries.filter((i) => !i.id);
|
||||
|
||||
const entriesIdsShouldDelete = this.entriesShouldDeleted(
|
||||
storedEntries,
|
||||
entriesIds,
|
||||
// Detarmines which entries ids should be deleted.
|
||||
const entriesIdsShouldDelete = ServiceItemsEntries.entriesShouldDeleted(
|
||||
oldPaymentReceive.entries,
|
||||
entriesIds
|
||||
);
|
||||
if (entriesIdsShouldDelete.length > 0) {
|
||||
const deleteOper = PaymentReceiveEntry.tenant().query()
|
||||
.whereIn('id', entriesIdsShouldDelete)
|
||||
.delete();
|
||||
// Deletes the given payment receive entries.
|
||||
const deleteOper = PaymentReceiveEntryRepository.deleteBulk(
|
||||
entriesIdsShouldDelete
|
||||
);
|
||||
opers.push(deleteOper);
|
||||
}
|
||||
entriesIds.forEach((entry) => {
|
||||
const updateOper = PaymentReceiveEntry.tenant()
|
||||
.query()
|
||||
.pathAndFetchById(entry.id, {
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
// Entries that should be updated to the storage.
|
||||
if (entriesIds.length > 0) {
|
||||
const updateOper = PaymentReceiveEntryRepository.updateBulk(entriesIds);
|
||||
opers.push(updateOper);
|
||||
});
|
||||
await Promise.all([...opers]);
|
||||
}
|
||||
// Entries should insert to the storage.
|
||||
if (entriesShouldInsert.length > 0) {
|
||||
const insertOper = PaymentReceiveEntryRepository.insertBulk(
|
||||
entriesShouldInsert,
|
||||
paymentReceiveId
|
||||
);
|
||||
opers.push(insertOper);
|
||||
}
|
||||
// Re-write the journal transactions of the given payment receive.
|
||||
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries(
|
||||
{
|
||||
id: oldPaymentReceive.id,
|
||||
...paymentReceive,
|
||||
},
|
||||
paymentReceiveId
|
||||
);
|
||||
// Increment/decrement the customer balance after calc the diff
|
||||
// between old and new value.
|
||||
const changeCustomerBalance = CustomerRepository.changeDiffBalance(
|
||||
paymentReceive.customer_id,
|
||||
oldPaymentReceive.customerId,
|
||||
paymentAmount,
|
||||
oldPaymentReceive.amount,
|
||||
);
|
||||
// Change the difference between the old and new invoice payment amount.
|
||||
const diffInvoicePaymentAmount = this.saveChangeInvoicePaymentAmount(
|
||||
oldPaymentReceive.entries,
|
||||
paymentReceive.entries
|
||||
);
|
||||
// Await the async operations.
|
||||
await Promise.all([
|
||||
...opers,
|
||||
recordJournalTransactions,
|
||||
changeCustomerBalance,
|
||||
diffInvoicePaymentAmount,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given payment receive with associated entries
|
||||
* Deletes the given payment receive with associated entries
|
||||
* and journal transactions.
|
||||
* @param {Integer} paymentReceiveId
|
||||
* -----
|
||||
* - Deletes the payment receive transaction.
|
||||
* - Deletes the payment receive associated entries.
|
||||
* - Deletes the payment receive associated journal transactions.
|
||||
* - Revert the customer balance.
|
||||
* - Revert the payment amount of the associated invoices.
|
||||
* @async
|
||||
* @param {Integer} paymentReceiveId
|
||||
*/
|
||||
static async deletePaymentReceive(paymentReceiveId) {
|
||||
await PaymentReceive.tenant().query().where('id', paymentReceiveId).delete();
|
||||
await PaymentReceiveEntry.tenant().query().where('payment_receive_id', paymentReceiveId).delete();
|
||||
static async deletePaymentReceive(paymentReceiveId, paymentReceive) {
|
||||
// Deletes the payment receive transaction.
|
||||
await PaymentReceive.tenant()
|
||||
.query()
|
||||
.where('id', paymentReceiveId)
|
||||
.delete();
|
||||
|
||||
await this.deleteJournalTransactions(paymentReceiveId);
|
||||
// Deletes the payment receive associated entries.
|
||||
await PaymentReceiveEntry.tenant()
|
||||
.query()
|
||||
.where('payment_receive_id', paymentReceiveId)
|
||||
.delete();
|
||||
|
||||
// Delete all associated journal transactions to payment receive transaction.
|
||||
const deleteTransactionsOper = this.deleteJournalTransactions(
|
||||
paymentReceiveId,
|
||||
'PaymentReceive'
|
||||
);
|
||||
// Revert the customer balance.
|
||||
const revertCustomerBalance = Customer.incrementBalance(
|
||||
paymentReceive.customerId,
|
||||
paymentReceive.amount
|
||||
);
|
||||
// Revert the invoices payments amount.
|
||||
const revertInvoicesPaymentAmount = this.revertInvoicePaymentAmount(
|
||||
paymentReceive.entries.map((entry) => ({
|
||||
invoiceId: entry.invoiceId,
|
||||
revertAmount: entry.paymentAmount,
|
||||
}))
|
||||
);
|
||||
await Promise.all([
|
||||
deleteTransactionsOper,
|
||||
revertCustomerBalance,
|
||||
revertInvoicesPaymentAmount,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the payment receive details of the given id.
|
||||
* @param {Integer} paymentReceiveId
|
||||
* @param {Integer} paymentReceiveId
|
||||
*/
|
||||
static async getPaymentReceive(paymentReceiveId) {
|
||||
const paymentReceive = await PaymentReceive.tenant().query().where('id', paymentReceiveId).first();
|
||||
const paymentReceive = await PaymentReceive.tenant()
|
||||
.query()
|
||||
.where('id', paymentReceiveId)
|
||||
.withGraphFetched('entries')
|
||||
.first();
|
||||
return paymentReceive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the payment receive details with associated invoices.
|
||||
* @param {Integer} paymentReceiveId
|
||||
* @param {Integer} paymentReceiveId
|
||||
*/
|
||||
static async getPaymentReceiveWithInvoices(paymentReceiveId) {
|
||||
const paymentReceive = await PaymentReceive.tenant().query()
|
||||
return PaymentReceive.tenant()
|
||||
.query()
|
||||
.where('id', paymentReceiveId)
|
||||
.withGraphFetched('invoices')
|
||||
.first();
|
||||
return paymentReceive;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detarmines whether the payment receive exists on the storage.
|
||||
* @param {Integer} paymentReceiveId
|
||||
*/
|
||||
static async isPaymentReceiveExists(paymentReceiveId) {
|
||||
const paymentReceives = await PaymentReceive.tenant().query().where('id', paymentReceiveId)
|
||||
const paymentReceives = await PaymentReceive.tenant()
|
||||
.query()
|
||||
.where('id', paymentReceiveId);
|
||||
return paymentReceives.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the payment receive number existance.
|
||||
* @async
|
||||
* @param {Integer} paymentReceiveNumber - Payment receive number.
|
||||
* @param {Integer} paymentReceiveId - Payment receive id.
|
||||
*/
|
||||
static async isPaymentReceiveNoExists(paymentReceiveNumber) {
|
||||
const paymentReceives = await PaymentReceive.tenant().query().where('payment_receive_no', paymentReceiveNumber);
|
||||
static async isPaymentReceiveNoExists(
|
||||
paymentReceiveNumber,
|
||||
paymentReceiveId
|
||||
) {
|
||||
const paymentReceives = await PaymentReceive.tenant()
|
||||
.query()
|
||||
.where('payment_receive_no', paymentReceiveNumber)
|
||||
.onBuild((query) => {
|
||||
if (paymentReceiveId) {
|
||||
query.whereNot('id', paymentReceiveId);
|
||||
}
|
||||
});
|
||||
return paymentReceives.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records payment receive journal transactions.
|
||||
* @async
|
||||
* @param {IPaymentReceive} paymentReceive
|
||||
*/
|
||||
static async recordPaymentReceiveJournalEntries(
|
||||
paymentReceive,
|
||||
paymentReceiveId
|
||||
) {
|
||||
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
|
||||
const formattedDate = moment(paymentReceive.payment_date).format(
|
||||
'YYYY-MM-DD'
|
||||
);
|
||||
const receivableAccount = await AccountsService.getAccountByType(
|
||||
'accounts_receivable'
|
||||
);
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
const journal = new JournalPoster(accountsDepGraph);
|
||||
const commonJournal = {
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
referenceId: paymentReceive.id,
|
||||
referenceType: 'PaymentReceive',
|
||||
date: formattedDate,
|
||||
};
|
||||
if (paymentReceiveId) {
|
||||
const transactions = await AccountTransaction.tenant()
|
||||
.query()
|
||||
.whereIn('reference_type', ['PaymentReceive'])
|
||||
.where('reference_id', paymentReceiveId)
|
||||
.withGraphFetched('account.type');
|
||||
|
||||
journal.loadEntries(transactions);
|
||||
journal.removeEntries();
|
||||
}
|
||||
const creditReceivable = new JournalEntry({
|
||||
...commonJournal,
|
||||
credit: paymentAmount,
|
||||
contactType: 'Customer',
|
||||
contactId: paymentReceive.customer_id,
|
||||
account: receivableAccount.id,
|
||||
});
|
||||
const debitDepositAccount = new JournalEntry({
|
||||
...commonJournal,
|
||||
debit: paymentAmount,
|
||||
account: paymentReceive.deposit_account_id,
|
||||
});
|
||||
journal.credit(creditReceivable);
|
||||
journal.debit(debitDepositAccount);
|
||||
|
||||
await Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveEntries(),
|
||||
journal.saveBalance(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the payment amount of the given invoices ids.
|
||||
* @param {Array} revertInvoices
|
||||
*/
|
||||
static async revertInvoicePaymentAmount(revertInvoices) {
|
||||
const opers = [];
|
||||
|
||||
revertInvoices.forEach((revertInvoice) => {
|
||||
const { revertAmount, invoiceId } = revertInvoice;
|
||||
const oper = SaleInvoice.tenant()
|
||||
.query()
|
||||
.where('id', invoiceId)
|
||||
.decrement('payment_amount', revertAmount);
|
||||
opers.push(oper);
|
||||
});
|
||||
await Promise.all(opers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves difference changing between old and new invoice payment amount.
|
||||
* @param {Array} paymentReceiveEntries
|
||||
* @param {Array} newPaymentReceiveEntries
|
||||
* @return
|
||||
*/
|
||||
static async saveChangeInvoicePaymentAmount(
|
||||
paymentReceiveEntries,
|
||||
newPaymentReceiveEntries
|
||||
) {
|
||||
const opers = [];
|
||||
const newEntriesTable = chain(newPaymentReceiveEntries)
|
||||
.groupBy('invoice_id')
|
||||
.mapValues((group) => (sumBy(group, 'payment_amount') || 0) * -1)
|
||||
.value();
|
||||
|
||||
const diffEntries = chain(paymentReceiveEntries)
|
||||
.groupBy('invoiceId')
|
||||
.mapValues((group) => (sumBy(group, 'paymentAmount') || 0) * -1)
|
||||
.mapValues((value, key) => value - (newEntriesTable[key] || 0))
|
||||
.mapValues((value, key) => ({ invoice_id: key, payment_amount: value }))
|
||||
.filter((entry) => entry.payment_amount != 0)
|
||||
.values()
|
||||
.value();
|
||||
|
||||
diffEntries.forEach((diffEntry) => {
|
||||
const oper = SaleInvoice.changePaymentAmount(
|
||||
diffEntry.invoice_id,
|
||||
diffEntry.payment_amount
|
||||
);
|
||||
opers.push(oper);
|
||||
});
|
||||
return Promise.all([ ...opers ]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { omit, update, difference } from 'lodash';
|
||||
import { omit, sumBy, difference } from 'lodash';
|
||||
import {
|
||||
SaleInvoice,
|
||||
SaleInvoiceEntry,
|
||||
AccountTransaction,
|
||||
Account,
|
||||
Item,
|
||||
ItemEntry,
|
||||
Customer,
|
||||
} from '@/models';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
|
||||
@@ -17,47 +18,37 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
* @return {ISaleInvoice}
|
||||
*/
|
||||
static async createSaleInvoice(saleInvoice) {
|
||||
const balance = sumBy(saleInvoice.entries, 'amount');
|
||||
const storedInvoice = await SaleInvoice.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
...omit(saleInvoice, ['entries']),
|
||||
balance,
|
||||
payment_amount: 0,
|
||||
});
|
||||
const opers = [];
|
||||
|
||||
saleInvoice.entries.forEach((entry) => {
|
||||
const oper = SaleInvoiceEntry.tenant()
|
||||
const oper = ItemEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
sale_invoice_id: storedInvoice.id,
|
||||
...entry,
|
||||
reference_type: 'SaleInvoice',
|
||||
reference_id: storedInvoice.id,
|
||||
...omit(entry, ['amount', 'id']),
|
||||
});
|
||||
opers.push(oper);
|
||||
});
|
||||
await Promise.all([
|
||||
...opers,
|
||||
this.recordCreateJournalEntries(saleInvoice),
|
||||
]);
|
||||
const incrementOper = Customer.incrementBalance(
|
||||
saleInvoice.customer_id,
|
||||
balance,
|
||||
);
|
||||
await Promise.all([...opers, incrementOper]);
|
||||
return storedInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates total of the sale invoice entries.
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @return {ISaleInvoice}
|
||||
*/
|
||||
calcSaleInvoiceEntriesTotal(saleInvoice) {
|
||||
return {
|
||||
...saleInvoice,
|
||||
entries: saleInvoice.entries.map((entry) => ({
|
||||
...entry,
|
||||
total: 0,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the journal entries of sale invoice.
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @return {void}
|
||||
*/
|
||||
async recordJournalEntries(saleInvoice) {
|
||||
@@ -69,8 +60,10 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
const formattedDate = moment(saleInvoice.invoice_date).format('YYYY-MM-DD');
|
||||
|
||||
const saleItemsIds = saleInvoice.entries.map((e) => e.item_id);
|
||||
const storedInvoiceItems = await Item.tenant().query().whereIn('id', saleItemsIds)
|
||||
|
||||
const storedInvoiceItems = await Item.tenant()
|
||||
.query()
|
||||
.whereIn('id', saleItemsIds);
|
||||
|
||||
const commonJournalMeta = {
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
@@ -111,7 +104,6 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
accountNormal: 'debit',
|
||||
note: '',
|
||||
});
|
||||
|
||||
journal.debit(costEntry);
|
||||
}
|
||||
journal.credit(incomeEntry);
|
||||
@@ -129,9 +121,10 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
*/
|
||||
static async deleteSaleInvoice(saleInvoiceId) {
|
||||
await SaleInvoice.tenant().query().where('id', saleInvoiceId).delete();
|
||||
await SaleInvoiceEntry.tenant()
|
||||
await ItemEntry.tenant()
|
||||
.query()
|
||||
.where('sale_invoice_id', saleInvoiceId)
|
||||
.where('reference_id', saleInvoiceId)
|
||||
.where('reference_type', 'SaleInvoice')
|
||||
.delete();
|
||||
|
||||
const invoiceTransactions = await AccountTransaction.tenant()
|
||||
@@ -151,39 +144,69 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
|
||||
/**
|
||||
* Edit the given sale invoice.
|
||||
* @param {Integer} saleInvoiceId -
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
* @param {Integer} saleInvoiceId -
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
*/
|
||||
static async editSaleInvoice(saleInvoiceId, saleInvoice) {
|
||||
const updatedSaleInvoices = await SaleInvoice.tenant().query()
|
||||
const updatedSaleInvoices = await SaleInvoice.tenant()
|
||||
.query()
|
||||
.where('id', saleInvoiceId)
|
||||
.update({
|
||||
...omit(saleInvoice, ['entries']),
|
||||
});
|
||||
const opers = [];
|
||||
const entriesIds = saleInvoice.entries.filter((entry) => entry.id);
|
||||
const storedEntries = await SaleInvoiceEntry.tenant().query()
|
||||
.where('sale_invoice_id', saleInvoiceId);
|
||||
const entriesNoIds = saleInvoice.entries.filter((entry) => !entry.id);
|
||||
|
||||
const storedEntries = await ItemEntry.tenant()
|
||||
.query()
|
||||
.where('reference_id', saleInvoiceId)
|
||||
.where('reference_type', 'SaleInvoice');
|
||||
|
||||
const entriesIdsShouldDelete = this.entriesShouldDeleted(
|
||||
storedEntries,
|
||||
entriesIds,
|
||||
entriesIds
|
||||
);
|
||||
if (entriesIdsShouldDelete.length > 0) {
|
||||
const updateOper = SaleInvoiceEntry.tenant().query().where('id', entriesIdsShouldDelete);
|
||||
const updateOper = ItemEntry.tenant()
|
||||
.query()
|
||||
.whereIn('id', entriesIdsShouldDelete)
|
||||
.delete();
|
||||
opers.push(updateOper);
|
||||
}
|
||||
entriesIds.forEach((entry) => {
|
||||
const updateOper = SaleInvoiceEntry.tenant()
|
||||
const updateOper = ItemEntry.tenant()
|
||||
.query()
|
||||
.patchAndFetchById(entry.id, {
|
||||
.where('id', entry.id)
|
||||
.update({
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(updateOper);
|
||||
});
|
||||
entriesNoIds.forEach((entry) => {
|
||||
const insertOper = ItemEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
reference_type: 'SaleInvoice',
|
||||
reference_id: saleInvoiceId,
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(insertOper);
|
||||
})
|
||||
await Promise.all([...opers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice with associated entries.
|
||||
* @param {Integer} saleInvoiceId
|
||||
*/
|
||||
static async getSaleInvoiceWithEntries(saleInvoiceId) {
|
||||
return SaleInvoice.tenant().query()
|
||||
.where('id', saleInvoiceId)
|
||||
.withGraphFetched('entries')
|
||||
.first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the sale invoice number id exists on the storage.
|
||||
* @param {Integer} saleInvoiceId
|
||||
@@ -208,7 +231,7 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
query.where('invoice_no', saleInvoiceNumber);
|
||||
|
||||
if (saleInvoiceId) {
|
||||
query.whereNot('id', saleInvoiceId)
|
||||
query.whereNot('id', saleInvoiceId);
|
||||
}
|
||||
return query;
|
||||
});
|
||||
@@ -217,7 +240,7 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
|
||||
/**
|
||||
* Detarmine the invoices IDs in bulk and returns the not found ones.
|
||||
* @param {Array} invoicesIds
|
||||
* @param {Array} invoicesIds
|
||||
* @return {Array}
|
||||
*/
|
||||
static async isInvoicesExist(invoicesIds) {
|
||||
@@ -227,11 +250,8 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
builder.whereIn('id', invoicesIds);
|
||||
return builder;
|
||||
});
|
||||
const storedInvoicesIds = storedInvoices.map(i => i.id);
|
||||
const notStoredInvoices = difference(
|
||||
invoicesIds,
|
||||
storedInvoicesIds,
|
||||
);
|
||||
const storedInvoicesIds = storedInvoices.map((i) => i.id);
|
||||
const notStoredInvoices = difference(invoicesIds, storedInvoicesIds);
|
||||
return notStoredInvoices;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { omit, difference } from 'lodash';
|
||||
import { SaleEstimate, SaleEstimateEntry } from '@/models';
|
||||
|
||||
export default class SaleEstimateService {
|
||||
constructor() {}
|
||||
import { omit, difference, sumBy } from 'lodash';
|
||||
import { SaleEstimate, ItemEntry } from '@/models';
|
||||
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
|
||||
|
||||
export default class SaleEstimateService extends ServiceItemsEntries {
|
||||
/**
|
||||
* Creates a new estimate with associated entries.
|
||||
* @async
|
||||
@@ -11,23 +10,27 @@ export default class SaleEstimateService {
|
||||
* @return {void}
|
||||
*/
|
||||
static async createEstimate(estimate) {
|
||||
const amount = sumBy(estimate.entries, 'amount');
|
||||
const storedEstimate = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
amount,
|
||||
...omit(estimate, ['entries']),
|
||||
});
|
||||
const storeEstimateEntriesOpers = [];
|
||||
|
||||
estimate.entries.forEach((entry) => {
|
||||
const oper = SaleEstimateEntry.tenant()
|
||||
const oper = ItemEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
estimate_id: storedEstimate.id,
|
||||
...entry,
|
||||
reference_type: 'SaleEstimate',
|
||||
reference_id: storedEstimate.id,
|
||||
...omit(entry, ['total', 'amount']),
|
||||
});
|
||||
storeEstimateEntriesOpers.push(oper);
|
||||
});
|
||||
await Promise.all([...storeEstimateEntriesOpers]);
|
||||
|
||||
return storedEstimate;
|
||||
}
|
||||
|
||||
@@ -38,9 +41,10 @@ export default class SaleEstimateService {
|
||||
* @return {void}
|
||||
*/
|
||||
static async deleteEstimate(estimateId) {
|
||||
await SaleEstimateEntry.tenant()
|
||||
await ItemEntry.tenant()
|
||||
.query()
|
||||
.where('estimate_id', estimateId)
|
||||
.where('reference_id', estimateId)
|
||||
.where('reference_type', 'SaleEstimate')
|
||||
.delete();
|
||||
await SaleEstimate.tenant().query().where('id', estimateId).delete();
|
||||
}
|
||||
@@ -53,43 +57,57 @@ export default class SaleEstimateService {
|
||||
* @return {void}
|
||||
*/
|
||||
static async editEstimate(estimateId, estimate) {
|
||||
const amount = sumBy(estimate.entries, 'amount');
|
||||
const updatedEstimate = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.update({
|
||||
amount,
|
||||
...omit(estimate, ['entries']),
|
||||
});
|
||||
const storedEstimateEntries = await SaleEstimateEntry.tenant()
|
||||
const storedEstimateEntries = await ItemEntry.tenant()
|
||||
.query()
|
||||
.where('estimate_id', estimateId);
|
||||
.where('reference_id', estimateId)
|
||||
.where('reference_type', 'SaleEstimate');
|
||||
|
||||
const opers = [];
|
||||
const storedEstimateEntriesIds = storedEstimateEntries.map((e) => e.id);
|
||||
const estimateEntriesHasID = estimate.entries.filter((entry) => entry.id);
|
||||
const formEstimateEntriesIds = estimateEntriesHasID.map(
|
||||
(entry) => entry.id
|
||||
);
|
||||
const entriesHasID = estimate.entries.filter((entry) => entry.id);
|
||||
const entriesHasNoIDs = estimate.entries.filter((entry) => !entry.id);
|
||||
|
||||
const storedEntriesIds = storedEstimateEntries.map((e) => e.id);
|
||||
const formEstimateEntriesIds = entriesHasID.map((entry) => entry.id);
|
||||
|
||||
const entriesIdsShouldBeDeleted = difference(
|
||||
storedEstimateEntriesIds,
|
||||
storedEntriesIds,
|
||||
formEstimateEntriesIds,
|
||||
);
|
||||
|
||||
console.log(entriesIdsShouldBeDeleted);
|
||||
// Deletes the given sale estimate entries ids.
|
||||
if (entriesIdsShouldBeDeleted.length > 0) {
|
||||
const oper = SaleEstimateEntry.tenant()
|
||||
const oper = ItemEntry.tenant()
|
||||
.query()
|
||||
.where('id', entriesIdsShouldBeDeleted)
|
||||
.whereIn('id', entriesIdsShouldBeDeleted)
|
||||
.delete();
|
||||
opers.push(oper);
|
||||
}
|
||||
estimateEntriesHasID.forEach((entry) => {
|
||||
const oper = SaleEstimateEntry.tenant()
|
||||
// Insert the new sale estimate entries.
|
||||
entriesHasNoIDs.forEach((entry) => {
|
||||
const oper = ItemEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
reference_type: 'SaleEstimate',
|
||||
reference_id: estimateId,
|
||||
...entry,
|
||||
});
|
||||
opers.push(oper);
|
||||
});
|
||||
entriesHasID.forEach((entry) => {
|
||||
const oper = ItemEntry.tenant()
|
||||
.query()
|
||||
.patchAndFetchById(entry.id, {
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(oper);
|
||||
});
|
||||
await Promise.all([...opers]);
|
||||
return Promise.all([...opers]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,10 +134,11 @@ export default class SaleEstimateService {
|
||||
.filter((e) => e.id)
|
||||
.map((e) => e.id);
|
||||
|
||||
const estimateEntries = await SaleEstimateEntry.tenant()
|
||||
const estimateEntries = await ItemEntry.tenant()
|
||||
.query()
|
||||
.whereIn('id', estimateEntriesIds)
|
||||
.where('estimate_id', estimateId);
|
||||
.where('reference_id', estimateId)
|
||||
.where('reference_type', 'SaleEstimate');
|
||||
|
||||
const storedEstimateEntriesIds = estimateEntries.map((e) => e.id);
|
||||
const notFoundEntriesIDs = difference(
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
import { omit, difference } from 'lodash';
|
||||
import { omit, difference, sumBy } from 'lodash';
|
||||
import {
|
||||
SaleReceipt,
|
||||
SaleReceiptEntry,
|
||||
AccountTransaction,
|
||||
Account,
|
||||
} from '@/models';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import ItemEntry from '../../models/ItemEntry';
|
||||
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
||||
|
||||
export default class SalesReceipt {
|
||||
constructor() {}
|
||||
|
||||
export default class SalesReceipt extends JournalPosterService {
|
||||
/**
|
||||
* Creates a new sale receipt with associated entries.
|
||||
* @async
|
||||
* @param {ISaleReceipt} saleReceipt
|
||||
* @return {Object}
|
||||
*/
|
||||
static async createSaleReceipt(saleReceipt) {
|
||||
const amount = sumBy(saleReceipt.entries, 'amount');
|
||||
const storedSaleReceipt = await SaleReceipt.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
amount,
|
||||
...omit(saleReceipt, ['entries']),
|
||||
});
|
||||
const storeSaleReceiptEntriesOpers = [];
|
||||
|
||||
saleReceipt.entries.forEach((entry) => {
|
||||
const oper = SaleReceiptEntry.tenant()
|
||||
const oper = ItemEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
sale_receipt_id: storedSaleReceipt.id,
|
||||
...entry,
|
||||
reference_type: 'SaleReceipt',
|
||||
reference_id: storedSaleReceipt.id,
|
||||
...omit(entry, ['id', 'amount']),
|
||||
});
|
||||
storeSaleReceiptEntriesOpers.push(oper);
|
||||
});
|
||||
@@ -38,34 +41,11 @@ export default class SalesReceipt {
|
||||
/**
|
||||
* Records journal transactions for sale receipt.
|
||||
* @param {ISaleReceipt} saleReceipt
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async _recordJournalTransactions(saleReceipt) {
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
const journalPoster = new JournalPoster(accountsDepGraph);
|
||||
|
||||
const creditEntry = new journalEntry({
|
||||
debit: 0,
|
||||
credit: saleReceipt.total,
|
||||
account: saleReceipt.incomeAccountId,
|
||||
referenceType: 'SaleReceipt',
|
||||
referenceId: saleReceipt.id,
|
||||
note: saleReceipt.note,
|
||||
});
|
||||
const debitEntry = new journalEntry({
|
||||
debit: saleReceipt.total,
|
||||
credit: 0,
|
||||
account: saleReceipt.incomeAccountId,
|
||||
referenceType: 'SaleReceipt',
|
||||
referenceId: saleReceipt.id,
|
||||
note: saleReceipt.note,
|
||||
});
|
||||
journalPoster.credit(creditEntry);
|
||||
journalPoster.credit(debitEntry);
|
||||
|
||||
await Promise.all([
|
||||
journalPoster.saveEntries(),
|
||||
journalPoster.saveBalance(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,42 +55,45 @@ export default class SalesReceipt {
|
||||
* @return {void}
|
||||
*/
|
||||
static async editSaleReceipt(saleReceiptId, saleReceipt) {
|
||||
const amount = sumBy(saleReceipt.entries, 'amount');
|
||||
const updatedSaleReceipt = await SaleReceipt.tenant()
|
||||
.query()
|
||||
.where('id', saleReceiptId)
|
||||
.update({
|
||||
amount,
|
||||
...omit(saleReceipt, ['entries']),
|
||||
});
|
||||
const storedSaleReceiptEntries = await SaleReceiptEntry.tenant()
|
||||
const storedSaleReceiptEntries = await ItemEntry.tenant()
|
||||
.query()
|
||||
.where('sale_receipt_id', saleReceiptId);
|
||||
.where('reference_id', saleReceiptId)
|
||||
.where('reference_type', 'SaleReceipt');
|
||||
|
||||
const storedSaleReceiptsIds = storedSaleReceiptEntries.map((e) => e.id);
|
||||
const entriesHasID = saleReceipt.entries.filter((entry) => entry.id);
|
||||
const entriesIds = entriesHasID.map((e) => e.id);
|
||||
|
||||
const opers = [];
|
||||
const entriesIdsShouldBeDeleted = difference(
|
||||
storedSaleReceiptsIds,
|
||||
entriesIds
|
||||
);
|
||||
const opers = [];
|
||||
|
||||
if (entriesIdsShouldBeDeleted.length > 0) {
|
||||
const deleteOper = SaleReceiptEntry.tenant()
|
||||
const deleteOper = ItemEntry.tenant()
|
||||
.query()
|
||||
.where('id', entriesIdsShouldBeDeleted)
|
||||
.whereIn('id', entriesIdsShouldBeDeleted)
|
||||
.where('reference_type', 'SaleReceipt')
|
||||
.delete();
|
||||
opers.push(deleteOper);
|
||||
}
|
||||
entriesHasID.forEach((entry) => {
|
||||
const updateOper = SaleReceiptEntry.tenant()
|
||||
const updateOper = ItemEntry.tenant()
|
||||
.query()
|
||||
.patchAndFetchById(entry.id, {
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(updateOper);
|
||||
});
|
||||
await Promise.all([...opers]);
|
||||
return Promise.all([...opers]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,27 +103,20 @@ export default class SalesReceipt {
|
||||
*/
|
||||
static async deleteSaleReceipt(saleReceiptId) {
|
||||
await SaleReceipt.tenant().query().where('id', saleReceiptId).delete();
|
||||
await SaleReceiptEntry.tenant()
|
||||
await ItemEntry.tenant()
|
||||
.query()
|
||||
.where('sale_receipt_id', saleReceiptId)
|
||||
.where('reference_id', saleReceiptId)
|
||||
.where('reference_type', 'SaleReceipt')
|
||||
.delete();
|
||||
|
||||
const receiptTransactions = await AccountTransaction.tenant()
|
||||
.query()
|
||||
.whereIn('reference_type', ['SaleReceipt'])
|
||||
.where('reference_id', saleReceiptId)
|
||||
.withGraphFetched('account.type');
|
||||
|
||||
const accountsDepGraph = await Account.tenant()
|
||||
.depGraph()
|
||||
.query()
|
||||
.remember();
|
||||
const journal = new JournalPoster(accountsDepGraph);
|
||||
|
||||
journal.loadEntries(receiptTransactions);
|
||||
journal.removeEntries();
|
||||
|
||||
await Promise.all([journal.deleteEntries(), journal.saveBalance()]);
|
||||
// Delete all associated journal transactions to payment receive transaction.
|
||||
const deleteTransactionsOper = this.deleteJournalTransactions(
|
||||
saleReceiptId,
|
||||
'SaleReceipt'
|
||||
);
|
||||
return Promise.all([
|
||||
deleteTransactionsOper,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,10 +141,11 @@ export default class SalesReceipt {
|
||||
.filter((e) => e.id)
|
||||
.map((e) => e.id);
|
||||
|
||||
const storedEntries = await SaleReceiptEntry.tenant()
|
||||
const storedEntries = await ItemEntry.tenant()
|
||||
.query()
|
||||
.whereIn('id', entriesIDs)
|
||||
.where('sale_receipt_id', saleReceiptId);
|
||||
.where('reference_id', saleReceiptId)
|
||||
.where('reference_type', 'SaleReceipt');
|
||||
|
||||
const storedEntriesIDs = storedEntries.map((e) => e.id);
|
||||
const notFoundEntriesIDs = difference(
|
||||
@@ -178,6 +155,10 @@ export default class SalesReceipt {
|
||||
return notFoundEntriesIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve sale receipt with associated entries.
|
||||
* @param {Integer} saleReceiptId
|
||||
*/
|
||||
static async getSaleReceiptWithEntries(saleReceiptId) {
|
||||
const saleReceipt = await SaleReceipt.tenant().query()
|
||||
.where('id', saleReceiptId)
|
||||
|
||||
Reference in New Issue
Block a user