.
This commit is contained in:
parent
dc6382dd41
commit
5bc9920538
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
File diff suppressed because one or more lines are too long
@ -208,7 +208,7 @@ function validateModuleServices(moduleName: string, services: Record<string, unk
|
|||||||
const fullName = `${moduleName}:${serviceKey}`;
|
const fullName = `${moduleName}:${serviceKey}`;
|
||||||
|
|
||||||
if (serviceApi === undefined) {
|
if (serviceApi === undefined) {
|
||||||
throw new Error(`Service "${fullName}" is undefined`);
|
throw new Error(`Service "${fullName}" is undefined (validateModuleServices)`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const services: Record<string, unknown> = {};
|
|||||||
* Registra un objeto de servicio (API) bajo un nombre.
|
* Registra un objeto de servicio (API) bajo un nombre.
|
||||||
*/
|
*/
|
||||||
export function registerService(name: string, api: unknown) {
|
export function registerService(name: string, api: unknown) {
|
||||||
|
console.debug(`Registering service: ${name}`);
|
||||||
if (services[name]) {
|
if (services[name]) {
|
||||||
throw new Error(`❌ Servicio "${name}" ya fue registrado.`);
|
throw new Error(`❌ Servicio "${name}" ya fue registrado.`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
export * from "./payment-terms";
|
export * from "./payment-terms";
|
||||||
export * from "./services";
|
|
||||||
export * from "./tax-definitions";
|
export * from "./tax-definitions";
|
||||||
export * from "./tax-regimes";
|
export * from "./tax-regimes";
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
export * from "./di";
|
export * from "./di";
|
||||||
export * from "./mappers";
|
export * from "./mappers";
|
||||||
export * from "./models";
|
export * from "./models";
|
||||||
|
export * from "./public";
|
||||||
export * from "./repositories";
|
export * from "./repositories";
|
||||||
export * from "./services";
|
export * from "./services";
|
||||||
export * from "./snapshot-builders";
|
export * from "./snapshot-builders";
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './payment-method-not-active.error';
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { DomainError, UniqueID } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
export class PaymentMethodNotActiveError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_METHOD_NOT_ACTIVE" as const;
|
||||||
|
|
||||||
|
public constructor(public readonly id: UniqueID) {
|
||||||
|
super(`Payment method with id ${id.toString()} is not active`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./mappers";
|
||||||
|
export * from "./models/";
|
||||||
|
export * from "./services";
|
||||||
|
export * from "./services/";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './payment-method-public-model.mapper';
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
import type {
|
||||||
|
PaymentMethodPublicModel
|
||||||
|
} from "../models";
|
||||||
|
import { PaymentMethod } from '../../../../domain';
|
||||||
|
|
||||||
|
export class PaymentMethodPublicModelMapper {
|
||||||
|
public toPublicModel(paymentMethod: PaymentMethod): PaymentMethodPublicModel {
|
||||||
|
return {
|
||||||
|
id: paymentMethod.id,
|
||||||
|
companyId: paymentMethod.companyId,
|
||||||
|
name: paymentMethod.name.toPrimitive(),
|
||||||
|
description: paymentMethod.description.map((value) => value.toPrimitive()),
|
||||||
|
isActive: paymentMethod.isActive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './payment-method-public.model';
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
export interface PaymentMethodPublicModel {
|
||||||
|
id: UniqueID;
|
||||||
|
companyId: UniqueID;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
description: Maybe<string>;
|
||||||
|
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './payment-method-public-finder.interface';
|
||||||
|
export * from './payment-method-public-finder';
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentMethodPublicModel } from "../models/payment-method-public.model";
|
||||||
|
|
||||||
|
export interface FindPaymentMethodByIdInCompanyParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
transaction?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPaymentMethodPublicFinder {
|
||||||
|
existsByIdInCompany(
|
||||||
|
params: FindPaymentMethodByIdInCompanyParams,
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
getByIdInCompany(
|
||||||
|
params: FindPaymentMethodByIdInCompanyParams,
|
||||||
|
): Promise<Result<PaymentMethodPublicModel, Error>>;
|
||||||
|
|
||||||
|
findByIdInCompany(
|
||||||
|
params: FindPaymentMethodByIdInCompanyParams,
|
||||||
|
): Promise<Result<Maybe<PaymentMethodPublicModel>, Error>>;
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IPaymentMethodRepository } from "../../repositories";
|
||||||
|
import type { PaymentMethodPublicModelMapper } from "../mappers/payment-method-public-model.mapper";
|
||||||
|
import type { PaymentMethodPublicModel } from "../models/payment-method-public.model";
|
||||||
|
import type {
|
||||||
|
FindPaymentMethodByIdInCompanyParams,
|
||||||
|
IPaymentMethodPublicFinder,
|
||||||
|
} from "./payment-method-public-finder.interface";
|
||||||
|
|
||||||
|
export class PaymentMethodPublicFinder implements IPaymentMethodPublicFinder {
|
||||||
|
public constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
repository: IPaymentMethodRepository;
|
||||||
|
mapper: PaymentMethodPublicModelMapper;
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async existsByIdInCompany(
|
||||||
|
params: FindPaymentMethodByIdInCompanyParams,
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
const result = await this.deps.repository.existsByIdInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(result.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getByIdInCompany(
|
||||||
|
params: FindPaymentMethodByIdInCompanyParams,
|
||||||
|
): Promise<Result<PaymentMethodPublicModel, Error>> {
|
||||||
|
const result = await this.deps.repository.getByIdInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(this.deps.mapper.toPublicModel(result.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findByIdInCompany(
|
||||||
|
params: FindPaymentMethodByIdInCompanyParams,
|
||||||
|
): Promise<Result<Maybe<PaymentMethodPublicModel>, Error>> {
|
||||||
|
const result = await this.deps.repository.findByIdInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(
|
||||||
|
result.data.match(
|
||||||
|
(paymentMethod) => Maybe.some(this.deps.mapper.toPublicModel(paymentMethod)),
|
||||||
|
() => Maybe.none<PaymentMethodPublicModel>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
import type { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { PaymentMethod } from "../../../domain";
|
import type { PaymentMethod } from "../../../domain";
|
||||||
import type { PaymentMethodSummary } from "../models";
|
import type { PaymentMethodSummary } from "../models";
|
||||||
@ -18,6 +18,11 @@ export interface IPaymentMethodRepository {
|
|||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<PaymentMethod, Error>>;
|
): Promise<Result<PaymentMethod, Error>>;
|
||||||
|
findByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<Maybe<PaymentMethod>, Error>>;
|
||||||
findByCriteriaInCompany(
|
findByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
|
|||||||
@ -9,13 +9,13 @@ import type { IPaymentMethodRepository } from "../repositories";
|
|||||||
export interface IPaymentMethodFinder {
|
export interface IPaymentMethodFinder {
|
||||||
findPaymentMethodById(
|
findPaymentMethodById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
paymentMethodId: UniqueID,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<PaymentMethod, Error>>;
|
): Promise<Result<PaymentMethod, Error>>;
|
||||||
|
|
||||||
paymentmethodExists(
|
paymentmethodExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
paymentMethodId: UniqueID,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
@ -31,18 +31,18 @@ export class PaymentMethodFinder implements IPaymentMethodFinder {
|
|||||||
|
|
||||||
async findPaymentMethodById(
|
async findPaymentMethodById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
paymentmethodId: UniqueID,
|
paymentMethodId: UniqueID,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<PaymentMethod, Error>> {
|
): Promise<Result<PaymentMethod, Error>> {
|
||||||
return this.repository.getByIdInCompany(companyId, paymentmethodId, transaction);
|
return this.repository.getByIdInCompany(companyId, paymentMethodId, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
async paymentmethodExists(
|
async paymentmethodExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
paymentmethodId: UniqueID,
|
paymentMethodId: UniqueID,
|
||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.existsByIdInCompany(companyId, paymentmethodId, transaction);
|
return this.repository.existsByIdInCompany(companyId, paymentMethodId, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findPaymentMethodsByCriteria(
|
async findPaymentMethodsByCriteria(
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
|
|
||||||
import type { IPaymentMethodPublicServices } from "../payment-methods";
|
|
||||||
import type { IPaymentTermPublicServices } from "../payment-terms";
|
|
||||||
import type { ITaxRegimePublicServices } from "../tax-regimes";
|
|
||||||
|
|
||||||
export type { IPaymentMethodPublicServices } from "../payment-methods";
|
|
||||||
export type { IPaymentTermPublicServices } from "../payment-terms";
|
|
||||||
export type { ITaxRegimePublicServices } from "../tax-regimes";
|
|
||||||
|
|
||||||
export interface ICatalogServicesContext {
|
|
||||||
transaction: unknown;
|
|
||||||
companyId: UniqueID;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICatalogPublicServices {
|
|
||||||
paymentMethods: IPaymentMethodPublicServices;
|
|
||||||
paymentTerms: IPaymentTermPublicServices;
|
|
||||||
taxRegimes: ITaxRegimePublicServices;
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from './catalog-public-services.interface';
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
export * from "./mappers";
|
export * from "./mappers";
|
||||||
export * from "./models";
|
export * from "./models";
|
||||||
|
export * from "./public";
|
||||||
export * from "./repositories";
|
export * from "./repositories";
|
||||||
export * from "./services";
|
export * from "./services";
|
||||||
export * from "./snapshot-builders";
|
export * from "./snapshot-builders";
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './tax-definitions-not-found.error';
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { DomainError } from "@repo/rdx-ddd";
|
||||||
|
import type { Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
export class TaxDefinitionsNotFoundError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITIONS_NOT_FOUND" as const;
|
||||||
|
|
||||||
|
public constructor(public readonly missingCodes: Collection<string>) {
|
||||||
|
super(`Tax definitions not found for codes: ${missingCodes.getAll().join(", ")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./errors/";
|
||||||
|
export * from "./mappers";
|
||||||
|
export * from "./models/";
|
||||||
|
export * from "./services";
|
||||||
|
export * from "./services/";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './tax-definition-public-model.mapper';
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
import { Collection, Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxDefinition } from "../../../../domain/tax-definitions";
|
||||||
|
import type {
|
||||||
|
TaxDefinitionPublicCalculationBehavior,
|
||||||
|
TaxDefinitionPublicFamily,
|
||||||
|
TaxDefinitionPublicModel,
|
||||||
|
TaxDefinitionPublicScope,
|
||||||
|
} from "../models/tax-definition-public.model";
|
||||||
|
|
||||||
|
export class TaxDefinitionPublicModelMapper {
|
||||||
|
public toPublicModel(taxDefinition: TaxDefinition): TaxDefinitionPublicModel {
|
||||||
|
return {
|
||||||
|
id: taxDefinition.id,
|
||||||
|
companyId: Maybe.some(taxDefinition.companyId),
|
||||||
|
code: taxDefinition.code.toPrimitive(),
|
||||||
|
name: taxDefinition.name.toPrimitive(),
|
||||||
|
description: taxDefinition.description.map((value) => value.toPrimitive()),
|
||||||
|
rate: taxDefinition.rate,
|
||||||
|
taxFamily: this.mapTaxFamily(taxDefinition.taxFamily.toPrimitive()),
|
||||||
|
calculationBehavior: this.mapCalculationBehavior(
|
||||||
|
taxDefinition.calculationBehavior.toPrimitive()
|
||||||
|
),
|
||||||
|
taxScope: this.mapTaxScope(taxDefinition.taxScope.toPrimitive()),
|
||||||
|
jurisdictionCountryCode: taxDefinition.jurisdictionCountryCode,
|
||||||
|
jurisdictionRegionCode: taxDefinition.jurisdictionRegionCode,
|
||||||
|
invoiceNote: taxDefinition.invoiceNote.map((value) => value.toPrimitive()),
|
||||||
|
allowedSurchargeCodes: new Collection(
|
||||||
|
taxDefinition.allowedSurchargeCodes.match(
|
||||||
|
(codes) => codes.map((code) => code.toPrimitive()),
|
||||||
|
() => []
|
||||||
|
)
|
||||||
|
),
|
||||||
|
isSystem: taxDefinition.isSystem,
|
||||||
|
isActive: taxDefinition.isActive,
|
||||||
|
validFrom: taxDefinition.validFrom,
|
||||||
|
validTo: taxDefinition.validTo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapTaxFamily(value: string): TaxDefinitionPublicFamily {
|
||||||
|
switch (value) {
|
||||||
|
case "iva":
|
||||||
|
case "igic":
|
||||||
|
case "ipsi":
|
||||||
|
return value;
|
||||||
|
case "equivalence_surcharge":
|
||||||
|
return "surcharge";
|
||||||
|
case "withholding":
|
||||||
|
return "retention";
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported tax family for public model: ${value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapCalculationBehavior(value: string): TaxDefinitionPublicCalculationBehavior {
|
||||||
|
switch (value) {
|
||||||
|
case "additive":
|
||||||
|
case "subtractive":
|
||||||
|
return value;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported calculation behavior for public model: ${value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapTaxScope(value: string): TaxDefinitionPublicScope {
|
||||||
|
switch (value) {
|
||||||
|
case "domestic":
|
||||||
|
return "sales";
|
||||||
|
case "intra_eu":
|
||||||
|
case "import":
|
||||||
|
return "purchases";
|
||||||
|
case "export":
|
||||||
|
case "international":
|
||||||
|
return "both";
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported tax scope for public model: ${value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './tax-definition-public.model';
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import type {
|
||||||
|
CountryCode,
|
||||||
|
CountryRegionCode, Percentage, UniqueID,
|
||||||
|
UtcDate
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import type { Collection, Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Familia fiscal pública expuesta por `catalogs`.
|
||||||
|
*
|
||||||
|
* No se expone el Value Object interno de dominio para evitar acoplar
|
||||||
|
* consumidores externos a las invariantes privadas de `tax-definitions`.
|
||||||
|
*/
|
||||||
|
export type TaxDefinitionPublicFamily = "iva" | "igic" | "ipsi" | "surcharge" | "retention";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comportamiento de cálculo público de un impuesto.
|
||||||
|
*
|
||||||
|
* `additive` suma al total.
|
||||||
|
* `subtractive` resta del total, por ejemplo retenciones.
|
||||||
|
*/
|
||||||
|
export type TaxDefinitionPublicCalculationBehavior = "additive" | "subtractive";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ámbito público de aplicación fiscal.
|
||||||
|
*/
|
||||||
|
export type TaxDefinitionPublicScope = "sales" | "purchases" | "both";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modelo público de lectura expuesto por el módulo `catalogs`.
|
||||||
|
*
|
||||||
|
* Es un contrato backend entre módulos, no un DTO HTTP y no una entidad
|
||||||
|
* de dominio. Puede usar Value Objects comunes del ERP, pero no debe
|
||||||
|
* exponer Value Objects internos de `tax-definitions`.
|
||||||
|
*/
|
||||||
|
export interface TaxDefinitionPublicModel {
|
||||||
|
id: UniqueID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `none` si la definición es global/sistema.
|
||||||
|
* `some(companyId)` si la definición está sobrescrita para una empresa.
|
||||||
|
*/
|
||||||
|
companyId: Maybe<UniqueID>;
|
||||||
|
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: Maybe<string>;
|
||||||
|
|
||||||
|
rate: Percentage;
|
||||||
|
|
||||||
|
taxFamily: TaxDefinitionPublicFamily;
|
||||||
|
calculationBehavior: TaxDefinitionPublicCalculationBehavior;
|
||||||
|
taxScope: TaxDefinitionPublicScope;
|
||||||
|
|
||||||
|
jurisdictionCountryCode: CountryCode;
|
||||||
|
jurisdictionRegionCode: Maybe<CountryRegionCode>;
|
||||||
|
|
||||||
|
invoiceNote: Maybe<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Códigos de recargo compatibles con esta definición.
|
||||||
|
*
|
||||||
|
* Colección vacía => no permite recargos.
|
||||||
|
*/
|
||||||
|
allowedSurchargeCodes: Collection<string>;
|
||||||
|
|
||||||
|
isSystem: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
validFrom: Maybe<UtcDate>;
|
||||||
|
validTo: Maybe<UtcDate>;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './tax-definition-public-finder.interface';
|
||||||
|
export * from './tax-definition-public-finder';
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import type { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
|
import type { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxDefinitionPublicModel } from "../models/tax-definition-public.model";
|
||||||
|
|
||||||
|
export interface FindActiveTaxDefinitionByCodeParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
code: string;
|
||||||
|
atDate: UtcDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FindActiveTaxDefinitionsByCodesParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
codes: Collection<string>;
|
||||||
|
atDate: UtcDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITaxDefinitionPublicFinder {
|
||||||
|
findActiveByCode(
|
||||||
|
params: FindActiveTaxDefinitionByCodeParams
|
||||||
|
): Promise<Result<Maybe<TaxDefinitionPublicModel>, Error>>;
|
||||||
|
|
||||||
|
findActiveByCodes(
|
||||||
|
params: FindActiveTaxDefinitionsByCodesParams
|
||||||
|
): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>>;
|
||||||
|
|
||||||
|
ensureActiveByCodes(
|
||||||
|
params: FindActiveTaxDefinitionsByCodesParams
|
||||||
|
): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>>;
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionRepository } from "../../repositories";
|
||||||
|
import { TaxDefinitionsNotFoundError } from "../errors/tax-definitions-not-found.error";
|
||||||
|
import type { TaxDefinitionPublicModelMapper } from "../mappers/tax-definition-public-model.mapper";
|
||||||
|
import type {
|
||||||
|
FindActiveTaxDefinitionByCodeParams,
|
||||||
|
FindActiveTaxDefinitionsByCodesParams,
|
||||||
|
ITaxDefinitionPublicFinder,
|
||||||
|
} from "./tax-definition-public-finder.interface";
|
||||||
|
import { TaxDefinitionPublicModel } from '../models';
|
||||||
|
|
||||||
|
export class TaxDefinitionPublicFinder implements ITaxDefinitionPublicFinder {
|
||||||
|
public constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
repository: ITaxDefinitionRepository;
|
||||||
|
mapper: TaxDefinitionPublicModelMapper;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async findActiveByCode(params: FindActiveTaxDefinitionByCodeParams): Promise<Result<Maybe<TaxDefinitionPublicModel>, Error>> {
|
||||||
|
const result = await this.deps.repository.findActiveByCodeInCompany(params);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(result.data.map((taxDefinition) => this.deps.mapper.toPublicModel(taxDefinition)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findActiveByCodes(params: FindActiveTaxDefinitionsByCodesParams): Promise<Result<Collection<TaxDefinitionPublicModel>, Error>> {
|
||||||
|
const result = await this.deps.repository.findActiveByCodesInCompany(params);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new Collection(result.data.map((taxDefinition) => this.deps.mapper.toPublicModel(taxDefinition))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ensureActiveByCodes(params: FindActiveTaxDefinitionsByCodesParams):Promise<Result<Collection<TaxDefinitionPublicModel>, Error>> {
|
||||||
|
const result = await this.findActiveByCodes(params);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestedCodes = Array.from(
|
||||||
|
new Set(params.codes.getAll().map((code) => this.normalizeCode(code)))
|
||||||
|
);
|
||||||
|
const foundCodes = new Set(result.data.getAll().map((taxDefinition) => taxDefinition.code));
|
||||||
|
const missingCodes = requestedCodes.filter((code) => !foundCodes.has(code));
|
||||||
|
|
||||||
|
if (missingCodes.length > 0) {
|
||||||
|
return Result.fail(new TaxDefinitionsNotFoundError(new Collection(missingCodes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeCode(code: string): string {
|
||||||
|
return code.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
import type { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { TaxDefinition, TaxDefinitionCode } from "../../../domain";
|
import type { TaxDefinition, TaxDefinitionCode } from "../../../domain";
|
||||||
import type { TaxDefinitionSummary } from "../../tax-definitions/models";
|
import type { TaxDefinitionSummary } from "../../tax-definitions/models";
|
||||||
@ -32,6 +32,20 @@ export interface ITaxDefinitionRepository {
|
|||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<TaxDefinition, Error>>;
|
): Promise<Result<TaxDefinition, Error>>;
|
||||||
|
|
||||||
|
findActiveByCodeInCompany(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
code: string;
|
||||||
|
atDate: UtcDate;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<Maybe<TaxDefinition>, Error>>;
|
||||||
|
|
||||||
|
findActiveByCodesInCompany(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
codes: Collection<string>;
|
||||||
|
atDate: UtcDate;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<Collection<TaxDefinition>, Error>>;
|
||||||
|
|
||||||
findByCriteriaInCompany(
|
findByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export * from "./tax-definition-creator.service";
|
export * from "./tax-definition-creator.service";
|
||||||
export * from "./tax-definition-deleter.service";
|
export * from "./tax-definition-deleter.service";
|
||||||
export * from "./tax-definition-finder.service";
|
export * from "./tax-definition-finder.service";
|
||||||
|
export * from "./tax-definition-public-services";
|
||||||
export * from "./tax-definition-status-changer.service";
|
export * from "./tax-definition-status-changer.service";
|
||||||
export * from "./tax-definition-updater.service";
|
export * from "./tax-definition-updater.service";
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
import type { ITaxDefinitionPublicFinder } from "../public";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionPublicServices {
|
||||||
|
finder: ITaxDefinitionPublicFinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildTaxDefinitionsPublicServices = (
|
||||||
|
finder: ITaxDefinitionPublicFinder
|
||||||
|
): ITaxDefinitionPublicServices => ({
|
||||||
|
finder,
|
||||||
|
});
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import type { TaxDefinitionDetailDTO } from "@erp/catalogs/common";
|
|
||||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||||
|
|
||||||
import type { TaxDefinitionDetail } from "../../models";
|
import type { TaxDefinitionDetail } from "../../models";
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export * from "./mappers";
|
export * from "./mappers";
|
||||||
export * from "./models";
|
export * from "./models";
|
||||||
|
export * from "./public";
|
||||||
export * from "./repositories";
|
export * from "./repositories";
|
||||||
export * from "./services";
|
export * from "./services";
|
||||||
export * from "./snapshot-builders";
|
export * from "./snapshot-builders";
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './tax-regime-not-found.error';
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { DomainError } from "@repo/rdx-ddd";
|
||||||
|
import type { Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
export class TaxRegimesNotFoundError extends DomainError {
|
||||||
|
public readonly code = "TAX_REGIME_NOT_FOUND" as const;
|
||||||
|
|
||||||
|
public constructor(public readonly missingCodes: Collection<string>) {
|
||||||
|
super(`Tax regime not found for codes: ${missingCodes.getAll().join(", ")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
export { TaxRegimesNotFoundError } from "./errors/";
|
||||||
|
export { TaxRegimePublicModelMapper } from "./mappers";
|
||||||
|
export type {
|
||||||
|
TaxRegimePublicCalculationBehavior,
|
||||||
|
TaxRegimePublicFamily,
|
||||||
|
TaxRegimePublicModel,
|
||||||
|
TaxRegimePublicScope,
|
||||||
|
} from "./models/";
|
||||||
|
export type {
|
||||||
|
FindActiveTaxRegimeByCodeParams,
|
||||||
|
FindActiveTaxRegimesByCodesParams,
|
||||||
|
ITaxRegimePublicFinder,
|
||||||
|
} from "./services";
|
||||||
|
export { TaxRegimePublicFinder } from "./services/";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './tax-regime-public-model.mapper';
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
import type { TaxRegime } from "../../../../domain/tax-regimes";
|
||||||
|
import type {
|
||||||
|
TaxRegimePublicModel
|
||||||
|
} from "../models/tax-regime-public.model";
|
||||||
|
|
||||||
|
export class TaxRegimePublicModelMapper {
|
||||||
|
public toPublicModel(TaxRegime: TaxRegime): TaxRegimePublicModel {
|
||||||
|
return {
|
||||||
|
id: TaxRegime.id,
|
||||||
|
companyId: TaxRegime.companyId,
|
||||||
|
code: TaxRegime.code.toPrimitive(),
|
||||||
|
description: TaxRegime.description.toPrimitive(),
|
||||||
|
isSystem: TaxRegime.isSystem,
|
||||||
|
isActive: TaxRegime.isActive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './tax-regime-public.model';
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modelo público de lectura expuesto por el módulo `catalogs`.
|
||||||
|
*
|
||||||
|
* Es un contrato backend entre módulos, no un DTO HTTP y no una entidad
|
||||||
|
* de dominio. Puede usar Value Objects comunes del ERP, pero no debe
|
||||||
|
* exponer Value Objects internos de `tax-regimes`.
|
||||||
|
*/
|
||||||
|
export interface TaxRegimePublicModel {
|
||||||
|
id: UniqueID;
|
||||||
|
companyId: UniqueID;
|
||||||
|
|
||||||
|
code: string;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
isSystem: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './tax-regime-public-finder.interface';
|
||||||
|
export * from './tax-regime-public-finder';
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxRegimePublicModel } from "../models";
|
||||||
|
|
||||||
|
export interface FindTaxRegimeByCodeInCompanyParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
code: string;
|
||||||
|
transaction?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITaxRegimePublicFinder {
|
||||||
|
existsByCodeInCompany(
|
||||||
|
params: FindTaxRegimeByCodeInCompanyParams,
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
getByCodeInCompany(
|
||||||
|
params: FindTaxRegimeByCodeInCompanyParams,
|
||||||
|
): Promise<Result<TaxRegimePublicModel, Error>>;
|
||||||
|
|
||||||
|
findByCodeInCompany(
|
||||||
|
params: FindTaxRegimeByCodeInCompanyParams,
|
||||||
|
): Promise<Result<Maybe<TaxRegimePublicModel>, Error>>;
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { TaxRegimeCode } from "../../../../domain/tax-regimes";
|
||||||
|
import type { ITaxRegimeRepository } from "../../repositories";
|
||||||
|
import type { TaxRegimePublicModelMapper } from "../mappers/tax-regime-public-model.mapper";
|
||||||
|
import type { TaxRegimePublicModel } from "../models";
|
||||||
|
import type {
|
||||||
|
FindTaxRegimeByCodeInCompanyParams,
|
||||||
|
ITaxRegimePublicFinder,
|
||||||
|
} from "./tax-regime-public-finder.interface";
|
||||||
|
|
||||||
|
export class TaxRegimePublicFinder implements ITaxRegimePublicFinder {
|
||||||
|
public constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
repository: ITaxRegimeRepository;
|
||||||
|
mapper: TaxRegimePublicModelMapper;
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async existsByCodeInCompany(
|
||||||
|
params: FindTaxRegimeByCodeInCompanyParams,
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
const code = TaxRegimeCode.create(params.code);
|
||||||
|
|
||||||
|
if (code.isFailure) {
|
||||||
|
return Result.fail(code.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.deps.repository.existsByCodeInCompany(
|
||||||
|
params.companyId,
|
||||||
|
code.data,
|
||||||
|
params.transaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(result.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getByCodeInCompany(
|
||||||
|
params: FindTaxRegimeByCodeInCompanyParams,
|
||||||
|
): Promise<Result<TaxRegimePublicModel, Error>> {
|
||||||
|
const code = TaxRegimeCode.create(params.code);
|
||||||
|
|
||||||
|
if (code.isFailure) {
|
||||||
|
return Result.fail(code.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.deps.repository.getByCodeInCompany(
|
||||||
|
params.companyId,
|
||||||
|
code.data,
|
||||||
|
params.transaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(this.deps.mapper.toPublicModel(result.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Busca un régimen fiscal por código dentro de una empresa.
|
||||||
|
*
|
||||||
|
* No falla si el régimen fiscal no existe. En ese caso devuelve `Maybe.none()`.
|
||||||
|
* Usar `getByCodeInCompany` cuando la ausencia deba tratarse como error.
|
||||||
|
*/
|
||||||
|
public async findByCodeInCompany(
|
||||||
|
params: FindTaxRegimeByCodeInCompanyParams,
|
||||||
|
): Promise<Result<Maybe<TaxRegimePublicModel>, Error>> {
|
||||||
|
const code = TaxRegimeCode.create(params.code);
|
||||||
|
|
||||||
|
if (code.isFailure) {
|
||||||
|
return Result.fail(code.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.deps.repository.findByCodeInCompany(
|
||||||
|
params.companyId,
|
||||||
|
code.data,
|
||||||
|
params.transaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(
|
||||||
|
result.data.match(
|
||||||
|
(taxRegime) => Maybe.some(this.deps.mapper.toPublicModel(taxRegime)),
|
||||||
|
() => Maybe.none<TaxRegimePublicModel>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
import type { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { TaxRegime, TaxRegimeCode } from "../../../domain";
|
import type { TaxRegime, TaxRegimeCode } from "../../../domain";
|
||||||
import type { TaxRegimeSummary } from "../models";
|
import type { TaxRegimeSummary } from "../models";
|
||||||
@ -20,6 +20,12 @@ export interface ITaxRegimeRepository {
|
|||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
existsByCodeInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
code: TaxRegimeCode,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
getByIdInCompany(
|
getByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
@ -32,6 +38,12 @@ export interface ITaxRegimeRepository {
|
|||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<TaxRegime, Error>>;
|
): Promise<Result<TaxRegime, Error>>;
|
||||||
|
|
||||||
|
findByCodeInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
code: TaxRegimeCode,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<Maybe<TaxRegime>, Error>>;
|
||||||
|
|
||||||
findByCriteriaInCompany(
|
findByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
|
|||||||
@ -61,24 +61,6 @@ export const isInvalidTaxDefinitionCalculationBehaviorError = (
|
|||||||
): e is InvalidTaxDefinitionCalculationBehaviorError =>
|
): e is InvalidTaxDefinitionCalculationBehaviorError =>
|
||||||
e instanceof InvalidTaxDefinitionCalculationBehaviorError;
|
e instanceof InvalidTaxDefinitionCalculationBehaviorError;
|
||||||
|
|
||||||
export class InvalidTaxDefinitionJurisdictionCountryCodeError extends DomainError {
|
|
||||||
public readonly code = "TAX_DEFINITION_INVALID_JURISDICTION_COUNTRY_CODE" as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isInvalidTaxDefinitionJurisdictionCountryCodeError = (
|
|
||||||
e: unknown
|
|
||||||
): e is InvalidTaxDefinitionJurisdictionCountryCodeError =>
|
|
||||||
e instanceof InvalidTaxDefinitionJurisdictionCountryCodeError;
|
|
||||||
|
|
||||||
export class InvalidTaxDefinitionJurisdictionRegionCodeError extends DomainError {
|
|
||||||
public readonly code = "TAX_DEFINITION_INVALID_JURISDICTION_REGION_CODE" as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isInvalidTaxDefinitionJurisdictionRegionCodeError = (
|
|
||||||
e: unknown
|
|
||||||
): e is InvalidTaxDefinitionJurisdictionRegionCodeError =>
|
|
||||||
e instanceof InvalidTaxDefinitionJurisdictionRegionCodeError;
|
|
||||||
|
|
||||||
export class InvalidTaxDefinitionScopeError extends DomainError {
|
export class InvalidTaxDefinitionScopeError extends DomainError {
|
||||||
public readonly code = "TAX_DEFINITION_INVALID_SCOPE" as const;
|
public readonly code = "TAX_DEFINITION_INVALID_SCOPE" as const;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
export * from "./calculation-behavior";
|
export * from "./calculation-behavior";
|
||||||
export * from "./errors";
|
export * from "./errors";
|
||||||
export * from "./jurisdiction-country-code";
|
|
||||||
export * from "./jurisdiction-region-code";
|
|
||||||
export * from "./tax-definition.aggregate";
|
export * from "./tax-definition.aggregate";
|
||||||
export * from "./tax-definition-code";
|
export * from "./tax-definition-code";
|
||||||
export * from "./tax-definition-name";
|
export * from "./tax-definition-name";
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import { InvalidTaxDefinitionJurisdictionCountryCodeError } from "./errors";
|
|
||||||
|
|
||||||
export class TaxJurisdictionCountryCode {
|
|
||||||
private constructor(private readonly value: string) {}
|
|
||||||
|
|
||||||
public static create(code: string): Result<TaxJurisdictionCountryCode, Error> {
|
|
||||||
const trimmed = code?.trim() ?? "";
|
|
||||||
if (trimmed.length !== 2) {
|
|
||||||
return Result.fail(
|
|
||||||
new InvalidTaxDefinitionJurisdictionCountryCodeError("Country code must be 2 letters")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalized = trimmed.toUpperCase();
|
|
||||||
const regex = /^[A-Z]{2}$/;
|
|
||||||
if (!regex.test(normalized)) {
|
|
||||||
return Result.fail(
|
|
||||||
new InvalidTaxDefinitionJurisdictionCountryCodeError(
|
|
||||||
"Country code must be 2 uppercase letters"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: We allow two-letter codes including 'EU' to support union-level definitions like reverse charge.
|
|
||||||
return Result.ok(new TaxJurisdictionCountryCode(normalized));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static fromPersistence(code: string): TaxJurisdictionCountryCode {
|
|
||||||
return new TaxJurisdictionCountryCode(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public toPrimitive(): string {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString(): string {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import { InvalidTaxDefinitionJurisdictionRegionCodeError } from "./errors";
|
|
||||||
|
|
||||||
export class TaxJurisdictionRegionCode {
|
|
||||||
private constructor(private readonly value: string) {}
|
|
||||||
|
|
||||||
public static create(code: string): Result<TaxJurisdictionRegionCode, Error> {
|
|
||||||
const trimmed = code?.trim() ?? "";
|
|
||||||
if (trimmed.length === 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new InvalidTaxDefinitionJurisdictionRegionCodeError("Region code cannot be empty")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalized = trimmed.toUpperCase();
|
|
||||||
|
|
||||||
// basic pattern: CC-... (we won't validate full ISO-3166-2 list)
|
|
||||||
const regex = /^[A-Z]{2}-[A-Z0-9-]+$/;
|
|
||||||
if (!regex.test(normalized)) {
|
|
||||||
return Result.fail(
|
|
||||||
new InvalidTaxDefinitionJurisdictionRegionCodeError(
|
|
||||||
"Region code must follow ISO-3166-2 pattern COUNTRY-REGION"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(new TaxJurisdictionRegionCode(normalized));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static fromPersistence(code: string): TaxJurisdictionRegionCode {
|
|
||||||
return new TaxJurisdictionRegionCode(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public toPrimitive(): string {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString(): string {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +1,18 @@
|
|||||||
import { AggregateRoot, type TextValue, type UniqueID, type UtcDate } from "@repo/rdx-ddd";
|
import {
|
||||||
|
AggregateRoot,
|
||||||
|
type CountryCode,
|
||||||
|
type CountryRegionCode,
|
||||||
|
type TextValue,
|
||||||
|
type UniqueID,
|
||||||
|
type UtcDate,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { TaxCalculationBehavior } from "./calculation-behavior";
|
import type { TaxCalculationBehavior } from "./calculation-behavior";
|
||||||
import {
|
import {
|
||||||
InvalidTaxDefinitionAllowedSurchargeCodesError,
|
InvalidTaxDefinitionAllowedSurchargeCodesError,
|
||||||
InvalidTaxDefinitionJurisdictionRegionCodeError,
|
|
||||||
InvalidTaxDefinitionValidityPeriodError,
|
InvalidTaxDefinitionValidityPeriodError,
|
||||||
} from "./errors";
|
} from "./errors";
|
||||||
import type { TaxJurisdictionCountryCode } from "./jurisdiction-country-code";
|
|
||||||
import type { TaxJurisdictionRegionCode } from "./jurisdiction-region-code";
|
|
||||||
import type { TaxDefinitionCode } from "./tax-definition-code";
|
import type { TaxDefinitionCode } from "./tax-definition-code";
|
||||||
import type { TaxDefinitionName } from "./tax-definition-name";
|
import type { TaxDefinitionName } from "./tax-definition-name";
|
||||||
import type { TaxFamily } from "./tax-family";
|
import type { TaxFamily } from "./tax-family";
|
||||||
@ -23,8 +27,8 @@ export interface ITaxDefinitionCreateProps {
|
|||||||
rate: TaxRate;
|
rate: TaxRate;
|
||||||
taxFamily: TaxFamily;
|
taxFamily: TaxFamily;
|
||||||
calculationBehavior: TaxCalculationBehavior;
|
calculationBehavior: TaxCalculationBehavior;
|
||||||
jurisdictionCountryCode: TaxJurisdictionCountryCode;
|
jurisdictionCountryCode: CountryCode;
|
||||||
jurisdictionRegionCode: Maybe<TaxJurisdictionRegionCode>;
|
jurisdictionRegionCode: Maybe<CountryRegionCode>;
|
||||||
taxScope: TaxScope;
|
taxScope: TaxScope;
|
||||||
invoiceNote: Maybe<TextValue>; // Texto fiscal que aparece en el documento
|
invoiceNote: Maybe<TextValue>; // Texto fiscal que aparece en el documento
|
||||||
allowedSurchargeCodes: Maybe<TaxDefinitionCode[]>;
|
allowedSurchargeCodes: Maybe<TaxDefinitionCode[]>;
|
||||||
@ -146,19 +150,6 @@ export class TaxDefinition extends AggregateRoot<TaxDefinitionInternalProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// region coherence with country
|
|
||||||
if (props.jurisdictionRegionCode && props.jurisdictionRegionCode.isSome()) {
|
|
||||||
const region = props.jurisdictionRegionCode.unwrap().toPrimitive();
|
|
||||||
const country = props.jurisdictionCountryCode.toPrimitive();
|
|
||||||
if (!region.startsWith(`${country}-`)) {
|
|
||||||
return Result.fail(
|
|
||||||
new InvalidTaxDefinitionJurisdictionRegionCodeError(
|
|
||||||
"Region code must start with country code"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,11 +189,11 @@ export class TaxDefinition extends AggregateRoot<TaxDefinitionInternalProps> {
|
|||||||
return this.props.calculationBehavior;
|
return this.props.calculationBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get jurisdictionCountryCode(): TaxJurisdictionCountryCode {
|
public get jurisdictionCountryCode(): CountryCode {
|
||||||
return this.props.jurisdictionCountryCode;
|
return this.props.jurisdictionCountryCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get jurisdictionRegionCode(): Maybe<TaxJurisdictionRegionCode> {
|
public get jurisdictionRegionCode(): Maybe<CountryRegionCode> {
|
||||||
return this.props.jurisdictionRegionCode;
|
return this.props.jurisdictionRegionCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import {
|
|||||||
paymentMethodsRouter,
|
paymentMethodsRouter,
|
||||||
paymentTermModels,
|
paymentTermModels,
|
||||||
paymentTermsRouter,
|
paymentTermsRouter,
|
||||||
|
taxDefinitionModels,
|
||||||
|
taxDefinitionsRouter,
|
||||||
taxRegimeModels,
|
taxRegimeModels,
|
||||||
taxRegimesRouter,
|
taxRegimesRouter,
|
||||||
} from "./infrastructure";
|
} from "./infrastructure";
|
||||||
@ -13,10 +15,14 @@ import {
|
|||||||
buildCatalogsPublicServices,
|
buildCatalogsPublicServices,
|
||||||
} from "./infrastructure/di/catalogs.di";
|
} from "./infrastructure/di/catalogs.di";
|
||||||
|
|
||||||
export * from "./application/services/catalog-public-services.interface"; // <- exportamos la interfaz de los servicios públicos para que otros módulos puedan usarla en sus dependencias
|
export * from "./application/payment-methods/public";
|
||||||
|
export * from "./application/tax-definitions/public";
|
||||||
|
export * from "./application/tax-regimes/public";
|
||||||
|
|
||||||
//export * from "./infrastructure/payment-methods/persistence/sequelize"; <- ???
|
//export * from "./infrastructure/payment-methods/persistence/sequelize"; <- ???
|
||||||
|
|
||||||
|
export type CatalogsPublicServicesType = ReturnType<typeof buildCatalogsPublicServices>;
|
||||||
|
|
||||||
export const catalogsAPIModule: IModuleServer = {
|
export const catalogsAPIModule: IModuleServer = {
|
||||||
name: "catalogs",
|
name: "catalogs",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
@ -44,13 +50,16 @@ export const catalogsAPIModule: IModuleServer = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
// Modelos Sequelize del módulo
|
// Modelos Sequelize del módulo
|
||||||
models: [...paymentMethodModels, ...paymentTermModels, ...taxRegimeModels],
|
models: [
|
||||||
|
...paymentMethodModels,
|
||||||
|
...paymentTermModels,
|
||||||
|
...taxDefinitionModels,
|
||||||
|
...taxRegimeModels,
|
||||||
|
],
|
||||||
|
|
||||||
// Servicios expuestos a otros módulos
|
// Servicios expuestos a otros módulos
|
||||||
services: {
|
services: {
|
||||||
paymentMethods: publicServices.paymentMethods,
|
...publicServices,
|
||||||
paymentTerms: publicServices.paymentTerms,
|
|
||||||
taxRegimes: publicServices.taxRegimes,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Implementación privada del módulo
|
// Implementación privada del módulo
|
||||||
@ -72,6 +81,7 @@ export const catalogsAPIModule: IModuleServer = {
|
|||||||
paymentMethodsRouter(params);
|
paymentMethodsRouter(params);
|
||||||
paymentTermsRouter(params);
|
paymentTermsRouter(params);
|
||||||
taxRegimesRouter(params);
|
taxRegimesRouter(params);
|
||||||
|
taxDefinitionsRouter(params);
|
||||||
|
|
||||||
logger.info("🚀 Catalogs module started", {
|
logger.info("🚀 Catalogs module started", {
|
||||||
label: this.name,
|
label: this.name,
|
||||||
|
|||||||
@ -10,6 +10,11 @@ import {
|
|||||||
buildPaymentTermsDependencies,
|
buildPaymentTermsDependencies,
|
||||||
buildPaymentTermsPublicServices,
|
buildPaymentTermsPublicServices,
|
||||||
} from "../payment-terms/di";
|
} from "../payment-terms/di";
|
||||||
|
import {
|
||||||
|
type TaxDefinitionsInternalDeps,
|
||||||
|
buildTaxDefinitionsDependencies,
|
||||||
|
buildTaxDefinitionsPublicServices,
|
||||||
|
} from "../tax-definitions/di";
|
||||||
import {
|
import {
|
||||||
type TaxRegimesInternalDeps,
|
type TaxRegimesInternalDeps,
|
||||||
buildTaxRegimesDependencies,
|
buildTaxRegimesDependencies,
|
||||||
@ -19,6 +24,7 @@ import {
|
|||||||
export type CatalogsInternalDeps = {
|
export type CatalogsInternalDeps = {
|
||||||
paymentMethods: PaymentMethodsInternalDeps;
|
paymentMethods: PaymentMethodsInternalDeps;
|
||||||
paymentTerms: PaymentTermsInternalDeps;
|
paymentTerms: PaymentTermsInternalDeps;
|
||||||
|
taxDefinitions: TaxDefinitionsInternalDeps;
|
||||||
taxRegimes: TaxRegimesInternalDeps;
|
taxRegimes: TaxRegimesInternalDeps;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,6 +32,7 @@ export const buildCatalogsDependencies = (params: ModuleParams): CatalogsInterna
|
|||||||
return {
|
return {
|
||||||
paymentMethods: buildPaymentMethodsDependencies(params),
|
paymentMethods: buildPaymentMethodsDependencies(params),
|
||||||
paymentTerms: buildPaymentTermsDependencies(params),
|
paymentTerms: buildPaymentTermsDependencies(params),
|
||||||
|
taxDefinitions: buildTaxDefinitionsDependencies(params),
|
||||||
taxRegimes: buildTaxRegimesDependencies(params),
|
taxRegimes: buildTaxRegimesDependencies(params),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -34,6 +41,7 @@ export const buildCatalogsPublicServices = (params: SetupParams, deps: CatalogsI
|
|||||||
return {
|
return {
|
||||||
paymentMethods: buildPaymentMethodsPublicServices(params, deps.paymentMethods),
|
paymentMethods: buildPaymentMethodsPublicServices(params, deps.paymentMethods),
|
||||||
paymentTerms: buildPaymentTermsPublicServices(params, deps.paymentTerms),
|
paymentTerms: buildPaymentTermsPublicServices(params, deps.paymentTerms),
|
||||||
|
taxDefinitions: buildTaxDefinitionsPublicServices(params, deps.taxDefinitions),
|
||||||
taxRegimes: buildTaxRegimesPublicServices(params, deps.taxRegimes),
|
taxRegimes: buildTaxRegimesPublicServices(params, deps.taxRegimes),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { type Collection, Result } from "@repo/rdx-utils";
|
import { type Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
import type { Sequelize, Transaction } from "sequelize";
|
import type { Sequelize, Transaction } from "sequelize";
|
||||||
|
|
||||||
import type { IPaymentMethodRepository } from "../../../../../application";
|
import type { IPaymentMethodRepository } from "../../../../../application";
|
||||||
@ -145,6 +145,35 @@ export class SequelizePaymentMethodRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<Maybe<PaymentMethod>, Error>> {
|
||||||
|
try {
|
||||||
|
const row = await PaymentMethodModel.findOne({
|
||||||
|
where: {
|
||||||
|
id: id.toString(),
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return Result.ok(Maybe.none<PaymentMethod>());
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedResult = this.domainMapper.mapToDomain(row);
|
||||||
|
if (mappedResult.isFailure) {
|
||||||
|
return Result.fail(mappedResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(Maybe.some(mappedResult.data));
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recupera múltiples customers dentro de una empresa según un criterio dinámico (búsqueda, paginación, etc.).
|
* Recupera múltiples customers dentro de una empresa según un criterio dinámico (búsqueda, paginación, etc.).
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export * from "./tax-definition-persistence-mappers.di";
|
export * from "./tax-definition-persistence-mappers.di";
|
||||||
export * from "./tax-definition-repositories.di";
|
export * from "./tax-definition-repositories.di";
|
||||||
|
export * from "./tax-definitions.di";
|
||||||
|
|||||||
@ -0,0 +1,125 @@
|
|||||||
|
import { type ModuleParams, type SetupParams, buildTransactionManager } from "@erp/core/api";
|
||||||
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateTaxDefinitionInputMapper,
|
||||||
|
CreateTaxDefinitionUseCase,
|
||||||
|
DeleteTaxDefinitionByIdUseCase,
|
||||||
|
DisableTaxDefinitionByIdUseCase,
|
||||||
|
EnableTaxDefinitionByIdUseCase,
|
||||||
|
GetTaxDefinitionByIdUseCase,
|
||||||
|
type ITaxDefinitionPublicFinder,
|
||||||
|
type ITaxDefinitionRepository,
|
||||||
|
ListTaxDefinitionsUseCase,
|
||||||
|
TaxDefinitionCreator,
|
||||||
|
TaxDefinitionDeleter,
|
||||||
|
TaxDefinitionFinder,
|
||||||
|
TaxDefinitionFullSnapshotBuilder,
|
||||||
|
TaxDefinitionPublicFinder,
|
||||||
|
TaxDefinitionPublicModelMapper,
|
||||||
|
TaxDefinitionStatusChanger,
|
||||||
|
TaxDefinitionSummarySnapshotBuilder,
|
||||||
|
TaxDefinitionUpdater,
|
||||||
|
UpdateTaxDefinitionByIdInputMapper,
|
||||||
|
UpdateTaxDefinitionByIdUseCase,
|
||||||
|
} from "../../../application";
|
||||||
|
|
||||||
|
import { buildTaxDefinitionPersistenceMappers } from "./tax-definition-persistence-mappers.di";
|
||||||
|
import { buildTaxDefinitionRepository } from "./tax-definition-repositories.di";
|
||||||
|
|
||||||
|
export type TaxDefinitionsInternalDeps = {
|
||||||
|
repository: ITaxDefinitionRepository;
|
||||||
|
useCases: {
|
||||||
|
listTaxDefinitions: () => ListTaxDefinitionsUseCase;
|
||||||
|
getTaxDefinitionById: () => GetTaxDefinitionByIdUseCase;
|
||||||
|
createTaxDefinition: () => CreateTaxDefinitionUseCase;
|
||||||
|
updateTaxDefinitionById: () => UpdateTaxDefinitionByIdUseCase;
|
||||||
|
deleteTaxDefinitionById: () => DeleteTaxDefinitionByIdUseCase;
|
||||||
|
disableTaxDefinitionById: () => DisableTaxDefinitionByIdUseCase;
|
||||||
|
enableTaxDefinitionById: () => EnableTaxDefinitionByIdUseCase;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildTaxDefinitionsDependencies = (
|
||||||
|
params: ModuleParams
|
||||||
|
): TaxDefinitionsInternalDeps => {
|
||||||
|
const { database } = params;
|
||||||
|
|
||||||
|
const transactionManager = buildTransactionManager(database as Sequelize);
|
||||||
|
const persistenceMappers = buildTaxDefinitionPersistenceMappers();
|
||||||
|
|
||||||
|
const repository = buildTaxDefinitionRepository({ database, mappers: persistenceMappers });
|
||||||
|
|
||||||
|
const finder = new TaxDefinitionFinder(repository);
|
||||||
|
const creator = new TaxDefinitionCreator(repository);
|
||||||
|
const updater = new TaxDefinitionUpdater(repository);
|
||||||
|
const deleter = new TaxDefinitionDeleter(repository);
|
||||||
|
const statusChanger = new TaxDefinitionStatusChanger(repository);
|
||||||
|
|
||||||
|
const createInputMapper = new CreateTaxDefinitionInputMapper();
|
||||||
|
const updateInputMapper = new UpdateTaxDefinitionByIdInputMapper();
|
||||||
|
|
||||||
|
const fullSnapshotBuilder = new TaxDefinitionFullSnapshotBuilder();
|
||||||
|
const summarySnapshotBuilder = new TaxDefinitionSummarySnapshotBuilder();
|
||||||
|
|
||||||
|
return {
|
||||||
|
repository,
|
||||||
|
useCases: {
|
||||||
|
listTaxDefinitions: () =>
|
||||||
|
new ListTaxDefinitionsUseCase({
|
||||||
|
repository,
|
||||||
|
summarySnapshotBuilder,
|
||||||
|
}),
|
||||||
|
|
||||||
|
getTaxDefinitionById: () =>
|
||||||
|
new GetTaxDefinitionByIdUseCase({
|
||||||
|
finder,
|
||||||
|
fullSnapshotBuilder,
|
||||||
|
}),
|
||||||
|
|
||||||
|
createTaxDefinition: () =>
|
||||||
|
new CreateTaxDefinitionUseCase({
|
||||||
|
dtoMapper: createInputMapper,
|
||||||
|
creator,
|
||||||
|
fullSnapshotBuilder,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateTaxDefinitionById: () =>
|
||||||
|
new UpdateTaxDefinitionByIdUseCase({
|
||||||
|
dtoMapper: updateInputMapper,
|
||||||
|
updater,
|
||||||
|
fullSnapshotBuilder,
|
||||||
|
}),
|
||||||
|
|
||||||
|
deleteTaxDefinitionById: () =>
|
||||||
|
new DeleteTaxDefinitionByIdUseCase({
|
||||||
|
deleter,
|
||||||
|
}),
|
||||||
|
|
||||||
|
disableTaxDefinitionById: () =>
|
||||||
|
new DisableTaxDefinitionByIdUseCase({
|
||||||
|
statusChanger,
|
||||||
|
}),
|
||||||
|
|
||||||
|
enableTaxDefinitionById: () =>
|
||||||
|
new EnableTaxDefinitionByIdUseCase({
|
||||||
|
statusChanger,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildTaxDefinitionsPublicServices = (
|
||||||
|
_params: SetupParams,
|
||||||
|
deps: TaxDefinitionsInternalDeps
|
||||||
|
): { finder: ITaxDefinitionPublicFinder } => {
|
||||||
|
const mapper = new TaxDefinitionPublicModelMapper();
|
||||||
|
|
||||||
|
return {
|
||||||
|
finder: new TaxDefinitionPublicFinder({
|
||||||
|
repository: deps.repository,
|
||||||
|
mapper,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import type { CreateTaxDefinitionRequestDTO } from "@erp/catalogs/common";
|
||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { CreateTaxDefinitionUseCase } from "../../../../application/tax-definitions";
|
||||||
|
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
|
||||||
|
|
||||||
|
export class CreateTaxDefinitionController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: CreateTaxDefinitionUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = taxDefinitionsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const dto = this.req.body satisfies CreateTaxDefinitionRequestDTO;
|
||||||
|
const result = await this.useCase.execute({ dto, companyId });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data: unknown) => this.created(data),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { DeleteTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
|
||||||
|
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
|
||||||
|
|
||||||
|
export class DeleteTaxDefinitionByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: DeleteTaxDefinitionByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = taxDefinitionsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tax_definition_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute(companyId, tax_definition_id as any);
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data: unknown) => this.ok(data),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { DisableTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
|
||||||
|
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
|
||||||
|
|
||||||
|
export class DisableTaxDefinitionByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: DisableTaxDefinitionByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = taxDefinitionsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tax_definition_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute(companyId, tax_definition_id as any);
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data: unknown) => this.ok(data),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { EnableTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
|
||||||
|
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
|
||||||
|
|
||||||
|
export class EnableTaxDefinitionByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: EnableTaxDefinitionByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = taxDefinitionsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tax_definition_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute(companyId, tax_definition_id as any);
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data: unknown) => this.ok(data),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { GetTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
|
||||||
|
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
|
||||||
|
|
||||||
|
export class GetTaxDefinitionByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: GetTaxDefinitionByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = taxDefinitionsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tax_definition_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute(companyId, tax_definition_id as any);
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data) => this.ok(data),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./create-tax-definition.controller";
|
||||||
|
export * from "./delete-tax-definition-by-id.controller";
|
||||||
|
export * from "./disable-tax-definition-by-id.controller";
|
||||||
|
export * from "./enable-tax-definition-by-id.controller";
|
||||||
|
export * from "./get-tax-definition-by-id.controller";
|
||||||
|
export * from "./list-tax-definitions.controller";
|
||||||
|
export * from "./update-tax-definition-by-id.controller";
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
|
|
||||||
|
import type { ListTaxDefinitionsUseCase } from "../../../../application/tax-definitions";
|
||||||
|
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
|
||||||
|
|
||||||
|
export class ListTaxDefinitionsController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: ListTaxDefinitionsUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = taxDefinitionsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCriteriaWithDefaultOrder() {
|
||||||
|
if (this.criteria.hasOrder()) {
|
||||||
|
return this.criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { q: quicksearch, filters, pageSize, pageNumber } = this.criteria.toPrimitives();
|
||||||
|
return Criteria.fromPrimitives(filters, "code", "ASC", pageSize, pageNumber, quicksearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const criteria = this.getCriteriaWithDefaultOrder();
|
||||||
|
const result = await this.useCase.execute(companyId, criteria);
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data: any) =>
|
||||||
|
this.ok(data, {
|
||||||
|
"X-Total-Count": String(data.total_items),
|
||||||
|
"Pagination-Count": String(data.total_pages),
|
||||||
|
"Pagination-Page": String(data.page),
|
||||||
|
"Pagination-Limit": String(data.per_page),
|
||||||
|
}),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import type { UpdateTaxDefinitionByIdRequestDTO } from "@erp/catalogs/common";
|
||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { UpdateTaxDefinitionByIdUseCase } from "../../../../application/tax-definitions";
|
||||||
|
import { taxDefinitionsApiErrorMapper } from "../tax-definitions-api-error-mapper";
|
||||||
|
|
||||||
|
export class UpdateTaxDefinitionByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: UpdateTaxDefinitionByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = taxDefinitionsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tax_definition_id } = this.req.params;
|
||||||
|
if (!tax_definition_id) {
|
||||||
|
return this.invalidInputError("Tax definition ID missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
const dto = this.req.body as UpdateTaxDefinitionByIdRequestDTO;
|
||||||
|
const result = await this.useCase.execute(companyId, { id: tax_definition_id as any, dto });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data: unknown) => this.ok(data),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./controllers";
|
||||||
|
export * from "./tax-definitions.routes";
|
||||||
@ -0,0 +1,186 @@
|
|||||||
|
import {
|
||||||
|
ApiErrorMapper,
|
||||||
|
ConflictApiError,
|
||||||
|
EntityNotFoundError,
|
||||||
|
type ErrorToApiRule,
|
||||||
|
NotFoundApiError,
|
||||||
|
ValidationApiError,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type InvalidTaxDefinitionAllowedSurchargeCodesError,
|
||||||
|
type InvalidTaxDefinitionCalculationBehaviorError,
|
||||||
|
type InvalidTaxDefinitionCodeError,
|
||||||
|
type InvalidTaxDefinitionDescriptionError,
|
||||||
|
type InvalidTaxDefinitionFamilyError,
|
||||||
|
type InvalidTaxDefinitionIdError,
|
||||||
|
type InvalidTaxDefinitionInvoiceNoteError,
|
||||||
|
type InvalidTaxDefinitionNameError,
|
||||||
|
type InvalidTaxDefinitionRateError,
|
||||||
|
type InvalidTaxDefinitionScopeError,
|
||||||
|
type InvalidTaxDefinitionValidityPeriodError,
|
||||||
|
type TaxDefinitionCannotBeDisabledError,
|
||||||
|
type TaxDefinitionCannotBeEnabledError,
|
||||||
|
isInvalidTaxDefinitionAllowedSurchargeCodesError,
|
||||||
|
isInvalidTaxDefinitionCalculationBehaviorError,
|
||||||
|
isInvalidTaxDefinitionCodeError,
|
||||||
|
isInvalidTaxDefinitionDescriptionError,
|
||||||
|
isInvalidTaxDefinitionFamilyError,
|
||||||
|
isInvalidTaxDefinitionIdError,
|
||||||
|
isInvalidTaxDefinitionInvoiceNoteError,
|
||||||
|
isInvalidTaxDefinitionNameError,
|
||||||
|
isInvalidTaxDefinitionRateError,
|
||||||
|
isInvalidTaxDefinitionScopeError,
|
||||||
|
isInvalidTaxDefinitionValidityPeriodError,
|
||||||
|
isTaxDefinitionCannotBeDisabledError,
|
||||||
|
isTaxDefinitionCannotBeEnabledError,
|
||||||
|
} from "../../../domain/tax-definitions";
|
||||||
|
|
||||||
|
const invalidTaxDefinitionIdRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionIdError,
|
||||||
|
build: (error) =>
|
||||||
|
new ConflictApiError(
|
||||||
|
(error as InvalidTaxDefinitionIdError).message ||
|
||||||
|
"Tax definition with the provided id already exists."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionCodeRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionCodeError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionCodeError).message || "Tax definition code is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionNameRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionNameError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionNameError).message || "Tax definition name is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionDescriptionRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionDescriptionError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionDescriptionError).message ||
|
||||||
|
"Tax definition description is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionRateRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionRateError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionRateError).message || "Tax definition rate is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionFamilyRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionFamilyError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionFamilyError).message || "Tax definition family is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionCalculationBehaviorRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionCalculationBehaviorError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionCalculationBehaviorError).message ||
|
||||||
|
"Tax definition calculation behavior is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionScopeRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionScopeError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionScopeError).message || "Tax definition scope is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionInvoiceNoteRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionInvoiceNoteError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionInvoiceNoteError).message ||
|
||||||
|
"Tax definition invoice note is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionAllowedSurchargeCodesRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionAllowedSurchargeCodesError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionAllowedSurchargeCodesError).message ||
|
||||||
|
"Tax definition allowed surcharge codes are invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidTaxDefinitionValidityPeriodRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidTaxDefinitionValidityPeriodError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidTaxDefinitionValidityPeriodError).message ||
|
||||||
|
"Tax definition validity period is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const taxDefinitionNotFoundRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: (error) =>
|
||||||
|
error instanceof EntityNotFoundError &&
|
||||||
|
((error as EntityNotFoundError).message.includes("TaxDefinition") ||
|
||||||
|
(error as EntityNotFoundError).message.includes("Tax definition")),
|
||||||
|
build: (error) =>
|
||||||
|
new NotFoundApiError((error as EntityNotFoundError).message || "Tax definition not found."),
|
||||||
|
};
|
||||||
|
|
||||||
|
const taxDefinitionCannotBeDisabledRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isTaxDefinitionCannotBeDisabledError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as TaxDefinitionCannotBeDisabledError).message || "Tax definition cannot be disabled."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const taxDefinitionCannotBeEnabledRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isTaxDefinitionCannotBeEnabledError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as TaxDefinitionCannotBeEnabledError).message || "Tax definition cannot be enabled."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const taxDefinitionsApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
||||||
|
.register(invalidTaxDefinitionIdRule)
|
||||||
|
.register(invalidTaxDefinitionCodeRule)
|
||||||
|
.register(invalidTaxDefinitionNameRule)
|
||||||
|
.register(invalidTaxDefinitionDescriptionRule)
|
||||||
|
.register(invalidTaxDefinitionRateRule)
|
||||||
|
.register(invalidTaxDefinitionFamilyRule)
|
||||||
|
.register(invalidTaxDefinitionCalculationBehaviorRule)
|
||||||
|
.register(invalidTaxDefinitionScopeRule)
|
||||||
|
.register(invalidTaxDefinitionInvoiceNoteRule)
|
||||||
|
.register(invalidTaxDefinitionAllowedSurchargeCodesRule)
|
||||||
|
.register(invalidTaxDefinitionValidityPeriodRule)
|
||||||
|
.register(taxDefinitionNotFoundRule)
|
||||||
|
.register(taxDefinitionCannotBeDisabledRule)
|
||||||
|
.register(taxDefinitionCannotBeEnabledRule);
|
||||||
@ -0,0 +1,120 @@
|
|||||||
|
import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth/api";
|
||||||
|
import { type RequestWithAuth, type StartParams, validateRequest } from "@erp/core/api";
|
||||||
|
import type { NextFunction, Request, Response } from "express";
|
||||||
|
import { Router } from "express";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateTaxDefinitionRequestSchema,
|
||||||
|
DeleteTaxDefinitionByIdRequestSchema,
|
||||||
|
GetTaxDefinitionByIdRequestSchema,
|
||||||
|
ListTaxDefinitionsRequestSchema,
|
||||||
|
UpdateTaxDefinitionByIdParamsRequestSchema,
|
||||||
|
UpdateTaxDefinitionByIdRequestSchema,
|
||||||
|
} from "../../../../common";
|
||||||
|
import type { CatalogsInternalDeps } from "../../di";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateTaxDefinitionController,
|
||||||
|
DeleteTaxDefinitionByIdController,
|
||||||
|
DisableTaxDefinitionByIdController,
|
||||||
|
EnableTaxDefinitionByIdController,
|
||||||
|
GetTaxDefinitionByIdController,
|
||||||
|
ListTaxDefinitionsController,
|
||||||
|
UpdateTaxDefinitionByIdController,
|
||||||
|
} from "./controllers";
|
||||||
|
|
||||||
|
export const taxDefinitionsRouter = (params: StartParams) => {
|
||||||
|
const { app, config, getInternal } = params;
|
||||||
|
const deps = getInternal<CatalogsInternalDeps>("catalogs").taxDefinitions;
|
||||||
|
|
||||||
|
const router = Router({ mergeParams: true });
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
|
||||||
|
router.use((req: Request, res: Response, next: NextFunction) =>
|
||||||
|
mockUser(req as RequestWithAuth, res, next)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.use([
|
||||||
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
|
requireAuthenticated()(req as RequestWithAuth, res, next),
|
||||||
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
|
requireCompanyContext()(req as RequestWithAuth, res, next),
|
||||||
|
]);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/",
|
||||||
|
validateRequest(ListTaxDefinitionsRequestSchema, "query"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new ListTaxDefinitionsController(deps.useCases.listTaxDefinitions());
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/",
|
||||||
|
validateRequest(CreateTaxDefinitionRequestSchema, "body"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new CreateTaxDefinitionController(deps.useCases.createTaxDefinition());
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:tax_definition_id",
|
||||||
|
validateRequest(GetTaxDefinitionByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new GetTaxDefinitionByIdController(
|
||||||
|
deps.useCases.getTaxDefinitionById()
|
||||||
|
);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:tax_definition_id",
|
||||||
|
validateRequest(DeleteTaxDefinitionByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new DeleteTaxDefinitionByIdController(
|
||||||
|
deps.useCases.deleteTaxDefinitionById()
|
||||||
|
);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put(
|
||||||
|
"/:tax_definition_id",
|
||||||
|
validateRequest(UpdateTaxDefinitionByIdParamsRequestSchema, "params"),
|
||||||
|
validateRequest(UpdateTaxDefinitionByIdRequestSchema, "body"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new UpdateTaxDefinitionByIdController(
|
||||||
|
deps.useCases.updateTaxDefinitionById()
|
||||||
|
);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:tax_definition_id/disable",
|
||||||
|
validateRequest(GetTaxDefinitionByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new DisableTaxDefinitionByIdController(
|
||||||
|
deps.useCases.disableTaxDefinitionById()
|
||||||
|
);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:tax_definition_id/enable",
|
||||||
|
validateRequest(GetTaxDefinitionByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new EnableTaxDefinitionByIdController(
|
||||||
|
deps.useCases.enableTaxDefinitionById()
|
||||||
|
);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(`${config.server.apiBasePath}/catalogs/tax-definitions`, router);
|
||||||
|
};
|
||||||
@ -1,2 +1,3 @@
|
|||||||
|
export * from "./express";
|
||||||
export * from "./di";
|
export * from "./di";
|
||||||
export * from "./persistence";
|
export * from "./persistence";
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { SequelizeQueryMapper } from "@erp/core/api";
|
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
|
CountryCode,
|
||||||
|
CountryRegionCode,
|
||||||
TextValue,
|
TextValue,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
@ -15,18 +17,20 @@ import {
|
|||||||
TaxDefinitionCode as TaxDefinitionCodeVO,
|
TaxDefinitionCode as TaxDefinitionCodeVO,
|
||||||
TaxDefinitionName as TaxDefinitionNameVO,
|
TaxDefinitionName as TaxDefinitionNameVO,
|
||||||
TaxFamily,
|
TaxFamily,
|
||||||
TaxJurisdictionCountryCode,
|
|
||||||
TaxJurisdictionRegionCode,
|
|
||||||
TaxRate as TaxRateVO,
|
TaxRate as TaxRateVO,
|
||||||
TaxScope,
|
TaxScope,
|
||||||
} from "../../../../../domain";
|
} from "../../../../../domain";
|
||||||
import type { TaxDefinitionModel } from "../models";
|
import type { TaxDefinitionCreationAttributes, TaxDefinitionModel } from "../models";
|
||||||
|
|
||||||
export class SequelizeTaxDefinitionDomainMapper extends SequelizeQueryMapper<
|
export class SequelizeTaxDefinitionDomainMapper extends SequelizeDomainMapper<
|
||||||
TaxDefinitionModel,
|
TaxDefinitionModel,
|
||||||
|
TaxDefinitionCreationAttributes,
|
||||||
TaxDefinition
|
TaxDefinition
|
||||||
> {
|
> {
|
||||||
public mapToDomain(raw: TaxDefinitionModel): Result<TaxDefinition, Error> {
|
public mapToDomain(
|
||||||
|
raw: TaxDefinitionModel,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<TaxDefinition, Error> {
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
||||||
@ -51,13 +55,13 @@ export class SequelizeTaxDefinitionDomainMapper extends SequelizeQueryMapper<
|
|||||||
);
|
);
|
||||||
|
|
||||||
const jurisdictionCountryCode = extractOrPushError(
|
const jurisdictionCountryCode = extractOrPushError(
|
||||||
TaxJurisdictionCountryCode.create(raw.jurisdiction_country_code),
|
CountryCode.create(raw.jurisdiction_country_code),
|
||||||
"jurisdiction_country_code",
|
"jurisdiction_country_code",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const jurisdictionRegionCode = maybeFromNullableResult(raw.jurisdiction_region_code, (v) =>
|
const jurisdictionRegionCode = maybeFromNullableResult(raw.jurisdiction_region_code, (v) =>
|
||||||
TaxJurisdictionRegionCode.create(v)
|
CountryRegionCode.create(v)
|
||||||
);
|
);
|
||||||
|
|
||||||
const taxScope = extractOrPushError(TaxScope.create(raw.tax_scope), "tax_scope", errors);
|
const taxScope = extractOrPushError(TaxScope.create(raw.tax_scope), "tax_scope", errors);
|
||||||
@ -69,9 +73,10 @@ export class SequelizeTaxDefinitionDomainMapper extends SequelizeQueryMapper<
|
|||||||
|
|
||||||
const arr: any[] = [];
|
const arr: any[] = [];
|
||||||
for (const el of v) {
|
for (const el of v) {
|
||||||
const r = TaxDefinitionCodeVO.create(String(el));
|
const _result = TaxDefinitionCodeVO.create(String(el));
|
||||||
if (r.isFailure) return Result.fail(new Error("Invalid allowed_surcharge_codes element"));
|
if (_result.isFailure)
|
||||||
arr.push(r.getValue());
|
return Result.fail(new Error("Invalid allowed_surcharge_codes element"));
|
||||||
|
arr.push(_result.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(arr);
|
return Result.ok(arr);
|
||||||
@ -112,46 +117,51 @@ export class SequelizeTaxDefinitionDomainMapper extends SequelizeQueryMapper<
|
|||||||
return Result.ok(domainOrError);
|
return Result.ok(domainOrError);
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapToPersistence(domain: TaxDefinition): Result<Record<string, unknown>, Error> {
|
public mapToPersistence(
|
||||||
const dto: Record<string, unknown> = {
|
source: TaxDefinition,
|
||||||
id: domain.id.toPrimitive(),
|
params?: MapperParamsType
|
||||||
company_id: domain.companyId.toPrimitive(),
|
): Result<TaxDefinitionCreationAttributes, Error> {
|
||||||
code: domain.code.toPrimitive(),
|
const dto: TaxDefinitionCreationAttributes = {
|
||||||
name: domain.name.toPrimitive(),
|
id: source.id.toPrimitive(),
|
||||||
description: domain.description.match(
|
company_id: source.companyId.toPrimitive(),
|
||||||
|
code: source.code.toPrimitive(),
|
||||||
|
name: source.name.toPrimitive(),
|
||||||
|
description: source.description.match(
|
||||||
(v) => v.toPrimitive(),
|
(v) => v.toPrimitive(),
|
||||||
() => null
|
() => null
|
||||||
),
|
),
|
||||||
rate_value: domain.rate.toPrimitive(),
|
rate_value: source.rate.toPrimitive(),
|
||||||
rate_scale: (domain.rate as any).scale ?? 2,
|
rate_scale: (source.rate as any).scale ?? 2,
|
||||||
tax_family: domain.taxFamily.toPrimitive(),
|
tax_family: source.taxFamily.toPrimitive(),
|
||||||
calculation_behavior: domain.calculationBehavior.toPrimitive(),
|
calculation_behavior: source.calculationBehavior.toPrimitive(),
|
||||||
jurisdiction_country_code: domain.jurisdictionCountryCode.toPrimitive(),
|
jurisdiction_country_code: source.jurisdictionCountryCode.toPrimitive(),
|
||||||
jurisdiction_region_code: domain.jurisdictionRegionCode.match(
|
jurisdiction_region_code: source.jurisdictionRegionCode.match(
|
||||||
(v) => v.toPrimitive(),
|
(v) => v.toPrimitive(),
|
||||||
() => null
|
() => null
|
||||||
),
|
),
|
||||||
tax_scope: domain.taxScope.toPrimitive(),
|
tax_scope: source.taxScope.toPrimitive(),
|
||||||
invoice_note: domain.invoiceNote.match(
|
invoice_note: source.invoiceNote.match(
|
||||||
(v) => v.toPrimitive(),
|
(v) => v.toPrimitive(),
|
||||||
() => null
|
() => null
|
||||||
),
|
),
|
||||||
allowed_surcharge_codes: domain.allowedSurchargeCodes.match(
|
allowed_surcharge_codes: source.allowedSurchargeCodes.match(
|
||||||
(arr) => arr.map((c) => c.toPrimitive()),
|
(arr) => arr.map((c) => c.toPrimitive()),
|
||||||
() => null
|
() => null
|
||||||
),
|
),
|
||||||
is_system: domain.isSystem,
|
is_system: source.isSystem,
|
||||||
is_active: domain.isActive,
|
is_active: source.isActive,
|
||||||
valid_from: domain.validFrom.match(
|
valid_from: source.validFrom.match(
|
||||||
(d) => d.toPrimitive(),
|
(d) => d.toPrimitive(),
|
||||||
() => null
|
() => null
|
||||||
),
|
),
|
||||||
valid_to: domain.validTo.match(
|
valid_to: source.validTo.match(
|
||||||
(d) => d.toPrimitive(),
|
(d) => d.toPrimitive(),
|
||||||
() => null
|
() => null
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return Result.ok(dto);
|
return Result.ok<TaxDefinitionCreationAttributes>(
|
||||||
|
dto satisfies TaxDefinitionCreationAttributes
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize-tax-definition.model";
|
||||||
@ -5,9 +5,9 @@ import {
|
|||||||
translateSequelizeError,
|
translateSequelizeError,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
import { type Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
import type { Sequelize, Transaction } from "sequelize";
|
import { Op, type Sequelize, type Transaction, type WhereOptions } from "sequelize";
|
||||||
|
|
||||||
import type { ITaxDefinitionRepository, TaxDefinitionSummary } from "../../../../../application";
|
import type { ITaxDefinitionRepository, TaxDefinitionSummary } from "../../../../../application";
|
||||||
import type { TaxDefinition, TaxDefinitionCode } from "../../../../../domain";
|
import type { TaxDefinition, TaxDefinitionCode } from "../../../../../domain";
|
||||||
@ -123,6 +123,62 @@ export class SequelizeTaxDefinitionRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findActiveByCodeInCompany(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
code: string;
|
||||||
|
atDate: UtcDate;
|
||||||
|
transaction?: Transaction;
|
||||||
|
}): Promise<Result<Maybe<TaxDefinition>, Error>> {
|
||||||
|
try {
|
||||||
|
const row = await TaxDefinitionModel.findOne({
|
||||||
|
where: {
|
||||||
|
...this.buildActiveWhereClause(params.companyId, params.atDate),
|
||||||
|
code: this.normalizeCode(params.code),
|
||||||
|
},
|
||||||
|
transaction: params.transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return Result.ok(Maybe.none<TaxDefinition>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.domainMapper.mapToDomain(row).map((taxDefinition) => Maybe.some(taxDefinition));
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findActiveByCodesInCompany(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
codes: Collection<string>;
|
||||||
|
atDate: UtcDate;
|
||||||
|
transaction?: Transaction;
|
||||||
|
}): Promise<Result<Collection<TaxDefinition>, Error>> {
|
||||||
|
try {
|
||||||
|
const normalizedCodes = Array.from(
|
||||||
|
new Set(params.codes.getAll().map((code) => this.normalizeCode(code)))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (normalizedCodes.length === 0) {
|
||||||
|
return Result.ok(new Collection([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await TaxDefinitionModel.findAll({
|
||||||
|
where: {
|
||||||
|
...this.buildActiveWhereClause(params.companyId, params.atDate),
|
||||||
|
code: {
|
||||||
|
[Op.in]: normalizedCodes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transaction: params.transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.domainMapper.mapToDomainCollection(rows, rows.length);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async findByCriteriaInCompany(
|
async findByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
@ -171,4 +227,25 @@ export class SequelizeTaxDefinitionRepository
|
|||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildActiveWhereClause(companyId: UniqueID, atDate: UtcDate): WhereOptions {
|
||||||
|
const atDateISO = atDate.toPrimitive();
|
||||||
|
|
||||||
|
return {
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
is_active: true,
|
||||||
|
[Op.and]: [
|
||||||
|
{
|
||||||
|
[Op.or]: [{ valid_from: null }, { valid_from: { [Op.lte]: atDateISO } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[Op.or]: [{ valid_to: null }, { valid_to: { [Op.gte]: atDateISO } }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeCode(code: string): string {
|
||||||
|
return code.trim().toLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,7 @@ import type { ModuleParams, SetupParams } from "@erp/core/api";
|
|||||||
import { buildTransactionManager } from "@erp/core/api";
|
import { buildTransactionManager } from "@erp/core/api";
|
||||||
import type { Sequelize } from "sequelize";
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
import type { ITaxRegimeRepository } from "../../../application/";
|
import { type ITaxRegimeRepository, TaxRegimePublicFinder } from "../../../application/";
|
||||||
import { TaxRegimeFinder } from "../../../application/";
|
|
||||||
import {
|
import {
|
||||||
CreateTaxRegimeUseCase,
|
CreateTaxRegimeUseCase,
|
||||||
DeleteTaxRegimeByIdUseCase,
|
DeleteTaxRegimeByIdUseCase,
|
||||||
@ -11,6 +10,7 @@ import {
|
|||||||
EnableTaxRegimeByIdUseCase,
|
EnableTaxRegimeByIdUseCase,
|
||||||
GetTaxRegimeByIdUseCase,
|
GetTaxRegimeByIdUseCase,
|
||||||
ListTaxRegimesUseCase,
|
ListTaxRegimesUseCase,
|
||||||
|
TaxRegimePublicModelMapper,
|
||||||
UpdateTaxRegimeByIdUseCase,
|
UpdateTaxRegimeByIdUseCase,
|
||||||
} from "../../../application/tax-regimes";
|
} from "../../../application/tax-regimes";
|
||||||
import {
|
import {
|
||||||
@ -112,8 +112,13 @@ export const buildTaxRegimesDependencies = (params: ModuleParams): TaxRegimesInt
|
|||||||
export const buildTaxRegimesPublicServices = (
|
export const buildTaxRegimesPublicServices = (
|
||||||
_params: SetupParams,
|
_params: SetupParams,
|
||||||
deps: TaxRegimesInternalDeps
|
deps: TaxRegimesInternalDeps
|
||||||
): { finder: TaxRegimeFinder } => {
|
): { finder: TaxRegimePublicFinder } => {
|
||||||
|
const mapper = new TaxRegimePublicModelMapper();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
finder: new TaxRegimeFinder(deps.repository),
|
finder: new TaxRegimePublicFinder({
|
||||||
|
repository: deps.repository,
|
||||||
|
mapper,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING(2),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
unique: true,
|
unique: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import {
|
|||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { type Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
import type { Sequelize, Transaction } from "sequelize";
|
import { Op, type Sequelize, type Transaction } from "sequelize";
|
||||||
|
|
||||||
import type { ITaxRegimeRepository, TaxRegimeSummary } from "../../../../../application";
|
import type { ITaxRegimeRepository, TaxRegimeSummary } from "../../../../../application";
|
||||||
import type { TaxRegime, TaxRegimeCode } from "../../../../../domain";
|
import type { TaxRegime, TaxRegimeCode } from "../../../../../domain";
|
||||||
@ -105,6 +105,22 @@ export class SequelizeTaxRegimeRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async existsByCodeInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
code: TaxRegimeCode,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const count = await TaxRegimeModel.count({
|
||||||
|
where: { code: code.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
return Result.ok(Boolean(count > 0));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recupera un método de pago por su ID y companyId.
|
* Recupera un método de pago por su ID y companyId.
|
||||||
*
|
*
|
||||||
@ -250,4 +266,55 @@ export class SequelizeTaxRegimeRepository
|
|||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findByCodeInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
code: TaxRegimeCode,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<Maybe<TaxRegime>, Error>> {
|
||||||
|
try {
|
||||||
|
const row = await TaxRegimeModel.findOne({
|
||||||
|
where: { code: code.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return Result.ok(Maybe.none<TaxRegime>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.domainMapper.mapToDomain(row).map((taxDefinition) => Maybe.some(taxDefinition));
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByCodesInCompany(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
codes: Collection<TaxRegimeCode>;
|
||||||
|
transaction?: Transaction;
|
||||||
|
}): Promise<Result<Collection<TaxRegime>, Error>> {
|
||||||
|
try {
|
||||||
|
const normalizedCodes = Array.from(
|
||||||
|
new Set(params.codes.getAll().map((code) => code.toString()))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (normalizedCodes.length === 0) {
|
||||||
|
return Result.ok(new Collection());
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await TaxRegimeModel.findAll({
|
||||||
|
where: {
|
||||||
|
company_id: params.companyId.toString(),
|
||||||
|
code: {
|
||||||
|
[Op.in]: normalizedCodes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transaction: params.transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.domainMapper.mapToDomainCollection(rows, rows.length);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
export * from "./payment-terms";
|
export * from "./payment-terms";
|
||||||
|
export * from "./tax-definitions";
|
||||||
export * from "./tax-regimes";
|
export * from "./tax-regimes";
|
||||||
|
|||||||
3
modules/catalogs/src/common/dto/tax-definitions/index.ts
Normal file
3
modules/catalogs/src/common/dto/tax-definitions/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./request";
|
||||||
|
export * from "./response";
|
||||||
|
export * from "./shared";
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { IsoDateSchema, PercentageSchema } from "@erp/core";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
const TaxDefinitionCodeSchema = z.string().regex(/^[a-z0-9][a-z0-9_]*$/);
|
||||||
|
const TaxDefinitionFamilySchema = z.enum([
|
||||||
|
"iva",
|
||||||
|
"igic",
|
||||||
|
"ipsi",
|
||||||
|
"equivalence_surcharge",
|
||||||
|
"withholding",
|
||||||
|
"vat",
|
||||||
|
"gst",
|
||||||
|
"sales_tax",
|
||||||
|
"reverse_charge",
|
||||||
|
"exempt",
|
||||||
|
"not_subject",
|
||||||
|
"custom",
|
||||||
|
]);
|
||||||
|
const TaxCalculationBehaviorSchema = z.enum(["additive", "subtractive", "neutral"]);
|
||||||
|
const TaxScopeSchema = z.enum(["domestic", "intra_eu", "export", "import", "international"]);
|
||||||
|
const JurisdictionCountryCodeSchema = z.string().regex(/^[A-Z]{2}$/);
|
||||||
|
const JurisdictionRegionCodeSchema = z.string().regex(/^[A-Z]{2}-[A-Z0-9-]+$/);
|
||||||
|
|
||||||
|
export const CreateTaxDefinitionRequestSchema = z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
code: TaxDefinitionCodeSchema,
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
rate: PercentageSchema,
|
||||||
|
tax_family: TaxDefinitionFamilySchema,
|
||||||
|
calculation_behavior: TaxCalculationBehaviorSchema,
|
||||||
|
jurisdiction_country_code: JurisdictionCountryCodeSchema,
|
||||||
|
jurisdiction_region_code: JurisdictionRegionCodeSchema.nullable().optional(),
|
||||||
|
tax_scope: TaxScopeSchema,
|
||||||
|
invoice_note: z.string().nullable().optional(),
|
||||||
|
allowed_surcharge_codes: z.array(TaxDefinitionCodeSchema).nullable().optional(),
|
||||||
|
is_system: z.boolean().optional(),
|
||||||
|
is_active: z.boolean(),
|
||||||
|
valid_from: IsoDateSchema.nullable().optional(),
|
||||||
|
valid_to: IsoDateSchema.nullable().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CreateTaxDefinitionRequestDTO = z.infer<typeof CreateTaxDefinitionRequestSchema>;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const DeleteTaxDefinitionByIdRequestSchema = z.object({
|
||||||
|
tax_definition_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DeleteTaxDefinitionByIdRequestDTO = z.infer<
|
||||||
|
typeof DeleteTaxDefinitionByIdRequestSchema
|
||||||
|
>;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const DisableTaxDefinitionByIdRequestSchema = z.object({
|
||||||
|
tax_definition_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DisableTaxDefinitionByIdRequestDTO = z.infer<
|
||||||
|
typeof DisableTaxDefinitionByIdRequestSchema
|
||||||
|
>;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const EnableTaxDefinitionByIdRequestSchema = z.object({
|
||||||
|
tax_definition_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type EnableTaxDefinitionByIdRequestDTO = z.infer<
|
||||||
|
typeof EnableTaxDefinitionByIdRequestSchema
|
||||||
|
>;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const GetTaxDefinitionByIdRequestSchema = z.object({
|
||||||
|
tax_definition_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GetTaxDefinitionByIdRequestDTO = z.infer<typeof GetTaxDefinitionByIdRequestSchema>;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./create-tax-definition.request.dto";
|
||||||
|
export * from "./delete-tax-definition-by-id.request.dto";
|
||||||
|
export * from "./disable-tax-definition-by-id.request.dto";
|
||||||
|
export * from "./enable-tax-definition-by-id.request.dto";
|
||||||
|
export * from "./get-tax-definition-by-id.request.dto";
|
||||||
|
export * from "./list-tax-definitions.request.dto";
|
||||||
|
export * from "./update-tax-definition-by-id.request.dto";
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
import { CriteriaSchema } from "@erp/core";
|
||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const ListTaxDefinitionsRequestSchema = CriteriaSchema;
|
||||||
|
export type ListTaxDefinitionsRequestDTO = z.infer<typeof ListTaxDefinitionsRequestSchema>;
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { IsoDateSchema, PercentageSchema } from "@erp/core";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
const TaxDefinitionCodeSchema = z.string().regex(/^[a-z0-9][a-z0-9_]*$/);
|
||||||
|
|
||||||
|
export const UpdateTaxDefinitionByIdParamsRequestSchema = z.object({
|
||||||
|
tax_definition_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateTaxDefinitionByIdRequestSchema = z.object({
|
||||||
|
name: z.string().optional(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
rate: PercentageSchema.optional(),
|
||||||
|
invoice_note: z.string().nullable().optional(),
|
||||||
|
allowed_surcharge_codes: z.array(TaxDefinitionCodeSchema).nullable().optional(),
|
||||||
|
is_active: z.boolean().optional(),
|
||||||
|
valid_from: IsoDateSchema.nullable().optional(),
|
||||||
|
valid_to: IsoDateSchema.nullable().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UpdateTaxDefinitionByIdParamsRequestDTO = z.infer<
|
||||||
|
typeof UpdateTaxDefinitionByIdParamsRequestSchema
|
||||||
|
>;
|
||||||
|
export type UpdateTaxDefinitionByIdRequestDTO = z.infer<
|
||||||
|
typeof UpdateTaxDefinitionByIdRequestSchema
|
||||||
|
>;
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { TaxDefinitionDetailSchema } from "../shared";
|
||||||
|
|
||||||
|
export const CreateTaxDefinitionResponseSchema = TaxDefinitionDetailSchema;
|
||||||
|
export type CreateTaxDefinitionResponseDTO = z.infer<typeof CreateTaxDefinitionResponseSchema>;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { TaxDefinitionDetailSchema } from "../shared";
|
||||||
|
|
||||||
|
export const DisableTaxDefinitionByIdResponseSchema = TaxDefinitionDetailSchema;
|
||||||
|
export type DisableTaxDefinitionByIdResponseDTO = z.infer<
|
||||||
|
typeof DisableTaxDefinitionByIdResponseSchema
|
||||||
|
>;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { TaxDefinitionDetailSchema } from "../shared";
|
||||||
|
|
||||||
|
export const EnableTaxDefinitionByIdResponseSchema = TaxDefinitionDetailSchema;
|
||||||
|
export type EnableTaxDefinitionByIdResponseDTO = z.infer<
|
||||||
|
typeof EnableTaxDefinitionByIdResponseSchema
|
||||||
|
>;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { TaxDefinitionDetailSchema } from "../shared";
|
||||||
|
|
||||||
|
export const GetTaxDefinitionByIdResponseSchema = TaxDefinitionDetailSchema;
|
||||||
|
export type GetTaxDefinitionByIdResponseDTO = z.infer<
|
||||||
|
typeof GetTaxDefinitionByIdResponseSchema
|
||||||
|
>;
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
export * from "./create-tax-definition.response.dto";
|
||||||
|
export * from "./disable-tax-definition-by-id.response.dto";
|
||||||
|
export * from "./enable-tax-definition-by-id.response.dto";
|
||||||
|
export * from "./get-tax-definition-by-id.response.dto";
|
||||||
|
export * from "./list-tax-definitions.response.dto";
|
||||||
|
export * from "./update-tax-definition-by-id.response.dto";
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { createPaginatedListSchema } from "@erp/core";
|
||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { TaxDefinitionSummarySchema } from "../shared";
|
||||||
|
|
||||||
|
export const ListTaxDefinitionsResponseSchema = createPaginatedListSchema(
|
||||||
|
TaxDefinitionSummarySchema
|
||||||
|
);
|
||||||
|
export type ListTaxDefinitionsResponseDTO = z.infer<typeof ListTaxDefinitionsResponseSchema>;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { TaxDefinitionDetailSchema } from "../shared";
|
||||||
|
|
||||||
|
export const UpdateTaxDefinitionByIdResponseSchema = TaxDefinitionDetailSchema;
|
||||||
|
export type UpdateTaxDefinitionByIdResponseDTO = z.infer<
|
||||||
|
typeof UpdateTaxDefinitionByIdResponseSchema
|
||||||
|
>;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./tax-definition-detail.dto";
|
||||||
|
export * from "./tax-definition-summary.dto";
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import { IsoDateSchema, PercentageSchema } from "@erp/core";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
const TaxDefinitionCodeSchema = z.string().regex(/^[a-z0-9][a-z0-9_]*$/);
|
||||||
|
const TaxDefinitionFamilySchema = z.enum([
|
||||||
|
"iva",
|
||||||
|
"igic",
|
||||||
|
"ipsi",
|
||||||
|
"equivalence_surcharge",
|
||||||
|
"withholding",
|
||||||
|
"vat",
|
||||||
|
"gst",
|
||||||
|
"sales_tax",
|
||||||
|
"reverse_charge",
|
||||||
|
"exempt",
|
||||||
|
"not_subject",
|
||||||
|
"custom",
|
||||||
|
]);
|
||||||
|
const TaxCalculationBehaviorSchema = z.enum(["additive", "subtractive", "neutral"]);
|
||||||
|
const TaxScopeSchema = z.enum(["domestic", "intra_eu", "export", "import", "international"]);
|
||||||
|
const JurisdictionCountryCodeSchema = z.string().regex(/^[A-Z]{2}$/);
|
||||||
|
const JurisdictionRegionCodeSchema = z.string().regex(/^[A-Z]{2}-[A-Z0-9-]+$/);
|
||||||
|
|
||||||
|
export const TaxDefinitionDetailSchema = z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
company_id: z.uuid(),
|
||||||
|
code: TaxDefinitionCodeSchema,
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
rate: PercentageSchema,
|
||||||
|
tax_family: TaxDefinitionFamilySchema,
|
||||||
|
calculation_behavior: TaxCalculationBehaviorSchema,
|
||||||
|
jurisdiction_country_code: JurisdictionCountryCodeSchema,
|
||||||
|
jurisdiction_region_code: JurisdictionRegionCodeSchema.nullable(),
|
||||||
|
tax_scope: TaxScopeSchema,
|
||||||
|
invoice_note: z.string().nullable(),
|
||||||
|
allowed_surcharge_codes: z.array(TaxDefinitionCodeSchema).nullable(),
|
||||||
|
is_system: z.boolean(),
|
||||||
|
is_active: z.boolean(),
|
||||||
|
valid_from: IsoDateSchema.nullable(),
|
||||||
|
valid_to: IsoDateSchema.nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TaxDefinitionDetailDTO = z.infer<typeof TaxDefinitionDetailSchema>;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
const TaxDefinitionCodeSchema = z.string().regex(/^[a-z0-9][a-z0-9_]*$/);
|
||||||
|
|
||||||
|
export const TaxDefinitionSummarySchema = z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
company_id: z.uuid(),
|
||||||
|
code: TaxDefinitionCodeSchema,
|
||||||
|
name: z.string(),
|
||||||
|
is_active: z.boolean(),
|
||||||
|
is_system: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TaxDefinitionSummaryDTO = z.infer<typeof TaxDefinitionSummarySchema>;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
export type ISnapshotBuilderParams = Readonly<Record<string, unknown>>;
|
export type ISnapshotBuilderParams = {}; //Readonly<Record<string, unknown>>;
|
||||||
|
|
||||||
export interface ISnapshotBuilder<TSource, TSnapshot = unknown> {
|
export interface ISnapshotBuilder<TSource, TSnapshot = unknown> {
|
||||||
toOutput(source: TSource, params?: ISnapshotBuilderParams): TSnapshot;
|
toOutput(source: TSource, params?: ISnapshotBuilderParams): TSnapshot;
|
||||||
|
|||||||
@ -1,19 +1,25 @@
|
|||||||
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
||||||
import type { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type TaxPercentageProps = Pick<PercentageProps, "value">;
|
type TaxPercentageProps = Pick<PercentageProps, "value">;
|
||||||
|
|
||||||
export class TaxPercentage extends Percentage {
|
export class TaxPercentage extends Percentage {
|
||||||
static DEFAULT_SCALE = 2;
|
public static readonly DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
static create({ value }: TaxPercentageProps): Result<Percentage> {
|
public static create({ value }: TaxPercentageProps): Result<TaxPercentage> {
|
||||||
return Percentage.create({
|
const result = Percentage.create({
|
||||||
value,
|
value,
|
||||||
scale: TaxPercentage.DEFAULT_SCALE,
|
scale: TaxPercentage.DEFAULT_SCALE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new TaxPercentage(result.data.getProps()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static zero() {
|
public static zero(): TaxPercentage {
|
||||||
return TaxPercentage.create({ value: 0 }).data;
|
return TaxPercentage.create({ value: 0 }).data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,178 +1,183 @@
|
|||||||
import type { TaxCatalogProvider } from "@erp/core";
|
|
||||||
import { ValueObject } from "@repo/rdx-ddd";
|
import { ValueObject } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { TaxPercentage } from "./tax-percentage.vo";
|
import { TaxPercentage } from "./tax-percentage.vo.js";
|
||||||
|
|
||||||
const TAX_GROUPS = ["IVA", "IPSI", "IGIC", "retention", "rec"] as const;
|
export const TAX_GROUPS = ["iva", "ipsi", "igic", "retention", "surcharge"] as const;
|
||||||
type TaxGroup = (typeof TAX_GROUPS)[number];
|
|
||||||
|
export type TaxGroup = (typeof TAX_GROUPS)[number];
|
||||||
|
|
||||||
|
export type TaxCalculationBehavior = "additive" | "subtractive";
|
||||||
|
|
||||||
export interface TaxProps {
|
export interface TaxProps {
|
||||||
code: string; // iva_21
|
code: string;
|
||||||
name: string; // 21% IVA
|
name: string;
|
||||||
value: number; // 2100
|
rate: TaxPercentage;
|
||||||
group: TaxGroup;
|
group: TaxGroup;
|
||||||
|
calculationBehavior: TaxCalculationBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateTaxProps {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
rate: TaxPercentage;
|
||||||
|
group: TaxGroup;
|
||||||
|
calculationBehavior: TaxCalculationBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tax extends ValueObject<TaxProps> {
|
export class Tax extends ValueObject<TaxProps> {
|
||||||
static readonly DEFAULT_SCALE = TaxPercentage.DEFAULT_SCALE;
|
public static readonly DEFAULT_SCALE = TaxPercentage.DEFAULT_SCALE;
|
||||||
static readonly MIN_VALUE = TaxPercentage.MIN_VALUE;
|
public static readonly MIN_VALUE = TaxPercentage.MIN_VALUE;
|
||||||
static readonly MAX_VALUE = TaxPercentage.MAX_VALUE;
|
public static readonly MAX_VALUE = TaxPercentage.MAX_VALUE;
|
||||||
static readonly MIN_SCALE = TaxPercentage.MIN_SCALE;
|
public static readonly MIN_SCALE = TaxPercentage.MIN_SCALE;
|
||||||
static readonly MAX_SCALE = TaxPercentage.MAX_SCALE;
|
public static readonly MAX_SCALE = TaxPercentage.MAX_SCALE;
|
||||||
|
|
||||||
private static CODE_REGEX = /^[a-z0-9_:-]+$/;
|
private static readonly CODE_REGEX = /^[a-z0-9_:-]+$/;
|
||||||
|
|
||||||
private _percentage!: TaxPercentage;
|
public static create(props: CreateTaxProps): Result<Tax> {
|
||||||
|
const validationResult = Tax.validate(props);
|
||||||
|
|
||||||
protected static validate(values: TaxProps) {
|
if (!validationResult.success) {
|
||||||
|
return Result.fail(
|
||||||
|
new Error(validationResult.error.issues.map((issue) => issue.message).join(", "))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(
|
||||||
|
new Tax({
|
||||||
|
code: props.code.trim(),
|
||||||
|
name: props.name.trim(),
|
||||||
|
rate: props.rate,
|
||||||
|
group: props.group,
|
||||||
|
calculationBehavior: props.calculationBehavior,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static validate(values: CreateTaxProps) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
value: z
|
|
||||||
.number()
|
|
||||||
.int()
|
|
||||||
.min(Tax.MIN_VALUE, "La tasa de impuesto no puede ser negativa.")
|
|
||||||
.max(Tax.MAX_VALUE * 10 ** Tax.MAX_SCALE, "La tasa de impuesto es demasiado alta."),
|
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
|
.trim()
|
||||||
.min(1, "El nombre del impuesto es obligatorio.")
|
.min(1, "El nombre del impuesto es obligatorio.")
|
||||||
.max(100, "El nombre del impuesto no puede exceder 100 caracteres."),
|
.max(100, "El nombre del impuesto no puede exceder 100 caracteres."),
|
||||||
|
|
||||||
code: z
|
code: z
|
||||||
.string()
|
.string()
|
||||||
|
.trim()
|
||||||
.min(1, "El código del impuesto es obligatorio.")
|
.min(1, "El código del impuesto es obligatorio.")
|
||||||
.max(40, "El código del impuesto no puede exceder 40 caracteres.")
|
.max(40, "El código del impuesto no puede exceder 40 caracteres.")
|
||||||
.regex(Tax.CODE_REGEX, "El código contiene caracteres no permitidos."),
|
.regex(Tax.CODE_REGEX, "El código contiene caracteres no permitidos."),
|
||||||
group: z.enum(TAX_GROUPS, "El impuesto debe ser un IVA, retención o rec. equivalencia"),
|
|
||||||
|
group: z.enum(TAX_GROUPS),
|
||||||
|
|
||||||
|
calculationBehavior: z.enum(["additive", "subtractive"]),
|
||||||
});
|
});
|
||||||
|
|
||||||
return schema.safeParse(values);
|
return schema.safeParse(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: TaxProps): Result<Tax> {
|
public get code(): string {
|
||||||
const { value, name, code, group } = props;
|
|
||||||
|
|
||||||
const validationResult = Tax.validate({ value, name, code, group });
|
|
||||||
if (!validationResult.success) {
|
|
||||||
return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", ")));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(new Tax({ value, name, code, group }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crea un Tax usando solo el 'code', resolviendo el resto de datos desde el catálogo.
|
|
||||||
* @param code Código del impuesto (p.ej. "iva_21")
|
|
||||||
* @param provider Proveedor del catálogo de impuestos
|
|
||||||
*/
|
|
||||||
static createFromCode(code: string, provider: TaxCatalogProvider): Result<Tax, Error> {
|
|
||||||
const normalized = (code ?? "").trim().toLowerCase();
|
|
||||||
|
|
||||||
const schema = z
|
|
||||||
.string()
|
|
||||||
.min(1, "El código del impuesto es obligatorio.")
|
|
||||||
.max(40, "El código del impuesto no puede exceder 40 caracteres.")
|
|
||||||
.regex(Tax.CODE_REGEX, "El código contiene caracteres no permitidos.");
|
|
||||||
|
|
||||||
const validationResult = schema.safeParse(normalized);
|
|
||||||
|
|
||||||
if (!validationResult.success) {
|
|
||||||
return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", ")));
|
|
||||||
}
|
|
||||||
|
|
||||||
const maybeItem = provider.findByCode(normalized);
|
|
||||||
if (maybeItem.isNone()) {
|
|
||||||
return Result.fail(
|
|
||||||
new Error(`Código de impuesto no encontrado en el catálogo: "${normalized}"`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = maybeItem.unwrap();
|
|
||||||
|
|
||||||
// Delegamos en create para reusar validación y límites
|
|
||||||
return Tax.create({
|
|
||||||
value: Number(item.value),
|
|
||||||
name: item.name,
|
|
||||||
code: item.code, // guardamos el code tal cual viene del catálogo
|
|
||||||
group: item.group as TaxGroup,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get value(): number {
|
|
||||||
return this.props.value;
|
|
||||||
}
|
|
||||||
get scale(): number {
|
|
||||||
return Tax.DEFAULT_SCALE;
|
|
||||||
}
|
|
||||||
get name(): string {
|
|
||||||
return this.props.name;
|
|
||||||
}
|
|
||||||
get code(): string {
|
|
||||||
return this.props.code;
|
return this.props.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
get group(): string {
|
public get name(): string {
|
||||||
|
return this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get rate(): TaxPercentage {
|
||||||
|
return this.props.rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get group(): TaxGroup {
|
||||||
return this.props.group;
|
return this.props.group;
|
||||||
}
|
}
|
||||||
|
|
||||||
get percentage(): TaxPercentage {
|
public get calculationBehavior(): TaxCalculationBehavior {
|
||||||
return TaxPercentage.create({ value: this.value }).data;
|
return this.props.calculationBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
isVATLike(): boolean {
|
public get value(): number {
|
||||||
return this.group === "IVA" || this.group === "IGIC" || this.group === "IPSI";
|
return this.props.rate.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
isRetention(): boolean {
|
public get scale(): number {
|
||||||
return this.group === "retention";
|
return this.props.rate.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
isRec(): boolean {
|
public get percentage(): TaxPercentage {
|
||||||
return this.group === "rec";
|
return this.props.rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProps(): TaxProps {
|
public isAdditive(): boolean {
|
||||||
|
return this.props.calculationBehavior === "additive";
|
||||||
|
}
|
||||||
|
|
||||||
|
public isSubtractive(): boolean {
|
||||||
|
return this.props.calculationBehavior === "subtractive";
|
||||||
|
}
|
||||||
|
|
||||||
|
public isIva(): boolean {
|
||||||
|
return this.props.group === "iva";
|
||||||
|
}
|
||||||
|
|
||||||
|
public isIgic(): boolean {
|
||||||
|
return this.props.group === "igic";
|
||||||
|
}
|
||||||
|
|
||||||
|
public isIpsi(): boolean {
|
||||||
|
return this.props.group === "ipsi";
|
||||||
|
}
|
||||||
|
|
||||||
|
public isSurcharge(): boolean {
|
||||||
|
return this.props.group === "surcharge";
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRetention(): boolean {
|
||||||
|
return this.props.group === "retention";
|
||||||
|
}
|
||||||
|
|
||||||
|
public getProps(): TaxProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
toPrimitive() {
|
public toPrimitive(): TaxProps {
|
||||||
return this.getProps();
|
return this.getProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
toNumber(): number {
|
public toNumber(): number {
|
||||||
return this.value / 10 ** this.scale;
|
return this.value / 10 ** this.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
public toString(): string {
|
||||||
return `${this.toNumber().toFixed(this.scale)}%`;
|
return this.rate.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
isZero(): boolean {
|
public isZero(): boolean {
|
||||||
return this.value === 0;
|
return this.value === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isPositive(): boolean {
|
public isPositive(): boolean {
|
||||||
return this.value > 0;
|
return this.value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
equalsTo(other: Tax): boolean {
|
public equalsTo(other: Tax): boolean {
|
||||||
return this.equals(other);
|
return (
|
||||||
|
this.code === other.code &&
|
||||||
|
this.name === other.name &&
|
||||||
|
this.value === other.value &&
|
||||||
|
this.scale === other.scale &&
|
||||||
|
this.group === other.group &&
|
||||||
|
this.calculationBehavior === other.calculationBehavior
|
||||||
|
);
|
||||||
}
|
}
|
||||||
greaterThan(other: Tax): boolean {
|
|
||||||
|
public greaterThan(other: Tax): boolean {
|
||||||
return this.toNumber() > other.toNumber();
|
return this.toNumber() > other.toNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
lessThan(other: Tax): boolean {
|
public lessThan(other: Tax): boolean {
|
||||||
return this.toNumber() < other.toNumber();
|
return this.toNumber() < other.toNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
value: this.value,
|
|
||||||
scale: this.scale,
|
|
||||||
name: this.name,
|
|
||||||
code: this.code,
|
|
||||||
percentage: this.toNumber(),
|
|
||||||
formatted: this.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,16 @@
|
|||||||
import {
|
import { type JsonTaxCatalogProvider, SpainTaxCatalogProvider } from "../../../common";
|
||||||
FactuGESPaymentCatalogProvider,
|
|
||||||
type JsonPaymentCatalogProvider,
|
|
||||||
type JsonTaxCatalogProvider,
|
|
||||||
SpainTaxCatalogProvider,
|
|
||||||
} from "../../../common";
|
|
||||||
|
|
||||||
export interface ICatalogs {
|
export interface ICatalogs {
|
||||||
taxCatalog: JsonTaxCatalogProvider;
|
taxCatalog: JsonTaxCatalogProvider;
|
||||||
paymentCatalog: JsonPaymentCatalogProvider;
|
//paymentCatalog: JsonPaymentCatalogProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildCatalogs = (): ICatalogs => {
|
export const buildCatalogs = (): ICatalogs => {
|
||||||
const taxCatalog = SpainTaxCatalogProvider();
|
const taxCatalog = SpainTaxCatalogProvider();
|
||||||
const paymentCatalog = FactuGESPaymentCatalogProvider();
|
//const paymentCatalog = FactuGESPaymentCatalogProvider();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
taxCatalog,
|
taxCatalog,
|
||||||
paymentCatalog,
|
//paymentCatalog,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,9 +17,8 @@ import {
|
|||||||
isDomainValidationError,
|
isDomainValidationError,
|
||||||
isValidationErrorCollection,
|
isValidationErrorCollection,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import type { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import { isSchemaError } from "../../../common/schemas";
|
|
||||||
import { type DocumentGenerationError, isDocumentGenerationError } from "../../application";
|
import { type DocumentGenerationError, isDocumentGenerationError } from "../../application";
|
||||||
import {
|
import {
|
||||||
type DuplicateEntityError,
|
type DuplicateEntityError,
|
||||||
@ -46,6 +45,8 @@ import {
|
|||||||
ValidationApiError,
|
ValidationApiError,
|
||||||
} from "./errors";
|
} from "./errors";
|
||||||
|
|
||||||
|
export const isSchemaError = (e: unknown): e is ZodError => e instanceof ZodError;
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────────
|
||||||
// Contexto opcional para enriquecer Problem+JSON (útil en middleware Express)
|
// Contexto opcional para enriquecer Problem+JSON (útil en middleware Express)
|
||||||
// ────────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export abstract class SequelizeDomainMapper<TModel extends Model, TModelAttribut
|
|||||||
public abstract mapToPersistence(
|
public abstract mapToPersistence(
|
||||||
domain: TEntity,
|
domain: TEntity,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Result<TModelAttributes, Error>;
|
): Result<TModelAttributes, Error> | Promise<Result<TModelAttributes, Error>>;
|
||||||
|
|
||||||
public mapToDomainCollection(
|
public mapToDomainCollection(
|
||||||
raws: (TModel | TModelAttributes)[],
|
raws: (TModel | TModelAttributes)[],
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user