mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
refactoring: payment receive and sale invoice actions.
This commit is contained in:
@@ -152,7 +152,11 @@ export default class BillsController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const storedBill = await this.billsService.createBill(tenantId, billDTO, user);
|
const storedBill = await this.billsService.createBill(tenantId, billDTO, user);
|
||||||
return res.status(200).send({ id: storedBill.id });
|
|
||||||
|
return res.status(200).send({
|
||||||
|
id: storedBill.id,
|
||||||
|
message: 'The bill has been created successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -170,7 +174,10 @@ export default class BillsController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const editedBill = await this.billsService.editBill(tenantId, billId, billDTO);
|
const editedBill = await this.billsService.editBill(tenantId, billId, billDTO);
|
||||||
return res.status(200).send({ id: billId });
|
return res.status(200).send({
|
||||||
|
id: billId,
|
||||||
|
message: 'The bill has been edited successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Router, Request, Response } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query, ValidationChain, matchedData } from 'express-validator';
|
import { check, param, query, ValidationChain } from 'express-validator';
|
||||||
import { difference } from 'lodash';
|
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { IPaymentReceive, IPaymentReceiveOTD } from 'interfaces';
|
import { IPaymentReceiveDTO } from 'interfaces';
|
||||||
import BaseController from 'api/controllers/BaseController';
|
import BaseController from 'api/controllers/BaseController';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
import PaymentReceiveService from 'services/Sales/PaymentsReceives';
|
import PaymentReceiveService from 'services/Sales/PaymentsReceives';
|
||||||
import SaleInvoiceService from 'services/Sales/SalesInvoices';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import AccountsService from 'services/Accounts/AccountsService';
|
import { ServiceError } from 'exceptions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Payments receives controller.
|
* Payments receives controller.
|
||||||
@@ -19,10 +18,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
paymentReceiveService: PaymentReceiveService;
|
paymentReceiveService: PaymentReceiveService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
accountsService: AccountsService;
|
dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
saleInvoiceService: SaleInvoiceService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -34,45 +30,37 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
'/:id',
|
'/:id',
|
||||||
this.editPaymentReceiveValidation,
|
this.editPaymentReceiveValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validatePaymentReceiveNoExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validateCustomerExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validateDepositAccount.bind(this)),
|
|
||||||
asyncMiddleware(this.validateInvoicesIDs.bind(this)),
|
|
||||||
asyncMiddleware(this.validateEntriesIdsExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validateInvoicesPaymentsAmount.bind(this)),
|
|
||||||
asyncMiddleware(this.editPaymentReceive.bind(this)),
|
asyncMiddleware(this.editPaymentReceive.bind(this)),
|
||||||
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
this.newPaymentReceiveValidation,
|
this.newPaymentReceiveValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validatePaymentReceiveNoExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validateCustomerExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validateDepositAccount.bind(this)),
|
|
||||||
asyncMiddleware(this.validateInvoicesIDs.bind(this)),
|
|
||||||
asyncMiddleware(this.validateInvoicesPaymentsAmount.bind(this)),
|
|
||||||
asyncMiddleware(this.newPaymentReceive.bind(this)),
|
asyncMiddleware(this.newPaymentReceive.bind(this)),
|
||||||
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
this.paymentReceiveValidation,
|
this.paymentReceiveValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
asyncMiddleware(this.getPaymentReceive.bind(this)),
|
||||||
asyncMiddleware(this.getPaymentReceive.bind(this))
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
this.validatePaymentReceiveList,
|
this.validatePaymentReceiveList,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getPaymentReceiveList.bind(this)),
|
asyncMiddleware(this.getPaymentReceiveList.bind(this)),
|
||||||
|
this.handleServiceErrors,
|
||||||
|
this.dynamicListService.handlerErrorsToResponse,
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:id',
|
'/:id',
|
||||||
this.paymentReceiveValidation,
|
this.paymentReceiveValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validatePaymentReceiveExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.deletePaymentReceive.bind(this)),
|
asyncMiddleware(this.deletePaymentReceive.bind(this)),
|
||||||
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -126,198 +114,6 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
return [...this.paymentReceiveSchema];
|
return [...this.paymentReceiveSchema];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the payment receive number existance.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validatePaymentReceiveNoExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const tenantId = req.tenantId;
|
|
||||||
const isPaymentNoExists = await this.paymentReceiveService.isPaymentReceiveNoExists(
|
|
||||||
tenantId,
|
|
||||||
req.body.payment_receive_no,
|
|
||||||
req.params.id,
|
|
||||||
);
|
|
||||||
if (isPaymentNoExists) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'PAYMENT.RECEIVE.NUMBER.EXISTS', code: 400 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the payment receive existance.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validatePaymentReceiveExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const tenantId = req.tenantId;
|
|
||||||
const isPaymentNoExists = await this.paymentReceiveService
|
|
||||||
.isPaymentReceiveExists(
|
|
||||||
tenantId,
|
|
||||||
req.params.id
|
|
||||||
);
|
|
||||||
if (!isPaymentNoExists) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'PAYMENT.RECEIVE.NOT.EXISTS', code: 600 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the deposit account id existance.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateDepositAccount(req: Request, res: Response, next: Function) {
|
|
||||||
const tenantId = req.tenantId;
|
|
||||||
const isDepositAccExists = await this.accountsService.isAccountExists(
|
|
||||||
tenantId,
|
|
||||||
req.body.deposit_account_id
|
|
||||||
);
|
|
||||||
if (!isDepositAccExists) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'DEPOSIT.ACCOUNT.NOT.EXISTS', code: 300 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the `customer_id` existance.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateCustomerExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { Customer } = req.models;
|
|
||||||
|
|
||||||
const isCustomerExists = await Customer.query().findById(req.body.customer_id);
|
|
||||||
|
|
||||||
if (!isCustomerExists) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the invoices IDs existance.
|
|
||||||
* @param {Request} req -
|
|
||||||
* @param {Response} res -
|
|
||||||
* @param {Function} next -
|
|
||||||
*/
|
|
||||||
async validateInvoicesIDs(req: Request, res: Response, next: Function) {
|
|
||||||
const paymentReceive = { ...req.body };
|
|
||||||
const { tenantId } = req;
|
|
||||||
const invoicesIds = paymentReceive.entries
|
|
||||||
.map((e) => e.invoice_id);
|
|
||||||
|
|
||||||
const notFoundInvoicesIDs = await this.saleInvoiceService.isInvoicesExist(
|
|
||||||
tenantId,
|
|
||||||
invoicesIds,
|
|
||||||
paymentReceive.customer_id,
|
|
||||||
);
|
|
||||||
if (notFoundInvoicesIDs.length > 0) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'INVOICES.IDS.NOT.FOUND', code: 500 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates entries invoice payment amount.
|
|
||||||
* @param {Request} req -
|
|
||||||
* @param {Response} res -
|
|
||||||
* @param {Function} next -
|
|
||||||
*/
|
|
||||||
async validateInvoicesPaymentsAmount(req: Request, res: Response, next: Function) {
|
|
||||||
const { SaleInvoice } = req.models;
|
|
||||||
const invoicesIds = req.body.entries.map((e) => e.invoice_id);
|
|
||||||
|
|
||||||
const storedInvoices = await SaleInvoice.query()
|
|
||||||
.whereIn('id', invoicesIds);
|
|
||||||
|
|
||||||
const storedInvoicesMap = new Map(
|
|
||||||
storedInvoices.map((invoice) => [invoice.id, invoice])
|
|
||||||
);
|
|
||||||
const hasWrongPaymentAmount: any[] = [];
|
|
||||||
|
|
||||||
req.body.entries.forEach((entry, index: number) => {
|
|
||||||
const entryInvoice = storedInvoicesMap.get(entry.invoice_id);
|
|
||||||
const { dueAmount } = entryInvoice;
|
|
||||||
|
|
||||||
if (dueAmount < entry.payment_amount) {
|
|
||||||
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (hasWrongPaymentAmount.length > 0) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: 'INVOICE.PAYMENT.AMOUNT',
|
|
||||||
code: 200,
|
|
||||||
indexes: hasWrongPaymentAmount,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the payment receive entries IDs existance.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @return {Response}
|
|
||||||
*/
|
|
||||||
async validateEntriesIdsExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const paymentReceive = { id: req.params.id, ...req.body };
|
|
||||||
const entriesIds = paymentReceive.entries
|
|
||||||
.filter(entry => entry.id)
|
|
||||||
.map(entry => entry.id);
|
|
||||||
|
|
||||||
const { PaymentReceiveEntry } = req.models;
|
|
||||||
|
|
||||||
const storedEntries = await PaymentReceiveEntry.query()
|
|
||||||
.where('payment_receive_id', paymentReceive.id);
|
|
||||||
|
|
||||||
const storedEntriesIds = storedEntries.map((entry) => entry.id);
|
|
||||||
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
|
||||||
|
|
||||||
if (notFoundEntriesIds.length > 0) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'ENTEIES.IDS.NOT.FOUND', code: 800 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records payment receive to the given customer with associated invoices.
|
|
||||||
*/
|
|
||||||
async newPaymentReceive(req: Request, res: Response) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const paymentReceive: IPaymentReceiveOTD = matchedData(req, {
|
|
||||||
locations: ['body'],
|
|
||||||
includeOptionals: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const storedPaymentReceive = await this.paymentReceiveService
|
|
||||||
.createPaymentReceive(
|
|
||||||
tenantId,
|
|
||||||
paymentReceive,
|
|
||||||
);
|
|
||||||
return res.status(200).send({ id: storedPaymentReceive.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit payment receive validation.
|
* Edit payment receive validation.
|
||||||
*/
|
*/
|
||||||
@@ -328,34 +124,48 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records payment receive to the given customer with associated invoices.
|
||||||
|
*/
|
||||||
|
async newPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const storedPaymentReceive = await this.paymentReceiveService
|
||||||
|
.createPaymentReceive(
|
||||||
|
tenantId,
|
||||||
|
paymentReceive,
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
id: storedPaymentReceive.id,
|
||||||
|
message: 'The payment receive has been created successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit the given payment receive.
|
* Edit the given payment receive.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async editPaymentReceive(req: Request, res: Response) {
|
async editPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
const { PaymentReceive } = req.models;
|
|
||||||
|
|
||||||
const paymentReceive: IPaymentReceiveOTD = matchedData(req, {
|
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
||||||
locations: ['body'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retrieve the payment receive before updating.
|
|
||||||
const oldPaymentReceive: IPaymentReceive = await PaymentReceive.query()
|
|
||||||
.where('id', paymentReceiveId)
|
|
||||||
.withGraphFetched('entries')
|
|
||||||
.first();
|
|
||||||
|
|
||||||
|
try {
|
||||||
await this.paymentReceiveService.editPaymentReceive(
|
await this.paymentReceiveService.editPaymentReceive(
|
||||||
tenantId,
|
tenantId, paymentReceiveId, paymentReceive,
|
||||||
paymentReceiveId,
|
|
||||||
paymentReceive,
|
|
||||||
oldPaymentReceive,
|
|
||||||
);
|
);
|
||||||
return res.status(200).send({ id: paymentReceiveId });
|
return res.status(200).send({ id: paymentReceiveId });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -363,22 +173,22 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async deletePaymentReceive(req: Request, res: Response) {
|
async deletePaymentReceive(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
const { PaymentReceive } = req.models;
|
|
||||||
|
|
||||||
const storedPaymentReceive = await PaymentReceive.query()
|
|
||||||
.where('id', paymentReceiveId)
|
|
||||||
.withGraphFetched('entries')
|
|
||||||
.first();
|
|
||||||
|
|
||||||
|
try {
|
||||||
await this.paymentReceiveService.deletePaymentReceive(
|
await this.paymentReceiveService.deletePaymentReceive(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
storedPaymentReceive
|
|
||||||
);
|
);
|
||||||
return res.status(200).send({ id: paymentReceiveId });
|
return res.status(200).send({
|
||||||
|
id: paymentReceiveId,
|
||||||
|
message: 'The payment receive has been edited successfully',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -387,12 +197,18 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Request} req -
|
* @param {Request} req -
|
||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
*/
|
*/
|
||||||
async getPaymentReceive(req: Request, res: Response) {
|
async getPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
const paymentReceive = await PaymentReceiveService.getPaymentReceive(
|
|
||||||
paymentReceiveId
|
try {
|
||||||
|
const paymentReceive = await this.paymentReceiveService.getPaymentReceive(
|
||||||
|
tenantId, paymentReceiveId
|
||||||
);
|
);
|
||||||
return res.status(200).send({ paymentReceive });
|
return res.status(200).send({ paymentReceive });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -401,7 +217,88 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @return {Response}
|
* @return {Response}
|
||||||
*/
|
*/
|
||||||
async getPaymentReceiveList(req: Request, res: Response) {
|
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const filter = {
|
||||||
|
filterRoles: [],
|
||||||
|
sortOrder: 'asc',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
page: 1,
|
||||||
|
pageSize: 12,
|
||||||
|
...this.matchedQueryData(req),
|
||||||
|
};
|
||||||
|
if (filter.stringifiedFilterRoles) {
|
||||||
|
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
paymentReceives,
|
||||||
|
pagination,
|
||||||
|
filterMeta,
|
||||||
|
} = await this.paymentReceiveService.listPaymentReceives(tenantId, filter);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
payment_receives: paymentReceives,
|
||||||
|
pagination: this.transfromToResponse(pagination),
|
||||||
|
filter_meta: this.transfromToResponse(filterMeta),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles service errors.
|
||||||
|
* @param error
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
*/
|
||||||
|
handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||||
|
if (error instanceof ServiceError) {
|
||||||
|
if (error.errorType === 'DEPOSIT_ACCOUNT_NOT_FOUND') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'DEPOSIT.ACCOUNT.NOT.EXISTS', code: 300 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'PAYMENT_RECEIVE_NO_EXISTS') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'PAYMENT_RECEIVE_NO_EXISTS', code: 300 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'PAYMENT_RECEIVE_NOT_EXISTS') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'PAYMENT_RECEIVE_NOT_EXISTS', code: 300 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE', code: 300 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'INVALID_PAYMENT_AMOUNT_INVALID') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'INVALID_PAYMENT_AMOUNT', code: 300 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'INVOICES_IDS_NOT_FOUND') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'INVOICES_IDS_NOT_FOUND', code: 300 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'ENTRIES_IDS_NOT_FOUND') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'ENTRIES_IDS_NOT_FOUND', code: 300 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'contact_not_found') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'CUSTOMER_NOT_FOUND', code: 300 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log(error.errorType);
|
||||||
|
}
|
||||||
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query, matchedData } from 'express-validator';
|
import { check, param, query } from 'express-validator';
|
||||||
import { difference } from 'lodash';
|
|
||||||
import { raw } from 'objection';
|
import { raw } from 'objection';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
@@ -8,6 +7,7 @@ import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
|||||||
import SaleInvoiceService from 'services/Sales/SalesInvoices';
|
import SaleInvoiceService from 'services/Sales/SalesInvoices';
|
||||||
import ItemsService from 'services/Items/ItemsService';
|
import ItemsService from 'services/Items/ItemsService';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
|
import { ServiceError } from 'exceptions';
|
||||||
import { ISaleInvoiceOTD, ISalesInvoicesFilter } from 'interfaces';
|
import { ISaleInvoiceOTD, ISalesInvoicesFilter } from 'interfaces';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -31,11 +31,8 @@ export default class SaleInvoicesController extends BaseController{
|
|||||||
'/',
|
'/',
|
||||||
this.saleInvoiceValidationSchema,
|
this.saleInvoiceValidationSchema,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validateInvoiceCustomerExistance.bind(this)),
|
asyncMiddleware(this.newSaleInvoice.bind(this)),
|
||||||
// asyncMiddleware(this.validateInvoiceNumberUnique.bind(this)),
|
this.handleServiceErrors,
|
||||||
asyncMiddleware(this.validateInvoiceItemsIdsExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validateNonSellableEntriesItems.bind(this)),
|
|
||||||
asyncMiddleware(this.newSaleInvoice.bind(this))
|
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id',
|
'/:id',
|
||||||
@@ -44,39 +41,36 @@ export default class SaleInvoicesController extends BaseController{
|
|||||||
...this.specificSaleInvoiceValidation,
|
...this.specificSaleInvoiceValidation,
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
asyncMiddleware(this.editSaleInvoice.bind(this)),
|
||||||
asyncMiddleware(this.validateInvoiceCustomerExistance.bind(this)),
|
this.handleServiceErrors,
|
||||||
// asyncMiddleware(this.validateInvoiceNumberUnique.bind(this)),
|
|
||||||
asyncMiddleware(this.validateInvoiceItemsIdsExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.valdiateInvoiceEntriesIdsExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validateEntriesIdsExistance.bind(this)),
|
|
||||||
asyncMiddleware(this.validateNonSellableEntriesItems.bind(this)),
|
|
||||||
asyncMiddleware(this.editSaleInvoice.bind(this))
|
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:id',
|
'/:id',
|
||||||
this.specificSaleInvoiceValidation,
|
this.specificSaleInvoiceValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
asyncMiddleware(this.deleteSaleInvoice.bind(this)),
|
||||||
asyncMiddleware(this.deleteSaleInvoice.bind(this))
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/due_invoices',
|
'/due_invoices',
|
||||||
this.dueSalesInvoicesListValidationSchema,
|
this.dueSalesInvoicesListValidationSchema,
|
||||||
asyncMiddleware(this.getDueSalesInvoice.bind(this)),
|
asyncMiddleware(this.getDueSalesInvoice.bind(this)),
|
||||||
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
this.specificSaleInvoiceValidation,
|
this.specificSaleInvoiceValidation,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.validateInvoiceExistance.bind(this)),
|
asyncMiddleware(this.getSaleInvoice.bind(this)),
|
||||||
asyncMiddleware(this.getSaleInvoice.bind(this))
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
this.saleInvoiceListValidationSchema,
|
this.saleInvoiceListValidationSchema,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getSalesInvoices.bind(this))
|
asyncMiddleware(this.getSalesInvoices.bind(this)),
|
||||||
|
this.handleServiceErrors,
|
||||||
|
this.dynamicListService.handlerErrorsToResponse,
|
||||||
)
|
)
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -134,176 +128,7 @@ export default class SaleInvoicesController extends BaseController{
|
|||||||
get dueSalesInvoicesListValidationSchema() {
|
get dueSalesInvoicesListValidationSchema() {
|
||||||
return [
|
return [
|
||||||
query('customer_id').optional().isNumeric().toInt(),
|
query('customer_id').optional().isNumeric().toInt(),
|
||||||
]
|
];
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether sale invoice customer exists on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateInvoiceCustomerExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const saleInvoice = { ...req.body };
|
|
||||||
const { Customer } = req.models;
|
|
||||||
|
|
||||||
const isCustomerIDExists = await Customer.query().findById(saleInvoice.customer_id);
|
|
||||||
|
|
||||||
if (!isCustomerIDExists) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether sale invoice items ids esits on the storage.
|
|
||||||
* @param {Request} req -
|
|
||||||
* @param {Response} res -
|
|
||||||
* @param {Function} next -
|
|
||||||
*/
|
|
||||||
async validateInvoiceItemsIdsExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const saleInvoice = { ...req.body };
|
|
||||||
const entriesItemsIds = saleInvoice.entries.map((e) => e.item_id);
|
|
||||||
|
|
||||||
const isItemsIdsExists = await this.itemsService.isItemsIdsExists(
|
|
||||||
tenantId, entriesItemsIds,
|
|
||||||
);
|
|
||||||
if (isItemsIdsExists.length > 0) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'ITEMS.IDS.NOT.EXISTS', code: 300 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Validate whether sale invoice number unqiue on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateInvoiceNumberUnique(req: Request, res: Response, next: Function) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const saleInvoice = { ...req.body };
|
|
||||||
|
|
||||||
const isInvoiceNoExists = await this.saleInvoiceService.isSaleInvoiceNumberExists(
|
|
||||||
tenantId,
|
|
||||||
saleInvoice.invoice_no,
|
|
||||||
req.params.id
|
|
||||||
);
|
|
||||||
if (isInvoiceNoExists) {
|
|
||||||
return res
|
|
||||||
.status(400)
|
|
||||||
.send({
|
|
||||||
errors: [{ type: 'SALE.INVOICE.NUMBER.IS.EXISTS', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether sale invoice exists on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateInvoiceExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { id: saleInvoiceId } = req.params;
|
|
||||||
const { tenantId } = req;
|
|
||||||
|
|
||||||
const isSaleInvoiceExists = await this.saleInvoiceService.isSaleInvoiceExists(
|
|
||||||
tenantId, saleInvoiceId,
|
|
||||||
);
|
|
||||||
if (!isSaleInvoiceExists) {
|
|
||||||
return res
|
|
||||||
.status(404)
|
|
||||||
.send({ errors: [{ type: 'SALE.INVOICE.NOT.FOUND', code: 200 }] });
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate sale invoice entries ids existance on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async valdiateInvoiceEntriesIdsExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const saleInvoice = { ...req.body };
|
|
||||||
const entriesItemsIds = saleInvoice.entries.map((e) => e.item_id);
|
|
||||||
|
|
||||||
const isItemsIdsExists = await this.itemsService.isItemsIdsExists(
|
|
||||||
tenantId, entriesItemsIds,
|
|
||||||
);
|
|
||||||
if (isItemsIdsExists.length > 0) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'ITEMS.IDS.NOT.EXISTS', code: 300 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether the sale estimate entries IDs exist on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateEntriesIdsExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { ItemEntry } = req.models;
|
|
||||||
const { id: saleInvoiceId } = req.params;
|
|
||||||
const saleInvoice = { ...req.body };
|
|
||||||
|
|
||||||
const entriesIds = saleInvoice.entries
|
|
||||||
.filter(e => e.id)
|
|
||||||
.map(e => e.id);
|
|
||||||
|
|
||||||
const storedEntries = await ItemEntry.query()
|
|
||||||
.whereIn('reference_id', [saleInvoiceId])
|
|
||||||
.whereIn('reference_type', ['SaleInvoice']);
|
|
||||||
|
|
||||||
const storedEntriesIds = storedEntries.map((entry) => entry.id);
|
|
||||||
const notFoundEntriesIds = difference(
|
|
||||||
entriesIds,
|
|
||||||
storedEntriesIds,
|
|
||||||
);
|
|
||||||
if (notFoundEntriesIds.length > 0) {
|
|
||||||
return res.boom.badRequest(null, {
|
|
||||||
errors: [{ type: 'SALE.INVOICE.ENTRIES.IDS.NOT.FOUND', code: 500 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the entries items that not sellable.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateNonSellableEntriesItems(req: Request, res: Response, next: Function) {
|
|
||||||
const { Item } = req.models;
|
|
||||||
const saleInvoice = { ...req.body };
|
|
||||||
const itemsIds = saleInvoice.entries.map(e => e.item_id);
|
|
||||||
|
|
||||||
const sellableItems = await Item.query()
|
|
||||||
.where('sellable', true)
|
|
||||||
.whereIn('id', itemsIds);
|
|
||||||
|
|
||||||
const sellableItemsIds = sellableItems.map((item) => item.id);
|
|
||||||
const notSellableItems = difference(itemsIds, sellableItemsIds);
|
|
||||||
|
|
||||||
if (notSellableItems.length > 0) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'NOT.SELLABLE.ITEMS', code: 600 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -372,14 +197,18 @@ export default class SaleInvoicesController extends BaseController{
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async getSaleInvoice(req: Request, res: Response) {
|
async getSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
||||||
const { id: saleInvoiceId } = req.params;
|
const { id: saleInvoiceId } = req.params;
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
const saleInvoice = await this.saleInvoiceService.getSaleInvoiceWithEntries(
|
const saleInvoice = await this.saleInvoiceService.getSaleInvoiceWithEntries(
|
||||||
tenantId, saleInvoiceId,
|
tenantId, saleInvoiceId,
|
||||||
);
|
);
|
||||||
return res.status(200).send({ sale_invoice: saleInvoice });
|
return res.status(200).send({ sale_invoice: saleInvoice });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -422,12 +251,20 @@ export default class SaleInvoicesController extends BaseController{
|
|||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
public async getSalesInvoices(req: Request, res: Response, next: NextFunction) {
|
public async getSalesInvoices(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req.params;
|
const { tenantId } = req;
|
||||||
const salesInvoicesFilter: ISalesInvoicesFilter = req.query;
|
const filter: ISalesInvoicesFilter = {
|
||||||
|
filterRoles: [],
|
||||||
|
sortOrder: 'asc',
|
||||||
|
columnSortBy: 'name',
|
||||||
|
...this.matchedQueryData(req),
|
||||||
|
};
|
||||||
|
if (filter.stringifiedFilterRoles) {
|
||||||
|
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { salesInvoices, filterMeta, pagination } = await this.saleInvoiceService.salesInvoicesList(
|
const { salesInvoices, filterMeta, pagination } = await this.saleInvoiceService.salesInvoicesList(
|
||||||
tenantId, salesInvoicesFilter,
|
tenantId, filter,
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
sales_invoices: salesInvoices,
|
sales_invoices: salesInvoices,
|
||||||
@@ -438,4 +275,63 @@ export default class SaleInvoicesController extends BaseController{
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles service errors.
|
||||||
|
* @param {Error} error
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||||
|
if (error instanceof ServiceError) {
|
||||||
|
if (error.errorType === 'INVOICE_NUMBER_NOT_UNIQUE') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'SALE.INVOICE.NUMBER.IS.EXISTS', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'SALE_INVOICE_NOT_FOUND') {
|
||||||
|
return res.status(404).send({
|
||||||
|
errors: [{ type: 'SALE.INVOICE.NOT.FOUND', code: 200 }]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'ENTRIES_ITEMS_IDS_NOT_EXISTS') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'ENTRIES_ITEMS_IDS_NOT_EXISTS', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'NOT_SELLABLE_ITEMS') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'NOT_SELLABLE_ITEMS', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'SALE_INVOICE_NO_NOT_UNIQUE') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'SALE_INVOICE_NO_NOT_UNIQUE', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'ITEMS_NOT_FOUND') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'ITEMS_NOT_FOUND', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'ENTRIES_IDS_NOT_FOUND') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'ENTRIES_IDS_NOT_FOUND', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'NOT_SELL_ABLE_ITEMS') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'NOT_SELL_ABLE_ITEMS', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'contact_not_found') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'CUSTOMER_NOT_FOUND', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(error.errorType);
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ export default class SalesController {
|
|||||||
router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// router.use('/invoices', Container.get(SalesInvoices).router());
|
router.use('/invoices', Container.get(SalesInvoices).router());
|
||||||
router.use('/estimates', Container.get(SalesEstimates).router());
|
router.use('/estimates', Container.get(SalesEstimates).router());
|
||||||
router.use('/receipts', Container.get(SalesReceipts).router());
|
router.use('/receipts', Container.get(SalesReceipts).router());
|
||||||
// router.use('/payment_receives', Container.get(PaymentReceives).router());
|
router.use('/payment_receives', Container.get(PaymentReceives).router());
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,43 @@
|
|||||||
|
import { IDynamicListFilterDTO } from "./DynamicFilter";
|
||||||
|
|
||||||
|
|
||||||
export interface IPaymentReceive { };
|
export interface IPaymentReceive {
|
||||||
export interface IPaymentReceiveOTD { };
|
id?: number,
|
||||||
|
customerId: number,
|
||||||
|
paymentDate: Date,
|
||||||
|
amount: number,
|
||||||
|
referenceNo: string,
|
||||||
|
depositAccountId: number,
|
||||||
|
paymentReceiveNo: string,
|
||||||
|
description: string,
|
||||||
|
entries: IPaymentReceiveEntry[],
|
||||||
|
userId: number,
|
||||||
|
};
|
||||||
|
export interface IPaymentReceiveDTO {
|
||||||
|
customerId: number,
|
||||||
|
paymentDate: Date,
|
||||||
|
amount: number,
|
||||||
|
referenceNo: string,
|
||||||
|
depositAccountId: number,
|
||||||
|
paymentReceiveNo: string,
|
||||||
|
description: string,
|
||||||
|
entries: IPaymentReceiveEntryDTO[],
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IPaymentReceiveEntry {
|
||||||
|
id?: number,
|
||||||
|
paymentReceiveId: number,
|
||||||
|
invoiceId: number,
|
||||||
|
paymentAmount: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IPaymentReceiveEntryDTO {
|
||||||
|
id?: number,
|
||||||
|
paymentReceiveId: number,
|
||||||
|
invoiceId: number,
|
||||||
|
paymentAmount: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IPaymentReceivesFilter extends IDynamicListFilterDTO {
|
||||||
|
stringifiedFilterRoles?: string,
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ export interface ISaleInvoiceOTD {
|
|||||||
invoiceDate: Date,
|
invoiceDate: Date,
|
||||||
dueDate: Date,
|
dueDate: Date,
|
||||||
referenceNo: string,
|
referenceNo: string,
|
||||||
|
invoiceNo: string,
|
||||||
|
customerId: number,
|
||||||
invoiceMessage: string,
|
invoiceMessage: string,
|
||||||
termsConditions: string,
|
termsConditions: string,
|
||||||
entries: IItemEntryDTO[],
|
entries: IItemEntryDTO[],
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import 'subscribers/organization';
|
|||||||
import 'subscribers/manualJournals';
|
import 'subscribers/manualJournals';
|
||||||
import 'subscribers/expenses';
|
import 'subscribers/expenses';
|
||||||
import 'subscribers/bills';
|
import 'subscribers/bills';
|
||||||
// import 'subscribers/saleInvoices';
|
import 'subscribers/saleInvoices';
|
||||||
import 'subscribers/customers';
|
import 'subscribers/customers';
|
||||||
import 'subscribers/vendors';
|
import 'subscribers/vendors';
|
||||||
import 'subscribers/paymentMades';
|
import 'subscribers/paymentMades';
|
||||||
|
import 'subscribers/paymentReceives';
|
||||||
|
|||||||
@@ -69,4 +69,17 @@ export default class PaymentReceive extends TenantModel {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model defined fields.
|
||||||
|
*/
|
||||||
|
static get fields() {
|
||||||
|
return {
|
||||||
|
created_at: {
|
||||||
|
label: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,4 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
.where('id', invoiceId)
|
.where('id', invoiceId)
|
||||||
[changeMethod]('payment_amount', Math.abs(amount));
|
[changeMethod]('payment_amount', Math.abs(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,4 +68,36 @@ export default class CustomerRepository extends TenantRepository {
|
|||||||
.whereIn('id', customersIds)
|
.whereIn('id', customersIds)
|
||||||
.withGraphFetched('salesInvoices');
|
.withGraphFetched('salesInvoices');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeBalance(vendorId: number, amount: number) {
|
||||||
|
const { Contact } = this.models;
|
||||||
|
const changeMethod = (amount > 0) ? 'increment' : 'decrement';
|
||||||
|
|
||||||
|
return Contact.query()
|
||||||
|
.where('id', vendorId)
|
||||||
|
[changeMethod]('balance', Math.abs(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
async changeDiffBalance(
|
||||||
|
vendorId: number,
|
||||||
|
amount: number,
|
||||||
|
oldAmount: number,
|
||||||
|
oldVendorId?: number,
|
||||||
|
) {
|
||||||
|
const diffAmount = amount - oldAmount;
|
||||||
|
const asyncOpers = [];
|
||||||
|
const _oldVendorId = oldVendorId || vendorId;
|
||||||
|
|
||||||
|
if (vendorId != _oldVendorId) {
|
||||||
|
const oldCustomerOper = this.changeBalance(_oldVendorId, (oldAmount * -1));
|
||||||
|
const customerOper = this.changeBalance(vendorId, amount);
|
||||||
|
|
||||||
|
asyncOpers.push(customerOper);
|
||||||
|
asyncOpers.push(oldCustomerOper);
|
||||||
|
} else {
|
||||||
|
const balanceChangeOper = this.changeBalance(vendorId, diffAmount);
|
||||||
|
asyncOpers.push(balanceChangeOper);
|
||||||
|
}
|
||||||
|
await Promise.all(asyncOpers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -69,12 +69,26 @@ export default class VendorRepository extends TenantRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
changeDiffBalance(
|
async changeDiffBalance(
|
||||||
vendorId: number,
|
vendorId: number,
|
||||||
amount: number,
|
amount: number,
|
||||||
oldAmount: number,
|
oldAmount: number,
|
||||||
oldVendorId?: number,
|
oldVendorId?: number,
|
||||||
) {
|
) {
|
||||||
|
const diffAmount = amount - oldAmount;
|
||||||
|
const asyncOpers = [];
|
||||||
|
const _oldVendorId = oldVendorId || vendorId;
|
||||||
|
|
||||||
|
if (vendorId != _oldVendorId) {
|
||||||
|
const oldCustomerOper = this.changeBalance(_oldVendorId, (oldAmount * -1));
|
||||||
|
const customerOper = this.changeBalance(vendorId, amount);
|
||||||
|
|
||||||
|
asyncOpers.push(customerOper);
|
||||||
|
asyncOpers.push(oldCustomerOper);
|
||||||
|
} else {
|
||||||
|
const balanceChangeOper = this.changeBalance(vendorId, diffAmount);
|
||||||
|
asyncOpers.push(balanceChangeOper);
|
||||||
|
}
|
||||||
|
await Promise.all(asyncOpers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,15 +46,15 @@ export default class ItemsEntriesService {
|
|||||||
* @param {number} billId -
|
* @param {number} billId -
|
||||||
* @param {IItemEntry[]} billEntries -
|
* @param {IItemEntry[]} billEntries -
|
||||||
*/
|
*/
|
||||||
public async validateEntriesIdsExistance(tenantId: number, billId: number, modelName: string, billEntries: IItemEntryDTO[]) {
|
public async validateEntriesIdsExistance(tenantId: number, referenceId: number, referenceType: string, billEntries: IItemEntryDTO[]) {
|
||||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||||
const entriesIds = billEntries
|
const entriesIds = billEntries
|
||||||
.filter((e: IItemEntry) => e.id)
|
.filter((e: IItemEntry) => e.id)
|
||||||
.map((e: IItemEntry) => e.id);
|
.map((e: IItemEntry) => e.id);
|
||||||
|
|
||||||
const storedEntries = await ItemEntry.query()
|
const storedEntries = await ItemEntry.query()
|
||||||
.whereIn('reference_id', [billId])
|
.whereIn('reference_id', [referenceId])
|
||||||
.whereIn('reference_type', [modelName]);
|
.whereIn('reference_type', [referenceType]);
|
||||||
|
|
||||||
const storedEntriesIds = storedEntries.map((entry) => entry.id);
|
const storedEntriesIds = storedEntries.map((entry) => entry.id);
|
||||||
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
||||||
|
|||||||
@@ -316,7 +316,9 @@ export default class BillPaymentsService {
|
|||||||
...omit(billPaymentObj, ['entries']),
|
...omit(billPaymentObj, ['entries']),
|
||||||
entries: billPaymentDTO.entries,
|
entries: billPaymentDTO.entries,
|
||||||
});
|
});
|
||||||
await this.eventDispatcher.dispatch(events.billPayments.onEdited);
|
await this.eventDispatcher.dispatch(events.billPayments.onEdited, {
|
||||||
|
tenantId, billPaymentId, billPayment, oldPaymentMade,
|
||||||
|
});
|
||||||
this.logger.info('[bill_payment] edited successfully.', { tenantId, billPaymentId, billPayment, oldPaymentMade });
|
this.logger.info('[bill_payment] edited successfully.', { tenantId, billPaymentId, billPayment, oldPaymentMade });
|
||||||
|
|
||||||
return billPayment;
|
return billPayment;
|
||||||
|
|||||||
@@ -184,7 +184,10 @@ export default class BillsService extends SalesInvoicesCost {
|
|||||||
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
|
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
|
||||||
await this.validateBillNumberExists(tenantId, billDTO.billNumber);
|
await this.validateBillNumberExists(tenantId, billDTO.billNumber);
|
||||||
|
|
||||||
|
// Validate items IDs existance.
|
||||||
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries);
|
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries);
|
||||||
|
|
||||||
|
// Validate non-purchasable items.
|
||||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries);
|
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries);
|
||||||
|
|
||||||
const bill = await Bill.query()
|
const bill = await Bill.query()
|
||||||
@@ -232,7 +235,7 @@ export default class BillsService extends SalesInvoicesCost {
|
|||||||
|
|
||||||
this.logger.info('[bill] trying to edit bill.', { tenantId, billId });
|
this.logger.info('[bill] trying to edit bill.', { tenantId, billId });
|
||||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||||
const billObj = this.billDTOToModel(tenantId, billDTO, oldBill);
|
const billObj = await this.billDTOToModel(tenantId, billDTO, oldBill);
|
||||||
|
|
||||||
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
|
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
|
||||||
await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId);
|
await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId);
|
||||||
@@ -242,7 +245,7 @@ export default class BillsService extends SalesInvoicesCost {
|
|||||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries);
|
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries);
|
||||||
|
|
||||||
// Update the bill transaction.
|
// Update the bill transaction.
|
||||||
const bill = await Bill.query().upsertGraph({
|
const bill = await Bill.query().upsertGraphAndFetch({
|
||||||
id: billId,
|
id: billId,
|
||||||
...omit(billObj, ['entries', 'invLotNumber']),
|
...omit(billObj, ['entries', 'invLotNumber']),
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { omit, sumBy, chain } from 'lodash';
|
import { omit, sumBy, chain, difference } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import {
|
import {
|
||||||
@@ -6,18 +6,35 @@ import {
|
|||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
} from 'decorators/eventDispatcher';
|
} from 'decorators/eventDispatcher';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
import { IPaymentReceiveOTD } from 'interfaces';
|
import {
|
||||||
|
IAccount,
|
||||||
|
IFilterMeta,
|
||||||
|
IPaginationMeta,
|
||||||
|
IPaymentReceive,
|
||||||
|
IPaymentReceiveDTO,
|
||||||
|
IPaymentReceiveEntryDTO,
|
||||||
|
IPaymentReceivesFilter,
|
||||||
|
ISaleInvoice
|
||||||
|
} from 'interfaces';
|
||||||
import AccountsService from 'services/Accounts/AccountsService';
|
import AccountsService from 'services/Accounts/AccountsService';
|
||||||
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 JournalPosterService from 'services/Sales/JournalPosterService';
|
import JournalPosterService from 'services/Sales/JournalPosterService';
|
||||||
import ServiceItemsEntries from 'services/Sales/ServiceItemsEntries';
|
|
||||||
import PaymentReceiveEntryRepository from 'repositories/PaymentReceiveEntryRepository';
|
|
||||||
import CustomerRepository from 'repositories/CustomerRepository';
|
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import { formatDateFields } from 'utils';
|
import { formatDateFields } from 'utils';
|
||||||
|
import { ServiceError } from 'exceptions';
|
||||||
|
import CustomersService from 'services/Contacts/CustomersService';
|
||||||
|
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||||
|
|
||||||
|
const ERRORS = {
|
||||||
|
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
|
||||||
|
PAYMENT_RECEIVE_NOT_EXISTS: 'PAYMENT_RECEIVE_NOT_EXISTS',
|
||||||
|
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'
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Payment receive service.
|
* Payment receive service.
|
||||||
* @service
|
* @service
|
||||||
@@ -27,6 +44,12 @@ export default class PaymentReceiveService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
accountsService: AccountsService;
|
accountsService: AccountsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
customersService: CustomersService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
itemsEntries: ItemsEntriesService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
@@ -42,112 +65,88 @@ export default class PaymentReceiveService {
|
|||||||
@EventDispatcher()
|
@EventDispatcher()
|
||||||
eventDispatcher: EventDispatcherInterface;
|
eventDispatcher: EventDispatcherInterface;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the payment receive number existance.
|
* Validates the payment receive number existance.
|
||||||
* @param {Request} req
|
* @param {number} tenantId -
|
||||||
* @param {Response} res
|
* @param {string} paymentReceiveNo -
|
||||||
* @param {Function} next
|
|
||||||
*/
|
*/
|
||||||
async validatePaymentReceiveNoExistance(req: Request, res: Response, next: Function) {
|
async validatePaymentReceiveNoExistance(
|
||||||
const tenantId = req.tenantId;
|
tenantId: number,
|
||||||
const isPaymentNoExists = await this.paymentReceiveService.isPaymentReceiveNoExists(
|
paymentReceiveNo: string,
|
||||||
tenantId,
|
notPaymentReceiveId?: number
|
||||||
req.body.payment_receive_no,
|
): Promise<void> {
|
||||||
req.params.id,
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
);
|
const paymentReceive = await PaymentReceive.query().findOne('payment_receive_no', paymentReceiveNo)
|
||||||
if (isPaymentNoExists) {
|
.onBuild((builder) => {
|
||||||
return res.status(400).send({
|
if (notPaymentReceiveId) {
|
||||||
errors: [{ type: 'PAYMENT.RECEIVE.NUMBER.EXISTS', code: 400 }],
|
builder.whereNot('id', notPaymentReceiveId);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (paymentReceive) {
|
||||||
|
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NO_EXISTS);
|
||||||
}
|
}
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the payment receive existance.
|
* Validates the payment receive existance.
|
||||||
* @param {Request} req
|
* @param {number} tenantId -
|
||||||
* @param {Response} res
|
* @param {number} paymentReceiveId -
|
||||||
* @param {Function} next
|
|
||||||
*/
|
*/
|
||||||
async validatePaymentReceiveExistance(req: Request, res: Response, next: Function) {
|
async getPaymentReceiveOrThrowError(
|
||||||
const tenantId = req.tenantId;
|
tenantId: number,
|
||||||
const isPaymentNoExists = await this.paymentReceiveService
|
paymentReceiveId: number
|
||||||
.isPaymentReceiveExists(
|
): Promise<IPaymentReceive> {
|
||||||
tenantId,
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
req.params.id
|
const paymentReceive = await PaymentReceive.query()
|
||||||
);
|
.withGraphFetched('entries')
|
||||||
if (!isPaymentNoExists) {
|
.findById(paymentReceiveId);
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'PAYMENT.RECEIVE.NOT.EXISTS', code: 600 }],
|
if (!paymentReceive) {
|
||||||
});
|
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
|
||||||
}
|
}
|
||||||
next();
|
return paymentReceive;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the deposit account id existance.
|
* Validate the deposit account id existance.
|
||||||
* @param {Request} req
|
* @param {number} tenantId -
|
||||||
* @param {Response} res
|
* @param {number} depositAccountId -
|
||||||
* @param {Function} next
|
|
||||||
*/
|
*/
|
||||||
async validateDepositAccount(req: Request, res: Response, next: Function) {
|
async getDepositAccountOrThrowError(tenantId: number, depositAccountId: number): Promise<IAccount> {
|
||||||
const tenantId = req.tenantId;
|
const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
const isDepositAccExists = await this.accountsService.isAccountExists(
|
|
||||||
tenantId,
|
|
||||||
req.body.deposit_account_id
|
|
||||||
);
|
|
||||||
if (!isDepositAccExists) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'DEPOSIT.ACCOUNT.NOT.EXISTS', code: 300 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const currentAssetTypes = await accountTypeRepository.getByChildType('current_asset');
|
||||||
* Validates the `customer_id` existance.
|
const depositAccount = await accountRepository.findById(depositAccountId);
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateCustomerExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { Customer } = req.models;
|
|
||||||
|
|
||||||
const isCustomerExists = await Customer.query().findById(req.body.customer_id);
|
const currentAssetTypesIds = currentAssetTypes.map(type => type.id);
|
||||||
|
|
||||||
if (!isCustomerExists) {
|
if (!depositAccount) {
|
||||||
return res.status(400).send({
|
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND);
|
||||||
errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
next();
|
if (currentAssetTypesIds.indexOf(depositAccount.accountTypeId) === -1) {
|
||||||
|
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE);
|
||||||
|
}
|
||||||
|
return depositAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the invoices IDs existance.
|
* Validates the invoices IDs existance.
|
||||||
* @param {Request} req -
|
* @param {number} tenantId -
|
||||||
* @param {Response} res -
|
* @param {} paymentReceiveEntries -
|
||||||
* @param {Function} next -
|
|
||||||
*/
|
*/
|
||||||
async validateInvoicesIDs(req: Request, res: Response, next: Function) {
|
async validateInvoicesIDsExistance(tenantId: number, paymentReceiveEntries: any): Promise<void> {
|
||||||
const paymentReceive = { ...req.body };
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const { tenantId } = req;
|
|
||||||
const invoicesIds = paymentReceive.entries
|
const invoicesIds = paymentReceiveEntries.map((e) => e.invoiceId);
|
||||||
.map((e) => e.invoice_id);
|
const storedInvoices = await SaleInvoice.query().whereIn('id', invoicesIds);
|
||||||
|
const storedInvoicesIds = storedInvoices.map((invoice) => invoice.id);
|
||||||
|
|
||||||
|
const notFoundInvoicesIDs = difference(invoicesIds, storedInvoicesIds);
|
||||||
|
|
||||||
const notFoundInvoicesIDs = await this.saleInvoiceService.isInvoicesExist(
|
|
||||||
tenantId,
|
|
||||||
invoicesIds,
|
|
||||||
paymentReceive.customer_id,
|
|
||||||
);
|
|
||||||
if (notFoundInvoicesIDs.length > 0) {
|
if (notFoundInvoicesIDs.length > 0) {
|
||||||
return res.status(400).send({
|
throw new ServiceError(ERRORS.INVOICES_IDS_NOT_FOUND);
|
||||||
errors: [{ type: 'INVOICES.IDS.NOT.FOUND', code: 500 }],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,69 +155,30 @@ export default class PaymentReceiveService {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @param {Function} next -
|
* @param {Function} next -
|
||||||
*/
|
*/
|
||||||
async validateInvoicesPaymentsAmount(req: Request, res: Response, next: Function) {
|
async validateInvoicesPaymentsAmount(tenantId: number, paymentReceiveEntries: IPaymentReceiveEntryDTO[]) {
|
||||||
const { SaleInvoice } = req.models;
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const invoicesIds = req.body.entries.map((e) => e.invoice_id);
|
const invoicesIds = paymentReceiveEntries.map((e: IPaymentReceiveEntryDTO) => e.invoiceId);
|
||||||
|
|
||||||
const storedInvoices = await SaleInvoice.query()
|
const storedInvoices = await SaleInvoice.query().whereIn('id', invoicesIds);
|
||||||
.whereIn('id', invoicesIds);
|
|
||||||
|
|
||||||
const storedInvoicesMap = new Map(
|
const storedInvoicesMap = new Map(
|
||||||
storedInvoices.map((invoice) => [invoice.id, invoice])
|
storedInvoices.map((invoice: ISaleInvoice) => [invoice.id, invoice])
|
||||||
);
|
);
|
||||||
const hasWrongPaymentAmount: any[] = [];
|
const hasWrongPaymentAmount: any[] = [];
|
||||||
|
|
||||||
req.body.entries.forEach((entry, index: number) => {
|
paymentReceiveEntries.forEach((entry: IPaymentReceiveEntryDTO, index: number) => {
|
||||||
const entryInvoice = storedInvoicesMap.get(entry.invoice_id);
|
const entryInvoice = storedInvoicesMap.get(entry.invoiceId);
|
||||||
const { dueAmount } = entryInvoice;
|
const { dueAmount } = entryInvoice;
|
||||||
|
|
||||||
if (dueAmount < entry.payment_amount) {
|
if (dueAmount < entry.paymentAmount) {
|
||||||
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (hasWrongPaymentAmount.length > 0) {
|
if (hasWrongPaymentAmount.length > 0) {
|
||||||
return res.status(400).send({
|
throw new ServiceError(ERRORS.INVALID_PAYMENT_AMOUNT);
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: 'INVOICE.PAYMENT.AMOUNT',
|
|
||||||
code: 200,
|
|
||||||
indexes: hasWrongPaymentAmount,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the payment receive entries IDs existance.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @return {Response}
|
|
||||||
*/
|
|
||||||
async validateEntriesIdsExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const paymentReceive = { id: req.params.id, ...req.body };
|
|
||||||
const entriesIds = paymentReceive.entries
|
|
||||||
.filter(entry => entry.id)
|
|
||||||
.map(entry => entry.id);
|
|
||||||
|
|
||||||
const { PaymentReceiveEntry } = req.models;
|
|
||||||
|
|
||||||
const storedEntries = await PaymentReceiveEntry.query()
|
|
||||||
.where('payment_receive_id', paymentReceive.id);
|
|
||||||
|
|
||||||
const storedEntriesIds = storedEntries.map((entry) => entry.id);
|
|
||||||
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
|
||||||
|
|
||||||
if (notFoundEntriesIds.length > 0) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'ENTEIES.IDS.NOT.FOUND', code: 800 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -226,61 +186,42 @@ export default class PaymentReceiveService {
|
|||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {IPaymentReceive} paymentReceive
|
* @param {IPaymentReceive} paymentReceive
|
||||||
*/
|
*/
|
||||||
public async createPaymentReceive(tenantId: number, paymentReceive: IPaymentReceiveOTD) {
|
public async createPaymentReceive(tenantId: number, paymentReceiveDTO: IPaymentReceiveDTO) {
|
||||||
const {
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
PaymentReceive,
|
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
||||||
PaymentReceiveEntry,
|
|
||||||
SaleInvoice,
|
|
||||||
Customer,
|
|
||||||
} = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
|
// Validate payment receive number uniquiness.
|
||||||
|
await this.validatePaymentReceiveNoExistance(tenantId, paymentReceiveDTO.paymentReceiveNo);
|
||||||
|
|
||||||
|
// Validate customer existance.
|
||||||
|
await this.customersService.getCustomerByIdOrThrowError(tenantId, paymentReceiveDTO.customerId);
|
||||||
|
|
||||||
|
// Validate the deposit account existance and type.
|
||||||
|
await this.getDepositAccountOrThrowError(tenantId, paymentReceiveDTO.depositAccountId);
|
||||||
|
|
||||||
|
// Validate payment receive invoices IDs existance.
|
||||||
|
await this.validateInvoicesIDsExistance(tenantId, paymentReceiveDTO.entries);
|
||||||
|
|
||||||
|
// Validate invoice payment amount.
|
||||||
|
await this.validateInvoicesPaymentsAmount(tenantId, paymentReceiveDTO.entries);
|
||||||
|
|
||||||
this.logger.info('[payment_receive] inserting to the storage.');
|
this.logger.info('[payment_receive] inserting to the storage.');
|
||||||
const storedPaymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query()
|
||||||
.insertGraph({
|
.insertGraphAndFetch({
|
||||||
amount: paymentAmount,
|
amount: paymentAmount,
|
||||||
...formatDateFields(omit(paymentReceive, ['entries']), ['payment_date']),
|
...formatDateFields(omit(paymentReceiveDTO, ['entries']), ['paymentDate']),
|
||||||
entries: paymentReceive.entries.map((entry) => ({ ...entry })),
|
|
||||||
});
|
|
||||||
const storeOpers: Array<any> = [];
|
|
||||||
|
|
||||||
this.logger.info('[payment_receive] inserting associated entries to the storage.');
|
entries: paymentReceiveDTO.entries.map((entry) => ({
|
||||||
paymentReceive.entries.forEach((entry: any) => {
|
...omit(entry, ['id']),
|
||||||
const oper = PaymentReceiveEntry.query()
|
})),
|
||||||
.insert({
|
|
||||||
payment_receive_id: storedPaymentReceive.id,
|
|
||||||
...entry,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.info('[payment_receive] increment the sale invoice payment amount.');
|
await this.eventDispatcher.dispatch(events.paymentReceipts.onCreated, {
|
||||||
// Increment the invoice payment amount.
|
tenantId, paymentReceive, paymentReceiveId: paymentReceive.id,
|
||||||
const invoice = SaleInvoice.query()
|
|
||||||
.where('id', entry.invoice_id)
|
|
||||||
.increment('payment_amount', entry.payment_amount);
|
|
||||||
|
|
||||||
storeOpers.push(oper);
|
|
||||||
storeOpers.push(invoice);
|
|
||||||
});
|
});
|
||||||
|
this.logger.info('[payment_receive] updated successfully.', { tenantId, paymentReceive });
|
||||||
|
|
||||||
this.logger.info('[payment_receive] decrementing customer balance.');
|
return paymentReceive;
|
||||||
const customerIncrementOper = Customer.decrementBalance(
|
|
||||||
paymentReceive.customer_id,
|
|
||||||
paymentAmount,
|
|
||||||
);
|
|
||||||
// Records the sale invoice journal transactions.
|
|
||||||
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries(tenantId,{
|
|
||||||
id: storedPaymentReceive.id,
|
|
||||||
...paymentReceive,
|
|
||||||
});
|
|
||||||
await Promise.all([
|
|
||||||
...storeOpers,
|
|
||||||
customerIncrementOper,
|
|
||||||
recordJournalTransactions,
|
|
||||||
]);
|
|
||||||
await this.eventDispatcher.dispatch(events.paymentReceipts.onCreated);
|
|
||||||
|
|
||||||
return storedPaymentReceive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -297,84 +238,52 @@ export default class PaymentReceiveService {
|
|||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {Integer} paymentReceiveId -
|
* @param {Integer} paymentReceiveId -
|
||||||
* @param {IPaymentReceive} paymentReceive -
|
* @param {IPaymentReceive} paymentReceive -
|
||||||
* @param {IPaymentReceive} oldPaymentReceive -
|
|
||||||
*/
|
*/
|
||||||
public async editPaymentReceive(
|
public async editPaymentReceive(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveId: number,
|
paymentReceiveId: number,
|
||||||
paymentReceive: any,
|
paymentReceiveDTO: any,
|
||||||
oldPaymentReceive: any
|
|
||||||
) {
|
) {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
|
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
|
||||||
|
|
||||||
|
this.logger.info('[payment_receive] trying to edit payment receive.', { tenantId, paymentReceiveId, paymentReceiveDTO });
|
||||||
|
|
||||||
|
// Validate the payment receive existance.
|
||||||
|
const oldPaymentReceive = await this.getPaymentReceiveOrThrowError(tenantId, paymentReceiveId);
|
||||||
|
|
||||||
|
// Validate payment receive number uniquiness.
|
||||||
|
await this.validatePaymentReceiveNoExistance(tenantId, paymentReceiveDTO.paymentReceiveNo, paymentReceiveId);
|
||||||
|
|
||||||
|
// Validate the deposit account existance and type.
|
||||||
|
this.getDepositAccountOrThrowError(tenantId, paymentReceiveDTO.depositAccountId);
|
||||||
|
|
||||||
|
// Validate customer existance.
|
||||||
|
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
|
||||||
|
);
|
||||||
|
// Validate payment receive invoices IDs existance.
|
||||||
|
await this.validateInvoicesIDsExistance(tenantId, paymentReceiveDTO.entries);
|
||||||
|
|
||||||
|
// Validate invoice payment amount.
|
||||||
|
await this.validateInvoicesPaymentsAmount(tenantId, paymentReceiveDTO.entries);
|
||||||
|
|
||||||
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
|
|
||||||
// Update the payment receive transaction.
|
// Update the payment receive transaction.
|
||||||
const updatePaymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query()
|
||||||
.where('id', paymentReceiveId)
|
.upsertGraphAndFetch({
|
||||||
.update({
|
id: paymentReceiveId,
|
||||||
amount: paymentAmount,
|
amount: paymentAmount,
|
||||||
...formatDateFields(omit(paymentReceive, ['entries']), ['payment_date']),
|
...formatDateFields(omit(paymentReceiveDTO, ['entries']), ['paymentDate']),
|
||||||
|
entries: paymentReceiveDTO.entries,
|
||||||
});
|
});
|
||||||
const opers = [];
|
|
||||||
const entriesIds = paymentReceive.entries.filter((i: any) => i.id);
|
|
||||||
const entriesShouldInsert = paymentReceive.entries.filter((i: any) => !i.id);
|
|
||||||
|
|
||||||
// Detarmines which entries ids should be deleted.
|
await this.eventDispatcher.dispatch(events.paymentReceipts.onEdited, {
|
||||||
const entriesIdsShouldDelete = ServiceItemsEntries.entriesShouldDeleted(
|
tenantId, paymentReceiveId, paymentReceive, oldPaymentReceive
|
||||||
oldPaymentReceive.entries,
|
});
|
||||||
entriesIds
|
this.logger.info('[payment_receive] upserted successfully.', { tenantId, paymentReceiveId });
|
||||||
);
|
|
||||||
if (entriesIdsShouldDelete.length > 0) {
|
|
||||||
// Deletes the given payment receive entries.
|
|
||||||
const deleteOper = PaymentReceiveEntryRepository.deleteBulk(
|
|
||||||
entriesIdsShouldDelete
|
|
||||||
);
|
|
||||||
opers.push(deleteOper);
|
|
||||||
}
|
|
||||||
// Entries that should be updated to the storage.
|
|
||||||
if (entriesIds.length > 0) {
|
|
||||||
const updateOper = PaymentReceiveEntryRepository.updateBulk(entriesIds);
|
|
||||||
opers.push(updateOper);
|
|
||||||
}
|
|
||||||
// Entries should insert to the storage.
|
|
||||||
if (entriesShouldInsert.length > 0) {
|
|
||||||
const insertOper = PaymentReceiveEntryRepository.insertBulk(
|
|
||||||
entriesShouldInsert,
|
|
||||||
paymentReceiveId
|
|
||||||
);
|
|
||||||
opers.push(insertOper);
|
|
||||||
}
|
|
||||||
// Re-write the journal transactions of the given payment receive.
|
|
||||||
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries(
|
|
||||||
tenantId,
|
|
||||||
{
|
|
||||||
id: oldPaymentReceive.id,
|
|
||||||
...paymentReceive,
|
|
||||||
},
|
|
||||||
paymentReceiveId,
|
|
||||||
);
|
|
||||||
// Increment/decrement the customer balance after calc the diff
|
|
||||||
// between old and new value.
|
|
||||||
const changeCustomerBalance = CustomerRepository.changeDiffBalance(
|
|
||||||
paymentReceive.customer_id,
|
|
||||||
oldPaymentReceive.customerId,
|
|
||||||
paymentAmount * -1,
|
|
||||||
oldPaymentReceive.amount * -1,
|
|
||||||
);
|
|
||||||
// Change the difference between the old and new invoice payment amount.
|
|
||||||
const diffInvoicePaymentAmount = this.saveChangeInvoicePaymentAmount(
|
|
||||||
tenantId,
|
|
||||||
oldPaymentReceive.entries,
|
|
||||||
paymentReceive.entries
|
|
||||||
);
|
|
||||||
// Await the async operations.
|
|
||||||
await Promise.all([
|
|
||||||
...opers,
|
|
||||||
recordJournalTransactions,
|
|
||||||
changeCustomerBalance,
|
|
||||||
diffInvoicePaymentAmount,
|
|
||||||
]);
|
|
||||||
await this.eventDispatcher.dispatch(events.paymentReceipts.onEdited);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -392,43 +301,20 @@ export default class PaymentReceiveService {
|
|||||||
* @param {IPaymentReceive} paymentReceive - Payment receive object.
|
* @param {IPaymentReceive} paymentReceive - Payment receive object.
|
||||||
*/
|
*/
|
||||||
async deletePaymentReceive(tenantId: number, paymentReceiveId: number, paymentReceive: any) {
|
async deletePaymentReceive(tenantId: number, paymentReceiveId: number, paymentReceive: any) {
|
||||||
const { PaymentReceive, PaymentReceiveEntry, Customer } = this.tenancy.models(tenantId);
|
const { PaymentReceive, PaymentReceiveEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
// Deletes the payment receive transaction.
|
const oldPaymentReceive = this.getPaymentReceiveOrThrowError(tenantId, paymentReceiveId);
|
||||||
await PaymentReceive.query()
|
|
||||||
.where('id', paymentReceiveId)
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
// Deletes the payment receive associated entries.
|
// Deletes the payment receive associated entries.
|
||||||
await PaymentReceiveEntry.query()
|
await PaymentReceiveEntry.query().where('payment_receive_id', paymentReceiveId).delete();
|
||||||
.where('payment_receive_id', paymentReceiveId)
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
// Delete all associated journal transactions to payment receive transaction.
|
// Deletes the payment receive transaction.
|
||||||
const deleteTransactionsOper = this.journalService.deleteJournalTransactions(
|
await PaymentReceive.query().where('id', paymentReceiveId).delete();
|
||||||
tenantId,
|
|
||||||
paymentReceiveId,
|
await this.eventDispatcher.dispatch(events.paymentReceipts.onDeleted, {
|
||||||
'PaymentReceive'
|
tenantId, paymentReceiveId, oldPaymentReceive,
|
||||||
);
|
});
|
||||||
// Revert the customer balance.
|
this.logger.info('[payment_receive] deleted successfully.', { tenantId, paymentReceiveId });
|
||||||
const revertCustomerBalance = Customer.incrementBalance(
|
|
||||||
paymentReceive.customerId,
|
|
||||||
paymentReceive.amount
|
|
||||||
);
|
|
||||||
// Revert the invoices payments amount.
|
|
||||||
const revertInvoicesPaymentAmount = this.revertInvoicePaymentAmount(
|
|
||||||
tenantId,
|
|
||||||
paymentReceive.entries.map((entry: any) => ({
|
|
||||||
invoiceId: entry.invoiceId,
|
|
||||||
revertAmount: entry.paymentAmount,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
await Promise.all([
|
|
||||||
deleteTransactionsOper,
|
|
||||||
revertCustomerBalance,
|
|
||||||
revertInvoicesPaymentAmount,
|
|
||||||
]);
|
|
||||||
await this.eventDispatcher.dispatch(events.paymentReceipts.onDeleted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -439,9 +325,12 @@ export default class PaymentReceiveService {
|
|||||||
public async getPaymentReceive(tenantId: number, paymentReceiveId: number) {
|
public async getPaymentReceive(tenantId: number, paymentReceiveId: number) {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
const paymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query()
|
||||||
.where('id', paymentReceiveId)
|
.findById(paymentReceiveId)
|
||||||
.withGraphFetched('entries.invoice')
|
.withGraphFetched('entries.invoice');
|
||||||
.first();
|
|
||||||
|
if (!paymentReceive) {
|
||||||
|
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
|
||||||
|
}
|
||||||
return paymentReceive;
|
return paymentReceive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +339,10 @@ export default class PaymentReceiveService {
|
|||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IPaymentReceivesFilter} paymentReceivesFilter
|
* @param {IPaymentReceivesFilter} paymentReceivesFilter
|
||||||
*/
|
*/
|
||||||
public async listPaymentReceives(tenantId: number, paymentReceivesFilter: IPaymentReceivesFilter) {
|
public async listPaymentReceives(
|
||||||
|
tenantId: number,
|
||||||
|
paymentReceivesFilter: IPaymentReceivesFilter,
|
||||||
|
): Promise<{ paymentReceives: IPaymentReceive[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, PaymentReceive, paymentReceivesFilter);
|
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, PaymentReceive, paymentReceivesFilter);
|
||||||
|
|
||||||
@@ -481,41 +373,6 @@ export default class PaymentReceiveService {
|
|||||||
.first();
|
.first();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the payment receive exists on the storage.
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {Integer} paymentReceiveId
|
|
||||||
*/
|
|
||||||
async isPaymentReceiveExists(tenantId: number, paymentReceiveId: number) {
|
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
|
||||||
const paymentReceives = await PaymentReceive.query()
|
|
||||||
.where('id', paymentReceiveId);
|
|
||||||
return paymentReceives.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines the payment receive number existance.
|
|
||||||
* @async
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {Integer} paymentReceiveNumber - Payment receive number.
|
|
||||||
* @param {Integer} paymentReceiveId - Payment receive id.
|
|
||||||
*/
|
|
||||||
async isPaymentReceiveNoExists(
|
|
||||||
tenantId: number,
|
|
||||||
paymentReceiveNumber: string|number,
|
|
||||||
paymentReceiveId: number
|
|
||||||
) {
|
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
|
||||||
const paymentReceives = await PaymentReceive.query()
|
|
||||||
.where('payment_receive_no', paymentReceiveNumber)
|
|
||||||
.onBuild((query) => {
|
|
||||||
if (paymentReceiveId) {
|
|
||||||
query.whereNot('id', paymentReceiveId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return paymentReceives.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records payment receive journal transactions.
|
* Records payment receive journal transactions.
|
||||||
*
|
*
|
||||||
@@ -581,26 +438,6 @@ export default class PaymentReceiveService {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Revert the payment amount of the given invoices ids.
|
|
||||||
* @async
|
|
||||||
* @param {number} tenantId - Tenant id.
|
|
||||||
* @param {Array} revertInvoices
|
|
||||||
* @return {Promise}
|
|
||||||
*/
|
|
||||||
private async revertInvoicePaymentAmount(tenantId: number, revertInvoices: any[]) {
|
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
|
||||||
const opers: Promise<T>[] = [];
|
|
||||||
|
|
||||||
revertInvoices.forEach((revertInvoice) => {
|
|
||||||
const { revertAmount, invoiceId } = revertInvoice;
|
|
||||||
const oper = SaleInvoice.query()
|
|
||||||
.where('id', invoiceId)
|
|
||||||
.decrement('payment_amount', revertAmount);
|
|
||||||
opers.push(oper);
|
|
||||||
});
|
|
||||||
await Promise.all(opers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves difference changing between old and new invoice payment amount.
|
* Saves difference changing between old and new invoice payment amount.
|
||||||
@@ -610,34 +447,35 @@ export default class PaymentReceiveService {
|
|||||||
* @param {Array} newPaymentReceiveEntries
|
* @param {Array} newPaymentReceiveEntries
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private async saveChangeInvoicePaymentAmount(
|
public async saveChangeInvoicePaymentAmount(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveEntries: [],
|
newPaymentReceiveEntries: IPaymentReceiveEntryDTO[],
|
||||||
newPaymentReceiveEntries: [],
|
oldPaymentReceiveEntries?: IPaymentReceiveEntryDTO[],
|
||||||
) {
|
): Promise<void> {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const opers: Promise<T>[] = [];
|
const opers: Promise<T>[] = [];
|
||||||
const newEntriesTable = chain(newPaymentReceiveEntries)
|
|
||||||
.groupBy('invoice_id')
|
|
||||||
.mapValues((group) => (sumBy(group, 'payment_amount') || 0) * -1)
|
|
||||||
.value();
|
|
||||||
|
|
||||||
const diffEntries = chain(paymentReceiveEntries)
|
const oldEntriesTable = chain(oldPaymentReceiveEntries)
|
||||||
.groupBy('invoiceId')
|
.groupBy('invoiceId')
|
||||||
.mapValues((group) => (sumBy(group, 'paymentAmount') || 0) * -1)
|
.mapValues((group) => (sumBy(group, 'paymentAmount') || 0) * -1)
|
||||||
.mapValues((value, key) => value - (newEntriesTable[key] || 0))
|
.value();
|
||||||
.mapValues((value, key) => ({ invoice_id: key, payment_amount: value }))
|
|
||||||
.filter((entry) => entry.payment_amount != 0)
|
const diffEntries = chain(newPaymentReceiveEntries)
|
||||||
|
.groupBy('invoiceId')
|
||||||
|
.mapValues((group) => (sumBy(group, 'paymentAmount') || 0))
|
||||||
|
.mapValues((value, key) => value - (oldEntriesTable[key] || 0))
|
||||||
|
.mapValues((value, key) => ({ invoiceId: key, paymentAmount: value }))
|
||||||
|
.filter((entry) => entry.paymentAmount != 0)
|
||||||
.values()
|
.values()
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
diffEntries.forEach((diffEntry: any) => {
|
diffEntries.forEach((diffEntry: any) => {
|
||||||
const oper = SaleInvoice.changePaymentAmount(
|
const oper = SaleInvoice.changePaymentAmount(
|
||||||
diffEntry.invoice_id,
|
diffEntry.invoiceId,
|
||||||
diffEntry.payment_amount
|
diffEntry.paymentAmount
|
||||||
);
|
);
|
||||||
opers.push(oper);
|
opers.push(oper);
|
||||||
});
|
});
|
||||||
return Promise.all([ ...opers ]);
|
await Promise.all([ ...opers ]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||||
import HasItemsEntries from 'services/Sales/HasItemsEntries';
|
|
||||||
import InventoryService from 'services/Inventory/Inventory';
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
@@ -22,14 +21,17 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
|||||||
import { formatDateFields } from 'utils';
|
import { formatDateFields } from 'utils';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import ItemsService from 'services/Items/ItemsService';
|
import ItemsService from 'services/Items/ItemsService';
|
||||||
|
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||||
|
import CustomersService from 'services/Contacts/CustomersService';
|
||||||
|
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
|
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
||||||
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
||||||
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
||||||
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
||||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE'
|
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE'
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sales invoices service
|
* Sales invoices service
|
||||||
@@ -44,7 +46,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
inventoryService: InventoryService;
|
inventoryService: InventoryService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
itemsEntriesService: HasItemsEntries;
|
itemsEntriesService: ItemsEntriesService;
|
||||||
|
|
||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
@@ -58,14 +60,43 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
@Inject()
|
@Inject()
|
||||||
itemsService: ItemsService;
|
itemsService: ItemsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
customersService: CustomersService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve sale invoice or throw not found error.
|
*
|
||||||
* @param {number} tenantId
|
* Validate whether sale invoice number unqiue on the storage.
|
||||||
* @param {number} saleInvoiceId
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
private async getSaleInvoiceOrThrowError(tenantId: number, saleInvoiceId: number): Promise<ISaleInvoice> {
|
async validateInvoiceNumberUnique(tenantId: number, invoiceNumber: string, notInvoiceId?: number) {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const saleInvoice = await SaleInvoice.query().where('id', saleInvoiceId);
|
|
||||||
|
this.logger.info('[sale_invoice] validating sale invoice number existance.', { tenantId, invoiceNumber });
|
||||||
|
const saleInvoice = await SaleInvoice.query()
|
||||||
|
.findOne('invoice_no', invoiceNumber)
|
||||||
|
.onBuild((builder) => {
|
||||||
|
if (notInvoiceId) {
|
||||||
|
builder.whereNot('id', notInvoiceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (saleInvoice) {
|
||||||
|
this.logger.info('[sale_invoice] sale invoice number not unique.', { tenantId, invoiceNumber });
|
||||||
|
throw new ServiceError(ERRORS.INVOICE_NUMBER_NOT_UNIQUE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate whether sale invoice exists on the storage.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
async getInvoiceOrThrowError(tenantId: number, saleInvoiceId: number) {
|
||||||
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
const saleInvoice = await SaleInvoice.query().findById(saleInvoiceId);
|
||||||
|
|
||||||
if (!saleInvoice) {
|
if (!saleInvoice) {
|
||||||
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
||||||
@@ -73,63 +104,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
return saleInvoice;
|
return saleInvoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether sale invoice number unqiue on the storage.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} saleInvoiceNo
|
|
||||||
* @param {number} notSaleInvoiceId
|
|
||||||
*/
|
|
||||||
private async validateSaleInvoiceNoUniquiness(tenantId: number, saleInvoiceNo: string, notSaleInvoiceId: number) {
|
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const foundSaleInvoice = await SaleInvoice.query()
|
|
||||||
.onBuild((query: any) => {
|
|
||||||
query.where('invoice_no', saleInvoiceNo);
|
|
||||||
|
|
||||||
if (notSaleInvoiceId) {
|
|
||||||
query.whereNot('id', notSaleInvoiceId);
|
|
||||||
}
|
|
||||||
return query;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (foundSaleInvoice.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.SALE_INVOICE_NO_NOT_UNIQUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates sale invoice items that not sellable.
|
|
||||||
*/
|
|
||||||
private async validateNonSellableEntriesItems(tenantId: number, saleInvoiceEntries: any) {
|
|
||||||
const { Item } = this.tenancy.models(tenantId);
|
|
||||||
const itemsIds = saleInvoiceEntries.map(e => e.itemId);
|
|
||||||
|
|
||||||
const sellableItems = await Item.query().where('sellable', true).whereIn('id', itemsIds);
|
|
||||||
|
|
||||||
const sellableItemsIds = sellableItems.map((item) => item.id);
|
|
||||||
const notSellableItems = difference(itemsIds, sellableItemsIds);
|
|
||||||
|
|
||||||
if (notSellableItems.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {} saleInvoiceEntries
|
|
||||||
*/
|
|
||||||
validateEntriesIdsExistance(tenantId: number, saleInvoiceEntries: any) {
|
|
||||||
const entriesItemsIds = saleInvoiceEntries.map((e) => e.item_id);
|
|
||||||
|
|
||||||
const isItemsIdsExists = await this.itemsService.isItemsIdsExists(
|
|
||||||
tenantId, entriesItemsIds,
|
|
||||||
);
|
|
||||||
if (isItemsIdsExists.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.ENTRIES_ITEMS_IDS_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -138,62 +112,46 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
* @param {ISaleInvoice} saleInvoiceDTO -
|
* @param {ISaleInvoice} saleInvoiceDTO -
|
||||||
* @return {ISaleInvoice}
|
* @return {ISaleInvoice}
|
||||||
*/
|
*/
|
||||||
public async createSaleInvoice(tenantId: number, saleInvoiceDTO: ISaleInvoiceOTD) {
|
public async createSaleInvoice(tenantId: number, saleInvoiceDTO: ISaleInvoiceOTD): Promise<ISaleInvoice> {
|
||||||
const { SaleInvoice, Customer, ItemEntry } = this.tenancy.models(tenantId);
|
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||||
const invLotNumber = await this.inventoryService.nextLotNumber(tenantId);
|
const invLotNumber = 1;
|
||||||
|
|
||||||
const saleInvoice: ISaleInvoice = {
|
const saleInvoiceObj: ISaleInvoice = {
|
||||||
...formatDateFields(saleInvoiceDTO, ['invoice_date', 'due_date']),
|
...formatDateFields(saleInvoiceDTO, ['invoice_date', 'due_date']),
|
||||||
balance,
|
balance,
|
||||||
paymentAmount: 0,
|
paymentAmount: 0,
|
||||||
invLotNumber,
|
// invLotNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.validateSaleInvoiceNoUniquiness(tenantId, saleInvoiceDTO.invoiceNo);
|
// Validate customer existance.
|
||||||
await this.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
|
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
|
||||||
|
|
||||||
|
// Validate sale invoice number uniquiness.
|
||||||
|
await this.validateInvoiceNumberUnique(tenantId, saleInvoiceDTO.invoiceNo);
|
||||||
|
|
||||||
|
// Validate items ids existance.
|
||||||
|
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleInvoiceDTO.entries);
|
||||||
|
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
|
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
|
||||||
const storedInvoice = await SaleInvoice.query()
|
const saleInvoice = await SaleInvoice.query()
|
||||||
.insert({
|
.insertGraph({
|
||||||
...omit(saleInvoice, ['entries']),
|
...omit(saleInvoiceObj, ['entries']),
|
||||||
});
|
|
||||||
const opers: Array<any> = [];
|
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] inserting sale invoice entries to the storage.');
|
entries: saleInvoiceObj.entries.map((entry) => ({
|
||||||
saleInvoice.entries.forEach((entry: any) => {
|
|
||||||
const oper = ItemEntry.query()
|
|
||||||
.insertAndFetch({
|
|
||||||
reference_type: 'SaleInvoice',
|
reference_type: 'SaleInvoice',
|
||||||
reference_id: storedInvoice.id,
|
|
||||||
...omit(entry, ['amount', 'id']),
|
...omit(entry, ['amount', 'id']),
|
||||||
}).then((itemEntry) => {
|
}))
|
||||||
entry.id = itemEntry.id;
|
|
||||||
});
|
|
||||||
opers.push(oper);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] trying to increment the customer balance.');
|
await this.eventDispatcher.dispatch(events.saleInvoice.onCreated, {
|
||||||
// Increment the customer balance after deliver the sale invoice.
|
tenantId, saleInvoice, saleInvoiceId: saleInvoice.id,
|
||||||
const incrementOper = Customer.incrementBalance(
|
});
|
||||||
saleInvoice.customer_id,
|
this.logger.info('[sale_invoice] successfully inserted.', { tenantId, saleInvoice });
|
||||||
balance,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Await all async operations.
|
return saleInvoice;
|
||||||
await Promise.all([ ...opers, incrementOper ]);
|
|
||||||
|
|
||||||
// Records the inventory transactions for inventory items.
|
|
||||||
await this.recordInventoryTranscactions(tenantId, saleInvoice, storedInvoice.id);
|
|
||||||
|
|
||||||
// Schedule sale invoice re-compute based on the item cost
|
|
||||||
// method and starting date.
|
|
||||||
await this.scheduleComputeInvoiceItemsCost(tenantId, storedInvoice.id);
|
|
||||||
|
|
||||||
await this.eventDispatcher.dispatch(events.saleInvoice.onCreated);
|
|
||||||
|
|
||||||
return storedInvoice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,37 +161,51 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
* @param {Number} saleInvoiceId -
|
* @param {Number} saleInvoiceId -
|
||||||
* @param {ISaleInvoice} saleInvoice -
|
* @param {ISaleInvoice} saleInvoice -
|
||||||
*/
|
*/
|
||||||
public async editSaleInvoice(tenantId: number, saleInvoiceId: number, saleInvoiceDTO: any) {
|
public async editSaleInvoice(tenantId: number, saleInvoiceId: number, saleInvoiceDTO: any): Promise<ISaleInvoice> {
|
||||||
const { SaleInvoice, ItemEntry, Customer } = this.tenancy.models(tenantId);
|
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||||
const oldSaleInvoice = await SaleInvoice.query()
|
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||||
.where('id', saleInvoiceId)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
const saleInvoice = {
|
const saleInvoiceObj = {
|
||||||
...formatDateFields(saleInvoiceDTO, ['invoice_date', 'due_date']),
|
...formatDateFields(saleInvoiceDTO, ['invoice_date', 'due_date']),
|
||||||
balance,
|
balance,
|
||||||
invLotNumber: oldSaleInvoice.invLotNumber,
|
// invLotNumber: oldSaleInvoice.invLotNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] trying to update sale invoice.');
|
// Validate customer existance.
|
||||||
const updatedSaleInvoices: ISaleInvoice = await SaleInvoice.query()
|
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
|
||||||
.where('id', saleInvoiceId)
|
|
||||||
.update({
|
// Validate sale invoice number uniquiness.
|
||||||
...omit(saleInvoice, ['entries', 'invLotNumber']),
|
await this.validateInvoiceNumberUnique(tenantId, saleInvoiceDTO.invoiceNo, saleInvoiceId);
|
||||||
});
|
|
||||||
// Fetches the sale invoice items entries.
|
// Validate items ids existance.
|
||||||
const storedEntries = await ItemEntry.query()
|
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleInvoiceDTO.entries);
|
||||||
.where('reference_id', saleInvoiceId)
|
|
||||||
.where('reference_type', 'SaleInvoice');
|
// Validate non-sellable entries items.
|
||||||
|
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
|
||||||
|
|
||||||
|
// Validate the items entries existance.
|
||||||
|
await this.itemsEntriesService.validateEntriesIdsExistance(tenantId, saleInvoiceId, 'SaleInvoice', saleInvoiceDTO.entries);
|
||||||
|
|
||||||
|
this.logger.info('[sale_invoice] trying to update sale invoice.');
|
||||||
|
const saleInvoice: ISaleInvoice = await SaleInvoice.query()
|
||||||
|
.upsertGraph({
|
||||||
|
id: saleInvoiceId,
|
||||||
|
...omit(saleInvoiceObj, ['entries', 'invLotNumber']),
|
||||||
|
|
||||||
|
entries: saleInvoiceObj.entries.map((entry) => ({
|
||||||
|
reference_type: 'SaleInvoice',
|
||||||
|
...omit(entry, ['amount']),
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
// Patch update the sale invoice items entries.
|
|
||||||
await this.itemsEntriesService.patchItemsEntries(
|
|
||||||
tenantId, saleInvoice.entries, storedEntries, 'SaleInvoice', saleInvoiceId,
|
|
||||||
);
|
|
||||||
// Triggers `onSaleInvoiceEdited` event.
|
// Triggers `onSaleInvoiceEdited` event.
|
||||||
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited);
|
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
|
||||||
|
saleInvoice, oldSaleInvoice, tenantId, saleInvoiceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return saleInvoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -242,16 +214,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
* @async
|
* @async
|
||||||
* @param {Number} saleInvoiceId - The given sale invoice id.
|
* @param {Number} saleInvoiceId - The given sale invoice id.
|
||||||
*/
|
*/
|
||||||
public async deleteSaleInvoice(tenantId: number, saleInvoiceId: number) {
|
public async deleteSaleInvoice(tenantId: number, saleInvoiceId: number): Promise<void> {
|
||||||
const {
|
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
|
||||||
SaleInvoice,
|
|
||||||
ItemEntry,
|
|
||||||
Customer,
|
|
||||||
InventoryTransaction,
|
|
||||||
AccountTransaction,
|
|
||||||
} = this.tenancy.models(tenantId);
|
|
||||||
|
|
||||||
const oldSaleInvoice = await this.getSaleInvoiceOrThrowError(tenantId, saleInvoiceId);
|
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] delete sale invoice with entries.');
|
this.logger.info('[sale_invoice] delete sale invoice with entries.');
|
||||||
await SaleInvoice.query().where('id', saleInvoiceId).delete();
|
await SaleInvoice.query().where('id', saleInvoiceId).delete();
|
||||||
@@ -260,42 +226,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
.where('reference_type', 'SaleInvoice')
|
.where('reference_type', 'SaleInvoice')
|
||||||
.delete();
|
.delete();
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] revert the customer balance.');
|
await this.eventDispatcher.dispatch(events.saleInvoice.onDeleted, {
|
||||||
const revertCustomerBalanceOper = Customer.changeBalance(
|
tenantId, oldSaleInvoice,
|
||||||
oldSaleInvoice.customerId,
|
});
|
||||||
oldSaleInvoice.balance * -1,
|
|
||||||
);
|
|
||||||
const invoiceTransactions = await AccountTransaction.query()
|
|
||||||
.whereIn('reference_type', ['SaleInvoice'])
|
|
||||||
.where('reference_id', saleInvoiceId)
|
|
||||||
.withGraphFetched('account.type');
|
|
||||||
|
|
||||||
const journal = new JournalPoster(tenantId);
|
|
||||||
|
|
||||||
journal.loadEntries(invoiceTransactions);
|
|
||||||
journal.removeEntries();
|
|
||||||
|
|
||||||
const inventoryTransactions = await InventoryTransaction.query()
|
|
||||||
.where('transaction_type', 'SaleInvoice')
|
|
||||||
.where('transaction_id', saleInvoiceId);
|
|
||||||
|
|
||||||
// Revert inventory transactions.
|
|
||||||
const revertInventoryTransactionsOper = this.revertInventoryTransactions(
|
|
||||||
tenantId,
|
|
||||||
inventoryTransactions,
|
|
||||||
);
|
|
||||||
// Await all async operations.
|
|
||||||
await Promise.all([
|
|
||||||
journal.deleteEntries(),
|
|
||||||
journal.saveBalance(),
|
|
||||||
revertCustomerBalanceOper,
|
|
||||||
revertInventoryTransactionsOper,
|
|
||||||
]);
|
|
||||||
// Schedule sale invoice re-compute based on the item cost
|
|
||||||
// method and starting date.
|
|
||||||
await this.scheduleComputeItemsCost(tenantId, oldSaleInvoice)
|
|
||||||
|
|
||||||
await this.eventDispatcher.dispatch(events.saleInvoice.onDeleted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -378,60 +311,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
.first();
|
.first();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines the sale invoice number id exists on the storage.
|
|
||||||
* @param {Integer} saleInvoiceId
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
async isSaleInvoiceExists(tenantId: number, saleInvoiceId: number) {
|
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
|
||||||
const foundSaleInvoice = await SaleInvoice.query()
|
|
||||||
.where('id', saleInvoiceId);
|
|
||||||
|
|
||||||
return foundSaleInvoice.length !== 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines the sale invoice number exists on the storage.
|
|
||||||
* @async
|
|
||||||
* @param {Number|String} saleInvoiceNumber
|
|
||||||
* @param {Number} saleInvoiceId
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
async isSaleInvoiceNumberExists(
|
|
||||||
tenantId: number,
|
|
||||||
saleInvoiceNumber: string|number,
|
|
||||||
saleInvoiceId: number
|
|
||||||
) {
|
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
|
||||||
const foundSaleInvoice = await SaleInvoice.query()
|
|
||||||
.onBuild((query: any) => {
|
|
||||||
query.where('invoice_no', saleInvoiceNumber);
|
|
||||||
if (saleInvoiceId) {
|
|
||||||
query.whereNot('id', saleInvoiceId);
|
|
||||||
}
|
|
||||||
return query;
|
|
||||||
});
|
|
||||||
return (foundSaleInvoice.length !== 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmine the invoices IDs in bulk and returns the not found ones.
|
|
||||||
* @param {Array} invoicesIds
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
async isInvoicesExist(tenantId: number, invoicesIds: Array<number>) {
|
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
|
||||||
const storedInvoices = await SaleInvoice.query()
|
|
||||||
.onBuild((builder: any) => {
|
|
||||||
builder.whereIn('id', invoicesIds);
|
|
||||||
return builder;
|
|
||||||
});
|
|
||||||
const storedInvoicesIds = storedInvoices.map((i) => i.id);
|
|
||||||
const notStoredInvoices = difference(invoicesIds, storedInvoicesIds);
|
|
||||||
return notStoredInvoices;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules compute sale invoice items cost based on each item
|
* Schedules compute sale invoice items cost based on each item
|
||||||
* cost method.
|
* cost method.
|
||||||
@@ -501,8 +380,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
public async salesInvoicesList(tenantId: number, salesInvoicesFilter: ISalesInvoicesFilter):
|
public async salesInvoicesList(
|
||||||
Promise<{ salesInvoices: ISaleInvoice[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
|
tenantId: number,
|
||||||
|
salesInvoicesFilter: ISalesInvoicesFilter
|
||||||
|
): Promise<{ salesInvoices: ISaleInvoice[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleInvoice, salesInvoicesFilter);
|
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleInvoice, salesInvoicesFilter);
|
||||||
|
|
||||||
@@ -515,6 +396,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
salesInvoicesFilter.page - 1,
|
salesInvoicesFilter.page - 1,
|
||||||
salesInvoicesFilter.pageSize,
|
salesInvoicesFilter.pageSize,
|
||||||
);
|
);
|
||||||
return { salesInvoices: results, pagination, filterMeta: dynamicFilter.getResponseMeta() };
|
return {
|
||||||
|
salesInvoices: results,
|
||||||
|
pagination,
|
||||||
|
filterMeta: dynamicFilter.getResponseMeta(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default class BillSubscriber {
|
|||||||
async handlerWriteJournalEntries({ tenantId, billId, bill }) {
|
async handlerWriteJournalEntries({ tenantId, billId, bill }) {
|
||||||
// Writes the journal entries for the given bill transaction.
|
// Writes the journal entries for the given bill transaction.
|
||||||
this.logger.info('[bill] writing bill journal entries.', { tenantId });
|
this.logger.info('[bill] writing bill journal entries.', { tenantId });
|
||||||
await this.billsService.recordJournalTransactions(tenantId, bill);
|
await this.billsService.recordJournalTransactions(tenantId, bill, billId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,18 +66,20 @@ export default class BillSubscriber {
|
|||||||
await this.journalPosterService.revertJournalTransactions(tenantId, billId, 'Bill');
|
await this.journalPosterService.revertJournalTransactions(tenantId, billId, 'Bill');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles vendor balance difference change.
|
||||||
|
*/
|
||||||
@On(events.bills.onEdited)
|
@On(events.bills.onEdited)
|
||||||
async handleCustomerBalanceDiffChange({ tenantId, billId, oldBill, bill }) {
|
async handleVendorBalanceDiffChange({ tenantId, billId, oldBill, bill }) {
|
||||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
// Changes the diff vendor balance between old and new amount.
|
// Changes the diff vendor balance between old and new amount.
|
||||||
this.logger.info('[bill[ change vendor the different balance.', { tenantId, billId });
|
this.logger.info('[bill[ change vendor the different balance.', { tenantId, billId });
|
||||||
await vendorRepository.changeDiffBalance(
|
await vendorRepository.changeDiffBalance(
|
||||||
bill.vendorId,
|
bill.vendorId,
|
||||||
oldBill.vendorId,
|
|
||||||
bill.amount,
|
bill.amount,
|
||||||
oldBill.amount,
|
oldBill.amount,
|
||||||
|
oldBill.vendorId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ export default class PaymentMadesSubscriber {
|
|||||||
* Handles bills payment amount increment once payment made created.
|
* Handles bills payment amount increment once payment made created.
|
||||||
*/
|
*/
|
||||||
@On(events.billPayments.onCreated)
|
@On(events.billPayments.onCreated)
|
||||||
async handleBillsIncrement({ tenantId, billPayment, billPaymentId }) {
|
async handleBillsIncrementPaymentAmount({ tenantId, billPayment, billPaymentId }) {
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
const storeOpers = [];
|
const storeOpers = [];
|
||||||
|
|
||||||
|
|||||||
88
server/src/subscribers/paymentReceives.ts
Normal file
88
server/src/subscribers/paymentReceives.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { Container, Inject, Service } from 'typedi';
|
||||||
|
import { EventSubscriber, On } from 'event-dispatch';
|
||||||
|
import events from 'subscribers/events';
|
||||||
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
|
import PaymentReceiveService from 'services/Sales/PaymentsReceives';
|
||||||
|
|
||||||
|
@EventSubscriber()
|
||||||
|
export default class PaymentReceivesSubscriber {
|
||||||
|
tenancy: TenancyService;
|
||||||
|
logger: any;
|
||||||
|
paymentReceivesService: PaymentReceiveService;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.tenancy = Container.get(TenancyService);
|
||||||
|
this.logger = Container.get('logger');
|
||||||
|
this.paymentReceivesService = Container.get(PaymentReceiveService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle customer balance decrement once payment receive created.
|
||||||
|
*/
|
||||||
|
@On(events.paymentReceipts.onCreated)
|
||||||
|
async handleCustomerBalanceDecrement({ tenantId, paymentReceiveId, paymentReceive }) {
|
||||||
|
const { customerRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
this.logger.info('[payment_receive] trying to decrement customer balance.');
|
||||||
|
await customerRepository.changeBalance(paymentReceive.customerId, paymentReceive.amount * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle sale invoice increment/decrement payment amount once created, edited or deleted.
|
||||||
|
*/
|
||||||
|
@On(events.paymentReceipts.onCreated)
|
||||||
|
@On(events.paymentReceipts.onEdited)
|
||||||
|
async handleInvoiceIncrementPaymentAmount({ tenantId, paymentReceiveId, paymentReceive, oldPaymentReceive }) {
|
||||||
|
|
||||||
|
this.logger.info('[payment_receive] trying to change sale invoice payment amount.', { tenantId });
|
||||||
|
await this.paymentReceivesService.saveChangeInvoicePaymentAmount(
|
||||||
|
tenantId,
|
||||||
|
paymentReceive.entries,
|
||||||
|
oldPaymentReceive?.entries || null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle sale invoice diff payment amount change on payment receive edited.
|
||||||
|
*/
|
||||||
|
@On(events.paymentReceipts.onEdited)
|
||||||
|
async handleInvoiceDecrementPaymentAmount({ tenantId, paymentReceiveId, paymentReceive, oldPaymentReceive }) {
|
||||||
|
this.logger.info('[payment_receive] trying to decrement sale invoice payment amount.');
|
||||||
|
|
||||||
|
await this.paymentReceivesService.saveChangeInvoicePaymentAmount(
|
||||||
|
tenantId,
|
||||||
|
paymentReceive.entries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
paymentAmount: entry.paymentAmount * -1,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle customer balance increment once payment receive deleted.
|
||||||
|
*/
|
||||||
|
@On(events.paymentReceipts.onDeleted)
|
||||||
|
async handleCustomerBalanceIncrement({ tenantId, paymentReceiveId, oldPaymentReceive }) {
|
||||||
|
const { customerRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
this.logger.info('[payment_receive] trying to increment customer balance.');
|
||||||
|
await customerRepository.changeBalance(oldPaymentReceive.customerId, oldPaymentReceive.amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles customer balance diff balance change once payment receive edited.
|
||||||
|
*/
|
||||||
|
@On(events.paymentReceipts.onEdited)
|
||||||
|
async handleCustomerBalanceDiffChange({ tenantId, paymentReceiveId, paymentReceive, oldPaymentReceive }) {
|
||||||
|
const { customerRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
console.log(paymentReceive, oldPaymentReceive, 'XX');
|
||||||
|
|
||||||
|
await customerRepository.changeDiffBalance(
|
||||||
|
paymentReceive.customerId,
|
||||||
|
paymentReceive.amount * -1,
|
||||||
|
oldPaymentReceive.amount * -1,
|
||||||
|
oldPaymentReceive.customerId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,56 @@
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { On, EventSubscriber } from "event-dispatch";
|
import { On, EventSubscriber } from "event-dispatch";
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
|
|
||||||
@EventSubscriber()
|
@EventSubscriber()
|
||||||
export default class SaleInvoiceSubscriber {
|
export default class SaleInvoiceSubscriber {
|
||||||
|
logger: any;
|
||||||
|
tenancy: TenancyService;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.logger = Container.get('logger');
|
||||||
|
this.tenancy = Container.get(TenancyService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles customer balance increment once sale invoice created.
|
||||||
|
*/
|
||||||
@On(events.saleInvoice.onCreated)
|
@On(events.saleInvoice.onCreated)
|
||||||
public onSaleInvoiceCreated(payload) {
|
public async handleCustomerBalanceIncrement({ tenantId, saleInvoice, saleInvoiceId }) {
|
||||||
|
const { customerRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
this.logger.info('[sale_invoice] trying to increment customer balance.', { tenantId });
|
||||||
|
await customerRepository.changeBalance(saleInvoice.customerId, saleInvoice.balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles customer balance diff balnace change once sale invoice edited.
|
||||||
|
*/
|
||||||
@On(events.saleInvoice.onEdited)
|
@On(events.saleInvoice.onEdited)
|
||||||
public onSaleInvoiceEdited(payload) {
|
public async onSaleInvoiceEdited({ tenantId, saleInvoice, oldSaleInvoice, saleInvoiceId }) {
|
||||||
|
const { customerRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
this.logger.info('[sale_invoice] trying to change diff customer balance.', { tenantId });
|
||||||
|
await customerRepository.changeDiffBalance(
|
||||||
|
saleInvoice.customerId,
|
||||||
|
saleInvoice.balance,
|
||||||
|
oldSaleInvoice.balance,
|
||||||
|
oldSaleInvoice.customerId,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles customer balance decrement once sale invoice deleted.
|
||||||
|
*/
|
||||||
@On(events.saleInvoice.onDeleted)
|
@On(events.saleInvoice.onDeleted)
|
||||||
public onSaleInvoiceDeleted(payload) {
|
public async handleCustomerBalanceDecrement({ tenantId, saleInvoiceId, oldSaleInvoice }) {
|
||||||
|
const { customerRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
|
this.logger.info('[sale_invoice] trying to decrement customer balance.', { tenantId });
|
||||||
|
await customerRepository.changeBalance(
|
||||||
|
oldSaleInvoice.customerId,
|
||||||
|
oldSaleInvoice.balance * -1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user