Catálogo de régimen de IVA
This commit is contained in:
parent
402b8300b1
commit
cea1ab2766
@ -1,2 +1,3 @@
|
||||
export * from "./payment-methods";
|
||||
export * from "./payment-terms";
|
||||
export * from "./tax-regimes";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export * from "./payment-term-creator";
|
||||
export * from "./payment-term-deleter";
|
||||
export * from "./payment-term-finder";
|
||||
export * from "./payment-term-creator.service";
|
||||
export * from "./payment-term-deleter.service";
|
||||
export * from "./payment-term-finder.service";
|
||||
export * from "./payment-term-public-services";
|
||||
export * from "./payment-term-status-changer";
|
||||
export * from "./payment-term-updater";
|
||||
export * from "./payment-term-status-changer.service";
|
||||
export * from "./payment-term-updater.service";
|
||||
|
||||
@ -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 {
|
||||
finder: IPaymentTermFinder;
|
||||
creator: IPaymentTermCreator;
|
||||
updater: IPaymentTermUpdater;
|
||||
deleter: IPaymentTermDeleter;
|
||||
statusChanger: IPaymentTermStatusChanger;
|
||||
}
|
||||
|
||||
export const buildPaymentTermPublicServices = (
|
||||
finder: IPaymentTermFinder
|
||||
finder: IPaymentTermFinder,
|
||||
creator: IPaymentTermCreator,
|
||||
updater: IPaymentTermUpdater,
|
||||
deleter: IPaymentTermDeleter,
|
||||
statusChanger: IPaymentTermStatusChanger
|
||||
): IPaymentTermPublicServices => ({
|
||||
finder,
|
||||
creator,
|
||||
updater,
|
||||
deleter,
|
||||
statusChanger,
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { PaymentTerm } from "../../../domain";
|
||||
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||
import type { IPaymentTermRepository } from "../repositories";
|
||||
|
||||
export type PaymentTermStatusChangeAction = "enable" | "disable";
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./tax-regime-input-mappers.di";
|
||||
export * from "./tax-regime-services.di";
|
||||
export * from "./tax-regime-snapshot-builders.di";
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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);
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
export * from "./mappers";
|
||||
export * from "./models";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
export * from "./snapshot-builders";
|
||||
export * from "./use-cases";
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./create-tax-regime-input.mapper";
|
||||
export * from "./update-tax-regime-by-id-input.mapper";
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./tax-regime-detail.model";
|
||||
export * from "./tax-regime-summary.model";
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./tax-regime-repository.interface";
|
||||
@ -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>>;
|
||||
}
|
||||
@ -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";
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./full-tax-regime-snapshot.builder";
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./full";
|
||||
export * from "./summary";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./summary-tax-regime-snapshot.builder";
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./payment-methods";
|
||||
export * from "./payment-terms";
|
||||
export * from "./tax-regimes";
|
||||
|
||||
@ -154,7 +154,7 @@ export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
|
||||
return this._dueRules;
|
||||
}
|
||||
|
||||
public update(patchProps: PaymentTermPatchProps): Result<void, Error> {
|
||||
public update(patchProps: PaymentTermPatchProps): Result<boolean, Error> {
|
||||
if (this.isSystem) {
|
||||
return Result.fail(
|
||||
new PaymentTermCannotBeUpdatedError("System payment terms cannot be updated.")
|
||||
@ -166,6 +166,7 @@ export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
|
||||
return Result.fail(validationResult.error);
|
||||
}
|
||||
|
||||
let hasChanges = false;
|
||||
const { dueRules, ...otherProps } = patchProps;
|
||||
|
||||
const candidateProps: PaymentTermInternalProps = {
|
||||
@ -175,6 +176,7 @@ export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
|
||||
|
||||
// Aplicar cambios
|
||||
Object.assign(this.props, candidateProps);
|
||||
hasChanges = true;
|
||||
|
||||
// Reemplazo de items (si se proporciona)
|
||||
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> {
|
||||
|
||||
66
modules/catalogs/src/api/domain/tax-regimes/errors.ts
Normal file
66
modules/catalogs/src/api/domain/tax-regimes/errors.ts
Normal 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;
|
||||
3
modules/catalogs/src/api/domain/tax-regimes/index.ts
Normal file
3
modules/catalogs/src/api/domain/tax-regimes/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./errors";
|
||||
export * from "./tax-regime.aggregate";
|
||||
export * from "./tax-regime-code";
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,8 @@ import {
|
||||
paymentMethodsRouter,
|
||||
paymentTermModels,
|
||||
paymentTermsRouter,
|
||||
taxRegimeModels,
|
||||
taxRegimesRouter,
|
||||
} from "./infrastructure";
|
||||
import {
|
||||
buildCatalogsDependencies,
|
||||
@ -27,10 +29,11 @@ export const catalogsAPIModule: IModuleServer = {
|
||||
});
|
||||
|
||||
return {
|
||||
models: [...paymentMethodModels, ...paymentTermModels],
|
||||
models: [...paymentMethodModels, ...paymentTermModels, ...taxRegimeModels],
|
||||
services: {
|
||||
paymentMethod: publicServices.paymentMethods,
|
||||
paymentTerms: publicServices.paymentTerms,
|
||||
taxRegimes: publicServices.taxRegimes,
|
||||
},
|
||||
internal,
|
||||
};
|
||||
@ -41,6 +44,7 @@ export const catalogsAPIModule: IModuleServer = {
|
||||
|
||||
paymentMethodsRouter(params);
|
||||
paymentTermsRouter(params);
|
||||
taxRegimesRouter(params);
|
||||
|
||||
logger.info("🚀 Catalogs module started", {
|
||||
label: this.name,
|
||||
|
||||
@ -10,16 +10,23 @@ import {
|
||||
buildPaymentTermsDependencies,
|
||||
buildPaymentTermsPublicServices,
|
||||
} from "../payment-terms/di";
|
||||
import {
|
||||
type TaxRegimesInternalDeps,
|
||||
buildTaxRegimesDependencies,
|
||||
buildTaxRegimesPublicServices,
|
||||
} from "../tax-regimes/di";
|
||||
|
||||
export type CatalogsInternalDeps = {
|
||||
paymentMethods: PaymentMethodsInternalDeps;
|
||||
paymentTerms: PaymentTermsInternalDeps;
|
||||
taxRegimes: TaxRegimesInternalDeps;
|
||||
};
|
||||
|
||||
export const buildCatalogsDependencies = (params: ModuleParams): CatalogsInternalDeps => {
|
||||
return {
|
||||
paymentMethods: buildPaymentMethodsDependencies(params),
|
||||
paymentTerms: buildPaymentTermsDependencies(params),
|
||||
taxRegimes: buildTaxRegimesDependencies(params),
|
||||
};
|
||||
};
|
||||
|
||||
@ -27,5 +34,6 @@ export const buildCatalogsPublicServices = (params: SetupParams, deps: CatalogsI
|
||||
return {
|
||||
paymentMethods: buildPaymentMethodsPublicServices(params, deps.paymentMethods),
|
||||
paymentTerms: buildPaymentTermsPublicServices(params, deps.paymentTerms),
|
||||
taxRegimes: buildTaxRegimesPublicServices(params, deps.taxRegimes),
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./payment-methods";
|
||||
export * from "./payment-terms";
|
||||
export * from "./tax-regimes";
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import {
|
||||
SequelizePaymentMethodDomainMapper,
|
||||
SequelizePaymentMethodSummaryMapper,
|
||||
} from "../persistence/index";
|
||||
} from "../persistence";
|
||||
|
||||
export interface IPaymentMethodPersistenceMappers {
|
||||
domainMapper: SequelizePaymentMethodDomainMapper;
|
||||
listMapper: SequelizePaymentMethodSummaryMapper;
|
||||
|
||||
//createMapper: CreatePaymentMethodRequestMapper;
|
||||
}
|
||||
|
||||
export const buildPaymentMethodPersistenceMappers = (): IPaymentMethodPersistenceMappers => {
|
||||
@ -15,13 +13,8 @@ export const buildPaymentMethodPersistenceMappers = (): IPaymentMethodPersistenc
|
||||
const domainMapper = new SequelizePaymentMethodDomainMapper();
|
||||
const listMapper = new SequelizePaymentMethodSummaryMapper();
|
||||
|
||||
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
|
||||
//const createMapper = new CreatePaymentMethodRequestMapper(catalogs);
|
||||
|
||||
return {
|
||||
domainMapper,
|
||||
listMapper,
|
||||
|
||||
//createMapper,
|
||||
};
|
||||
};
|
||||
|
||||
@ -173,8 +173,6 @@ export class SequelizePaymentMethodRepository
|
||||
strictMode: true, // fuerza error si ORDER BY no permitido
|
||||
});
|
||||
|
||||
console.log(query?.where);
|
||||
|
||||
query.where = {
|
||||
...query.where,
|
||||
company_id: companyId.toString(),
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./tax-regime-persistence-mappers.di";
|
||||
export * from "./tax-regime-repositories.di";
|
||||
export * from "./tax-regimes.di";
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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);
|
||||
};
|
||||
@ -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),
|
||||
};
|
||||
};
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./controllers";
|
||||
export * from "./tax-regimes.routes";
|
||||
@ -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);
|
||||
@ -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);
|
||||
};
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./express";
|
||||
export * from "./persistence";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./sequelize";
|
||||
@ -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];
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./sequelize-tax-regime-domain.mapper";
|
||||
export * from "./sequelize-tax-regime-summary.mapper";
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export { type TaxRegimeCreationAttributes, TaxRegimeModel } from "./sequelize-tax-regime.model";
|
||||
@ -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;
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export { SequelizeTaxRegimeRepository } from "./sequelize-tax-regime.repository";
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./payment-methods";
|
||||
export * from "./payment-terms";
|
||||
export * from "./tax-regimes";
|
||||
|
||||
3
modules/catalogs/src/common/dto/tax-regimes/index.ts
Normal file
3
modules/catalogs/src/common/dto/tax-regimes/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./request";
|
||||
export * from "./response";
|
||||
export * from "./shared";
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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";
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -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";
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./tax-regime-detail.dto";
|
||||
export * from "./tax-regime-summary.dto";
|
||||
@ -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>;
|
||||
@ -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>;
|
||||
Loading…
Reference in New Issue
Block a user