Catálogo de régimen de IVA

This commit is contained in:
David Arranz 2026-06-04 18:37:58 +02:00
parent 402b8300b1
commit cea1ab2766
97 changed files with 2587 additions and 21 deletions

View File

@ -1,2 +1,3 @@
export * from "./payment-methods"; export * from "./payment-methods";
export * from "./payment-terms"; export * from "./payment-terms";
export * from "./tax-regimes";

View File

@ -1,6 +1,6 @@
export * from "./payment-term-creator"; export * from "./payment-term-creator.service";
export * from "./payment-term-deleter"; export * from "./payment-term-deleter.service";
export * from "./payment-term-finder"; export * from "./payment-term-finder.service";
export * from "./payment-term-public-services"; export * from "./payment-term-public-services";
export * from "./payment-term-status-changer"; export * from "./payment-term-status-changer.service";
export * from "./payment-term-updater"; export * from "./payment-term-updater.service";

View File

@ -1,11 +1,27 @@
import type { IPaymentTermFinder } from "./payment-term-finder"; import type { IPaymentTermCreator } from "./payment-term-creator.service";
import type { IPaymentTermDeleter } from "./payment-term-deleter.service";
import type { IPaymentTermFinder } from "./payment-term-finder.service";
import type { IPaymentTermStatusChanger } from "./payment-term-status-changer.service";
import type { IPaymentTermUpdater } from "./payment-term-updater.service";
export interface IPaymentTermPublicServices { export interface IPaymentTermPublicServices {
finder: IPaymentTermFinder; finder: IPaymentTermFinder;
creator: IPaymentTermCreator;
updater: IPaymentTermUpdater;
deleter: IPaymentTermDeleter;
statusChanger: IPaymentTermStatusChanger;
} }
export const buildPaymentTermPublicServices = ( export const buildPaymentTermPublicServices = (
finder: IPaymentTermFinder finder: IPaymentTermFinder,
creator: IPaymentTermCreator,
updater: IPaymentTermUpdater,
deleter: IPaymentTermDeleter,
statusChanger: IPaymentTermStatusChanger
): IPaymentTermPublicServices => ({ ): IPaymentTermPublicServices => ({
finder, finder,
creator,
updater,
deleter,
statusChanger,
}); });

View File

@ -1,7 +1,7 @@
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import type { PaymentTerm } from "../../../domain"; import type { PaymentTerm } from "../../../domain";
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface"; import type { IPaymentTermRepository } from "../repositories";
export type PaymentTermStatusChangeAction = "enable" | "disable"; export type PaymentTermStatusChangeAction = "enable" | "disable";

View File

@ -0,0 +1,3 @@
export * from "./tax-regime-input-mappers.di";
export * from "./tax-regime-services.di";
export * from "./tax-regime-snapshot-builders.di";

View File

@ -0,0 +1,20 @@
import {
CreateTaxRegimeInputMapper,
type ICreateTaxRegimeInputMapper,
UpdateTaxRegimeInputMapper,
} from "../mappers";
export interface ITaxRegimeInputMappers {
createInputMapper: ICreateTaxRegimeInputMapper;
updateInputMapper: UpdateTaxRegimeInputMapper;
}
export const buildTaxRegimeInputMappers = (): ITaxRegimeInputMappers => {
const createInputMapper = new CreateTaxRegimeInputMapper();
const updateInputMapper = new UpdateTaxRegimeInputMapper();
return {
createInputMapper,
updateInputMapper,
};
};

View File

@ -0,0 +1,53 @@
import type { ITaxRegimeRepository } from "../repositories";
import {
type ITaxRegimeCreator,
type ITaxRegimeDeleter,
type ITaxRegimeFinder,
type ITaxRegimeStatusChanger,
type ITaxRegimeUpdater,
TaxRegimeCreator,
TaxRegimeDeleter,
TaxRegimeFinder,
TaxRegimeStatusChanger,
TaxRegimeUpdater,
} from "../services";
export function buildTaxRegimeFinder(params: {
repository: ITaxRegimeRepository;
}): ITaxRegimeFinder {
const { repository } = params;
return new TaxRegimeFinder(repository);
}
export const buildTaxRegimeCreator = (params: {
repository: ITaxRegimeRepository;
}): ITaxRegimeCreator => {
const { repository } = params;
return new TaxRegimeCreator(repository);
};
export const buildTaxRegimeDeleter = (params: {
repository: ITaxRegimeRepository;
}): ITaxRegimeDeleter => {
const { repository } = params;
return new TaxRegimeDeleter(repository);
};
export const buildTaxRegimeStatusChanger = (params: {
repository: ITaxRegimeRepository;
}): ITaxRegimeStatusChanger => {
const { repository } = params;
return new TaxRegimeStatusChanger(repository);
};
export const buildTaxRegimeUpdater = (params: {
repository: ITaxRegimeRepository;
}): ITaxRegimeUpdater => {
const { repository } = params;
return new TaxRegimeUpdater(repository);
};

View File

@ -0,0 +1,14 @@
import {
TaxRegimeFullSnapshotBuilder,
TaxRegimeSummarySnapshotBuilder,
} from "../snapshot-builders";
export function buildTaxRegimeSnapshotBuilders() {
const fullSnapshotBuilder = new TaxRegimeFullSnapshotBuilder();
const summarySnapshotBuilder = new TaxRegimeSummarySnapshotBuilder();
return {
full: fullSnapshotBuilder,
summary: summarySnapshotBuilder,
};
}

View File

@ -0,0 +1,6 @@
export * from "./mappers";
export * from "./models";
export * from "./repositories";
export * from "./services";
export * from "./snapshot-builders";
export * from "./use-cases";

View File

@ -0,0 +1,62 @@
import {
DomainError,
TextValue,
UniqueID,
ValidationErrorCollection,
type ValidationErrorDetail,
extractOrPushError,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { CreateTaxRegimeRequestDTO } from "../../../../common";
import { type ITaxRegimeCreateProps, TaxRegimeCode } from "../../../domain";
export interface ICreateTaxRegimeInputMapper {
map(
dto: CreateTaxRegimeRequestDTO,
params: { companyId: UniqueID }
): Result<{ id: UniqueID; props: ITaxRegimeCreateProps }, Error>;
}
export class CreateTaxRegimeInputMapper implements ICreateTaxRegimeInputMapper {
public map(
dto: CreateTaxRegimeRequestDTO,
params: { companyId: UniqueID }
): Result<{ id: UniqueID; props: ITaxRegimeCreateProps }, Error> {
const errors: ValidationErrorDetail[] = [];
try {
const taxRegimeId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
const code = extractOrPushError(TaxRegimeCode.create(dto.code), "code", errors);
const description = extractOrPushError(
TextValue.create(dto.description),
"description",
errors
);
const isActive = extractOrPushError(Result.ok(dto.is_active), "is_active", errors);
this.throwIfValidationErrors(errors);
const props: ITaxRegimeCreateProps = {
companyId: params.companyId,
code: code!,
description: description!,
isActive: isActive ?? true,
isSystem: false,
};
return Result.ok({ id: taxRegimeId!, props });
} catch (err: unknown) {
return Result.fail(new DomainError("Tax regime props mapping failed", { cause: err }));
}
}
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
if (errors.length > 0) {
throw new ValidationErrorCollection("Tax regime props mapping failed", errors);
}
}
}

View File

@ -0,0 +1,2 @@
export * from "./create-tax-regime-input.mapper";
export * from "./update-tax-regime-by-id-input.mapper";

View File

@ -0,0 +1,63 @@
import {
DomainError,
TextValue,
type UniqueID,
ValidationErrorCollection,
type ValidationErrorDetail,
extractOrPushError,
} from "@repo/rdx-ddd";
import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
import type { UpdateTaxRegimeByIdRequestDTO } from "../../../../common";
import type { TaxRegimePatchProps } from "../../../domain";
export interface IUpdateTaxRegimeInputMapper {
map(
dto: UpdateTaxRegimeByIdRequestDTO,
_params: { companyId: UniqueID }
): Result<TaxRegimePatchProps>;
}
export class UpdateTaxRegimeInputMapper implements IUpdateTaxRegimeInputMapper {
public map(
dto: UpdateTaxRegimeByIdRequestDTO,
_params: { companyId: UniqueID }
): Result<TaxRegimePatchProps> {
try {
const errors: ValidationErrorDetail[] = [];
const taxRegimePatchProps: TaxRegimePatchProps = {};
toPatchField(dto.description).ifSet((description) => {
if (isNullishOrEmpty(description)) {
errors.push({ path: "description", message: "Description cannot be empty" });
return;
}
taxRegimePatchProps.description = extractOrPushError(
TextValue.create(description),
"description",
errors
);
});
toPatchField(dto.is_active).ifSet((isActive) => {
taxRegimePatchProps.isActive = isActive;
});
this.throwIfValidationErrors(errors);
return Result.ok(taxRegimePatchProps);
} catch (err: unknown) {
console.error(err);
return Result.fail(
new DomainError("Tax regime props mapping failed (update)", { cause: err })
);
}
}
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
if (errors.length > 0) {
throw new ValidationErrorCollection("Tax regime props mapping failed", errors);
}
}
}

View File

@ -0,0 +1,2 @@
export * from "./tax-regime-detail.model";
export * from "./tax-regime-summary.model";

View File

@ -0,0 +1,11 @@
import type { TaxRegimeCode } from "@erp/catalogs/api/domain";
import type { TextValue, UniqueID } from "@repo/rdx-ddd";
export type TaxRegimeDetail = {
id: UniqueID;
companyId: UniqueID;
code: TaxRegimeCode;
description: TextValue;
is_active: boolean;
is_system: boolean;
};

View File

@ -0,0 +1,11 @@
import type { TaxRegimeCode } from "@erp/catalogs/api/domain";
import type { TextValue, UniqueID } from "@repo/rdx-ddd";
export type TaxRegimeSummary = {
id: UniqueID;
companyId: UniqueID;
code: TaxRegimeCode;
description: TextValue;
isActive: boolean;
isSystem: boolean;
};

View File

@ -0,0 +1 @@
export * from "./tax-regime-repository.interface";

View File

@ -0,0 +1,40 @@
import type { Criteria } from "@repo/rdx-criteria/server";
import type { UniqueID } from "@repo/rdx-ddd";
import type { Collection, Result } from "@repo/rdx-utils";
import type { TaxRegime, TaxRegimeCode } from "../../../domain";
import type { TaxRegimeSummary } from "../models";
export interface ITaxRegimeRepository {
create(taxRegime: TaxRegime, transaction?: unknown): Promise<Result<void, Error>>;
update(taxRegime: TaxRegime, transaction?: unknown): Promise<Result<void, Error>>;
deleteByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction: unknown
): Promise<Result<boolean, Error>>;
existsByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction?: unknown
): Promise<Result<boolean, Error>>;
getByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction?: unknown
): Promise<Result<TaxRegime, Error>>;
getByCodeInCompany(
companyId: UniqueID,
code: TaxRegimeCode,
transaction?: unknown
): Promise<Result<TaxRegime, Error>>;
findByCriteriaInCompany(
companyId: UniqueID,
criteria: Criteria,
transaction?: unknown
): Promise<Result<Collection<TaxRegimeSummary>, Error>>;
}

View File

@ -0,0 +1,6 @@
export * from "./tax-regime-creator.service";
export * from "./tax-regime-deleter.service";
export * from "./tax-regime-finder.service";
export * from "./tax-regime-public-services";
export * from "./tax-regime-status-changer.service";
export * from "./tax-regime-updater.service";

View File

@ -0,0 +1,39 @@
import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { type ITaxRegimeCreateProps, TaxRegime } from "../../../domain";
import type { ITaxRegimeRepository } from "../repositories";
export interface ITaxRegimeCreatorParams {
companyId: UniqueID;
id: UniqueID;
props: ITaxRegimeCreateProps;
transaction: unknown;
}
export interface ITaxRegimeCreator {
create(params: ITaxRegimeCreatorParams): Promise<Result<TaxRegime, Error>>;
}
export class TaxRegimeCreator implements ITaxRegimeCreator {
constructor(private readonly repository: ITaxRegimeRepository) {}
public async create(params: ITaxRegimeCreatorParams): Promise<Result<TaxRegime, Error>> {
const { companyId, id, props, transaction } = params;
// Create aggregate
const taxRegimeResult = TaxRegime.create({ ...props, companyId }, id);
if (taxRegimeResult.isFailure) {
return taxRegimeResult;
}
const taxRegime = taxRegimeResult.data;
const saveResult = await this.repository.create(taxRegime, transaction);
if (saveResult.isFailure) {
return Result.fail(saveResult.error);
}
return Result.ok(taxRegime);
}
}

View File

@ -0,0 +1,38 @@
import { Result } from "@repo/rdx-utils";
import { type TaxRegime, TaxRegimeCannotBeDeletedError } from "../../../domain";
import type { ITaxRegimeRepository } from "../repositories";
export interface ITaxRegimeDeleter {
delete(params: {
taxRegime: TaxRegime;
transaction?: unknown;
}): Promise<Result<TaxRegime, Error>>;
}
export class TaxRegimeDeleter implements ITaxRegimeDeleter {
public constructor(private readonly repository: ITaxRegimeRepository) {}
public async delete(params: {
taxRegime: TaxRegime;
transaction?: unknown;
}): Promise<Result<TaxRegime, Error>> {
const { taxRegime, transaction } = params;
if (taxRegime.isSystem) {
return Result.fail(new TaxRegimeCannotBeDeletedError("System tax regime cannot be deleted."));
}
const deleteResult = await this.repository.deleteByIdInCompany(
taxRegime.companyId,
taxRegime.id,
transaction
);
if (deleteResult.isFailure) {
return Result.fail(deleteResult.error);
}
return Result.ok(taxRegime);
}
}

View File

@ -0,0 +1,69 @@
import type { Criteria } from "@repo/rdx-criteria/server";
import type { UniqueID } from "@repo/rdx-ddd";
import type { Collection, Result } from "@repo/rdx-utils";
import type { TaxRegime, TaxRegimeCode } from "../../../domain";
import type { TaxRegimeSummary } from "../models";
import type { ITaxRegimeRepository } from "../repositories";
export interface ITaxRegimeFinder {
findTaxRegimeById(
companyId: UniqueID,
id: UniqueID,
transaction?: unknown
): Promise<Result<TaxRegime, Error>>;
findTaxRegimeByCode(
companyId: UniqueID,
code: TaxRegimeCode,
transaction?: unknown
): Promise<Result<TaxRegime, Error>>;
taxRegimeExists(
companyId: UniqueID,
id: UniqueID,
transaction?: unknown
): Promise<Result<boolean, Error>>;
findTaxRegimesByCriteria(
companyId: UniqueID,
criteria: Criteria,
transaction?: unknown
): Promise<Result<Collection<TaxRegimeSummary>, Error>>;
}
export class TaxRegimeFinder implements ITaxRegimeFinder {
constructor(private readonly repository: ITaxRegimeRepository) {}
async findTaxRegimeById(
companyId: UniqueID,
taxRegimeId: UniqueID,
transaction?: unknown
): Promise<Result<TaxRegime, Error>> {
return this.repository.getByIdInCompany(companyId, taxRegimeId, transaction);
}
async findTaxRegimeByCode(
companyId: UniqueID,
code: TaxRegimeCode,
transaction?: unknown
): Promise<Result<TaxRegime, Error>> {
return this.repository.getByCodeInCompany(companyId, code, transaction);
}
async taxRegimeExists(
companyId: UniqueID,
taxRegimeId: UniqueID,
transaction?: unknown
): Promise<Result<boolean, Error>> {
return this.repository.existsByIdInCompany(companyId, taxRegimeId, transaction);
}
async findTaxRegimesByCriteria(
companyId: UniqueID,
criteria: Criteria,
transaction?: unknown
): Promise<Result<Collection<TaxRegimeSummary>, Error>> {
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
}
}

View File

@ -0,0 +1,27 @@
import type { ITaxRegimeCreator } from "./tax-regime-creator.service";
import type { ITaxRegimeDeleter } from "./tax-regime-deleter.service";
import type { ITaxRegimeFinder } from "./tax-regime-finder.service";
import type { ITaxRegimeStatusChanger } from "./tax-regime-status-changer.service";
import type { ITaxRegimeUpdater } from "./tax-regime-updater.service";
export interface ITaxRegimePublicServices {
finder: ITaxRegimeFinder;
creator: ITaxRegimeCreator;
updater: ITaxRegimeUpdater;
deleter: ITaxRegimeDeleter;
statusChanger: ITaxRegimeStatusChanger;
}
export const buildTaxRegimePublicServices = (
finder: ITaxRegimeFinder,
creator: ITaxRegimeCreator,
updater: ITaxRegimeUpdater,
deleter: ITaxRegimeDeleter,
statusChanger: ITaxRegimeStatusChanger
): ITaxRegimePublicServices => ({
finder,
creator,
updater,
deleter,
statusChanger,
});

View File

@ -0,0 +1,42 @@
import type { TaxRegime } from "@erp/catalogs/api/domain";
import { Result } from "@repo/rdx-utils";
import type { ITaxRegimeRepository } from "../repositories";
export type TaxRegimeStatusChangeAction = "enable" | "disable";
export interface ITaxRegimeStatusChanger {
changeStatus(params: {
taxRegime: TaxRegime;
action: TaxRegimeStatusChangeAction;
transaction?: unknown;
}): Promise<Result<TaxRegime, Error>>;
}
export class TaxRegimeStatusChanger implements ITaxRegimeStatusChanger {
public constructor(private readonly repository: ITaxRegimeRepository) {}
public async changeStatus(params: {
taxRegime: TaxRegime;
action: TaxRegimeStatusChangeAction;
transaction?: unknown;
}): Promise<Result<TaxRegime, Error>> {
const { taxRegime, action, transaction } = params;
const statusResult = action === "enable" ? taxRegime.enable() : taxRegime.disable();
if (statusResult.isFailure) {
return Result.fail(statusResult.error);
}
if (!statusResult.data) {
return Result.ok(taxRegime);
}
const persistenceResult = await this.repository.update(taxRegime, transaction);
if (persistenceResult.isFailure) {
return Result.fail(persistenceResult.error);
}
return Result.ok(taxRegime);
}
}

View File

@ -0,0 +1,52 @@
import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { TaxRegime, TaxRegimePatchProps } from "../../../domain";
import type { ITaxRegimeRepository } from "../repositories";
export interface ITaxRegimeUpdater {
update(params: {
companyId: UniqueID;
id: UniqueID;
patchProps: TaxRegimePatchProps;
transaction?: unknown;
}): Promise<Result<TaxRegime, Error>>;
}
export class TaxRegimeUpdater implements ITaxRegimeUpdater {
constructor(private readonly repository: ITaxRegimeRepository) {}
public async update(params: {
companyId: UniqueID;
id: UniqueID;
patchProps: TaxRegimePatchProps;
transaction?: unknown;
}): Promise<Result<TaxRegime, Error>> {
const { companyId, id, patchProps, transaction } = params;
const existingResult = await this.repository.getByIdInCompany(companyId, id, transaction);
if (existingResult.isFailure) {
return Result.fail(existingResult.error);
}
const taxRegime = existingResult.data;
// Aplicar cambios en el agregado
const updateResult = taxRegime.update(patchProps);
if (updateResult.isFailure) {
return Result.fail(updateResult.error);
}
if (!updateResult.data) {
return Result.ok(taxRegime);
}
// Persistir cambios
const saveResult = await this.repository.update(taxRegime, transaction);
if (saveResult.isFailure) {
return Result.fail(saveResult.error);
}
return Result.ok(taxRegime);
}
}

View File

@ -0,0 +1,20 @@
import type { GetTaxRegimeByIdResponseDTO } from "@erp/catalogs/common";
import type { ISnapshotBuilder } from "@erp/core/api";
import type { TaxRegime } from "../../../../domain";
export interface ITaxRegimeFullSnapshotBuilder
extends ISnapshotBuilder<TaxRegime, GetTaxRegimeByIdResponseDTO> {}
export class TaxRegimeFullSnapshotBuilder implements ITaxRegimeFullSnapshotBuilder {
public toOutput(taxRegime: TaxRegime): GetTaxRegimeByIdResponseDTO {
return {
id: taxRegime.code.toPrimitive(),
company_id: taxRegime.companyId.toPrimitive(),
code: taxRegime.code.toPrimitive(),
description: taxRegime.description.toPrimitive(),
is_active: taxRegime.isActive,
is_system: taxRegime.isSystem,
};
}
}

View File

@ -0,0 +1 @@
export * from "./full-tax-regime-snapshot.builder";

View File

@ -0,0 +1,2 @@
export * from "./full";
export * from "./summary";

View File

@ -0,0 +1 @@
export * from "./summary-tax-regime-snapshot.builder";

View File

@ -0,0 +1,20 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import type { TaxRegimeSummaryDTO } from "../../../../../common";
import type { TaxRegimeSummary } from "../../models";
export interface ITaxRegimeSummarySnapshotBuilder
extends ISnapshotBuilder<TaxRegimeSummary, TaxRegimeSummaryDTO> {}
export class TaxRegimeSummarySnapshotBuilder implements ITaxRegimeSummarySnapshotBuilder {
public toOutput(taxRegime: TaxRegimeSummary): TaxRegimeSummaryDTO {
return {
id: taxRegime.id.toString(),
company_id: taxRegime.companyId.toString(),
code: taxRegime.code.toPrimitive(),
description: taxRegime.description.toPrimitive(),
is_active: taxRegime.isActive,
is_system: taxRegime.isSystem,
};
}
}

View File

@ -0,0 +1,55 @@
import type { CreateTaxRegimeRequestDTO } from "@erp/catalogs/common";
import type { ITransactionManager } from "@erp/core/api";
import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { ICreateTaxRegimeInputMapper } from "../mappers";
import type { ITaxRegimeCreator } from "../services";
import type { ITaxRegimeFullSnapshotBuilder } from "../snapshot-builders";
export type CreateTaxRegimeUseCaseInput = {
companyId: UniqueID;
dto: CreateTaxRegimeRequestDTO;
};
type CreateTaxRegimeUseCaseDeps = {
dtoMapper: ICreateTaxRegimeInputMapper;
creator: ITaxRegimeCreator;
fullSnapshotBuilder: ITaxRegimeFullSnapshotBuilder;
transactionManager: ITransactionManager;
};
export class CreateTaxRegimeUseCase {
constructor(private readonly deps: CreateTaxRegimeUseCaseDeps) {}
public execute(params: CreateTaxRegimeUseCaseInput) {
const { dto, companyId } = params;
const mappedPropsResult = this.deps.dtoMapper.map(dto, { companyId });
if (mappedPropsResult.isFailure) {
return mappedPropsResult;
}
const { props, id } = mappedPropsResult.data;
return this.deps.transactionManager.complete(async (transaction: unknown) => {
try {
const createResult = await this.deps.creator.create({
companyId,
id,
props,
transaction,
});
if (createResult.isFailure) {
return createResult;
}
const snapshot = this.deps.fullSnapshotBuilder.toOutput(createResult.data);
return Result.ok(snapshot);
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -0,0 +1,57 @@
import type { ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { ITaxRegimeDeleter, ITaxRegimeFinder } from "../services";
import type { ITaxRegimeFullSnapshotBuilder } from "../snapshot-builders";
export type DeleteTaxRegimeByIdUseCaseInput = {
companyId: UniqueID;
tax_regime_id: string;
};
export class DeleteTaxRegimeByIdUseCase {
constructor(
private readonly deps: {
finder: ITaxRegimeFinder;
deleter: ITaxRegimeDeleter;
fullSnapshotBuilder: ITaxRegimeFullSnapshotBuilder;
transactionManager: ITransactionManager;
}
) {}
public execute(params: DeleteTaxRegimeByIdUseCaseInput) {
const { tax_regime_id, companyId } = params;
const idOrError = UniqueID.create(tax_regime_id);
if (idOrError.isFailure) return Result.fail(idOrError.error);
const taxRegimeId = idOrError.data;
return this.deps.transactionManager.complete(async (transaction: unknown) => {
try {
const findResult = await this.deps.finder.findTaxRegimeById(
companyId,
taxRegimeId,
transaction
);
if (findResult.isFailure) {
return Result.fail(findResult.error);
}
const deleteResult = await this.deps.deleter.delete({
taxRegime: findResult.data,
transaction,
});
if (deleteResult.isFailure) {
return Result.fail(deleteResult.error);
}
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(deleteResult.data));
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -0,0 +1,58 @@
import type { ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { ITaxRegimeFinder, ITaxRegimeStatusChanger } from "../services";
import type { ITaxRegimeFullSnapshotBuilder } from "../snapshot-builders";
export type DisableTaxRegimeByIdUseCaseInput = {
companyId: UniqueID;
tax_regime_id: string;
};
export class DisableTaxRegimeByIdUseCase {
constructor(
private readonly deps: {
finder: ITaxRegimeFinder;
changer: ITaxRegimeStatusChanger;
fullSnapshotBuilder: ITaxRegimeFullSnapshotBuilder;
transactionManager: ITransactionManager;
}
) {}
public execute(params: DisableTaxRegimeByIdUseCaseInput) {
const { tax_regime_id, companyId } = params;
const idOrError = UniqueID.create(tax_regime_id);
if (idOrError.isFailure) return Result.fail(idOrError.error);
const taxRegimeId = idOrError.data;
return this.deps.transactionManager.complete(async (transaction: unknown) => {
try {
const findResult = await this.deps.finder.findTaxRegimeById(
companyId,
taxRegimeId,
transaction
);
if (findResult.isFailure) {
return Result.fail(findResult.error);
}
const disableResult = await this.deps.changer.changeStatus({
taxRegime: findResult.data,
action: "disable",
transaction,
});
if (disableResult.isFailure) {
return Result.fail(disableResult.error);
}
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(disableResult.data));
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -0,0 +1,58 @@
import type { ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { ITaxRegimeFinder, ITaxRegimeStatusChanger } from "../services";
import type { ITaxRegimeFullSnapshotBuilder } from "../snapshot-builders";
export type EnableTaxRegimeByIdUseCaseInput = {
companyId: UniqueID;
tax_regime_id: string;
};
export class EnableTaxRegimeByIdUseCase {
constructor(
private readonly deps: {
finder: ITaxRegimeFinder;
changer: ITaxRegimeStatusChanger;
fullSnapshotBuilder: ITaxRegimeFullSnapshotBuilder;
transactionManager: ITransactionManager;
}
) {}
public execute(params: EnableTaxRegimeByIdUseCaseInput) {
const { tax_regime_id, companyId } = params;
const idOrError = UniqueID.create(tax_regime_id);
if (idOrError.isFailure) return Result.fail(idOrError.error);
const taxRegimeId = idOrError.data;
return this.deps.transactionManager.complete(async (transaction: unknown) => {
try {
const findResult = await this.deps.finder.findTaxRegimeById(
companyId,
taxRegimeId,
transaction
);
if (findResult.isFailure) {
return Result.fail(findResult.error);
}
const enableResult = await this.deps.changer.changeStatus({
taxRegime: findResult.data,
action: "enable",
transaction,
});
if (enableResult.isFailure) {
return Result.fail(enableResult.error);
}
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(enableResult.data));
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -0,0 +1,44 @@
import type { ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { ITaxRegimeFinder } from "../services";
import type { ITaxRegimeFullSnapshotBuilder } from "../snapshot-builders";
export type GetTaxRegimeByIdUseCaseInput = {
companyId: UniqueID;
tax_regime_id: string;
};
export class GetTaxRegimeByIdUseCase {
constructor(
private readonly finder: ITaxRegimeFinder,
private readonly fullSnapshotBuilder: ITaxRegimeFullSnapshotBuilder,
private readonly transactionManager: ITransactionManager
) {}
public execute(params: GetTaxRegimeByIdUseCaseInput) {
const { tax_regime_id, companyId } = params;
const idOrError = UniqueID.create(tax_regime_id);
if (idOrError.isFailure) {
return Result.fail(idOrError.error);
}
const taxRegimeId = idOrError.data;
return this.transactionManager.complete(async (transaction: unknown) => {
try {
const result = await this.finder.findTaxRegimeById(companyId, taxRegimeId, transaction);
if (result.isFailure) {
return Result.fail(result.error);
}
return Result.ok(this.fullSnapshotBuilder.toOutput(result.data));
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -0,0 +1,7 @@
export * from "./create-tax-regime.use-case";
export * from "./delete-tax-regime-by-id.use-case";
export * from "./disable-tax-regime-by-id.use-case";
export * from "./enable-tax-regime-by-id.use-case";
export * from "./get-tax-regime-by-id.use-case";
export * from "./list-tax-regimes.use-case";
export * from "./update-tax-regime-by-id.use-case";

View File

@ -0,0 +1,55 @@
import type { ITransactionManager } from "@erp/core/api";
import type { Criteria } from "@repo/rdx-criteria/server";
import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { ITaxRegimeFinder } from "../services";
import type { ITaxRegimeSummarySnapshotBuilder } from "../snapshot-builders";
type ListTaxRegimesUseCaseInput = {
companyId: UniqueID;
criteria: Criteria;
};
export class ListTaxRegimesUseCase {
constructor(
private readonly finder: ITaxRegimeFinder,
private readonly summarySnapshotBuilder: ITaxRegimeSummarySnapshotBuilder,
private readonly transactionManager: ITransactionManager
) {}
public execute(params: ListTaxRegimesUseCaseInput) {
const { criteria, companyId } = params;
return this.transactionManager.complete(async (transaction: unknown) => {
try {
const result = await this.finder.findTaxRegimesByCriteria(companyId, criteria, transaction);
if (result.isFailure) {
return Result.fail(result.error);
}
const taxRegimes = result.data;
const totalItems = taxRegimes.total();
const items = taxRegimes.map((item) => this.summarySnapshotBuilder.toOutput(item));
const snapshot = {
page: criteria.pageNumber,
per_page: criteria.pageSize,
total_pages: Math.ceil(totalItems / criteria.pageSize),
total_items: totalItems,
items,
metadata: {
entity: "tax_regimes",
criteria: criteria.toJSON(),
},
};
return Result.ok(snapshot);
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -0,0 +1,63 @@
import type { UpdateTaxRegimeByIdRequestDTO } from "@erp/catalogs/common";
import type { ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { IUpdateTaxRegimeInputMapper } from "../mappers";
import type { ITaxRegimeFinder, ITaxRegimeUpdater } from "../services";
import type { ITaxRegimeFullSnapshotBuilder } from "../snapshot-builders";
export type UpdateTaxRegimeByIdUseCaseInput = {
companyId: UniqueID;
tax_regime_id: string;
dto: UpdateTaxRegimeByIdRequestDTO;
};
export class UpdateTaxRegimeByIdUseCase {
constructor(
private readonly deps: {
updater: ITaxRegimeUpdater;
finder: ITaxRegimeFinder;
dtoMapper: IUpdateTaxRegimeInputMapper;
fullSnapshotBuilder: ITaxRegimeFullSnapshotBuilder;
transactionManager: ITransactionManager;
}
) {}
public execute(params: UpdateTaxRegimeByIdUseCaseInput) {
const { companyId, tax_regime_id, dto } = params;
const idOrError = UniqueID.create(tax_regime_id);
if (idOrError.isFailure) {
return Result.fail(idOrError.error);
}
const taxRegimeId = idOrError.data;
const patchPropsResult = this.deps.dtoMapper.map(dto, { companyId });
if (patchPropsResult.isFailure) {
return patchPropsResult;
}
const patchProps = patchPropsResult.data;
return this.deps.transactionManager.complete(async (transaction: unknown) => {
try {
const updateResult = await this.deps.updater.update({
companyId,
id: taxRegimeId,
patchProps,
transaction,
});
if (updateResult.isFailure) {
return Result.fail(updateResult.error);
}
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(updateResult.data));
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -1,2 +1,3 @@
export * from "./payment-methods"; export * from "./payment-methods";
export * from "./payment-terms"; export * from "./payment-terms";
export * from "./tax-regimes";

View File

@ -154,7 +154,7 @@ export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
return this._dueRules; return this._dueRules;
} }
public update(patchProps: PaymentTermPatchProps): Result<void, Error> { public update(patchProps: PaymentTermPatchProps): Result<boolean, Error> {
if (this.isSystem) { if (this.isSystem) {
return Result.fail( return Result.fail(
new PaymentTermCannotBeUpdatedError("System payment terms cannot be updated.") new PaymentTermCannotBeUpdatedError("System payment terms cannot be updated.")
@ -166,6 +166,7 @@ export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
return Result.fail(validationResult.error); return Result.fail(validationResult.error);
} }
let hasChanges = false;
const { dueRules, ...otherProps } = patchProps; const { dueRules, ...otherProps } = patchProps;
const candidateProps: PaymentTermInternalProps = { const candidateProps: PaymentTermInternalProps = {
@ -175,6 +176,7 @@ export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
// Aplicar cambios // Aplicar cambios
Object.assign(this.props, candidateProps); Object.assign(this.props, candidateProps);
hasChanges = true;
// Reemplazo de items (si se proporciona) // Reemplazo de items (si se proporciona)
if (patchProps.dueRules !== undefined) { if (patchProps.dueRules !== undefined) {
@ -184,7 +186,7 @@ export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
} }
} }
return Result.ok(); return Result.ok(hasChanges);
} }
public disable(): Result<boolean, Error> { public disable(): Result<boolean, Error> {

View File

@ -0,0 +1,66 @@
import { DomainError } from "@repo/rdx-ddd";
export class InvalidTaxRegimeIdError extends DomainError {
public readonly code = "TAX_REGIME_INVALID_ID" as const;
}
export const isInvalidTaxRegimeIdError = (e: unknown): e is InvalidTaxRegimeIdError =>
e instanceof InvalidTaxRegimeIdError;
export class InvalidTaxRegimeCodeError extends DomainError {
public readonly code = "TAX_REGIME_INVALID_CODE" as const;
}
export const isInvalidTaxRegimeCodeError = (e: unknown): e is InvalidTaxRegimeCodeError =>
e instanceof InvalidTaxRegimeCodeError;
//
export class InvalidTaxRegimeDescriptionError extends DomainError {
public readonly code = "TAX_REGIME_DESCRIPTION" as const;
}
export const isInvalidTaxRegimeDescriptionError = (
e: unknown
): e is InvalidTaxRegimeDescriptionError => e instanceof InvalidTaxRegimeDescriptionError;
//
export class TaxRegimeNotFoundError extends DomainError {
public readonly code = "TAX_REGIME_NOT_FOUND" as const;
}
export const isTaxRegimeNotFoundError = (e: unknown): e is TaxRegimeNotFoundError =>
e instanceof TaxRegimeNotFoundError;
//
export class TaxRegimeCannotBeDeletedError extends DomainError {
public readonly code = "TAX_REGIME_CANNOT_BE_DELETED" as const;
}
export const isTaxRegimeCannotBeDeletedError = (e: unknown): e is TaxRegimeCannotBeDeletedError =>
e instanceof TaxRegimeCannotBeDeletedError;
//
export class TaxRegimeCannotBeUpdatedError extends DomainError {
public readonly code = "TAX_REGIME_CANNOT_BE_UPDATED" as const;
}
export const isTaxRegimeCannotBeUpdatedError = (e: unknown): e is TaxRegimeCannotBeUpdatedError =>
e instanceof TaxRegimeCannotBeUpdatedError;
export class TaxRegimeCannotBeEnabledError extends DomainError {
public readonly code = "TAX_REGIME_CANNOT_BE_ENABLED" as const;
}
export const isTaxRegimeCannotBeEnabledError = (e: unknown): e is TaxRegimeCannotBeEnabledError =>
e instanceof TaxRegimeCannotBeEnabledError;
export class TaxRegimeCannotBeDisabledError extends DomainError {
public readonly code = "TAX_REGIME_CANNOT_BE_DISABLED" as const;
}
export const isTaxRegimeCannotBeDisabledError = (e: unknown): e is TaxRegimeCannotBeDisabledError =>
e instanceof TaxRegimeCannotBeDisabledError;

View File

@ -0,0 +1,3 @@
export * from "./errors";
export * from "./tax-regime.aggregate";
export * from "./tax-regime-code";

View File

@ -0,0 +1,28 @@
import { Result } from "@repo/rdx-utils";
import { InvalidTaxRegimeCodeError } from "./errors";
export class TaxRegimeCode {
private constructor(private readonly value: string) {}
public static create(code: string): Result<TaxRegimeCode, Error> {
const trimmed = code?.trim() ?? "";
if (trimmed.length === 0) {
return Result.fail(new InvalidTaxRegimeCodeError("Tax regime code cannot be empty"));
}
return Result.ok(new TaxRegimeCode(trimmed));
}
public static fromPersistence(code: string): TaxRegimeCode {
return new TaxRegimeCode(code);
}
public toString(): string {
return this.value;
}
public toPrimitive(): string {
return this.value;
}
}

View File

@ -0,0 +1,158 @@
import { AggregateRoot, type TextValue, type UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { TaxRegimeCannotBeUpdatedError } from "./errors";
import type { TaxRegimeCode } from "./tax-regime-code";
export interface ITaxRegimeCreateProps {
companyId: UniqueID;
code: TaxRegimeCode;
description: TextValue;
isActive: boolean;
isSystem: boolean;
}
export type TaxRegimePatchProps = Partial<{
description: TextValue;
isActive: boolean;
}>;
export type TaxRegimeInternalProps = ITaxRegimeCreateProps;
export class TaxRegime extends AggregateRoot<TaxRegimeInternalProps> {
protected constructor(props: TaxRegimeInternalProps, id?: UniqueID) {
super(props, id); // eslint-disable-line @typescript-eslint/no-unused-vars
}
public static create(props: ITaxRegimeCreateProps, id?: UniqueID): Result<TaxRegime, Error> {
const validationResult = TaxRegime.validateCreateProps(props);
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
}
// Crear instancia
const taxRegime = new TaxRegime(props, id);
// Disparar eventos de dominio
// ...
// ...
return Result.ok(taxRegime);
}
public static rehydrate(props: TaxRegimeInternalProps, id: UniqueID): TaxRegime {
return new TaxRegime(props, id);
}
private static validateCreateProps(props: ITaxRegimeCreateProps): Result<void, Error> {
if (!props.companyId) {
return Result.fail(new Error("Payment term company ID is required"));
}
if (!props.code) {
return Result.fail(new Error("Tax regime code is required"));
}
return Result.ok();
}
private static validatePatchProps(patchProps: TaxRegimePatchProps): Result<void, Error> {
if (Object.keys(patchProps).length === 0) {
return Result.ok();
}
return Result.ok();
}
public get companyId(): UniqueID {
return this.props.companyId;
}
public get code(): TaxRegimeCode {
return this.props.code;
}
public get description(): TextValue {
return this.props.description;
}
public get isActive(): boolean {
return this.props.isActive;
}
public get isSystem(): boolean {
return this.props.isSystem;
}
public update(patchProps: TaxRegimePatchProps): Result<boolean, Error> {
if (this.isSystem) {
return Result.fail(
new TaxRegimeCannotBeUpdatedError("System tax regimes cannot be updated.")
);
}
const validationResult = TaxRegime.validatePatchProps(patchProps);
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
}
let hasChanges = false;
if (patchProps.description !== undefined) {
if (!TaxRegime.sameDescription(this.props.description, patchProps.description)) {
this.props.description = patchProps.description;
hasChanges = true;
}
}
if (patchProps.isActive !== undefined && this.props.isActive !== patchProps.isActive) {
this.props.isActive = patchProps.isActive;
hasChanges = true;
}
return Result.ok(hasChanges);
}
public disable(): Result<boolean, Error> {
if (this.isSystem) {
return Result.fail(
new TaxRegimeCannotBeUpdatedError("System tax regimes cannot be disabled.")
);
}
if (!this.isActive) {
return Result.ok(false);
}
this.props.isActive = false;
return Result.ok(true);
}
public enable(): Result<boolean, Error> {
if (this.isSystem) {
return Result.ok(false);
}
if (this.isActive) {
return Result.ok(false);
}
this.props.isActive = true;
return Result.ok(true);
}
public toJSON() {
return {
id: this.id.toPrimitive(),
code: this.code.toPrimitive(),
description: this.description.toPrimitive(),
is_active: this.isActive,
is_system: this.isSystem,
};
}
private static sameDescription(current: TextValue, next: TextValue): boolean {
return current.toPrimitive() === next.toPrimitive();
}
}

View File

@ -5,6 +5,8 @@ import {
paymentMethodsRouter, paymentMethodsRouter,
paymentTermModels, paymentTermModels,
paymentTermsRouter, paymentTermsRouter,
taxRegimeModels,
taxRegimesRouter,
} from "./infrastructure"; } from "./infrastructure";
import { import {
buildCatalogsDependencies, buildCatalogsDependencies,
@ -27,10 +29,11 @@ export const catalogsAPIModule: IModuleServer = {
}); });
return { return {
models: [...paymentMethodModels, ...paymentTermModels], models: [...paymentMethodModels, ...paymentTermModels, ...taxRegimeModels],
services: { services: {
paymentMethod: publicServices.paymentMethods, paymentMethod: publicServices.paymentMethods,
paymentTerms: publicServices.paymentTerms, paymentTerms: publicServices.paymentTerms,
taxRegimes: publicServices.taxRegimes,
}, },
internal, internal,
}; };
@ -41,6 +44,7 @@ export const catalogsAPIModule: IModuleServer = {
paymentMethodsRouter(params); paymentMethodsRouter(params);
paymentTermsRouter(params); paymentTermsRouter(params);
taxRegimesRouter(params);
logger.info("🚀 Catalogs module started", { logger.info("🚀 Catalogs module started", {
label: this.name, label: this.name,

View File

@ -10,16 +10,23 @@ import {
buildPaymentTermsDependencies, buildPaymentTermsDependencies,
buildPaymentTermsPublicServices, buildPaymentTermsPublicServices,
} from "../payment-terms/di"; } from "../payment-terms/di";
import {
type TaxRegimesInternalDeps,
buildTaxRegimesDependencies,
buildTaxRegimesPublicServices,
} from "../tax-regimes/di";
export type CatalogsInternalDeps = { export type CatalogsInternalDeps = {
paymentMethods: PaymentMethodsInternalDeps; paymentMethods: PaymentMethodsInternalDeps;
paymentTerms: PaymentTermsInternalDeps; paymentTerms: PaymentTermsInternalDeps;
taxRegimes: TaxRegimesInternalDeps;
}; };
export const buildCatalogsDependencies = (params: ModuleParams): CatalogsInternalDeps => { export const buildCatalogsDependencies = (params: ModuleParams): CatalogsInternalDeps => {
return { return {
paymentMethods: buildPaymentMethodsDependencies(params), paymentMethods: buildPaymentMethodsDependencies(params),
paymentTerms: buildPaymentTermsDependencies(params), paymentTerms: buildPaymentTermsDependencies(params),
taxRegimes: buildTaxRegimesDependencies(params),
}; };
}; };
@ -27,5 +34,6 @@ 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),
taxRegimes: buildTaxRegimesPublicServices(params, deps.taxRegimes),
}; };
}; };

View File

@ -1,2 +1,3 @@
export * from "./payment-methods"; export * from "./payment-methods";
export * from "./payment-terms"; export * from "./payment-terms";
export * from "./tax-regimes";

View File

@ -1,13 +1,11 @@
import { import {
SequelizePaymentMethodDomainMapper, SequelizePaymentMethodDomainMapper,
SequelizePaymentMethodSummaryMapper, SequelizePaymentMethodSummaryMapper,
} from "../persistence/index"; } from "../persistence";
export interface IPaymentMethodPersistenceMappers { export interface IPaymentMethodPersistenceMappers {
domainMapper: SequelizePaymentMethodDomainMapper; domainMapper: SequelizePaymentMethodDomainMapper;
listMapper: SequelizePaymentMethodSummaryMapper; listMapper: SequelizePaymentMethodSummaryMapper;
//createMapper: CreatePaymentMethodRequestMapper;
} }
export const buildPaymentMethodPersistenceMappers = (): IPaymentMethodPersistenceMappers => { export const buildPaymentMethodPersistenceMappers = (): IPaymentMethodPersistenceMappers => {
@ -15,13 +13,8 @@ export const buildPaymentMethodPersistenceMappers = (): IPaymentMethodPersistenc
const domainMapper = new SequelizePaymentMethodDomainMapper(); const domainMapper = new SequelizePaymentMethodDomainMapper();
const listMapper = new SequelizePaymentMethodSummaryMapper(); const listMapper = new SequelizePaymentMethodSummaryMapper();
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
//const createMapper = new CreatePaymentMethodRequestMapper(catalogs);
return { return {
domainMapper, domainMapper,
listMapper, listMapper,
//createMapper,
}; };
}; };

View File

@ -173,8 +173,6 @@ export class SequelizePaymentMethodRepository
strictMode: true, // fuerza error si ORDER BY no permitido strictMode: true, // fuerza error si ORDER BY no permitido
}); });
console.log(query?.where);
query.where = { query.where = {
...query.where, ...query.where,
company_id: companyId.toString(), company_id: companyId.toString(),

View File

@ -0,0 +1,3 @@
export * from "./tax-regime-persistence-mappers.di";
export * from "./tax-regime-repositories.di";
export * from "./tax-regimes.di";

View File

@ -0,0 +1,17 @@
import { SequelizeTaxRegimeDomainMapper, SequelizeTaxRegimeSummaryMapper } from "../persistence";
export interface ITaxRegimePersistenceMappers {
domainMapper: SequelizeTaxRegimeDomainMapper;
listMapper: SequelizeTaxRegimeSummaryMapper;
}
export const buildTaxRegimePersistenceMappers = (): ITaxRegimePersistenceMappers => {
// Mappers para el repositorio
const domainMapper = new SequelizeTaxRegimeDomainMapper();
const listMapper = new SequelizeTaxRegimeSummaryMapper();
return {
domainMapper,
listMapper,
};
};

View File

@ -0,0 +1,13 @@
import type { Sequelize } from "sequelize";
import { SequelizeTaxRegimeRepository } from "../persistence";
import type { ITaxRegimePersistenceMappers } from "./tax-regime-persistence-mappers.di";
export const buildTaxRegimeRepository = (params: {
database: Sequelize;
mappers: ITaxRegimePersistenceMappers;
}) => {
const { database, mappers } = params;
return new SequelizeTaxRegimeRepository(mappers.domainMapper, mappers.listMapper, database);
};

View File

@ -0,0 +1,119 @@
import type { ModuleParams, SetupParams } from "@erp/core/api";
import { buildTransactionManager } from "@erp/core/api";
import type { Sequelize } from "sequelize";
import type { ITaxRegimeRepository } from "../../../application/";
import { TaxRegimeFinder } from "../../../application/";
import {
CreateTaxRegimeUseCase,
DeleteTaxRegimeByIdUseCase,
DisableTaxRegimeByIdUseCase,
EnableTaxRegimeByIdUseCase,
GetTaxRegimeByIdUseCase,
ListTaxRegimesUseCase,
UpdateTaxRegimeByIdUseCase,
} from "../../../application/tax-regimes";
import {
buildTaxRegimeCreator,
buildTaxRegimeDeleter,
buildTaxRegimeFinder,
buildTaxRegimeInputMappers,
buildTaxRegimeSnapshotBuilders,
buildTaxRegimeStatusChanger,
buildTaxRegimeUpdater,
} from "../../../application/tax-regimes/di";
import { buildTaxRegimePersistenceMappers } from "./tax-regime-persistence-mappers.di";
import { buildTaxRegimeRepository } from "./tax-regime-repositories.di";
export type TaxRegimesInternalDeps = {
repository: ITaxRegimeRepository;
useCases: {
listTaxRegimes: () => ListTaxRegimesUseCase;
getTaxRegimeById: () => GetTaxRegimeByIdUseCase;
createTaxRegime: () => CreateTaxRegimeUseCase;
updateTaxRegimeById: () => UpdateTaxRegimeByIdUseCase;
deleteTaxRegimeById: () => DeleteTaxRegimeByIdUseCase;
disableTaxRegimeById: () => DisableTaxRegimeByIdUseCase;
enableTaxRegimeById: () => EnableTaxRegimeByIdUseCase;
};
};
export const buildTaxRegimesDependencies = (params: ModuleParams): TaxRegimesInternalDeps => {
const { database } = params;
const transactionManager = buildTransactionManager(database as Sequelize);
const persistenceMappers = buildTaxRegimePersistenceMappers();
const repository = buildTaxRegimeRepository({ database, mappers: persistenceMappers });
const inputMappers = buildTaxRegimeInputMappers();
const finder = buildTaxRegimeFinder({ repository });
const creator = buildTaxRegimeCreator({ repository });
const updater = buildTaxRegimeUpdater({ repository });
const deleter = buildTaxRegimeDeleter({ repository });
const statusChanger = buildTaxRegimeStatusChanger({ repository });
const snapshotBuilders = buildTaxRegimeSnapshotBuilders();
return {
repository,
useCases: {
listTaxRegimes: () =>
new ListTaxRegimesUseCase(finder, snapshotBuilders.summary, transactionManager),
getTaxRegimeById: () =>
new GetTaxRegimeByIdUseCase(finder, snapshotBuilders.full, transactionManager),
createTaxRegime: () =>
new CreateTaxRegimeUseCase({
dtoMapper: inputMappers.createInputMapper,
creator,
fullSnapshotBuilder: snapshotBuilders.full,
transactionManager,
}),
updateTaxRegimeById: () =>
new UpdateTaxRegimeByIdUseCase({
updater,
finder,
dtoMapper: inputMappers.updateInputMapper,
fullSnapshotBuilder: snapshotBuilders.full,
transactionManager,
}),
deleteTaxRegimeById: () =>
new DeleteTaxRegimeByIdUseCase({
deleter,
finder,
fullSnapshotBuilder: snapshotBuilders.full,
transactionManager,
}),
disableTaxRegimeById: () =>
new DisableTaxRegimeByIdUseCase({
finder,
changer: statusChanger,
fullSnapshotBuilder: snapshotBuilders.full,
transactionManager,
}),
enableTaxRegimeById: () =>
new EnableTaxRegimeByIdUseCase({
finder,
changer: statusChanger,
fullSnapshotBuilder: snapshotBuilders.full,
transactionManager,
}),
},
};
};
export const buildTaxRegimesPublicServices = (
_params: SetupParams,
deps: TaxRegimesInternalDeps
): { finder: TaxRegimeFinder } => {
return {
finder: new TaxRegimeFinder(deps.repository),
};
};

View File

@ -0,0 +1,39 @@
import type { CreateTaxRegimeRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import type { CreateTaxRegimeUseCase } from "../../../../application/tax-regimes";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
export class CreateTaxRegimeController extends ExpressController {
constructor(private readonly useCase: CreateTaxRegimeUseCase) {
super();
this.errorMapper = taxRegimesApiErrorMapper;
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 CreateTaxRegimeRequestDTO;
const result = await this.useCase.execute({ dto, companyId });
return result.match(
(data: unknown) => this.created(data),
(err: Error) => this.handleError(err)
);
}
}

View File

@ -0,0 +1,38 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import type { DeleteTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
export class DeleteTaxRegimeByIdController extends ExpressController {
constructor(private readonly useCase: DeleteTaxRegimeByIdUseCase) {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {
const companyId = this.getTenantId();
if (!companyId) {
return this.forbiddenError("Tenant ID not found");
}
const { tax_regime_id } = this.req.params;
const result = await this.useCase.execute({ tax_regime_id, companyId });
return result.match(
(data: unknown) => this.ok(data),
(err: Error) => this.handleError(err)
);
}
}

View File

@ -0,0 +1,38 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import type { DisableTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
export class DisableTaxRegimeByIdController extends ExpressController {
constructor(private readonly useCase: DisableTaxRegimeByIdUseCase) {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {
const companyId = this.getTenantId();
if (!companyId) {
return this.forbiddenError("Tenant ID not found");
}
const { tax_regime_id } = this.req.params;
const result = await this.useCase.execute({ tax_regime_id, companyId });
return result.match(
(data: unknown) => this.ok(data),
(err: Error) => this.handleError(err)
);
}
}

View File

@ -0,0 +1,38 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import type { EnableTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
export class EnableTaxRegimeByIdController extends ExpressController {
constructor(private readonly useCase: EnableTaxRegimeByIdUseCase) {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {
const companyId = this.getTenantId();
if (!companyId) {
return this.forbiddenError("Tenant ID not found");
}
const { tax_regime_id } = this.req.params;
const result = await this.useCase.execute({ tax_regime_id, companyId });
return result.match(
(data: unknown) => this.ok(data),
(err: Error) => this.handleError(err)
);
}
}

View File

@ -0,0 +1,38 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import type { GetTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
export class GetTaxRegimeByIdController extends ExpressController {
constructor(private readonly useCase: GetTaxRegimeByIdUseCase) {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {
const companyId = this.getTenantId();
if (!companyId) {
return this.forbiddenError("Tenant ID not found");
}
const { tax_regime_id } = this.req.params;
const result = await this.useCase.execute({ tax_regime_id, companyId });
return result.match(
(data: unknown) => this.ok(data),
(err: Error) => this.handleError(err)
);
}
}

View File

@ -0,0 +1,7 @@
export * from "./create-tax-regime.controller";
export * from "./delete-tax-regime-by-id.controller";
export * from "./disable-tax-regime-by-id.controller";
export * from "./enable-tax-regime-by-id.controller";
export * from "./get-tax-regime-by-id.controller";
export * from "./list-tax-regimes.controller";
export * from "./update-tax-regime-by-id.controller";

View File

@ -0,0 +1,54 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import type { ListTaxRegimesUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
export class ListTaxRegimesController extends ExpressController {
constructor(private readonly useCase: ListTaxRegimesUseCase) {
super();
this.errorMapper = taxRegimesApiErrorMapper;
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({ criteria, companyId });
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)
);
}
}

View File

@ -0,0 +1,44 @@
import type { UpdateTaxRegimeByIdRequestDTO } from "@erp/catalogs/common";
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import type { UpdateTaxRegimeByIdUseCase } from "../../../../application";
import { taxRegimesApiErrorMapper } from "../tax-regime-api-error-mapper";
export class UpdateTaxRegimeByIdController extends ExpressController {
constructor(private readonly useCase: UpdateTaxRegimeByIdUseCase) {
super();
this.errorMapper = taxRegimesApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {
const companyId = this.getTenantId();
if (!companyId) {
return this.forbiddenError("Tenant ID not found");
}
const { tax_regime_id } = this.req.params;
if (!tax_regime_id) {
return this.invalidInputError("Payment term ID missing");
}
const dto = this.req.body as UpdateTaxRegimeByIdRequestDTO;
const result = await this.useCase.execute({ tax_regime_id, companyId, dto });
return result.match(
(data: unknown) => this.ok(data),
(err: Error) => this.handleError(err)
);
}
}

View File

@ -0,0 +1,2 @@
export * from "./controllers";
export * from "./tax-regimes.routes";

View File

@ -0,0 +1,107 @@
import {
ApiErrorMapper,
ConflictApiError,
type ErrorToApiRule,
NotFoundApiError,
ValidationApiError,
} from "@erp/core/api";
import {
type InvalidTaxRegimeCodeError,
type InvalidTaxRegimeDescriptionError,
type InvalidTaxRegimeIdError,
type TaxRegimeCannotBeDeletedError,
type TaxRegimeCannotBeDisabledError,
type TaxRegimeCannotBeEnabledError,
type TaxRegimeCannotBeUpdatedError,
type TaxRegimeNotFoundError,
isInvalidTaxRegimeCodeError,
isInvalidTaxRegimeDescriptionError,
isInvalidTaxRegimeIdError,
isTaxRegimeCannotBeDeletedError,
isTaxRegimeCannotBeDisabledError,
isTaxRegimeCannotBeEnabledError,
isTaxRegimeCannotBeUpdatedError,
isTaxRegimeNotFoundError,
} from "../../../domain/tax-regimes";
const invalidTaxRegimeIdRule: ErrorToApiRule = {
priority: 120,
matches: isInvalidTaxRegimeIdError,
build: (error) =>
new ConflictApiError(
(error as InvalidTaxRegimeIdError).message ||
"Tax regime with the provided id already exists."
),
};
const invalidTaxRegimeCodeRule: ErrorToApiRule = {
priority: 120,
matches: isInvalidTaxRegimeCodeError,
build: (error) =>
new ValidationApiError(
(error as InvalidTaxRegimeCodeError).message || "Tax regime code is invalid."
),
};
const invalidTaxRegimeDescriptionRule: ErrorToApiRule = {
priority: 120,
matches: isInvalidTaxRegimeDescriptionError,
build: (error) =>
new ValidationApiError(
(error as InvalidTaxRegimeDescriptionError).message || "Tax regime description is invalid."
),
};
const taxRegimeNotFoundRule: ErrorToApiRule = {
priority: 120,
matches: isTaxRegimeNotFoundError,
build: (error) =>
new NotFoundApiError((error as TaxRegimeNotFoundError).message || "Tax regime not found."),
};
const taxRegimeCannotBeDeletedRule: ErrorToApiRule = {
priority: 120,
matches: isTaxRegimeCannotBeDeletedError,
build: (error) =>
new ValidationApiError(
(error as TaxRegimeCannotBeDeletedError).message || "Tax regime cannot be deleted."
),
};
const taxRegimeCannotBeDisabledRule: ErrorToApiRule = {
priority: 120,
matches: isTaxRegimeCannotBeDisabledError,
build: (error) =>
new ValidationApiError(
(error as TaxRegimeCannotBeDisabledError).message || "Tax regime cannot be disabled."
),
};
const taxRegimeCannotBeEnabledRule: ErrorToApiRule = {
priority: 120,
matches: isTaxRegimeCannotBeEnabledError,
build: (error) =>
new ValidationApiError(
(error as TaxRegimeCannotBeEnabledError).message || "Tax regime cannot be enabled."
),
};
const taxRegimeCannotBeUpdatedRule: ErrorToApiRule = {
priority: 120,
matches: isTaxRegimeCannotBeUpdatedError,
build: (error) =>
new ValidationApiError(
(error as TaxRegimeCannotBeUpdatedError).message || "Tax regime cannot be updated."
),
};
export const taxRegimesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
.register(invalidTaxRegimeIdRule)
.register(invalidTaxRegimeCodeRule)
.register(invalidTaxRegimeDescriptionRule)
.register(taxRegimeNotFoundRule)
.register(taxRegimeCannotBeDeletedRule)
.register(taxRegimeCannotBeDisabledRule)
.register(taxRegimeCannotBeEnabledRule)
.register(taxRegimeCannotBeUpdatedRule);

View File

@ -0,0 +1,102 @@
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 {
CreateTaxRegimeRequestSchema,
DeleteTaxRegimeByIdRequestSchema,
GetTaxRegimeByIdRequestSchema,
ListTaxRegimesRequestSchema,
UpdateTaxRegimeByIdParamsRequestSchema,
UpdateTaxRegimeByIdRequestSchema,
} from "../../../../common";
import type { CatalogsInternalDeps } from "../../di";
import {
CreateTaxRegimeController,
DeleteTaxRegimeByIdController,
DisableTaxRegimeByIdController,
EnableTaxRegimeByIdController,
GetTaxRegimeByIdController,
ListTaxRegimesController,
UpdateTaxRegimeByIdController,
} from "./controllers";
export const taxRegimesRouter = (params: StartParams) => {
const { app, config, getInternal } = params;
const deps = getInternal<CatalogsInternalDeps>("catalogs").taxRegimes;
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(ListTaxRegimesRequestSchema, "query"), (req, res, next) => {
const controller = new ListTaxRegimesController(deps.useCases.listTaxRegimes());
return controller.execute(req, res, next);
});
router.post("/", validateRequest(CreateTaxRegimeRequestSchema, "body"), (req, res, next) => {
const controller = new CreateTaxRegimeController(deps.useCases.createTaxRegime());
return controller.execute(req, res, next);
});
router.get(
"/:tax_regime_id",
validateRequest(GetTaxRegimeByIdRequestSchema, "params"),
(req, res, next) => {
const controller = new GetTaxRegimeByIdController(deps.useCases.getTaxRegimeById());
return controller.execute(req, res, next);
}
);
router.delete(
"/:tax_regime_id",
validateRequest(DeleteTaxRegimeByIdRequestSchema, "params"),
(req, res, next) => {
const controller = new DeleteTaxRegimeByIdController(deps.useCases.deleteTaxRegimeById());
return controller.execute(req, res, next);
}
);
router.put(
"/:tax_regime_id",
validateRequest(UpdateTaxRegimeByIdParamsRequestSchema, "params"),
validateRequest(UpdateTaxRegimeByIdRequestSchema, "body"),
(req, res, next) => {
const controller = new UpdateTaxRegimeByIdController(deps.useCases.updateTaxRegimeById());
return controller.execute(req, res, next);
}
);
router.patch(
"/:tax_regime_id/disable",
validateRequest(GetTaxRegimeByIdRequestSchema, "params"),
(req, res, next) => {
const controller = new DisableTaxRegimeByIdController(deps.useCases.disableTaxRegimeById());
return controller.execute(req, res, next);
}
);
router.patch(
"/:tax_regime_id/enable",
validateRequest(GetTaxRegimeByIdRequestSchema, "params"),
(req, res, next) => {
const controller = new EnableTaxRegimeByIdController(deps.useCases.enableTaxRegimeById());
return controller.execute(req, res, next);
}
);
app.use(`${config.server.apiBasePath}/catalogs/tax-regimes`, router);
};

View File

@ -0,0 +1,2 @@
export * from "./express";
export * from "./persistence";

View File

@ -0,0 +1 @@
export * from "./sequelize";

View File

@ -0,0 +1,7 @@
export * from "./mappers";
export * from "./models";
export * from "./repositories";
import taxRegimeModelInit from "./models/sequelize-tax-regime.model";
export const taxRegimeModels = [taxRegimeModelInit];

View File

@ -0,0 +1,2 @@
export * from "./sequelize-tax-regime-domain.mapper";
export * from "./sequelize-tax-regime-summary.mapper";

View File

@ -0,0 +1,79 @@
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import {
TextValue,
UniqueID,
ValidationErrorCollection,
type ValidationErrorDetail,
extractOrPushError,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { TaxRegime, TaxRegimeCode } from "../../../../../domain";
import type { TaxRegimeCreationAttributes, TaxRegimeModel } from "../models";
export class SequelizeTaxRegimeDomainMapper extends SequelizeDomainMapper<
TaxRegimeModel,
TaxRegimeCreationAttributes,
TaxRegime
> {
public mapToDomain(source: TaxRegimeModel, _params?: MapperParamsType): Result<TaxRegime, Error> {
try {
const errors: ValidationErrorDetail[] = [];
const idResult = UniqueID.create(source.id, true);
if (idResult.isFailure) {
return Result.fail(idResult.error);
}
const companyId = extractOrPushError(
UniqueID.create(source.company_id),
"company_id",
errors
);
const code = extractOrPushError(TaxRegimeCode.create(source.code), "code", errors);
const description = extractOrPushError(
TextValue.create(source.description),
"description",
errors
);
// Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Payment method props mapping failed", errors)
);
}
const taxRegime = TaxRegime.rehydrate(
{
companyId: companyId!,
code: code!,
description: description!,
isActive: source.is_active,
isSystem: source.is_system,
},
idResult.data
);
return Result.ok(taxRegime);
} catch (error: unknown) {
return Result.fail(error as Error);
}
}
public mapToPersistence(
source: TaxRegime,
_params?: MapperParamsType
): Result<TaxRegimeCreationAttributes, Error> {
return Result.ok<TaxRegimeCreationAttributes>({
id: source.id.toPrimitive(),
company_id: source.companyId.toPrimitive(),
code: source.code.toPrimitive(),
description: source.description.toPrimitive(),
is_active: source.isActive,
is_system: source.isSystem,
});
}
}

View File

@ -0,0 +1,53 @@
import { type MapperParamsType, SequelizeQueryMapper } from "@erp/core/api";
import {
TextValue,
UniqueID,
ValidationErrorCollection,
type ValidationErrorDetail,
extractOrPushError,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { TaxRegimeSummary } from "../../../../../application";
import { TaxRegimeCode } from "../../../../../domain";
import type { TaxRegimeModel } from "../models";
export class SequelizeTaxRegimeSummaryMapper extends SequelizeQueryMapper<
TaxRegimeModel,
TaxRegimeSummary
> {
public mapToReadModel(
raw: TaxRegimeModel,
_params?: MapperParamsType
): Result<TaxRegimeSummary, Error> {
const errors: ValidationErrorDetail[] = [];
// 1) Valores escalares (atributos generales)
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
const id = extractOrPushError(UniqueID.create(raw.id), "id", errors);
const code = extractOrPushError(TaxRegimeCode.create(raw.code), "code", errors);
const description = extractOrPushError(
TextValue.create(raw.description),
"description",
errors
);
const isActive = raw.is_active;
const isSystem = raw.is_system;
// Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("TaxRegime mapping failed [mapToDTO]", errors)
);
}
return Result.ok<TaxRegimeSummary>({
id: id!,
companyId: companyId!,
code: code!,
description: description!,
isActive: isActive,
isSystem: isSystem,
});
}
}

View File

@ -0,0 +1 @@
export { type TaxRegimeCreationAttributes, TaxRegimeModel } from "./sequelize-tax-regime.model";

View File

@ -0,0 +1,89 @@
import {
type CreationOptional,
DataTypes,
type InferAttributes,
type InferCreationAttributes,
Model,
type Sequelize,
} from "sequelize";
export type TaxRegimeCreationAttributes = InferCreationAttributes<TaxRegimeModel, {}> & {};
export class TaxRegimeModel extends Model<
InferAttributes<TaxRegimeModel>,
InferCreationAttributes<TaxRegimeModel>
> {
declare id: string;
declare company_id: string;
declare code: string;
declare description: CreationOptional<string>;
declare is_active: boolean;
declare is_system: boolean;
static associate(_database: Sequelize) {}
static hooks(_database: Sequelize) {}
}
export default (database: Sequelize) => {
TaxRegimeModel.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
},
company_id: {
type: DataTypes.UUID,
allowNull: false,
},
code: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
description: {
type: DataTypes.STRING,
allowNull: false,
},
is_active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
},
is_system: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
},
{
sequelize: database,
modelName: "TaxRegimeModel",
tableName: "tax_regimes",
underscored: true,
paranoid: true, // softs deletes
timestamps: true,
createdAt: "created_at",
updatedAt: "updated_at",
deletedAt: "deleted_at",
indexes: [
{
name: "idx_tax_regimes_code",
fields: ["code", "deleted_at"],
unique: true,
},
],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
defaultScope: {},
scopes: {},
}
);
return TaxRegimeModel;
};

View File

@ -0,0 +1 @@
export { SequelizeTaxRegimeRepository } from "./sequelize-tax-regime.repository";

View File

@ -0,0 +1,253 @@
import {
EntityNotFoundError,
InfrastructureRepositoryError,
SequelizeRepository,
translateSequelizeError,
} from "@erp/core/api";
import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
import type { UniqueID } from "@repo/rdx-ddd";
import { type Collection, Result } from "@repo/rdx-utils";
import type { Sequelize, Transaction } from "sequelize";
import type { ITaxRegimeRepository, TaxRegimeSummary } from "../../../../../application";
import type { TaxRegime, TaxRegimeCode } from "../../../../../domain";
import type { SequelizeTaxRegimeDomainMapper, SequelizeTaxRegimeSummaryMapper } from "../mappers";
import { TaxRegimeModel } from "../models";
export class SequelizeTaxRegimeRepository
extends SequelizeRepository<TaxRegime>
implements ITaxRegimeRepository
{
constructor(
private readonly domainMapper: SequelizeTaxRegimeDomainMapper,
private readonly summaryMapper: SequelizeTaxRegimeSummaryMapper,
database: Sequelize
) {
super({ database });
}
/**
*
* Crea un nuevo método de pago
*
* @param taxRegime - El método de pago nuevo a guardar.
* @param transaction - Transacción activa para la operación.
* @returns Result<void, Error>
*/
async create(taxRegime: TaxRegime, transaction?: Transaction): Promise<Result<void, Error>> {
try {
const dtoResult = this.domainMapper.mapToPersistence(taxRegime);
if (dtoResult.isFailure) {
return Result.fail(dtoResult.error);
}
await TaxRegimeModel.create(dtoResult.data, { transaction });
return Result.ok();
} catch (err: unknown) {
return Result.fail(translateSequelizeError(err));
}
}
/**
* Actualiza un método de pago existente.
*
* @param taxRegime - El método de pago a actualizar.
* @param transaction - Transacción activa para la operación.
* @returns Result<void, Error>
*/
async update(taxRegime: TaxRegime, transaction?: Transaction): Promise<Result<void, Error>> {
try {
const dtoResult = this.domainMapper.mapToPersistence(taxRegime);
if (dtoResult.isFailure) {
return Result.fail(dtoResult.error);
}
const { id, ...payload } = dtoResult.data;
const [affected] = await TaxRegimeModel.update(payload, {
where: { id },
transaction,
individualHooks: true,
});
if (affected === 0) {
return Result.fail(
new InfrastructureRepositoryError("Concurrency conflict or payment method not found")
);
}
return Result.ok();
} catch (err: unknown) {
return Result.fail(translateSequelizeError(err));
}
}
/**
* Comprueba si existe un TaxRegime con un `id` dentro de una `company`.
*
* @param companyId - Identificador UUID de la empresa a la que pertenece el método de pago.
* @param id - Identificador UUID del método de pago.
* @param transaction - Transacción activa para la operación.
* @returns Result<boolean, Error>
*/
async existsByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction?: Transaction
): Promise<Result<boolean, Error>> {
try {
const count = await TaxRegimeModel.count({
where: { id: id.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.
*
* @param companyId - Identificador UUID de la empresa a la que pertenece el método de pago.
* @param id - Identificador UUID del método de pago.
* @param transaction - Transacción activa para la operación.
* @returns Result<TaxRegime, Error>
*/
async getByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction?: Transaction
): Promise<Result<TaxRegime, Error>> {
try {
const row = await TaxRegimeModel.findOne({
where: {
id: id.toString(),
company_id: companyId.toString(),
},
transaction,
});
if (!row) {
return Result.fail(new EntityNotFoundError("TaxRegime", "id", id.toString()));
}
return this.domainMapper.mapToDomain(row);
} catch (err: unknown) {
return Result.fail(translateSequelizeError(err));
}
}
/**
* Recupera un método de pago por su code y companyId.
*
* @param companyId - Identificador UUID de la empresa a la que pertenece el método de pago.
* @param code - Código del método de pago.
* @param transaction - Transacción activa para la operación.
* @returns Result<TaxRegime, Error>
*/
async getByCodeInCompany(
companyId: UniqueID,
code: TaxRegimeCode,
transaction?: Transaction
): Promise<Result<TaxRegime, Error>> {
try {
const row = await TaxRegimeModel.findOne({
where: {
code: code.toString(),
company_id: companyId.toString(),
},
transaction,
});
if (!row) {
return Result.fail(new EntityNotFoundError("TaxRegime", "code", code.toString()));
}
return this.domainMapper.mapToDomain(row);
} 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.).
*
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
* @param criteria - Criterios de búsqueda.
* @param transaction - Transacción activa para la operación.
* @returns Result<Collection<CustomerListDTO>, Error>
*
* @see Criteria
*/
async findByCriteriaInCompany(
companyId: UniqueID,
criteria: Criteria,
transaction?: Transaction
): Promise<Result<Collection<TaxRegimeSummary>, Error>> {
try {
const criteriaConverter = new CriteriaToSequelizeConverter();
const query = criteriaConverter.convert(criteria, {
mappings: {
isActive: "is_active",
},
searchableFields: [],
sortableFields: ["code"],
enableFullText: true,
database: this.database,
strictMode: true, // fuerza error si ORDER BY no permitido
});
query.where = {
...query.where,
company_id: companyId.toString(),
deleted_at: null,
};
const [rows, count] = await Promise.all([
TaxRegimeModel.findAll({
...query,
transaction,
}),
TaxRegimeModel.count({
where: query.where,
distinct: true, // evita duplicados por LEFT JOIN
transaction,
}),
]);
return this.summaryMapper.mapToReadModelCollection(rows, count);
} catch (err: unknown) {
return Result.fail(translateSequelizeError(err));
}
}
/**
*
* Elimina o marca como eliminado una forma de pago.
*
* @param companyId - Identificador UUID de la empresa a la que pertenece la forma de pago.
* @param id - UUID de la forma de pago a eliminar.
* @param transaction - Transacción activa para la operación.
* @returns Result<boolean, Error>
*/
async deleteByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction: Transaction
): Promise<Result<boolean, Error>> {
try {
const deleted = await TaxRegimeModel.destroy({
where: { id: id.toString(), company_id: companyId.toString() },
transaction,
});
if (deleted === 0) {
return Result.fail(new EntityNotFoundError("TaxRegime", "id", id.toString()));
}
return Result.ok(true);
} catch (err: unknown) {
return Result.fail(translateSequelizeError(err));
}
}
}

View File

@ -1,2 +1,3 @@
export * from "./payment-methods"; export * from "./payment-methods";
export * from "./payment-terms"; export * from "./payment-terms";
export * from "./tax-regimes";

View File

@ -0,0 +1,3 @@
export * from "./request";
export * from "./response";
export * from "./shared";

View File

@ -0,0 +1,10 @@
import { z } from "zod/v4";
export const CreateTaxRegimeRequestSchema = z.object({
id: z.uuid(),
code: z.string().regex(/^\d{2}$/),
description: z.string(),
is_active: z.boolean(),
});
export type CreateTaxRegimeRequestDTO = z.infer<typeof CreateTaxRegimeRequestSchema>;

View File

@ -0,0 +1,7 @@
import { z } from "zod/v4";
export const DeleteTaxRegimeByIdRequestSchema = z.object({
tax_regime_id: z.uuid(),
});
export type DeleteTaxRegimeByIdRequestDTO = z.infer<typeof DeleteTaxRegimeByIdRequestSchema>;

View File

@ -0,0 +1,7 @@
import { z } from "zod/v4";
export const DisableTaxRegimeByIdRequestSchema = z.object({
tax_regime_id: z.uuid(),
});
export type DisableTaxRegimeByIdRequestDTO = z.infer<typeof DisableTaxRegimeByIdRequestSchema>;

View File

@ -0,0 +1,7 @@
import { z } from "zod/v4";
export const EnableTaxRegimeByIdRequestSchema = z.object({
tax_regime_id: z.uuid(),
});
export type EnableTaxRegimeByIdRequestDTO = z.infer<typeof EnableTaxRegimeByIdRequestSchema>;

View File

@ -0,0 +1,7 @@
import { z } from "zod/v4";
export const GetTaxRegimeByIdRequestSchema = z.object({
tax_regime_id: z.uuid(),
});
export type GetTaxRegimeByIdRequestDTO = z.infer<typeof GetTaxRegimeByIdRequestSchema>;

View File

@ -0,0 +1,7 @@
export * from "./create-tax-regime.request.dto";
export * from "./delete-tax-regime-by-id.request.dto";
export * from "./disable-tax-regime-by-id.request.dto";
export * from "./enable-tax-regime-by-id.request.dto";
export * from "./get-tax-regime-by-id.request.dto";
export * from "./list-tax-regimes.request.dto";
export * from "./update-tax-regime-by-id.request.dto";

View File

@ -0,0 +1,5 @@
import { CriteriaSchema } from "@erp/core";
import type { z } from "zod/v4";
export const ListTaxRegimesRequestSchema = CriteriaSchema;
export type ListTaxRegimesRequestDTO = z.infer<typeof ListTaxRegimesRequestSchema>;

View File

@ -0,0 +1,15 @@
import { z } from "zod/v4";
export const UpdateTaxRegimeByIdParamsRequestSchema = z.object({
tax_regime_id: z.uuid(),
});
export const UpdateTaxRegimeByIdRequestSchema = z.object({
description: z.string().optional(),
is_active: z.boolean().optional(),
});
export type UpdateTaxRegimeByIdParamsRequestDTO = z.infer<
typeof UpdateTaxRegimeByIdParamsRequestSchema
>;
export type UpdateTaxRegimeByIdRequestDTO = z.infer<typeof UpdateTaxRegimeByIdRequestSchema>;

View File

@ -0,0 +1,6 @@
import type { z } from "zod/v4";
import { TaxRegimeDetailSchema } from "../shared";
export const CreateTaxRegimeResponseSchema = TaxRegimeDetailSchema;
export type CreateTaxRegimeResponseDTO = z.infer<typeof CreateTaxRegimeResponseSchema>;

View File

@ -0,0 +1,6 @@
import type { z } from "zod/v4";
import { TaxRegimeDetailSchema } from "../shared";
export const DisableTaxRegimeByIdResponseSchema = TaxRegimeDetailSchema;
export type DisableTaxRegimeByIdResponseDTO = z.infer<typeof DisableTaxRegimeByIdResponseSchema>;

View File

@ -0,0 +1,6 @@
import type { z } from "zod/v4";
import { TaxRegimeDetailSchema } from "../shared";
export const EnableTaxRegimeByIdResponseSchema = TaxRegimeDetailSchema;
export type EnableTaxRegimeByIdResponseDTO = z.infer<typeof EnableTaxRegimeByIdResponseSchema>;

View File

@ -0,0 +1,6 @@
import type { z } from "zod/v4";
import { TaxRegimeDetailSchema } from "../shared";
export const GetTaxRegimeByIdResponseSchema = TaxRegimeDetailSchema;
export type GetTaxRegimeByIdResponseDTO = z.infer<typeof GetTaxRegimeByIdResponseSchema>;

View File

@ -0,0 +1,6 @@
export * from "./create-tax-regime.response.dto";
export * from "./disable-tax-regime-by-id.response.dto";
export * from "./enable-tax-regime-by-id.response.dto";
export * from "./get-tax-regime-by-id.response.dto";
export * from "./list-tax-regimes.response.dto";
export * from "./update-tax-regime-by-id.response.dto";

View File

@ -0,0 +1,7 @@
import { createPaginatedListSchema } from "@erp/core";
import type { z } from "zod/v4";
import { TaxRegimeSummarySchema } from "../shared";
export const ListTaxRegimesResponseSchema = createPaginatedListSchema(TaxRegimeSummarySchema);
export type ListTaxRegimesResponseDTO = z.infer<typeof ListTaxRegimesResponseSchema>;

View File

@ -0,0 +1,6 @@
import type { z } from "zod/v4";
import { TaxRegimeDetailSchema } from "../shared";
export const UpdateTaxRegimeByIdResponseSchema = TaxRegimeDetailSchema;
export type UpdateTaxRegimeByIdResponseDTO = z.infer<typeof UpdateTaxRegimeByIdResponseSchema>;

View File

@ -0,0 +1,2 @@
export * from "./tax-regime-detail.dto";
export * from "./tax-regime-summary.dto";

View File

@ -0,0 +1,12 @@
import { z } from "zod/v4";
export const TaxRegimeDetailSchema = z.object({
id: z.uuid(),
company_id: z.uuid(),
code: z.string().regex(/^\d{2}$/),
description: z.string(),
is_active: z.boolean(),
is_system: z.boolean(),
});
export type TaxRegimeDetailDTO = z.infer<typeof TaxRegimeDetailSchema>;

View File

@ -0,0 +1,12 @@
import { z } from "zod/v4";
export const TaxRegimeSummarySchema = z.object({
id: z.uuid(),
company_id: z.uuid(),
code: z.string().regex(/^\d{2}$/),
description: z.string(),
is_active: z.boolean(),
is_system: z.boolean(),
});
export type TaxRegimeSummaryDTO = z.infer<typeof TaxRegimeSummarySchema>;