diff --git a/server/src/api/controllers/Purchases/BillsPayments.ts b/server/src/api/controllers/Purchases/BillsPayments.ts index cfa954993..b13013e94 100644 --- a/server/src/api/controllers/Purchases/BillsPayments.ts +++ b/server/src/api/controllers/Purchases/BillsPayments.ts @@ -198,13 +198,16 @@ export default class BillsPayments extends BaseController { const { id: billPaymentId } = req.params; 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({ - bill_payment: { - ...this.transfromToResponse({ ...billPayment }), - payable_bills: payableBills, - }, + bill_payment: this.transfromToResponse({ ...billPayment }), + payable_bills: this.transfromToResponse([ ...payableBills ]), + payment_bills: this.transfromToResponse([ ...paymentMadeBills ]), }); } catch (error) { next(error); diff --git a/server/src/api/controllers/Sales/PaymentReceives.ts b/server/src/api/controllers/Sales/PaymentReceives.ts index 37d66dc56..965515d9b 100644 --- a/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/server/src/api/controllers/Sales/PaymentReceives.ts @@ -7,6 +7,7 @@ import asyncMiddleware from 'api/middleware/asyncMiddleware'; import PaymentReceiveService from 'services/Sales/PaymentsReceives'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { ServiceError } from 'exceptions'; +import HasItemEntries from 'services/Sales/HasItemsEntries'; /** * Payments receives controller. @@ -209,10 +210,19 @@ export default class PaymentReceivesController extends BaseController { const { id: paymentReceiveId } = req.params; try { - const paymentReceive = await this.paymentReceiveService.getPaymentReceive( + const { + paymentReceive, + receivableInvoices, + paymentReceiveInvoices, + } = await this.paymentReceiveService.getPaymentReceive( 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) { next(error); } @@ -314,7 +324,7 @@ export default class PaymentReceivesController extends BaseController { 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, { 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 }], }); } + if (error.errorType === 'INVALID_PAYMENT_AMOUNT') { + return res.boom.badRequest(null, { + errors: [{ type: 'INVALID_PAYMENT_AMOUNT', code: 1000 }], + }); + } + console.log(error.errorType); } next(error); } diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts index ae1a1aa60..29bbd53b0 100644 --- a/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -84,7 +84,7 @@ export default class SaleInvoicesController extends BaseController{ check('customer_id').exists().isNumeric().toInt(), check('invoice_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('status').exists().trim().escape(), diff --git a/server/src/models/BillPaymentEntry.js b/server/src/models/BillPaymentEntry.js index 88414bdb4..3167ec553 100644 --- a/server/src/models/BillPaymentEntry.js +++ b/server/src/models/BillPaymentEntry.js @@ -1,4 +1,4 @@ -import { mixin } from 'objection'; +import { mixin, Model } from 'objection'; import TenantModel from 'models/TenantModel'; export default class BillPaymentEntry extends TenantModel { @@ -15,4 +15,22 @@ export default class BillPaymentEntry extends TenantModel { get timestamps() { 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', + }, + }, + }; + } } diff --git a/server/src/services/Purchases/BillPayments.ts b/server/src/services/Purchases/BillPayments.ts index c6f8dba45..821de0115 100644 --- a/server/src/services/Purchases/BillPayments.ts +++ b/server/src/services/Purchases/BillPayments.ts @@ -1,5 +1,5 @@ import { Inject, Service } from 'typedi'; -import { entries, omit, sumBy, difference } from 'lodash'; +import { omit, sumBy, difference } from 'lodash'; import { EventDispatcher, EventDispatcherInterface, @@ -488,27 +488,46 @@ export default class BillPaymentsService { * @param {number} billPaymentId - The bill payment id. * @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 = await BillPayment.query() .findById(billPaymentId) - .withGraphFetched('entries') + .withGraphFetched('entries.bill') .withGraphFetched('vendor') .withGraphFetched('paymentAccount'); - + if (!billPayment) { throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND); } + const billsIds = billPayment.entries.map((entry) => entry.billId); - const payableBills = await Bill.query().onBuild((builder) => { - const billsIds = billPayment.entries.map((entry) => entry.billId); + // Retrieve all payable bills that assocaited to the payment made transaction. + const payableBills = await Bill.query() + .modify('dueBills') + .whereNotIn('id', billsIds) + .where('vendor_id', billPayment.vendorId) + .orderBy('bill_date', 'ASC'); - builder.where('vendor_id', billPayment.vendorId); - builder.orWhereIn('id', billsIds); - builder.orderByRaw(`FIELD(id, ${billsIds.join(', ')}) DESC`); - builder.orderBy('bill_date', 'ASC'); - }) - return { billPayment, payableBills }; + // 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, + }; } /** diff --git a/server/src/services/Sales/PaymentsReceives.ts b/server/src/services/Sales/PaymentsReceives.ts index f2c3112c3..06919570f 100644 --- a/server/src/services/Sales/PaymentsReceives.ts +++ b/server/src/services/Sales/PaymentsReceives.ts @@ -33,7 +33,8 @@ const ERRORS = { DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND', DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE', 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. @@ -180,6 +181,34 @@ export default class PaymentReceiveService { throw new ServiceError(ERRORS.INVALID_PAYMENT_AMOUNT); } } + + /** + * 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 @@ -266,9 +295,8 @@ export default class PaymentReceiveService { await this.customersService.getCustomerByIdOrThrowError(tenantId, paymentReceiveDTO.customerId); // Validate the entries ids existance on payment receive type. - await this.itemsEntries.validateEntriesIdsExistance( - tenantId, paymentReceiveId, 'PaymentReceive', paymentReceiveDTO.entries - ); + await this.validateEntriesIdsExistance(tenantId, paymentReceiveId, paymentReceiveDTO.entries); + // Validate payment receive invoices IDs existance and associated to the given customer id. await this.validateInvoicesIDsExistance(tenantId, paymentReceiveDTO.customerId, paymentReceiveDTO.entries); @@ -329,27 +357,37 @@ export default class PaymentReceiveService { public async getPaymentReceive( tenantId: number, paymentReceiveId: number - ): Promise<{ paymentReceive: IPaymentReceive[], receivableInvoices: ISaleInvoice }> { + ): Promise<{ + paymentReceive: IPaymentReceive, + receivableInvoices: ISaleInvoice[], + paymentReceiveInvoices: ISaleInvoice[], + }> { const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId); const paymentReceive = await PaymentReceive.query() .findById(paymentReceiveId) - .withGraphFetched('entries') + .withGraphFetched('entries.invoice') .withGraphFetched('customer') .withGraphFetched('depositAccount'); - + if (!paymentReceive) { 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); - builder.orWhereIn('id', invoicesIds); - builder.orderByRaw(`FIELD(id, ${invoicesIds.join(', ')}) DESC`); - builder.orderBy('invoice_date', 'ASC'); - }); - return { paymentReceive, receivableInvoices }; + // Retrieves all receivable bills that associated to the payment receive transaction. + const receivableInvoices = await SaleInvoice.query() + .modify('dueInvoices') + .where('customer_id', paymentReceive.customerId) + .whereNotIn('id', invoicesIds) + .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 }; } /**