mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20: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:
@@ -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) {
|
||||
|
||||
9
server/src/services/Accounts/AccountsService.js
Normal file
9
server/src/services/Accounts/AccountsService.js
Normal 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;
|
||||
}
|
||||
}
|
||||
10
server/src/services/Customers/CustomersService.js
Normal file
10
server/src/services/Customers/CustomersService.js
Normal 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;
|
||||
}
|
||||
}
|
||||
75
server/src/services/DynamicListing/DynamicListing.js
Normal file
75
server/src/services/DynamicListing/DynamicListing.js
Normal 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();
|
||||
}
|
||||
}
|
||||
25
server/src/services/DynamicListing/DynamicListingBuilder.js
Normal file
25
server/src/services/DynamicListing/DynamicListingBuilder.js
Normal 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;
|
||||
}
|
||||
}
|
||||
22
server/src/services/DynamicListing/HasDynamicListing.js
Normal file
22
server/src/services/DynamicListing/HasDynamicListing.js
Normal 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;
|
||||
};
|
||||
21
server/src/services/Items/ItemsService.js
Normal file
21
server/src/services/Items/ItemsService.js
Normal 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;
|
||||
}
|
||||
}
|
||||
30
server/src/services/Purchases/BillPayments.js
Normal file
30
server/src/services/Purchases/BillPayments.js
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
||||
114
server/src/services/Purchases/Bills.js
Normal file
114
server/src/services/Purchases/Bills.js
Normal 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;
|
||||
}
|
||||
}
|
||||
5
server/src/services/Resource/ResourceService.js
Normal file
5
server/src/services/Resource/ResourceService.js
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
export default class ResourceService {
|
||||
|
||||
}
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
15
server/src/services/Vendors/VendorsService.js
Normal file
15
server/src/services/Vendors/VendorsService.js
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user