refactor: split the services to multiple service classes (#202)
This commit is contained in:
7
e2e/items.spec.ts
Normal file
7
e2e/items.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
test.describe('item', () => {
|
||||
test('should validate all required fields.', () => {});
|
||||
test('should save the item successfully.', () => {});
|
||||
test('should item code be unqiue.', () => {});
|
||||
});
|
||||
@@ -1,26 +1,27 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query } from 'express-validator';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { AbilitySubject, BillAction, IBillDTO, IBillEditDTO } from '@/interfaces';
|
||||
import {
|
||||
AbilitySubject,
|
||||
BillAction,
|
||||
IBillDTO,
|
||||
IBillEditDTO,
|
||||
} from '@/interfaces';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import BillsService from '@/services/Purchases/Bills';
|
||||
import BaseController from '@/api/controllers/BaseController';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import BillPaymentsService from '@/services/Purchases/BillPaymentsService';
|
||||
import { BillsApplication } from '@/services/Purchases/Bills/BillsApplication';
|
||||
|
||||
@Service()
|
||||
export default class BillsController extends BaseController {
|
||||
@Inject()
|
||||
private billsService: BillsService;
|
||||
private billsApplication: BillsApplication;
|
||||
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
private billPayments: BillPaymentsService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
@@ -97,7 +98,7 @@ export default class BillsController extends BaseController {
|
||||
/**
|
||||
* Common validation schema.
|
||||
*/
|
||||
get billValidationSchema() {
|
||||
private get billValidationSchema() {
|
||||
return [
|
||||
check('bill_number').exists().trim().escape(),
|
||||
check('reference_no').optional().trim().escape(),
|
||||
@@ -142,7 +143,7 @@ export default class BillsController extends BaseController {
|
||||
/**
|
||||
* Common validation schema.
|
||||
*/
|
||||
get billEditValidationSchema() {
|
||||
private get billEditValidationSchema() {
|
||||
return [
|
||||
check('bill_number').optional().trim().escape(),
|
||||
check('reference_no').optional().trim().escape(),
|
||||
@@ -184,14 +185,14 @@ export default class BillsController extends BaseController {
|
||||
/**
|
||||
* Bill validation schema.
|
||||
*/
|
||||
get specificBillValidationSchema() {
|
||||
private get specificBillValidationSchema() {
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Bills list validation schema.
|
||||
*/
|
||||
get billsListingValidationSchema() {
|
||||
private get billsListingValidationSchema() {
|
||||
return [
|
||||
query('view_slug').optional().isString().trim(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
@@ -203,7 +204,7 @@ export default class BillsController extends BaseController {
|
||||
];
|
||||
}
|
||||
|
||||
get dueBillsListingValidationSchema() {
|
||||
private get dueBillsListingValidationSchema() {
|
||||
return [
|
||||
query('vendor_id').optional().trim().escape(),
|
||||
query('payment_made_id').optional().trim().escape(),
|
||||
@@ -216,17 +217,16 @@ export default class BillsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async newBill(req: Request, res: Response, next: NextFunction) {
|
||||
private async newBill(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId, user } = req;
|
||||
const billDTO: IBillDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const storedBill = await this.billsService.createBill(
|
||||
const storedBill = await this.billsApplication.createBill(
|
||||
tenantId,
|
||||
billDTO,
|
||||
user
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: storedBill.id,
|
||||
message: 'The bill has been created successfully.',
|
||||
@@ -241,13 +241,13 @@ export default class BillsController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async editBill(req: Request, res: Response, next: NextFunction) {
|
||||
private async editBill(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: billId } = req.params;
|
||||
const { tenantId, user } = req;
|
||||
const billDTO: IBillEditDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
await this.billsService.editBill(tenantId, billId, billDTO, user);
|
||||
await this.billsApplication.editBill(tenantId, billId, billDTO, user);
|
||||
|
||||
return res.status(200).send({
|
||||
id: billId,
|
||||
@@ -263,12 +263,12 @@ export default class BillsController extends BaseController {
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
async openBill(req: Request, res: Response, next: NextFunction) {
|
||||
private async openBill(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: billId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.billsService.openBill(tenantId, billId);
|
||||
await this.billsApplication.openBill(tenantId, billId);
|
||||
|
||||
return res.status(200).send({
|
||||
id: billId,
|
||||
@@ -285,12 +285,12 @@ export default class BillsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async getBill(req: Request, res: Response, next: NextFunction) {
|
||||
private async getBill(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: billId } = req.params;
|
||||
|
||||
try {
|
||||
const bill = await this.billsService.getBill(tenantId, billId);
|
||||
const bill = await this.billsApplication.getBill(tenantId, billId);
|
||||
|
||||
return res.status(200).send(this.transfromToResponse({ bill }));
|
||||
} catch (error) {
|
||||
@@ -304,12 +304,12 @@ export default class BillsController extends BaseController {
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async deleteBill(req: Request, res: Response, next: NextFunction) {
|
||||
private async deleteBill(req: Request, res: Response, next: NextFunction) {
|
||||
const billId = req.params.id;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.billsService.deleteBill(tenantId, billId);
|
||||
await this.billsApplication.deleteBill(tenantId, billId);
|
||||
|
||||
return res.status(200).send({
|
||||
id: billId,
|
||||
@@ -326,7 +326,7 @@ export default class BillsController extends BaseController {
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
public async billsList(req: Request, res: Response, next: NextFunction) {
|
||||
private async billsList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
page: 1,
|
||||
@@ -338,7 +338,7 @@ export default class BillsController extends BaseController {
|
||||
|
||||
try {
|
||||
const { bills, pagination, filterMeta } =
|
||||
await this.billsService.getBills(tenantId, filter);
|
||||
await this.billsApplication.getBills(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
bills: this.transfromToResponse(bills),
|
||||
@@ -356,12 +356,13 @@ export default class BillsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public async getDueBills(req: Request, res: Response, next: NextFunction) {
|
||||
private async getDueBills(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { vendorId } = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const bills = await this.billsService.getDueBills(tenantId, vendorId);
|
||||
const bills = await this.billsApplication.getDueBills(tenantId, vendorId);
|
||||
|
||||
return res.status(200).send({ bills });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -374,7 +375,7 @@ export default class BillsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public getBillPaymentsTransactions = async (
|
||||
private getBillPaymentsTransactions = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
@@ -383,7 +384,7 @@ export default class BillsController extends BaseController {
|
||||
const { id: billId } = req.params;
|
||||
|
||||
try {
|
||||
const billPayments = await this.billPayments.getBillPayments(
|
||||
const billPayments = await this.billsApplication.getBillPayments(
|
||||
tenantId,
|
||||
billId
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { check, param, query, ValidationChain } from 'express-validator';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import BaseController from '@/api/controllers/BaseController';
|
||||
import BillPaymentsService from '@/services/Purchases/BillPayments/BillPayments';
|
||||
import { BillPaymentsApplication } from '@/services/Purchases/BillPayments/BillPaymentsApplication';
|
||||
import BillPaymentsPages from '@/services/Purchases/BillPayments/BillPaymentsPages';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
@@ -17,18 +17,18 @@ import { AbilitySubject, IPaymentMadeAction } from '@/interfaces';
|
||||
@Service()
|
||||
export default class BillsPayments extends BaseController {
|
||||
@Inject()
|
||||
billPaymentService: BillPaymentsService;
|
||||
private billPaymentsApplication: BillPaymentsApplication;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
billPaymentsPages: BillPaymentsPages;
|
||||
private billPaymentsPages: BillPaymentsPages;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
public router() {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
@@ -106,7 +106,7 @@ export default class BillsPayments extends BaseController {
|
||||
* Bill payments schema validation.
|
||||
* @return {ValidationChain[]}
|
||||
*/
|
||||
get billPaymentSchemaValidation(): ValidationChain[] {
|
||||
private get billPaymentSchemaValidation(): ValidationChain[] {
|
||||
return [
|
||||
check('vendor_id').exists().isNumeric().toInt(),
|
||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||
@@ -129,7 +129,7 @@ export default class BillsPayments extends BaseController {
|
||||
* Specific bill payment schema validation.
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get specificBillPaymentValidateSchema(): ValidationChain[] {
|
||||
private get specificBillPaymentValidateSchema(): ValidationChain[] {
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export default class BillsPayments extends BaseController {
|
||||
* Bills payment list validation schema.
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get listingValidationSchema(): ValidationChain[] {
|
||||
private get listingValidationSchema(): ValidationChain[] {
|
||||
return [
|
||||
query('custom_view_id').optional().isNumeric().toInt(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
@@ -154,7 +154,7 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
async getBillPaymentNewPageEntries(req: Request, res: Response) {
|
||||
private async getBillPaymentNewPageEntries(req: Request, res: Response) {
|
||||
const { tenantId } = req;
|
||||
const { vendorId } = this.matchedQueryData(req);
|
||||
|
||||
@@ -174,7 +174,7 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getBillPaymentEditPage(
|
||||
private async getBillPaymentEditPage(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
@@ -205,16 +205,19 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {Response} res
|
||||
*/
|
||||
async createBillPayment(req: Request, res: Response, next: NextFunction) {
|
||||
private async createBillPayment(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const billPaymentDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const billPayment = await this.billPaymentService.createBillPayment(
|
||||
const billPayment = await this.billPaymentsApplication.createBillPayment(
|
||||
tenantId,
|
||||
billPaymentDTO
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: billPayment.id,
|
||||
message: 'Payment made has been created successfully.',
|
||||
@@ -229,13 +232,17 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async editBillPayment(req: Request, res: Response, next: NextFunction) {
|
||||
private async editBillPayment(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const billPaymentDTO = this.matchedBodyData(req);
|
||||
const { id: billPaymentId } = req.params;
|
||||
|
||||
try {
|
||||
const paymentMade = await this.billPaymentService.editBillPayment(
|
||||
const paymentMade = await this.billPaymentsApplication.editBillPayment(
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
billPaymentDTO
|
||||
@@ -256,12 +263,19 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Response} res -
|
||||
* @return {Response} res -
|
||||
*/
|
||||
async deleteBillPayment(req: Request, res: Response, next: NextFunction) {
|
||||
private async deleteBillPayment(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { id: billPaymentId } = req.params;
|
||||
|
||||
try {
|
||||
await this.billPaymentService.deleteBillPayment(tenantId, billPaymentId);
|
||||
await this.billPaymentsApplication.deleteBillPayment(
|
||||
tenantId,
|
||||
billPaymentId
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: billPaymentId,
|
||||
@@ -277,16 +291,19 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getBillPayment(req: Request, res: Response, next: NextFunction) {
|
||||
private async getBillPayment(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { id: billPaymentId } = req.params;
|
||||
|
||||
try {
|
||||
const billPayment = await this.billPaymentService.getBillPayment(
|
||||
const billPayment = await this.billPaymentsApplication.getBillPayment(
|
||||
tenantId,
|
||||
billPaymentId
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
bill_payment: this.transfromToResponse(billPayment),
|
||||
});
|
||||
@@ -301,12 +318,16 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async getPaymentBills(req: Request, res: Response, next: NextFunction) {
|
||||
private async getPaymentBills(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { id: billPaymentId } = req.params;
|
||||
|
||||
try {
|
||||
const bills = await this.billPaymentService.getPaymentBills(
|
||||
const bills = await this.billPaymentsApplication.getPaymentBills(
|
||||
tenantId,
|
||||
billPaymentId
|
||||
);
|
||||
@@ -322,7 +343,11 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async getBillsPayments(req: Request, res: Response, next: NextFunction) {
|
||||
private async getBillsPayments(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const billPaymentsFilter = {
|
||||
page: 1,
|
||||
@@ -335,7 +360,7 @@ export default class BillsPayments extends BaseController {
|
||||
|
||||
try {
|
||||
const { billPayments, pagination, filterMeta } =
|
||||
await this.billPaymentService.listBillPayments(
|
||||
await this.billPaymentsApplication.getBillPayments(
|
||||
tenantId,
|
||||
billPaymentsFilter
|
||||
);
|
||||
@@ -357,7 +382,7 @@ export default class BillsPayments extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handleServiceError(
|
||||
private handleServiceError(
|
||||
error: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
|
||||
@@ -1,42 +1,29 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query, ValidationChain } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
AbilitySubject,
|
||||
IPaymentReceiveDTO,
|
||||
PaymentReceiveAction,
|
||||
SaleInvoiceAction,
|
||||
} from '@/interfaces';
|
||||
import BaseController from '@/api/controllers/BaseController';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
|
||||
import PaymentReceivesPages from '@/services/Sales/PaymentReceives/PaymentReceivesPages';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import PaymentReceiveNotifyBySms from '@/services/Sales/PaymentReceives/PaymentReceiveSmsNotify';
|
||||
import { PaymentReceivesApplication } from '@/services/Sales/PaymentReceives/PaymentReceivesApplication';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import GetPaymentReceivePdf from '@/services/Sales/PaymentReceives/GetPaymentReeceivePdf';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
|
||||
/**
|
||||
* Payments receives controller.
|
||||
* @service
|
||||
*/
|
||||
@Service()
|
||||
export default class PaymentReceivesController extends BaseController {
|
||||
@Inject()
|
||||
paymentReceiveService: PaymentReceiveService;
|
||||
private paymentReceiveApplication: PaymentReceivesApplication;
|
||||
|
||||
@Inject()
|
||||
PaymentReceivesPages: PaymentReceivesPages;
|
||||
private PaymentReceivesPages: PaymentReceivesPages;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
paymentReceiveSmsNotify: PaymentReceiveNotifyBySms;
|
||||
|
||||
@Inject()
|
||||
paymentReceivePdf: GetPaymentReceivePdf;
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
@@ -137,7 +124,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* Payment receive schema.
|
||||
* @return {Array}
|
||||
*/
|
||||
get paymentReceiveSchema(): ValidationChain[] {
|
||||
private get paymentReceiveSchema(): ValidationChain[] {
|
||||
return [
|
||||
check('customer_id').exists().isNumeric().toInt(),
|
||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||
@@ -162,7 +149,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
/**
|
||||
* Payment receive list validation schema.
|
||||
*/
|
||||
get validatePaymentReceiveList(): ValidationChain[] {
|
||||
private get validatePaymentReceiveList(): ValidationChain[] {
|
||||
return [
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
|
||||
@@ -181,7 +168,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
/**
|
||||
* Validate payment receive parameters.
|
||||
*/
|
||||
get paymentReceiveValidation() {
|
||||
private get paymentReceiveValidation() {
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
@@ -189,14 +176,14 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* New payment receive validation schema.
|
||||
* @return {Array}
|
||||
*/
|
||||
get newPaymentReceiveValidation() {
|
||||
private get newPaymentReceiveValidation() {
|
||||
return [...this.paymentReceiveSchema];
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit payment receive validation.
|
||||
*/
|
||||
get editPaymentReceiveValidation() {
|
||||
private get editPaymentReceiveValidation() {
|
||||
return [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
...this.paymentReceiveSchema,
|
||||
@@ -209,13 +196,17 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async newPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
||||
private async newPaymentReceive(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId, user } = req;
|
||||
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const storedPaymentReceive =
|
||||
await this.paymentReceiveService.createPaymentReceive(
|
||||
await this.paymentReceiveApplication.createPaymentReceive(
|
||||
tenantId,
|
||||
paymentReceive,
|
||||
user
|
||||
@@ -235,14 +226,18 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async editPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
||||
private async editPaymentReceive(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId, user } = req;
|
||||
const { id: paymentReceiveId } = req.params;
|
||||
|
||||
const paymentReceive: IPaymentReceiveDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
await this.paymentReceiveService.editPaymentReceive(
|
||||
await this.paymentReceiveApplication.editPaymentReceive(
|
||||
tenantId,
|
||||
paymentReceiveId,
|
||||
paymentReceive,
|
||||
@@ -262,17 +257,20 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deletePaymentReceive(req: Request, res: Response, next: NextFunction) {
|
||||
private async deletePaymentReceive(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId, user } = req;
|
||||
const { id: paymentReceiveId } = req.params;
|
||||
|
||||
try {
|
||||
await this.paymentReceiveService.deletePaymentReceive(
|
||||
await this.paymentReceiveApplication.deletePaymentReceive(
|
||||
tenantId,
|
||||
paymentReceiveId,
|
||||
user
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: paymentReceiveId,
|
||||
message: 'The payment receive has been deleted successfully',
|
||||
@@ -288,7 +286,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async getPaymentReceiveInvoices(
|
||||
private async getPaymentReceiveInvoices(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
@@ -298,7 +296,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
|
||||
try {
|
||||
const saleInvoices =
|
||||
await this.paymentReceiveService.getPaymentReceiveInvoices(
|
||||
await this.paymentReceiveApplication.getPaymentReceiveInvoices(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
@@ -315,7 +313,11 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
||||
private async getPaymentReceiveList(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
sortOrder: 'desc',
|
||||
@@ -327,7 +329,10 @@ export default class PaymentReceivesController extends BaseController {
|
||||
|
||||
try {
|
||||
const { paymentReceives, pagination, filterMeta } =
|
||||
await this.paymentReceiveService.listPaymentReceives(tenantId, filter);
|
||||
await this.paymentReceiveApplication.getPaymentReceives(
|
||||
tenantId,
|
||||
filter
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
payment_receives: this.transfromToResponse(paymentReceives),
|
||||
@@ -344,7 +349,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Request} req - Request.
|
||||
* @param {Response} res - Response.
|
||||
*/
|
||||
async getPaymentReceiveNewPageEntries(
|
||||
private async getPaymentReceiveNewPageEntries(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
@@ -371,7 +376,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
async getPaymentReceiveEditPage(
|
||||
private async getPaymentReceiveEditPage(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
@@ -402,15 +407,20 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async getPaymentReceive(req: Request, res: Response, next: NextFunction) {
|
||||
private async getPaymentReceive(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { id: paymentReceiveId } = req.params;
|
||||
|
||||
try {
|
||||
const paymentReceive = await this.paymentReceiveService.getPaymentReceive(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
const paymentReceive =
|
||||
await this.paymentReceiveApplication.getPaymentReceive(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
|
||||
const ACCEPT_TYPE = {
|
||||
APPLICATION_PDF: 'application/pdf',
|
||||
@@ -423,10 +433,11 @@ export default class PaymentReceivesController extends BaseController {
|
||||
});
|
||||
},
|
||||
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
||||
const pdfContent = await this.paymentReceivePdf.getPaymentReceivePdf(
|
||||
tenantId,
|
||||
paymentReceive
|
||||
);
|
||||
const pdfContent =
|
||||
await this.paymentReceiveApplication.getPaymentReceivePdf(
|
||||
tenantId,
|
||||
paymentReceive
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
@@ -454,10 +465,11 @@ export default class PaymentReceivesController extends BaseController {
|
||||
const { id: paymentReceiveId } = req.params;
|
||||
|
||||
try {
|
||||
const paymentReceive = await this.paymentReceiveSmsNotify.notifyBySms(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
const paymentReceive =
|
||||
await this.paymentReceiveApplication.notifyPaymentBySms(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: paymentReceive.id,
|
||||
message: 'The payment notification has been sent successfully.',
|
||||
@@ -482,10 +494,11 @@ export default class PaymentReceivesController extends BaseController {
|
||||
const { id: paymentReceiveId } = req.params;
|
||||
|
||||
try {
|
||||
const smsDetails = await this.paymentReceiveSmsNotify.smsDetails(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
const smsDetails =
|
||||
await this.paymentReceiveApplication.getPaymentSmsDetails(
|
||||
tenantId,
|
||||
paymentReceiveId
|
||||
);
|
||||
return res.status(200).send({
|
||||
data: smsDetails,
|
||||
});
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query, matchedData } from 'express-validator';
|
||||
import { check, param, query } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
AbilitySubject,
|
||||
ISaleEstimateDTO,
|
||||
SaleEstimateAction,
|
||||
SaleInvoiceAction,
|
||||
} from '@/interfaces';
|
||||
import BaseController from '@/api/controllers/BaseController';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import SaleEstimateService from '@/services/Sales/SalesEstimate';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import SaleEstimatesPdfService from '@/services/Sales/Estimates/SaleEstimatesPdf';
|
||||
import SaleEstimateNotifyBySms from '@/services/Sales/Estimates/SaleEstimateSmsNotify';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { SaleEstimatesApplication } from '@/services/Sales/Estimates/SaleEstimatesApplication';
|
||||
|
||||
const ACCEPT_TYPE = {
|
||||
APPLICATION_PDF: 'application/pdf',
|
||||
@@ -23,21 +20,15 @@ const ACCEPT_TYPE = {
|
||||
@Service()
|
||||
export default class SalesEstimatesController extends BaseController {
|
||||
@Inject()
|
||||
saleEstimateService: SaleEstimateService;
|
||||
private saleEstimatesApplication: SaleEstimatesApplication;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
saleEstimatesPdf: SaleEstimatesPdfService;
|
||||
|
||||
@Inject()
|
||||
saleEstimateNotifySms: SaleEstimateNotifyBySms;
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
public router() {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
@@ -136,7 +127,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
/**
|
||||
* Estimate validation schema.
|
||||
*/
|
||||
get estimateValidationSchema() {
|
||||
private get estimateValidationSchema() {
|
||||
return [
|
||||
check('customer_id').exists().isNumeric().toInt(),
|
||||
check('estimate_date').exists().isISO8601().toDate(),
|
||||
@@ -177,14 +168,14 @@ export default class SalesEstimatesController extends BaseController {
|
||||
/**
|
||||
* Specific sale estimate validation schema.
|
||||
*/
|
||||
get validateSpecificEstimateSchema() {
|
||||
private get validateSpecificEstimateSchema() {
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sales estimates list validation schema.
|
||||
*/
|
||||
get validateEstimateListSchema() {
|
||||
private get validateEstimateListSchema() {
|
||||
return [
|
||||
query('view_slug').optional().isString().trim(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
@@ -202,15 +193,16 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Response} res -
|
||||
* @return {Response} res -
|
||||
*/
|
||||
async newEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
private async newEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const storedEstimate = await this.saleEstimateService.createEstimate(
|
||||
tenantId,
|
||||
estimateDTO
|
||||
);
|
||||
const storedEstimate =
|
||||
await this.saleEstimatesApplication.createSaleEstimate(
|
||||
tenantId,
|
||||
estimateDTO
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: storedEstimate.id,
|
||||
@@ -226,19 +218,18 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async editEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
private async editEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
const { tenantId } = req;
|
||||
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
// Update estimate with associated estimate entries.
|
||||
await this.saleEstimateService.editEstimate(
|
||||
await this.saleEstimatesApplication.editSaleEstimate(
|
||||
tenantId,
|
||||
estimateId,
|
||||
estimateDTO
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: estimateId,
|
||||
message: 'The sale estimate has been created successfully.',
|
||||
@@ -253,13 +244,19 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deleteEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
private async deleteEstimate(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { id: estimateId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.saleEstimateService.deleteEstimate(tenantId, estimateId);
|
||||
|
||||
await this.saleEstimatesApplication.deleteSaleEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: estimateId,
|
||||
message: 'The sale estimate has been deleted successfully.',
|
||||
@@ -274,13 +271,19 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deliverSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
private async deliverSaleEstimate(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { id: estimateId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.saleEstimateService.deliverSaleEstimate(tenantId, estimateId);
|
||||
|
||||
await this.saleEstimatesApplication.deliverSaleEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: estimateId,
|
||||
message: 'The sale estimate has been delivered successfully.',
|
||||
@@ -296,12 +299,19 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async approveSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
private async approveSaleEstimate(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { id: estimateId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.saleEstimateService.approveSaleEstimate(tenantId, estimateId);
|
||||
await this.saleEstimatesApplication.approveSaleEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: estimateId,
|
||||
@@ -318,12 +328,19 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async rejectSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
private async rejectSaleEstimate(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { id: estimateId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.saleEstimateService.rejectSaleEstimate(tenantId, estimateId);
|
||||
await this.saleEstimatesApplication.rejectSaleEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: estimateId,
|
||||
@@ -340,12 +357,12 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async getEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
private async getEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const estimate = await this.saleEstimateService.getEstimate(
|
||||
const estimate = await this.saleEstimatesApplication.getSaleEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
@@ -357,10 +374,11 @@ export default class SalesEstimatesController extends BaseController {
|
||||
},
|
||||
// PDF content type.
|
||||
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
||||
const pdfContent = await this.saleEstimatesPdf.saleEstimatePdf(
|
||||
tenantId,
|
||||
estimate
|
||||
);
|
||||
const pdfContent =
|
||||
await this.saleEstimatesApplication.getSaleEstimatePdf(
|
||||
tenantId,
|
||||
estimate
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
@@ -378,7 +396,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
||||
private async getEstimates(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
sortOrder: 'desc',
|
||||
@@ -390,7 +408,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
try {
|
||||
const { salesEstimates, pagination, filterMeta } =
|
||||
await this.saleEstimateService.estimatesList(tenantId, filter);
|
||||
await this.saleEstimatesApplication.getSaleEstimates(tenantId, filter);
|
||||
|
||||
res.format({
|
||||
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
|
||||
@@ -408,7 +426,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
public saleEstimateNotifyBySms = async (
|
||||
private saleEstimateNotifyBySms = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
@@ -417,10 +435,11 @@ export default class SalesEstimatesController extends BaseController {
|
||||
const { id: estimateId } = req.params;
|
||||
|
||||
try {
|
||||
const saleEstimate = await this.saleEstimateNotifySms.notifyBySms(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
const saleEstimate =
|
||||
await this.saleEstimatesApplication.notifySaleEstimateBySms(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: saleEstimate.id,
|
||||
message:
|
||||
@@ -437,7 +456,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public saleEstimateSmsDetails = async (
|
||||
private saleEstimateSmsDetails = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
@@ -446,10 +465,11 @@ export default class SalesEstimatesController extends BaseController {
|
||||
const { id: estimateId } = req.params;
|
||||
|
||||
try {
|
||||
const estimateSmsDetails = await this.saleEstimateNotifySms.smsDetails(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
const estimateSmsDetails =
|
||||
await this.saleEstimatesApplication.getSaleEstimateSmsDetails(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
return res.status(200).send({
|
||||
data: estimateSmsDetails,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import { check, param, query } from 'express-validator';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import BaseController from '../BaseController';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import SaleInvoiceService from '@/services/Sales/SalesInvoices';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
@@ -12,11 +11,8 @@ import {
|
||||
SaleInvoiceAction,
|
||||
AbilitySubject,
|
||||
} from '@/interfaces';
|
||||
import SaleInvoicePdf from '@/services/Sales/SaleInvoicePdf';
|
||||
import SaleInvoiceWriteoff from '@/services/Sales/SaleInvoiceWriteoff';
|
||||
import SaleInvoiceNotifyBySms from '@/services/Sales/SaleInvoiceNotifyBySms';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import InvoicePaymentsService from '@/services/Sales/Invoices/InvoicePaymentsService';
|
||||
import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication';
|
||||
|
||||
const ACCEPT_TYPE = {
|
||||
APPLICATION_PDF: 'application/pdf',
|
||||
@@ -25,27 +21,15 @@ const ACCEPT_TYPE = {
|
||||
@Service()
|
||||
export default class SaleInvoicesController extends BaseController {
|
||||
@Inject()
|
||||
saleInvoiceService: SaleInvoiceService;
|
||||
private saleInvoiceApplication: SaleInvoiceApplication;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
saleInvoicePdf: SaleInvoicePdf;
|
||||
|
||||
@Inject()
|
||||
saleInvoiceWriteoff: SaleInvoiceWriteoff;
|
||||
|
||||
@Inject()
|
||||
saleInvoiceSmsNotify: SaleInvoiceNotifyBySms;
|
||||
|
||||
@Inject()
|
||||
invoicePaymentsSerivce: InvoicePaymentsService;
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
public router() {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
@@ -167,7 +151,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
/**
|
||||
* Sale invoice validation schema.
|
||||
*/
|
||||
get saleInvoiceValidationSchema() {
|
||||
private get saleInvoiceValidationSchema() {
|
||||
return [
|
||||
check('customer_id').exists().isNumeric().toInt(),
|
||||
check('invoice_date').exists().isISO8601().toDate(),
|
||||
@@ -227,14 +211,14 @@ export default class SaleInvoicesController extends BaseController {
|
||||
/**
|
||||
* Specific sale invoice validation schema.
|
||||
*/
|
||||
get specificSaleInvoiceValidation() {
|
||||
private get specificSaleInvoiceValidation() {
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sales invoices list validation schema.
|
||||
*/
|
||||
get saleInvoiceListValidationSchema() {
|
||||
private get saleInvoiceListValidationSchema() {
|
||||
return [
|
||||
query('view_slug').optional({ nullable: true }).isString().trim(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
@@ -249,7 +233,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
/**
|
||||
* Due sale invoice list validation schema.
|
||||
*/
|
||||
get dueSalesInvoicesListValidationSchema() {
|
||||
private get dueSalesInvoicesListValidationSchema() {
|
||||
return [query('customer_id').optional().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
@@ -259,17 +243,22 @@ export default class SaleInvoicesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async newSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
||||
private async newSaleInvoice(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId, user } = req;
|
||||
const saleInvoiceDTO: ISaleInvoiceCreateDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
// Creates a new sale invoice with associated entries.
|
||||
const storedSaleInvoice = await this.saleInvoiceService.createSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
user
|
||||
);
|
||||
const storedSaleInvoice =
|
||||
await this.saleInvoiceApplication.createSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
user
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: storedSaleInvoice.id,
|
||||
message: 'The sale invoice has been created successfully.',
|
||||
@@ -285,14 +274,18 @@ export default class SaleInvoicesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async editSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
||||
private async editSaleInvoice(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId, user } = req;
|
||||
const { id: saleInvoiceId } = req.params;
|
||||
const saleInvoiceOTD: ISaleInvoiceDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
// Update the given sale invoice details.
|
||||
await this.saleInvoiceService.editSaleInvoice(
|
||||
await this.saleInvoiceApplication.editSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
saleInvoiceOTD,
|
||||
@@ -313,12 +306,16 @@ export default class SaleInvoicesController extends BaseController {
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
async deliverSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
||||
private async deliverSaleInvoice(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId, user } = req;
|
||||
const { id: saleInvoiceId } = req.params;
|
||||
|
||||
try {
|
||||
await this.saleInvoiceService.deliverSaleInvoice(
|
||||
await this.saleInvoiceApplication.deliverSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
user
|
||||
@@ -338,13 +335,17 @@ export default class SaleInvoicesController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async deleteSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
||||
private async deleteSaleInvoice(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { id: saleInvoiceId } = req.params;
|
||||
const { tenantId, user } = req;
|
||||
|
||||
try {
|
||||
// Deletes the sale invoice with associated entries and journal transaction.
|
||||
await this.saleInvoiceService.deleteSaleInvoice(
|
||||
await this.saleInvoiceApplication.deleteSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
user
|
||||
@@ -364,12 +365,16 @@ export default class SaleInvoicesController extends BaseController {
|
||||
* @param {Request} req - Request object.
|
||||
* @param {Response} res - Response object.
|
||||
*/
|
||||
async getSaleInvoice(req: Request, res: Response, next: NextFunction) {
|
||||
private async getSaleInvoice(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { id: saleInvoiceId } = req.params;
|
||||
const { tenantId, user } = req;
|
||||
|
||||
try {
|
||||
const saleInvoice = await this.saleInvoiceService.getSaleInvoice(
|
||||
const saleInvoice = await this.saleInvoiceApplication.getSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
user
|
||||
@@ -384,7 +389,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
},
|
||||
// PDF content type.
|
||||
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
||||
const pdfContent = await this.saleInvoicePdf.saleInvoicePdf(
|
||||
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(
|
||||
tenantId,
|
||||
saleInvoice
|
||||
);
|
||||
@@ -420,7 +425,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
};
|
||||
try {
|
||||
const { salesInvoices, filterMeta, pagination } =
|
||||
await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
||||
await this.saleInvoiceApplication.getSaleInvoices(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
sales_invoices: this.transfromToResponse(salesInvoices),
|
||||
@@ -448,10 +453,11 @@ export default class SaleInvoicesController extends BaseController {
|
||||
const { customerId } = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const salesInvoices = await this.saleInvoiceService.getPayableInvoices(
|
||||
tenantId,
|
||||
customerId
|
||||
);
|
||||
const salesInvoices =
|
||||
await this.saleInvoiceApplication.getReceivableSaleInvoices(
|
||||
tenantId,
|
||||
customerId
|
||||
);
|
||||
return res.status(200).send({
|
||||
sales_invoices: this.transfromToResponse(salesInvoices),
|
||||
});
|
||||
@@ -477,7 +483,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
const writeoffDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const saleInvoice = await this.saleInvoiceWriteoff.writeOff(
|
||||
const saleInvoice = await this.saleInvoiceApplication.writeOff(
|
||||
tenantId,
|
||||
invoiceId,
|
||||
writeoffDTO
|
||||
@@ -485,7 +491,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
|
||||
return res.status(200).send({
|
||||
id: saleInvoice.id,
|
||||
message: 'The given sale invoice has been writte-off successfully.',
|
||||
message: 'The given sale invoice has been written-off successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -507,7 +513,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
const { id: invoiceId } = req.params;
|
||||
|
||||
try {
|
||||
const saleInvoice = await this.saleInvoiceWriteoff.cancelWrittenoff(
|
||||
const saleInvoice = await this.saleInvoiceApplication.cancelWrittenoff(
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
@@ -538,11 +544,12 @@ export default class SaleInvoicesController extends BaseController {
|
||||
const invoiceNotifySmsDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const saleInvoice = await this.saleInvoiceSmsNotify.notifyBySms(
|
||||
tenantId,
|
||||
invoiceId,
|
||||
invoiceNotifySmsDTO.notificationKey
|
||||
);
|
||||
const saleInvoice =
|
||||
await this.saleInvoiceApplication.notifySaleInvoiceBySms(
|
||||
tenantId,
|
||||
invoiceId,
|
||||
invoiceNotifySmsDTO.notificationKey
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: saleInvoice.id,
|
||||
message:
|
||||
@@ -569,11 +576,12 @@ export default class SaleInvoicesController extends BaseController {
|
||||
const smsDetailsDTO = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const invoiceSmsDetails = await this.saleInvoiceSmsNotify.smsDetails(
|
||||
tenantId,
|
||||
invoiceId,
|
||||
smsDetailsDTO
|
||||
);
|
||||
const invoiceSmsDetails =
|
||||
await this.saleInvoiceApplication.getSaleInvoiceSmsDetails(
|
||||
tenantId,
|
||||
invoiceId,
|
||||
smsDetailsDTO
|
||||
);
|
||||
return res.status(200).send({
|
||||
data: invoiceSmsDetails,
|
||||
});
|
||||
@@ -599,7 +607,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
|
||||
try {
|
||||
const invoicePayments =
|
||||
await this.invoicePaymentsSerivce.getInvoicePayments(
|
||||
await this.saleInvoiceApplication.getInvoicePayments(
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
|
||||
@@ -2,34 +2,26 @@ import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||
import SaleReceiptService from '@/services/Sales/SalesReceipts';
|
||||
import SaleReceiptsPdfService from '@/services/Sales/Receipts/SaleReceiptsPdfService';
|
||||
import BaseController from '../BaseController';
|
||||
import { ISaleReceiptDTO } from '@/interfaces/SaleReceipt';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import SaleReceiptNotifyBySms from '@/services/Sales/SaleReceiptNotifyBySms';
|
||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||
import { AbilitySubject, SaleReceiptAction } from '@/interfaces';
|
||||
import { SaleReceiptApplication } from '@/services/Sales/Receipts/SaleReceiptApplication';
|
||||
|
||||
@Service()
|
||||
export default class SalesReceiptsController extends BaseController {
|
||||
@Inject()
|
||||
saleReceiptService: SaleReceiptService;
|
||||
private saleReceiptsApplication: SaleReceiptApplication;
|
||||
|
||||
@Inject()
|
||||
saleReceiptsPdf: SaleReceiptsPdfService;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
saleReceiptSmsNotify: SaleReceiptNotifyBySms;
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
public router() {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
@@ -105,7 +97,7 @@ export default class SalesReceiptsController extends BaseController {
|
||||
* Sales receipt validation schema.
|
||||
* @return {Array}
|
||||
*/
|
||||
get salesReceiptsValidationSchema() {
|
||||
private get salesReceiptsValidationSchema() {
|
||||
return [
|
||||
check('customer_id').exists().isNumeric().toInt(),
|
||||
check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(),
|
||||
@@ -146,14 +138,14 @@ export default class SalesReceiptsController extends BaseController {
|
||||
/**
|
||||
* Specific sale receipt validation schema.
|
||||
*/
|
||||
get specificReceiptValidationSchema() {
|
||||
private get specificReceiptValidationSchema() {
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
/**
|
||||
* List sales receipts validation schema.
|
||||
*/
|
||||
get listSalesReceiptsValidationSchema() {
|
||||
private get listSalesReceiptsValidationSchema() {
|
||||
return [
|
||||
query('view_slug').optional().isString().trim(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
@@ -170,16 +162,21 @@ export default class SalesReceiptsController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async newSaleReceipt(req: Request, res: Response, next: NextFunction) {
|
||||
private async newSaleReceipt(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const saleReceiptDTO: ISaleReceiptDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
// Store the given sale receipt details with associated entries.
|
||||
const storedSaleReceipt = await this.saleReceiptService.createSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptDTO
|
||||
);
|
||||
const storedSaleReceipt =
|
||||
await this.saleReceiptsApplication.createSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptDTO
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: storedSaleReceipt.id,
|
||||
message: 'Sale receipt has been created successfully.',
|
||||
@@ -194,13 +191,20 @@ export default class SalesReceiptsController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deleteSaleReceipt(req: Request, res: Response, next: NextFunction) {
|
||||
private async deleteSaleReceipt(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { id: saleReceiptId } = req.params;
|
||||
|
||||
try {
|
||||
// Deletes the sale receipt.
|
||||
await this.saleReceiptService.deleteSaleReceipt(tenantId, saleReceiptId);
|
||||
await this.saleReceiptsApplication.deleteSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: saleReceiptId,
|
||||
@@ -217,14 +221,18 @@ export default class SalesReceiptsController extends BaseController {
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
async editSaleReceipt(req: Request, res: Response, next: NextFunction) {
|
||||
private async editSaleReceipt(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { id: saleReceiptId } = req.params;
|
||||
const saleReceipt = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
// Update the given sale receipt details.
|
||||
await this.saleReceiptService.editSaleReceipt(
|
||||
await this.saleReceiptsApplication.editSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptId,
|
||||
saleReceipt
|
||||
@@ -244,13 +252,20 @@ export default class SalesReceiptsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async closeSaleReceipt(req: Request, res: Response, next: NextFunction) {
|
||||
private async closeSaleReceipt(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const { id: saleReceiptId } = req.params;
|
||||
|
||||
try {
|
||||
// Update the given sale receipt details.
|
||||
await this.saleReceiptService.closeSaleReceipt(tenantId, saleReceiptId);
|
||||
await this.saleReceiptsApplication.closeSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: saleReceiptId,
|
||||
message: 'Sale receipt has been closed successfully.',
|
||||
@@ -265,7 +280,11 @@ export default class SalesReceiptsController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
|
||||
private async getSalesReceipts(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
sortOrder: 'desc',
|
||||
@@ -274,10 +293,9 @@ export default class SalesReceiptsController extends BaseController {
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
|
||||
try {
|
||||
const { data, pagination, filterMeta } =
|
||||
await this.saleReceiptService.salesReceiptsList(tenantId, filter);
|
||||
await this.saleReceiptsApplication.getSaleReceipts(tenantId, filter);
|
||||
|
||||
const response = this.transfromToResponse({
|
||||
data,
|
||||
@@ -301,11 +319,10 @@ export default class SalesReceiptsController extends BaseController {
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const saleReceipt = await this.saleReceiptService.getSaleReceipt(
|
||||
const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
|
||||
res.format({
|
||||
'application/json': () => {
|
||||
return res
|
||||
@@ -313,10 +330,11 @@ export default class SalesReceiptsController extends BaseController {
|
||||
.send(this.transfromToResponse({ saleReceipt }));
|
||||
},
|
||||
'application/pdf': async () => {
|
||||
const pdfContent = await this.saleReceiptsPdf.saleReceiptPdf(
|
||||
tenantId,
|
||||
saleReceipt
|
||||
);
|
||||
const pdfContent =
|
||||
await this.saleReceiptsApplication.getSaleReceiptPdf(
|
||||
tenantId,
|
||||
saleReceipt
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
@@ -344,10 +362,11 @@ export default class SalesReceiptsController extends BaseController {
|
||||
const { id: receiptId } = req.params;
|
||||
|
||||
try {
|
||||
const saleReceipt = await this.saleReceiptSmsNotify.notifyBySms(
|
||||
tenantId,
|
||||
receiptId
|
||||
);
|
||||
const saleReceipt =
|
||||
await this.saleReceiptsApplication.saleReceiptNotifyBySms(
|
||||
tenantId,
|
||||
receiptId
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: saleReceipt.id,
|
||||
message:
|
||||
@@ -373,10 +392,11 @@ export default class SalesReceiptsController extends BaseController {
|
||||
const { id: receiptId } = req.params;
|
||||
|
||||
try {
|
||||
const smsDetails = await this.saleReceiptSmsNotify.smsDetails(
|
||||
tenantId,
|
||||
receiptId
|
||||
);
|
||||
const smsDetails =
|
||||
await this.saleReceiptsApplication.getSaleReceiptSmsDetails(
|
||||
tenantId,
|
||||
receiptId
|
||||
);
|
||||
return res.status(200).send({
|
||||
data: smsDetails,
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Router } from 'express';
|
||||
import { Container, Service } from 'typedi';
|
||||
import SalesInvoices from './SalesInvoices'
|
||||
import SalesEstimates from './SalesEstimates';
|
||||
import SalesReceipts from './SalesReceipts';
|
||||
import SalesInvoices from './SalesInvoices'
|
||||
import PaymentReceives from './PaymentReceives';
|
||||
import CreditNotes from './CreditNotes';
|
||||
import PaymentReceives from './PaymentReceives';
|
||||
@Service()
|
||||
export default class SalesController {
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Container } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { SaleInvoicesCost } from '@/services/Sales/Invoices/SalesInvoicesCost';
|
||||
|
||||
export default class WriteInvoicesJournalEntries {
|
||||
eventPublisher: EventPublisher;
|
||||
@@ -26,7 +26,7 @@ export default class WriteInvoicesJournalEntries {
|
||||
*/
|
||||
public async handler(job, done: Function): Promise<void> {
|
||||
const { startingDate, tenantId } = job.attrs.data;
|
||||
const salesInvoicesCost = Container.get(SalesInvoicesCost);
|
||||
const salesInvoicesCost = Container.get(SaleInvoicesCost);
|
||||
|
||||
try {
|
||||
await salesInvoicesCost.writeCostLotsGLEntries(tenantId, startingDate);
|
||||
|
||||
@@ -7,11 +7,11 @@ import PaymentSyncBillBalance from '@/subscribers/PaymentMades/PaymentSyncBillBa
|
||||
import SaleReceiptInventoryTransactionsSubscriber from '@/subscribers/SaleReceipt/WriteInventoryTransactions';
|
||||
import SaleInvoiceWriteInventoryTransactions from '@/subscribers/SaleInvoices/WriteInventoryTransactions';
|
||||
import SaleInvoiceWriteGLEntriesSubscriber from '@/subscribers/SaleInvoices/WriteJournalEntries';
|
||||
import SaleReceiptWriteGLEntriesSubscriber from '@/subscribers/SaleReceipt/WriteJournalEntries';
|
||||
import PaymentReceiveSyncInvoices from '@/subscribers/PaymentReceive/PaymentReceiveSyncInvoices';
|
||||
import CashflowTransactionSubscriber from '@/services/Cashflow/CashflowTransactionSubscriber';
|
||||
import PaymentReceivesWriteGLEntriesSubscriber from '@/subscribers/PaymentReceive/WriteGLEntries';
|
||||
import InventorySubscriber from '@/subscribers/Inventory/Inventory';
|
||||
import SaleReceiptWriteGLEntriesSubscriber from '@/subscribers/SaleReceipt/WriteJournalEntries';
|
||||
import { CustomerWriteGLOpeningBalanceSubscriber } from '@/services/Contacts/Customers/Subscribers/CustomerGLEntriesSubscriber';
|
||||
import { VendorsWriteGLOpeningSubscriber } from '@/services/Contacts/Vendors/Subscribers/VendorGLEntriesSubscriber';
|
||||
import SaleEstimateAutoSerialSubscriber from '@/subscribers/SaleEstimate/AutoIncrementSerial';
|
||||
@@ -35,7 +35,7 @@ import PurgeAuthorizedUserOnceRoleMutate from '@/services/Roles/PurgeAuthorizedU
|
||||
import SendSmsNotificationToCustomer from '@/subscribers/SaleInvoices/SendSmsNotificationToCustomer';
|
||||
import SendSmsNotificationSaleReceipt from '@/subscribers/SaleReceipt/SendSmsNotificationToCustomer';
|
||||
import SendSmsNotificationPaymentReceive from '@/subscribers/PaymentReceive/SendSmsNotificationToCustomer';
|
||||
import SaleInvoiceWriteoffSubscriber from '@/services/Sales/SaleInvoiceWriteoffSubscriber';
|
||||
import SaleInvoiceWriteoffSubscriber from '@/services/Sales/Invoices/SaleInvoiceWriteoffSubscriber';
|
||||
import LandedCostSyncCostTransactionsSubscriber from '@/services/Purchases/LandedCost/LandedCostSyncCostTransactionsSubscriber';
|
||||
import LandedCostInventoryTransactionsSubscriber from '@/services/Purchases/LandedCost/LandedCostInventoryTransactionsSubscriber';
|
||||
import CreditNoteGLEntriesSubscriber from '@/services/CreditNotes/CreditNoteGLEntriesSubscriber';
|
||||
@@ -66,7 +66,6 @@ import { ActivateWarehousesSubscriber } from '@/services/Warehouses/ActivateWare
|
||||
import { ManualJournalWriteGLSubscriber } from '@/services/ManualJournals/ManualJournalGLEntriesSubscriber';
|
||||
import { BillGLEntriesSubscriber } from '@/services/Purchases/Bills/BillGLEntriesSubscriber';
|
||||
import { PaymentWriteGLEntriesSubscriber } from '@/services/Purchases/BillPayments/BillPaymentGLEntriesSubscriber';
|
||||
|
||||
import BranchesIntegrationsSubscribers from '@/services/Branches/EventsProvider';
|
||||
import WarehousesIntegrationsSubscribers from '@/services/Warehouses/EventsProvider';
|
||||
import { WarehouseTransferAutoIncrementSubscriber } from '@/services/Warehouses/WarehousesTransfers/WarehouseTransferAutoIncrementSubscriber';
|
||||
|
||||
@@ -5,7 +5,7 @@ import TenantModel from 'models/TenantModel';
|
||||
import BillSettings from './Bill.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Purchases/constants';
|
||||
import { DEFAULT_VIEWS } from '@/services/Purchases/Bills/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class Bill extends mixin(TenantModel, [
|
||||
|
||||
@@ -5,7 +5,7 @@ import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import SaleInvoiceMeta from './SaleInvoice.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/constants';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/Invoices/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class SaleInvoice extends mixin(TenantModel, [
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IBranchDeletedPayload, IBranchDeletePayload } from '@/interfaces';
|
||||
import { CURDBranch } from './CRUDBranch';
|
||||
import { BranchValidator } from './BranchValidate';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export class DeleteBranch extends CURDBranch {
|
||||
@Inject()
|
||||
|
||||
@@ -14,8 +14,8 @@ export class ValidateBranchExistance {
|
||||
|
||||
/**
|
||||
* Validate transaction branch id when the feature is active.
|
||||
* @param {number} tenantId
|
||||
* @param {number} branchId
|
||||
* @param {number} tenantId
|
||||
* @param {number} branchId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public validateTransactionBranchWhenActive = async (
|
||||
@@ -32,18 +32,16 @@ export class ValidateBranchExistance {
|
||||
|
||||
/**
|
||||
* Validate transaction branch id existance.
|
||||
* @param {number} tenantId
|
||||
* @param {number} branchId
|
||||
* @param {number} tenantId
|
||||
* @param {number} branchId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public validateTransactionBranch = async (
|
||||
tenantId: number,
|
||||
branchId: number | null
|
||||
) => {
|
||||
//
|
||||
this.validateBranchIdExistance(branchId);
|
||||
|
||||
//
|
||||
await this.validateBranchExistance(tenantId, branchId);
|
||||
};
|
||||
|
||||
@@ -62,7 +60,10 @@ export class ValidateBranchExistance {
|
||||
* @param tenantId
|
||||
* @param branchId
|
||||
*/
|
||||
public validateBranchExistance = async (tenantId: number, branchId: number) => {
|
||||
public validateBranchExistance = async (
|
||||
tenantId: number,
|
||||
branchId: number
|
||||
) => {
|
||||
const { Branch } = this.tenancy.models(tenantId);
|
||||
|
||||
const branch = await Branch.query().findById(branchId);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import Knex from 'knex';
|
||||
import { Knex } from 'knex';
|
||||
import { sumBy } from 'lodash';
|
||||
import {
|
||||
ICreditNote,
|
||||
@@ -8,27 +8,31 @@ import {
|
||||
ISaleInvoice,
|
||||
} from '@/interfaces';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import events from '@/subscribers/events';
|
||||
import { PaymentReceiveValidators } from '../Sales/PaymentReceives/PaymentReceiveValidators';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import {
|
||||
IApplyCreditToInvoicesDTO,
|
||||
IApplyCreditToInvoicesCreatedPayload,
|
||||
} from '@/interfaces';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import events from '@/subscribers/events';
|
||||
import { ERRORS } from './constants';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
|
||||
@Inject('PaymentReceives')
|
||||
paymentReceive: PaymentReceiveService;
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
private paymentReceiveValidators: PaymentReceiveValidators;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Apply credit note to the given invoices.
|
||||
@@ -50,7 +54,7 @@ export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
|
||||
);
|
||||
// Retrieve the applied invoices that associated to the credit note customer.
|
||||
const appliedInvoicesEntries =
|
||||
await this.paymentReceive.validateInvoicesIDsExistance(
|
||||
await this.paymentReceiveValidators.validateInvoicesIDsExistance(
|
||||
tenantId,
|
||||
creditNote.customerId,
|
||||
applyCreditToInvoicesDTO.entries
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import Knex from 'knex';
|
||||
import { Knex } from 'knex';
|
||||
import { IApplyCreditToInvoicesDeletedPayload } from '@/interfaces';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import events from '@/subscribers/events';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export default class DeletreCreditNoteApplyToInvoices extends BaseCreditNotes {
|
||||
@Inject('PaymentReceives')
|
||||
paymentReceive: PaymentReceiveService;
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Apply credit note to the given invoices.
|
||||
|
||||
@@ -12,10 +12,10 @@ import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
@Service()
|
||||
export default class SyncSystemSendInvite {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
|
||||
@@ -27,7 +27,7 @@ export class ItemInvoicesTransactionsTransformer extends Transformer {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Formatted invoice date.
|
||||
* @param item
|
||||
* @returns
|
||||
*/
|
||||
@@ -36,16 +36,17 @@ export class ItemInvoicesTransactionsTransformer extends Transformer {
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Formatted item quantity.
|
||||
* @returns {string}
|
||||
*/
|
||||
public formattedQuantity = (entry): string => {
|
||||
return entry.quantity;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Formatted date.
|
||||
* @param entry
|
||||
* @returns
|
||||
* @returns {string}
|
||||
*/
|
||||
public formattedRate = (entry): string => {
|
||||
return formatNumber(entry.rate, {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Inject, Service } from 'typedi';
|
||||
import { IItemEntry, IItemEntryDTO, IItem } from '@/interfaces';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ItemEntry } from '@/models';
|
||||
import { entriesAmountDiff } from 'utils';
|
||||
import { ItemEntry } from 'models';
|
||||
|
||||
const ERRORS = {
|
||||
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
|
||||
@@ -16,7 +16,7 @@ const ERRORS = {
|
||||
@Service()
|
||||
export default class ItemsEntriesService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
private tenancy: TenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the inventory items entries of the reference id and type.
|
||||
@@ -235,7 +235,7 @@ export default class ItemsEntriesService {
|
||||
/**
|
||||
* Sets the cost/sell accounts to the invoice entries.
|
||||
*/
|
||||
setItemsEntriesDefaultAccounts(tenantId: number) {
|
||||
public setItemsEntriesDefaultAccounts(tenantId: number) {
|
||||
return async (entries: IItemEntry[]) => {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
@@ -258,10 +258,10 @@ export default class ItemsEntriesService {
|
||||
|
||||
/**
|
||||
* Retrieve the total items entries.
|
||||
* @param entries
|
||||
* @returns
|
||||
* @param entries
|
||||
* @returns
|
||||
*/
|
||||
getTotalItemsEntries(entries: ItemEntry[]): number {
|
||||
public getTotalItemsEntries(entries: ItemEntry[]): number {
|
||||
return sumBy(entries, (e) => ItemEntry.calcAmount(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import { IBillPaymentEntryDTO } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { entriesAmountDiff } from '@/utils';
|
||||
|
||||
@Service()
|
||||
export class BillPaymentBillSync {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Saves bills payment amount changes different.
|
||||
* @param {number} tenantId -
|
||||
* @param {IBillPaymentEntryDTO[]} paymentMadeEntries -
|
||||
* @param {IBillPaymentEntryDTO[]} oldPaymentMadeEntries -
|
||||
*/
|
||||
public async saveChangeBillsPaymentAmount(
|
||||
tenantId: number,
|
||||
paymentMadeEntries: IBillPaymentEntryDTO[],
|
||||
oldPaymentMadeEntries?: IBillPaymentEntryDTO[],
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const opers: Promise<void>[] = [];
|
||||
|
||||
const diffEntries = entriesAmountDiff(
|
||||
paymentMadeEntries,
|
||||
oldPaymentMadeEntries,
|
||||
'paymentAmount',
|
||||
'billId'
|
||||
);
|
||||
diffEntries.forEach(
|
||||
(diffEntry: { paymentAmount: number; billId: number }) => {
|
||||
if (diffEntry.paymentAmount === 0) {
|
||||
return;
|
||||
}
|
||||
const oper = Bill.changePaymentAmount(
|
||||
diffEntry.billId,
|
||||
diffEntry.paymentAmount,
|
||||
trx
|
||||
);
|
||||
opers.push(oper);
|
||||
}
|
||||
);
|
||||
await Promise.all(opers);
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export class BillPaymentGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the payment common entry.
|
||||
* @param {IBillPayment} billPayment
|
||||
* @param {IBillPayment} billPayment
|
||||
* @returns {}
|
||||
*/
|
||||
private getPaymentCommonEntry = (billPayment: IBillPayment) => {
|
||||
|
||||
@@ -24,7 +24,7 @@ export class BillPaymentTransactionTransformer extends Transformer {
|
||||
/**
|
||||
* Retrieve formatted bill payment date.
|
||||
* @param entry
|
||||
* @returns
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedPaymentDate = (entry): string => {
|
||||
return this.formatDate(entry.payment.paymentDate);
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { sumBy, difference } from 'lodash';
|
||||
import {
|
||||
IBill,
|
||||
IBillPaymentDTO,
|
||||
IBillPaymentEntryDTO,
|
||||
IBillPayment,
|
||||
IBillPaymentEntry,
|
||||
} from '@/interfaces';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||
import { BillPayment } from '@/models';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export class BillPaymentValidators {
|
||||
@Inject()
|
||||
private tenancy: TenancyService;
|
||||
|
||||
/**
|
||||
* Validates the payment existance.
|
||||
* @param {BillPayment | undefined | null} payment
|
||||
* @throws {ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND)}
|
||||
*/
|
||||
public async validateBillPaymentExistance(
|
||||
payment: BillPayment | undefined | null
|
||||
) {
|
||||
if (!payment) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the bill payment existance.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
public async getPaymentMadeOrThrowError(
|
||||
tenantid: number,
|
||||
paymentMadeId: number
|
||||
) {
|
||||
const { BillPayment } = this.tenancy.models(tenantid);
|
||||
const billPayment = await BillPayment.query()
|
||||
.withGraphFetched('entries')
|
||||
.findById(paymentMadeId);
|
||||
|
||||
if (!billPayment) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
||||
}
|
||||
return billPayment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the payment account.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} paymentAccountId
|
||||
* @return {Promise<IAccountType>}
|
||||
*/
|
||||
public async getPaymentAccountOrThrowError(
|
||||
tenantId: number,
|
||||
paymentAccountId: number
|
||||
) {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const paymentAccount = await accountRepository.findOneById(
|
||||
paymentAccountId
|
||||
);
|
||||
if (!paymentAccount) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_FOUND);
|
||||
}
|
||||
// Validate the payment account type.
|
||||
if (
|
||||
!paymentAccount.isAccountType([
|
||||
ACCOUNT_TYPE.BANK,
|
||||
ACCOUNT_TYPE.CASH,
|
||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||
])
|
||||
) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_CURRENT_ASSET_TYPE);
|
||||
}
|
||||
return paymentAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the payment number uniqness.
|
||||
* @param {number} tenantId -
|
||||
* @param {string} paymentMadeNumber -
|
||||
* @return {Promise<IBillPayment>}
|
||||
*/
|
||||
public async validatePaymentNumber(
|
||||
tenantId: number,
|
||||
paymentMadeNumber: string,
|
||||
notPaymentMadeId?: number
|
||||
) {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
const foundBillPayment = await BillPayment.query().onBuild(
|
||||
(builder: any) => {
|
||||
builder.findOne('payment_number', paymentMadeNumber);
|
||||
|
||||
if (notPaymentMadeId) {
|
||||
builder.whereNot('id', notPaymentMadeId);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (foundBillPayment) {
|
||||
throw new ServiceError(ERRORS.BILL_PAYMENT_NUMBER_NOT_UNQIUE);
|
||||
}
|
||||
return foundBillPayment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether the entries bills ids exist on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public async validateBillsExistance(
|
||||
tenantId: number,
|
||||
billPaymentEntries: { billId: number }[],
|
||||
vendorId: number
|
||||
) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const entriesBillsIds = billPaymentEntries.map((e: any) => e.billId);
|
||||
|
||||
const storedBills = await Bill.query()
|
||||
.whereIn('id', entriesBillsIds)
|
||||
.where('vendor_id', vendorId);
|
||||
|
||||
const storedBillsIds = storedBills.map((t: IBill) => t.id);
|
||||
const notFoundBillsIds = difference(entriesBillsIds, storedBillsIds);
|
||||
|
||||
if (notFoundBillsIds.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_ENTRIES_IDS_NOT_FOUND);
|
||||
}
|
||||
// Validate the not opened bills.
|
||||
const notOpenedBills = storedBills.filter((bill) => !bill.openedAt);
|
||||
|
||||
if (notOpenedBills.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILLS_NOT_OPENED_YET, null, {
|
||||
notOpenedBills,
|
||||
});
|
||||
}
|
||||
return storedBills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate wether the payment amount bigger than the payable amount.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @return {void}
|
||||
*/
|
||||
public async validateBillsDueAmount(
|
||||
tenantId: number,
|
||||
billPaymentEntries: IBillPaymentEntryDTO[],
|
||||
oldPaymentEntries: IBillPaymentEntry[] = []
|
||||
) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const billsIds = billPaymentEntries.map(
|
||||
(entry: IBillPaymentEntryDTO) => entry.billId
|
||||
);
|
||||
|
||||
const storedBills = await Bill.query().whereIn('id', billsIds);
|
||||
const storedBillsMap = new Map(
|
||||
storedBills.map((bill) => {
|
||||
const oldEntries = oldPaymentEntries.filter(
|
||||
(entry) => entry.billId === bill.id
|
||||
);
|
||||
const oldPaymentAmount = sumBy(oldEntries, 'paymentAmount') || 0;
|
||||
|
||||
return [
|
||||
bill.id,
|
||||
{ ...bill, dueAmount: bill.dueAmount + oldPaymentAmount },
|
||||
];
|
||||
})
|
||||
);
|
||||
interface invalidPaymentAmountError {
|
||||
index: number;
|
||||
due_amount: number;
|
||||
}
|
||||
const hasWrongPaymentAmount: invalidPaymentAmountError[] = [];
|
||||
|
||||
billPaymentEntries.forEach((entry: IBillPaymentEntryDTO, index: number) => {
|
||||
const entryBill = storedBillsMap.get(entry.billId);
|
||||
const { dueAmount } = entryBill;
|
||||
|
||||
if (dueAmount < entry.paymentAmount) {
|
||||
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
||||
}
|
||||
});
|
||||
if (hasWrongPaymentAmount.length > 0) {
|
||||
throw new ServiceError(ERRORS.INVALID_BILL_PAYMENT_AMOUNT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the payment receive entries IDs existance.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
public async validateEntriesIdsExistance(
|
||||
tenantId: number,
|
||||
billPaymentId: number,
|
||||
billPaymentEntries: IBillPaymentEntry[]
|
||||
) {
|
||||
const { BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const entriesIds = billPaymentEntries
|
||||
.filter((entry: any) => entry.id)
|
||||
.map((entry: any) => entry.id);
|
||||
|
||||
const storedEntries = await BillPaymentEntry.query().where(
|
||||
'bill_payment_id',
|
||||
billPaymentId
|
||||
);
|
||||
|
||||
const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
|
||||
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
||||
|
||||
if (notFoundEntriesIds.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_PAYMENT_ENTRIES_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* * Validate the payment vendor whether modified.
|
||||
* @param {string} billPaymentNo
|
||||
*/
|
||||
public validateVendorNotModified(
|
||||
billPaymentDTO: IBillPaymentDTO,
|
||||
oldBillPayment: IBillPayment
|
||||
) {
|
||||
if (billPaymentDTO.vendorId !== oldBillPayment.vendorId) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_NUMBER_SHOULD_NOT_MODIFY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the payment account currency code. The deposit account curreny
|
||||
* should be equals the customer currency code or the base currency.
|
||||
* @param {string} paymentAccountCurrency
|
||||
* @param {string} customerCurrency
|
||||
* @param {string} baseCurrency
|
||||
* @throws {ServiceError(ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID)}
|
||||
*/
|
||||
public validateWithdrawalAccountCurrency = (
|
||||
paymentAccountCurrency: string,
|
||||
customerCurrency: string,
|
||||
baseCurrency: string
|
||||
) => {
|
||||
if (
|
||||
paymentAccountCurrency !== customerCurrency &&
|
||||
paymentAccountCurrency !== baseCurrency
|
||||
) {
|
||||
throw new ServiceError(ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the given vendor has no associated payments.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
*/
|
||||
public async validateVendorHasNoPayments(tenantId: number, vendorId: number) {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
const payments = await BillPayment.query().where('vendor_id', vendorId);
|
||||
|
||||
if (payments.length > 0) {
|
||||
throw new ServiceError(ERRORS.VENDOR_HAS_PAYMENTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,713 +0,0 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { sumBy, difference } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
IBill,
|
||||
IBillPaymentDTO,
|
||||
IBillPaymentEntryDTO,
|
||||
IBillPayment,
|
||||
IBillPaymentsFilter,
|
||||
IPaginationMeta,
|
||||
IFilterMeta,
|
||||
IBillPaymentEntry,
|
||||
IBillPaymentEventCreatedPayload,
|
||||
IBillPaymentEventEditedPayload,
|
||||
IBillPaymentEventDeletedPayload,
|
||||
IBillPaymentCreatingPayload,
|
||||
IBillPaymentEditingPayload,
|
||||
IBillPaymentDeletingPayload,
|
||||
IVendor,
|
||||
} from '@/interfaces';
|
||||
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { entriesAmountDiff, formatDateFields } from 'utils';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||
import { BillPaymentTransformer } from './BillPaymentTransformer';
|
||||
import { ERRORS } from './constants';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { TenantMetadata } from '@/system/models';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
/**
|
||||
* Bill payments service.
|
||||
* @service
|
||||
*/
|
||||
@Service('BillPayments')
|
||||
export default class BillPaymentsService implements IBillPaymentsService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
journalService: JournalPosterService;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
/**
|
||||
* Validates the bill payment existance.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
private async getPaymentMadeOrThrowError(
|
||||
tenantid: number,
|
||||
paymentMadeId: number
|
||||
) {
|
||||
const { BillPayment } = this.tenancy.models(tenantid);
|
||||
const billPayment = await BillPayment.query()
|
||||
.withGraphFetched('entries')
|
||||
.findById(paymentMadeId);
|
||||
|
||||
if (!billPayment) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
||||
}
|
||||
return billPayment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the payment account.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} paymentAccountId
|
||||
* @return {Promise<IAccountType>}
|
||||
*/
|
||||
private async getPaymentAccountOrThrowError(
|
||||
tenantId: number,
|
||||
paymentAccountId: number
|
||||
) {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const paymentAccount = await accountRepository.findOneById(
|
||||
paymentAccountId
|
||||
);
|
||||
if (!paymentAccount) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_FOUND);
|
||||
}
|
||||
// Validate the payment account type.
|
||||
if (
|
||||
!paymentAccount.isAccountType([
|
||||
ACCOUNT_TYPE.BANK,
|
||||
ACCOUNT_TYPE.CASH,
|
||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||
])
|
||||
) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_CURRENT_ASSET_TYPE);
|
||||
}
|
||||
return paymentAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the payment number uniqness.
|
||||
* @param {number} tenantId -
|
||||
* @param {string} paymentMadeNumber -
|
||||
* @return {Promise<IBillPayment>}
|
||||
*/
|
||||
private async validatePaymentNumber(
|
||||
tenantId: number,
|
||||
paymentMadeNumber: string,
|
||||
notPaymentMadeId?: number
|
||||
) {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
const foundBillPayment = await BillPayment.query().onBuild(
|
||||
(builder: any) => {
|
||||
builder.findOne('payment_number', paymentMadeNumber);
|
||||
|
||||
if (notPaymentMadeId) {
|
||||
builder.whereNot('id', notPaymentMadeId);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (foundBillPayment) {
|
||||
throw new ServiceError(ERRORS.BILL_PAYMENT_NUMBER_NOT_UNQIUE);
|
||||
}
|
||||
return foundBillPayment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether the entries bills ids exist on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public async validateBillsExistance(
|
||||
tenantId: number,
|
||||
billPaymentEntries: { billId: number }[],
|
||||
vendorId: number
|
||||
) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const entriesBillsIds = billPaymentEntries.map((e: any) => e.billId);
|
||||
|
||||
const storedBills = await Bill.query()
|
||||
.whereIn('id', entriesBillsIds)
|
||||
.where('vendor_id', vendorId);
|
||||
|
||||
const storedBillsIds = storedBills.map((t: IBill) => t.id);
|
||||
const notFoundBillsIds = difference(entriesBillsIds, storedBillsIds);
|
||||
|
||||
if (notFoundBillsIds.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_ENTRIES_IDS_NOT_FOUND);
|
||||
}
|
||||
// Validate the not opened bills.
|
||||
const notOpenedBills = storedBills.filter((bill) => !bill.openedAt);
|
||||
|
||||
if (notOpenedBills.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILLS_NOT_OPENED_YET, null, {
|
||||
notOpenedBills,
|
||||
});
|
||||
}
|
||||
return storedBills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate wether the payment amount bigger than the payable amount.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @return {void}
|
||||
*/
|
||||
private async validateBillsDueAmount(
|
||||
tenantId: number,
|
||||
billPaymentEntries: IBillPaymentEntryDTO[],
|
||||
oldPaymentEntries: IBillPaymentEntry[] = []
|
||||
) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const billsIds = billPaymentEntries.map(
|
||||
(entry: IBillPaymentEntryDTO) => entry.billId
|
||||
);
|
||||
|
||||
const storedBills = await Bill.query().whereIn('id', billsIds);
|
||||
const storedBillsMap = new Map(
|
||||
storedBills.map((bill) => {
|
||||
const oldEntries = oldPaymentEntries.filter(
|
||||
(entry) => entry.billId === bill.id
|
||||
);
|
||||
const oldPaymentAmount = sumBy(oldEntries, 'paymentAmount') || 0;
|
||||
|
||||
return [
|
||||
bill.id,
|
||||
{ ...bill, dueAmount: bill.dueAmount + oldPaymentAmount },
|
||||
];
|
||||
})
|
||||
);
|
||||
interface invalidPaymentAmountError {
|
||||
index: number;
|
||||
due_amount: number;
|
||||
}
|
||||
const hasWrongPaymentAmount: invalidPaymentAmountError[] = [];
|
||||
|
||||
billPaymentEntries.forEach((entry: IBillPaymentEntryDTO, index: number) => {
|
||||
const entryBill = storedBillsMap.get(entry.billId);
|
||||
const { dueAmount } = entryBill;
|
||||
|
||||
if (dueAmount < entry.paymentAmount) {
|
||||
hasWrongPaymentAmount.push({ index, due_amount: dueAmount });
|
||||
}
|
||||
});
|
||||
if (hasWrongPaymentAmount.length > 0) {
|
||||
throw new ServiceError(ERRORS.INVALID_BILL_PAYMENT_AMOUNT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the payment receive entries IDs existance.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
private async validateEntriesIdsExistance(
|
||||
tenantId: number,
|
||||
billPaymentId: number,
|
||||
billPaymentEntries: IBillPaymentEntry[]
|
||||
) {
|
||||
const { BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const entriesIds = billPaymentEntries
|
||||
.filter((entry: any) => entry.id)
|
||||
.map((entry: any) => entry.id);
|
||||
|
||||
const storedEntries = await BillPaymentEntry.query().where(
|
||||
'bill_payment_id',
|
||||
billPaymentId
|
||||
);
|
||||
|
||||
const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
|
||||
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
|
||||
|
||||
if (notFoundEntriesIds.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_PAYMENT_ENTRIES_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* * Validate the payment vendor whether modified.
|
||||
* @param {string} billPaymentNo
|
||||
*/
|
||||
private validateVendorNotModified(
|
||||
billPaymentDTO: IBillPaymentDTO,
|
||||
oldBillPayment: IBillPayment
|
||||
) {
|
||||
if (billPaymentDTO.vendorId !== oldBillPayment.vendorId) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_NUMBER_SHOULD_NOT_MODIFY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the payment account currency code. The deposit account curreny
|
||||
* should be equals the customer currency code or the base currency.
|
||||
* @param {string} paymentAccountCurrency
|
||||
* @param {string} customerCurrency
|
||||
* @param {string} baseCurrency
|
||||
* @throws {ServiceError(ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID)}
|
||||
*/
|
||||
public validateWithdrawalAccountCurrency = (
|
||||
paymentAccountCurrency: string,
|
||||
customerCurrency: string,
|
||||
baseCurrency: string
|
||||
) => {
|
||||
if (
|
||||
paymentAccountCurrency !== customerCurrency &&
|
||||
paymentAccountCurrency !== baseCurrency
|
||||
) {
|
||||
throw new ServiceError(ERRORS.WITHDRAWAL_ACCOUNT_CURRENCY_INVALID);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms create/edit DTO to model.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillPaymentDTO} billPaymentDTO - Bill payment.
|
||||
* @param {IBillPayment} oldBillPayment - Old bill payment.
|
||||
* @return {Promise<IBillPayment>}
|
||||
*/
|
||||
async transformDTOToModel(
|
||||
tenantId: number,
|
||||
billPaymentDTO: IBillPaymentDTO,
|
||||
vendor: IVendor,
|
||||
oldBillPayment?: IBillPayment
|
||||
): Promise<IBillPayment> {
|
||||
const initialDTO = {
|
||||
...formatDateFields(billPaymentDTO, ['paymentDate']),
|
||||
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
|
||||
currencyCode: vendor.currencyCode,
|
||||
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
||||
entries: billPaymentDTO.entries,
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO<IBillPayment>(tenantId)
|
||||
)(initialDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new bill payment transcations and store it to the storage
|
||||
* with associated bills entries and journal transactions.
|
||||
*
|
||||
* Precedures:-
|
||||
* ------
|
||||
* - Records the bill payment transaction.
|
||||
* - Records the bill payment associated entries.
|
||||
* - Increment the payment amount of the given vendor bills.
|
||||
* - Decrement the vendor balance.
|
||||
* - Records payment journal entries.
|
||||
* ------
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {BillPaymentDTO} billPayment - Bill payment object.
|
||||
*/
|
||||
public async createBillPayment(
|
||||
tenantId: number,
|
||||
billPaymentDTO: IBillPaymentDTO
|
||||
): Promise<IBillPayment> {
|
||||
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||
|
||||
// Retrieves the payment vendor or throw not found error.
|
||||
const vendor = await Contact.query()
|
||||
.findById(billPaymentDTO.vendorId)
|
||||
.modify('vendor')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transform create DTO to model object.
|
||||
const billPaymentObj = await this.transformDTOToModel(
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
vendor
|
||||
);
|
||||
// Validate the payment account existance and type.
|
||||
const paymentAccount = await this.getPaymentAccountOrThrowError(
|
||||
tenantId,
|
||||
billPaymentObj.paymentAccountId
|
||||
);
|
||||
// Validate the payment number uniquiness.
|
||||
if (billPaymentObj.paymentNumber) {
|
||||
await this.validatePaymentNumber(tenantId, billPaymentObj.paymentNumber);
|
||||
}
|
||||
// Validates the bills existance and associated to the given vendor.
|
||||
await this.validateBillsExistance(
|
||||
tenantId,
|
||||
billPaymentObj.entries,
|
||||
billPaymentDTO.vendorId
|
||||
);
|
||||
// Validates the bills due payment amount.
|
||||
await this.validateBillsDueAmount(tenantId, billPaymentObj.entries);
|
||||
|
||||
// Validates the withdrawal account currency code.
|
||||
this.validateWithdrawalAccountCurrency(
|
||||
paymentAccount.currencyCode,
|
||||
vendor.currencyCode,
|
||||
tenantMeta.baseCurrency
|
||||
);
|
||||
// Writes bill payment transacation with associated transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillPaymentCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onCreating, {
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
trx,
|
||||
} as IBillPaymentCreatingPayload);
|
||||
|
||||
// Writes the bill payment graph to the storage.
|
||||
const billPayment = await BillPayment.query(trx).insertGraphAndFetch({
|
||||
...billPaymentObj,
|
||||
});
|
||||
|
||||
// Triggers `onBillPaymentCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
|
||||
tenantId,
|
||||
billPayment,
|
||||
billPaymentId: billPayment.id,
|
||||
trx,
|
||||
} as IBillPaymentEventCreatedPayload);
|
||||
|
||||
return billPayment;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the details of the given bill payment.
|
||||
*
|
||||
* Preceducres:
|
||||
* ------
|
||||
* - Update the bill payment transaction.
|
||||
* - Insert the new bill payment entries that have no ids.
|
||||
* - Update the bill paymeny entries that have ids.
|
||||
* - Delete the bill payment entries that not presented.
|
||||
* - Re-insert the journal transactions and update the diff accounts balance.
|
||||
* - Update the diff vendor balance.
|
||||
* - Update the diff bill payment amount.
|
||||
* ------
|
||||
* @param {number} tenantId - Tenant id
|
||||
* @param {Integer} billPaymentId
|
||||
* @param {BillPaymentDTO} billPayment
|
||||
* @param {IBillPayment} oldBillPayment
|
||||
*/
|
||||
public async editBillPayment(
|
||||
tenantId: number,
|
||||
billPaymentId: number,
|
||||
billPaymentDTO
|
||||
): Promise<IBillPayment> {
|
||||
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||
|
||||
//
|
||||
const oldBillPayment = await this.getPaymentMadeOrThrowError(
|
||||
tenantId,
|
||||
billPaymentId
|
||||
);
|
||||
|
||||
//
|
||||
const vendor = await Contact.query()
|
||||
.modify('vendor')
|
||||
.findById(billPaymentDTO.vendorId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transform bill payment DTO to model object.
|
||||
const billPaymentObj = await this.transformDTOToModel(
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
vendor,
|
||||
oldBillPayment
|
||||
);
|
||||
// Validate vendor not modified.
|
||||
this.validateVendorNotModified(billPaymentDTO, oldBillPayment);
|
||||
|
||||
// Validate the payment account existance and type.
|
||||
const paymentAccount = await this.getPaymentAccountOrThrowError(
|
||||
tenantId,
|
||||
billPaymentObj.paymentAccountId
|
||||
);
|
||||
// Validate the items entries IDs existance on the storage.
|
||||
await this.validateEntriesIdsExistance(
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
billPaymentObj.entries
|
||||
);
|
||||
// Validate the bills existance and associated to the given vendor.
|
||||
await this.validateBillsExistance(
|
||||
tenantId,
|
||||
billPaymentObj.entries,
|
||||
billPaymentDTO.vendorId
|
||||
);
|
||||
// Validates the bills due payment amount.
|
||||
await this.validateBillsDueAmount(
|
||||
tenantId,
|
||||
billPaymentObj.entries,
|
||||
oldBillPayment.entries
|
||||
);
|
||||
// Validate the payment number uniquiness.
|
||||
if (billPaymentObj.paymentNumber) {
|
||||
await this.validatePaymentNumber(
|
||||
tenantId,
|
||||
billPaymentObj.paymentNumber,
|
||||
billPaymentId
|
||||
);
|
||||
}
|
||||
// Validates the withdrawal account currency code.
|
||||
this.validateWithdrawalAccountCurrency(
|
||||
paymentAccount.currencyCode,
|
||||
vendor.currencyCode,
|
||||
tenantMeta.baseCurrency
|
||||
);
|
||||
// Edits the bill transactions with associated transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillPaymentEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onEditing, {
|
||||
tenantId,
|
||||
oldBillPayment,
|
||||
billPaymentDTO,
|
||||
trx,
|
||||
} as IBillPaymentEditingPayload);
|
||||
|
||||
// Deletes the bill payment transaction graph from the storage.
|
||||
const billPayment = await BillPayment.query(trx).upsertGraphAndFetch({
|
||||
id: billPaymentId,
|
||||
...billPaymentObj,
|
||||
});
|
||||
// Triggers `onBillPaymentEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onEdited, {
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
billPayment,
|
||||
oldBillPayment,
|
||||
trx,
|
||||
} as IBillPaymentEventEditedPayload);
|
||||
|
||||
return billPayment;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the bill payment and associated transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Integer} billPaymentId - The given bill payment id.
|
||||
* @return {Promise}
|
||||
*/
|
||||
public async deleteBillPayment(tenantId: number, billPaymentId: number) {
|
||||
const { BillPayment, BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the bill payment or throw not found service error.
|
||||
const oldBillPayment = await this.getPaymentMadeOrThrowError(
|
||||
tenantId,
|
||||
billPaymentId
|
||||
);
|
||||
// Deletes the bill transactions with associated transactions under
|
||||
// unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillPaymentDeleting` payload.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onDeleting, {
|
||||
tenantId,
|
||||
trx,
|
||||
oldBillPayment,
|
||||
} as IBillPaymentDeletingPayload);
|
||||
|
||||
// Deletes the bill payment assocaited entries.
|
||||
await BillPaymentEntry.query(trx)
|
||||
.where('bill_payment_id', billPaymentId)
|
||||
.delete();
|
||||
|
||||
// Deletes the bill payment transaction.
|
||||
await BillPayment.query(trx).where('id', billPaymentId).delete();
|
||||
|
||||
// Triggers `onBillPaymentDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onDeleted, {
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
oldBillPayment,
|
||||
trx,
|
||||
} as IBillPaymentEventDeletedPayload);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve payment made associated bills.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} billPaymentId -
|
||||
*/
|
||||
public async getPaymentBills(tenantId: number, billPaymentId: number) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const billPayment = await this.getPaymentMadeOrThrowError(
|
||||
tenantId,
|
||||
billPaymentId
|
||||
);
|
||||
const paymentBillsIds = billPayment.entries.map((entry) => entry.id);
|
||||
|
||||
const bills = await Bill.query().whereIn('id', paymentBillsIds);
|
||||
|
||||
return bills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve bill payment.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billPyamentId
|
||||
* @return {Promise<IBillPayment>}
|
||||
*/
|
||||
public async getBillPayment(
|
||||
tenantId: number,
|
||||
billPyamentId: number
|
||||
): Promise<IBillPayment> {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
const billPayment = await BillPayment.query()
|
||||
.withGraphFetched('entries.bill')
|
||||
.withGraphFetched('vendor')
|
||||
.withGraphFetched('paymentAccount')
|
||||
.withGraphFetched('transactions')
|
||||
.withGraphFetched('branch')
|
||||
.findById(billPyamentId);
|
||||
|
||||
if (!billPayment) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
||||
}
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
billPayment,
|
||||
new BillPaymentTransformer()
|
||||
);
|
||||
}
|
||||
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve bill payment paginted and filterable list.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillPaymentsFilter} billPaymentsFilter
|
||||
*/
|
||||
public async listBillPayments(
|
||||
tenantId: number,
|
||||
filterDTO: IBillPaymentsFilter
|
||||
): Promise<{
|
||||
billPayments: IBillPayment;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
BillPayment,
|
||||
filter
|
||||
);
|
||||
|
||||
const { results, pagination } = await BillPayment.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('vendor');
|
||||
builder.withGraphFetched('paymentAccount');
|
||||
|
||||
dynamicList.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Transformes the bill payments models to POJO.
|
||||
const billPayments = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new BillPaymentTransformer()
|
||||
);
|
||||
return {
|
||||
billPayments,
|
||||
pagination,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves bills payment amount changes different.
|
||||
* @param {number} tenantId -
|
||||
* @param {IBillPaymentEntryDTO[]} paymentMadeEntries -
|
||||
* @param {IBillPaymentEntryDTO[]} oldPaymentMadeEntries -
|
||||
*/
|
||||
public async saveChangeBillsPaymentAmount(
|
||||
tenantId: number,
|
||||
paymentMadeEntries: IBillPaymentEntryDTO[],
|
||||
oldPaymentMadeEntries?: IBillPaymentEntryDTO[],
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const opers: Promise<void>[] = [];
|
||||
|
||||
const diffEntries = entriesAmountDiff(
|
||||
paymentMadeEntries,
|
||||
oldPaymentMadeEntries,
|
||||
'paymentAmount',
|
||||
'billId'
|
||||
);
|
||||
diffEntries.forEach(
|
||||
(diffEntry: { paymentAmount: number; billId: number }) => {
|
||||
if (diffEntry.paymentAmount === 0) {
|
||||
return;
|
||||
}
|
||||
const oper = Bill.changePaymentAmount(
|
||||
diffEntry.billId,
|
||||
diffEntry.paymentAmount,
|
||||
trx
|
||||
);
|
||||
opers.push(oper);
|
||||
}
|
||||
);
|
||||
await Promise.all(opers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given vendor has no associated payments.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
*/
|
||||
public async validateVendorHasNoPayments(tenantId: number, vendorId: number) {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
const payments = await BillPayment.query().where('vendor_id', vendorId);
|
||||
|
||||
if (payments.length > 0) {
|
||||
throw new ServiceError(ERRORS.VENDOR_HAS_PAYMENTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IBillPaymentDTO, IBillPayment } from '@/interfaces';
|
||||
import { CreateBillPayment } from './CreateBillPayment';
|
||||
import { DeleteBillPayment } from './DeleteBillPayment';
|
||||
import { EditBillPayment } from './EditBillPayment';
|
||||
import { GetBillPayments } from './GetBillPayments';
|
||||
import { GetBillPayment } from './GetBillPayment';
|
||||
import { GetPaymentBills } from './GetPaymentBills';
|
||||
|
||||
/**
|
||||
* Bill payments application.
|
||||
* @service
|
||||
*/
|
||||
@Service()
|
||||
export class BillPaymentsApplication {
|
||||
@Inject()
|
||||
private createBillPaymentService: CreateBillPayment;
|
||||
|
||||
@Inject()
|
||||
private deleteBillPaymentService: DeleteBillPayment;
|
||||
|
||||
@Inject()
|
||||
private editBillPaymentService: EditBillPayment;
|
||||
|
||||
@Inject()
|
||||
private getBillPaymentsService: GetBillPayments;
|
||||
|
||||
@Inject()
|
||||
private getBillPaymentService: GetBillPayment;
|
||||
|
||||
@Inject()
|
||||
private getPaymentBillsService: GetPaymentBills;
|
||||
|
||||
/**
|
||||
* Creates a bill payment with associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillPaymentDTO} billPaymentDTO
|
||||
* @returns {Promise<IBillPayment>}
|
||||
*/
|
||||
public createBillPayment(
|
||||
tenantId: number,
|
||||
billPaymentDTO: IBillPaymentDTO
|
||||
): Promise<IBillPayment> {
|
||||
return this.createBillPaymentService.createBillPayment(
|
||||
tenantId,
|
||||
billPaymentDTO
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delets the given bill payment with associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billPaymentId
|
||||
*/
|
||||
public deleteBillPayment(tenantId: number, billPaymentId: number) {
|
||||
return this.deleteBillPaymentService.deleteBillPayment(
|
||||
tenantId,
|
||||
billPaymentId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the given bill payment with associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billPaymentId
|
||||
* @param billPaymentDTO
|
||||
* @returns {Promise<IBillPayment>}
|
||||
*/
|
||||
public editBillPayment(
|
||||
tenantId: number,
|
||||
billPaymentId: number,
|
||||
billPaymentDTO
|
||||
): Promise<IBillPayment> {
|
||||
return this.editBillPaymentService.editBillPayment(
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
billPaymentDTO
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves bill payments list.
|
||||
* @param {number} tenantId
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
public getBillPayments(tenantId: number, filterDTO: IBillPaymentsFilter) {
|
||||
return this.getBillPaymentsService.getBillPayments(tenantId, filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve specific bill payment.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billPyamentId
|
||||
* @returns
|
||||
*/
|
||||
public getBillPayment(tenantId: number, billPyamentId: number) {
|
||||
return this.getBillPaymentService.getBillPayment(tenantId, billPyamentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve payment made associated bills.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} billPaymentId -
|
||||
*/
|
||||
public getPaymentBills(tenantId: number, billPaymentId: number) {
|
||||
return this.getPaymentBillsService.getPaymentBills(tenantId, billPaymentId);
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,10 @@ import { IBill, IBillPayment, IBillReceivePageEntry } from '@/interfaces';
|
||||
import { ERRORS } from './constants';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
|
||||
/**
|
||||
* Bill payments edit and create pages services.
|
||||
*/
|
||||
@Service()
|
||||
export default class BillPaymentsPages {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
private tenancy: TenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve bill payment with associated metadata.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import { sumBy } from 'lodash';
|
||||
import { IBillPayment, IBillPaymentDTO, IVendor } from '@/interfaces';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { formatDateFields } from '@/utils';
|
||||
|
||||
@Service()
|
||||
export class CommandBillPaymentDTOTransformer {
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
/**
|
||||
* Transforms create/edit DTO to model.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillPaymentDTO} billPaymentDTO - Bill payment.
|
||||
* @param {IBillPayment} oldBillPayment - Old bill payment.
|
||||
* @return {Promise<IBillPayment>}
|
||||
*/
|
||||
public async transformDTOToModel(
|
||||
tenantId: number,
|
||||
billPaymentDTO: IBillPaymentDTO,
|
||||
vendor: IVendor,
|
||||
oldBillPayment?: IBillPayment
|
||||
): Promise<IBillPayment> {
|
||||
const initialDTO = {
|
||||
...formatDateFields(billPaymentDTO, ['paymentDate']),
|
||||
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
|
||||
currencyCode: vendor.currencyCode,
|
||||
exchangeRate: billPaymentDTO.exchangeRate || 1,
|
||||
entries: billPaymentDTO.entries,
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO<IBillPayment>(tenantId)
|
||||
)(initialDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
IBillPaymentDTO,
|
||||
IBillPayment,
|
||||
IBillPaymentEventCreatedPayload,
|
||||
IBillPaymentCreatingPayload,
|
||||
} from '@/interfaces';
|
||||
import { TenantMetadata } from '@/system/models';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||
import { CommandBillPaymentDTOTransformer } from './CommandBillPaymentDTOTransformer';
|
||||
|
||||
@Service()
|
||||
export class CreateBillPayment {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validators: BillPaymentValidators;
|
||||
|
||||
@Inject()
|
||||
private commandTransformerDTO: CommandBillPaymentDTOTransformer;
|
||||
|
||||
/**
|
||||
* Creates a new bill payment transcations and store it to the storage
|
||||
* with associated bills entries and journal transactions.
|
||||
* ------
|
||||
* Precedures:-
|
||||
* ------
|
||||
* - Records the bill payment transaction.
|
||||
* - Records the bill payment associated entries.
|
||||
* - Increment the payment amount of the given vendor bills.
|
||||
* - Decrement the vendor balance.
|
||||
* - Records payment journal entries.
|
||||
* ------
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {BillPaymentDTO} billPayment - Bill payment object.
|
||||
*/
|
||||
public async createBillPayment(
|
||||
tenantId: number,
|
||||
billPaymentDTO: IBillPaymentDTO
|
||||
): Promise<IBillPayment> {
|
||||
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||
|
||||
// Retrieves the payment vendor or throw not found error.
|
||||
const vendor = await Contact.query()
|
||||
.findById(billPaymentDTO.vendorId)
|
||||
.modify('vendor')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transform create DTO to model object.
|
||||
const billPaymentObj = await this.commandTransformerDTO.transformDTOToModel(
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
vendor
|
||||
);
|
||||
// Validate the payment account existance and type.
|
||||
const paymentAccount = await this.validators.getPaymentAccountOrThrowError(
|
||||
tenantId,
|
||||
billPaymentObj.paymentAccountId
|
||||
);
|
||||
// Validate the payment number uniquiness.
|
||||
if (billPaymentObj.paymentNumber) {
|
||||
await this.validators.validatePaymentNumber(
|
||||
tenantId,
|
||||
billPaymentObj.paymentNumber
|
||||
);
|
||||
}
|
||||
// Validates the bills existance and associated to the given vendor.
|
||||
await this.validators.validateBillsExistance(
|
||||
tenantId,
|
||||
billPaymentObj.entries,
|
||||
billPaymentDTO.vendorId
|
||||
);
|
||||
// Validates the bills due payment amount.
|
||||
await this.validators.validateBillsDueAmount(
|
||||
tenantId,
|
||||
billPaymentObj.entries
|
||||
);
|
||||
// Validates the withdrawal account currency code.
|
||||
this.validators.validateWithdrawalAccountCurrency(
|
||||
paymentAccount.currencyCode,
|
||||
vendor.currencyCode,
|
||||
tenantMeta.baseCurrency
|
||||
);
|
||||
// Writes bill payment transacation with associated transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillPaymentCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onCreating, {
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
trx,
|
||||
} as IBillPaymentCreatingPayload);
|
||||
|
||||
// Writes the bill payment graph to the storage.
|
||||
const billPayment = await BillPayment.query(trx).insertGraphAndFetch({
|
||||
...billPaymentObj,
|
||||
});
|
||||
|
||||
// Triggers `onBillPaymentCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
|
||||
tenantId,
|
||||
billPayment,
|
||||
billPaymentId: billPayment.id,
|
||||
trx,
|
||||
} as IBillPaymentEventCreatedPayload);
|
||||
|
||||
return billPayment;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Knex } from 'knex';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IBillPaymentDeletingPayload,
|
||||
IBillPaymentEventDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class DeleteBillPayment {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validators: BillPaymentValidators;
|
||||
|
||||
/**
|
||||
* Deletes the bill payment and associated transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Integer} billPaymentId - The given bill payment id.
|
||||
* @return {Promise}
|
||||
*/
|
||||
public async deleteBillPayment(tenantId: number, billPaymentId: number) {
|
||||
const { BillPayment, BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the bill payment or throw not found service error.
|
||||
const oldBillPayment = await BillPayment.query()
|
||||
.withGraphFetched('entries')
|
||||
.findById(billPaymentId);
|
||||
|
||||
// Validates the bill payment existance.
|
||||
this.validators.validateBillPaymentExistance(oldBillPayment);
|
||||
|
||||
// Deletes the bill transactions with associated transactions under
|
||||
// unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillPaymentDeleting` payload.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onDeleting, {
|
||||
tenantId,
|
||||
trx,
|
||||
oldBillPayment,
|
||||
} as IBillPaymentDeletingPayload);
|
||||
|
||||
// Deletes the bill payment assocaited entries.
|
||||
await BillPaymentEntry.query(trx)
|
||||
.where('bill_payment_id', billPaymentId)
|
||||
.delete();
|
||||
|
||||
// Deletes the bill payment transaction.
|
||||
await BillPayment.query(trx).where('id', billPaymentId).delete();
|
||||
|
||||
// Triggers `onBillPaymentDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onDeleted, {
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
oldBillPayment,
|
||||
trx,
|
||||
} as IBillPaymentEventDeletedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
IBillPayment,
|
||||
IBillPaymentEditingPayload,
|
||||
IBillPaymentEventEditedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { Knex } from 'knex';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { CommandBillPaymentDTOTransformer } from './CommandBillPaymentDTOTransformer';
|
||||
import { TenantMetadata } from '@/system/models';
|
||||
|
||||
@Service()
|
||||
export class EditBillPayment {
|
||||
@Inject()
|
||||
private validators: BillPaymentValidators;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private transformer: CommandBillPaymentDTOTransformer;
|
||||
|
||||
/**
|
||||
* Edits the details of the given bill payment.
|
||||
*
|
||||
* Preceducres:
|
||||
* ------
|
||||
* - Update the bill payment transaction.
|
||||
* - Insert the new bill payment entries that have no ids.
|
||||
* - Update the bill paymeny entries that have ids.
|
||||
* - Delete the bill payment entries that not presented.
|
||||
* - Re-insert the journal transactions and update the diff accounts balance.
|
||||
* - Update the diff vendor balance.
|
||||
* - Update the diff bill payment amount.
|
||||
* ------
|
||||
* @param {number} tenantId - Tenant id
|
||||
* @param {Integer} billPaymentId
|
||||
* @param {BillPaymentDTO} billPayment
|
||||
* @param {IBillPayment} oldBillPayment
|
||||
*/
|
||||
public async editBillPayment(
|
||||
tenantId: number,
|
||||
billPaymentId: number,
|
||||
billPaymentDTO
|
||||
): Promise<IBillPayment> {
|
||||
const { BillPayment, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||
|
||||
const oldBillPayment = await BillPayment.query().findById(billPaymentId);
|
||||
|
||||
// Validates the bill payment existance.
|
||||
this.validators.validateBillPaymentExistance(oldBillPayment);
|
||||
|
||||
//
|
||||
const vendor = await Contact.query()
|
||||
.modify('vendor')
|
||||
.findById(billPaymentDTO.vendorId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transform bill payment DTO to model object.
|
||||
const billPaymentObj = await this.transformer.transformDTOToModel(
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
vendor,
|
||||
oldBillPayment
|
||||
);
|
||||
// Validate vendor not modified.
|
||||
this.validators.validateVendorNotModified(billPaymentDTO, oldBillPayment);
|
||||
|
||||
// Validate the payment account existance and type.
|
||||
const paymentAccount = await this.validators.getPaymentAccountOrThrowError(
|
||||
tenantId,
|
||||
billPaymentObj.paymentAccountId
|
||||
);
|
||||
// Validate the items entries IDs existance on the storage.
|
||||
await this.validators.validateEntriesIdsExistance(
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
billPaymentObj.entries
|
||||
);
|
||||
// Validate the bills existance and associated to the given vendor.
|
||||
await this.validators.validateBillsExistance(
|
||||
tenantId,
|
||||
billPaymentObj.entries,
|
||||
billPaymentDTO.vendorId
|
||||
);
|
||||
// Validates the bills due payment amount.
|
||||
await this.validators.validateBillsDueAmount(
|
||||
tenantId,
|
||||
billPaymentObj.entries,
|
||||
oldBillPayment.entries
|
||||
);
|
||||
// Validate the payment number uniquiness.
|
||||
if (billPaymentObj.paymentNumber) {
|
||||
await this.validators.validatePaymentNumber(
|
||||
tenantId,
|
||||
billPaymentObj.paymentNumber,
|
||||
billPaymentId
|
||||
);
|
||||
}
|
||||
// Validates the withdrawal account currency code.
|
||||
this.validators.validateWithdrawalAccountCurrency(
|
||||
paymentAccount.currencyCode,
|
||||
vendor.currencyCode,
|
||||
tenantMeta.baseCurrency
|
||||
);
|
||||
// Edits the bill transactions with associated transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillPaymentEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onEditing, {
|
||||
tenantId,
|
||||
oldBillPayment,
|
||||
billPaymentDTO,
|
||||
trx,
|
||||
} as IBillPaymentEditingPayload);
|
||||
|
||||
// Deletes the bill payment transaction graph from the storage.
|
||||
const billPayment = await BillPayment.query(trx).upsertGraphAndFetch({
|
||||
id: billPaymentId,
|
||||
...billPaymentObj,
|
||||
});
|
||||
// Triggers `onBillPaymentEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.billPayment.onEdited, {
|
||||
tenantId,
|
||||
billPaymentId,
|
||||
billPayment,
|
||||
oldBillPayment,
|
||||
trx,
|
||||
} as IBillPaymentEventEditedPayload);
|
||||
|
||||
return billPayment;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { IBillPayment } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ERRORS } from './constants';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { BillPaymentTransformer } from './BillPaymentTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { BillsValidators } from '../Bills/BillsValidators';
|
||||
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||
|
||||
@Service()
|
||||
export class GetBillPayment {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private validators: BillPaymentValidators;
|
||||
|
||||
/**
|
||||
* Retrieve bill payment.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billPyamentId
|
||||
* @return {Promise<IBillPayment>}
|
||||
*/
|
||||
public async getBillPayment(
|
||||
tenantId: number,
|
||||
billPyamentId: number
|
||||
): Promise<IBillPayment> {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
const billPayment = await BillPayment.query()
|
||||
.withGraphFetched('entries.bill')
|
||||
.withGraphFetched('vendor')
|
||||
.withGraphFetched('paymentAccount')
|
||||
.withGraphFetched('transactions')
|
||||
.withGraphFetched('branch')
|
||||
.findById(billPyamentId);
|
||||
|
||||
// Validates the bill payment existance.
|
||||
this.validators.validateBillPaymentExistance(billPayment);
|
||||
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
billPayment,
|
||||
new BillPaymentTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IBillPayment,
|
||||
IBillPaymentsFilter,
|
||||
IPaginationMeta,
|
||||
IFilterMeta,
|
||||
} from '@/interfaces';
|
||||
import { BillPaymentTransformer } from './BillPaymentTransformer';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export class GetBillPayments {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve bill payment paginted and filterable list.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillPaymentsFilter} billPaymentsFilter
|
||||
*/
|
||||
public async getBillPayments(
|
||||
tenantId: number,
|
||||
filterDTO: IBillPaymentsFilter
|
||||
): Promise<{
|
||||
billPayments: IBillPayment;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
BillPayment,
|
||||
filter
|
||||
);
|
||||
const { results, pagination } = await BillPayment.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('vendor');
|
||||
builder.withGraphFetched('paymentAccount');
|
||||
|
||||
dynamicList.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Transformes the bill payments models to POJO.
|
||||
const billPayments = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new BillPaymentTransformer()
|
||||
);
|
||||
return {
|
||||
billPayments,
|
||||
pagination,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { BillPaymentValidators } from './BillPaymentValidators';
|
||||
|
||||
@Service()
|
||||
export class GetPaymentBills {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private validators: BillPaymentValidators;
|
||||
|
||||
/**
|
||||
* Retrieve payment made associated bills.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} billPaymentId -
|
||||
*/
|
||||
public async getPaymentBills(tenantId: number, billPaymentId: number) {
|
||||
const { Bill, BillPayment } = this.tenancy.models(tenantId);
|
||||
|
||||
const billPayment = await BillPayment.query().findById(billPaymentId);
|
||||
|
||||
// Validates the bill payment existance.
|
||||
this.validators.validateBillPaymentExistance(billPayment);
|
||||
|
||||
const paymentBillsIds = billPayment.entries.map((entry) => entry.id);
|
||||
|
||||
const bills = await Bill.query().whereIn('id', paymentBillsIds);
|
||||
|
||||
return bills;
|
||||
}
|
||||
}
|
||||
@@ -1,751 +0,0 @@
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import { Knex } from 'knex';
|
||||
import composeAsync from 'async/compose';
|
||||
import events from '@/subscribers/events';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { formatDateFields, transformToMap } from 'utils';
|
||||
import {
|
||||
IBillDTO,
|
||||
IBill,
|
||||
ISystemUser,
|
||||
IBillEditDTO,
|
||||
IPaginationMeta,
|
||||
IFilterMeta,
|
||||
IBillsFilter,
|
||||
IBillsService,
|
||||
IItemEntry,
|
||||
IItemEntryDTO,
|
||||
IBillCreatedPayload,
|
||||
IBillEditedPayload,
|
||||
IBIllEventDeletedPayload,
|
||||
IBillEventDeletingPayload,
|
||||
IBillEditingPayload,
|
||||
IBillCreatingPayload,
|
||||
IVendor,
|
||||
} from '@/interfaces';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import JournalPosterService from '@/services/Sales/JournalPosterService';
|
||||
import { ERRORS } from './constants';
|
||||
import EntriesService from '@/services/Entries';
|
||||
import { PurchaseInvoiceTransformer } from './PurchaseInvoices/PurchaseInvoiceTransformer';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
/**
|
||||
* Vendor bills services.
|
||||
* @service
|
||||
*/
|
||||
@Service('Bills')
|
||||
export default class BillsService
|
||||
extends SalesInvoicesCost
|
||||
implements IBillsService
|
||||
{
|
||||
@Inject()
|
||||
inventoryService: InventoryService;
|
||||
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
journalPosterService: JournalPosterService;
|
||||
|
||||
@Inject()
|
||||
entriesService: EntriesService;
|
||||
|
||||
@Inject()
|
||||
transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||
|
||||
/**
|
||||
* Validates the given bill existance.
|
||||
* @async
|
||||
* @param {number} tenantId -
|
||||
* @param {number} billId -
|
||||
*/
|
||||
public async getBillOrThrowError(tenantId: number, billId: number) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const foundBill = await Bill.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
if (!foundBill) {
|
||||
throw new ServiceError(ERRORS.BILL_NOT_FOUND);
|
||||
}
|
||||
return foundBill;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the bill number existance.
|
||||
* @async
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
private async validateBillNumberExists(
|
||||
tenantId: number,
|
||||
billNumber: string,
|
||||
notBillId?: number
|
||||
) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const foundBills = await Bill.query()
|
||||
.where('bill_number', billNumber)
|
||||
.onBuild((builder) => {
|
||||
if (notBillId) {
|
||||
builder.whereNot('id', notBillId);
|
||||
}
|
||||
});
|
||||
|
||||
if (foundBills.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the bill has no payment entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId - Bill id.
|
||||
*/
|
||||
private async validateBillHasNoEntries(tenantId, billId: number) {
|
||||
const { BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retireve the bill associate payment made entries.
|
||||
const entries = await BillPaymentEntry.query().where('bill_id', billId);
|
||||
|
||||
if (entries.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the bill number require.
|
||||
* @param {string} billNo -
|
||||
*/
|
||||
private validateBillNoRequire(billNo: string) {
|
||||
if (!billNo) {
|
||||
throw new ServiceError(ERRORS.BILL_NO_IS_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate bill transaction has no associated allocated landed cost transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
*/
|
||||
private async validateBillHasNoLandedCost(tenantId: number, billId: number) {
|
||||
const { BillLandedCost } = this.tenancy.models(tenantId);
|
||||
|
||||
const billLandedCosts = await BillLandedCost.query().where(
|
||||
'billId',
|
||||
billId
|
||||
);
|
||||
if (billLandedCosts.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_HAS_ASSOCIATED_LANDED_COSTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate transaction entries that have landed cost type should not be
|
||||
* inventory items.
|
||||
* @param {number} tenantId -
|
||||
* @param {IItemEntryDTO[]} newEntriesDTO -
|
||||
*/
|
||||
public async validateCostEntriesShouldBeInventoryItems(
|
||||
tenantId: number,
|
||||
newEntriesDTO: IItemEntryDTO[]
|
||||
) {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
const entriesItemsIds = newEntriesDTO.map((e) => e.itemId);
|
||||
const entriesItems = await Item.query().whereIn('id', entriesItemsIds);
|
||||
|
||||
const entriesItemsById = transformToMap(entriesItems, 'id');
|
||||
|
||||
// Filter the landed cost entries that not associated with inventory item.
|
||||
const nonInventoryHasCost = newEntriesDTO.filter((entry) => {
|
||||
const item = entriesItemsById.get(entry.itemId);
|
||||
|
||||
return entry.landedCost && item.type !== 'inventory';
|
||||
});
|
||||
if (nonInventoryHasCost.length > 0) {
|
||||
throw new ServiceError(
|
||||
ERRORS.LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default cost account to the bill entries.
|
||||
*/
|
||||
private setBillEntriesDefaultAccounts(tenantId: number) {
|
||||
return async (entries: IItemEntry[]) => {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
const entriesItemsIds = entries.map((e) => e.itemId);
|
||||
const items = await Item.query().whereIn('id', entriesItemsIds);
|
||||
|
||||
return entries.map((entry) => {
|
||||
const item = items.find((i) => i.id === entry.itemId);
|
||||
|
||||
return {
|
||||
...entry,
|
||||
...(item.type !== 'inventory' && {
|
||||
costAccountId: entry.costAccountId || item.costAccountId,
|
||||
}),
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the bill entries total.
|
||||
* @param {IItemEntry[]} entries
|
||||
* @returns {number}
|
||||
*/
|
||||
private getBillEntriesTotal(tenantId: number, entries: IItemEntry[]): number {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
return sumBy(entries, (e) => ItemEntry.calcAmount(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the bill landed cost amount.
|
||||
* @param {IBillDTO} billDTO
|
||||
* @returns {number}
|
||||
*/
|
||||
private getBillLandedCostAmount(tenantId: number, billDTO: IBillDTO): number {
|
||||
const costEntries = billDTO.entries.filter((entry) => entry.landedCost);
|
||||
|
||||
return this.getBillEntriesTotal(tenantId, costEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts create bill DTO to model.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillDTO} billDTO
|
||||
* @param {IBill} oldBill
|
||||
* @returns {IBill}
|
||||
*/
|
||||
private async billDTOToModel(
|
||||
tenantId: number,
|
||||
billDTO: IBillDTO,
|
||||
vendor: IVendor,
|
||||
authorizedUser: ISystemUser,
|
||||
oldBill?: IBill
|
||||
) {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const amount = sumBy(billDTO.entries, (e) => ItemEntry.calcAmount(e));
|
||||
|
||||
// Retrieve the landed cost amount from landed cost entries.
|
||||
const landedCostAmount = this.getBillLandedCostAmount(tenantId, billDTO);
|
||||
|
||||
// Bill number from DTO or from auto-increment.
|
||||
const billNumber = billDTO.billNumber || oldBill?.billNumber;
|
||||
|
||||
const initialEntries = billDTO.entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
...omit(entry, ['amount']),
|
||||
}));
|
||||
const entries = await composeAsync(
|
||||
// Sets the default cost account to the bill entries.
|
||||
this.setBillEntriesDefaultAccounts(tenantId)
|
||||
)(initialEntries);
|
||||
|
||||
const initialDTO = {
|
||||
...formatDateFields(omit(billDTO, ['open', 'entries']), [
|
||||
'billDate',
|
||||
'dueDate',
|
||||
]),
|
||||
amount,
|
||||
landedCostAmount,
|
||||
currencyCode: vendor.currencyCode,
|
||||
exchangeRate: billDTO.exchangeRate || 1,
|
||||
billNumber,
|
||||
entries,
|
||||
// Avoid rewrite the open date in edit mode when already opened.
|
||||
...(billDTO.open &&
|
||||
!oldBill?.openedAt && {
|
||||
openedAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
userId: authorizedUser.id,
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO(tenantId),
|
||||
this.warehouseDTOTransform.transformDTO(tenantId)
|
||||
)(initialDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new bill and stored it to the storage.
|
||||
* ----
|
||||
* Precedures.
|
||||
* ----
|
||||
* - Insert bill transactions to the storage.
|
||||
* - Insert bill entries to the storage.
|
||||
* - Increment the given vendor id.
|
||||
* - Record bill journal transactions on the given accounts.
|
||||
* - Record bill items inventory transactions.
|
||||
* ----
|
||||
* @param {number} tenantId - The given tenant id.
|
||||
* @param {IBillDTO} billDTO -
|
||||
* @return {Promise<IBill>}
|
||||
*/
|
||||
public async createBill(
|
||||
tenantId: number,
|
||||
billDTO: IBillDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
const { Bill, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieves the given bill vendor or throw not found error.
|
||||
const vendor = await Contact.query()
|
||||
.modify('vendor')
|
||||
.findById(billDTO.vendorId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the bill number uniqiness on the storage.
|
||||
await this.validateBillNumberExists(tenantId, billDTO.billNumber);
|
||||
|
||||
// Validate items IDs existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Validate non-purchasable items.
|
||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Validates the cost entries should be with inventory items.
|
||||
await this.validateCostEntriesShouldBeInventoryItems(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Transform the bill DTO to model object.
|
||||
const billObj = await this.billDTOToModel(
|
||||
tenantId,
|
||||
billDTO,
|
||||
vendor,
|
||||
authorizedUser
|
||||
);
|
||||
// Write new bill transaction with associated transactions under UOW env.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onCreating, {
|
||||
trx,
|
||||
billDTO,
|
||||
tenantId,
|
||||
} as IBillCreatingPayload);
|
||||
|
||||
// Inserts the bill graph object to the storage.
|
||||
const bill = await Bill.query(trx).upsertGraph(billObj);
|
||||
|
||||
// Triggers `onBillCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onCreated, {
|
||||
tenantId,
|
||||
bill,
|
||||
billId: bill.id,
|
||||
trx,
|
||||
} as IBillCreatedPayload);
|
||||
|
||||
return bill;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits details of the given bill id with associated entries.
|
||||
*
|
||||
* Precedures:
|
||||
* -------
|
||||
* - Update the bill transaction on the storage.
|
||||
* - Update the bill entries on the storage and insert the not have id and delete
|
||||
* once that not presented.
|
||||
* - Increment the diff amount on the given vendor id.
|
||||
* - Re-write the inventory transactions.
|
||||
* - Re-write the bill journal transactions.
|
||||
* ------
|
||||
* @param {number} tenantId - The given tenant id.
|
||||
* @param {Integer} billId - The given bill id.
|
||||
* @param {IBillEditDTO} billDTO - The given new bill details.
|
||||
* @return {Promise<IBill>}
|
||||
*/
|
||||
public async editBill(
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
billDTO: IBillEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
const { Bill, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
|
||||
// Retrieve vendor details or throw not found service error.
|
||||
const vendor = await Contact.query()
|
||||
.findById(billDTO.vendorId)
|
||||
.modify('vendor')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate bill number uniqiness on the storage.
|
||||
if (billDTO.billNumber) {
|
||||
await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId);
|
||||
}
|
||||
// Validate the entries ids existance.
|
||||
await this.itemsEntriesService.validateEntriesIdsExistance(
|
||||
tenantId,
|
||||
billId,
|
||||
'Bill',
|
||||
billDTO.entries
|
||||
);
|
||||
// Validate the items ids existance on the storage.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Accept the purchasable items only.
|
||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Transforms the bill DTO to model object.
|
||||
const billObj = await this.billDTOToModel(
|
||||
tenantId,
|
||||
billDTO,
|
||||
vendor,
|
||||
authorizedUser,
|
||||
oldBill
|
||||
);
|
||||
// Validate landed cost entries that have allocated cost could not be deleted.
|
||||
await this.entriesService.validateLandedCostEntriesNotDeleted(
|
||||
oldBill.entries,
|
||||
billObj.entries
|
||||
);
|
||||
// Validate new landed cost entries should be bigger than new entries.
|
||||
await this.entriesService.validateLocatedCostEntriesSmallerThanNewEntries(
|
||||
oldBill.entries,
|
||||
billObj.entries
|
||||
);
|
||||
// Edits bill transactions and associated transactions under UOW envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onEditing, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldBill,
|
||||
billDTO,
|
||||
} as IBillEditingPayload);
|
||||
|
||||
// Update the bill transaction.
|
||||
const bill = await Bill.query(trx).upsertGraph({
|
||||
id: billId,
|
||||
...billObj,
|
||||
});
|
||||
// Triggers event `onBillEdited`.
|
||||
await this.eventPublisher.emitAsync(events.bill.onEdited, {
|
||||
tenantId,
|
||||
billId,
|
||||
oldBill,
|
||||
bill,
|
||||
trx,
|
||||
} as IBillEditedPayload);
|
||||
|
||||
return bill;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the bill with associated entries.
|
||||
* @param {Integer} billId
|
||||
* @return {void}
|
||||
*/
|
||||
public async deleteBill(tenantId: number, billId: number) {
|
||||
const { ItemEntry, Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given bill or throw not found error.
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
|
||||
// Validate the givne bill has no associated landed cost transactions.
|
||||
await this.validateBillHasNoLandedCost(tenantId, billId);
|
||||
|
||||
// Validate the purchase bill has no assocaited payments transactions.
|
||||
await this.validateBillHasNoEntries(tenantId, billId);
|
||||
|
||||
// Validate the given bill has no associated reconciled with vendor credits.
|
||||
await this.validateBillHasNoAppliedToCredit(tenantId, billId);
|
||||
|
||||
// Deletes bill transaction with associated transactions under
|
||||
// unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onDeleting, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldBill,
|
||||
} as IBillEventDeletingPayload);
|
||||
|
||||
// Delete all associated bill entries.
|
||||
await ItemEntry.query(trx)
|
||||
.where('reference_type', 'Bill')
|
||||
.where('reference_id', billId)
|
||||
.delete();
|
||||
|
||||
// Delete the bill transaction.
|
||||
await Bill.query(trx).findById(billId).delete();
|
||||
|
||||
// Triggers `onBillDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onDeleted, {
|
||||
tenantId,
|
||||
billId,
|
||||
oldBill,
|
||||
trx,
|
||||
} as IBIllEventDeletedPayload);
|
||||
});
|
||||
}
|
||||
|
||||
validateBillHasNoAppliedToCredit = async (
|
||||
tenantId: number,
|
||||
billId: number
|
||||
) => {
|
||||
const { VendorCreditAppliedBill } = this.tenancy.models(tenantId);
|
||||
|
||||
const appliedTransactions = await VendorCreditAppliedBill.query().where(
|
||||
'billId',
|
||||
billId
|
||||
);
|
||||
if (appliedTransactions.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_HAS_APPLIED_TO_VENDOR_CREDIT);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses bills list filter DTO.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve bills data table list.
|
||||
* @param {number} tenantId -
|
||||
* @param {IBillsFilter} billsFilter -
|
||||
*/
|
||||
public async getBills(
|
||||
tenantId: number,
|
||||
filterDTO: IBillsFilter
|
||||
): Promise<{
|
||||
bills: IBill;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses bills list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Bill,
|
||||
filter
|
||||
);
|
||||
const { results, pagination } = await Bill.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('vendor');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Tranform the bills to POJO.
|
||||
const bills = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new PurchaseInvoiceTransformer()
|
||||
);
|
||||
return {
|
||||
bills,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all due bills or for specific given vendor id.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} vendorId -
|
||||
*/
|
||||
public async getDueBills(
|
||||
tenantId: number,
|
||||
vendorId?: number
|
||||
): Promise<IBill[]> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const dueBills = await Bill.query().onBuild((query) => {
|
||||
query.orderBy('bill_date', 'DESC');
|
||||
query.modify('dueBills');
|
||||
|
||||
if (vendorId) {
|
||||
query.where('vendor_id', vendorId);
|
||||
}
|
||||
});
|
||||
return dueBills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given bill details with associated items entries.
|
||||
* @param {Integer} billId - Specific bill.
|
||||
* @returns {Promise<IBill>}
|
||||
*/
|
||||
public async getBill(tenantId: number, billId: number): Promise<IBill> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const bill = await Bill.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('vendor')
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('branch');
|
||||
|
||||
if (!bill) {
|
||||
throw new ServiceError(ERRORS.BILL_NOT_FOUND);
|
||||
}
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
bill,
|
||||
new PurchaseInvoiceTransformer()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the bill as open.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
*/
|
||||
public async openBill(tenantId: number, billId: number): Promise<void> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given bill or throw not found error.
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
|
||||
if (oldBill.isOpen) {
|
||||
throw new ServiceError(ERRORS.BILL_ALREADY_OPEN);
|
||||
}
|
||||
//
|
||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||
// Record the bill opened at on the storage.
|
||||
await Bill.query(trx).findById(billId).patch({
|
||||
openedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the inventory transactions from the given bill input.
|
||||
* @param {Bill} bill - Bill model object.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async recordInventoryTransactions(
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
override?: boolean,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retireve bill with assocaited entries and allocated cost entries.
|
||||
const bill = await Bill.query(trx)
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries.allocatedCostEntries');
|
||||
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
bill.entries
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: bill.id,
|
||||
transactionType: 'Bill',
|
||||
exchangeRate: bill.exchangeRate,
|
||||
|
||||
date: bill.billDate,
|
||||
direction: 'IN',
|
||||
entries: inventoryEntries,
|
||||
createdAt: bill.createdAt,
|
||||
|
||||
warehouseId: bill.warehouseId,
|
||||
};
|
||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
tenantId,
|
||||
transaction,
|
||||
override,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the inventory transactions of the given bill id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async revertInventoryTransactions(
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
// Deletes the inventory transactions by the given reference id and type.
|
||||
await this.inventoryService.deleteInventoryTransactions(
|
||||
tenantId,
|
||||
billId,
|
||||
'Bill',
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given vendor has no associated bills transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId - Vendor id.
|
||||
*/
|
||||
public async validateVendorHasNoBills(tenantId: number, vendorId: number) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const bills = await Bill.query().where('vendor_id', vendorId);
|
||||
|
||||
if (bills.length > 0) {
|
||||
throw new ServiceError(ERRORS.VENDOR_HAS_BILLS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import composeAsync from 'async/compose';
|
||||
import { formatDateFields } from 'utils';
|
||||
import {
|
||||
IBillDTO,
|
||||
IBill,
|
||||
ISystemUser,
|
||||
IVendor,
|
||||
IItemEntry,
|
||||
} from '@/interfaces';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class BillDTOTransformer {
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the bill entries total.
|
||||
* @param {IItemEntry[]} entries
|
||||
* @returns {number}
|
||||
*/
|
||||
private getBillEntriesTotal(tenantId: number, entries: IItemEntry[]): number {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
return sumBy(entries, (e) => ItemEntry.calcAmount(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the bill landed cost amount.
|
||||
* @param {IBillDTO} billDTO
|
||||
* @returns {number}
|
||||
*/
|
||||
private getBillLandedCostAmount(tenantId: number, billDTO: IBillDTO): number {
|
||||
const costEntries = billDTO.entries.filter((entry) => entry.landedCost);
|
||||
|
||||
return this.getBillEntriesTotal(tenantId, costEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts create bill DTO to model.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillDTO} billDTO
|
||||
* @param {IBill} oldBill
|
||||
* @returns {IBill}
|
||||
*/
|
||||
public async billDTOToModel(
|
||||
tenantId: number,
|
||||
billDTO: IBillDTO,
|
||||
vendor: IVendor,
|
||||
authorizedUser: ISystemUser,
|
||||
oldBill?: IBill
|
||||
) {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const amount = sumBy(billDTO.entries, (e) => ItemEntry.calcAmount(e));
|
||||
|
||||
// Retrieve the landed cost amount from landed cost entries.
|
||||
const landedCostAmount = this.getBillLandedCostAmount(tenantId, billDTO);
|
||||
|
||||
// Bill number from DTO or from auto-increment.
|
||||
const billNumber = billDTO.billNumber || oldBill?.billNumber;
|
||||
|
||||
const initialEntries = billDTO.entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
...omit(entry, ['amount']),
|
||||
}));
|
||||
const entries = await composeAsync(
|
||||
// Sets the default cost account to the bill entries.
|
||||
this.setBillEntriesDefaultAccounts(tenantId)
|
||||
)(initialEntries);
|
||||
|
||||
const initialDTO = {
|
||||
...formatDateFields(omit(billDTO, ['open', 'entries']), [
|
||||
'billDate',
|
||||
'dueDate',
|
||||
]),
|
||||
amount,
|
||||
landedCostAmount,
|
||||
currencyCode: vendor.currencyCode,
|
||||
exchangeRate: billDTO.exchangeRate || 1,
|
||||
billNumber,
|
||||
entries,
|
||||
// Avoid rewrite the open date in edit mode when already opened.
|
||||
...(billDTO.open &&
|
||||
!oldBill?.openedAt && {
|
||||
openedAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
userId: authorizedUser.id,
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO(tenantId),
|
||||
this.warehouseDTOTransform.transformDTO(tenantId)
|
||||
)(initialDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default cost account to the bill entries.
|
||||
*/
|
||||
private setBillEntriesDefaultAccounts(tenantId: number) {
|
||||
return async (entries: IItemEntry[]) => {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
const entriesItemsIds = entries.map((e) => e.itemId);
|
||||
const items = await Item.query().whereIn('id', entriesItemsIds);
|
||||
|
||||
return entries.map((entry) => {
|
||||
const item = items.find((i) => i.id === entry.itemId);
|
||||
|
||||
return {
|
||||
...entry,
|
||||
...(item.type !== 'inventory' && {
|
||||
costAccountId: entry.costAccountId || item.costAccountId,
|
||||
}),
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import BillsService from '@/services/Purchases/Bills';
|
||||
import {
|
||||
IBillCreatedPayload,
|
||||
IBillEditedPayload,
|
||||
@@ -12,15 +10,12 @@ import { BillGLEntries } from './BillGLEntries';
|
||||
@Service()
|
||||
export class BillGLEntriesSubscriber {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
billGLEntries: BillGLEntries;
|
||||
private billGLEntries: BillGLEntries;
|
||||
|
||||
/**
|
||||
* Attachs events with handles.
|
||||
*/
|
||||
attach(bus) {
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.bill.onCreated,
|
||||
this.handlerWriteJournalEntriesOnCreate
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class BillInventoryTransactions {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private inventoryService: InventoryService;
|
||||
|
||||
/**
|
||||
* Records the inventory transactions from the given bill input.
|
||||
* @param {Bill} bill - Bill model object.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async recordInventoryTransactions(
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
override?: boolean,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retireve bill with assocaited entries and allocated cost entries.
|
||||
const bill = await Bill.query(trx)
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries.allocatedCostEntries');
|
||||
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
bill.entries
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: bill.id,
|
||||
transactionType: 'Bill',
|
||||
exchangeRate: bill.exchangeRate,
|
||||
|
||||
date: bill.billDate,
|
||||
direction: 'IN',
|
||||
entries: inventoryEntries,
|
||||
createdAt: bill.createdAt,
|
||||
|
||||
warehouseId: bill.warehouseId,
|
||||
};
|
||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
tenantId,
|
||||
transaction,
|
||||
override,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the inventory transactions of the given bill id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async revertInventoryTransactions(
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
// Deletes the inventory transactions by the given reference id and type.
|
||||
await this.inventoryService.deleteInventoryTransactions(
|
||||
tenantId,
|
||||
billId,
|
||||
'Bill',
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export class BillPaymentsGLEntriesRewriteSubscriber {
|
||||
/**
|
||||
* Attachs events with handles.
|
||||
*/
|
||||
attach(bus) {
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.bill.onEdited,
|
||||
this.handlerRewritePaymentsGLOnBillEdited
|
||||
|
||||
147
packages/server/src/services/Purchases/Bills/BillsApplication.ts
Normal file
147
packages/server/src/services/Purchases/Bills/BillsApplication.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { CreateBill } from './CreateBill';
|
||||
import { EditBill } from './EditBill';
|
||||
import { GetBill } from './GetBill';
|
||||
import { GetBills } from './GetBills';
|
||||
import { DeleteBill } from './DeleteBill';
|
||||
import {
|
||||
IBill,
|
||||
IBillDTO,
|
||||
IBillEditDTO,
|
||||
IBillsFilter,
|
||||
IFilterMeta,
|
||||
IPaginationMeta,
|
||||
ISystemUser,
|
||||
} from '@/interfaces';
|
||||
import { GetDueBills } from './GetDueBills';
|
||||
import { OpenBill } from './OpenBill';
|
||||
import { GetBillPayments } from './GetBillPayments';
|
||||
|
||||
@Service()
|
||||
export class BillsApplication {
|
||||
@Inject()
|
||||
private createBillService: CreateBill;
|
||||
|
||||
@Inject()
|
||||
private editBillService: EditBill;
|
||||
|
||||
@Inject()
|
||||
private getBillService: GetBill;
|
||||
|
||||
@Inject()
|
||||
private getBillsService: GetBills;
|
||||
|
||||
@Inject()
|
||||
private deleteBillService: DeleteBill;
|
||||
|
||||
@Inject()
|
||||
private getDueBillsService: GetDueBills;
|
||||
|
||||
@Inject()
|
||||
private openBillService: OpenBill;
|
||||
|
||||
@Inject()
|
||||
private getBillPaymentsService: GetBillPayments;
|
||||
|
||||
/**
|
||||
* Creates a new bill with associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {IBillDTO} billDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns
|
||||
*/
|
||||
public createBill(
|
||||
tenantId: number,
|
||||
billDTO: IBillDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
return this.createBillService.createBill(tenantId, billDTO, authorizedUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the given bill with associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
* @param {IBillEditDTO} billDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns
|
||||
*/
|
||||
public editBill(
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
billDTO: IBillEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
return this.editBillService.editBill(
|
||||
tenantId,
|
||||
billId,
|
||||
billDTO,
|
||||
authorizedUser
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given bill with associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteBill(tenantId: number, billId: number) {
|
||||
return this.deleteBillService.deleteBill(tenantId, billId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve bills data table list.
|
||||
* @param {number} tenantId -
|
||||
* @param {IBillsFilter} billsFilter -
|
||||
*/
|
||||
public getBills(
|
||||
tenantId: number,
|
||||
filterDTO: IBillsFilter
|
||||
): Promise<{
|
||||
bills: IBill;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
return this.getBillsService.getBills(tenantId, filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given bill details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
* @returns
|
||||
*/
|
||||
public getBill(tenantId: number, billId: number): Promise<IBill> {
|
||||
return this.getBillService.getBill(tenantId, billId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the given bill.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public openBill(tenantId: number, billId: number): Promise<void> {
|
||||
return this.openBillService.openBill(tenantId, billId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves due bills of the given vendor.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @returns
|
||||
*/
|
||||
public getDueBills(tenantId: number, vendorId?: number) {
|
||||
return this.getDueBillsService.getDueBills(tenantId, vendorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the specific bill associated payment transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
*/
|
||||
public getBillPayments = async (tenantId: number, billId: number) => {
|
||||
return this.getBillPaymentsService.getBillPayments(tenantId, billId);
|
||||
};
|
||||
}
|
||||
154
packages/server/src/services/Purchases/Bills/BillsValidators.ts
Normal file
154
packages/server/src/services/Purchases/Bills/BillsValidators.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ERRORS } from './constants';
|
||||
import { IItemEntryDTO } from '@/interfaces';
|
||||
import { transformToMap } from '@/utils';
|
||||
import { Bill } from '@/models';
|
||||
|
||||
@Service()
|
||||
export class BillsValidators {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Validates the bill existance.
|
||||
* @param {Bill | undefined | null} bill
|
||||
*/
|
||||
public validateBillExistance(bill: Bill | undefined | null) {
|
||||
if (!bill) {
|
||||
throw new ServiceError(ERRORS.BILL_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the bill number existance.
|
||||
*/
|
||||
public async validateBillNumberExists(
|
||||
tenantId: number,
|
||||
billNumber: string,
|
||||
notBillId?: number
|
||||
) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const foundBills = await Bill.query()
|
||||
.where('bill_number', billNumber)
|
||||
.onBuild((builder) => {
|
||||
if (notBillId) {
|
||||
builder.whereNot('id', notBillId);
|
||||
}
|
||||
});
|
||||
|
||||
if (foundBills.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the bill has no payment entries.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
*/
|
||||
public async validateBillHasNoEntries(tenantId, billId: number) {
|
||||
const { BillPaymentEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retireve the bill associate payment made entries.
|
||||
const entries = await BillPaymentEntry.query().where('bill_id', billId);
|
||||
|
||||
if (entries.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the bill number require.
|
||||
* @param {string} billNo -
|
||||
*/
|
||||
public validateBillNoRequire(billNo: string) {
|
||||
if (!billNo) {
|
||||
throw new ServiceError(ERRORS.BILL_NO_IS_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate bill transaction has no associated allocated landed cost transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
*/
|
||||
public async validateBillHasNoLandedCost(tenantId: number, billId: number) {
|
||||
const { BillLandedCost } = this.tenancy.models(tenantId);
|
||||
|
||||
const billLandedCosts = await BillLandedCost.query().where(
|
||||
'billId',
|
||||
billId
|
||||
);
|
||||
if (billLandedCosts.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_HAS_ASSOCIATED_LANDED_COSTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate transaction entries that have landed cost type should not be
|
||||
* inventory items.
|
||||
* @param {number} tenantId -
|
||||
* @param {IItemEntryDTO[]} newEntriesDTO -
|
||||
*/
|
||||
public async validateCostEntriesShouldBeInventoryItems(
|
||||
tenantId: number,
|
||||
newEntriesDTO: IItemEntryDTO[]
|
||||
) {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
const entriesItemsIds = newEntriesDTO.map((e) => e.itemId);
|
||||
const entriesItems = await Item.query().whereIn('id', entriesItemsIds);
|
||||
|
||||
const entriesItemsById = transformToMap(entriesItems, 'id');
|
||||
|
||||
// Filter the landed cost entries that not associated with inventory item.
|
||||
const nonInventoryHasCost = newEntriesDTO.filter((entry) => {
|
||||
const item = entriesItemsById.get(entry.itemId);
|
||||
|
||||
return entry.landedCost && item.type !== 'inventory';
|
||||
});
|
||||
if (nonInventoryHasCost.length > 0) {
|
||||
throw new ServiceError(
|
||||
ERRORS.LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
*/
|
||||
public validateBillHasNoAppliedToCredit = async (
|
||||
tenantId: number,
|
||||
billId: number
|
||||
) => {
|
||||
const { VendorCreditAppliedBill } = this.tenancy.models(tenantId);
|
||||
|
||||
const appliedTransactions = await VendorCreditAppliedBill.query().where(
|
||||
'billId',
|
||||
billId
|
||||
);
|
||||
if (appliedTransactions.length > 0) {
|
||||
throw new ServiceError(ERRORS.BILL_HAS_APPLIED_TO_VENDOR_CREDIT);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the given vendor has no associated bills transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId - Vendor id.
|
||||
*/
|
||||
public async validateVendorHasNoBills(tenantId: number, vendorId: number) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const bills = await Bill.query().where('vendor_id', vendorId);
|
||||
|
||||
if (bills.length > 0) {
|
||||
throw new ServiceError(ERRORS.VENDOR_HAS_BILLS);
|
||||
}
|
||||
}
|
||||
}
|
||||
116
packages/server/src/services/Purchases/Bills/CreateBill.ts
Normal file
116
packages/server/src/services/Purchases/Bills/CreateBill.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
IBillDTO,
|
||||
IBill,
|
||||
ISystemUser,
|
||||
IBillCreatedPayload,
|
||||
IBillCreatingPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { BillsValidators } from './BillsValidators';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import { BillDTOTransformer } from './BillDTOTransformer';
|
||||
|
||||
@Service()
|
||||
export class CreateBill {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private validators: BillsValidators;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private transformerDTO: BillDTOTransformer;
|
||||
|
||||
/**
|
||||
* Creates a new bill and stored it to the storage.
|
||||
* ----
|
||||
* Precedures.
|
||||
* ----
|
||||
* - Insert bill transactions to the storage.
|
||||
* - Insert bill entries to the storage.
|
||||
* - Increment the given vendor id.
|
||||
* - Record bill journal transactions on the given accounts.
|
||||
* - Record bill items inventory transactions.
|
||||
* ----
|
||||
* @param {number} tenantId - The given tenant id.
|
||||
* @param {IBillDTO} billDTO -
|
||||
* @return {Promise<IBill>}
|
||||
*/
|
||||
public async createBill(
|
||||
tenantId: number,
|
||||
billDTO: IBillDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
const { Bill, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieves the given bill vendor or throw not found error.
|
||||
const vendor = await Contact.query()
|
||||
.modify('vendor')
|
||||
.findById(billDTO.vendorId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the bill number uniqiness on the storage.
|
||||
await this.validators.validateBillNumberExists(
|
||||
tenantId,
|
||||
billDTO.billNumber
|
||||
);
|
||||
// Validate items IDs existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Validate non-purchasable items.
|
||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Validates the cost entries should be with inventory items.
|
||||
await this.validators.validateCostEntriesShouldBeInventoryItems(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Transform the bill DTO to model object.
|
||||
const billObj = await this.transformerDTO.billDTOToModel(
|
||||
tenantId,
|
||||
billDTO,
|
||||
vendor,
|
||||
authorizedUser
|
||||
);
|
||||
// Write new bill transaction with associated transactions under UOW env.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onCreating, {
|
||||
trx,
|
||||
billDTO,
|
||||
tenantId,
|
||||
} as IBillCreatingPayload);
|
||||
|
||||
// Inserts the bill graph object to the storage.
|
||||
const bill = await Bill.query(trx).upsertGraph(billObj);
|
||||
|
||||
// Triggers `onBillCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onCreated, {
|
||||
tenantId,
|
||||
bill,
|
||||
billId: bill.id,
|
||||
trx,
|
||||
} as IBillCreatedPayload);
|
||||
|
||||
return bill;
|
||||
});
|
||||
}
|
||||
}
|
||||
80
packages/server/src/services/Purchases/Bills/DeleteBill.ts
Normal file
80
packages/server/src/services/Purchases/Bills/DeleteBill.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
IBIllEventDeletedPayload,
|
||||
IBillEventDeletingPayload,
|
||||
} from '@/interfaces';
|
||||
import { BillsValidators } from './BillsValidators';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class DeleteBill {
|
||||
@Inject()
|
||||
private validators: BillsValidators;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Deletes the bill with associated entries.
|
||||
* @param {number} billId
|
||||
* @return {void}
|
||||
*/
|
||||
public async deleteBill(tenantId: number, billId: number) {
|
||||
const { ItemEntry, Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given bill or throw not found error.
|
||||
const oldBill = await Bill.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
// Validates the bill existance.
|
||||
this.validators.validateBillExistance(oldBill);
|
||||
|
||||
// Validate the givne bill has no associated landed cost transactions.
|
||||
await this.validators.validateBillHasNoLandedCost(tenantId, billId);
|
||||
|
||||
// Validate the purchase bill has no assocaited payments transactions.
|
||||
await this.validators.validateBillHasNoEntries(tenantId, billId);
|
||||
|
||||
// Validate the given bill has no associated reconciled with vendor credits.
|
||||
await this.validators.validateBillHasNoAppliedToCredit(tenantId, billId);
|
||||
|
||||
// Deletes bill transaction with associated transactions under
|
||||
// unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onDeleting, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldBill,
|
||||
} as IBillEventDeletingPayload);
|
||||
|
||||
// Delete all associated bill entries.
|
||||
await ItemEntry.query(trx)
|
||||
.where('reference_type', 'Bill')
|
||||
.where('reference_id', billId)
|
||||
.delete();
|
||||
|
||||
// Delete the bill transaction.
|
||||
await Bill.query(trx).findById(billId).delete();
|
||||
|
||||
// Triggers `onBillDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onDeleted, {
|
||||
tenantId,
|
||||
billId,
|
||||
oldBill,
|
||||
trx,
|
||||
} as IBIllEventDeletedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
151
packages/server/src/services/Purchases/Bills/EditBill.ts
Normal file
151
packages/server/src/services/Purchases/Bills/EditBill.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
IBill,
|
||||
IBillEditDTO,
|
||||
IBillEditedPayload,
|
||||
IBillEditingPayload,
|
||||
ISystemUser,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { BillsValidators } from './BillsValidators';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { Knex } from 'knex';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import EntriesService from '@/services/Entries';
|
||||
import { BillDTOTransformer } from './BillDTOTransformer';
|
||||
|
||||
@Service()
|
||||
export class EditBill {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private validators: BillsValidators;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private entriesService: EntriesService;
|
||||
|
||||
@Inject()
|
||||
private transformerDTO: BillDTOTransformer;
|
||||
|
||||
/**
|
||||
* Edits details of the given bill id with associated entries.
|
||||
*
|
||||
* Precedures:
|
||||
* -------
|
||||
* - Update the bill transaction on the storage.
|
||||
* - Update the bill entries on the storage and insert the not have id and delete
|
||||
* once that not presented.
|
||||
* - Increment the diff amount on the given vendor id.
|
||||
* - Re-write the inventory transactions.
|
||||
* - Re-write the bill journal transactions.
|
||||
* ------
|
||||
* @param {number} tenantId - The given tenant id.
|
||||
* @param {Integer} billId - The given bill id.
|
||||
* @param {IBillEditDTO} billDTO - The given new bill details.
|
||||
* @return {Promise<IBill>}
|
||||
*/
|
||||
public async editBill(
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
billDTO: IBillEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
const { Bill, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given bill or throw not found error.
|
||||
const oldBill = await Bill.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
// Validate bill existance.
|
||||
this.validators.validateBillExistance(oldBill);
|
||||
|
||||
// Retrieve vendor details or throw not found service error.
|
||||
const vendor = await Contact.query()
|
||||
.findById(billDTO.vendorId)
|
||||
.modify('vendor')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate bill number uniqiness on the storage.
|
||||
if (billDTO.billNumber) {
|
||||
await this.validators.validateBillNumberExists(
|
||||
tenantId,
|
||||
billDTO.billNumber,
|
||||
billId
|
||||
);
|
||||
}
|
||||
// Validate the entries ids existance.
|
||||
await this.itemsEntriesService.validateEntriesIdsExistance(
|
||||
tenantId,
|
||||
billId,
|
||||
'Bill',
|
||||
billDTO.entries
|
||||
);
|
||||
// Validate the items ids existance on the storage.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Accept the purchasable items only.
|
||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Transforms the bill DTO to model object.
|
||||
const billObj = await this.transformerDTO.billDTOToModel(
|
||||
tenantId,
|
||||
billDTO,
|
||||
vendor,
|
||||
authorizedUser,
|
||||
oldBill
|
||||
);
|
||||
// Validate landed cost entries that have allocated cost could not be deleted.
|
||||
await this.entriesService.validateLandedCostEntriesNotDeleted(
|
||||
oldBill.entries,
|
||||
billObj.entries
|
||||
);
|
||||
// Validate new landed cost entries should be bigger than new entries.
|
||||
await this.entriesService.validateLocatedCostEntriesSmallerThanNewEntries(
|
||||
oldBill.entries,
|
||||
billObj.entries
|
||||
);
|
||||
// Edits bill transactions and associated transactions under UOW envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBillEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.bill.onEditing, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldBill,
|
||||
billDTO,
|
||||
} as IBillEditingPayload);
|
||||
|
||||
// Update the bill transaction.
|
||||
const bill = await Bill.query(trx).upsertGraph({
|
||||
id: billId,
|
||||
...billObj,
|
||||
});
|
||||
// Triggers event `onBillEdited`.
|
||||
await this.eventPublisher.emitAsync(events.bill.onEdited, {
|
||||
tenantId,
|
||||
billId,
|
||||
oldBill,
|
||||
bill,
|
||||
trx,
|
||||
} as IBillEditedPayload);
|
||||
|
||||
return bill;
|
||||
});
|
||||
}
|
||||
}
|
||||
42
packages/server/src/services/Purchases/Bills/GetBill.ts
Normal file
42
packages/server/src/services/Purchases/Bills/GetBill.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { IBill } from '@/interfaces';
|
||||
import { BillsValidators } from './BillsValidators';
|
||||
import { PurchaseInvoiceTransformer } from './PurchaseInvoiceTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetBill {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private validators: BillsValidators;
|
||||
|
||||
/**
|
||||
* Retrieve the given bill details with associated items entries.
|
||||
* @param {Integer} billId - Specific bill.
|
||||
* @returns {Promise<IBill>}
|
||||
*/
|
||||
public async getBill(tenantId: number, billId: number): Promise<IBill> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const bill = await Bill.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('vendor')
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('branch');
|
||||
|
||||
// Validates the bill existance.
|
||||
this.validators.validateBillExistance(bill);
|
||||
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
bill,
|
||||
new PurchaseInvoiceTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { BillPaymentTransactionTransformer } from './BillPayments/BillPaymentTransactionTransformer';
|
||||
import { BillPaymentTransactionTransformer } from '../BillPayments/BillPaymentTransactionTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export default class BillPaymentsService {
|
||||
export class GetBillPayments {
|
||||
@Inject()
|
||||
private tenancy: TenancyService;
|
||||
|
||||
76
packages/server/src/services/Purchases/Bills/GetBills.ts
Normal file
76
packages/server/src/services/Purchases/Bills/GetBills.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IBill,
|
||||
IBillsFilter,
|
||||
IFilterMeta,
|
||||
IPaginationMeta,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { PurchaseInvoiceTransformer } from './PurchaseInvoiceTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
|
||||
@Service()
|
||||
export class GetBills {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
/**
|
||||
* Retrieve bills data table list.
|
||||
* @param {number} tenantId -
|
||||
* @param {IBillsFilter} billsFilter -
|
||||
*/
|
||||
public async getBills(
|
||||
tenantId: number,
|
||||
filterDTO: IBillsFilter
|
||||
): Promise<{
|
||||
bills: IBill;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses bills list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Bill,
|
||||
filter
|
||||
);
|
||||
const { results, pagination } = await Bill.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('vendor');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Tranform the bills to POJO.
|
||||
const bills = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new PurchaseInvoiceTransformer()
|
||||
);
|
||||
return {
|
||||
bills,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses bills list filter DTO.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
32
packages/server/src/services/Purchases/Bills/GetDueBills.ts
Normal file
32
packages/server/src/services/Purchases/Bills/GetDueBills.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { IBill } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export class GetDueBills {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve all due bills or for specific given vendor id.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} vendorId -
|
||||
*/
|
||||
public async getDueBills(
|
||||
tenantId: number,
|
||||
vendorId?: number
|
||||
): Promise<IBill[]> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const dueBills = await Bill.query().onBuild((query) => {
|
||||
query.orderBy('bill_date', 'DESC');
|
||||
query.modify('dueBills');
|
||||
|
||||
if (vendorId) {
|
||||
query.where('vendor_id', vendorId);
|
||||
}
|
||||
});
|
||||
return dueBills;
|
||||
}
|
||||
}
|
||||
46
packages/server/src/services/Purchases/Bills/OpenBill.ts
Normal file
46
packages/server/src/services/Purchases/Bills/OpenBill.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import moment from 'moment';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { BillsValidators } from './BillsValidators';
|
||||
|
||||
@Service()
|
||||
export class OpenBill {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validators: BillsValidators;
|
||||
|
||||
/**
|
||||
* Mark the bill as open.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
*/
|
||||
public async openBill(tenantId: number, billId: number): Promise<void> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given bill or throw not found error.
|
||||
const oldBill = await Bill.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
// Validates the bill existance.
|
||||
this.validators.validateBillExistance(oldBill);
|
||||
|
||||
if (oldBill.isOpen) {
|
||||
throw new ServiceError(ERRORS.BILL_ALREADY_OPEN);
|
||||
}
|
||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||
// Record the bill opened at on the storage.
|
||||
await Bill.query(trx).findById(billId).patch({
|
||||
openedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -41,13 +41,16 @@ export default class AllocateLandedCost extends BaseLandedCostService {
|
||||
allocateCostDTO: ILandedCostDTO,
|
||||
billId: number
|
||||
): Promise<IBillLandedCost> => {
|
||||
const { BillLandedCost } = this.tenancy.models(tenantId);
|
||||
const { BillLandedCost, Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve total cost of allocated items.
|
||||
const amount = this.getAllocateItemsCostTotal(allocateCostDTO);
|
||||
|
||||
// Retrieve the purchase invoice or throw not found error.
|
||||
const bill = await this.billsService.getBillOrThrowError(tenantId, billId);
|
||||
const bill = await Bill.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Retrieve landed cost transaction or throw not found service error.
|
||||
const costTransaction = await this.getLandedCostOrThrowError(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { difference, sumBy } from 'lodash';
|
||||
import BillsService from '../Bills';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
IItemEntry,
|
||||
@@ -13,14 +12,10 @@ import {
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import TransactionLandedCost from './TransctionLandedCost';
|
||||
import { ERRORS } from './utils';
|
||||
import { CONFIG } from './utils';
|
||||
import { ERRORS, CONFIG } from './utils';
|
||||
|
||||
@Service()
|
||||
export default class BaseLandedCostService {
|
||||
@Inject()
|
||||
public billsService: BillsService;
|
||||
|
||||
@Inject()
|
||||
public tenancy: HasTenancyService;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import LandedCostSyncCostTransactions from './LandedCostSyncCostTransactions';
|
||||
@Service()
|
||||
export default class LandedCostSyncCostTransactionsSubscriber {
|
||||
@Inject()
|
||||
landedCostSyncCostTransaction: LandedCostSyncCostTransactions;
|
||||
private landedCostSyncCostTransaction: LandedCostSyncCostTransactions;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
|
||||
@@ -9,20 +9,12 @@ import {
|
||||
ILandedCostTransactionEntryDOJO,
|
||||
} from '@/interfaces';
|
||||
import TransactionLandedCost from './TransctionLandedCost';
|
||||
import BillsService from '../Bills';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
@Service()
|
||||
export default class LandedCostTranasctions {
|
||||
@Inject()
|
||||
transactionLandedCost: TransactionLandedCost;
|
||||
|
||||
@Inject()
|
||||
billsService: BillsService;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
private transactionLandedCost: TransactionLandedCost;
|
||||
|
||||
/**
|
||||
* Retrieve the landed costs based on the given query.
|
||||
|
||||
@@ -16,13 +16,13 @@ import { ERRORS } from './utils';
|
||||
@Service()
|
||||
export default class TransactionLandedCost {
|
||||
@Inject()
|
||||
billLandedCost: BillLandedCost;
|
||||
private billLandedCost: BillLandedCost;
|
||||
|
||||
@Inject()
|
||||
expenseLandedCost: ExpenseLandedCost;
|
||||
private expenseLandedCost: ExpenseLandedCost;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the cost transaction code model.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import Knex from 'knex';
|
||||
import { Knex } from 'knex';
|
||||
import { sumBy } from 'lodash';
|
||||
import {
|
||||
IVendorCredit,
|
||||
@@ -9,27 +9,27 @@ import {
|
||||
IBill,
|
||||
} from '@/interfaces';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import events from '@/subscribers/events';
|
||||
import VendorCredit from '../BaseVendorCredit';
|
||||
import BillPaymentsService from '@/services/Purchases/BillPayments/BillPayments';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { BillPaymentValidators } from '../../BillPayments/BillPaymentValidators';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ERRORS } from '../constants';
|
||||
|
||||
@Service()
|
||||
export default class ApplyVendorCreditToBills extends VendorCredit {
|
||||
@Inject('PaymentReceives')
|
||||
paymentReceive: PaymentReceiveService;
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
billPayment: BillPaymentsService;
|
||||
private billPaymentValidators: BillPaymentValidators;
|
||||
|
||||
/**
|
||||
* Apply credit note to the given invoices.
|
||||
@@ -55,11 +55,12 @@ export default class ApplyVendorCreditToBills extends VendorCredit {
|
||||
vendorCredit
|
||||
);
|
||||
// Validate bills entries existance.
|
||||
const appliedBills = await this.billPayment.validateBillsExistance(
|
||||
tenantId,
|
||||
vendorCreditAppliedModel.entries,
|
||||
vendorCredit.vendorId
|
||||
);
|
||||
const appliedBills =
|
||||
await this.billPaymentValidators.validateBillsExistance(
|
||||
tenantId,
|
||||
vendorCreditAppliedModel.entries,
|
||||
vendorCredit.vendorId
|
||||
);
|
||||
// Validate bills has remaining amount to apply.
|
||||
this.validateBillsRemainingAmount(
|
||||
appliedBills,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
ISaleEstimateApprovedEvent,
|
||||
ISaleEstimateApprovingEvent,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { ERRORS } from './constants';
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import moment from 'moment';
|
||||
|
||||
@Service()
|
||||
export class ApproveSaleEstimate {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Mark the sale estimate as approved from the customer.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
*/
|
||||
public async approveSaleEstimate(
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate id.
|
||||
const oldSaleEstimate = await SaleEstimate.query()
|
||||
.findById(saleEstimateId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Throws error in case the sale estimate still not delivered to customer.
|
||||
if (!oldSaleEstimate.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_DELIVERED);
|
||||
}
|
||||
// Throws error in case the sale estimate already approved.
|
||||
if (oldSaleEstimate.isApproved) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_APPROVED);
|
||||
}
|
||||
// Triggers `onSaleEstimateApproving` event.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleEstimateApproving` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onApproving, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldSaleEstimate,
|
||||
} as ISaleEstimateApprovingEvent);
|
||||
|
||||
// Update estimate as approved.
|
||||
const saleEstimate = await SaleEstimate.query(trx)
|
||||
.where('id', saleEstimateId)
|
||||
.patch({
|
||||
approvedAt: moment().toMySqlDateTime(),
|
||||
rejectedAt: null,
|
||||
});
|
||||
// Triggers `onSaleEstimateApproved` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onApproved, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldSaleEstimate,
|
||||
saleEstimate,
|
||||
} as ISaleEstimateApprovedEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import events from '@/subscribers/events';
|
||||
import { Knex } from 'knex';
|
||||
import moment from 'moment';
|
||||
import { Inject, Service } from 'typedi';
|
||||
|
||||
@Service()
|
||||
export class ConvertSaleEstimate {
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Converts estimate to invoice.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} estimateId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async convertEstimateToInvoice(
|
||||
tenantId: number,
|
||||
estimateId: number,
|
||||
invoiceId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate.
|
||||
const saleEstimate = await SaleEstimate.query()
|
||||
.findById(estimateId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Marks the estimate as converted from the givne invoice.
|
||||
await SaleEstimate.query(trx).where('id', estimateId).patch({
|
||||
convertedToInvoiceId: invoiceId,
|
||||
convertedToInvoiceAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
// Triggers `onSaleEstimateConvertedToInvoice` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleEstimate.onConvertedToInvoice,
|
||||
{}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
ISaleEstimate,
|
||||
ISaleEstimateCreatedPayload,
|
||||
ISaleEstimateCreatingPayload,
|
||||
ISaleEstimateDTO,
|
||||
} from '@/interfaces';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { SaleEstimateDTOTransformer } from './SaleEstimateDTOTransformer';
|
||||
import events from '@/subscribers/events';
|
||||
import { SaleEstimateValidators } from './SaleEstimateValidators';
|
||||
|
||||
@Service()
|
||||
export class CreateSaleEstimate {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private transformerDTO: SaleEstimateDTOTransformer;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleEstimateValidators;
|
||||
|
||||
/**
|
||||
* Creates a new estimate with associated entries.
|
||||
* @async
|
||||
* @param {number} tenantId - The tenant id.
|
||||
* @param {EstimateDTO} estimate
|
||||
* @return {Promise<ISaleEstimate>}
|
||||
*/
|
||||
public async createEstimate(
|
||||
tenantId: number,
|
||||
estimateDTO: ISaleEstimateDTO
|
||||
): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given customer or throw not found service error.
|
||||
const customer = await Contact.query()
|
||||
.modify('customer')
|
||||
.findById(estimateDTO.customerId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transform DTO object ot model object.
|
||||
const estimateObj = await this.transformerDTO.transformDTOToModel(
|
||||
tenantId,
|
||||
estimateDTO,
|
||||
customer
|
||||
);
|
||||
// Validate estimate number uniquiness on the storage.
|
||||
await this.validators.validateEstimateNumberExistance(
|
||||
tenantId,
|
||||
estimateObj.estimateNumber
|
||||
);
|
||||
// Validate items IDs existance on the storage.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
estimateDTO.entries
|
||||
);
|
||||
// Validate non-sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
estimateDTO.entries
|
||||
);
|
||||
// Creates a sale estimate transaction with associated transactions as UOW.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleEstimateCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onCreating, {
|
||||
estimateDTO,
|
||||
tenantId,
|
||||
trx,
|
||||
} as ISaleEstimateCreatingPayload);
|
||||
|
||||
// Upsert the sale estimate graph to the storage.
|
||||
const saleEstimate = await SaleEstimate.query(trx).upsertGraphAndFetch({
|
||||
...estimateObj,
|
||||
});
|
||||
// Triggers `onSaleEstimateCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onCreated, {
|
||||
tenantId,
|
||||
saleEstimate,
|
||||
saleEstimateId: saleEstimate.id,
|
||||
saleEstimateDTO: estimateDTO,
|
||||
trx,
|
||||
} as ISaleEstimateCreatedPayload);
|
||||
|
||||
return saleEstimate;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
ISaleEstimateDeletedPayload,
|
||||
ISaleEstimateDeletingPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { ERRORS } from './constants';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
@Service()
|
||||
export class DeleteSaleEstimate {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
* Deletes the given estimate id with associated entries.
|
||||
* @async
|
||||
* @param {number} tenantId - The tenant id.
|
||||
* @param {IEstimate} estimateId
|
||||
* @return {void}
|
||||
*/
|
||||
public async deleteEstimate(
|
||||
tenantId: number,
|
||||
estimateId: number
|
||||
): Promise<void> {
|
||||
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve sale estimate or throw not found service error.
|
||||
const oldSaleEstimate = await SaleEstimate.query()
|
||||
.findById(estimateId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Throw error if the sale estimate converted to sale invoice.
|
||||
if (oldSaleEstimate.convertedToInvoiceId) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_CONVERTED_TO_INVOICE);
|
||||
}
|
||||
// Deletes the estimate with associated transactions under UOW enivrement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleEstimatedDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onDeleting, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldSaleEstimate,
|
||||
} as ISaleEstimateDeletingPayload);
|
||||
|
||||
// Delete sale estimate entries.
|
||||
await ItemEntry.query(trx)
|
||||
.where('reference_id', estimateId)
|
||||
.where('reference_type', 'SaleEstimate')
|
||||
.delete();
|
||||
|
||||
// Delete sale estimate transaction.
|
||||
await SaleEstimate.query(trx).where('id', estimateId).delete();
|
||||
|
||||
// Triggers `onSaleEstimatedDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onDeleted, {
|
||||
tenantId,
|
||||
saleEstimateId: estimateId,
|
||||
oldSaleEstimate,
|
||||
trx,
|
||||
} as ISaleEstimateDeletedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
ISaleEstimateEventDeliveredPayload,
|
||||
ISaleEstimateEventDeliveringPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ERRORS } from './constants';
|
||||
import moment from 'moment';
|
||||
|
||||
@Service()
|
||||
export class DeliverSaleEstimate {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
* Mark the sale estimate as delivered.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} saleEstimateId - Sale estimate id.
|
||||
*/
|
||||
public async deliverSaleEstimate(
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate id.
|
||||
const oldSaleEstimate = await SaleEstimate.query()
|
||||
.findById(saleEstimateId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Throws error in case the sale estimate already published.
|
||||
if (oldSaleEstimate.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_DELIVERED);
|
||||
}
|
||||
// Updates the sale estimate transaction with assocaited transactions
|
||||
// under UOW envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleEstimateDelivering` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onDelivering, {
|
||||
oldSaleEstimate,
|
||||
trx,
|
||||
tenantId,
|
||||
} as ISaleEstimateEventDeliveringPayload);
|
||||
|
||||
// Record the delivered at on the storage.
|
||||
const saleEstimate = await SaleEstimate.query(trx).patchAndFetchById(
|
||||
saleEstimateId,
|
||||
{
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}
|
||||
);
|
||||
// Triggers `onSaleEstimateDelivered` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onDelivered, {
|
||||
tenantId,
|
||||
saleEstimate,
|
||||
trx,
|
||||
} as ISaleEstimateEventDeliveredPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
123
packages/server/src/services/Sales/Estimates/EditSaleEstimate.ts
Normal file
123
packages/server/src/services/Sales/Estimates/EditSaleEstimate.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
ISaleEstimate,
|
||||
ISaleEstimateDTO,
|
||||
ISaleEstimateEditedPayload,
|
||||
ISaleEstimateEditingPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { SaleEstimateValidators } from './SaleEstimateValidators';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { SaleEstimateDTOTransformer } from './SaleEstimateDTOTransformer';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class EditSaleEstimate {
|
||||
@Inject()
|
||||
private validators: SaleEstimateValidators;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private transformerDTO: SaleEstimateDTOTransformer;
|
||||
|
||||
/**
|
||||
* Edit details of the given estimate with associated entries.
|
||||
* @async
|
||||
* @param {number} tenantId - The tenant id.
|
||||
* @param {Integer} estimateId
|
||||
* @param {EstimateDTO} estimate
|
||||
* @return {Promise<ISaleEstimate>}
|
||||
*/
|
||||
public async editEstimate(
|
||||
tenantId: number,
|
||||
estimateId: number,
|
||||
estimateDTO: ISaleEstimateDTO
|
||||
): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate id.
|
||||
const oldSaleEstimate = await SaleEstimate.query().findById(estimateId);
|
||||
|
||||
// Validates the given estimate existance.
|
||||
this.validators.validateEstimateExistance(oldSaleEstimate);
|
||||
|
||||
// Retrieve the given customer or throw not found service error.
|
||||
const customer = await Contact.query()
|
||||
.modify('customer')
|
||||
.findById(estimateDTO.customerId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transform DTO object ot model object.
|
||||
const estimateObj = await this.transformerDTO.transformDTOToModel(
|
||||
tenantId,
|
||||
estimateDTO,
|
||||
oldSaleEstimate,
|
||||
customer
|
||||
);
|
||||
// Validate estimate number uniquiness on the storage.
|
||||
if (estimateDTO.estimateNumber) {
|
||||
await this.validators.validateEstimateNumberExistance(
|
||||
tenantId,
|
||||
estimateDTO.estimateNumber,
|
||||
estimateId
|
||||
);
|
||||
}
|
||||
// Validate sale estimate entries existance.
|
||||
await this.itemsEntriesService.validateEntriesIdsExistance(
|
||||
tenantId,
|
||||
estimateId,
|
||||
'SaleEstimate',
|
||||
estimateDTO.entries
|
||||
);
|
||||
// Validate items IDs existance on the storage.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
estimateDTO.entries
|
||||
);
|
||||
// Validate non-sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
estimateDTO.entries
|
||||
);
|
||||
// Edits estimate transaction with associated transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||
// Trigger `onSaleEstimateEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onEditing, {
|
||||
tenantId,
|
||||
oldSaleEstimate,
|
||||
estimateDTO,
|
||||
trx,
|
||||
} as ISaleEstimateEditingPayload);
|
||||
|
||||
// Upsert the estimate graph to the storage.
|
||||
const saleEstimate = await SaleEstimate.query(trx).upsertGraphAndFetch({
|
||||
id: estimateId,
|
||||
...estimateObj,
|
||||
});
|
||||
// Trigger `onSaleEstimateEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onEdited, {
|
||||
tenantId,
|
||||
estimateId,
|
||||
saleEstimate,
|
||||
oldSaleEstimate,
|
||||
trx,
|
||||
} as ISaleEstimateEditedPayload);
|
||||
|
||||
return saleEstimate;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { SaleEstimateTransfromer } from './SaleEstimateTransformer';
|
||||
import { SaleEstimateValidators } from './SaleEstimateValidators';
|
||||
|
||||
@Service()
|
||||
export class GetSaleEstimate {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleEstimateValidators;
|
||||
|
||||
/**
|
||||
* Retrieve the estimate details with associated entries.
|
||||
* @async
|
||||
* @param {number} tenantId - The tenant id.
|
||||
* @param {Integer} estimateId
|
||||
*/
|
||||
public async getEstimate(tenantId: number, estimateId: number) {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
const estimate = await SaleEstimate.query()
|
||||
.findById(estimateId)
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('customer')
|
||||
.withGraphFetched('branch');
|
||||
|
||||
// Validates the estimate existance.
|
||||
this.validators.validateEstimateExistance(estimate);
|
||||
|
||||
// Transformes sale estimate model to POJO.
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
estimate,
|
||||
new SaleEstimateTransfromer()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import * as R from 'ramda';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IFilterMeta,
|
||||
IPaginationMeta,
|
||||
ISaleEstimate,
|
||||
ISalesEstimatesFilter,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { SaleEstimateDTOTransformer } from './SaleEstimateDTOTransformer';
|
||||
import { SaleEstimateTransfromer } from './SaleEstimateTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetSaleEstimates {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieves estimates filterable and paginated list.
|
||||
* @param {number} tenantId -
|
||||
* @param {IEstimatesFilter} estimatesFilter -
|
||||
*/
|
||||
public async getEstimates(
|
||||
tenantId: number,
|
||||
filterDTO: ISalesEstimatesFilter
|
||||
): Promise<{
|
||||
salesEstimates: ISaleEstimate[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
SaleEstimate,
|
||||
filter
|
||||
);
|
||||
const { results, pagination } = await SaleEstimate.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('entries');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
const transformedEstimates = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new SaleEstimateTransfromer()
|
||||
);
|
||||
return {
|
||||
salesEstimates: transformedEstimates,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sale receipts list filter DTO.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
@Service()
|
||||
export class RejectSaleEstimate {
|
||||
@Inject()
|
||||
private tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
* Mark the sale estimate as rejected from the customer.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
*/
|
||||
public async rejectSaleEstimate(
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate id.
|
||||
const saleEstimate = await SaleEstimate.query()
|
||||
.findById(saleEstimateId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Throws error in case the sale estimate still not delivered to customer.
|
||||
if (!saleEstimate.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_DELIVERED);
|
||||
}
|
||||
// Throws error in case the sale estimate already rejected.
|
||||
if (saleEstimate.isRejected) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_REJECTED);
|
||||
}
|
||||
//
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Mark the sale estimate as reject on the storage.
|
||||
await SaleEstimate.query(trx).where('id', saleEstimateId).patch({
|
||||
rejectedAt: moment().toMySqlDateTime(),
|
||||
approvedAt: null,
|
||||
});
|
||||
// Triggers `onSaleEstimateRejected` event.
|
||||
await this.eventPublisher.emitAsync(events.saleEstimate.onRejected, {});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import * as R from 'ramda';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ICustomer, ISaleEstimate, ISaleEstimateDTO } from '@/interfaces';
|
||||
import { SaleEstimateValidators } from './SaleEstimateValidators';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||
import { formatDateFields } from '@/utils';
|
||||
import moment from 'moment';
|
||||
import { SaleEstimateIncrement } from './SaleEstimateIncrement';
|
||||
|
||||
@Service()
|
||||
export class SaleEstimateDTOTransformer {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleEstimateValidators;
|
||||
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private estimateIncrement: SaleEstimateIncrement;
|
||||
|
||||
/**
|
||||
* Transform create DTO object ot model object.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleEstimateDTO} saleEstimateDTO - Sale estimate DTO.
|
||||
* @return {ISaleEstimate}
|
||||
*/
|
||||
async transformDTOToModel(
|
||||
tenantId: number,
|
||||
estimateDTO: ISaleEstimateDTO,
|
||||
paymentCustomer: ICustomer,
|
||||
oldSaleEstimate?: ISaleEstimate
|
||||
): Promise<ISaleEstimate> {
|
||||
const { ItemEntry, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
const amount = sumBy(estimateDTO.entries, (e) => ItemEntry.calcAmount(e));
|
||||
|
||||
// Retreive the next invoice number.
|
||||
const autoNextNumber =
|
||||
this.estimateIncrement.getNextEstimateNumber(tenantId);
|
||||
|
||||
// Retreive the next estimate number.
|
||||
const estimateNumber =
|
||||
estimateDTO.estimateNumber ||
|
||||
oldSaleEstimate?.estimateNumber ||
|
||||
autoNextNumber;
|
||||
|
||||
// Validate the sale estimate number require.
|
||||
this.validators.validateEstimateNoRequire(estimateNumber);
|
||||
|
||||
const initialDTO = {
|
||||
amount,
|
||||
...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [
|
||||
'estimateDate',
|
||||
'expirationDate',
|
||||
]),
|
||||
currencyCode: paymentCustomer.currencyCode,
|
||||
exchangeRate: estimateDTO.exchangeRate || 1,
|
||||
...(estimateNumber ? { estimateNumber } : {}),
|
||||
entries: estimateDTO.entries.map((entry) => ({
|
||||
reference_type: 'SaleEstimate',
|
||||
...entry,
|
||||
})),
|
||||
// Avoid rewrite the deliver date in edit mode when already published.
|
||||
...(estimateDTO.delivered &&
|
||||
!oldSaleEstimate?.deliveredAt && {
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO<ISaleEstimate>(tenantId),
|
||||
this.warehouseDTOTransform.transformDTO<ISaleEstimate>(tenantId)
|
||||
)(initialDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve estimate number to object model.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleEstimateDTO} saleEstimateDTO
|
||||
* @param {ISaleEstimate} oldSaleEstimate
|
||||
*/
|
||||
public transformEstimateNumberToModel(
|
||||
tenantId: number,
|
||||
saleEstimateDTO: ISaleEstimateDTO,
|
||||
oldSaleEstimate?: ISaleEstimate
|
||||
): string {
|
||||
// Retreive the next invoice number.
|
||||
const autoNextNumber =
|
||||
this.estimateIncrement.getNextEstimateNumber(tenantId);
|
||||
|
||||
if (saleEstimateDTO.estimateNumber) {
|
||||
return saleEstimateDTO.estimateNumber;
|
||||
}
|
||||
return oldSaleEstimate ? oldSaleEstimate.estimateNumber : autoNextNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import AutoIncrementOrdersService from '../AutoIncrementOrdersService';
|
||||
|
||||
@Service()
|
||||
export class SaleEstimateIncrement {
|
||||
@Inject()
|
||||
private autoIncrementOrdersService: AutoIncrementOrdersService;
|
||||
|
||||
/**
|
||||
* Retrieve the next unique estimate number.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @return {string}
|
||||
*/
|
||||
public getNextEstimateNumber(tenantId: number): string {
|
||||
return this.autoIncrementOrdersService.getNextTransactionNumber(
|
||||
tenantId,
|
||||
'sales_estimates'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the estimate next number.
|
||||
* @param {number} tenantId -
|
||||
*/
|
||||
public incrementNextEstimateNumber(tenantId: number) {
|
||||
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
|
||||
tenantId,
|
||||
'sales_estimates'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import events from '@/subscribers/events';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import SaleNotifyBySms from '../SaleNotifyBySms';
|
||||
import SmsNotificationsSettingsService from '@/services/Settings/SmsNotificationsSettings';
|
||||
import SMSClient from '@/services/SMSClient';
|
||||
import {
|
||||
ICustomer,
|
||||
IPaymentReceiveSmsDetails,
|
||||
@@ -21,18 +20,18 @@ const ERRORS = {
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class SaleEstimateNotifyBySms {
|
||||
export class SaleEstimateNotifyBySms {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
saleSmsNotification: SaleNotifyBySms;
|
||||
private saleSmsNotification: SaleNotifyBySms;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
smsNotificationsSettings: SmsNotificationsSettingsService;
|
||||
private smsNotificationsSettings: SmsNotificationsSettingsService;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -187,6 +186,7 @@ export default class SaleEstimateNotifyBySms {
|
||||
.findById(saleEstimateId)
|
||||
.withGraphFetched('customer');
|
||||
|
||||
// Validates the estimate existance.
|
||||
this.validateEstimateExistance(saleEstimate);
|
||||
|
||||
// Retrieve the current tenant metadata.
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ISaleEstimate } from '@/interfaces';
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export default class SaleEstimateTransfromer extends Transformer {
|
||||
export class SaleEstimateTransfromer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ISaleEstimate } from '@/interfaces';
|
||||
import { ERRORS } from './constants';
|
||||
import { SaleEstimate } from '@/models';
|
||||
|
||||
@Service()
|
||||
export class SaleEstimateValidators {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Validates the given estimate existance.
|
||||
* @param {SaleEstimate | undefined | null} estimate -
|
||||
*/
|
||||
public validateEstimateExistance(estimate: SaleEstimate | undefined | null) {
|
||||
if (!estimate) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the estimate number unique on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
public async validateEstimateNumberExistance(
|
||||
tenantId: number,
|
||||
estimateNumber: string,
|
||||
notEstimateId?: number
|
||||
) {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
const foundSaleEstimate = await SaleEstimate.query()
|
||||
.findOne('estimate_number', estimateNumber)
|
||||
.onBuild((builder) => {
|
||||
if (notEstimateId) {
|
||||
builder.whereNot('id', notEstimateId);
|
||||
}
|
||||
});
|
||||
if (foundSaleEstimate) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_NUMBER_EXISTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given sale estimate not already converted to invoice.
|
||||
* @param {ISaleEstimate} saleEstimate -
|
||||
*/
|
||||
public validateEstimateNotConverted(saleEstimate: ISaleEstimate) {
|
||||
if (saleEstimate.isConvertedToInvoice) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_CONVERTED_TO_INVOICE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the sale estimate number require.
|
||||
* @param {ISaleEstimate} saleInvoiceObj
|
||||
*/
|
||||
public validateEstimateNoRequire(estimateNumber: string) {
|
||||
if (!estimateNumber) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_NO_IS_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given customer has no sales estimates.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId - Customer id.
|
||||
*/
|
||||
public async validateCustomerHasNoEstimates(
|
||||
tenantId: number,
|
||||
customerId: number
|
||||
) {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
const estimates = await SaleEstimate.query().where(
|
||||
'customer_id',
|
||||
customerId
|
||||
);
|
||||
if (estimates.length > 0) {
|
||||
throw new ServiceError(ERRORS.CUSTOMER_HAS_SALES_ESTIMATES);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { CreateSaleEstimate } from './CreateSaleEstimate';
|
||||
import {
|
||||
IFilterMeta,
|
||||
IPaginationMeta,
|
||||
IPaymentReceiveSmsDetails,
|
||||
ISaleEstimate,
|
||||
ISaleEstimateDTO,
|
||||
ISalesEstimatesFilter,
|
||||
} from '@/interfaces';
|
||||
import { EditSaleEstimate } from './EditSaleEstimate';
|
||||
import { DeleteSaleEstimate } from './DeleteSaleEstimate';
|
||||
import { GetSaleEstimate } from './GetSaleEstimate';
|
||||
import { GetSaleEstimates } from './GetSaleEstimates';
|
||||
import { DeliverSaleEstimate } from './DeliverSaleEstimate';
|
||||
import { ApproveSaleEstimate } from './ApproveSaleEstimate';
|
||||
import { RejectSaleEstimate } from './RejectSaleEstimate';
|
||||
import { SaleEstimateNotifyBySms } from './SaleEstimateSmsNotify';
|
||||
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
|
||||
|
||||
@Service()
|
||||
export class SaleEstimatesApplication {
|
||||
@Inject()
|
||||
private createSaleEstimateService: CreateSaleEstimate;
|
||||
|
||||
@Inject()
|
||||
private editSaleEstimateService: EditSaleEstimate;
|
||||
|
||||
@Inject()
|
||||
private deleteSaleEstimateService: DeleteSaleEstimate;
|
||||
|
||||
@Inject()
|
||||
private getSaleEstimateService: GetSaleEstimate;
|
||||
|
||||
@Inject()
|
||||
private getSaleEstimatesService: GetSaleEstimates;
|
||||
|
||||
@Inject()
|
||||
private deliverSaleEstimateService: DeliverSaleEstimate;
|
||||
|
||||
@Inject()
|
||||
private approveSaleEstimateService: ApproveSaleEstimate;
|
||||
|
||||
@Inject()
|
||||
private rejectSaleEstimateService: RejectSaleEstimate;
|
||||
|
||||
@Inject()
|
||||
private saleEstimateNotifyBySmsService: SaleEstimateNotifyBySms;
|
||||
|
||||
@Inject()
|
||||
private saleEstimatesPdfService: SaleEstimatesPdf;
|
||||
|
||||
/**
|
||||
* Create a sale estimate.
|
||||
* @param {number} tenantId - The tenant id.
|
||||
* @param {EstimateDTO} estimate
|
||||
* @return {Promise<ISaleEstimate>}
|
||||
*/
|
||||
public createSaleEstimate(
|
||||
tenantId: number,
|
||||
estimateDTO: ISaleEstimateDTO
|
||||
): Promise<ISaleEstimate> {
|
||||
return this.createSaleEstimateService.createEstimate(tenantId, estimateDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given sale estimate.
|
||||
* @param {number} tenantId - The tenant id.
|
||||
* @param {Integer} estimateId
|
||||
* @param {EstimateDTO} estimate
|
||||
* @return {Promise<ISaleEstimate>}
|
||||
*/
|
||||
public editSaleEstimate(
|
||||
tenantId: number,
|
||||
estimateId: number,
|
||||
estimateDTO: ISaleEstimateDTO
|
||||
): Promise<ISaleEstimate> {
|
||||
return this.editSaleEstimateService.editEstimate(
|
||||
tenantId,
|
||||
estimateId,
|
||||
estimateDTO
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given sale estimate.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} estimateId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public deleteSaleEstimate(
|
||||
tenantId: number,
|
||||
estimateId: number
|
||||
): Promise<void> {
|
||||
return this.deleteSaleEstimateService.deleteEstimate(tenantId, estimateId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given sale estimate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} estimateId
|
||||
*/
|
||||
public getSaleEstimate(tenantId: number, estimateId: number) {
|
||||
return this.getSaleEstimateService.getEstimate(tenantId, estimateId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sale estimate.
|
||||
* @param {number} tenantId
|
||||
* @param {ISalesEstimatesFilter} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
public getSaleEstimates(
|
||||
tenantId: number,
|
||||
filterDTO: ISalesEstimatesFilter
|
||||
): Promise<{
|
||||
salesEstimates: ISaleEstimate[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
return this.getSaleEstimatesService.getEstimates(tenantId, filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver the given sale estimate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deliverSaleEstimate(tenantId: number, saleEstimateId: number) {
|
||||
return this.deliverSaleEstimateService.deliverSaleEstimate(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve the given sale estimate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public approveSaleEstimate(
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
): Promise<void> {
|
||||
return this.approveSaleEstimateService.approveSaleEstimate(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the sale estimate as rejected from the customer.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
*/
|
||||
public async rejectSaleEstimate(
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
): Promise<void> {
|
||||
return this.rejectSaleEstimateService.rejectSaleEstimate(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the customer of the given sale estimate by SMS.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @returns {Promise<ISaleEstimate>}
|
||||
*/
|
||||
public notifySaleEstimateBySms = async (
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
): Promise<ISaleEstimate> => {
|
||||
return this.saleEstimateNotifyBySmsService.notifyBySms(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the SMS details of the given payment receive transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @returns {Promise<IPaymentReceiveSmsDetails>}
|
||||
*/
|
||||
public getSaleEstimateSmsDetails = (
|
||||
tenantId: number,
|
||||
saleEstimateId: number
|
||||
): Promise<IPaymentReceiveSmsDetails> => {
|
||||
return this.saleEstimateNotifyBySmsService.smsDetails(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {} saleEstimate
|
||||
* @returns
|
||||
*/
|
||||
public getSaleEstimatePdf(tenantId: number, saleEstimate) {
|
||||
return this.saleEstimatesPdfService.getSaleEstimatePdf(
|
||||
tenantId,
|
||||
saleEstimate
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,18 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
@Service()
|
||||
export default class SaleEstimatesPdf {
|
||||
export class SaleEstimatesPdf {
|
||||
@Inject()
|
||||
pdfService: PdfService;
|
||||
private pdfService: PdfService;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice pdf content.
|
||||
* @param {} saleInvoice -
|
||||
*/
|
||||
async saleEstimatePdf(tenantId: number, saleEstimate) {
|
||||
async getSaleEstimatePdf(tenantId: number, saleEstimate) {
|
||||
const i18n = this.tenancy.i18n(tenantId);
|
||||
|
||||
const organization = await Tenant.query()
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class UnlinkConvertedSaleEstimate {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Unlink the converted sale estimates from the given sale invoice.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} invoiceId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async unlinkConvertedEstimateFromInvoice(
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
await SaleEstimate.query(trx)
|
||||
.where({
|
||||
convertedToInvoiceId: invoiceId,
|
||||
})
|
||||
.patch({
|
||||
convertedToInvoiceId: null,
|
||||
convertedToInvoiceAt: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import moment from 'moment';
|
||||
import composeAsync from 'async/compose';
|
||||
import {
|
||||
ISaleInvoice,
|
||||
ISaleInvoiceCreateDTO,
|
||||
ISaleInvoiceEditDTO,
|
||||
ICustomer,
|
||||
ITenantUser,
|
||||
} from '@/interfaces';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||
import { SaleInvoiceIncrement } from './SaleInvoiceIncrement';
|
||||
import { formatDateFields } from 'utils';
|
||||
|
||||
@Service()
|
||||
export class CommandSaleInvoiceDTOTransformer {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private validators: CommandSaleInvoiceValidators;
|
||||
|
||||
@Inject()
|
||||
private invoiceIncrement: SaleInvoiceIncrement;
|
||||
|
||||
/**
|
||||
* Transformes the create DTO to invoice object model.
|
||||
* @param {ISaleInvoiceCreateDTO} saleInvoiceDTO - Sale invoice DTO.
|
||||
* @param {ISaleInvoice} oldSaleInvoice - Old sale invoice.
|
||||
* @return {ISaleInvoice}
|
||||
*/
|
||||
public async transformDTOToModel(
|
||||
tenantId: number,
|
||||
customer: ICustomer,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO,
|
||||
authorizedUser: ITenantUser,
|
||||
oldSaleInvoice?: ISaleInvoice
|
||||
): Promise<ISaleInvoice> {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const balance = sumBy(saleInvoiceDTO.entries, (e) =>
|
||||
ItemEntry.calcAmount(e)
|
||||
);
|
||||
// Retreive the next invoice number.
|
||||
const autoNextNumber = this.invoiceIncrement.getNextInvoiceNumber(tenantId);
|
||||
|
||||
// Invoice number.
|
||||
const invoiceNo =
|
||||
saleInvoiceDTO.invoiceNo || oldSaleInvoice?.invoiceNo || autoNextNumber;
|
||||
|
||||
// Validate the invoice is required.
|
||||
this.validators.validateInvoiceNoRequire(invoiceNo);
|
||||
|
||||
const initialEntries = saleInvoiceDTO.entries.map((entry) => ({
|
||||
referenceType: 'SaleInvoice',
|
||||
...entry,
|
||||
}));
|
||||
const entries = await composeAsync(
|
||||
// Sets default cost and sell account to invoice items entries.
|
||||
this.itemsEntriesService.setItemsEntriesDefaultAccounts(tenantId)
|
||||
)(initialEntries);
|
||||
|
||||
const initialDTO = {
|
||||
...formatDateFields(
|
||||
omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']),
|
||||
['invoiceDate', 'dueDate']
|
||||
),
|
||||
// Avoid rewrite the deliver date in edit mode when already published.
|
||||
balance,
|
||||
currencyCode: customer.currencyCode,
|
||||
exchangeRate: saleInvoiceDTO.exchangeRate || 1,
|
||||
...(saleInvoiceDTO.delivered &&
|
||||
!oldSaleInvoice?.deliveredAt && {
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
// Avoid override payment amount in edit mode.
|
||||
...(!oldSaleInvoice && { paymentAmount: 0 }),
|
||||
...(invoiceNo ? { invoiceNo } : {}),
|
||||
entries,
|
||||
userId: authorizedUser.id,
|
||||
} as ISaleInvoice;
|
||||
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO<ISaleInvoice>(tenantId),
|
||||
this.warehouseDTOTransform.transformDTO<ISaleInvoice>(tenantId)
|
||||
)(initialDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { SaleInvoice } from '@/models';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export class CommandSaleInvoiceValidators {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Validates the given invoice is existance.
|
||||
* @param {SaleInvoice | undefined} invoice
|
||||
*/
|
||||
public validateInvoiceExistance(invoice: SaleInvoice | undefined) {
|
||||
if (!invoice) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether sale invoice number unqiue on the storage.
|
||||
*/
|
||||
public async validateInvoiceNumberUnique(
|
||||
tenantId: number,
|
||||
invoiceNumber: string,
|
||||
notInvoiceId?: number
|
||||
) {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const saleInvoice = await SaleInvoice.query()
|
||||
.findOne('invoice_no', invoiceNumber)
|
||||
.onBuild((builder) => {
|
||||
if (notInvoiceId) {
|
||||
builder.whereNot('id', notInvoiceId);
|
||||
}
|
||||
});
|
||||
|
||||
if (saleInvoice) {
|
||||
throw new ServiceError(ERRORS.INVOICE_NUMBER_NOT_UNIQUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the invoice amount is bigger than payment amount before edit the invoice.
|
||||
* @param {number} saleInvoiceAmount
|
||||
* @param {number} paymentAmount
|
||||
*/
|
||||
public validateInvoiceAmountBiggerPaymentAmount(
|
||||
saleInvoiceAmount: number,
|
||||
paymentAmount: number
|
||||
) {
|
||||
if (saleInvoiceAmount < paymentAmount) {
|
||||
throw new ServiceError(ERRORS.INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the invoice number require.
|
||||
* @param {ISaleInvoice} saleInvoiceObj
|
||||
*/
|
||||
public validateInvoiceNoRequire(invoiceNo: string) {
|
||||
if (!invoiceNo) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_NO_IS_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given customer has no sales invoices.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId - Customer id.
|
||||
*/
|
||||
public async validateCustomerHasNoInvoices(
|
||||
tenantId: number,
|
||||
customerId: number
|
||||
) {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const invoices = await SaleInvoice.query().where('customer_id', customerId);
|
||||
|
||||
if (invoices.length > 0) {
|
||||
throw new ServiceError(ERRORS.CUSTOMER_HAS_SALES_INVOICES);
|
||||
}
|
||||
}
|
||||
}
|
||||
147
packages/server/src/services/Sales/Invoices/CreateSaleInvoice.ts
Normal file
147
packages/server/src/services/Sales/Invoices/CreateSaleInvoice.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ICustomer,
|
||||
ISaleInvoice,
|
||||
ISaleInvoiceCreateDTO,
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceCreatingPaylaod,
|
||||
ITenantUser,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||
import { CommandSaleInvoiceDTOTransformer } from './CommandSaleInvoiceDTOTransformer';
|
||||
import { SaleEstimateValidators } from '../Estimates/SaleEstimateValidators';
|
||||
|
||||
@Service()
|
||||
export class CreateSaleInvoice {
|
||||
@Inject()
|
||||
private tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private validators: CommandSaleInvoiceValidators;
|
||||
|
||||
@Inject()
|
||||
private transformerDTO: CommandSaleInvoiceDTOTransformer;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private commandEstimateValidators: SaleEstimateValidators;
|
||||
|
||||
/**
|
||||
* Creates a new sale invoices and store it to the storage
|
||||
* with associated to entries and journal transactions.
|
||||
* @async
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {ISaleInvoice} saleInvoiceDTO - Sale invoice object DTO.
|
||||
* @return {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public createSaleInvoice = async (
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO,
|
||||
authorizedUser: ITenantUser
|
||||
): Promise<ISaleInvoice> => {
|
||||
const { SaleInvoice, SaleEstimate, Contact } =
|
||||
this.tenancy.models(tenantId);
|
||||
|
||||
// Validate customer existance.
|
||||
const customer = await Contact.query()
|
||||
.modify('customer')
|
||||
.findById(saleInvoiceDTO.customerId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the from estimate id exists on the storage.
|
||||
if (saleInvoiceDTO.fromEstimateId) {
|
||||
const fromEstimate = await SaleEstimate.query()
|
||||
.findById(saleInvoiceDTO.fromEstimateId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the sale estimate is not already converted to invoice.
|
||||
this.commandEstimateValidators.validateEstimateNotConverted(fromEstimate);
|
||||
}
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
// Validate items should be sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = await this.transformCreateDTOToModel(
|
||||
tenantId,
|
||||
customer,
|
||||
saleInvoiceDTO,
|
||||
authorizedUser
|
||||
);
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceObj.invoiceNo) {
|
||||
await this.validators.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceObj.invoiceNo
|
||||
);
|
||||
}
|
||||
// Creates a new sale invoice and associated transactions under unit of work env.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleInvoiceCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.saleInvoice.onCreating, {
|
||||
saleInvoiceDTO,
|
||||
tenantId,
|
||||
trx,
|
||||
} as ISaleInvoiceCreatingPaylaod);
|
||||
|
||||
// Create sale invoice graph to the storage.
|
||||
const saleInvoice = await SaleInvoice.query(trx).upsertGraph(
|
||||
saleInvoiceObj
|
||||
);
|
||||
const eventPayload: ISaleInvoiceCreatedPayload = {
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
saleInvoiceDTO,
|
||||
saleInvoiceId: saleInvoice.id,
|
||||
authorizedUser,
|
||||
trx,
|
||||
};
|
||||
// Triggers the event `onSaleInvoiceCreated`.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleInvoice.onCreated,
|
||||
eventPayload
|
||||
);
|
||||
return saleInvoice;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes create DTO to model.
|
||||
* @param {number} tenantId -
|
||||
* @param {ICustomer} customer -
|
||||
* @param {ISaleInvoiceCreateDTO} saleInvoiceDTO -
|
||||
*/
|
||||
private transformCreateDTOToModel = async (
|
||||
tenantId: number,
|
||||
customer: ICustomer,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO,
|
||||
authorizedUser: ITenantUser
|
||||
) => {
|
||||
return this.transformerDTO.transformDTOToModel(
|
||||
tenantId,
|
||||
customer,
|
||||
saleInvoiceDTO,
|
||||
authorizedUser
|
||||
);
|
||||
};
|
||||
}
|
||||
154
packages/server/src/services/Sales/Invoices/DeleteSaleInvoice.ts
Normal file
154
packages/server/src/services/Sales/Invoices/DeleteSaleInvoice.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ISystemUser,
|
||||
ISaleInvoiceDeletePayload,
|
||||
ISaleInvoiceDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
import { UnlinkConvertedSaleEstimate } from '../Estimates/UnlinkConvertedSaleEstimate';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class DeleteSaleInvoice {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private unlockEstimateFromInvoice: UnlinkConvertedSaleEstimate;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
* Validate the sale invoice has no payment entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
*/
|
||||
private async validateInvoiceHasNoPaymentEntries(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number
|
||||
) {
|
||||
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the sale invoice associated payment receive entries.
|
||||
const entries = await PaymentReceiveEntry.query().where(
|
||||
'invoice_id',
|
||||
saleInvoiceId
|
||||
);
|
||||
if (entries.length > 0) {
|
||||
throw new ServiceError(ERRORS.INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the sale invoice has no applied to credit note transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {number} invoiceId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public validateInvoiceHasNoAppliedToCredit = async (
|
||||
tenantId: number,
|
||||
invoiceId: number
|
||||
): Promise<void> => {
|
||||
const { CreditNoteAppliedInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const appliedTransactions = await CreditNoteAppliedInvoice.query().where(
|
||||
'invoiceId',
|
||||
invoiceId
|
||||
);
|
||||
if (appliedTransactions.length > 0) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_HAS_APPLIED_TO_CREDIT_NOTES);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate whether sale invoice exists on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
private async getInvoiceOrThrowError(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number
|
||||
) {
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const saleInvoice = await saleInvoiceRepository.findOneById(
|
||||
saleInvoiceId,
|
||||
'entries'
|
||||
);
|
||||
if (!saleInvoice) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
||||
}
|
||||
return saleInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given sale invoice with associated entries
|
||||
* and journal transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Number} saleInvoiceId - The given sale invoice id.
|
||||
* @param {ISystemUser} authorizedUser -
|
||||
*/
|
||||
public async deleteSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<void> {
|
||||
const { ItemEntry, SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given sale invoice with associated entries
|
||||
// or throw not found error.
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
// Validate the sale invoice has no associated payment entries.
|
||||
await this.validateInvoiceHasNoPaymentEntries(tenantId, saleInvoiceId);
|
||||
|
||||
// Validate the sale invoice has applied to credit note transaction.
|
||||
await this.validateInvoiceHasNoAppliedToCredit(tenantId, saleInvoiceId);
|
||||
|
||||
// Deletes sale invoice transaction and associate transactions with UOW env.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleInvoiceDelete` event.
|
||||
await this.eventPublisher.emitAsync(events.saleInvoice.onDeleting, {
|
||||
tenantId,
|
||||
saleInvoice: oldSaleInvoice,
|
||||
saleInvoiceId,
|
||||
trx,
|
||||
} as ISaleInvoiceDeletePayload);
|
||||
|
||||
// Unlink the converted sale estimates from the given sale invoice.
|
||||
await this.unlockEstimateFromInvoice.unlinkConvertedEstimateFromInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
trx
|
||||
);
|
||||
await ItemEntry.query(trx)
|
||||
.where('reference_id', saleInvoiceId)
|
||||
.where('reference_type', 'SaleInvoice')
|
||||
.delete();
|
||||
|
||||
await SaleInvoice.query(trx).findById(saleInvoiceId).delete();
|
||||
|
||||
// Triggers `onSaleInvoiceDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.saleInvoice.onDeleted, {
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
saleInvoiceId,
|
||||
authorizedUser,
|
||||
trx,
|
||||
} as ISaleInvoiceDeletedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Knex } from 'knex';
|
||||
import moment from 'moment';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
ISaleInvoiceDeliveringPayload,
|
||||
ISaleInvoiceEventDeliveredPayload,
|
||||
ISystemUser,
|
||||
} from '@/interfaces';
|
||||
import { ERRORS } from './constants';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import events from '@/subscribers/events';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||
|
||||
@Service()
|
||||
export class DeliverSaleInvoice {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validators: CommandSaleInvoiceValidators;
|
||||
|
||||
/**
|
||||
* Deliver the given sale invoice.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} saleInvoiceId - Sale invoice id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async deliverSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<void> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale invoice id.
|
||||
const oldSaleInvoice = await SaleInvoice.query().findById(saleInvoiceId);
|
||||
|
||||
// Validates the given invoice existance.
|
||||
this.validators.validateInvoiceExistance(oldSaleInvoice);
|
||||
|
||||
// Throws error in case the sale invoice already published.
|
||||
if (oldSaleInvoice.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_ALREADY_DELIVERED);
|
||||
}
|
||||
// Update sale invoice transaction with assocaite transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleInvoiceDelivering` event.
|
||||
await this.eventPublisher.emitAsync(events.saleInvoice.onDelivering, {
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx,
|
||||
} as ISaleInvoiceDeliveringPayload);
|
||||
|
||||
// Record the delivered at on the storage.
|
||||
const saleInvoice = await SaleInvoice.query(trx)
|
||||
.where({ id: saleInvoiceId })
|
||||
.update({ deliveredAt: moment().toMySqlDateTime() });
|
||||
|
||||
// Triggers `onSaleInvoiceDelivered` event.
|
||||
await this.eventPublisher.emitAsync(events.saleInvoice.onDelivered, {
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
saleInvoice,
|
||||
} as ISaleInvoiceEventDeliveredPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
165
packages/server/src/services/Sales/Invoices/EditSaleInvoice.ts
Normal file
165
packages/server/src/services/Sales/Invoices/EditSaleInvoice.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
ICustomer,
|
||||
ISaleInvoice,
|
||||
ISaleInvoiceEditDTO,
|
||||
ISaleInvoiceEditedPayload,
|
||||
ISaleInvoiceEditingPayload,
|
||||
ISystemUser,
|
||||
ITenantUser,
|
||||
} from '@/interfaces';
|
||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||
import { CommandSaleInvoiceDTOTransformer } from './CommandSaleInvoiceDTOTransformer';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class EditSaleInvoice {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private validators: CommandSaleInvoiceValidators;
|
||||
|
||||
@Inject()
|
||||
private transformerDTO: CommandSaleInvoiceDTOTransformer;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
* Edit the given sale invoice.
|
||||
* @async
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Number} saleInvoiceId - Sale invoice id.
|
||||
* @param {ISaleInvoice} saleInvoice - Sale invoice DTO object.
|
||||
* @return {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public async editSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the sale invoice or throw not found service error.
|
||||
const oldSaleInvoice = await SaleInvoice.query()
|
||||
.findById(saleInvoiceId)
|
||||
.withGraphJoined('entries');
|
||||
|
||||
// Validates the given invoice existance.
|
||||
this.validators.validateInvoiceExistance(oldSaleInvoice);
|
||||
|
||||
// Validate customer existance.
|
||||
const customer = await Contact.query()
|
||||
.findById(saleInvoiceDTO.customerId)
|
||||
.modify('customer')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
// 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
|
||||
);
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = await this.tranformEditDTOToModel(
|
||||
tenantId,
|
||||
customer,
|
||||
saleInvoiceDTO,
|
||||
oldSaleInvoice,
|
||||
authorizedUser
|
||||
);
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceObj.invoiceNo) {
|
||||
await this.validators.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceObj.invoiceNo,
|
||||
saleInvoiceId
|
||||
);
|
||||
}
|
||||
// Validate the invoice amount is not smaller than the invoice payment amount.
|
||||
this.validators.validateInvoiceAmountBiggerPaymentAmount(
|
||||
saleInvoiceObj.balance,
|
||||
oldSaleInvoice.paymentAmount
|
||||
);
|
||||
// Edit sale invoice transaction in UOW envirment.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleInvoiceEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.saleInvoice.onEditing, {
|
||||
trx,
|
||||
oldSaleInvoice,
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
} as ISaleInvoiceEditingPayload);
|
||||
|
||||
// Upsert the the invoice graph to the storage.
|
||||
const saleInvoice: ISaleInvoice =
|
||||
await SaleInvoice.query().upsertGraphAndFetch({
|
||||
id: saleInvoiceId,
|
||||
...saleInvoiceObj,
|
||||
});
|
||||
// Edit event payload.
|
||||
const editEventPayload: ISaleInvoiceEditedPayload = {
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
saleInvoice,
|
||||
saleInvoiceDTO,
|
||||
oldSaleInvoice,
|
||||
authorizedUser,
|
||||
trx,
|
||||
};
|
||||
// Triggers `onSaleInvoiceEdited` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleInvoice.onEdited,
|
||||
editEventPayload
|
||||
);
|
||||
return saleInvoice;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes edit DTO to model.
|
||||
* @param {number} tennatId -
|
||||
* @param {ICustomer} customer -
|
||||
* @param {ISaleInvoiceEditDTO} saleInvoiceDTO -
|
||||
* @param {ISaleInvoice} oldSaleInvoice
|
||||
*/
|
||||
private tranformEditDTOToModel = async (
|
||||
tenantId: number,
|
||||
customer: ICustomer,
|
||||
saleInvoiceDTO: ISaleInvoiceEditDTO,
|
||||
oldSaleInvoice: ISaleInvoice,
|
||||
authorizedUser: ITenantUser
|
||||
) => {
|
||||
return this.transformerDTO.transformDTOToModel(
|
||||
tenantId,
|
||||
customer,
|
||||
saleInvoiceDTO,
|
||||
authorizedUser,
|
||||
oldSaleInvoice
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { InvoicePaymentTransactionTransformer } from './InvoicePaymentTransactio
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export default class InvoicePaymentsService {
|
||||
export class GetInvoicePaymentsService {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleInvoice, ISystemUser } from '@/interfaces';
|
||||
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||
|
||||
@Service()
|
||||
export class GetSaleInvoice {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private validators: CommandSaleInvoiceValidators;
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice with associated entries.
|
||||
* @param {Number} saleInvoiceId -
|
||||
* @param {ISystemUser} authorizedUser -
|
||||
* @return {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public async getSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const saleInvoice = await SaleInvoice.query()
|
||||
.findById(saleInvoiceId)
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('customer')
|
||||
.withGraphFetched('branch');
|
||||
|
||||
// Validates the given sale invoice existance.
|
||||
this.validators.validateInvoiceExistance(saleInvoice);
|
||||
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
new SaleInvoiceTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IFilterMeta,
|
||||
IPaginationMeta,
|
||||
ISaleInvoice,
|
||||
ISalesInvoicesFilter,
|
||||
} from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetSaleInvoices {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve sales invoices filterable and paginated list.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public async getSaleInvoices(
|
||||
tenantId: number,
|
||||
filterDTO: ISalesInvoicesFilter
|
||||
): Promise<{
|
||||
salesInvoices: ISaleInvoice[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
SaleInvoice,
|
||||
filter
|
||||
);
|
||||
const { results, pagination } = await SaleInvoice.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('customer');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Retrieves the transformed sale invoices.
|
||||
const salesInvoices = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new SaleInvoiceTransformer()
|
||||
);
|
||||
|
||||
return {
|
||||
salesInvoices,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sale invoice list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleInvoice } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class GetSaleInvoicesPayable {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve due sales invoices.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
*/
|
||||
public async getPayableInvoices(
|
||||
tenantId: number,
|
||||
customerId?: number
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const salesInvoices = await SaleInvoice.query().onBuild((query) => {
|
||||
query.modify('dueInvoices');
|
||||
query.modify('delivered');
|
||||
|
||||
if (customerId) {
|
||||
query.where('customer_id', customerId);
|
||||
}
|
||||
});
|
||||
return salesInvoices;
|
||||
}
|
||||
}
|
||||
@@ -88,8 +88,8 @@ export class SaleInvoiceGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the given invoice ledger.
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @returns {ILedger}
|
||||
*/
|
||||
public getInvoiceGLedger = (
|
||||
@@ -103,7 +103,7 @@ export class SaleInvoiceGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the invoice GL common entry.
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @returns {Partial<ILedgerEntry>}
|
||||
*/
|
||||
private getInvoiceGLCommonEntry = (
|
||||
@@ -131,8 +131,8 @@ export class SaleInvoiceGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieve receivable entry of the given invoice.
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getInvoiceReceivableEntry = (
|
||||
@@ -153,9 +153,9 @@ export class SaleInvoiceGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieve item income entry of the given invoice.
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
* @param {IItemEntry} entry -
|
||||
* @param {number} index -
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
* @param {IItemEntry} entry -
|
||||
* @param {number} index -
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getInvoiceItemEntry = R.curry(
|
||||
@@ -183,8 +183,8 @@ export class SaleInvoiceGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the invoice GL entries.
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
public getInvoiceGLEntries = (
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import { ISaleInvoice } from '@/interfaces';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
|
||||
@Service()
|
||||
export class InvoiceInventoryTransactions {
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private inventoryService: InventoryService;
|
||||
|
||||
/**
|
||||
* Records the inventory transactions of the given sale invoice in case
|
||||
* the invoice has inventory entries only.
|
||||
*
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {SaleInvoice} saleInvoice - Sale invoice DTO.
|
||||
* @param {number} saleInvoiceId - Sale invoice id.
|
||||
* @param {boolean} override - Allow to override old transactions.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async recordInventoryTranscactions(
|
||||
tenantId: number,
|
||||
saleInvoice: ISaleInvoice,
|
||||
override?: boolean,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
saleInvoice.entries
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: saleInvoice.id,
|
||||
transactionType: 'SaleInvoice',
|
||||
transactionNumber: saleInvoice.invoiceNo,
|
||||
|
||||
exchangeRate: saleInvoice.exchangeRate,
|
||||
warehouseId: saleInvoice.warehouseId,
|
||||
|
||||
date: saleInvoice.invoiceDate,
|
||||
direction: 'OUT',
|
||||
entries: inventoryEntries,
|
||||
createdAt: saleInvoice.createdAt,
|
||||
};
|
||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
tenantId,
|
||||
transaction,
|
||||
override,
|
||||
trx
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Reverting the inventory transactions once the invoice deleted.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async revertInventoryTransactions(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
// Delete the inventory transaction of the given sale invoice.
|
||||
const { oldInventoryTransactions } =
|
||||
await this.inventoryService.deleteInventoryTransactions(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
'SaleInvoice',
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,11 @@ export class InvoicePaymentTransactionTransformer extends Transformer {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted payment date.
|
||||
* @param entry
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedPaymentDate = (entry): string => {
|
||||
return this.formatDate(entry.payment.paymentDate);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Knex } from 'knex';
|
||||
import async from 'async';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { PaymentReceiveGLEntries } from '../PaymentReceives/PaymentReceiveGLEntries';
|
||||
|
||||
@Service()
|
||||
|
||||
@@ -64,7 +64,7 @@ export class SaleInvoiceCostGLEntries {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {IInventoryLotCost} inventoryCostLot
|
||||
* @param {IInventoryLotCost} inventoryCostLot
|
||||
* @returns {}
|
||||
*/
|
||||
private getInvoiceCostGLCommonEntry = (
|
||||
@@ -91,7 +91,7 @@ export class SaleInvoiceCostGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the inventory cost GL entry.
|
||||
* @param {IInventoryLotCost} inventoryLotCost
|
||||
* @param {IInventoryLotCost} inventoryLotCost
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
private getInventoryCostGLEntry = R.curry(
|
||||
@@ -127,10 +127,10 @@ export class SaleInvoiceCostGLEntries {
|
||||
|
||||
/**
|
||||
* Writes journal entries for given sale invoice.
|
||||
* -------
|
||||
* -----
|
||||
* - Cost of goods sold -> Debit -> YYYY
|
||||
* - Inventory assets -> Credit -> YYYY
|
||||
* --------
|
||||
*-----
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {JournalPoster} journal
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import AutoIncrementOrdersService from '../AutoIncrementOrdersService';
|
||||
|
||||
@Service()
|
||||
export class SaleInvoiceIncrement {
|
||||
@Inject()
|
||||
private autoIncrementOrdersService: AutoIncrementOrdersService;
|
||||
|
||||
/**
|
||||
* Retrieves the next unique invoice number.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @return {string}
|
||||
*/
|
||||
public getNextInvoiceNumber(tenantId: number): string {
|
||||
return this.autoIncrementOrdersService.getNextTransactionNumber(
|
||||
tenantId,
|
||||
'sales_invoices'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the invoice next number.
|
||||
* @param {number} tenantId -
|
||||
*/
|
||||
public incrementNextInvoiceNumber(tenantId: number) {
|
||||
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
|
||||
tenantId,
|
||||
'sales_invoices'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import events from '@/subscribers/events';
|
||||
import SaleInvoicesService from './SalesInvoices';
|
||||
import SMSClient from '@/services/SMSClient';
|
||||
import {
|
||||
ISaleInvoice,
|
||||
ISaleInvoiceSmsDetailsDTO,
|
||||
@@ -15,27 +13,28 @@ import {
|
||||
import SmsNotificationsSettingsService from '@/services/Settings/SmsNotificationsSettings';
|
||||
import { formatSmsMessage, formatNumber } from 'utils';
|
||||
import { TenantMetadata } from '@/system/models';
|
||||
import SaleNotifyBySms from './SaleNotifyBySms';
|
||||
import SaleNotifyBySms from '../SaleNotifyBySms';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { ERRORS } from './constants';
|
||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||
|
||||
@Service()
|
||||
export default class SaleInvoiceNotifyBySms {
|
||||
export class SaleInvoiceNotifyBySms {
|
||||
@Inject()
|
||||
invoiceService: SaleInvoicesService;
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
private smsNotificationsSettings: SmsNotificationsSettingsService;
|
||||
|
||||
@Inject()
|
||||
smsNotificationsSettings: SmsNotificationsSettingsService;
|
||||
private saleSmsNotification: SaleNotifyBySms;
|
||||
|
||||
@Inject()
|
||||
saleSmsNotification: SaleNotifyBySms;
|
||||
private validators: CommandSaleInvoiceValidators;
|
||||
|
||||
/**
|
||||
* Notify customer via sms about sale invoice.
|
||||
@@ -54,6 +53,9 @@ export default class SaleInvoiceNotifyBySms {
|
||||
.findById(saleInvoiceId)
|
||||
.withGraphFetched('customer');
|
||||
|
||||
// Validates the givne invoice existance.
|
||||
this.validators.validateInvoiceExistance(saleInvoice);
|
||||
|
||||
// Validate the customer phone number existance and number validation.
|
||||
this.saleSmsNotification.validateCustomerPhoneNumber(
|
||||
saleInvoice.customer.personalPhone
|
||||
@@ -5,7 +5,7 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
@Service()
|
||||
export default class SaleInvoicePdf {
|
||||
export class SaleInvoicePdf {
|
||||
@Inject()
|
||||
pdfService: PdfService;
|
||||
|
||||
@@ -26,8 +26,8 @@ export class SaleInvoiceWriteoffGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the invoice write-off receiveable GL entry.
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getInvoiceWriteoffGLReceivableEntry = (
|
||||
@@ -50,7 +50,7 @@ export class SaleInvoiceWriteoffGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the invoice write-off expense GL entry.
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getInvoiceWriteoffGLExpenseEntry = (
|
||||
@@ -71,8 +71,8 @@ export class SaleInvoiceWriteoffGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the invoice write-off GL entries.
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
public getInvoiceWriteoffGLEntries = (
|
||||
@@ -89,8 +89,8 @@ export class SaleInvoiceWriteoffGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the invoice write-off ledger.
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} ARAccountId
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @returns {Ledger}
|
||||
*/
|
||||
public getInvoiceWriteoffLedger = (
|
||||
@@ -0,0 +1,282 @@
|
||||
import {
|
||||
IFilterMeta,
|
||||
IPaginationMeta,
|
||||
ISaleInvoice,
|
||||
ISaleInvoiceCreateDTO,
|
||||
ISaleInvoiceEditDTO,
|
||||
ISaleInvoiceSmsDetails,
|
||||
ISaleInvoiceSmsDetailsDTO,
|
||||
ISaleInvoiceWriteoffDTO,
|
||||
ISalesInvoicesFilter,
|
||||
ISystemUser,
|
||||
ITenantUser,
|
||||
InvoiceNotificationType,
|
||||
} from '@/interfaces';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { CreateSaleInvoice } from './CreateSaleInvoice';
|
||||
import { DeleteSaleInvoice } from './DeleteSaleInvoice';
|
||||
import { GetSaleInvoice } from './GetSaleInvoice';
|
||||
import { EditSaleInvoice } from './EditSaleInvoice';
|
||||
import { GetSaleInvoices } from './GetSaleInvoices';
|
||||
import { DeliverSaleInvoice } from './DeliverSaleInvoice';
|
||||
import { GetSaleInvoicesPayable } from './GetSaleInvoicesPayable';
|
||||
import { WriteoffSaleInvoice } from './WriteoffSaleInvoice';
|
||||
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
||||
import { GetInvoicePaymentsService } from './GetInvoicePaymentsService';
|
||||
import { SaleInvoiceNotifyBySms } from './SaleInvoiceNotifyBySms';
|
||||
|
||||
@Service()
|
||||
export class SaleInvoiceApplication {
|
||||
@Inject()
|
||||
private createSaleInvoiceService: CreateSaleInvoice;
|
||||
|
||||
@Inject()
|
||||
private deleteSaleInvoiceService: DeleteSaleInvoice;
|
||||
|
||||
@Inject()
|
||||
private getSaleInvoiceService: GetSaleInvoice;
|
||||
|
||||
@Inject()
|
||||
private getSaleInvoicesService: GetSaleInvoices;
|
||||
|
||||
@Inject()
|
||||
private editSaleInvoiceService: EditSaleInvoice;
|
||||
|
||||
@Inject()
|
||||
private deliverSaleInvoiceService: DeliverSaleInvoice;
|
||||
|
||||
@Inject()
|
||||
private getReceivableSaleInvoicesService: GetSaleInvoicesPayable;
|
||||
|
||||
@Inject()
|
||||
private writeoffInvoiceService: WriteoffSaleInvoice;
|
||||
|
||||
@Inject()
|
||||
private getInvoicePaymentsService: GetInvoicePaymentsService;
|
||||
|
||||
@Inject()
|
||||
private pdfSaleInvoiceService: SaleInvoicePdf;
|
||||
|
||||
@Inject()
|
||||
private invoiceSms: SaleInvoiceNotifyBySms;
|
||||
|
||||
/**
|
||||
* Creates a new sale invoice with associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoiceCreateDTO} saleInvoiceDTO
|
||||
* @param {ITenantUser} authorizedUser
|
||||
* @returns {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public createSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO,
|
||||
authorizedUser: ITenantUser
|
||||
): Promise<ISaleInvoice> {
|
||||
return this.createSaleInvoiceService.createSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
authorizedUser
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the given sale invoice with associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {ISaleInvoiceEditDTO} saleInvoiceDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public editSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
) {
|
||||
return this.editSaleInvoiceService.editSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
saleInvoiceDTO,
|
||||
authorizedUser
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given sale invoice with given associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<void> {
|
||||
return this.deleteSaleInvoiceService.deleteSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
authorizedUser
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given sale invoice details.
|
||||
* @param {number} tenantId
|
||||
* @param {ISalesInvoicesFilter} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
public getSaleInvoices(
|
||||
tenantId: number,
|
||||
filterDTO: ISalesInvoicesFilter
|
||||
): Promise<{
|
||||
salesInvoices: ISaleInvoice[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
return this.getSaleInvoicesService.getSaleInvoices(tenantId, filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves sale invoice details.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} saleInvoiceId -
|
||||
* @param {ISystemUser} authorizedUser -
|
||||
* @return {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public getSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
authorizedUser: ISystemUser
|
||||
) {
|
||||
return this.getSaleInvoiceService.getSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
authorizedUser
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given sale invoice as delivered.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns {}
|
||||
*/
|
||||
public deliverSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
authorizedUser: ISystemUser
|
||||
) {
|
||||
return this.deliverSaleInvoiceService.deliverSaleInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
authorizedUser
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the receivable sale invoices of the given customer.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @returns
|
||||
*/
|
||||
public getReceivableSaleInvoices(tenantId: number, customerId?: number) {
|
||||
return this.getReceivableSaleInvoicesService.getPayableInvoices(
|
||||
tenantId,
|
||||
customerId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes-off the sale invoice on bad debt expense account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {ISaleInvoiceWriteoffDTO} writeoffDTO
|
||||
* @return {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public writeOff = async (
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
writeoffDTO: ISaleInvoiceWriteoffDTO
|
||||
): Promise<ISaleInvoice> => {
|
||||
return this.writeoffInvoiceService.writeOff(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
writeoffDTO
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancels the written-off sale invoice.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @returns {Promise<ISaleInvoice>}
|
||||
*/
|
||||
public cancelWrittenoff = (
|
||||
tenantId: number,
|
||||
saleInvoiceId: number
|
||||
): Promise<ISaleInvoice> => {
|
||||
return this.writeoffInvoiceService.cancelWrittenoff(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the invoice assocaited payments transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} invoiceId - Invoice id.
|
||||
*/
|
||||
public getInvoicePayments = async (tenantId: number, invoiceId: number) => {
|
||||
return this.getInvoicePaymentsService.getInvoicePayments(
|
||||
tenantId,
|
||||
invoiceId
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId ]
|
||||
* @param saleInvoice
|
||||
* @returns
|
||||
*/
|
||||
public saleInvoicePdf(tenantId: number, saleInvoice) {
|
||||
return this.pdfSaleInvoiceService.saleInvoicePdf(tenantId, saleInvoice);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {InvoiceNotificationType} invoiceNotificationType
|
||||
*/
|
||||
public notifySaleInvoiceBySms = async (
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
invoiceNotificationType: InvoiceNotificationType
|
||||
) => {
|
||||
return this.invoiceSms.notifyBySms(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
invoiceNotificationType
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the SMS details of the given invoice.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} saleInvoiceId - Sale invoice id.
|
||||
*/
|
||||
public getSaleInvoiceSmsDetails = async (
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
invoiceSmsDetailsDTO: ISaleInvoiceSmsDetailsDTO
|
||||
): Promise<ISaleInvoiceSmsDetails> => {
|
||||
return this.invoiceSms.smsDetails(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
invoiceSmsDetailsDTO
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -3,27 +3,22 @@ import { chain } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Knex } from 'knex';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
IInventoryCostLotsGLEntriesWriteEvent,
|
||||
IInventoryTransaction,
|
||||
} from '@/interfaces';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { SaleInvoiceCostGLEntries } from './Invoices/SaleInvoiceCostGLEntries';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export default class SaleInvoicesCost {
|
||||
export class SaleInvoicesCost {
|
||||
@Inject()
|
||||
private inventoryService: InventoryService;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private costGLEntries: SaleInvoiceCostGLEntries;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@@ -122,8 +117,8 @@ export default class SaleInvoicesCost {
|
||||
|
||||
/**
|
||||
* Writes cost GL entries from the inventory cost lots.
|
||||
* @param {number} tenantId -
|
||||
* @param {Date} startingDate -
|
||||
* @param {number} tenantId -
|
||||
* @param {Date} startingDate -
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public writeCostLotsGLEntries = (tenantId: number, startingDate: Date) => {
|
||||
@@ -10,29 +10,24 @@ import {
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import events from '@/subscribers/events';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
|
||||
import JournalPosterService from './JournalPosterService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
const ERRORS = {
|
||||
SALE_INVOICE_ALREADY_WRITTEN_OFF: 'SALE_INVOICE_ALREADY_WRITTEN_OFF',
|
||||
SALE_INVOICE_NOT_WRITTEN_OFF: 'SALE_INVOICE_NOT_WRITTEN_OFF',
|
||||
};
|
||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export default class SaleInvoiceWriteoff {
|
||||
export class WriteoffSaleInvoice {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
journalService: JournalPosterService;
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
private validators: CommandSaleInvoiceValidators;
|
||||
|
||||
/**
|
||||
* Writes-off the sale invoice on bad debt expense account.
|
||||
@@ -48,16 +43,15 @@ export default class SaleInvoiceWriteoff {
|
||||
): Promise<ISaleInvoice> => {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate the sale invoice existance.
|
||||
// Retrieve the sale invoice or throw not found service error.
|
||||
const saleInvoice = await SaleInvoice.query()
|
||||
.findById(saleInvoiceId)
|
||||
.throwIfNotFound();
|
||||
const saleInvoice = await SaleInvoice.query().findById(saleInvoiceId);
|
||||
|
||||
// Validates the given invoice existance.
|
||||
this.validators.validateInvoiceExistance(saleInvoice);
|
||||
|
||||
// Validate the sale invoice whether already written-off.
|
||||
this.validateSaleInvoiceAlreadyWrittenoff(saleInvoice);
|
||||
|
||||
// Saves the invoice write-off transaction with associated transactions
|
||||
// Saves the invoice write-off transaction with associated transactions
|
||||
// under unit-of-work envirmenet.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
const eventPayload = {
|
||||
@@ -105,15 +99,16 @@ export default class SaleInvoiceWriteoff {
|
||||
|
||||
// Validate the sale invoice existance.
|
||||
// Retrieve the sale invoice or throw not found service error.
|
||||
const saleInvoice = await SaleInvoice.query()
|
||||
.findById(saleInvoiceId)
|
||||
.throwIfNotFound();
|
||||
const saleInvoice = await SaleInvoice.query().findById(saleInvoiceId);
|
||||
|
||||
// Validate the sale invoice existance.
|
||||
this.validators.validateInvoiceExistance(saleInvoice);
|
||||
|
||||
// Validate the sale invoice whether already written-off.
|
||||
this.validateSaleInvoiceNotWrittenoff(saleInvoice);
|
||||
|
||||
// Cancels the invoice written-off and removes the associated transactions.
|
||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleInvoiceWrittenoffCancel` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.saleInvoice.onWrittenoffCancel,
|
||||
@@ -11,8 +11,11 @@ export const ERRORS = {
|
||||
'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES',
|
||||
SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED',
|
||||
CUSTOMER_HAS_SALES_INVOICES: 'CUSTOMER_HAS_SALES_INVOICES',
|
||||
SALE_INVOICE_HAS_APPLIED_TO_CREDIT_NOTES: 'SALE_INVOICE_HAS_APPLIED_TO_CREDIT_NOTES',
|
||||
PAYMENT_ACCOUNT_CURRENCY_INVALID: 'PAYMENT_ACCOUNT_CURRENCY_INVALID'
|
||||
SALE_INVOICE_HAS_APPLIED_TO_CREDIT_NOTES:
|
||||
'SALE_INVOICE_HAS_APPLIED_TO_CREDIT_NOTES',
|
||||
PAYMENT_ACCOUNT_CURRENCY_INVALID: 'PAYMENT_ACCOUNT_CURRENCY_INVALID',
|
||||
SALE_INVOICE_ALREADY_WRITTEN_OFF: 'SALE_INVOICE_ALREADY_WRITTEN_OFF',
|
||||
SALE_INVOICE_NOT_WRITTEN_OFF: 'SALE_INVOICE_NOT_WRITTEN_OFF',
|
||||
};
|
||||
|
||||
export const DEFAULT_VIEW_COLUMNS = [];
|
||||
@@ -0,0 +1,136 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ICustomer,
|
||||
IPaymentReceiveCreateDTO,
|
||||
IPaymentReceiveCreatedPayload,
|
||||
IPaymentReceiveCreatingPayload,
|
||||
ISystemUser,
|
||||
} from '@/interfaces';
|
||||
import { PaymentReceiveValidators } from './PaymentReceiveValidators';
|
||||
import events from '@/subscribers/events';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { PaymentReceiveDTOTransformer } from './PaymentReceiveDTOTransformer';
|
||||
import { TenantMetadata } from '@/system/models';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
@Service()
|
||||
export class CreatePaymentReceive {
|
||||
@Inject()
|
||||
private validators: PaymentReceiveValidators;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private transformer: PaymentReceiveDTOTransformer;
|
||||
|
||||
/**
|
||||
* Creates a new payment receive and store it to the storage
|
||||
* with associated invoices payment and journal transactions.
|
||||
* @async
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {IPaymentReceive} paymentReceive
|
||||
*/
|
||||
public async createPaymentReceive(
|
||||
tenantId: number,
|
||||
paymentReceiveDTO: IPaymentReceiveCreateDTO,
|
||||
authorizedUser: ISystemUser
|
||||
) {
|
||||
const { PaymentReceive, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
|
||||
|
||||
// Validate customer existance.
|
||||
const paymentCustomer = await Contact.query()
|
||||
.modify('customer')
|
||||
.findById(paymentReceiveDTO.customerId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transformes the payment receive DTO to model.
|
||||
const paymentReceiveObj = await this.transformCreateDTOToModel(
|
||||
tenantId,
|
||||
paymentCustomer,
|
||||
paymentReceiveDTO
|
||||
);
|
||||
// Validate payment receive number uniquiness.
|
||||
await this.validators.validatePaymentReceiveNoExistance(
|
||||
tenantId,
|
||||
paymentReceiveObj.paymentReceiveNo
|
||||
);
|
||||
// Validate the deposit account existance and type.
|
||||
const depositAccount = await this.validators.getDepositAccountOrThrowError(
|
||||
tenantId,
|
||||
paymentReceiveDTO.depositAccountId
|
||||
);
|
||||
// Validate payment receive invoices IDs existance.
|
||||
await this.validators.validateInvoicesIDsExistance(
|
||||
tenantId,
|
||||
paymentReceiveDTO.customerId,
|
||||
paymentReceiveDTO.entries
|
||||
);
|
||||
// Validate invoice payment amount.
|
||||
await this.validators.validateInvoicesPaymentsAmount(
|
||||
tenantId,
|
||||
paymentReceiveDTO.entries
|
||||
);
|
||||
// Validates the payment account currency code.
|
||||
this.validators.validatePaymentAccountCurrency(
|
||||
depositAccount.currencyCode,
|
||||
paymentCustomer.currencyCode,
|
||||
tenantMeta.baseCurrency
|
||||
);
|
||||
// Creates a payment receive transaction under UOW envirment.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onPaymentReceiveCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.paymentReceive.onCreating, {
|
||||
trx,
|
||||
paymentReceiveDTO,
|
||||
tenantId,
|
||||
} as IPaymentReceiveCreatingPayload);
|
||||
|
||||
// Inserts the payment receive transaction.
|
||||
const paymentReceive = await PaymentReceive.query(
|
||||
trx
|
||||
).insertGraphAndFetch({
|
||||
...paymentReceiveObj,
|
||||
});
|
||||
// Triggers `onPaymentReceiveCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.paymentReceive.onCreated, {
|
||||
tenantId,
|
||||
paymentReceive,
|
||||
paymentReceiveId: paymentReceive.id,
|
||||
authorizedUser,
|
||||
trx,
|
||||
} as IPaymentReceiveCreatedPayload);
|
||||
|
||||
return paymentReceive;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the create payment receive DTO.
|
||||
* @param {number} tenantId
|
||||
* @param {ICustomer} customer
|
||||
* @param {IPaymentReceiveCreateDTO} paymentReceiveDTO
|
||||
* @returns
|
||||
*/
|
||||
private transformCreateDTOToModel = async (
|
||||
tenantId: number,
|
||||
customer: ICustomer,
|
||||
paymentReceiveDTO: IPaymentReceiveCreateDTO
|
||||
) => {
|
||||
return this.transformer.transformPaymentReceiveDTOToModel(
|
||||
tenantId,
|
||||
customer,
|
||||
paymentReceiveDTO
|
||||
);
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user