fix: validate payment made entries ids exists.

fix: retrieve payment made and receive details.
This commit is contained in:
Ahmed Bouhuolia
2020-11-04 00:23:58 +02:00
parent 083a2dfbc5
commit 1738a333c7
6 changed files with 132 additions and 38 deletions

View File

@@ -198,13 +198,16 @@ export default class BillsPayments extends BaseController {
const { id: billPaymentId } = req.params; const { id: billPaymentId } = req.params;
try { try {
const { billPayment, payableBills } = await this.billPaymentService.getBillPayment(tenantId, billPaymentId); const {
billPayment,
payableBills,
paymentMadeBills,
} = await this.billPaymentService.getBillPayment(tenantId, billPaymentId);
return res.status(200).send({ return res.status(200).send({
bill_payment: { bill_payment: this.transfromToResponse({ ...billPayment }),
...this.transfromToResponse({ ...billPayment }), payable_bills: this.transfromToResponse([ ...payableBills ]),
payable_bills: payableBills, payment_bills: this.transfromToResponse([ ...paymentMadeBills ]),
},
}); });
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -7,6 +7,7 @@ import asyncMiddleware from 'api/middleware/asyncMiddleware';
import PaymentReceiveService from 'services/Sales/PaymentsReceives'; import PaymentReceiveService from 'services/Sales/PaymentsReceives';
import DynamicListingService from 'services/DynamicListing/DynamicListService'; import DynamicListingService from 'services/DynamicListing/DynamicListService';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import HasItemEntries from 'services/Sales/HasItemsEntries';
/** /**
* Payments receives controller. * Payments receives controller.
@@ -209,10 +210,19 @@ export default class PaymentReceivesController extends BaseController {
const { id: paymentReceiveId } = req.params; const { id: paymentReceiveId } = req.params;
try { try {
const paymentReceive = await this.paymentReceiveService.getPaymentReceive( const {
paymentReceive,
receivableInvoices,
paymentReceiveInvoices,
} = await this.paymentReceiveService.getPaymentReceive(
tenantId, paymentReceiveId tenantId, paymentReceiveId
); );
return res.status(200).send({ paymentReceive });
return res.status(200).send({
payment_receive: this.transfromToResponse({ ...paymentReceive }),
receivable_invoices: this.transfromToResponse([ ...receivableInvoices ]),
payment_invoices: this.transfromToResponse([ ...paymentReceiveInvoices ]),
});
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -314,7 +324,7 @@ export default class PaymentReceivesController extends BaseController {
errors: [{ type: 'INVOICES_IDS_NOT_FOUND', code: 300 }], errors: [{ type: 'INVOICES_IDS_NOT_FOUND', code: 300 }],
}); });
} }
if (error.errorType === 'ENTRIES_IDS_NOT_FOUND') { if (error.errorType === 'ENTRIES_IDS_NOT_EXISTS') {
return res.boom.badRequest(null, { return res.boom.badRequest(null, {
errors: [{ type: 'ENTRIES_IDS_NOT_FOUND', code: 300 }], errors: [{ type: 'ENTRIES_IDS_NOT_FOUND', code: 300 }],
}); });
@@ -324,6 +334,12 @@ export default class PaymentReceivesController extends BaseController {
errors: [{ type: 'CUSTOMER_NOT_FOUND', code: 300 }], errors: [{ type: 'CUSTOMER_NOT_FOUND', code: 300 }],
}); });
} }
if (error.errorType === 'INVALID_PAYMENT_AMOUNT') {
return res.boom.badRequest(null, {
errors: [{ type: 'INVALID_PAYMENT_AMOUNT', code: 1000 }],
});
}
console.log(error.errorType);
} }
next(error); next(error);
} }

View File

@@ -84,7 +84,7 @@ export default class SaleInvoicesController extends BaseController{
check('customer_id').exists().isNumeric().toInt(), check('customer_id').exists().isNumeric().toInt(),
check('invoice_date').exists().isISO8601(), check('invoice_date').exists().isISO8601(),
check('due_date').exists().isISO8601(), check('due_date').exists().isISO8601(),
check('invoice_no').exists().trim().escape(), check('invoice_no').optional().trim().escape(),
check('reference_no').optional().trim().escape(), check('reference_no').optional().trim().escape(),
check('status').exists().trim().escape(), check('status').exists().trim().escape(),

View File

@@ -1,4 +1,4 @@
import { mixin } from 'objection'; import { mixin, Model } from 'objection';
import TenantModel from 'models/TenantModel'; import TenantModel from 'models/TenantModel';
export default class BillPaymentEntry extends TenantModel { export default class BillPaymentEntry extends TenantModel {
@@ -15,4 +15,22 @@ export default class BillPaymentEntry extends TenantModel {
get timestamps() { get timestamps() {
return []; return [];
} }
/**
* Relationship mapping.
*/
static get relationMappings() {
const Bill = require('models/Bill');
return {
bill: {
relation: Model.BelongsToOneRelation,
modelClass: Bill.default,
join: {
from: 'bills_payments_entries.billId',
to: 'bills.id',
},
},
};
}
} }

View File

@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { entries, omit, sumBy, difference } from 'lodash'; import { omit, sumBy, difference } from 'lodash';
import { import {
EventDispatcher, EventDispatcher,
EventDispatcherInterface, EventDispatcherInterface,
@@ -488,27 +488,46 @@ export default class BillPaymentsService {
* @param {number} billPaymentId - The bill payment id. * @param {number} billPaymentId - The bill payment id.
* @return {object} * @return {object}
*/ */
public async getBillPayment(tenantId: number, billPaymentId: number) { public async getBillPayment(tenantId: number, billPaymentId: number): Promise<{
billPayment: IBillPayment,
payableBills: IBill[],
paymentMadeBills: IBill[],
}> {
const { BillPayment, Bill } = this.tenancy.models(tenantId); const { BillPayment, Bill } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query() const billPayment = await BillPayment.query()
.findById(billPaymentId) .findById(billPaymentId)
.withGraphFetched('entries') .withGraphFetched('entries.bill')
.withGraphFetched('vendor') .withGraphFetched('vendor')
.withGraphFetched('paymentAccount'); .withGraphFetched('paymentAccount');
if (!billPayment) { if (!billPayment) {
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND); throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
} }
const payableBills = await Bill.query().onBuild((builder) => {
const billsIds = billPayment.entries.map((entry) => entry.billId); const billsIds = billPayment.entries.map((entry) => entry.billId);
builder.where('vendor_id', billPayment.vendorId); // Retrieve all payable bills that assocaited to the payment made transaction.
builder.orWhereIn('id', billsIds); const payableBills = await Bill.query()
builder.orderByRaw(`FIELD(id, ${billsIds.join(', ')}) DESC`); .modify('dueBills')
builder.orderBy('bill_date', 'ASC'); .whereNotIn('id', billsIds)
}) .where('vendor_id', billPayment.vendorId)
return { billPayment, payableBills }; .orderBy('bill_date', 'ASC');
// Retrieve all payment made assocaited bills.
const paymentMadeBills = billPayment.entries.map((entry) => ({
...(entry.bill),
dueAmount: (entry.bill.dueAmount + entry.paymentAmount),
}));
return {
billPayment: {
...billPayment,
entries: billPayment.entries.map((entry) => ({
...omit(entry, ['bill']),
})),
},
payableBills,
paymentMadeBills,
};
} }
/** /**

View File

@@ -33,7 +33,8 @@ const ERRORS = {
DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND', DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND',
DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE', DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE',
INVALID_PAYMENT_AMOUNT: 'INVALID_PAYMENT_AMOUNT', INVALID_PAYMENT_AMOUNT: 'INVALID_PAYMENT_AMOUNT',
INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND' INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND',
ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS'
}; };
/** /**
* Payment receive service. * Payment receive service.
@@ -181,6 +182,34 @@ export default class PaymentReceiveService {
} }
} }
/**
* Validate the payment receive entries IDs existance.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {IPaymentReceiveEntryDTO[]} paymentReceiveEntries
*/
private async validateEntriesIdsExistance(
tenantId: number,
paymentReceiveId: number,
paymentReceiveEntries: IPaymentReceiveEntryDTO[],
) {
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
const entriesIds = paymentReceiveEntries
.filter((entry) => entry.id)
.map((entry) => entry.id);
const storedEntries = await PaymentReceiveEntry.query()
.where('payment_receive_id', paymentReceiveId);
const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
if (notFoundEntriesIds.length > 0) {
throw new ServiceError(ERRORS.ENTRIES_IDS_NOT_EXISTS);
}
}
/** /**
* 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.
@@ -266,9 +295,8 @@ export default class PaymentReceiveService {
await this.customersService.getCustomerByIdOrThrowError(tenantId, paymentReceiveDTO.customerId); await this.customersService.getCustomerByIdOrThrowError(tenantId, paymentReceiveDTO.customerId);
// Validate the entries ids existance on payment receive type. // Validate the entries ids existance on payment receive type.
await this.itemsEntries.validateEntriesIdsExistance( await this.validateEntriesIdsExistance(tenantId, paymentReceiveId, paymentReceiveDTO.entries);
tenantId, paymentReceiveId, 'PaymentReceive', paymentReceiveDTO.entries
);
// Validate payment receive invoices IDs existance and associated to the given customer id. // Validate payment receive invoices IDs existance and associated to the given customer id.
await this.validateInvoicesIDsExistance(tenantId, paymentReceiveDTO.customerId, paymentReceiveDTO.entries); await this.validateInvoicesIDsExistance(tenantId, paymentReceiveDTO.customerId, paymentReceiveDTO.entries);
@@ -329,27 +357,37 @@ export default class PaymentReceiveService {
public async getPaymentReceive( public async getPaymentReceive(
tenantId: number, tenantId: number,
paymentReceiveId: number paymentReceiveId: number
): Promise<{ paymentReceive: IPaymentReceive[], receivableInvoices: ISaleInvoice }> { ): Promise<{
paymentReceive: IPaymentReceive,
receivableInvoices: ISaleInvoice[],
paymentReceiveInvoices: ISaleInvoice[],
}> {
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId); const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
const paymentReceive = await PaymentReceive.query() const paymentReceive = await PaymentReceive.query()
.findById(paymentReceiveId) .findById(paymentReceiveId)
.withGraphFetched('entries') .withGraphFetched('entries.invoice')
.withGraphFetched('customer') .withGraphFetched('customer')
.withGraphFetched('depositAccount'); .withGraphFetched('depositAccount');
if (!paymentReceive) { if (!paymentReceive) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS); throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
} }
// Receivable open invoices.
const receivableInvoices = await SaleInvoice.query().onBuild((builder) => {
const invoicesIds = paymentReceive.entries.map((entry) => entry.invoiceId); const invoicesIds = paymentReceive.entries.map((entry) => entry.invoiceId);
builder.where('customer_id', paymentReceive.customerId); // Retrieves all receivable bills that associated to the payment receive transaction.
builder.orWhereIn('id', invoicesIds); const receivableInvoices = await SaleInvoice.query()
builder.orderByRaw(`FIELD(id, ${invoicesIds.join(', ')}) DESC`); .modify('dueInvoices')
builder.orderBy('invoice_date', 'ASC'); .where('customer_id', paymentReceive.customerId)
}); .whereNotIn('id', invoicesIds)
return { paymentReceive, receivableInvoices }; .orderBy('invoice_date', 'ASC');
// Retrieve all payment receive associated invoices.
const paymentReceiveInvoices = paymentReceive.entries.map((entry) => ({
...(entry.invoice),
dueAmount: (entry.invoice.dueAmount + entry.paymentAmount),
}));
return { paymentReceive, receivableInvoices, paymentReceiveInvoices };
} }
/** /**