mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
- feat: Sales estimates APIs.
- feat: Sales invoices APIs. - feat: Sales receipts APIs. - WIP: Sales payment receipts. - WIP: Purchases bills. - WIP: Purchases payments made.
This commit is contained in:
25
server/src/services/Sales/JournalPosterService.js
Normal file
25
server/src/services/Sales/JournalPosterService.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Account, AccountTransaction } from '@/models';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
|
||||
|
||||
export default class JournalPosterService {
|
||||
/**
|
||||
* Deletes the journal transactions that associated to the given reference id.
|
||||
*/
|
||||
static async deleteJournalTransactions(referenceId) {
|
||||
const transactions = await AccountTransaction.tenant()
|
||||
.query()
|
||||
.whereIn('reference_type', ['SaleInvoice'])
|
||||
.where('reference_id', referenceId)
|
||||
.withGraphFetched('account.type');
|
||||
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
const journal = new JournalPoster(accountsDepGraph);
|
||||
|
||||
journal.loadEntries(transactions);
|
||||
journal.removeEntries();
|
||||
|
||||
await Promise.all([journal.deleteEntries(), journal.saveBalance()]);
|
||||
}
|
||||
}
|
||||
|
||||
116
server/src/services/Sales/PaymentReceive.js
Normal file
116
server/src/services/Sales/PaymentReceive.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import { omit } from 'lodash';
|
||||
import { PaymentReceive, PaymentReceiveEntry } from '@/models';
|
||||
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
||||
|
||||
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
|
||||
*/
|
||||
static async createPaymentReceive(paymentReceive) {
|
||||
const storedPaymentReceive = await PaymentReceive.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
...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 ]);
|
||||
|
||||
return storedPaymentReceive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details the given payment receive with associated entries.
|
||||
* @async
|
||||
* @param {Integer} paymentReceiveId
|
||||
* @param {IPaymentReceive} paymentReceive
|
||||
*/
|
||||
static async editPaymentReceive(paymentReceiveId, paymentReceive) {
|
||||
const updatePaymentReceive = await PaymentReceive.tenant().query()
|
||||
.where('id', paymentReceiveId)
|
||||
.update({
|
||||
...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 entriesIdsShouldDelete = this.entriesShouldDeleted(
|
||||
storedEntries,
|
||||
entriesIds,
|
||||
);
|
||||
if (entriesIdsShouldDelete.length > 0) {
|
||||
const deleteOper = PaymentReceiveEntry.tenant().query()
|
||||
.whereIn('id', entriesIdsShouldDelete)
|
||||
.delete();
|
||||
opers.push(deleteOper);
|
||||
}
|
||||
entriesIds.forEach((entry) => {
|
||||
const updateOper = PaymentReceiveEntry.tenant()
|
||||
.query()
|
||||
.pathAndFetchById(entry.id, {
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(updateOper);
|
||||
});
|
||||
await Promise.all([...opers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given payment receive with associated entries
|
||||
* and journal transactions.
|
||||
* @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();
|
||||
|
||||
await this.deleteJournalTransactions(paymentReceiveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the payment receive details of the given id.
|
||||
* @param {Integer} paymentReceiveId
|
||||
*/
|
||||
static async getPaymentReceive(paymentReceiveId) {
|
||||
const paymentReceive = await PaymentReceive.tenant().query().where('id', paymentReceiveId).first();
|
||||
return paymentReceive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the payment receive details with associated invoices.
|
||||
* @param {Integer} paymentReceiveId
|
||||
*/
|
||||
static async getPaymentReceiveWithInvoices(paymentReceiveId) {
|
||||
const paymentReceive = await PaymentReceive.tenant().query()
|
||||
.where('id', paymentReceiveId)
|
||||
.withGraphFetched('invoices')
|
||||
.first();
|
||||
return paymentReceive;
|
||||
}
|
||||
|
||||
static async isPaymentReceiveExists(paymentReceiveId) {
|
||||
const paymentReceives = await PaymentReceive.tenant().query().where('id', paymentReceiveId)
|
||||
return paymentReceives.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the payment receive number existance.
|
||||
*/
|
||||
static async isPaymentReceiveNoExists(paymentReceiveNumber) {
|
||||
const paymentReceives = await PaymentReceive.tenant().query().where('payment_receive_no', paymentReceiveNumber);
|
||||
return paymentReceives.length > 0;
|
||||
}
|
||||
}
|
||||
237
server/src/services/Sales/SaleInvoice.js
Normal file
237
server/src/services/Sales/SaleInvoice.js
Normal file
@@ -0,0 +1,237 @@
|
||||
import { omit, update, difference } from 'lodash';
|
||||
import {
|
||||
SaleInvoice,
|
||||
SaleInvoiceEntry,
|
||||
AccountTransaction,
|
||||
Account,
|
||||
Item,
|
||||
} from '@/models';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
|
||||
|
||||
export default class SaleInvoicesService extends ServiceItemsEntries {
|
||||
/**
|
||||
* Creates a new sale invoices and store it to the storage
|
||||
* with associated to entries and journal transactions.
|
||||
* @param {ISaleInvoice}
|
||||
* @return {ISaleInvoice}
|
||||
*/
|
||||
static async createSaleInvoice(saleInvoice) {
|
||||
const storedInvoice = await SaleInvoice.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
...omit(saleInvoice, ['entries']),
|
||||
});
|
||||
const opers = [];
|
||||
|
||||
saleInvoice.entries.forEach((entry) => {
|
||||
const oper = SaleInvoiceEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
sale_invoice_id: storedInvoice.id,
|
||||
...entry,
|
||||
});
|
||||
opers.push(oper);
|
||||
});
|
||||
await Promise.all([
|
||||
...opers,
|
||||
this.recordCreateJournalEntries(saleInvoice),
|
||||
]);
|
||||
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
|
||||
* @return {void}
|
||||
*/
|
||||
async recordJournalEntries(saleInvoice) {
|
||||
const accountsDepGraph = await Account.depGraph().query().remember();
|
||||
const journal = new JournalPoster(accountsDepGraph);
|
||||
const receivableTotal = sumBy(saleInvoice.entries, 'total');
|
||||
|
||||
const receivableAccount = await Account.tenant().query();
|
||||
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 commonJournalMeta = {
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
referenceId: saleInvoice.id,
|
||||
referenceType: 'SaleInvoice',
|
||||
date: formattedDate,
|
||||
};
|
||||
const totalReceivableEntry = new journalEntry({
|
||||
...commonJournalMeta,
|
||||
debit: receivableTotal,
|
||||
account: receivableAccount.id,
|
||||
accountNormal: 'debit',
|
||||
});
|
||||
journal.debit(totalReceivableEntry);
|
||||
|
||||
saleInvoice.entries.forEach((entry) => {
|
||||
const item = {};
|
||||
const incomeEntry = JournalEntry({
|
||||
...commonJournalMeta,
|
||||
credit: entry.total,
|
||||
account: item.sellAccountId,
|
||||
accountNormal: 'credit',
|
||||
note: '',
|
||||
});
|
||||
|
||||
if (item.type === 'inventory') {
|
||||
const inventoryCredit = JournalEntry({
|
||||
...commonJournalMeta,
|
||||
credit: entry.total,
|
||||
account: item.inventoryAccountId,
|
||||
accountNormal: 'credit',
|
||||
note: '',
|
||||
});
|
||||
const costEntry = JournalEntry({
|
||||
...commonJournalMeta,
|
||||
debit: entry.total,
|
||||
account: item.costAccountId,
|
||||
accountNormal: 'debit',
|
||||
note: '',
|
||||
});
|
||||
|
||||
journal.debit(costEntry);
|
||||
}
|
||||
journal.credit(incomeEntry);
|
||||
});
|
||||
await Promise.all([
|
||||
journalPoster.saveEntries(),
|
||||
journalPoster.saveBalance(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given sale invoice with associated entries
|
||||
* and journal transactions.
|
||||
* @param {Integer} saleInvoiceId
|
||||
*/
|
||||
static async deleteSaleInvoice(saleInvoiceId) {
|
||||
await SaleInvoice.tenant().query().where('id', saleInvoiceId).delete();
|
||||
await SaleInvoiceEntry.tenant()
|
||||
.query()
|
||||
.where('sale_invoice_id', saleInvoiceId)
|
||||
.delete();
|
||||
|
||||
const invoiceTransactions = await AccountTransaction.tenant()
|
||||
.query()
|
||||
.whereIn('reference_type', ['SaleInvoice'])
|
||||
.where('reference_id', saleInvoiceId)
|
||||
.withGraphFetched('account.type');
|
||||
|
||||
const accountsDepGraph = await Account.tenant().depGraph().query();
|
||||
const journal = new JournalPoster(accountsDepGraph);
|
||||
|
||||
journal.loadEntries(invoiceTransactions);
|
||||
journal.removeEntries();
|
||||
|
||||
await Promise.all([journal.deleteEntries(), journal.saveBalance()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given sale invoice.
|
||||
* @param {Integer} saleInvoiceId -
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
*/
|
||||
static async editSaleInvoice(saleInvoiceId, saleInvoice) {
|
||||
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 entriesIdsShouldDelete = this.entriesShouldDeleted(
|
||||
storedEntries,
|
||||
entriesIds,
|
||||
);
|
||||
if (entriesIdsShouldDelete.length > 0) {
|
||||
const updateOper = SaleInvoiceEntry.tenant().query().where('id', entriesIdsShouldDelete);
|
||||
opers.push(updateOper);
|
||||
}
|
||||
entriesIds.forEach((entry) => {
|
||||
const updateOper = SaleInvoiceEntry.tenant()
|
||||
.query()
|
||||
.patchAndFetchById(entry.id, {
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(updateOper);
|
||||
});
|
||||
await Promise.all([...opers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the sale invoice number id exists on the storage.
|
||||
* @param {Integer} saleInvoiceId
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static async isSaleInvoiceExists(saleInvoiceId) {
|
||||
const foundSaleInvoice = await SaleInvoice.tenant()
|
||||
.query()
|
||||
.where('id', saleInvoiceId);
|
||||
return foundSaleInvoice.length !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the sale invoice number exists on the storage.
|
||||
* @param {Integer} saleInvoiceNumber
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static async isSaleInvoiceNumberExists(saleInvoiceNumber, saleInvoiceId) {
|
||||
const foundSaleInvoice = await SaleInvoice.tenant()
|
||||
.query()
|
||||
.onBuild((query) => {
|
||||
query.where('invoice_no', saleInvoiceNumber);
|
||||
|
||||
if (saleInvoiceId) {
|
||||
query.whereNot('id', saleInvoiceId)
|
||||
}
|
||||
return query;
|
||||
});
|
||||
return foundSaleInvoice.length !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine the invoices IDs in bulk and returns the not found ones.
|
||||
* @param {Array} invoicesIds
|
||||
* @return {Array}
|
||||
*/
|
||||
static async isInvoicesExist(invoicesIds) {
|
||||
const storedInvoices = await SaleInvoice.tenant()
|
||||
.query()
|
||||
.onBuild((builder) => {
|
||||
builder.whereIn('id', invoicesIds);
|
||||
return builder;
|
||||
});
|
||||
const storedInvoicesIds = storedInvoices.map(i => i.id);
|
||||
const notStoredInvoices = difference(
|
||||
invoicesIds,
|
||||
storedInvoicesIds,
|
||||
);
|
||||
return notStoredInvoices;
|
||||
}
|
||||
}
|
||||
179
server/src/services/Sales/SalesEstimate.js
Normal file
179
server/src/services/Sales/SalesEstimate.js
Normal file
@@ -0,0 +1,179 @@
|
||||
import { omit, difference } from 'lodash';
|
||||
import { SaleEstimate, SaleEstimateEntry } from '@/models';
|
||||
|
||||
export default class SaleEstimateService {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Creates a new estimate with associated entries.
|
||||
* @async
|
||||
* @param {IEstimate} estimate
|
||||
* @return {void}
|
||||
*/
|
||||
static async createEstimate(estimate) {
|
||||
const storedEstimate = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
...omit(estimate, ['entries']),
|
||||
});
|
||||
const storeEstimateEntriesOpers = [];
|
||||
|
||||
estimate.entries.forEach((entry) => {
|
||||
const oper = SaleEstimateEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
estimate_id: storedEstimate.id,
|
||||
...entry,
|
||||
});
|
||||
storeEstimateEntriesOpers.push(oper);
|
||||
});
|
||||
await Promise.all([...storeEstimateEntriesOpers]);
|
||||
return storedEstimate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given estimate id with associated entries.
|
||||
* @async
|
||||
* @param {IEstimate} estimateId
|
||||
* @return {void}
|
||||
*/
|
||||
static async deleteEstimate(estimateId) {
|
||||
await SaleEstimateEntry.tenant()
|
||||
.query()
|
||||
.where('estimate_id', estimateId)
|
||||
.delete();
|
||||
await SaleEstimate.tenant().query().where('id', estimateId).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details of the given estimate with associated entries.
|
||||
* @async
|
||||
* @param {Integer} estimateId
|
||||
* @param {IEstimate} estimate
|
||||
* @return {void}
|
||||
*/
|
||||
static async editEstimate(estimateId, estimate) {
|
||||
const updatedEstimate = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.update({
|
||||
...omit(estimate, ['entries']),
|
||||
});
|
||||
const storedEstimateEntries = await SaleEstimateEntry.tenant()
|
||||
.query()
|
||||
.where('estimate_id', estimateId);
|
||||
|
||||
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 entriesIdsShouldBeDeleted = difference(
|
||||
storedEstimateEntriesIds,
|
||||
formEstimateEntriesIds,
|
||||
);
|
||||
|
||||
console.log(entriesIdsShouldBeDeleted);
|
||||
if (entriesIdsShouldBeDeleted.length > 0) {
|
||||
const oper = SaleEstimateEntry.tenant()
|
||||
.query()
|
||||
.where('id', entriesIdsShouldBeDeleted)
|
||||
.delete();
|
||||
opers.push(oper);
|
||||
}
|
||||
estimateEntriesHasID.forEach((entry) => {
|
||||
const oper = SaleEstimateEntry.tenant()
|
||||
.query()
|
||||
.patchAndFetchById(entry.id, {
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(oper);
|
||||
});
|
||||
await Promise.all([...opers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given estimate ID exists.
|
||||
* @async
|
||||
* @param {Numeric} estimateId
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static async isEstimateExists(estimateId) {
|
||||
const foundEstimate = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.where('id', estimateId);
|
||||
return foundEstimate.length !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given estimate entries IDs.
|
||||
* @async
|
||||
* @param {Numeric} estimateId
|
||||
* @param {IEstimate} estimate
|
||||
*/
|
||||
static async isEstimateEntriesIDsExists(estimateId, estimate) {
|
||||
const estimateEntriesIds = estimate.entries
|
||||
.filter((e) => e.id)
|
||||
.map((e) => e.id);
|
||||
|
||||
const estimateEntries = await SaleEstimateEntry.tenant()
|
||||
.query()
|
||||
.whereIn('id', estimateEntriesIds)
|
||||
.where('estimate_id', estimateId);
|
||||
|
||||
const storedEstimateEntriesIds = estimateEntries.map((e) => e.id);
|
||||
const notFoundEntriesIDs = difference(
|
||||
estimateEntriesIds,
|
||||
storedEstimateEntriesIds
|
||||
);
|
||||
return notFoundEntriesIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the estimate details of the given estimate id.
|
||||
* @param {Integer} estimateId
|
||||
* @return {IEstimate}
|
||||
*/
|
||||
static async getEstimate(estimateId) {
|
||||
const estimate = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.where('id', estimateId)
|
||||
.first();
|
||||
|
||||
return estimate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the estimate details with associated entries.
|
||||
* @param {Integer} estimateId
|
||||
*/
|
||||
static async getEstimateWithEntries(estimateId) {
|
||||
const estimate = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.where('id', estimateId)
|
||||
.withGraphFetched('entries')
|
||||
.first();
|
||||
|
||||
return estimate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the estimate number uniqness.
|
||||
* @param {Integer} estimateNumber
|
||||
* @param {Integer} excludeEstimateId
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static async isEstimateNumberUnique(estimateNumber, excludeEstimateId) {
|
||||
const foundEstimates = await SaleEstimate.tenant()
|
||||
.query()
|
||||
.onBuild((query) => {
|
||||
query.where('estimate_number', estimateNumber);
|
||||
|
||||
if (excludeEstimateId) {
|
||||
query.whereNot('id', excludeEstimateId);
|
||||
}
|
||||
return query;
|
||||
});
|
||||
return foundEstimates.length > 0;
|
||||
}
|
||||
}
|
||||
188
server/src/services/Sales/SalesReceipt.js
Normal file
188
server/src/services/Sales/SalesReceipt.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import { omit, difference } from 'lodash';
|
||||
import {
|
||||
SaleReceipt,
|
||||
SaleReceiptEntry,
|
||||
AccountTransaction,
|
||||
Account,
|
||||
} from '@/models';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
|
||||
export default class SalesReceipt {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Creates a new sale receipt with associated entries.
|
||||
* @param {ISaleReceipt} saleReceipt
|
||||
*/
|
||||
static async createSaleReceipt(saleReceipt) {
|
||||
const storedSaleReceipt = await SaleReceipt.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
...omit(saleReceipt, ['entries']),
|
||||
});
|
||||
const storeSaleReceiptEntriesOpers = [];
|
||||
|
||||
saleReceipt.entries.forEach((entry) => {
|
||||
const oper = SaleReceiptEntry.tenant()
|
||||
.query()
|
||||
.insert({
|
||||
sale_receipt_id: storedSaleReceipt.id,
|
||||
...entry,
|
||||
});
|
||||
storeSaleReceiptEntriesOpers.push(oper);
|
||||
});
|
||||
await Promise.all([...storeSaleReceiptEntriesOpers]);
|
||||
return storedSaleReceipt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records journal transactions for sale receipt.
|
||||
* @param {ISaleReceipt} saleReceipt
|
||||
*/
|
||||
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(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details sale receipt with associated entries.
|
||||
* @param {Integer} saleReceiptId
|
||||
* @param {ISaleReceipt} saleReceipt
|
||||
* @return {void}
|
||||
*/
|
||||
static async editSaleReceipt(saleReceiptId, saleReceipt) {
|
||||
const updatedSaleReceipt = await SaleReceipt.tenant()
|
||||
.query()
|
||||
.where('id', saleReceiptId)
|
||||
.update({
|
||||
...omit(saleReceipt, ['entries']),
|
||||
});
|
||||
const storedSaleReceiptEntries = await SaleReceiptEntry.tenant()
|
||||
.query()
|
||||
.where('sale_receipt_id', saleReceiptId);
|
||||
|
||||
const storedSaleReceiptsIds = storedSaleReceiptEntries.map((e) => e.id);
|
||||
const entriesHasID = saleReceipt.entries.filter((entry) => entry.id);
|
||||
const entriesIds = entriesHasID.map((e) => e.id);
|
||||
|
||||
const entriesIdsShouldBeDeleted = difference(
|
||||
storedSaleReceiptsIds,
|
||||
entriesIds
|
||||
);
|
||||
const opers = [];
|
||||
|
||||
if (entriesIdsShouldBeDeleted.length > 0) {
|
||||
const deleteOper = SaleReceiptEntry.tenant()
|
||||
.query()
|
||||
.where('id', entriesIdsShouldBeDeleted)
|
||||
.delete();
|
||||
opers.push(deleteOper);
|
||||
}
|
||||
entriesHasID.forEach((entry) => {
|
||||
const updateOper = SaleReceiptEntry.tenant()
|
||||
.query()
|
||||
.patchAndFetchById(entry.id, {
|
||||
...omit(entry, ['id']),
|
||||
});
|
||||
opers.push(updateOper);
|
||||
});
|
||||
await Promise.all([...opers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the sale receipt with associated entries.
|
||||
* @param {Integer} saleReceiptId
|
||||
* @return {void}
|
||||
*/
|
||||
static async deleteSaleReceipt(saleReceiptId) {
|
||||
await SaleReceipt.tenant().query().where('id', saleReceiptId).delete();
|
||||
await SaleReceiptEntry.tenant()
|
||||
.query()
|
||||
.where('sale_receipt_id', saleReceiptId)
|
||||
.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()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given sale receipt ID exists.
|
||||
* @param {Integer} saleReceiptId
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
static async isSaleReceiptExists(saleReceiptId) {
|
||||
const foundSaleReceipt = await SaleReceipt.tenant()
|
||||
.query()
|
||||
.where('id', saleReceiptId);
|
||||
return foundSaleReceipt.length !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the sale receipt entries IDs exists.
|
||||
* @param {Integer} saleReceiptId
|
||||
* @param {ISaleReceipt} saleReceipt
|
||||
*/
|
||||
static async isSaleReceiptEntriesIDsExists(saleReceiptId, saleReceipt) {
|
||||
const entriesIDs = saleReceipt.entries
|
||||
.filter((e) => e.id)
|
||||
.map((e) => e.id);
|
||||
|
||||
const storedEntries = await SaleReceiptEntry.tenant()
|
||||
.query()
|
||||
.whereIn('id', entriesIDs)
|
||||
.where('sale_receipt_id', saleReceiptId);
|
||||
|
||||
const storedEntriesIDs = storedEntries.map((e) => e.id);
|
||||
const notFoundEntriesIDs = difference(
|
||||
entriesIDs,
|
||||
storedEntriesIDs
|
||||
);
|
||||
return notFoundEntriesIDs;
|
||||
}
|
||||
|
||||
static async getSaleReceiptWithEntries(saleReceiptId) {
|
||||
const saleReceipt = await SaleReceipt.tenant().query()
|
||||
.where('id', saleReceiptId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
return saleReceipt;
|
||||
}
|
||||
}
|
||||
16
server/src/services/Sales/ServiceItemsEntries.js
Normal file
16
server/src/services/Sales/ServiceItemsEntries.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { difference } from "lodash";
|
||||
|
||||
|
||||
export default class ServiceItemsEntries {
|
||||
|
||||
static entriesShouldDeleted(storedEntries, entries) {
|
||||
const storedEntriesIds = storedEntries.map((e) => e.id);
|
||||
const entriesIds = entries.map((e) => e.id);
|
||||
|
||||
return difference(
|
||||
storedEntriesIds,
|
||||
entriesIds,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user