fix(server): pull-request nodes

This commit is contained in:
Ahmed Bouhuolia
2023-09-22 15:23:33 +02:00
parent eaf72d1608
commit ce41845bd7
23 changed files with 153 additions and 215 deletions

View File

@@ -6,15 +6,11 @@ import {
SalesTaxLiabilitySummaryTotal,
} from '@/interfaces/SalesTaxLiabilitySummary';
import { tableRowMapper } from '@/utils';
import { ITableColumn, ITableColumnAccessor, ITableRow } from '@/interfaces';
import { ITableColumn, ITableRow } from '@/interfaces';
import { FinancialSheetStructure } from '../FinancialSheetStructure';
import { FinancialTable } from '../FinancialTable';
import AgingReport from '../AgingSummary/AgingReport';
enum IROW_TYPE {
TaxRate = 'TaxRate',
Total = 'Total',
}
import { IROW_TYPE } from './_constants';
export class SalesTaxLiabilitySummaryTable extends R.compose(
FinancialSheetStructure,

View File

@@ -0,0 +1,4 @@
export enum IROW_TYPE {
TaxRate = 'TaxRate',
Total = 'Total',
}

View File

@@ -70,20 +70,25 @@ export class CommandSaleInvoiceDTOTransformer {
isInclusiveTax: saleInvoiceDTO.isInclusiveTax,
...entry,
}));
const entries = await composeAsync(
const asyncEntries = await composeAsync(
// Associate tax rate id from tax code to entries.
this.taxDTOTransformer.assocTaxRateIdFromCodeToEntries(tenantId),
// Sets default cost and sell account to invoice items entries.
this.itemsEntriesService.setItemsEntriesDefaultAccounts(tenantId)
)(initialEntries);
const entries = R.compose(
// Remove tax code from entries.
R.map(R.omit(['taxCode']))
)(asyncEntries);
const initialDTO = {
...formatDateFields(
omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']),
['invoiceDate', 'dueDate']
),
// Avoid rewrite the deliver date in edit mode when already published.
amount,
balance: amount,
currencyCode: customer.currencyCode,
exchangeRate: saleInvoiceDTO.exchangeRate || 1,
...(saleInvoiceDTO.delivered &&

View File

@@ -1,12 +1,12 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import {
ITaxRateActivatedPayload,
ITaxRateActivatingPayload,
} from '@/interfaces';
import { Inject, Service } from 'typedi';
import UnitOfWork from '../UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '../Tenancy/TenancyService';
import { Knex } from 'knex';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidators';
import events from '@/subscribers/events';
@@ -25,7 +25,7 @@ export class ActivateTaxRateService {
private validators: CommandTaxRatesValidators;
/**
* Edits the given tax rate.
* Activates the given tax rate.
* @param {number} tenantId
* @param {number} taxRateId
* @param {IEditTaxRateDTO} taxRateEditDTO

View File

@@ -59,6 +59,7 @@ export class CommandTaxRatesValidators {
* Validates the tax codes of the given item entries DTO.
* @param {number} tenantId
* @param {IItemEntryDTO[]} itemEntriesDTO
* @throws {ServiceError}
*/
public async validateItemEntriesTaxCode(
tenantId: number,
@@ -92,17 +93,17 @@ export class CommandTaxRatesValidators {
tenantId: number,
itemEntriesDTO: IItemEntryDTO[]
) {
const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxCodeId);
const taxCodes = filteredTaxEntries.map((e) => e.taxCodeId);
const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxRateId);
const taxRatesIds = filteredTaxEntries.map((e) => e.taxRateId);
// Can't validate if there is no tax codes.
if (taxCodes.length === 0) return;
if (taxRatesIds.length === 0) return;
const { TaxRate } = this.tenancy.models(tenantId);
const foundTaxCodes = await TaxRate.query().whereIn('id', taxCodes);
const foundCodes = foundTaxCodes.map((tax) => tax.id);
const foundTaxCodes = await TaxRate.query().whereIn('id', taxRatesIds);
const foundTaxRatesIds = foundTaxCodes.map((tax) => tax.id);
const notFoundTaxCodes = difference(taxCodes, foundCodes);
const notFoundTaxCodes = difference(taxRatesIds, foundTaxRatesIds);
if (notFoundTaxCodes.length > 0) {
throw new ServiceError(ERRORS.ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND);

View File

@@ -49,7 +49,9 @@ export class CreateTaxRate {
trx,
} as ITaxRateCreatingPayload);
const taxRate = await TaxRate.query(trx).insert({ ...createTaxRateDTO });
const taxRate = await TaxRate.query(trx).insertAndFetch({
...createTaxRateDTO,
});
// Triggers `onTaxRateCreated` event.
await this.eventPublisher.emitAsync(events.taxRates.onCreated, {

View File

@@ -2,6 +2,7 @@ import { sumBy, chain, keyBy } from 'lodash';
import { IItemEntry, ITaxTransaction } from '@/interfaces';
import HasTenancyService from '../Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
@Service()
export class WriteTaxTransactionsItemEntries {
@@ -15,24 +16,51 @@ export class WriteTaxTransactionsItemEntries {
*/
public async writeTaxTransactionsFromItemEntries(
tenantId: number,
itemEntries: IItemEntry[]
itemEntries: IItemEntry[],
trx?: Knex.Transaction
) {
const { TaxRateTransaction, TaxRate } = this.tenancy.models(tenantId);
const aggregatedEntries = this.aggregateItemEntriesByTaxCode(itemEntries);
const entriesTaxRateIds = aggregatedEntries.map((entry) => entry.taxRateId);
const taxRates = await TaxRate.query().whereIn('id', entriesTaxRateIds);
const taxRates = await TaxRate.query(trx).whereIn('id', entriesTaxRateIds);
const taxRatesById = keyBy(taxRates, 'id');
const taxTransactions = aggregatedEntries.map((entry) => ({
taxRateId: entry.taxRateId,
referenceType: entry.referenceType,
referenceId: entry.referenceId,
taxAmount: entry.taxRate || taxRatesById[entry.taxRateId]?.rate,
rate: entry.taxRate || taxRatesById[entry.taxRateId]?.rate,
})) as ITaxTransaction[];
await TaxRateTransaction.query().upsertGraph(taxTransactions);
await TaxRateTransaction.query(trx).upsertGraph(taxTransactions);
}
/**
* Rewrites the tax rate transactions from the given item entries.
* @param {number} tenantId
* @param {IItemEntry[]} itemEntries
* @param {string} referenceType
* @param {number} referenceId
* @param {Knex.Transaction} trx
*/
public async rewriteTaxRateTransactionsFromItemEntries(
tenantId: number,
itemEntries: IItemEntry[],
referenceType: string,
referenceId: number,
trx?: Knex.Transaction
) {
await Promise.all([
this.removeTaxTransactionsFromItemEntries(
tenantId,
referenceId,
referenceType,
trx
),
this.writeTaxTransactionsFromItemEntries(tenantId, itemEntries, trx),
]);
}
/**
@@ -59,11 +87,12 @@ export class WriteTaxTransactionsItemEntries {
public async removeTaxTransactionsFromItemEntries(
tenantId: number,
referenceId: number,
referenceType: string
referenceType: string,
trx?: Knex.Transaction
) {
const { TaxRateTransaction } = this.tenancy.models(tenantId);
await TaxRateTransaction.query()
await TaxRateTransaction.query(trx)
.where({ referenceType, referenceId })
.delete();
}

View File

@@ -1,58 +0,0 @@
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

@@ -27,6 +27,10 @@ export class SaleInvoiceTaxRateValidateSubscriber {
events.saleInvoice.onEditing,
this.validateSaleInvoiceEntriesTaxCodeExistanceOnEditing
);
bus.subscribe(
events.saleInvoice.onEditing,
this.validateSaleInvoiceEntriesTaxIdExistanceOnEditing
);
return bus;
}
@@ -71,4 +75,18 @@ export class SaleInvoiceTaxRateValidateSubscriber {
saleInvoiceDTO.entries
);
};
/**
* Validates the invoice entries tax rate id existance when editing.
* @param {ISaleInvoiceEditingPayload} payload -
*/
private validateSaleInvoiceEntriesTaxIdExistanceOnEditing = async ({
tenantId,
saleInvoiceDTO,
}: ISaleInvoiceEditingPayload) => {
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
tenantId,
saleInvoiceDTO.entries
);
};
}

View File

@@ -1,56 +0,0 @@
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
);
};
}

View File

@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
import {
ISaleInvoiceCreatedPayload,
ISaleInvoiceDeletedPayload,
ISaleInvoiceEditedPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries';
@@ -19,6 +20,10 @@ export class WriteInvoiceTaxTransactionsSubscriber {
events.saleInvoice.onCreated,
this.writeInvoiceTaxTransactionsOnCreated
);
bus.subscribe(
events.saleInvoice.onEdited,
this.rewriteInvoiceTaxTransactionsOnEdited
);
bus.subscribe(
events.saleInvoice.onDelete,
this.removeInvoiceTaxTransactionsOnDeleted
@@ -27,16 +32,36 @@ export class WriteInvoiceTaxTransactionsSubscriber {
}
/**
* Validate receipt entries tax rate code existance.
* Writes the invoice tax transactions on invoice created.
* @param {ISaleInvoiceCreatingPaylaod}
*/
private writeInvoiceTaxTransactionsOnCreated = async ({
tenantId,
saleInvoice,
trx
}: ISaleInvoiceCreatedPayload) => {
await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries(
tenantId,
saleInvoice.entries
saleInvoice.entries,
trx
);
};
/**
* Rewrites the invoice tax transactions on invoice edited.
* @param {ISaleInvoiceEditedPayload} payload -
*/
private rewriteInvoiceTaxTransactionsOnEdited = async ({
tenantId,
saleInvoice,
trx,
}: ISaleInvoiceEditedPayload) => {
await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries(
tenantId,
saleInvoice.entries,
'SaleInvoice',
saleInvoice.id,
trx
);
};
@@ -47,11 +72,13 @@ export class WriteInvoiceTaxTransactionsSubscriber {
private removeInvoiceTaxTransactionsOnDeleted = async ({
tenantId,
oldSaleInvoice,
trx
}: ISaleInvoiceDeletedPayload) => {
await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries(
tenantId,
oldSaleInvoice.id,
'SaleInvoice'
'SaleInvoice',
trx
);
};
}