Merge branch 'master' of https://github.com/abouolia/Bigcapital into sales

This commit is contained in:
elforjani3
2020-08-05 11:40:35 +02:00
31 changed files with 420 additions and 819 deletions

View File

@@ -9,6 +9,8 @@ exports.up = function(knex) {
table.string('reference_no'); table.string('reference_no');
table.integer('deposit_account_id').unsigned(); table.integer('deposit_account_id').unsigned();
table.string('payment_receive_no'); table.string('payment_receive_no');
table.text('description');
table.integer('user_id').unsigned();
table.timestamps(); table.timestamps();
}); });
}; };

View File

@@ -20,6 +20,7 @@ exports.seed = (knex) => {
{ id: 12, name: 'sales_payment_receives' }, { id: 12, name: 'sales_payment_receives' },
{ id: 13, name: 'bills' }, { id: 13, name: 'bills' },
{ id: 14, name: 'bill_payments' }, { id: 14, name: 'bill_payments' },
{ id: 16, name: 'payment_receives' },
]); ]);
}); });
}; };

View File

@@ -1,247 +0,0 @@
import express from 'express';
import {
check,
query,
param,
validationResult,
} from 'express-validator';
import { pick, difference, groupBy } from 'lodash';
import asyncMiddleware from "@/http/middleware/asyncMiddleware";
import JWTAuth from '@/http/middleware/jwtAuth';
import Budget from '@/models/Budget';
import BudgetEntry from '@/models/BudgetEntry';
import Account from '@/models/Account';
import moment from '@/services/Moment';
import BudgetEntriesSet from '@/collection/BudgetEntriesSet';
import AccountType from '@/models/AccountType';
import NestedSet from '@/collection/NestedSet';
import { dateRangeFormat } from '@/utils';
export default {
/**
* Router constructor.
*/
router() {
const router = express.Router();
router.use(JWTAuth);
router.post('/',
this.newBudget.validation,
asyncMiddleware(this.newBudget.handler));
router.get('/:id',
this.getBudget.validation,
asyncMiddleware(this.getBudget.handler));
router.get('/:id',
this.deleteBudget.validation,
asyncMiddleware(this.deleteBudget.handler));
router.get('/',
this.listBudgets.validation,
asyncMiddleware(this.listBudgets.handler));
return router;
},
/**
* Retrieve budget details of the given id.
*/
getBudget: {
validation: [
param('id').exists().isNumeric().toInt(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { id } = req.params;
const budget = await Budget.query().findById(id);
if (!budget) {
return res.status(404).send({
errors: [{ type: 'budget.not.found', code: 100 }],
});
}
const accountTypes = await AccountType.query().where('balance_sheet', true);
const [budgetEntries, accounts] = await Promise.all([
BudgetEntry.query().where('budget_id', budget.id),
Account.query().whereIn('account_type_id', accountTypes.map((a) => a.id)),
]);
const accountsNestedSet = new NestedSet(accounts);
const columns = [];
const fromDate = moment(budget.year).startOf('year')
.add(budget.rangeOffset, budget.rangeBy).toDate();
const toDate = moment(budget.year).endOf('year').toDate();
const dateRange = moment.range(fromDate, toDate);
const dateRangeCollection = Array.from(dateRange.by(budget.rangeBy, {
step: budget.rangeIncrement, excludeEnd: false, excludeStart: false,
}));
dateRangeCollection.forEach((date) => {
columns.push(date.format(dateRangeFormat(budget.rangeBy)));
});
const budgetEntriesSet = BudgetEntriesSet.from(budgetEntries, {
orderSize: columns.length,
});
budgetEntriesSet.setZeroPlaceholder();
budgetEntriesSet.calcTotalSummary();
return res.status(200).send({
columns,
accounts: budgetEntriesSet.toArray(),
total: budgetEntriesSet.toArrayTotalSummary(),
});
},
},
/**
* Delete the given budget.
*/
deleteBudget: {
validation: [
param('id').exists(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { id } = req.params;
const budget = await Budget.query().findById(id);
if (!budget) {
return res.status(404).send({
errors: [{ type: 'budget.not.found', code: 100 }],
});
}
await BudgetEntry.query().where('budget_id', budget.id).delete();
await budget.delete();
return res.status(200).send();
},
},
/**
* Saves the new budget.
*/
newBudget: {
validation: [
check('name').exists(),
check('fiscal_year').exists(),
check('period').exists().isIn(['year', 'month', 'quarter', 'half-year']),
check('accounts_type').exists().isIn(['balance_sheet', 'profit_loss']),
check('accounts').isArray(),
check('accounts.*.account_id').exists().isNumeric().toInt(),
check('accounts.*.entries').exists().isArray(),
check('accounts.*.entries.*.amount').exists().isNumeric().toFloat(),
check('accounts.*.entries.*.order').exists().isNumeric().toInt(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const form = { ...req.body };
const submitAccountsIds = form.accounts.map((a) => a.account_id);
const storedAccounts = await Account.query().whereIn('id', submitAccountsIds);
const storedAccountsIds = storedAccounts.map((a) => a.id);
const errorReasons = [];
const notFoundAccountsIds = difference(submitAccountsIds, storedAccountsIds);
if (notFoundAccountsIds.length > 0) {
errorReasons.push({
type: 'ACCOUNT.NOT.FOUND', code: 200, accounts: notFoundAccountsIds,
});
}
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
// validation entries order.
const budget = await Budget.query().insert({
...pick(form, ['name', 'fiscal_year', 'period', 'accounts_type']),
});
const promiseOpers = [];
form.accounts.forEach((account) => {
account.entries.forEach((entry) => {
const budgetEntry = BudgetEntry.query().insert({
account_id: account.account_id,
amount: entry.amount,
order: entry.order,
});
promiseOpers.push(budgetEntry);
});
});
await Promise.all(promiseOpers);
return res.status(200).send({ id: budget.id });
},
},
/**
* List of paginated budgets items.
*/
listBudgets: {
validation: [
query('year').optional(),
query('income_statement').optional().isBoolean().toBoolean(),
query('profit_loss').optional().isBoolean().toBoolean(),
query('page').optional().isNumeric().toInt(),
query('page_size').isNumeric().toInt(),
query('custom_view_id').optional().isNumeric().toInt(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const filter = {
page_size: 10,
page: 1,
...req.query,
};
const budgets = await Budget.query().runBefore((result, q) => {
if (filter.profit_loss) {
q.modify('filterByYear', filter.year);
}
if (filter.income_statement) {
q.modify('filterByIncomeStatement', filter.income_statement);
}
if (filter.profit_loss) {
q.modify('filterByProfitLoss', filter.profit_loss);
}
q.page(filter.page, filter.page_size);
return result;
});
return res.status(200).send({
items: budgets.items,
});
},
},
};

View File

@@ -1,122 +0,0 @@
import express from 'express';
import { query, validationResult } from 'express-validator';
import moment from 'moment';
import jwtAuth from '@/http/middleware/jwtAuth';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import Budget from '@/models/Budget';
import Account from '@/models/Account';
import AccountType from '@/models/AccountType';
import NestedSet from '@/collection/NestedSet';
import BudgetEntry from '@/models/BudgetEntry';
import { dateRangeFormat } from '@/utils';
export default {
/**
* Router constructor.
*/
router() {
const router = express.Router();
router.use(jwtAuth);
router.get('/budget_verses_actual/:reportId',
this.budgetVersesActual.validation,
asyncMiddleware(this.budgetVersesActual.handler));
return router;
},
budgetVersesActual: {
validation: [
query('basis').optional().isIn(['cash', 'accural']),
query('period').optional(),
query('active_accounts').optional().toBoolean(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { reportId } = req.params;
const form = { ...req.body };
const errorReasons = [];
const budget = await Budget.query().findById(reportId);
if (!budget) {
errorReasons.push({ type: 'BUDGET_NOT_FOUND', code: 100 });
}
const budgetEntries = await BudgetEntry.query().where('budget_id', budget.id);
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
const accountTypes = await AccountType.query()
.where('balance_sheet', budget.accountTypes === 'balance_sheet')
.where('income_sheet', budget.accountTypes === 'profit_losss');
const accounts = await Account.query().runBefore((result, q) => {
const accountTypesIds = accountTypes.map((t) => t.id);
if (accountTypesIds.length > 0) {
q.whereIn('account_type_id', accountTypesIds);
}
q.where('active', form.active_accounts === true);
q.withGraphFetched('transactions');
});
// const accountsNestedSet = NestedSet.from(accounts);
const fromDate = moment(budget.year).startOf('year')
.add(budget.rangeOffset, budget.rangeBy).toDate();
const toDate = moment(budget.year).endOf('year').toDate();
const dateRange = moment.range(fromDate, toDate);
const dateRangeCollection = Array.from(dateRange.by(budget.rangeBy, {
step: budget.rangeIncrement, excludeEnd: false, excludeStart: false,
}));
// // const accounts = {
// // assets: [
// // {
// // name: '',
// // code: '',
// // totalEntries: [
// // {
// // }
// // ],
// // children: [
// // {
// // name: '',
// // code: '',
// // entries: [
// // {
// // }
// // ]
// // }
// // ]
// // }
// // ]
// // }
return res.status(200).send({
columns: dateRangeCollection.map(d => d.format(dateRangeFormat(budget.rangeBy))),
// accounts: {
// asset: [],
// liabilities: [],
// equaity: [],
// income: [],
// expenses: [],
// }
});
},
},
}

View File

@@ -1,17 +0,0 @@
export default {
router() {
},
addExchangePrice: {
validation: {
},
async handler(req, res) {
},
},
}

View File

@@ -2,15 +2,18 @@
import { Router } from 'express'; import { Router } from 'express';
import { check, param, query, ValidationChain } from 'express-validator'; import { check, param, query, ValidationChain } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import validateMiddleware from '@/http/middleware/validateMiddleware';
import BaseController from '@/http/controllers/BaseController'; import BaseController from '@/http/controllers/BaseController';
import BillPaymentsService from '@/services/Purchases/BillPayments'; import BillPaymentsService from '@/services/Purchases/BillPayments';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
import ItemsService from '@/services/Items/ItemsService';
import { IBillPaymentEntry, IBillPayment } from '@/interfaces/BillPayment';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from '@/services/DynamicListing/DynamicListing';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing'; import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing';
/**
* Bills payments controller.
* @controller
*/
export default class BillsPayments extends BaseController { export default class BillsPayments extends BaseController {
/** /**
* Router constructor. * Router constructor.
@@ -21,38 +24,43 @@ export default class BillsPayments extends BaseController {
router.post('/', [ router.post('/', [
...this.billPaymentSchemaValidation, ...this.billPaymentSchemaValidation,
], ],
validateMiddleware,
asyncMiddleware(this.validateBillPaymentVendorExistance), asyncMiddleware(this.validateBillPaymentVendorExistance),
asyncMiddleware(this.validatePaymentAccount), asyncMiddleware(this.validatePaymentAccount),
asyncMiddleware(this.validatePaymentNumber), asyncMiddleware(this.validatePaymentNumber),
asyncMiddleware(this.validateItemsIds), asyncMiddleware(this.validateEntriesBillsExistance),
asyncMiddleware(this.validateVendorsDueAmount),
asyncMiddleware(this.createBillPayment), asyncMiddleware(this.createBillPayment),
); );
router.post('/:id', [ router.post('/:id', [
...this.billPaymentSchemaValidation, ...this.billPaymentSchemaValidation,
...this.specificBillPaymentValidateSchema, ...this.specificBillPaymentValidateSchema,
], ],
validateMiddleware,
asyncMiddleware(this.validateBillPaymentVendorExistance), asyncMiddleware(this.validateBillPaymentVendorExistance),
asyncMiddleware(this.validatePaymentAccount), asyncMiddleware(this.validatePaymentAccount),
asyncMiddleware(this.validatePaymentNumber), asyncMiddleware(this.validatePaymentNumber),
asyncMiddleware(this.validateItemsIds), asyncMiddleware(this.validateEntriesBillsExistance),
asyncMiddleware(this.validateEntriesIds), asyncMiddleware(this.validateVendorsDueAmount),
asyncMiddleware(this.editBillPayment), asyncMiddleware(this.editBillPayment),
) )
router.delete('/:id', router.delete('/:id',
this.specificBillPaymentValidateSchema, this.specificBillPaymentValidateSchema,
validateMiddleware,
asyncMiddleware(this.validateBillPaymentExistance), asyncMiddleware(this.validateBillPaymentExistance),
asyncMiddleware(this.deleteBillPayment), asyncMiddleware(this.deleteBillPayment),
); );
router.get('/:id', router.get('/:id',
this.specificBillPaymentValidateSchema, this.specificBillPaymentValidateSchema,
validateMiddleware,
asyncMiddleware(this.validateBillPaymentExistance), asyncMiddleware(this.validateBillPaymentExistance),
asyncMiddleware(this.getBillPayment), asyncMiddleware(this.getBillPayment),
); );
router.get('/', router.get('/',
this.listingValidationSchema, this.listingValidationSchema,
validateMiddleware,
asyncMiddleware(this.getBillsPayments) asyncMiddleware(this.getBillsPayments)
); );
return router; return router;
} }
@@ -69,13 +77,8 @@ export default class BillsPayments extends BaseController {
check('reference').optional().trim().escape(), check('reference').optional().trim().escape(),
check('entries').exists().isArray({ min: 1 }), check('entries').exists().isArray({ min: 1 }),
check('entries.*.id').optional().isNumeric().toInt(), check('entries.*.bill_id').exists().isNumeric().toInt(),
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.payment_amount').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.quantity').exists().isNumeric().toFloat(),
check('entries.*.discount').optional().isNumeric().toFloat(),
check('entries.*.description').optional().trim().escape(),
]; ];
} }
@@ -97,7 +100,7 @@ export default class BillsPayments extends BaseController {
static async validateBillPaymentVendorExistance(req: Request, res: Response, next: any ) { static async validateBillPaymentVendorExistance(req: Request, res: Response, next: any ) {
const billPayment = req.body; const billPayment = req.body;
const { Vendor } = req.models; const { Vendor } = req.models;
const isVendorExists = await Vendor.query('id', billPayment.vendor_id).first(); const isVendorExists = await Vendor.query().findById(billPayment.vendor_id);
if (!isVendorExists) { if (!isVendorExists) {
return res.status(400).send({ return res.status(400).send({
@@ -121,7 +124,7 @@ export default class BillsPayments extends BaseController {
errors: [{ type: 'BILL.PAYMENT.NOT.FOUND', code: 100 }], errors: [{ type: 'BILL.PAYMENT.NOT.FOUND', code: 100 }],
}); });
} }
next(req, res, next); next();
} }
/** /**
@@ -132,14 +135,15 @@ export default class BillsPayments extends BaseController {
*/ */
static async validatePaymentAccount(req: Request, res: Response, next: any) { static async validatePaymentAccount(req: Request, res: Response, next: any) {
const billPayment = { ...req.body }; const billPayment = { ...req.body };
const isAccountExists = AccountsService.isAccountExists(billPayment); const isAccountExists = await AccountsService.isAccountExists(
billPayment.payment_account_id
);
if (!isAccountExists) { if (!isAccountExists) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 200 }], errors: [{ type: 'PAYMENT.ACCOUNT.NOT.FOUND', code: 200 }],
}); });
} }
next(req, res, next); next();
} }
/** /**
@@ -149,61 +153,75 @@ export default class BillsPayments extends BaseController {
* @param {Function} res * @param {Function} res
*/ */
static async validatePaymentNumber(req: Request, res: Response, next: any) { static async validatePaymentNumber(req: Request, res: Response, next: any) {
const { BillPayment } = req.models;
const billPayment = { ...req.body }; const billPayment = { ...req.body };
const isNumberExists = await BillPaymentsService.isBillNoExists(billPayment); const foundBillPayment = await BillPayment.query()
.where('payment_number', billPayment.payment_number)
.first();
if (!isNumberExists) { if (foundBillPayment) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'PAYMENT.NUMBER.NOT.UNIQUE', code: 300 }], errors: [{ type: 'PAYMENT.NUMBER.NOT.UNIQUE', code: 300 }],
}); });
} }
next(req, res, next);
}
/**
* validate entries items ids existance on the storage.
* @param {Request} req
* @param {Response} res
* @param {Function} next
*/
static async validateItemsIds(req: Request, res: Response, next: Function) {
const billPayment: any = { ...req.body };
const itemsIds = billPayment.entries.map((e) => e.item_id);
const notFoundItemsIds = await ItemsService.isItemsIdsExists(
itemsIds
);
if (notFoundItemsIds.length > 0) {
return res.status(400).send({
errors: [{ type: 'ITEMS.IDS.NOT.FOUND', code: 400 }],
});
}
next(); next();
} }
/** /**
* Validates the entries ids in edit bill payment. * Validate whether the entries bills ids exist on the storage.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
static async validateEntriesIds(req: Request, res: Response, next: Function) { static async validateEntriesBillsExistance(req: Request, res: Response, next: any) {
const { BillPaymentEntry } = req.models; const { Bill } = req.models;
const { id: billPaymentId } = req.params; const billPayment = { ...req.body };
const billPayment = { id: billPaymentId, ...req.body }; const entriesBillsIds = billPayment.entries.map((e: any) => e.bill_id);
const entriesIds = billPayment.entries const notFoundBillsIds = await Bill.getNotFoundBills(entriesBillsIds);
.filter((entry: IBillPaymentEntry) => entry.id)
.map((entry: IBillPaymentEntry) => entry.id);
const storedEntries = await BillPaymentEntry.tenant().query() if (notFoundBillsIds.length > 0) {
.where('bill_payment_id', billPaymentId);
const storedEntriesIds = storedEntries.map((entry: IBillPaymentEntry) => entry.id);
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
if (notFoundEntriesIds.length > 0) {
return res.status(400).send({ return res.status(400).send({
errors: [{ type: 'ENTEIES.IDS.NOT.FOUND', code: 800 }], errors: [{ type: 'BILLS.IDS.NOT.EXISTS', code: 600 }],
});
}
next();
}
/**
* Validate wether the payment amount bigger than the payable amount.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @return {void}
*/
static async validateVendorsDueAmount(req: Request, res: Response, next: Function) {
const { Bill } = req.models;
const billsIds = req.body.entries.map((entry: any) => entry.bill_id);
const storedBills = await Bill.query()
.whereIn('id', billsIds);
const storedBillsMap = new Map(
storedBills.map((bill: any) => [bill.id, bill]),
);
interface invalidPaymentAmountError{
index: number,
due_amount: number
};
const hasWrongPaymentAmount: invalidPaymentAmountError[] = [];
const { entries } = req.body;
entries.forEach((entry: any, index: number) => {
const entryBill = storedBillsMap.get(entry.bill_id);
const { dueAmount } = entryBill;
if (dueAmount < entry.payment_amount) {
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
}
});
if (hasWrongPaymentAmount.length > 0) {
return res.status(400).send({
errors: [{ type: 'INVALID.BILL.PAYMENT.AMOUNT', code: 400, indexes: hasWrongPaymentAmount }]
}); });
} }
next(); next();
@@ -297,7 +315,6 @@ export default class BillsPayments extends BaseController {
errors: [{ type: 'BILL.PAYMENTS.RESOURCE.NOT_FOUND', code: 200 }], errors: [{ type: 'BILL.PAYMENTS.RESOURCE.NOT_FOUND', code: 200 }],
}); });
} }
const viewMeta = await View.query() const viewMeta = await View.query()
.modify('allMetadata') .modify('allMetadata')
.modify('specificOrFavourite', filter.custom_view_id) .modify('specificOrFavourite', filter.custom_view_id)

View File

@@ -1,15 +1,22 @@
import express from 'express'; import express from 'express';
import { check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import { difference } from 'lodash'; import { difference } from 'lodash';
import { PaymentReceiveEntry } from '@/models';
import BaseController from '@/http/controllers/BaseController'; import BaseController from '@/http/controllers/BaseController';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import PaymentReceiveService from '@/services/Sales/PaymentReceive'; import PaymentReceiveService from '@/services/Sales/PaymentsReceives';
import CustomersService from '@/services/Customers/CustomersService'; import CustomersService from '@/services/Customers/CustomersService';
import SaleInvoicesService from '@/services/Sales/SaleInvoice'; import SaleInvoicesService from '@/services/Sales/SalesInvoices';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
import { PaymentReceiveEntry } from '@/models'; import DynamicListing from '@/services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing';
/**
* Payments receives controller.
* @controller
*/
export default class PaymentReceivesController extends BaseController { export default class PaymentReceivesController extends BaseController {
/** /**
* Router constructor. * Router constructor.
@@ -27,7 +34,8 @@ export default class PaymentReceivesController extends BaseController {
asyncMiddleware(this.validateDepositAccount), asyncMiddleware(this.validateDepositAccount),
asyncMiddleware(this.validateInvoicesIDs), asyncMiddleware(this.validateInvoicesIDs),
asyncMiddleware(this.validateEntriesIdsExistance), asyncMiddleware(this.validateEntriesIdsExistance),
asyncMiddleware(this.editPaymentReceive) asyncMiddleware(this.validateInvoicesPaymentsAmount),
asyncMiddleware(this.editPaymentReceive),
); );
router.post( router.post(
'/', '/',
@@ -38,7 +46,7 @@ export default class PaymentReceivesController extends BaseController {
asyncMiddleware(this.validateDepositAccount), asyncMiddleware(this.validateDepositAccount),
asyncMiddleware(this.validateInvoicesIDs), asyncMiddleware(this.validateInvoicesIDs),
asyncMiddleware(this.validateInvoicesPaymentsAmount), asyncMiddleware(this.validateInvoicesPaymentsAmount),
asyncMiddleware(this.newPaymentReceive) asyncMiddleware(this.newPaymentReceive),
); );
router.get( router.get(
'/:id', '/:id',
@@ -58,7 +66,7 @@ export default class PaymentReceivesController extends BaseController {
this.paymentReceiveValidation, this.paymentReceiveValidation,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validatePaymentReceiveExistance), asyncMiddleware(this.validatePaymentReceiveExistance),
asyncMiddleware(this.deletePaymentReceive) asyncMiddleware(this.deletePaymentReceive),
); );
return router; return router;
} }
@@ -340,7 +348,7 @@ export default class PaymentReceivesController extends BaseController {
/** /**
* Payment receive list validation schema. * Payment receive list validation schema.
*/ */
static async validatePaymentReceiveList() { static get validatePaymentReceiveList() {
return [ return [
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
@@ -407,7 +415,8 @@ export default class PaymentReceivesController extends BaseController {
const paymentReceives = await PaymentReceive.query().onBuild((builder) => { const paymentReceives = await PaymentReceive.query().onBuild((builder) => {
dynamicListing.buildQuery()(builder); dynamicListing.buildQuery()(builder);
return builder; return builder;
}); }).pagination(filter.page - 1, filter.page_size);
return res.status(200).send({ return res.status(200).send({
payment_receives: { payment_receives: {
...paymentReceives, ...paymentReceives,

View File

@@ -103,9 +103,10 @@ export default class SalesEstimatesController extends BaseController {
return [ return [
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
query('column_sort_by').optional(), query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
] ]
} }
@@ -201,7 +202,7 @@ export default class SalesEstimatesController extends BaseController {
.filter(e => e.id) .filter(e => e.id)
.map((e) => e.id); .map((e) => e.id);
const foundEntries = await ItemEntry.query() const foundEntries = await ItemEntry.tenant().query()
.whereIn('id', entriesIds) .whereIn('id', entriesIds)
.where('reference_type', 'SaleInvoice') .where('reference_type', 'SaleInvoice')
.where('reference_id', saleInvoiceId); .where('reference_id', saleInvoiceId);
@@ -323,7 +324,7 @@ export default class SalesEstimatesController extends BaseController {
return res.status(400).send({ errors: errorReasons }); return res.status(400).send({ errors: errorReasons });
} }
const salesEstimates = await SaleEstimate.query().onBuild((query) => { const salesEstimates = await SaleEstimate.query().onBuild((builder) => {
dynamicListing.buildQuery()(builder); dynamicListing.buildQuery()(builder);
return builder; return builder;
}).pagination(filter.page - 1, filter.page_size); }).pagination(filter.page - 1, filter.page_size);

View File

@@ -1,16 +1,15 @@
import express from 'express'; import express from 'express';
import { check, param, query } from 'express-validator'; import { check, param, query } from 'express-validator';
import { difference } from 'lodash';
import { ItemEntry } from '@/models'; import { ItemEntry } from '@/models';
import validateMiddleware from '@/http/middleware/validateMiddleware'; import validateMiddleware from '@/http/middleware/validateMiddleware';
import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import SaleInvoiceService from '@/services/Sales/SaleInvoice'; import SaleInvoiceService from '@/services/Sales/SalesInvoices';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from '@/services/Items/ItemsService';
import CustomersService from '@/services/Customers/CustomersService'; import CustomersService from '@/services/Customers/CustomersService';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from '@/services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing'; import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing';
import { SaleInvoice } from '../../../models';
import { difference } from 'lodash';
export default class SaleInvoicesController { export default class SaleInvoicesController {
/** /**
@@ -23,6 +22,7 @@ export default class SaleInvoicesController {
'/', '/',
this.saleInvoiceValidationSchema, this.saleInvoiceValidationSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateInvoiceCustomerExistance),
asyncMiddleware(this.validateInvoiceNumberUnique), asyncMiddleware(this.validateInvoiceNumberUnique),
asyncMiddleware(this.validateInvoiceItemsIdsExistance), asyncMiddleware(this.validateInvoiceItemsIdsExistance),
asyncMiddleware(this.newSaleInvoice) asyncMiddleware(this.newSaleInvoice)
@@ -35,6 +35,7 @@ export default class SaleInvoicesController {
], ],
validateMiddleware, validateMiddleware,
asyncMiddleware(this.validateInvoiceExistance), asyncMiddleware(this.validateInvoiceExistance),
asyncMiddleware(this.validateInvoiceCustomerExistance),
asyncMiddleware(this.validateInvoiceNumberUnique), asyncMiddleware(this.validateInvoiceNumberUnique),
asyncMiddleware(this.validateInvoiceItemsIdsExistance), asyncMiddleware(this.validateInvoiceItemsIdsExistance),
asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance), asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance),
@@ -96,12 +97,17 @@ export default class SaleInvoicesController {
return [param('id').exists().isNumeric().toInt()]; return [param('id').exists().isNumeric().toInt()];
} }
/**
* Sales invoices list validation schema.
*/
static get saleInvoiceListValidationSchema() { static get saleInvoiceListValidationSchema() {
return [ return [
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
query('column_sort_by').optional(), query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
]; ];
} }
@@ -145,6 +151,7 @@ export default class SaleInvoicesController {
} }
/** /**
*
* Validate whether sale invoice number unqiue on the storage. * Validate whether sale invoice number unqiue on the storage.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
@@ -266,8 +273,13 @@ export default class SaleInvoicesController {
*/ */
static async editSaleInvoice(req, res) { static async editSaleInvoice(req, res) {
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const saleInvoice = { ...req.body }; const saleInvoice = {
...req.body,
entries: req.body.entries.map((entry) => ({
...entry,
amount: ItemEntry.calcAmount(entry),
})),
};
// Update the given sale invoice details. // Update the given sale invoice details.
await SaleInvoiceService.editSaleInvoice(saleInvoiceId, saleInvoice); await SaleInvoiceService.editSaleInvoice(saleInvoiceId, saleInvoice);
@@ -311,6 +323,8 @@ export default class SaleInvoicesController {
const filter = { const filter = {
filter_roles: [], filter_roles: [],
sort_order: 'asc', sort_order: 'asc',
page: 1,
page_size: 10,
...req.query, ...req.query,
}; };
if (filter.stringified_filter_roles) { if (filter.stringified_filter_roles) {

View File

@@ -6,7 +6,7 @@ import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import CustomersService from '@/services/Customers/CustomersService'; import CustomersService from '@/services/Customers/CustomersService';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
import ItemsService from '@/services/Items/ItemsService'; import ItemsService from '@/services/Items/ItemsService';
import SaleReceiptService from '@/services/Sales/SalesReceipt'; import SaleReceiptService from '@/services/Sales/SalesReceipts';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder'; import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import DynamicListing from '@/services/DynamicListing/DynamicListing'; import DynamicListing from '@/services/DynamicListing/DynamicListing';
import { import {
@@ -51,7 +51,7 @@ export default class SalesReceiptsController {
); );
router.get( router.get(
'/', '/',
this.listingSalesReceipts, this.listSalesReceiptsValidationSchema,
validateMiddleware, validateMiddleware,
asyncMiddleware(this.listingSalesReceipts) asyncMiddleware(this.listingSalesReceipts)
); );
@@ -103,6 +103,8 @@ export default class SalesReceiptsController {
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
query('column_sort_by').optional(), query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
]; ];
} }
@@ -272,6 +274,7 @@ export default class SalesReceiptsController {
sort_order: 'asc', sort_order: 'asc',
page: 1, page: 1,
page_size: 10, page_size: 10,
...req.query,
}; };
if (filter.stringified_filter_roles) { if (filter.stringified_filter_roles) {
filter.filter_roles = JSON.parse(filter.stringified_filter_roles); filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
@@ -312,14 +315,17 @@ export default class SalesReceiptsController {
const salesReceipts = await SaleReceipt.query().onBuild((builder) => { const salesReceipts = await SaleReceipt.query().onBuild((builder) => {
builder.withGraphFetched('entries'); builder.withGraphFetched('entries');
dynamicListing.buildQuery()(builder); dynamicListing.buildQuery()(builder);
return builder;
}).pagination(filter.page - 1, filter.page_size); }).pagination(filter.page - 1, filter.page_size);
return res.status(200).send({ return res.status(200).send({
sales_receipts: salesReceipts, sales_receipts: {
...(viewMeta ? { ...salesReceipts,
customViewId: viewMeta.id, ...(viewMeta ? {
} : {}), view_meta: {
customViewId: viewMeta.id,
}
} : {}),
},
}); });
} }
}; };

View File

@@ -1,6 +1,6 @@
import express from 'express'; import express from 'express';
import SalesEstimates from './SalesEstimates'; import SalesEstimates from './SalesEstimates';
import SalesReceipts from './SalesReceipt'; import SalesReceipts from './SalesReceipts';
import SalesInvoices from './SalesInvoices' import SalesInvoices from './SalesInvoices'
import PaymentReceives from './PaymentReceives'; import PaymentReceives from './PaymentReceives';

View File

@@ -1,10 +0,0 @@
import express from 'express';
export default {
router() {
const router = express.Router();
return router;
},
};

View File

@@ -3,36 +3,26 @@ import express from 'express';
import Authentication from '@/http/controllers/Authentication'; import Authentication from '@/http/controllers/Authentication';
import InviteUsers from '@/http/controllers/InviteUsers'; import InviteUsers from '@/http/controllers/InviteUsers';
import Users from '@/http/controllers/Users'; import Users from '@/http/controllers/Users';
// import Roles from '@/http/controllers/Roles';
import Items from '@/http/controllers/Items'; import Items from '@/http/controllers/Items';
import ItemCategories from '@/http/controllers/ItemCategories'; import ItemCategories from '@/http/controllers/ItemCategories';
import Accounts from '@/http/controllers/Accounts'; import Accounts from '@/http/controllers/Accounts';
import AccountTypes from '@/http/controllers/AccountTypes'; import AccountTypes from '@/http/controllers/AccountTypes';
// import AccountOpeningBalance from '@/http/controllers/AccountOpeningBalance';
import Views from '@/http/controllers/Views'; import Views from '@/http/controllers/Views';
// import CustomFields from '@/http/controllers/Fields';
import Accounting from '@/http/controllers/Accounting'; import Accounting from '@/http/controllers/Accounting';
import FinancialStatements from '@/http/controllers/FinancialStatements'; import FinancialStatements from '@/http/controllers/FinancialStatements';
import Expenses from '@/http/controllers/Expenses'; import Expenses from '@/http/controllers/Expenses';
import Options from '@/http/controllers/Options'; import Options from '@/http/controllers/Options';
// import Budget from '@/http/controllers/Budget';
// import BudgetReports from '@/http/controllers/BudgetReports';
import Currencies from '@/http/controllers/Currencies'; import Currencies from '@/http/controllers/Currencies';
import Customers from '@/http/controllers/Customers'; import Customers from '@/http/controllers/Customers';
import Vendors from '@/http/controllers/Vendors'; import Vendors from '@/http/controllers/Vendors';
import Sales from '@/http/controllers/Sales' import Sales from '@/http/controllers/Sales'
// import Suppliers from '@/http/controllers/Suppliers';
import Purchases from '@/http/controllers/Purchases'; import Purchases from '@/http/controllers/Purchases';
// import CurrencyAdjustment from './controllers/CurrencyAdjustment';
import Resources from './controllers/Resources'; import Resources from './controllers/Resources';
import ExchangeRates from '@/http/controllers/ExchangeRates'; import ExchangeRates from '@/http/controllers/ExchangeRates';
// import SalesReports from '@/http/controllers/SalesReports';
// import PurchasesReports from '@/http/controllers/PurchasesReports';
import Media from '@/http/controllers/Media'; import Media from '@/http/controllers/Media';
import JWTAuth from '@/http/middleware/jwtAuth'; import JWTAuth from '@/http/middleware/jwtAuth';
import TenancyMiddleware from '@/http/middleware/TenancyMiddleware'; import TenancyMiddleware from '@/http/middleware/TenancyMiddleware';
export default (app) => { export default (app) => {
// app.use('/api/oauth2', OAuth2.router()); // app.use('/api/oauth2', OAuth2.router());
app.use('/api/auth', Authentication.router()); app.use('/api/auth', Authentication.router());
@@ -45,31 +35,22 @@ export default (app) => {
dashboard.use('/api/currencies', Currencies.router()); dashboard.use('/api/currencies', Currencies.router());
dashboard.use('/api/users', Users.router()); dashboard.use('/api/users', Users.router());
// app.use('/api/roles', Roles.router());
dashboard.use('/api/accounts', Accounts.router()); dashboard.use('/api/accounts', Accounts.router());
dashboard.use('/api/account_types', AccountTypes.router()); dashboard.use('/api/account_types', AccountTypes.router());
dashboard.use('/api/accounting', Accounting.router()); dashboard.use('/api/accounting', Accounting.router());
// app.use('/api/accounts_opening_balances', AccountOpeningBalance.router());
dashboard.use('/api/views', Views.router()); dashboard.use('/api/views', Views.router());
// app.use('/api/fields', CustomFields.router());
dashboard.use('/api/items', Items.router()); dashboard.use('/api/items', Items.router());
dashboard.use('/api/item_categories', ItemCategories.router()); dashboard.use('/api/item_categories', ItemCategories.router());
dashboard.use('/api/expenses', Expenses.router()); dashboard.use('/api/expenses', Expenses.router());
dashboard.use('/api/financial_statements', FinancialStatements.router()); dashboard.use('/api/financial_statements', FinancialStatements.router());
dashboard.use('/api/options', Options.router()); dashboard.use('/api/options', Options.router());
dashboard.use('/api/sales', Sales.router()); dashboard.use('/api/sales', Sales.router());
// app.use('/api/budget_reports', BudgetReports.router());
dashboard.use('/api/customers', Customers.router()); dashboard.use('/api/customers', Customers.router());
dashboard.use('/api/vendors', Vendors.router()); dashboard.use('/api/vendors', Vendors.router());
dashboard.use('/api/purchases', Purchases.router()); dashboard.use('/api/purchases', Purchases.router());
// app.use('/api/suppliers', Suppliers.router());
// app.use('/api/budget', Budget.router());
dashboard.use('/api/resources', Resources.router()); dashboard.use('/api/resources', Resources.router());
dashboard.use('/api/exchange_rates', ExchangeRates.router()); dashboard.use('/api/exchange_rates', ExchangeRates.router());
dashboard.use('/api/media', Media.router()); dashboard.use('/api/media', Media.router());
app.use('/', dashboard); app.use('/', dashboard);
// app.use('/api/currency_adjustment', CurrencyAdjustment.router());
// app.use('/api/reports/sales', SalesReports.router());
// app.use('/api/reports/purchases', PurchasesReports.router());
}; };

View File

@@ -1,5 +1,6 @@
import { Model, mixin } from 'objection'; import { Model, mixin } from 'objection';
import moment from 'moment'; import moment from 'moment';
import { difference } from 'lodash';
import TenantModel from '@/models/TenantModel'; import TenantModel from '@/models/TenantModel';
import CachableQueryBuilder from '@/lib/Cachable/CachableQueryBuilder'; import CachableQueryBuilder from '@/lib/Cachable/CachableQueryBuilder';
import CachableModel from '@/lib/Cachable/CachableModel'; import CachableModel from '@/lib/Cachable/CachableModel';
@@ -29,8 +30,25 @@ export default class Bill extends mixin(TenantModel, [CachableModel]) {
/** /**
* Due amount of the given. * Due amount of the given.
* @return {number}
*/ */
get dueAmount() { get dueAmount() {
return Math.max(this.balance - this.paymentAmount, 0); return this.amount - this.paymentAmount;
}
/**
* Retrieve the not found bills ids as array.
* @param {Array} billsIds
* @return {Array}
*/
static async getNotFoundBills(billsIds) {
const storedBills = await this.tenant().query().whereIn('id', billsIds);
const storedBillsIds = storedBills.map((t) => t.id);
const notFoundBillsIds = difference(
billsIds,
storedBillsIds,
);
return notFoundBillsIds;
} }
} }

View File

@@ -39,10 +39,11 @@ export default class BillPayment extends mixin(TenantModel, [CachableModel]) {
*/ */
static get relationMappings() { static get relationMappings() {
const BillPaymentEntry = require('@/models/BillPaymentEntry'); const BillPaymentEntry = require('@/models/BillPaymentEntry');
const Vendor = require('@/models/Vendor');
return { return {
/** /**
* Account model may belongs to account type. *
*/ */
entries: { entries: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
@@ -52,6 +53,17 @@ export default class BillPayment extends mixin(TenantModel, [CachableModel]) {
to: 'bills_payments_entries.billPaymentId', to: 'bills_payments_entries.billPaymentId',
}, },
}, },
/**
*
*/
vendor: {
relation: Model.BelongsToOneRelation,
modelClass: this.relationBindKnex(Vendor.default),
join: {
from: 'bills_payments.vendorId',
to: 'vendors.id',
},
}
}; };
} }
} }

View File

@@ -3,17 +3,17 @@ import { Customer } from '@/models';
export default class CustomerRepository { export default class CustomerRepository {
static changeDiffBalance(customerId, oldCustomerId, amount, oldAmount) { static changeDiffBalance(customerId, oldCustomerId, amount, oldAmount) {
const diffAmount = (amount - oldAmount) * -1; const diffAmount = amount - oldAmount;
const asyncOpers = []; const asyncOpers = [];
if (customerId != oldCustomerId) { if (customerId != oldCustomerId) {
const oldCustomerOper = Customer.changeBalance( const oldCustomerOper = Customer.changeBalance(
oldCustomerId, oldCustomerId,
oldAmount (oldAmount * -1)
); );
const customerOper = Customer.changeBalance( const customerOper = Customer.changeBalance(
customerId, customerId,
(amount + diffAmount) * -1 amount,
); );
asyncOpers.push(customerOper); asyncOpers.push(customerOper);
asyncOpers.push(oldCustomerOper); asyncOpers.push(oldCustomerOper);

View File

@@ -0,0 +1,7 @@
export default class SaleInvoiceRepository {
}

View File

@@ -1,8 +1,6 @@
import express from 'express'; import express from 'express';
import { omit } from 'lodash'; import { omit, sumBy } from 'lodash';
import { check, query, validationResult, param } from 'express-validator';
import { BillPayment, BillPaymentEntry, Vendor } from '@/models'; import { BillPayment, BillPaymentEntry, Vendor } from '@/models';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import ServiceItemsEntries from '../Sales/ServiceItemsEntries'; import ServiceItemsEntries from '../Sales/ServiceItemsEntries';
import AccountsService from '../Accounts/AccountsService'; import AccountsService from '../Accounts/AccountsService';
import JournalPoster from '../Accounting/JournalPoster'; import JournalPoster from '../Accounting/JournalPoster';

View File

@@ -1,19 +1,20 @@
import { omit, sumBy, difference } from 'lodash'; import { omit, sumBy } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { import {
Account,
Bill, Bill,
Vendor, Vendor,
InventoryTransaction,
ItemEntry, ItemEntry,
Item, Item,
Account, InventoryTransaction,
AccountTransaction,
} from '@/models'; } from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry'; import JournalEntry from '@/services/Accounting/JournalEntry';
import AccountsService from '@/services/Accounts/AccountsService'; import AccountsService from '@/services/Accounts/AccountsService';
import JournalPosterService from '@/services/Sales/JournalPosterService'; import JournalPosterService from '@/services/Sales/JournalPosterService';
import InventoryService from '../Inventory/Inventory'; import InventoryService from '@/services/Inventory/Inventory';
import { AccountTransaction } from '../../models'; import HasItemsEntries from '@/services/Sales/HasItemsEntries';
/** /**
* Vendor bills services. * Vendor bills services.
@@ -65,56 +66,6 @@ export default class BillsService {
return storedBill; 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. * Edits details of the given bill id with associated entries.
@@ -150,8 +101,9 @@ export default class BillsService {
.where('reference_type', 'Bill'); .where('reference_type', 'Bill');
// Patch the bill entries. // Patch the bill entries.
const patchEntriesOper = this.patchItemsEntries(bill.entries, storedEntries, 'Bill', billId); const patchEntriesOper = HasItemsEntries.patchItemsEntries(
bill.entries, storedEntries, 'Bill', billId,
);
// Record bill journal transactions. // Record bill journal transactions.
const recordTransactionsOper = this.recordJournalTransactions(bill, billId); const recordTransactionsOper = this.recordJournalTransactions(bill, billId);

View File

@@ -0,0 +1,56 @@
import { difference, omit } from 'lodash';
import { ItemEntry } from '@/models';
export default class HasItemEntries {
/**
* Patch items entries to the storage.
*
* @param {Array} newEntries -
* @param {Array} oldEntries -
* @param {String} referenceType -
* @param {String|Number} referenceId -
*
* @return {Promise}
*/
static async patchItemsEntries(newEntries: Array<any>, oldEntries: Array<any>, referenceType: string, referenceId: string|number) {
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 excludeAttrs = ['id', 'amount'];
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, excludeAttrs),
});
opers.push(updateOper);
});
entriesHasNoIds.forEach((entry) => {
const insertOper = ItemEntry.tenant()
.query()
.insert({
reference_id: referenceId,
reference_type: referenceType,
...omit(entry, excludeAttrs),
});
opers.push(insertOper);
});
return Promise.all([...opers]);
}
}

View File

@@ -1,5 +1,5 @@
import { omit, sumBy, mapValues, groupBy, chain } from 'lodash'; import { omit, sumBy, chain } from 'lodash';
import moment, { updateLocale } from 'moment'; import moment from 'moment';
import { import {
AccountTransaction, AccountTransaction,
PaymentReceive, PaymentReceive,
@@ -16,14 +16,18 @@ import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
import PaymentReceiveEntryRepository from '@/repositories/PaymentReceiveEntryRepository'; import PaymentReceiveEntryRepository from '@/repositories/PaymentReceiveEntryRepository';
import CustomerRepository from '@/repositories/CustomerRepository'; import CustomerRepository from '@/repositories/CustomerRepository';
export default class PaymentReceiveService extends JournalPosterService { /**
* Payment receive service.
* @service
*/
export default class PaymentReceiveService {
/** /**
* Creates a new payment receive and store it to the storage * Creates a new payment receive and store it to the storage
* with associated invoices payment and journal transactions. * with associated invoices payment and journal transactions.
* @async * @async
* @param {IPaymentReceive} paymentReceive * @param {IPaymentReceive} paymentReceive
*/ */
static async createPaymentReceive(paymentReceive) { static async createPaymentReceive(paymentReceive: any) {
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount'); const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
const storedPaymentReceive = await PaymentReceive.tenant() const storedPaymentReceive = await PaymentReceive.tenant()
.query() .query()
@@ -31,9 +35,9 @@ export default class PaymentReceiveService extends JournalPosterService {
amount: paymentAmount, amount: paymentAmount,
...omit(paymentReceive, ['entries']), ...omit(paymentReceive, ['entries']),
}); });
const storeOpers = []; const storeOpers: Array<any> = [];
paymentReceive.entries.forEach((entry) => { paymentReceive.entries.forEach((entry: any) => {
const oper = PaymentReceiveEntry.tenant() const oper = PaymentReceiveEntry.tenant()
.query() .query()
.insert({ .insert({
@@ -51,12 +55,13 @@ export default class PaymentReceiveService extends JournalPosterService {
}); });
const customerIncrementOper = Customer.decrementBalance( const customerIncrementOper = Customer.decrementBalance(
paymentReceive.customer_id, paymentReceive.customer_id,
paymentAmount paymentAmount,
); );
// Records the sale invoice journal transactions.
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries({ const recordJournalTransactions = this.recordPaymentReceiveJournalEntries({
id: storedPaymentReceive.id, id: storedPaymentReceive.id,
...paymentReceive, ...paymentReceive,
}); });
await Promise.all([ await Promise.all([
...storeOpers, ...storeOpers,
customerIncrementOper, customerIncrementOper,
@@ -81,9 +86,9 @@ export default class PaymentReceiveService extends JournalPosterService {
* @param {IPaymentReceive} oldPaymentReceive * @param {IPaymentReceive} oldPaymentReceive
*/ */
static async editPaymentReceive( static async editPaymentReceive(
paymentReceiveId, paymentReceiveId: number,
paymentReceive, paymentReceive: any,
oldPaymentReceive oldPaymentReceive: any
) { ) {
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount'); const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
// Update the payment receive transaction. // Update the payment receive transaction.
@@ -129,14 +134,14 @@ export default class PaymentReceiveService extends JournalPosterService {
id: oldPaymentReceive.id, id: oldPaymentReceive.id,
...paymentReceive, ...paymentReceive,
}, },
paymentReceiveId paymentReceiveId,
); );
// Increment/decrement the customer balance after calc the diff // Increment/decrement the customer balance after calc the diff
// between old and new value. // between old and new value.
const changeCustomerBalance = CustomerRepository.changeDiffBalance( const changeCustomerBalance = CustomerRepository.changeDiffBalance(
paymentReceive.customer_id, paymentReceive.customer_id,
oldPaymentReceive.customerId, oldPaymentReceive.customerId,
paymentAmount, paymentAmount * -1,
oldPaymentReceive.amount, oldPaymentReceive.amount,
); );
// Change the difference between the old and new invoice payment amount. // Change the difference between the old and new invoice payment amount.
@@ -164,8 +169,9 @@ export default class PaymentReceiveService extends JournalPosterService {
* - Revert the payment amount of the associated invoices. * - Revert the payment amount of the associated invoices.
* @async * @async
* @param {Integer} paymentReceiveId * @param {Integer} paymentReceiveId
* @param {IPaymentReceive} paymentReceive
*/ */
static async deletePaymentReceive(paymentReceiveId, paymentReceive) { static async deletePaymentReceive(paymentReceiveId: number, paymentReceive: any) {
// Deletes the payment receive transaction. // Deletes the payment receive transaction.
await PaymentReceive.tenant() await PaymentReceive.tenant()
.query() .query()
@@ -179,7 +185,7 @@ export default class PaymentReceiveService extends JournalPosterService {
.delete(); .delete();
// Delete all associated journal transactions to payment receive transaction. // Delete all associated journal transactions to payment receive transaction.
const deleteTransactionsOper = this.deleteJournalTransactions( const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
paymentReceiveId, paymentReceiveId,
'PaymentReceive' 'PaymentReceive'
); );
@@ -190,7 +196,7 @@ export default class PaymentReceiveService extends JournalPosterService {
); );
// Revert the invoices payments amount. // Revert the invoices payments amount.
const revertInvoicesPaymentAmount = this.revertInvoicePaymentAmount( const revertInvoicesPaymentAmount = this.revertInvoicePaymentAmount(
paymentReceive.entries.map((entry) => ({ paymentReceive.entries.map((entry: any) => ({
invoiceId: entry.invoiceId, invoiceId: entry.invoiceId,
revertAmount: entry.paymentAmount, revertAmount: entry.paymentAmount,
})) }))
@@ -206,7 +212,7 @@ export default class PaymentReceiveService extends JournalPosterService {
* Retrieve the payment receive details of the given id. * Retrieve the payment receive details of the given id.
* @param {Integer} paymentReceiveId * @param {Integer} paymentReceiveId
*/ */
static async getPaymentReceive(paymentReceiveId) { static async getPaymentReceive(paymentReceiveId: number) {
const paymentReceive = await PaymentReceive.tenant() const paymentReceive = await PaymentReceive.tenant()
.query() .query()
.where('id', paymentReceiveId) .where('id', paymentReceiveId)
@@ -219,7 +225,7 @@ export default class PaymentReceiveService extends JournalPosterService {
* Retrieve the payment receive details with associated invoices. * Retrieve the payment receive details with associated invoices.
* @param {Integer} paymentReceiveId * @param {Integer} paymentReceiveId
*/ */
static async getPaymentReceiveWithInvoices(paymentReceiveId) { static async getPaymentReceiveWithInvoices(paymentReceiveId: number) {
return PaymentReceive.tenant() return PaymentReceive.tenant()
.query() .query()
.where('id', paymentReceiveId) .where('id', paymentReceiveId)
@@ -231,7 +237,7 @@ export default class PaymentReceiveService extends JournalPosterService {
* Detarmines whether the payment receive exists on the storage. * Detarmines whether the payment receive exists on the storage.
* @param {Integer} paymentReceiveId * @param {Integer} paymentReceiveId
*/ */
static async isPaymentReceiveExists(paymentReceiveId) { static async isPaymentReceiveExists(paymentReceiveId: number) {
const paymentReceives = await PaymentReceive.tenant() const paymentReceives = await PaymentReceive.tenant()
.query() .query()
.where('id', paymentReceiveId); .where('id', paymentReceiveId);
@@ -245,8 +251,8 @@ export default class PaymentReceiveService extends JournalPosterService {
* @param {Integer} paymentReceiveId - Payment receive id. * @param {Integer} paymentReceiveId - Payment receive id.
*/ */
static async isPaymentReceiveNoExists( static async isPaymentReceiveNoExists(
paymentReceiveNumber, paymentReceiveNumber: string|number,
paymentReceiveId paymentReceiveId: number
) { ) {
const paymentReceives = await PaymentReceive.tenant() const paymentReceives = await PaymentReceive.tenant()
.query() .query()
@@ -261,17 +267,22 @@ export default class PaymentReceiveService extends JournalPosterService {
/** /**
* Records payment receive journal transactions. * Records payment receive journal transactions.
*
* Invoice payment journals.
* --------
* - Account receivable -> Debit
* - Payment account [current asset] -> Credit
*
* @async * @async
* @param {IPaymentReceive} paymentReceive * @param {IPaymentReceive} paymentReceive
* @param {Number} paymentReceiveId
*/ */
static async recordPaymentReceiveJournalEntries( static async recordPaymentReceiveJournalEntries(
paymentReceive, paymentReceive: any,
paymentReceiveId paymentReceiveId?: number
) { ) {
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount'); const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
const formattedDate = moment(paymentReceive.payment_date).format( const formattedDate = moment(paymentReceive.payment_date).format('YYYY-MM-DD');
'YYYY-MM-DD'
);
const receivableAccount = await AccountsService.getAccountByType( const receivableAccount = await AccountsService.getAccountByType(
'accounts_receivable' 'accounts_receivable'
); );
@@ -320,8 +331,8 @@ export default class PaymentReceiveService extends JournalPosterService {
* Revert the payment amount of the given invoices ids. * Revert the payment amount of the given invoices ids.
* @param {Array} revertInvoices * @param {Array} revertInvoices
*/ */
static async revertInvoicePaymentAmount(revertInvoices) { static async revertInvoicePaymentAmount(revertInvoices: any[]) {
const opers = []; const opers: Promise<T>[] = [];
revertInvoices.forEach((revertInvoice) => { revertInvoices.forEach((revertInvoice) => {
const { revertAmount, invoiceId } = revertInvoice; const { revertAmount, invoiceId } = revertInvoice;
@@ -341,10 +352,10 @@ export default class PaymentReceiveService extends JournalPosterService {
* @return * @return
*/ */
static async saveChangeInvoicePaymentAmount( static async saveChangeInvoicePaymentAmount(
paymentReceiveEntries, paymentReceiveEntries: [],
newPaymentReceiveEntries newPaymentReceiveEntries: [],
) { ) {
const opers = []; const opers: Promise<T>[] = [];
const newEntriesTable = chain(newPaymentReceiveEntries) const newEntriesTable = chain(newPaymentReceiveEntries)
.groupBy('invoice_id') .groupBy('invoice_id')
.mapValues((group) => (sumBy(group, 'payment_amount') || 0) * -1) .mapValues((group) => (sumBy(group, 'payment_amount') || 0) * -1)
@@ -359,7 +370,7 @@ export default class PaymentReceiveService extends JournalPosterService {
.values() .values()
.value(); .value();
diffEntries.forEach((diffEntry) => { diffEntries.forEach((diffEntry: any) => {
const oper = SaleInvoice.changePaymentAmount( const oper = SaleInvoice.changePaymentAmount(
diffEntry.invoice_id, diffEntry.invoice_id,
diffEntry.payment_amount diffEntry.payment_amount

View File

@@ -1,15 +1,15 @@
import { omit, difference, sumBy } from 'lodash'; import { omit, difference, sumBy, mixin } from 'lodash';
import { SaleEstimate, ItemEntry } from '@/models'; import { SaleEstimate, ItemEntry } from '@/models';
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries'; import HasItemsEntries from '@/services/Sales/HasItemsEntries';
export default class SaleEstimateService extends ServiceItemsEntries { export default class SaleEstimateService {
/** /**
* Creates a new estimate with associated entries. * Creates a new estimate with associated entries.
* @async * @async
* @param {IEstimate} estimate * @param {IEstimate} estimate
* @return {void} * @return {void}
*/ */
static async createEstimate(estimate) { static async createEstimate(estimate: any) {
const amount = sumBy(estimate.entries, 'amount'); const amount = sumBy(estimate.entries, 'amount');
const storedEstimate = await SaleEstimate.tenant() const storedEstimate = await SaleEstimate.tenant()
.query() .query()
@@ -17,15 +17,15 @@ export default class SaleEstimateService extends ServiceItemsEntries {
amount, amount,
...omit(estimate, ['entries']), ...omit(estimate, ['entries']),
}); });
const storeEstimateEntriesOpers = []; const storeEstimateEntriesOpers: any[] = [];
estimate.entries.forEach((entry) => { estimate.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant() const oper = ItemEntry.tenant()
.query() .query()
.insert({ .insert({
reference_type: 'SaleEstimate', reference_type: 'SaleEstimate',
reference_id: storedEstimate.id, reference_id: storedEstimate.id,
...omit(entry, ['total', 'amount']), ...omit(entry, ['total', 'amount', 'id']),
}); });
storeEstimateEntriesOpers.push(oper); storeEstimateEntriesOpers.push(oper);
}); });
@@ -40,7 +40,7 @@ export default class SaleEstimateService extends ServiceItemsEntries {
* @param {IEstimate} estimateId * @param {IEstimate} estimateId
* @return {void} * @return {void}
*/ */
static async deleteEstimate(estimateId) { static async deleteEstimate(estimateId: number) {
await ItemEntry.tenant() await ItemEntry.tenant()
.query() .query()
.where('reference_id', estimateId) .where('reference_id', estimateId)
@@ -56,7 +56,7 @@ export default class SaleEstimateService extends ServiceItemsEntries {
* @param {IEstimate} estimate * @param {IEstimate} estimate
* @return {void} * @return {void}
*/ */
static async editEstimate(estimateId, estimate) { static async editEstimate(estimateId: number, estimate: any) {
const amount = sumBy(estimate.entries, 'amount'); const amount = sumBy(estimate.entries, 'amount');
const updatedEstimate = await SaleEstimate.tenant() const updatedEstimate = await SaleEstimate.tenant()
.query() .query()
@@ -69,45 +69,12 @@ export default class SaleEstimateService extends ServiceItemsEntries {
.where('reference_id', estimateId) .where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate'); .where('reference_type', 'SaleEstimate');
const opers = []; const patchItemsEntries = HasItemsEntries.patchItemsEntries(
const entriesHasID = estimate.entries.filter((entry) => entry.id); estimate.entries, storedEstimateEntries, 'SaleEstimate', estimateId
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(
storedEntriesIds,
formEstimateEntriesIds,
); );
// Deletes the given sale estimate entries ids. return Promise.all([
if (entriesIdsShouldBeDeleted.length > 0) { patchItemsEntries,
const oper = ItemEntry.tenant() ]);
.query()
.whereIn('id', entriesIdsShouldBeDeleted)
.delete();
opers.push(oper);
}
// 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);
});
return Promise.all([...opers]);
} }
/** /**
@@ -116,7 +83,7 @@ export default class SaleEstimateService extends ServiceItemsEntries {
* @param {Numeric} estimateId * @param {Numeric} estimateId
* @return {Boolean} * @return {Boolean}
*/ */
static async isEstimateExists(estimateId) { static async isEstimateExists(estimateId: number) {
const foundEstimate = await SaleEstimate.tenant() const foundEstimate = await SaleEstimate.tenant()
.query() .query()
.where('id', estimateId); .where('id', estimateId);
@@ -129,7 +96,7 @@ export default class SaleEstimateService extends ServiceItemsEntries {
* @param {Numeric} estimateId * @param {Numeric} estimateId
* @param {IEstimate} estimate * @param {IEstimate} estimate
*/ */
static async isEstimateEntriesIDsExists(estimateId, estimate) { static async isEstimateEntriesIDsExists(estimateId: number, estimate: any) {
const estimateEntriesIds = estimate.entries const estimateEntriesIds = estimate.entries
.filter((e) => e.id) .filter((e) => e.id)
.map((e) => e.id); .map((e) => e.id);
@@ -140,7 +107,7 @@ export default class SaleEstimateService extends ServiceItemsEntries {
.where('reference_id', estimateId) .where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate'); .where('reference_type', 'SaleEstimate');
const storedEstimateEntriesIds = estimateEntries.map((e) => e.id); const storedEstimateEntriesIds = estimateEntries.map((e: any) => e.id);
const notFoundEntriesIDs = difference( const notFoundEntriesIDs = difference(
estimateEntriesIds, estimateEntriesIds,
storedEstimateEntriesIds storedEstimateEntriesIds
@@ -153,7 +120,7 @@ export default class SaleEstimateService extends ServiceItemsEntries {
* @param {Integer} estimateId * @param {Integer} estimateId
* @return {IEstimate} * @return {IEstimate}
*/ */
static async getEstimate(estimateId) { static async getEstimate(estimateId: number) {
const estimate = await SaleEstimate.tenant() const estimate = await SaleEstimate.tenant()
.query() .query()
.where('id', estimateId) .where('id', estimateId)
@@ -166,7 +133,7 @@ export default class SaleEstimateService extends ServiceItemsEntries {
* Retrieve the estimate details with associated entries. * Retrieve the estimate details with associated entries.
* @param {Integer} estimateId * @param {Integer} estimateId
*/ */
static async getEstimateWithEntries(estimateId) { static async getEstimateWithEntries(estimateId: number) {
const estimate = await SaleEstimate.tenant() const estimate = await SaleEstimate.tenant()
.query() .query()
.where('id', estimateId) .where('id', estimateId)
@@ -178,14 +145,14 @@ export default class SaleEstimateService extends ServiceItemsEntries {
/** /**
* Detarmines the estimate number uniqness. * Detarmines the estimate number uniqness.
* @param {Integer} estimateNumber * @param {String} estimateNumber
* @param {Integer} excludeEstimateId * @param {Integer} excludeEstimateId
* @return {Boolean} * @return {Boolean}
*/ */
static async isEstimateNumberUnique(estimateNumber, excludeEstimateId) { static async isEstimateNumberUnique(estimateNumber: string, excludeEstimateId: number) {
const foundEstimates = await SaleEstimate.tenant() const foundEstimates = await SaleEstimate.tenant()
.query() .query()
.onBuild((query) => { .onBuild((query: any) => {
query.where('estimate_number', estimateNumber); query.where('estimate_number', estimateNumber);
if (excludeEstimateId) { if (excludeEstimateId) {

View File

@@ -3,21 +3,26 @@ import {
SaleInvoice, SaleInvoice,
AccountTransaction, AccountTransaction,
Account, Account,
Item,
ItemEntry, ItemEntry,
Customer, Customer,
} from '@/models'; } from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries'; import HasItemsEntries from '@/services/Sales/HasItemsEntries';
import CustomerRepository from '@/repositories/CustomerRepository';
export default class SaleInvoicesService extends ServiceItemsEntries { /**
* Sales invoices service
* @service
*/
export default class SaleInvoicesService {
/** /**
* Creates a new sale invoices and store it to the storage * Creates a new sale invoices and store it to the storage
* with associated to entries and journal transactions. * with associated to entries and journal transactions.
* @async
* @param {ISaleInvoice} * @param {ISaleInvoice}
* @return {ISaleInvoice} * @return {ISaleInvoice}
*/ */
static async createSaleInvoice(saleInvoice) { static async createSaleInvoice(saleInvoice: any) {
const balance = sumBy(saleInvoice.entries, 'amount'); const balance = sumBy(saleInvoice.entries, 'amount');
const storedInvoice = await SaleInvoice.tenant() const storedInvoice = await SaleInvoice.tenant()
.query() .query()
@@ -26,9 +31,9 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
balance, balance,
payment_amount: 0, payment_amount: 0,
}); });
const opers = []; const opers: Array<any> = [];
saleInvoice.entries.forEach((entry) => { saleInvoice.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant() const oper = ItemEntry.tenant()
.query() .query()
.insert({ .insert({
@@ -47,79 +52,56 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
} }
/** /**
* Records the journal entries of sale invoice. * Edit the given sale invoice.
* @param {ISaleInvoice} saleInvoice * @async
* @return {void} * @param {Number} saleInvoiceId -
* @param {ISaleInvoice} saleInvoice -
*/ */
async recordJournalEntries(saleInvoice) { static async editSaleInvoice(saleInvoiceId: number, saleInvoice: any) {
const accountsDepGraph = await Account.depGraph().query().remember(); const balance = sumBy(saleInvoice.entries, 'amount');
const journal = new JournalPoster(accountsDepGraph); const oldSaleInvoice = await SaleInvoice.tenant().query()
const receivableTotal = sumBy(saleInvoice.entries, 'total'); .where('id', saleInvoiceId)
.first();
const receivableAccount = await Account.tenant().query(); const updatedSaleInvoices = await SaleInvoice.tenant()
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() .query()
.whereIn('id', saleItemsIds); .where('id', saleInvoiceId)
.update({
const commonJournalMeta = { balance,
debit: 0, ...omit(saleInvoice, ['entries']),
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: '',
}); });
// Fetches the sale invoice items entries.
const storedEntries = await ItemEntry.tenant()
.query()
.where('reference_id', saleInvoiceId)
.where('reference_type', 'SaleInvoice');
if (item.type === 'inventory') { // Patch update the sale invoice items entries.
const inventoryCredit = JournalEntry({ const patchItemsEntriesOper = HasItemsEntries.patchItemsEntries(
...commonJournalMeta, saleInvoice.entries, storedEntries, 'SaleInvoice', saleInvoiceId,
credit: entry.total, );
account: item.inventoryAccountId, // Changes the diff customer balance between old and new amount.
accountNormal: 'credit', const changeCustomerBalanceOper = CustomerRepository.changeDiffBalance(
note: '', saleInvoice.customer_id,
}); oldSaleInvoice.customerId,
const costEntry = JournalEntry({ balance,
...commonJournalMeta, oldSaleInvoice.balance,
debit: entry.total, );
account: item.costAccountId,
accountNormal: 'debit',
note: '',
});
journal.debit(costEntry);
}
journal.credit(incomeEntry);
});
await Promise.all([ await Promise.all([
journalPoster.saveEntries(), patchItemsEntriesOper,
journalPoster.saveBalance(), changeCustomerBalanceOper,
]); ]);
} }
/** /**
* Deletes the given sale invoice with associated entries * Deletes the given sale invoice with associated entries
* and journal transactions. * and journal transactions.
* @param {Integer} saleInvoiceId * @async
* @param {Number} saleInvoiceId
*/ */
static async deleteSaleInvoice(saleInvoiceId) { static async deleteSaleInvoice(saleInvoiceId: number) {
const oldSaleInvoice = await SaleInvoice.tenant().query().findById(saleInvoiceId);
await SaleInvoice.tenant().query().where('id', saleInvoiceId).delete(); await SaleInvoice.tenant().query().where('id', saleInvoiceId).delete();
await ItemEntry.tenant() await ItemEntry.tenant()
.query() .query()
@@ -127,6 +109,10 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
.where('reference_type', 'SaleInvoice') .where('reference_type', 'SaleInvoice')
.delete(); .delete();
const revertCustomerBalanceOper = Customer.changeBalance(
oldSaleInvoice.customerId,
oldSaleInvoice.balance * -1,
);
const invoiceTransactions = await AccountTransaction.tenant() const invoiceTransactions = await AccountTransaction.tenant()
.query() .query()
.whereIn('reference_type', ['SaleInvoice']) .whereIn('reference_type', ['SaleInvoice'])
@@ -139,68 +125,29 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
journal.loadEntries(invoiceTransactions); journal.loadEntries(invoiceTransactions);
journal.removeEntries(); journal.removeEntries();
await Promise.all([journal.deleteEntries(), journal.saveBalance()]); await Promise.all([
journal.deleteEntries(),
journal.saveBalance(),
revertCustomerBalanceOper,
]);
} }
/** /**
* Edit the given sale invoice. * Records the journal entries of sale invoice.
* @param {Integer} saleInvoiceId - * @async
* @param {ISaleInvoice} saleInvoice - * @param {ISaleInvoice} saleInvoice
* @return {void}
*/ */
static async editSaleInvoice(saleInvoiceId, saleInvoice) { async recordJournalEntries(saleInvoice: any) {
const updatedSaleInvoices = await SaleInvoice.tenant()
.query()
.where('id', saleInvoiceId)
.update({
...omit(saleInvoice, ['entries']),
});
const opers = [];
const entriesIds = saleInvoice.entries.filter((entry) => entry.id);
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
);
if (entriesIdsShouldDelete.length > 0) {
const updateOper = ItemEntry.tenant()
.query()
.whereIn('id', entriesIdsShouldDelete)
.delete();
opers.push(updateOper);
}
entriesIds.forEach((entry) => {
const updateOper = ItemEntry.tenant()
.query()
.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. * Retrieve sale invoice with associated entries.
* @param {Integer} saleInvoiceId * @async
* @param {Number} saleInvoiceId
*/ */
static async getSaleInvoiceWithEntries(saleInvoiceId) { static async getSaleInvoiceWithEntries(saleInvoiceId: number) {
return SaleInvoice.tenant().query() return SaleInvoice.tenant().query()
.where('id', saleInvoiceId) .where('id', saleInvoiceId)
.withGraphFetched('entries') .withGraphFetched('entries')
@@ -212,7 +159,7 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
* @param {Integer} saleInvoiceId * @param {Integer} saleInvoiceId
* @return {Boolean} * @return {Boolean}
*/ */
static async isSaleInvoiceExists(saleInvoiceId) { static async isSaleInvoiceExists(saleInvoiceId: number) {
const foundSaleInvoice = await SaleInvoice.tenant() const foundSaleInvoice = await SaleInvoice.tenant()
.query() .query()
.where('id', saleInvoiceId); .where('id', saleInvoiceId);
@@ -221,10 +168,12 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
/** /**
* Detarmines the sale invoice number exists on the storage. * Detarmines the sale invoice number exists on the storage.
* @param {Integer} saleInvoiceNumber * @async
* @param {Number|String} saleInvoiceNumber
* @param {Number} saleInvoiceId
* @return {Boolean} * @return {Boolean}
*/ */
static async isSaleInvoiceNumberExists(saleInvoiceNumber, saleInvoiceId) { static async isSaleInvoiceNumberExists(saleInvoiceNumber: string|number, saleInvoiceId: number) {
const foundSaleInvoice = await SaleInvoice.tenant() const foundSaleInvoice = await SaleInvoice.tenant()
.query() .query()
.onBuild((query) => { .onBuild((query) => {
@@ -243,7 +192,7 @@ export default class SaleInvoicesService extends ServiceItemsEntries {
* @param {Array} invoicesIds * @param {Array} invoicesIds
* @return {Array} * @return {Array}
*/ */
static async isInvoicesExist(invoicesIds) { static async isInvoicesExist(invoicesIds: Array<number>) {
const storedInvoices = await SaleInvoice.tenant() const storedInvoices = await SaleInvoice.tenant()
.query() .query()
.onBuild((builder) => { .onBuild((builder) => {

View File

@@ -2,19 +2,20 @@ import { omit, difference, sumBy } from 'lodash';
import { import {
SaleReceipt, SaleReceipt,
Account, Account,
ItemEntry,
} from '@/models'; } from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster'; import JournalPoster from '@/services/Accounting/JournalPoster';
import ItemEntry from '../../models/ItemEntry';
import JournalPosterService from '@/services/Sales/JournalPosterService'; import JournalPosterService from '@/services/Sales/JournalPosterService';
import HasItemEntries from '@/services/Sales/HasItemsEntries';
export default class SalesReceipt extends JournalPosterService { export default class SalesReceipt {
/** /**
* Creates a new sale receipt with associated entries. * Creates a new sale receipt with associated entries.
* @async * @async
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
* @return {Object} * @return {Object}
*/ */
static async createSaleReceipt(saleReceipt) { static async createSaleReceipt(saleReceipt: any) {
const amount = sumBy(saleReceipt.entries, 'amount'); const amount = sumBy(saleReceipt.entries, 'amount');
const storedSaleReceipt = await SaleReceipt.tenant() const storedSaleReceipt = await SaleReceipt.tenant()
.query() .query()
@@ -22,9 +23,9 @@ export default class SalesReceipt extends JournalPosterService {
amount, amount,
...omit(saleReceipt, ['entries']), ...omit(saleReceipt, ['entries']),
}); });
const storeSaleReceiptEntriesOpers = []; const storeSaleReceiptEntriesOpers: Array<any> = [];
saleReceipt.entries.forEach((entry) => { saleReceipt.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant() const oper = ItemEntry.tenant()
.query() .query()
.insert({ .insert({
@@ -43,7 +44,7 @@ export default class SalesReceipt extends JournalPosterService {
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
* @return {Promise} * @return {Promise}
*/ */
static async _recordJournalTransactions(saleReceipt) { static async _recordJournalTransactions(saleReceipt: any) {
const accountsDepGraph = await Account.tenant().depGraph().query(); const accountsDepGraph = await Account.tenant().depGraph().query();
const journalPoster = new JournalPoster(accountsDepGraph); const journalPoster = new JournalPoster(accountsDepGraph);
} }
@@ -54,7 +55,7 @@ export default class SalesReceipt extends JournalPosterService {
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
* @return {void} * @return {void}
*/ */
static async editSaleReceipt(saleReceiptId, saleReceipt) { static async editSaleReceipt(saleReceiptId: number, saleReceipt: any) {
const amount = sumBy(saleReceipt.entries, 'amount'); const amount = sumBy(saleReceipt.entries, 'amount');
const updatedSaleReceipt = await SaleReceipt.tenant() const updatedSaleReceipt = await SaleReceipt.tenant()
.query() .query()
@@ -68,32 +69,11 @@ export default class SalesReceipt extends JournalPosterService {
.where('reference_id', saleReceiptId) .where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt'); .where('reference_type', 'SaleReceipt');
const storedSaleReceiptsIds = storedSaleReceiptEntries.map((e) => e.id); // Patch sale receipt items entries.
const entriesHasID = saleReceipt.entries.filter((entry) => entry.id); const patchItemsEntries = HasItemEntries.patchItemsEntries(
const entriesIds = entriesHasID.map((e) => e.id); saleReceipt.entries, storedSaleReceiptEntries, 'SaleReceipt', saleReceiptId,
const opers = [];
const entriesIdsShouldBeDeleted = difference(
storedSaleReceiptsIds,
entriesIds
); );
if (entriesIdsShouldBeDeleted.length > 0) { return Promise.all([patchItemsEntries]);
const deleteOper = ItemEntry.tenant()
.query()
.whereIn('id', entriesIdsShouldBeDeleted)
.where('reference_type', 'SaleReceipt')
.delete();
opers.push(deleteOper);
}
entriesHasID.forEach((entry) => {
const updateOper = ItemEntry.tenant()
.query()
.patchAndFetchById(entry.id, {
...omit(entry, ['id']),
});
opers.push(updateOper);
});
return Promise.all([...opers]);
} }
/** /**
@@ -101,20 +81,22 @@ export default class SalesReceipt extends JournalPosterService {
* @param {Integer} saleReceiptId * @param {Integer} saleReceiptId
* @return {void} * @return {void}
*/ */
static async deleteSaleReceipt(saleReceiptId) { static async deleteSaleReceipt(saleReceiptId: number) {
await SaleReceipt.tenant().query().where('id', saleReceiptId).delete(); const deleteSaleReceiptOper = SaleReceipt.tenant().query().where('id', saleReceiptId).delete();
await ItemEntry.tenant() const deleteItemsEntriesOper = ItemEntry.tenant()
.query() .query()
.where('reference_id', saleReceiptId) .where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt') .where('reference_type', 'SaleReceipt')
.delete(); .delete();
// Delete all associated journal transactions to payment receive transaction. // Delete all associated journal transactions to payment receive transaction.
const deleteTransactionsOper = this.deleteJournalTransactions( const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
saleReceiptId, saleReceiptId,
'SaleReceipt' 'SaleReceipt'
); );
return Promise.all([ return Promise.all([
deleteItemsEntriesOper,
deleteSaleReceiptOper,
deleteTransactionsOper, deleteTransactionsOper,
]); ]);
} }
@@ -124,7 +106,7 @@ export default class SalesReceipt extends JournalPosterService {
* @param {Integer} saleReceiptId * @param {Integer} saleReceiptId
* @returns {Boolean} * @returns {Boolean}
*/ */
static async isSaleReceiptExists(saleReceiptId) { static async isSaleReceiptExists(saleReceiptId: number) {
const foundSaleReceipt = await SaleReceipt.tenant() const foundSaleReceipt = await SaleReceipt.tenant()
.query() .query()
.where('id', saleReceiptId); .where('id', saleReceiptId);
@@ -136,7 +118,7 @@ export default class SalesReceipt extends JournalPosterService {
* @param {Integer} saleReceiptId * @param {Integer} saleReceiptId
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
*/ */
static async isSaleReceiptEntriesIDsExists(saleReceiptId, saleReceipt) { static async isSaleReceiptEntriesIDsExists(saleReceiptId: number, saleReceipt: any) {
const entriesIDs = saleReceipt.entries const entriesIDs = saleReceipt.entries
.filter((e) => e.id) .filter((e) => e.id)
.map((e) => e.id); .map((e) => e.id);
@@ -147,7 +129,7 @@ export default class SalesReceipt extends JournalPosterService {
.where('reference_id', saleReceiptId) .where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt'); .where('reference_type', 'SaleReceipt');
const storedEntriesIDs = storedEntries.map((e) => e.id); const storedEntriesIDs = storedEntries.map((e: any) => e.id);
const notFoundEntriesIDs = difference( const notFoundEntriesIDs = difference(
entriesIDs, entriesIDs,
storedEntriesIDs storedEntriesIDs
@@ -159,7 +141,7 @@ export default class SalesReceipt extends JournalPosterService {
* Retrieve sale receipt with associated entries. * Retrieve sale receipt with associated entries.
* @param {Integer} saleReceiptId * @param {Integer} saleReceiptId
*/ */
static async getSaleReceiptWithEntries(saleReceiptId) { static async getSaleReceiptWithEntries(saleReceiptId: number) {
const saleReceipt = await SaleReceipt.tenant().query() const saleReceipt = await SaleReceipt.tenant().query()
.where('id', saleReceiptId) .where('id', saleReceiptId)
.withGraphFetched('entries'); .withGraphFetched('entries');

View File

@@ -132,6 +132,18 @@ const getTotalDeep = (items, deepProp, totalProp) =>
return _.sumBy(item, totalProp) + total + acc; return _.sumBy(item, totalProp) + total + acc;
}, 0); }, 0);
function applyMixins(derivedCtor, baseCtors) {
baseCtors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name)
);
});
});
}
export { export {
hashPassword, hashPassword,
origin, origin,
@@ -143,4 +155,5 @@ export {
flatToNestedArray, flatToNestedArray,
itemsStartWith, itemsStartWith,
getTotalDeep, getTotalDeep,
applyMixins,
}; };

View File

@@ -6,6 +6,7 @@
"module": "commonjs", "module": "commonjs",
"target": "es5", "target": "es5",
"jsx": "react", "jsx": "react",
"allowJs": true "allowJs": true,
"esModuleInterop": true,
} }
} }