feat: tax rates on sale invoice service

This commit is contained in:
Ahmed Bouhuolia
2023-08-11 21:08:30 +02:00
parent d6f56568a3
commit a7644e6481
10 changed files with 235 additions and 12 deletions

View File

@@ -169,8 +169,9 @@ export default class SaleInvoicesController extends BaseController {
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
check('project_id').optional({ nullable: true }).isNumeric().toInt(),
check('entries').exists().isArray({ min: 1 }),
check('is_tax_exclusive').optional().isBoolean().toBoolean(),
check('entries').exists().isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
@@ -183,6 +184,8 @@ export default class SaleInvoicesController extends BaseController {
.optional({ nullable: true })
.trim()
.escape(),
check('entries.*.tax_code').optional({ nullable: true }).trim().escape(),
check('entries.*.tax_rate').optional().isNumeric().toFloat(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
@@ -756,6 +759,11 @@ export default class SaleInvoicesController extends BaseController {
],
});
}
if (error.errorType === 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND') {
return res.boom.badRequest(null, {
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND', code: 5000 }],
});
}
}
next(error);
}

View File

@@ -1,14 +1,25 @@
exports.up = (knex) => {
return knex.schema.createTable('tax_rates', (table) => {
table.increments();
table.string('name');
table.string('code');
table.decimal('rate');
table.boolean('is_non_recoverable');
table.boolean('is_compound');
table.integer('status');
table.timestamps();
});
return knex.schema
.createTable('tax_rates', (table) => {
table.increments();
table.string('name');
table.string('code');
table.decimal('rate');
table.boolean('is_non_recoverable');
table.boolean('is_compound');
table.integer('status');
table.timestamps();
})
.table('items_entries', (table) => {
table.boolean(['is_tax_exclusive']);
table.string('tax_code');
table.decimal('tax_rate');
table.decimal('tax_amount_withheld')
})
.table('sales_invoices', (table) => {
table.boolean(['is_tax_exclusive']);
table.decimal('tax_amount_withheld')
});
};
exports.down = (knex) => {

View File

@@ -32,6 +32,9 @@ export interface IItemEntry {
projectRefType?: ProjectLinkRefType;
projectRefInvoicedAmount?: number;
taxCode: string;
taxRate: number;
item?: IItem;
allocatedCostEntries?: IBillLandedCostEntry[];
@@ -46,6 +49,9 @@ export interface IItemEntryDTO {
projectRefId?: number;
projectRefType?: ProjectLinkRefType;
projectRefInvoicedAmount?: number;
taxCode: string;
taxRate: number;
}
export enum ProjectLinkRefType {

View File

@@ -44,6 +44,7 @@ export interface ISaleInvoiceDTO {
exchangeRate?: number;
invoiceMessage: string;
termsConditions: string;
isTaxExclusive: boolean;
entries: IItemEntryDTO[];
delivered: boolean;

View File

@@ -80,6 +80,7 @@ import { ProjectBillableTasksSubscriber } from '@/services/Projects/Projects/Pro
import { ProjectBillableExpensesSubscriber } from '@/services/Projects/Projects/ProjectBillableExpenseSubscriber';
import { ProjectBillableBillSubscriber } from '@/services/Projects/Projects/ProjectBillableBillSubscriber';
import { SyncActualTimeTaskSubscriber } from '@/services/Projects/Times/SyncActualTimeTaskSubscriber';
import { SaleInvoiceTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber';
export default () => {
return new EventPublisher();
@@ -187,5 +188,6 @@ export const susbcribers = () => {
ProjectBillableTasksSubscriber,
ProjectBillableExpensesSubscriber,
ProjectBillableBillSubscriber,
SaleInvoiceTaxRateValidateSubscriber
];
};

View File

@@ -1,8 +1,9 @@
import { ServiceError } from '@/exceptions';
import { Inject, Service } from 'typedi';
import HasTenancyService from '../Tenancy/TenancyService';
import { ITaxRate } from '@/interfaces';
import { IItemEntryDTO, ITaxRate } from '@/interfaces';
import { ERRORS } from './constants';
import { difference } from 'lodash';
@Service()
export class CommandTaxRatesValidators {
@@ -33,4 +34,27 @@ export class CommandTaxRatesValidators {
throw new ServiceError(ERRORS.TAX_CODE_NOT_UNIQUE);
}
}
/**
* Validates the tax codes of the given item entries DTO.
* @param {number} tenantId
* @param {IItemEntryDTO[]} itemEntriesDTO
*/
public async validateItemEntriesTaxCode(
tenantId: number,
itemEntriesDTO: IItemEntryDTO[]
) {
const { TaxRate } = this.tenancy.models(tenantId);
const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxCode);
const taxCodes = filteredTaxEntries.map((e) => e.taxCode);
const foundTaxCodes = await TaxRate.query().whereIn('code', taxCodes);
const foundCodes = foundTaxCodes.map((tax) => tax.code);
const notFoundTaxCodes = difference(taxCodes, foundCodes);
if (notFoundTaxCodes.length > 0) {
throw new ServiceError(ERRORS.ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND);
}
}
}

View File

@@ -1,4 +1,5 @@
export const ERRORS = {
TAX_RATE_NOT_FOUND: 'TAX_RATE_NOT_FOUND',
TAX_CODE_NOT_UNIQUE: 'TAX_CODE_NOT_UNIQUE',
ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND: 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND',
};

View File

@@ -0,0 +1,58 @@
import { Inject, Service } from 'typedi';
import {
ISaleEstimateCreatingPayload,
ISaleEstimateEditingPayload,
ISaleInvoiceCreatingPaylaod,
ISaleInvoiceEditingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { CommandTaxRatesValidators } from '../CommandTaxRatesValidators';
@Service()
export class SaleEstimateTaxRateValidateSubscriber {
@Inject()
private taxRateDTOValidator: CommandTaxRatesValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.saleEstimate.onCreating,
this.validateSaleEstimateEntriesTaxCodeExistanceOnCreating
);
bus.subscribe(
events.saleEstimate.onEditing,
this.validateSaleEstimateEntriesTaxCodeExistanceOnEditing
);
return bus;
}
/**
* Validate invoice entries tax rate code existance.
* @param {ISaleInvoiceCreatingPaylaod}
*/
private validateSaleEstimateEntriesTaxCodeExistanceOnCreating = async ({
estimateDTO,
tenantId,
}: ISaleEstimateCreatingPayload) => {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
tenantId,
estimateDTO.entries
);
};
/**
*
* @param {ISaleInvoiceEditingPayload}
*/
private validateSaleEstimateEntriesTaxCodeExistanceOnEditing = async ({
tenantId,
estimateDTO,
}: ISaleEstimateEditingPayload) => {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
tenantId,
estimateDTO.entries
);
};
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import {
ISaleInvoiceCreatingPaylaod,
ISaleInvoiceEditingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { CommandTaxRatesValidators } from '../CommandTaxRatesValidators';
@Service()
export class SaleInvoiceTaxRateValidateSubscriber {
@Inject()
private taxRateDTOValidator: CommandTaxRatesValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.saleInvoice.onCreating,
this.validateSaleInvoiceEntriesTaxCodeExistanceOnCreating
);
bus.subscribe(
events.saleInvoice.onEditing,
this.validateSaleInvoiceEntriesTaxCodeExistanceOnEditing
);
return bus;
}
/**
* Validate invoice entries tax rate code existance.
* @param {ISaleInvoiceCreatingPaylaod}
*/
private validateSaleInvoiceEntriesTaxCodeExistanceOnCreating = async ({
saleInvoiceDTO,
tenantId,
}: ISaleInvoiceCreatingPaylaod) => {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
tenantId,
saleInvoiceDTO.entries
);
};
/**
*
* @param {ISaleInvoiceEditingPayload}
*/
private validateSaleInvoiceEntriesTaxCodeExistanceOnEditing = async ({
tenantId,
saleInvoiceDTO,
}: ISaleInvoiceEditingPayload) => {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
tenantId,
saleInvoiceDTO.entries
);
};
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import {
ISaleReceiptCreatingPayload,
ISaleReceiptEditingPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { CommandTaxRatesValidators } from '../CommandTaxRatesValidators';
@Service()
export class SaleReceiptTaxRateValidateSubscriber {
@Inject()
private taxRateDTOValidator: CommandTaxRatesValidators;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.saleReceipt.onCreating,
this.validateSaleReceiptEntriesTaxCodeExistanceOnCreating
);
bus.subscribe(
events.saleReceipt.onEditing,
this.validateSaleReceiptEntriesTaxCodeExistanceOnEditing
);
return bus;
}
/**
* Validate receipt entries tax rate code existance.
* @param {ISaleInvoiceCreatingPaylaod}
*/
private validateSaleReceiptEntriesTaxCodeExistanceOnCreating = async ({
tenantId,
saleReceiptDTO,
}: ISaleReceiptCreatingPayload) => {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
tenantId,
saleReceiptDTO.entries
);
};
/**
*
* @param {ISaleInvoiceEditingPayload}
*/
private validateSaleReceiptEntriesTaxCodeExistanceOnEditing = async ({
tenantId,
saleReceiptDTO,
}: ISaleReceiptEditingPayload) => {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
tenantId,
saleReceiptDTO.entries
);
};
}