- 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:
Ahmed Bouhuolia
2020-07-22 02:03:12 +02:00
parent 9d9c7c1568
commit 56278a25f0
83 changed files with 5330 additions and 76 deletions

View File

@@ -63,6 +63,10 @@ export default class JournalPoster {
accountId: entry.account,
});
if (entry.contactType && entry.contactId) {
}
// Effect parent accounts of the given account id.
depAccountsIds.forEach((accountId) => {
this._setAccountBalanceChange({
@@ -96,6 +100,22 @@ export default class JournalPoster {
this.balancesChange[accountId] += change;
}
/**
* Set contact balance change.
* @param {Object} param -
*/
_setContactBalanceChange({
contactType,
contactId,
accountNormal,
debit,
credit,
entryType,
}) {
}
/**
* Mapping the balance change to list.
*/
@@ -455,6 +475,9 @@ export default class JournalPoster {
});
}
/**
* Calculates the entries balance change.
*/
calculateEntriesBalanceChange() {
this.entries.forEach((entry) => {
if (entry.credit) {

View File

@@ -0,0 +1,9 @@
import { Account } from '@/models';
export default class AccountsService {
static async isAccountExists(accountId) {
const foundAccounts = await Account.tenant().query().where('id', accountId);
return foundAccounts.length > 0;
}
}

View File

@@ -0,0 +1,10 @@
import Customer from "../../models/Customer";
export default class CustomersService {
static async isCustomerExists(customerId) {
const foundCustomeres = await Customer.tenant().query().where('id', customerId);
return foundCustomeres.length > 0;
}
}

View File

@@ -0,0 +1,75 @@
import {
DynamicFilter,
DynamicFilterSortBy,
DynamicFilterViews,
DynamicFilterFilterRoles,
} from '@/lib/DynamicFilter';
import {
mapViewRolesToConditionals,
mapFilterRolesToDynamicFilter,
} from '@/lib/ViewRolesBuilder';
export const DYNAMIC_LISTING_ERRORS = {
LOGIC_INVALID: 'VIEW.LOGIC.EXPRESSION.INVALID',
RESOURCE_HAS_NO_FIELDS: 'RESOURCE.HAS.NO.GIVEN.FIELDS',
};
export default class DynamicListing {
/**
* Constructor method.
* @param {DynamicListingBuilder} dynamicListingBuilder
* @return {DynamicListing|Error}
*/
constructor(dynamicListingBuilder) {
this.listingBuilder = dynamicListingBuilder;
this.dynamicFilter = new DynamicFilter(this.listingBuilder.modelClass.tableName);
return this.init();
}
/**
* Initialize the dynamic listing.
*/
init() {
// Initialize the column sort by.
if (this.listingBuilder.columnSortBy) {
const sortByFilter = new DynamicFilterSortBy(
filter.column_sort_by,
filter.sort_order
);
this.dynamicFilter.setFilter(sortByFilter);
}
// Initialize the view filter roles.
if (this.listingBuilder.view && this.listingBuilder.view.roles.length > 0) {
const viewFilter = new DynamicFilterViews(
mapViewRolesToConditionals(this.listingBuilder.view.roles),
this.listingBuilder.view.rolesLogicExpression
);
if (!viewFilter.validateFilterRoles()) {
return new Error(DYNAMIC_LISTING_ERRORS.LOGIC_INVALID);
}
this.dynamicFilter.setFilter(viewFilter);
}
// Initialize the dynamic filter roles.
if (this.listingBuilder.filterRoles.length > 0) {
const filterRoles = new DynamicFilterFilterRoles(
mapFilterRolesToDynamicFilter(filter.filter_roles),
accountsResource.fields
);
this.dynamicFilter.setFilter(filterRoles);
if (filterRoles.validateFilterRoles().length > 0) {
return new Error(DYNAMIC_LISTING_ERRORS.RESOURCE_HAS_NO_FIELDS);
}
}
return this;
}
/**
* Build query.
*/
buildQuery(){
return this.dynamicFilter.buildQuery();
}
}

View File

@@ -0,0 +1,25 @@
export default class DynamicListingBuilder {
addModelClass(modelClass) {
this.modelClass = modelClass;
}
addCustomViewId(customViewId) {
this.customViewId = customViewId;
}
addFilterRoles (filterRoles) {
this.filterRoles = filterRoles;
}
addSortBy(sortBy, sortOrder) {
this.sortBy = sortBy;
this.sortOrder = sortOrder;
}
addView(view) {
this.view = view;
}
}

View File

@@ -0,0 +1,22 @@
import { DYNAMIC_LISTING_ERRORS } from '@/services/DynamicListing/DynamicListing';
export const dynamicListingErrorsToResponse = (error) => {
let _errors;
if (error.message === DYNAMIC_LISTING_ERRORS.LOGIC_INVALID) {
_errors.push({
type: DYNAMIC_LISTING_ERRORS.LOGIC_INVALID,
code: 200,
});
}
if (
error.message ===
DYNAMIC_LISTING_ERRORS.RESOURCE_HAS_NO_FIELDS
) {
_errors.push({
type: DYNAMIC_LISTING_ERRORS.RESOURCE_HAS_NO_FIELDS,
code: 300,
});
}
return _errors;
};

View File

@@ -0,0 +1,21 @@
import { difference } from "lodash";
import { Item } from '@/models';
export default class ItemsService {
/**
* Validates the given items IDs exists or not returns the not found ones.
* @param {Array} itemsIDs
* @return {Array}
*/
static async isItemsIdsExists(itemsIDs) {
const storedItems = await Item.tenant().query().whereIn('id', itemsIDs);
const storedItemsIds = storedItems.map((t) => t.id);
const notFoundItemsIds = difference(
itemsIDs,
storedItemsIds,
);
return notFoundItemsIds;
}
}

View File

@@ -0,0 +1,30 @@
import { omit } from "lodash";
import { BillPayment } from '@/models';
export default class BillPaymentsService {
static async createBillPayment(billPayment) {
const storedBillPayment = await BillPayment.tenant().query().insert({
...omit(billPayment, ['entries']),
});
}
editBillPayment(billPaymentId, billPayment) {
}
static async isBillPaymentExists(billPaymentId) {
const foundBillPayments = await BillPayment.tenant().query().where('id', billPaymentId);
return foundBillPayments.lengh > 0;
}
static async isBillPaymentNumberExists(billPaymentNumber) {
const foundPayments = await Bill.tenant().query().where('bill_payment_number', billPaymentNumber);
return foundPayments.length > 0;
}
isBillPaymentsExist(billPaymentIds) {
}
}

View File

@@ -0,0 +1,114 @@
import { omit } from 'lodash';
import { Bill, BillPayment } from '@/models';
import { Item } from '@/models';
import { Account } from '../../models';
import JournalPoster from '../Accounting/JournalPoster';
export default class BillsService {
/**
* Creates a new bill and stored it to the storage.
* @param {IBill} bill -
* @return {void}
*/
static async createBill(bill) {
const storedBill = await Bill.tenant().query().insert({
...omit(bill, ['entries']),
});
}
/**
* Edits details of the given bill id with associated entries.
* @param {Integer} billId
* @param {IBill} bill
*/
static async editBill(billId, bill) {
const updatedBill = await Bill.tenant().query().insert({
...omit(bill, ['entries']),
});
}
/**
* Records the bill journal transactions.
* @param {IBill} bill
*/
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);
const payableAccount = await Account.tenant().query();
const formattedDate = moment(saleInvoice.invoice_date).format('YYYY-MM-DD');
const accountsDepGraph = await Account.depGraph().query().remember();
const journal = new JournalPoster(accountsDepGraph);
const commonJournalMeta = {
debit: 0,
credit: 0,
referenceId: bill.id,
referenceType: 'Bill',
date: formattedDate,
accural: true,
};
const payableEntry = await JournalEntry({
...commonJournalMeta,
credit: payableTotal,
contactId: bill.vendorId,
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);
}
});
await Promise.all([
journal.saveEntries(),
journal.saveBalance(),
])
}
/**
* Deletes the bill with associated entries.
* @param {Integer} billId
* @return {void}
*/
static async deleteBill(billId) {
await BillPayment.tenant().query().where('id', billId);
}
/**
* 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}
*/
isBillsExist(billsIds) {
}
static async isBillNoExists(billNumber) {
const foundBills = await Bill.tenant().query().where('bill_number', billNumber);
return foundBills.length > 0;
}
}

View File

@@ -0,0 +1,5 @@
export default class ResourceService {
}

View 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()]);
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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,
);
}
}

View File

@@ -0,0 +1,15 @@
import { Vendor } from '@/models';
export default class VendorsService {
static async isVendorExists(vendorId) {
const foundVendors = await Vendor.tenant().query().where('id', vendorId);
return foundVendors.length > 0;
}
static async isVendorsExist(vendorsIds) {
}
}