Catalogos
This commit is contained in:
parent
2a1d49d1c4
commit
567c9d9730
@ -7,6 +7,11 @@ import factuGESAPIModule from "@erp/factuges/api";
|
|||||||
|
|
||||||
import { registerModule } from "./lib";
|
import { registerModule } from "./lib";
|
||||||
|
|
||||||
|
// Aquí hay que registrar los módulos que
|
||||||
|
// queramos que se carguen en el servidor.
|
||||||
|
// El registro hace que estén disponibles
|
||||||
|
// las rutas, los modelos y los servicios
|
||||||
|
// públicos de ese módulo.
|
||||||
export const registerModules = () => {
|
export const registerModules = () => {
|
||||||
//registerModule(authAPIModule);
|
//registerModule(authAPIModule);
|
||||||
registerModule(catalogsAPIModule);
|
registerModule(catalogsAPIModule);
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
|
export * from "./payment-terms";
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export class CreatePaymentMethodInputMapper implements ICreatePaymentMethodInput
|
|||||||
|
|
||||||
this.throwIfValidationErrors(errors);
|
this.throwIfValidationErrors(errors);
|
||||||
|
|
||||||
const props = {
|
const props: IPaymentMethodCreateProps = {
|
||||||
companyId: params.companyId,
|
companyId: params.companyId,
|
||||||
name: name!,
|
name: name!,
|
||||||
description: description!,
|
description: description!,
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./payment-term-creator.di";
|
||||||
|
export * from "./payment-term-deleter.di";
|
||||||
|
export * from "./payment-term-finder.di";
|
||||||
|
export * from "./payment-term-input-mappers.di";
|
||||||
|
export * from "./payment-term-snapshot-builders.di";
|
||||||
|
export * from "./payment-term-status-changer.di";
|
||||||
|
export * from "./payment-term-updater.di";
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
|
import { type IPaymentTermCreator, PaymentTermCreator } from "../services";
|
||||||
|
|
||||||
|
export const buildPaymentTermCreator = (params: {
|
||||||
|
repository: IPaymentTermRepository;
|
||||||
|
}): IPaymentTermCreator => {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new PaymentTermCreator(repository);
|
||||||
|
};
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
|
import { type IPaymentTermDeleter, PaymentTermDeleter } from "../services";
|
||||||
|
|
||||||
|
export const buildPaymentTermDeleter = (params: {
|
||||||
|
repository: IPaymentTermRepository;
|
||||||
|
}): IPaymentTermDeleter => {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new PaymentTermDeleter(repository);
|
||||||
|
};
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
|
import { type IPaymentTermFinder, PaymentTermFinder } from "../services";
|
||||||
|
|
||||||
|
export function buildPaymentTermFinder(params: {
|
||||||
|
repository: IPaymentTermRepository;
|
||||||
|
}): IPaymentTermFinder {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new PaymentTermFinder(repository);
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import {
|
||||||
|
CreatePaymentTermInputMapper,
|
||||||
|
type ICreatePaymentTermInputMapper,
|
||||||
|
UpdatePaymentTermInputMapper,
|
||||||
|
} from "../mappers";
|
||||||
|
|
||||||
|
export interface IPaymentTermInputMappers {
|
||||||
|
createInputMapper: ICreatePaymentTermInputMapper;
|
||||||
|
updateInputMapper: UpdatePaymentTermInputMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildPaymentTermInputMappers = (): IPaymentTermInputMappers => {
|
||||||
|
const createInputMapper = new CreatePaymentTermInputMapper();
|
||||||
|
const updateInputMapper = new UpdatePaymentTermInputMapper();
|
||||||
|
|
||||||
|
return {
|
||||||
|
createInputMapper,
|
||||||
|
updateInputMapper,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import {
|
||||||
|
PaymentTermFullSnapshotBuilder,
|
||||||
|
PaymentTermSummarySnapshotBuilder,
|
||||||
|
} from "../snapshot-builders";
|
||||||
|
|
||||||
|
export function buildPaymentTermSnapshotBuilders() {
|
||||||
|
const fullSnapshotBuilder = new PaymentTermFullSnapshotBuilder();
|
||||||
|
const summarySnapshotBuilder = new PaymentTermSummarySnapshotBuilder();
|
||||||
|
|
||||||
|
return {
|
||||||
|
full: fullSnapshotBuilder,
|
||||||
|
summary: summarySnapshotBuilder,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
|
import { type IPaymentTermStatusChanger, PaymentTermStatusChanger } from "../services";
|
||||||
|
|
||||||
|
export const buildPaymentTermStatusChanger = (params: {
|
||||||
|
repository: IPaymentTermRepository;
|
||||||
|
}): IPaymentTermStatusChanger => {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new PaymentTermStatusChanger(repository);
|
||||||
|
};
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
|
import { type IPaymentTermUpdater, PaymentTermUpdater } from "../services";
|
||||||
|
|
||||||
|
export const buildPaymentTermUpdater = (params: {
|
||||||
|
repository: IPaymentTermRepository;
|
||||||
|
}): IPaymentTermUpdater => {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new PaymentTermUpdater(repository);
|
||||||
|
};
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./di";
|
||||||
|
export * from "./mappers";
|
||||||
|
export * from "./models";
|
||||||
|
export * from "./repositories";
|
||||||
|
export * from "./services";
|
||||||
|
export * from "./snapshot-builders";
|
||||||
|
export * from "./use-cases";
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
import {
|
||||||
|
DomainError,
|
||||||
|
Name,
|
||||||
|
TextValue,
|
||||||
|
UniqueID,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
maybeFromNullableResult,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { CreatePaymentTermRequestDTO } from "../../../../common";
|
||||||
|
import {
|
||||||
|
type IPaymentTermCreateProps,
|
||||||
|
PaymentTermDueDays,
|
||||||
|
PaymentTermPercentage,
|
||||||
|
} from "../../../domain";
|
||||||
|
import type { PaymentTermDueRuleCreateProps } from "../../../domain/payment-terms/payment-term-due-rule";
|
||||||
|
import type { PaymentTermName } from "../../../domain/payment-terms/payment-term-name";
|
||||||
|
|
||||||
|
export interface ICreatePaymentTermInputMapper {
|
||||||
|
map(
|
||||||
|
dto: CreatePaymentTermRequestDTO,
|
||||||
|
params: { companyId: UniqueID }
|
||||||
|
): Result<{ id: UniqueID; props: IPaymentTermCreateProps }, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreatePaymentTermInputMapper implements ICreatePaymentTermInputMapper {
|
||||||
|
public map(
|
||||||
|
dto: CreatePaymentTermRequestDTO,
|
||||||
|
params: { companyId: UniqueID }
|
||||||
|
): Result<{ id: UniqueID; props: IPaymentTermCreateProps }, Error> {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paymentTermId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||||
|
|
||||||
|
const name = extractOrPushError(Name.create(dto.name), "name", errors);
|
||||||
|
|
||||||
|
const description = extractOrPushError(
|
||||||
|
maybeFromNullableResult(dto.description, (value) => TextValue.create(value)),
|
||||||
|
"description",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const isActive = extractOrPushError(Result.ok(dto.is_active), "is_active", errors);
|
||||||
|
|
||||||
|
const dueRules = this.mapRulesProps(dto.due_rules, { errors });
|
||||||
|
|
||||||
|
this.throwIfValidationErrors(errors);
|
||||||
|
|
||||||
|
const props: IPaymentTermCreateProps = {
|
||||||
|
companyId: params.companyId,
|
||||||
|
name: name as PaymentTermName,
|
||||||
|
description: description!,
|
||||||
|
isActive: isActive!,
|
||||||
|
isSystem: false,
|
||||||
|
dueRules,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok({ id: paymentTermId!, props });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(
|
||||||
|
new DomainError("Payment term props mapping failed [CreatePaymentTermInputMapper.map]", {
|
||||||
|
cause: err,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapRulesProps(
|
||||||
|
rulesDTO: NonNullable<CreatePaymentTermRequestDTO["due_rules"]>,
|
||||||
|
params: { errors: ValidationErrorDetail[] }
|
||||||
|
): PaymentTermDueRuleCreateProps[] {
|
||||||
|
return rulesDTO.map((item, index) => {
|
||||||
|
const dueDays = extractOrPushError(
|
||||||
|
PaymentTermDueDays.create(Number(item.due_days)),
|
||||||
|
`due_rules[${index}].due_days`,
|
||||||
|
params.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const percentage = extractOrPushError(
|
||||||
|
PaymentTermPercentage.create({
|
||||||
|
value: Number(item.percentage.value),
|
||||||
|
scale: Number(item.percentage.scale),
|
||||||
|
}),
|
||||||
|
`due_rules[${index}].percentage`,
|
||||||
|
params.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dueDays: dueDays!,
|
||||||
|
percentage: percentage!,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new ValidationErrorCollection(
|
||||||
|
"Payment method props mapping failed [CreatePaymentTermInputMapper]",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./create-payment-term-input.mapper";
|
||||||
|
export * from "./update-payment-term-by-id-input.mapper";
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
import {
|
||||||
|
DomainError,
|
||||||
|
Name,
|
||||||
|
TextValue,
|
||||||
|
type UniqueID,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
maybeFromNullableResult,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { UpdatePaymentTermByIdRequestDTO } from "../../../../common";
|
||||||
|
import {
|
||||||
|
PaymentTermDueDays,
|
||||||
|
type PaymentTermDueRulePatchProps,
|
||||||
|
type PaymentTermPatchProps,
|
||||||
|
PaymentTermPercentage,
|
||||||
|
} from "../../../domain";
|
||||||
|
|
||||||
|
export interface IUpdatePaymentTermInputMapper {
|
||||||
|
map(
|
||||||
|
dto: UpdatePaymentTermByIdRequestDTO,
|
||||||
|
params: { companyId: UniqueID }
|
||||||
|
): Result<PaymentTermPatchProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Convierte el DTO de update de payment term en props de dominio.
|
||||||
|
* @remarks
|
||||||
|
* Respeta semántica PATCH en cabecera:
|
||||||
|
* - omitido: no modificar
|
||||||
|
* - null: limpiar valor cuando el campo lo permite
|
||||||
|
* - valor: asignar nuevo valor
|
||||||
|
*
|
||||||
|
* Para `items`, no aplica patch granular:
|
||||||
|
* - undefined: no tocar líneas
|
||||||
|
* - []: borrar todas las líneas
|
||||||
|
* - [...]: reemplazar colección completa
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class UpdatePaymentTermInputMapper implements IUpdatePaymentTermInputMapper {
|
||||||
|
public map(
|
||||||
|
dto: UpdatePaymentTermByIdRequestDTO,
|
||||||
|
_params: { companyId: UniqueID }
|
||||||
|
): Result<PaymentTermPatchProps> {
|
||||||
|
try {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
const paymentTermPatchProps: PaymentTermPatchProps = {};
|
||||||
|
|
||||||
|
toPatchField(dto.name).ifSet((name) => {
|
||||||
|
if (isNullishOrEmpty(name)) {
|
||||||
|
errors.push({ path: "name", message: "Name cannot be empty" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentTermPatchProps.name = extractOrPushError(Name.create(name), "name", errors);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.description).ifSet((description) => {
|
||||||
|
paymentTermPatchProps.description = extractOrPushError(
|
||||||
|
maybeFromNullableResult(description, (value) => TextValue.create(value)),
|
||||||
|
"description",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.is_active).ifSet((isActive) => {
|
||||||
|
paymentTermPatchProps.isActive = isActive;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dto.due_rules !== undefined) {
|
||||||
|
paymentTermPatchProps.dueRules = this.mapRulesProps(dto.due_rules, { errors });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.throwIfValidationErrors(errors);
|
||||||
|
|
||||||
|
return Result.ok(paymentTermPatchProps);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(new DomainError("Payment term props mapping failed", { cause: err }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapRulesProps(
|
||||||
|
rulesDTO: NonNullable<UpdatePaymentTermByIdRequestDTO["due_rules"]>,
|
||||||
|
params: { errors: ValidationErrorDetail[] }
|
||||||
|
): PaymentTermDueRulePatchProps[] {
|
||||||
|
const dueRuleProps = rulesDTO.map((rule, index) => {
|
||||||
|
const dueDays = extractOrPushError(
|
||||||
|
PaymentTermDueDays.create(Number(rule.due_days)),
|
||||||
|
`due_rules[${index}].due_days`,
|
||||||
|
params.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const percentage = extractOrPushError(
|
||||||
|
PaymentTermPercentage.create({
|
||||||
|
value: Number(rule.percentage.value),
|
||||||
|
scale: Number(rule.percentage.scale),
|
||||||
|
}),
|
||||||
|
`due_rules[${index}].percentage`,
|
||||||
|
params.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dueDays: dueDays!,
|
||||||
|
percentage: percentage!,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return dueRuleProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new ValidationErrorCollection(
|
||||||
|
"Payment method props mapping failed [CreatePaymentTermInputMapper]",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./payment-term-summary.model";
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import type { PaymentTermDueRule } from "@erp/catalogs/api/domain";
|
||||||
|
import type { Name, UniqueID } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
export type PaymentTermSummary = {
|
||||||
|
id: UniqueID;
|
||||||
|
companyId: UniqueID;
|
||||||
|
name: Name;
|
||||||
|
isActive: boolean;
|
||||||
|
isSystem: boolean;
|
||||||
|
dueRules: Array<PaymentTermDueRule>;
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './payment-term-repository.interface';
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
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 { PaymentTerm } from "../../../domain";
|
||||||
|
import type { PaymentTermSummary } from "../models/payment-term-summary.model";
|
||||||
|
|
||||||
|
export interface IPaymentTermRepository {
|
||||||
|
create(paymentTerm: PaymentTerm, transaction?: unknown): Promise<Result<void, Error>>;
|
||||||
|
update(paymentTerm: PaymentTerm, 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<PaymentTerm, Error>>;
|
||||||
|
findByCriteriaInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
criteria: Criteria,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<Collection<PaymentTermSummary>, Error>>;
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
export * from './payment-term-creator';
|
||||||
|
export * from './payment-term-deleter';
|
||||||
|
export * from './payment-term-finder';
|
||||||
|
export * from './payment-term-public-services';
|
||||||
|
export * from './payment-term-status-changer';
|
||||||
|
export * from './payment-term-updater';
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { type IPaymentTermCreateProps, PaymentTerm } from "../../../domain";
|
||||||
|
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||||
|
|
||||||
|
export interface IPaymentTermCreatorParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
props: IPaymentTermCreateProps;
|
||||||
|
transaction: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPaymentTermCreator {
|
||||||
|
create(params: IPaymentTermCreatorParams): Promise<Result<PaymentTerm, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentTermCreator implements IPaymentTermCreator {
|
||||||
|
constructor(private readonly repository: IPaymentTermRepository) {}
|
||||||
|
|
||||||
|
public async create(params: IPaymentTermCreatorParams): Promise<Result<PaymentTerm, Error>> {
|
||||||
|
const { companyId, id, props, transaction } = params;
|
||||||
|
|
||||||
|
const paymentTermResult = PaymentTerm.create({ ...props, companyId }, id);
|
||||||
|
if (paymentTermResult.isFailure) {
|
||||||
|
return Result.fail(paymentTermResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentTerm = paymentTermResult.data;
|
||||||
|
const saveResult = await this.repository.create(paymentTerm, transaction);
|
||||||
|
if (saveResult.isFailure) {
|
||||||
|
return Result.fail(saveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(paymentTerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentTerm } from "../../../domain";
|
||||||
|
import { PaymentTermCannotBeDeletedError } from "../../../domain";
|
||||||
|
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||||
|
|
||||||
|
export interface IPaymentTermDeleter {
|
||||||
|
delete(params: {
|
||||||
|
paymentTerm: PaymentTerm;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentTerm, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentTermDeleter implements IPaymentTermDeleter {
|
||||||
|
public constructor(private readonly repository: IPaymentTermRepository) {}
|
||||||
|
|
||||||
|
public async delete(params: {
|
||||||
|
paymentTerm: PaymentTerm;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentTerm, Error>> {
|
||||||
|
const { paymentTerm, transaction } = params;
|
||||||
|
|
||||||
|
if (paymentTerm.isSystem) {
|
||||||
|
return Result.fail(
|
||||||
|
new PaymentTermCannotBeDeletedError("System payment terms cannot be deleted.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteResult = await this.repository.deleteByIdInCompany(
|
||||||
|
paymentTerm.companyId,
|
||||||
|
paymentTerm.id,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deleteResult.isFailure) {
|
||||||
|
return Result.fail(deleteResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(paymentTerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
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 { PaymentTerm } from "../../../domain";
|
||||||
|
import type { PaymentTermSummary } from "../models/payment-term-summary.model";
|
||||||
|
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||||
|
|
||||||
|
export interface IPaymentTermFinder {
|
||||||
|
findPaymentTermById(
|
||||||
|
companyId: UniqueID,
|
||||||
|
paymentTermId: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<PaymentTerm, Error>>;
|
||||||
|
|
||||||
|
paymentTermExists(
|
||||||
|
companyId: UniqueID,
|
||||||
|
paymentTermId: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
findPaymentTermsByCriteria(
|
||||||
|
companyId: UniqueID,
|
||||||
|
criteria: Criteria,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<Collection<PaymentTermSummary>, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentTermFinder implements IPaymentTermFinder {
|
||||||
|
constructor(private readonly repository: IPaymentTermRepository) {}
|
||||||
|
|
||||||
|
public async findPaymentTermById(
|
||||||
|
companyId: UniqueID,
|
||||||
|
paymentTermId: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<PaymentTerm, Error>> {
|
||||||
|
return this.repository.getByIdInCompany(companyId, paymentTermId, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async paymentTermExists(
|
||||||
|
companyId: UniqueID,
|
||||||
|
paymentTermId: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
return this.repository.existsByIdInCompany(companyId, paymentTermId, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findPaymentTermsByCriteria(
|
||||||
|
companyId: UniqueID,
|
||||||
|
criteria: Criteria,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<Collection<PaymentTermSummary>, Error>> {
|
||||||
|
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import type { IPaymentTermFinder } from "./payment-term-finder";
|
||||||
|
|
||||||
|
export interface IPaymentTermPublicServices {
|
||||||
|
finder: IPaymentTermFinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildPaymentTermPublicServices = (
|
||||||
|
finder: IPaymentTermFinder
|
||||||
|
): IPaymentTermPublicServices => ({
|
||||||
|
finder,
|
||||||
|
});
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentTerm } from "../../../domain";
|
||||||
|
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||||
|
|
||||||
|
export type PaymentTermStatusChangeAction = "enable" | "disable";
|
||||||
|
|
||||||
|
export interface IPaymentTermStatusChanger {
|
||||||
|
changeStatus(params: {
|
||||||
|
paymentTerm: PaymentTerm;
|
||||||
|
action: PaymentTermStatusChangeAction;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentTerm, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentTermStatusChanger implements IPaymentTermStatusChanger {
|
||||||
|
public constructor(private readonly repository: IPaymentTermRepository) {}
|
||||||
|
|
||||||
|
public async changeStatus(params: {
|
||||||
|
paymentTerm: PaymentTerm;
|
||||||
|
action: PaymentTermStatusChangeAction;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentTerm, Error>> {
|
||||||
|
const { paymentTerm, action, transaction } = params;
|
||||||
|
|
||||||
|
const statusResult = action === "enable" ? paymentTerm.enable() : paymentTerm.disable();
|
||||||
|
if (statusResult.isFailure) {
|
||||||
|
return Result.fail(statusResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statusResult.data) {
|
||||||
|
return Result.ok(paymentTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistenceResult = await this.repository.update(paymentTerm, transaction);
|
||||||
|
if (persistenceResult.isFailure) {
|
||||||
|
return Result.fail(persistenceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(paymentTerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentTerm, PaymentTermPatchProps } from "../../../domain";
|
||||||
|
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||||
|
|
||||||
|
export interface IPaymentTermUpdater {
|
||||||
|
update(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
patchProps: PaymentTermPatchProps;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentTerm, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentTermUpdater implements IPaymentTermUpdater {
|
||||||
|
constructor(private readonly repository: IPaymentTermRepository) {}
|
||||||
|
|
||||||
|
public async update(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
patchProps: PaymentTermPatchProps;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentTerm, 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 paymentTerm = existingResult.data;
|
||||||
|
const updateResult = paymentTerm.update(patchProps);
|
||||||
|
if (updateResult.isFailure) {
|
||||||
|
return Result.fail(updateResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateResult.data) {
|
||||||
|
return Result.ok(paymentTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveResult = await this.repository.update(paymentTerm, transaction);
|
||||||
|
if (saveResult.isFailure) {
|
||||||
|
return Result.fail(saveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(paymentTerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./payment-term-full-snapshot-builder";
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import type { GetPaymentTermByIdResponseDTO } from "@erp/catalogs/common";
|
||||||
|
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||||
|
import { toNullable } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
import type { PaymentTerm } from "../../../../domain/payment-terms";
|
||||||
|
|
||||||
|
export interface IPaymentTermFullSnapshotBuilder
|
||||||
|
extends ISnapshotBuilder<PaymentTerm, GetPaymentTermByIdResponseDTO> {}
|
||||||
|
|
||||||
|
export class PaymentTermFullSnapshotBuilder implements IPaymentTermFullSnapshotBuilder {
|
||||||
|
public toOutput(paymentTerm: PaymentTerm): GetPaymentTermByIdResponseDTO {
|
||||||
|
return {
|
||||||
|
id: paymentTerm.id.toPrimitive(),
|
||||||
|
company_id: paymentTerm.companyId.toPrimitive(),
|
||||||
|
name: paymentTerm.name.toPrimitive(),
|
||||||
|
description: toNullable(paymentTerm.description, (value) => value.toPrimitive()),
|
||||||
|
is_active: paymentTerm.isActive,
|
||||||
|
is_system: paymentTerm.isSystem,
|
||||||
|
due_rules: paymentTerm.dueRules.map((rule) => ({
|
||||||
|
due_days: rule.dueDays.toString(),
|
||||||
|
percentage: rule.percentage.toObjectString(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./full";
|
||||||
|
export * from "./summary";
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import type { PaymentTermDueRuleDTO } from "../../../../common";
|
||||||
|
import type { PaymentTermDueRule } from "../../../domain/payment-terms/payment-term-due-rule";
|
||||||
|
|
||||||
|
export interface IPaymentTermDueRuleSnapshotBuilder {
|
||||||
|
toOutput(paymentTermDueRule: PaymentTermDueRule): PaymentTermDueRuleDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentTermDueRuleSnapshotBuilder implements IPaymentTermDueRuleSnapshotBuilder {
|
||||||
|
public toOutput(paymentTermDueRule: PaymentTermDueRule): PaymentTermDueRuleDTO {
|
||||||
|
return {
|
||||||
|
due_days: paymentTermDueRule.dueDays.toString(),
|
||||||
|
percentage: paymentTermDueRule.percentage.toObjectString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./payment-term-summary-snapshot-builder";
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { PaymentTermSummaryDTO } from "../../../../../common";
|
||||||
|
import type { PaymentTermSummary } from "../../models";
|
||||||
|
|
||||||
|
export interface IPaymentTermSummarySnapshotBuilder
|
||||||
|
extends ISnapshotBuilder<PaymentTermSummary, PaymentTermSummaryDTO> {}
|
||||||
|
|
||||||
|
export class PaymentTermSummarySnapshotBuilder implements IPaymentTermSummarySnapshotBuilder {
|
||||||
|
public toOutput(paymentTerm: PaymentTermSummary): PaymentTermSummaryDTO {
|
||||||
|
return {
|
||||||
|
id: paymentTerm.id.toString(),
|
||||||
|
company_id: paymentTerm.companyId.toString(),
|
||||||
|
name: paymentTerm.name.toString(),
|
||||||
|
is_system: paymentTerm.isSystem,
|
||||||
|
is_active: paymentTerm.isActive,
|
||||||
|
due_rules: paymentTerm.dueRules.map((rule) => ({
|
||||||
|
due_days: rule.dueDays.toString(),
|
||||||
|
percentage: rule.percentage.toObjectString(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import type { CreatePaymentTermRequestDTO } 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 { ICreatePaymentTermInputMapper } from "../mappers";
|
||||||
|
import type { IPaymentTermCreator } from "../services";
|
||||||
|
import type { IPaymentTermFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type CreatePaymentTermUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
dto: CreatePaymentTermRequestDTO;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CreatePaymentTermUseCaseDeps = {
|
||||||
|
dtoMapper: ICreatePaymentTermInputMapper;
|
||||||
|
creator: IPaymentTermCreator;
|
||||||
|
fullSnapshotBuilder: IPaymentTermFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CreatePaymentTermUseCase {
|
||||||
|
constructor(private readonly deps: CreatePaymentTermUseCaseDeps) {}
|
||||||
|
|
||||||
|
public execute(params: CreatePaymentTermUseCaseInput) {
|
||||||
|
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 type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IPaymentTermDeleter, IPaymentTermFinder } from "../services";
|
||||||
|
import type { IPaymentTermFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type DeletePaymentTermByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
payment_term_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DeletePaymentTermByIdUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
finder: IPaymentTermFinder;
|
||||||
|
deleter: IPaymentTermDeleter;
|
||||||
|
fullSnapshotBuilder: IPaymentTermFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: DeletePaymentTermByIdUseCaseInput) {
|
||||||
|
const { payment_term_id, companyId } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(payment_term_id);
|
||||||
|
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||||
|
|
||||||
|
const paymentTermId = idOrError.data;
|
||||||
|
|
||||||
|
return this.deps.transactionManager.complete(async (transaction: unknown) => {
|
||||||
|
try {
|
||||||
|
const findResult = await this.deps.finder.findPaymentTermById(
|
||||||
|
companyId,
|
||||||
|
paymentTermId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
if (findResult.isFailure) {
|
||||||
|
return Result.fail(findResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteResult = await this.deps.deleter.delete({
|
||||||
|
paymentTerm: 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 type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IPaymentTermFinder, IPaymentTermStatusChanger } from "../services";
|
||||||
|
import type { IPaymentTermFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type DisablePaymentTermByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
payment_term_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DisablePaymentTermByIdUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
finder: IPaymentTermFinder;
|
||||||
|
changer: IPaymentTermStatusChanger;
|
||||||
|
fullSnapshotBuilder: IPaymentTermFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: DisablePaymentTermByIdUseCaseInput) {
|
||||||
|
const { payment_term_id, companyId } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(payment_term_id);
|
||||||
|
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||||
|
|
||||||
|
const paymentTermId = idOrError.data;
|
||||||
|
|
||||||
|
return this.deps.transactionManager.complete(async (transaction: unknown) => {
|
||||||
|
try {
|
||||||
|
const findResult = await this.deps.finder.findPaymentTermById(
|
||||||
|
companyId,
|
||||||
|
paymentTermId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
if (findResult.isFailure) {
|
||||||
|
return Result.fail(findResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const disableResult = await this.deps.changer.changeStatus({
|
||||||
|
paymentTerm: 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 type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IPaymentTermFinder, IPaymentTermStatusChanger } from "../services";
|
||||||
|
import type { IPaymentTermFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type EnablePaymentTermByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
payment_term_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EnablePaymentTermByIdUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
finder: IPaymentTermFinder;
|
||||||
|
changer: IPaymentTermStatusChanger;
|
||||||
|
fullSnapshotBuilder: IPaymentTermFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: EnablePaymentTermByIdUseCaseInput) {
|
||||||
|
const { payment_term_id, companyId } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(payment_term_id);
|
||||||
|
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||||
|
|
||||||
|
const paymentTermId = idOrError.data;
|
||||||
|
|
||||||
|
return this.deps.transactionManager.complete(async (transaction: unknown) => {
|
||||||
|
try {
|
||||||
|
const findResult = await this.deps.finder.findPaymentTermById(
|
||||||
|
companyId,
|
||||||
|
paymentTermId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
if (findResult.isFailure) {
|
||||||
|
return Result.fail(findResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableResult = await this.deps.changer.changeStatus({
|
||||||
|
paymentTerm: 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 { IPaymentTermFinder } from "../services";
|
||||||
|
import type { IPaymentTermFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type GetPaymentTermByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
payment_term_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class GetPaymentTermByIdUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly finder: IPaymentTermFinder,
|
||||||
|
private readonly fullSnapshotBuilder: IPaymentTermFullSnapshotBuilder,
|
||||||
|
private readonly transactionManager: ITransactionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: GetPaymentTermByIdUseCaseInput) {
|
||||||
|
const { payment_term_id, companyId } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(payment_term_id);
|
||||||
|
if (idOrError.isFailure) {
|
||||||
|
return Result.fail(idOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentTermId = idOrError.data;
|
||||||
|
|
||||||
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
|
try {
|
||||||
|
const result = await this.finder.findPaymentTermById(companyId, paymentTermId, 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-payment-term.use-case";
|
||||||
|
export * from "./delete-payment-term-by-id.use-case";
|
||||||
|
export * from "./disable-payment-term-by-id.use-case";
|
||||||
|
export * from "./enable-payment-term-by-id.use-case";
|
||||||
|
export * from "./get-payment-term-by-id.use-case";
|
||||||
|
export * from "./list-payment-terms.use-case";
|
||||||
|
export * from "./update-payment-term-by-id.use-case";
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
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 { IPaymentTermFinder } from "../services";
|
||||||
|
import type { IPaymentTermSummarySnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
type ListPaymentTermsUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
criteria: Criteria;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ListPaymentTermsUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly finder: IPaymentTermFinder,
|
||||||
|
private readonly summarySnapshotBuilder: IPaymentTermSummarySnapshotBuilder,
|
||||||
|
private readonly transactionManager: ITransactionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: ListPaymentTermsUseCaseInput) {
|
||||||
|
const { criteria, companyId } = params;
|
||||||
|
|
||||||
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
|
try {
|
||||||
|
const result = await this.finder.findPaymentTermsByCriteria(
|
||||||
|
companyId,
|
||||||
|
criteria,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentTerms = result.data;
|
||||||
|
const totalItems = paymentTerms.total();
|
||||||
|
|
||||||
|
const items = paymentTerms.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: "payment_terms",
|
||||||
|
criteria: criteria.toJSON(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok(snapshot);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import type { UpdatePaymentTermByIdRequestDTO } 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 { IUpdatePaymentTermInputMapper } from "../mappers";
|
||||||
|
import type { IPaymentTermFinder, IPaymentTermUpdater } from "../services";
|
||||||
|
import type { IPaymentTermFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type UpdatePaymentTermByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
payment_term_id: string;
|
||||||
|
dto: UpdatePaymentTermByIdRequestDTO;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UpdatePaymentTermByIdUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
updater: IPaymentTermUpdater;
|
||||||
|
finder: IPaymentTermFinder;
|
||||||
|
dtoMapper: IUpdatePaymentTermInputMapper;
|
||||||
|
fullSnapshotBuilder: IPaymentTermFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: UpdatePaymentTermByIdUseCaseInput) {
|
||||||
|
const { companyId, payment_term_id, dto } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(payment_term_id);
|
||||||
|
if (idOrError.isFailure) {
|
||||||
|
return Result.fail(idOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentTermId = 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: paymentTermId,
|
||||||
|
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 +1,2 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
|
export * from "./payment-terms";
|
||||||
|
|||||||
126
modules/catalogs/src/api/domain/payment-terms/errors.ts
Normal file
126
modules/catalogs/src/api/domain/payment-terms/errors.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { DomainError } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
export class InvalidPaymentTermIdError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_INVALID_ID" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidPaymentTermIdError = (e: unknown): e is InvalidPaymentTermIdError =>
|
||||||
|
e instanceof InvalidPaymentTermIdError;
|
||||||
|
|
||||||
|
export class InvalidPaymentTermNameError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_NAME" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidPaymentTermNameError = (e: unknown): e is InvalidPaymentTermNameError =>
|
||||||
|
e instanceof InvalidPaymentTermNameError;
|
||||||
|
|
||||||
|
export class InvalidPaymentTermDescriptionError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_DESCRIPTION" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidPaymentTermDescriptionError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidPaymentTermDescriptionError => e instanceof InvalidPaymentTermDescriptionError;
|
||||||
|
|
||||||
|
export class InvalidPaymentTermDueDaysError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_DUE_DAYS" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidPaymentTermDueDaysError = (e: unknown): e is InvalidPaymentTermDueDaysError =>
|
||||||
|
e instanceof InvalidPaymentTermDueDaysError;
|
||||||
|
|
||||||
|
export class InvalidPaymentTermPercentageError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_PERCENTAGE" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidPaymentTermPercentageError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidPaymentTermPercentageError => e instanceof InvalidPaymentTermPercentageError;
|
||||||
|
|
||||||
|
export class InvalidPaymentTermDueRulesError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_DUE_RULES" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidPaymentTermDueRulesError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidPaymentTermDueRulesError => e instanceof InvalidPaymentTermDueRulesError;
|
||||||
|
|
||||||
|
export class PaymentTermDueDaysDuplicatedError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_DUE_DAYS_DUPLICATED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentTermDueDaysDuplicatedError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentTermDueDaysDuplicatedError => e instanceof PaymentTermDueDaysDuplicatedError;
|
||||||
|
|
||||||
|
export class PaymentTermPercentageSumMismatchError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_PERCENTAGE_SUM_MISMATCH" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentTermPercentageSumMismatchError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentTermPercentageSumMismatchError => e instanceof PaymentTermPercentageSumMismatchError;
|
||||||
|
|
||||||
|
export class PaymentTermNotFoundError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_NOT_FOUND" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentTermNotFoundError = (e: unknown): e is PaymentTermNotFoundError =>
|
||||||
|
e instanceof PaymentTermNotFoundError;
|
||||||
|
|
||||||
|
export class PaymentTermCannotBeUpdatedError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_CANNOT_BE_UPDATED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentTermCannotBeUpdatedError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentTermCannotBeUpdatedError => e instanceof PaymentTermCannotBeUpdatedError;
|
||||||
|
|
||||||
|
export class PaymentTermCannotBeDeletedError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_CANNOT_BE_DELETED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentTermCannotBeDeletedError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentTermCannotBeDeletedError => e instanceof PaymentTermCannotBeDeletedError;
|
||||||
|
|
||||||
|
export class PaymentTermCannotBeEnabledError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_CANNOT_BE_ENABLED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentTermCannotBeEnabledError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentTermCannotBeEnabledError => e instanceof PaymentTermCannotBeEnabledError;
|
||||||
|
|
||||||
|
export class PaymentTermCannotBeDisabledError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_TERM_CANNOT_BE_DISABLED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentTermCannotBeDisabledError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentTermCannotBeDisabledError => e instanceof PaymentTermCannotBeDisabledError;
|
||||||
|
|
||||||
|
export class PaymentTermDueRuleMismatch extends DomainError {
|
||||||
|
/**
|
||||||
|
* Crea una instancia del error con el identificador del item.
|
||||||
|
*
|
||||||
|
* @param position - Posición del item
|
||||||
|
* @param options - Opciones nativas de Error (puedes pasar `cause`).
|
||||||
|
*/
|
||||||
|
constructor(position: number, options?: ErrorOptions) {
|
||||||
|
super(
|
||||||
|
`Error. Payment term due rule with position '${position}' rejected due to currency/language mismatch.`,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
this.name = "PaymentTermDueRuleMismatch";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *Type guard* para `PaymentTermDueRuleMismatch`.
|
||||||
|
*
|
||||||
|
* @param e - Error desconocido
|
||||||
|
* @returns `true` si `e` es `PaymentTermDueRuleMismatch`
|
||||||
|
*/
|
||||||
|
export const isPaymentTermDueRuleMismatch = (e: unknown): e is PaymentTermDueRuleMismatch =>
|
||||||
|
e instanceof PaymentTermDueRuleMismatch;
|
||||||
7
modules/catalogs/src/api/domain/payment-terms/index.ts
Normal file
7
modules/catalogs/src/api/domain/payment-terms/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./errors";
|
||||||
|
export * from "./payment-term.aggregate";
|
||||||
|
export * from "./payment-term-due-days";
|
||||||
|
export * from "./payment-term-due-rule";
|
||||||
|
export * from "./payment-term-due-rules.collection";
|
||||||
|
export * from "./payment-term-name";
|
||||||
|
export * from "./payment-term-percentage";
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { ValueObject, translateZodValidationError } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export class PaymentTermDueDays extends ValueObject<{ value: number }> {
|
||||||
|
protected constructor(props: { value: number }) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static validate(value: number) {
|
||||||
|
const schema = z.number().int().min(0, "Payment term due days must be a non-negative integer");
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(value: number): Result<PaymentTermDueDays, Error> {
|
||||||
|
const validation = PaymentTermDueDays.validate(value);
|
||||||
|
|
||||||
|
if (!validation.success) {
|
||||||
|
return Result.fail(
|
||||||
|
translateZodValidationError("InvalidPaymentTermDueDays", validation.error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new PaymentTermDueDays({ value: validation.data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public get value(): number {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): number {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getProps() {
|
||||||
|
return this.props;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
import { DomainEntity, type UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentTermDueDays } from "./payment-term-due-days";
|
||||||
|
import type { PaymentTermPercentage } from "./payment-term-percentage";
|
||||||
|
|
||||||
|
export type PaymentTermDueRuleCreateProps = {
|
||||||
|
dueDays: PaymentTermDueDays;
|
||||||
|
percentage: PaymentTermPercentage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PaymentTermDueRulePatchProps = PaymentTermDueRuleCreateProps;
|
||||||
|
|
||||||
|
type InternalPaymentTermDueRuleProps = PaymentTermDueRuleCreateProps;
|
||||||
|
|
||||||
|
export class PaymentTermDueRule extends DomainEntity<InternalPaymentTermDueRuleProps> {
|
||||||
|
static create(
|
||||||
|
props: PaymentTermDueRuleCreateProps,
|
||||||
|
id?: UniqueID
|
||||||
|
): Result<PaymentTermDueRule, Error> {
|
||||||
|
if (!props.dueDays) {
|
||||||
|
return Result.fail(new Error("Payment term due days is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.percentage) {
|
||||||
|
return Result.fail(new Error("Payment term percentage is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPaymentTermDueRule = new PaymentTermDueRule(props, id);
|
||||||
|
|
||||||
|
return Result.ok(newPaymentTermDueRule);
|
||||||
|
}
|
||||||
|
|
||||||
|
static rehydrate(props: InternalPaymentTermDueRuleProps, id: UniqueID): PaymentTermDueRule {
|
||||||
|
return new PaymentTermDueRule(props, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor(props: InternalPaymentTermDueRuleProps, id?: UniqueID) {
|
||||||
|
super(props, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public static fromDTO(dto: PaymentTermDueRuleDTO): Result<PaymentTermDueRule, Error> {
|
||||||
|
const errors: Array<{ path: string; message: string }> = [];
|
||||||
|
|
||||||
|
const dueDaysResult = PaymentTermDueDays.create(dto.due_days);
|
||||||
|
const percentageResult = PaymentTermPercentage.create(Number(dto.percentage.value));
|
||||||
|
|
||||||
|
if (dueDaysResult.isFailure) {
|
||||||
|
errors.push({ path: "due_days", message: dueDaysResult.error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (percentageResult.isFailure) {
|
||||||
|
errors.push({ path: "percentage", message: percentageResult.error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(new ValidationErrorCollection("InvalidPaymentTermDueRule", errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentTermDueRule.create({
|
||||||
|
dueDays: dueDaysResult.data,
|
||||||
|
percentage: percentageResult.data,
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public get dueDays(): PaymentTermDueDays {
|
||||||
|
return this.props.dueDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get percentage(): PaymentTermPercentage {
|
||||||
|
return this.props.percentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive() {
|
||||||
|
return {
|
||||||
|
due_days: this.dueDays.toPrimitive(),
|
||||||
|
percentage: this.percentage.toPrimitive(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public equals(other: PaymentTermDueRule): boolean {
|
||||||
|
return (
|
||||||
|
this.dueDays.toPrimitive() === other.dueDays.toPrimitive() &&
|
||||||
|
this.percentage.value === other.percentage.value &&
|
||||||
|
this.percentage.scale === other.percentage.scale
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
import { Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
InvalidPaymentTermPercentageError,
|
||||||
|
PaymentTermDueDaysDuplicatedError,
|
||||||
|
PaymentTermPercentageSumMismatchError,
|
||||||
|
} from "./errors";
|
||||||
|
import type { PaymentTermDueRule } from "./payment-term-due-rule";
|
||||||
|
|
||||||
|
export interface PaymentTermDueRulesProps {
|
||||||
|
rules?: PaymentTermDueRule[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// OJO, no extendemos de Collection<IProformaItem> para no exponer
|
||||||
|
// públicamente métodos para manipular la colección.
|
||||||
|
|
||||||
|
export type IPaymentTermDueRules = {};
|
||||||
|
|
||||||
|
export class PaymentTermDueRules
|
||||||
|
extends Collection<PaymentTermDueRule>
|
||||||
|
implements IPaymentTermDueRules
|
||||||
|
{
|
||||||
|
// OJO, no extendemos de Collection<PaymentTermDueRule> para no exponer
|
||||||
|
// públicamente métodos para manipular la colección.
|
||||||
|
|
||||||
|
private constructor(props: PaymentTermDueRulesProps) {
|
||||||
|
super(props.rules ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(props: PaymentTermDueRulesProps): PaymentTermDueRules {
|
||||||
|
const { rules } = props;
|
||||||
|
|
||||||
|
if (!rules || rules.length === 0) {
|
||||||
|
return new PaymentTermDueRules({ rules: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const ordered = [...rules].sort(
|
||||||
|
(first, second) => first.dueDays.toPrimitive() - second.dueDays.toPrimitive()
|
||||||
|
);
|
||||||
|
|
||||||
|
const seenDueDays = new Set<number>();
|
||||||
|
let totalPercentage = 0;
|
||||||
|
|
||||||
|
for (const item of ordered) {
|
||||||
|
const dueDays = item.dueDays.toPrimitive();
|
||||||
|
if (seenDueDays.has(dueDays)) {
|
||||||
|
throw new PaymentTermDueDaysDuplicatedError(
|
||||||
|
`Duplicate dueDays found in payment term due rules: ${dueDays}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
seenDueDays.add(dueDays);
|
||||||
|
const percentage = item.percentage;
|
||||||
|
if (percentage.scale !== 2) {
|
||||||
|
throw new InvalidPaymentTermPercentageError("Payment term percentage scale must be 2.");
|
||||||
|
}
|
||||||
|
totalPercentage += percentage.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalPercentage !== 10000) {
|
||||||
|
throw new PaymentTermPercentageSumMismatchError(
|
||||||
|
"Payment term due rule percentages must sum exactly 100.00."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PaymentTermDueRules({ rules: ordered });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
import { Name } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
export const PaymentTermName = Name;
|
||||||
|
export type PaymentTermName = Name;
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import { Percentage, type PercentageProps, ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
type PaymentTermPercentageProps = PercentageProps;
|
||||||
|
|
||||||
|
export class PaymentTermPercentage extends Percentage {
|
||||||
|
static DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
|
static create({ value, scale }: PaymentTermPercentageProps): Result<Percentage> {
|
||||||
|
if (scale && scale !== PaymentTermPercentage.DEFAULT_SCALE) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("InvalidScale", [
|
||||||
|
{ message: `PaymentTermPercentage scale must be ${PaymentTermPercentage.DEFAULT_SCALE}` },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(
|
||||||
|
new PaymentTermPercentage({
|
||||||
|
value,
|
||||||
|
scale: PaymentTermPercentage.DEFAULT_SCALE,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static zero() {
|
||||||
|
return PaymentTermPercentage.create({ value: 0 }).data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,229 @@
|
|||||||
|
import { AggregateRoot, type TextValue, type UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
PaymentTermCannotBeDisabledError,
|
||||||
|
PaymentTermCannotBeUpdatedError,
|
||||||
|
PaymentTermDueRuleMismatch,
|
||||||
|
} from "./errors";
|
||||||
|
import {
|
||||||
|
PaymentTermDueRule,
|
||||||
|
type PaymentTermDueRuleCreateProps,
|
||||||
|
type PaymentTermDueRulePatchProps,
|
||||||
|
} from "./payment-term-due-rule";
|
||||||
|
import { PaymentTermDueRules } from "./payment-term-due-rules.collection";
|
||||||
|
import type { PaymentTermName } from "./payment-term-name";
|
||||||
|
|
||||||
|
export interface IPaymentTermCreateProps {
|
||||||
|
companyId: UniqueID;
|
||||||
|
name: PaymentTermName;
|
||||||
|
description: Maybe<TextValue>;
|
||||||
|
isActive: boolean;
|
||||||
|
isSystem: boolean;
|
||||||
|
dueRules: PaymentTermDueRuleCreateProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaymentTermPatchProps = Partial<Omit<IPaymentTermCreateProps, "dueRules">> & {
|
||||||
|
dueRules?: PaymentTermDueRulePatchProps[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PaymentTermInternalProps = Omit<IPaymentTermCreateProps, "dueRules">;
|
||||||
|
|
||||||
|
export class PaymentTerm extends AggregateRoot<PaymentTermInternalProps> {
|
||||||
|
private _dueRules: PaymentTermDueRules;
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
props: PaymentTermInternalProps,
|
||||||
|
dueRules: PaymentTermDueRules,
|
||||||
|
id?: UniqueID
|
||||||
|
) {
|
||||||
|
super(props, id);
|
||||||
|
this._dueRules = dueRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(props: IPaymentTermCreateProps, id?: UniqueID): Result<PaymentTerm, Error> {
|
||||||
|
const validationResult = PaymentTerm.validateCreateProps(props);
|
||||||
|
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(validationResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const internalDueRules = PaymentTermDueRules.create({
|
||||||
|
rules: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { dueRules, ...internalProps } = props;
|
||||||
|
const paymentTerm = new PaymentTerm(internalProps, internalDueRules, id);
|
||||||
|
|
||||||
|
const initializeResult = paymentTerm.initializeRules(dueRules);
|
||||||
|
if (initializeResult.isFailure) {
|
||||||
|
return Result.fail(initializeResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(paymentTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static rehydrate(
|
||||||
|
props: PaymentTermInternalProps,
|
||||||
|
rules: PaymentTermDueRules,
|
||||||
|
id: UniqueID
|
||||||
|
): PaymentTerm {
|
||||||
|
return new PaymentTerm(props, rules, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static validateCreateProps(props: IPaymentTermCreateProps): Result<void, Error> {
|
||||||
|
if (!props.companyId) {
|
||||||
|
return Result.fail(new Error("Payment term company ID is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.name) {
|
||||||
|
return Result.fail(new Error("Payment term name is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.dueRules || props.dueRules.length === 0) {
|
||||||
|
return Result.fail(new Error("Payment term due rules are required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static validatePatchProps(patchProps: PaymentTermPatchProps): Result<void, Error> {
|
||||||
|
if (Object.keys(patchProps).length === 0) {
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeRules(
|
||||||
|
rulesProps: PaymentTermDueRuleCreateProps[] | PaymentTermDueRulePatchProps[]
|
||||||
|
): Result<void, Error> {
|
||||||
|
this._dueRules.reset();
|
||||||
|
|
||||||
|
for (const [index, ruleProps] of rulesProps.entries()) {
|
||||||
|
const ruleResult = PaymentTermDueRule.create(ruleProps);
|
||||||
|
|
||||||
|
if (ruleResult.isFailure) {
|
||||||
|
return Result.fail(ruleResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const added = this._dueRules.add(ruleResult.data);
|
||||||
|
|
||||||
|
if (!added) {
|
||||||
|
return Result.fail(new PaymentTermDueRuleMismatch(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get companyId(): UniqueID {
|
||||||
|
return this.props.companyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name(): PaymentTermName {
|
||||||
|
return this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get description(): Maybe<TextValue> {
|
||||||
|
return this.props.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isActive(): boolean {
|
||||||
|
return this.props.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isSystem(): boolean {
|
||||||
|
return this.props.isSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get dueRules(): PaymentTermDueRules {
|
||||||
|
return this._dueRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(patchProps: PaymentTermPatchProps): Result<boolean, Error> {
|
||||||
|
if (this.isSystem) {
|
||||||
|
return Result.fail(
|
||||||
|
new PaymentTermCannotBeUpdatedError("System payment terms cannot be updated.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationResult = PaymentTerm.validatePatchProps(patchProps);
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(validationResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasChanges = false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
patchProps.name !== undefined &&
|
||||||
|
patchProps.name.toPrimitive() !== this.props.name.toPrimitive()
|
||||||
|
) {
|
||||||
|
this.props.name = patchProps.name;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
patchProps.description !== undefined &&
|
||||||
|
!PaymentTerm.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reemplazo de items (si se proporciona)
|
||||||
|
if (patchProps.dueRules !== undefined) {
|
||||||
|
const initializeResult = this.initializeRules(patchProps.dueRules);
|
||||||
|
if (initializeResult.isFailure) {
|
||||||
|
return Result.fail(initializeResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(hasChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public disable(): Result<boolean, Error> {
|
||||||
|
if (this.isSystem) {
|
||||||
|
return Result.fail(
|
||||||
|
new PaymentTermCannotBeDisabledError("System payment terms 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static sameDescription(current: Maybe<TextValue>, next: Maybe<TextValue>): boolean {
|
||||||
|
return current.match(
|
||||||
|
(currentValue: TextValue) =>
|
||||||
|
next.match(
|
||||||
|
(nextValue: TextValue) => currentValue.toPrimitive() === nextValue.toPrimitive(),
|
||||||
|
() => false
|
||||||
|
),
|
||||||
|
() => next.isNone()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,15 @@
|
|||||||
import type { IModuleServer } from "@erp/core/api";
|
import type { IModuleServer } from "@erp/core/api";
|
||||||
|
|
||||||
import { models, paymentMethodsRouter } from "./infrastructure";
|
import {
|
||||||
|
paymentMethodModels,
|
||||||
|
paymentMethodsRouter,
|
||||||
|
paymentTermModels,
|
||||||
|
paymentTermsRouter,
|
||||||
|
} from "./infrastructure";
|
||||||
import {
|
import {
|
||||||
buildCatalogsDependencies,
|
buildCatalogsDependencies,
|
||||||
buildCatalogsPublicServices,
|
buildCatalogsPublicServices,
|
||||||
} from "./infrastructure/payment-methods/di/catalogs.di";
|
} from "./infrastructure/di/catalogs.di";
|
||||||
|
|
||||||
export * from "./infrastructure/payment-methods/persistence/sequelize";
|
export * from "./infrastructure/payment-methods/persistence/sequelize";
|
||||||
|
|
||||||
@ -22,9 +27,10 @@ export const catalogsAPIModule: IModuleServer = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
models,
|
models: [...paymentMethodModels, ...paymentTermModels],
|
||||||
services: {
|
services: {
|
||||||
paymentMethod: publicServices,
|
paymentMethod: publicServices.paymentMethods,
|
||||||
|
paymentTerms: publicServices.paymentTerms,
|
||||||
},
|
},
|
||||||
internal,
|
internal,
|
||||||
};
|
};
|
||||||
@ -34,6 +40,7 @@ export const catalogsAPIModule: IModuleServer = {
|
|||||||
const { logger } = params;
|
const { logger } = params;
|
||||||
|
|
||||||
paymentMethodsRouter(params);
|
paymentMethodsRouter(params);
|
||||||
|
paymentTermsRouter(params);
|
||||||
|
|
||||||
logger.info("🚀 Catalogs module started", {
|
logger.info("🚀 Catalogs module started", {
|
||||||
label: this.name,
|
label: this.name,
|
||||||
|
|||||||
@ -4,20 +4,28 @@ import {
|
|||||||
type PaymentMethodsInternalDeps,
|
type PaymentMethodsInternalDeps,
|
||||||
buildPaymentMethodsDependencies,
|
buildPaymentMethodsDependencies,
|
||||||
buildPaymentMethodsPublicServices,
|
buildPaymentMethodsPublicServices,
|
||||||
} from "./payment-methods.di";
|
} from "../payment-methods/di";
|
||||||
|
import {
|
||||||
|
type PaymentTermsInternalDeps,
|
||||||
|
buildPaymentTermsDependencies,
|
||||||
|
buildPaymentTermsPublicServices,
|
||||||
|
} from "../payment-terms/di";
|
||||||
|
|
||||||
export type CatalogsInternalDeps = {
|
export type CatalogsInternalDeps = {
|
||||||
paymentMethods: PaymentMethodsInternalDeps;
|
paymentMethods: PaymentMethodsInternalDeps;
|
||||||
|
paymentTerms: PaymentTermsInternalDeps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildCatalogsDependencies = (params: ModuleParams): CatalogsInternalDeps => {
|
export const buildCatalogsDependencies = (params: ModuleParams): CatalogsInternalDeps => {
|
||||||
return {
|
return {
|
||||||
paymentMethods: buildPaymentMethodsDependencies(params),
|
paymentMethods: buildPaymentMethodsDependencies(params),
|
||||||
|
paymentTerms: buildPaymentTermsDependencies(params),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildCatalogsPublicServices = (params: SetupParams, deps: CatalogsInternalDeps) => {
|
export const buildCatalogsPublicServices = (params: SetupParams, deps: CatalogsInternalDeps) => {
|
||||||
return {
|
return {
|
||||||
paymentMethods: buildPaymentMethodsPublicServices(params, deps.paymentMethods),
|
paymentMethods: buildPaymentMethodsPublicServices(params, deps.paymentMethods),
|
||||||
|
paymentTerms: buildPaymentTermsPublicServices(params, deps.paymentTerms),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
1
modules/catalogs/src/api/infrastructure/di/index.ts
Normal file
1
modules/catalogs/src/api/infrastructure/di/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./catalogs.di.ts";
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
|
export * from "./payment-terms";
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
export * from "./catalogs.di";
|
export * from "./payment-method-persistence-mappers.di";
|
||||||
|
export * from "./payment-method-repositories.di";
|
||||||
|
export * from "./payment-methods.di";
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { CreatePaymentMethodRequestDTO } from "../../../../../../common";
|
import type { CreatePaymentMethodRequestDTO } from "../../../../../common";
|
||||||
import type { CreatePaymentMethodUseCase } from "../../../../../application";
|
import type { CreatePaymentMethodUseCase } from "../../../../application";
|
||||||
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class CreatePaymentMethodController extends ExpressController {
|
export class CreatePaymentMethodController extends ExpressController {
|
||||||
@ -5,7 +5,7 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { DeletePaymentMethodByIdUseCase } from "../../../../../application";
|
import type { DeletePaymentMethodByIdUseCase } from "../../../../application";
|
||||||
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class DeletePaymentMethodByIdController extends ExpressController {
|
export class DeletePaymentMethodByIdController extends ExpressController {
|
||||||
@ -5,7 +5,7 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { DisablePaymentMethodByIdUseCase } from "../../../../../application";
|
import type { DisablePaymentMethodByIdUseCase } from "../../../../application";
|
||||||
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class DisablePaymentMethodByIdController extends ExpressController {
|
export class DisablePaymentMethodByIdController extends ExpressController {
|
||||||
@ -5,7 +5,7 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { EnablePaymentMethodByIdUseCase } from "../../../../../application";
|
import type { EnablePaymentMethodByIdUseCase } from "../../../../application";
|
||||||
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class EnablePaymentMethodByIdController extends ExpressController {
|
export class EnablePaymentMethodByIdController extends ExpressController {
|
||||||
@ -5,7 +5,7 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { GetPaymentMethodByIdUseCase } from "../../../../../application";
|
import type { GetPaymentMethodByIdUseCase } from "../../../../application";
|
||||||
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class GetPaymentMethodByIdController extends ExpressController {
|
export class GetPaymentMethodByIdController extends ExpressController {
|
||||||
@ -6,7 +6,7 @@ import {
|
|||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
|
|
||||||
import type { ListPaymentMethodsUseCase } from "../../../../../application";
|
import type { ListPaymentMethodsUseCase } from "../../../../application";
|
||||||
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class ListPaymentMethodsController extends ExpressController {
|
export class ListPaymentMethodsController extends ExpressController {
|
||||||
@ -6,7 +6,7 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { UpdatePaymentMethodByIdUseCase } from "../../../../../application";
|
import type { UpdatePaymentMethodByIdUseCase } from "../../../../application";
|
||||||
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class UpdatePaymentMethodByIdController extends ExpressController {
|
export class UpdatePaymentMethodByIdController extends ExpressController {
|
||||||
@ -1 +1 @@
|
|||||||
export * from "./payment-methods/payment-methods.routes";
|
export * from "./payment-methods.routes";
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import {
|
|||||||
isPaymentMethodCannotBeEnabledError,
|
isPaymentMethodCannotBeEnabledError,
|
||||||
isPaymentMethodCannotBeUpdatedError,
|
isPaymentMethodCannotBeUpdatedError,
|
||||||
isPaymentMethodNotFoundError,
|
isPaymentMethodNotFoundError,
|
||||||
} from "../../../../domain/payment-methods";
|
} from "../../../domain/payment-methods";
|
||||||
|
|
||||||
const invalidPaymentMethodIdRule: ErrorToApiRule = {
|
const invalidPaymentMethodIdRule: ErrorToApiRule = {
|
||||||
priority: 120,
|
priority: 120,
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
ListPaymentMethodsRequestSchema,
|
ListPaymentMethodsRequestSchema,
|
||||||
UpdatePaymentMethodByIdParamsRequestSchema,
|
UpdatePaymentMethodByIdParamsRequestSchema,
|
||||||
UpdatePaymentMethodByIdRequestSchema,
|
UpdatePaymentMethodByIdRequestSchema,
|
||||||
} from "../../../../../common";
|
} from "../../../../common";
|
||||||
import type { CatalogsInternalDeps } from "../../di/catalogs.di";
|
import type { CatalogsInternalDeps } from "../../di/catalogs.di";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -4,4 +4,4 @@ export * from "./repositories";
|
|||||||
|
|
||||||
import paymentMethodModelInit from "./models/sequelize-payment-method.model";
|
import paymentMethodModelInit from "./models/sequelize-payment-method.model";
|
||||||
|
|
||||||
export const models = [paymentMethodModelInit];
|
export const paymentMethodModels = [paymentMethodModelInit];
|
||||||
|
|||||||
@ -71,14 +71,12 @@ export class SequelizePaymentMethodRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { id, ...payload } = dtoResult.data;
|
const { id, ...payload } = dtoResult.data;
|
||||||
const [affected, updated] = await PaymentMethodModel.update(payload, {
|
const [affected] = await PaymentMethodModel.update(payload, {
|
||||||
where: { id },
|
where: { id },
|
||||||
transaction,
|
transaction,
|
||||||
individualHooks: true,
|
individualHooks: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Update result:", { affected, updated });
|
|
||||||
|
|
||||||
if (affected === 0) {
|
if (affected === 0) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new InfrastructureRepositoryError("Concurrency conflict or payment method not found")
|
new InfrastructureRepositoryError("Concurrency conflict or payment method not found")
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./payment-term-persistence-mappers.di";
|
||||||
|
export * from "./payment-term-repositories.di";
|
||||||
|
export * from "./payment-terms.di";
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
SequelizePaymentTermDomainMapper,
|
||||||
|
SequelizePaymentTermSummaryMapper,
|
||||||
|
} from "../persistence";
|
||||||
|
|
||||||
|
export interface IPaymentTermPersistenceMappers {
|
||||||
|
domainMapper: SequelizePaymentTermDomainMapper;
|
||||||
|
listMapper: SequelizePaymentTermSummaryMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildPaymentTermPersistenceMappers = (): IPaymentTermPersistenceMappers => {
|
||||||
|
const domainMapper = new SequelizePaymentTermDomainMapper();
|
||||||
|
const listMapper = new SequelizePaymentTermSummaryMapper();
|
||||||
|
|
||||||
|
return {
|
||||||
|
domainMapper,
|
||||||
|
listMapper,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
import { SequelizePaymentTermRepository } from "../persistence";
|
||||||
|
|
||||||
|
import type { IPaymentTermPersistenceMappers } from "./payment-term-persistence-mappers.di";
|
||||||
|
|
||||||
|
export const buildPaymentTermRepository = (params: {
|
||||||
|
database: Sequelize;
|
||||||
|
mappers: IPaymentTermPersistenceMappers;
|
||||||
|
}) => {
|
||||||
|
const { database, mappers } = params;
|
||||||
|
return new SequelizePaymentTermRepository(mappers.domainMapper, mappers.listMapper, database);
|
||||||
|
};
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
import type { ModuleParams, SetupParams } from "@erp/core/api";
|
||||||
|
import { buildTransactionManager } from "@erp/core/api";
|
||||||
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreatePaymentTermUseCase,
|
||||||
|
DeletePaymentTermByIdUseCase,
|
||||||
|
DisablePaymentTermByIdUseCase,
|
||||||
|
EnablePaymentTermByIdUseCase,
|
||||||
|
GetPaymentTermByIdUseCase,
|
||||||
|
ListPaymentTermsUseCase,
|
||||||
|
UpdatePaymentTermByIdUseCase,
|
||||||
|
buildPaymentTermCreator,
|
||||||
|
buildPaymentTermDeleter,
|
||||||
|
buildPaymentTermFinder,
|
||||||
|
buildPaymentTermInputMappers,
|
||||||
|
buildPaymentTermSnapshotBuilders,
|
||||||
|
buildPaymentTermStatusChanger,
|
||||||
|
buildPaymentTermUpdater,
|
||||||
|
} from "../../../application/payment-terms";
|
||||||
|
import type { IPaymentTermRepository } from "../../../application/payment-terms/repositories";
|
||||||
|
import { PaymentTermFinder } from "../../../application/payment-terms/services";
|
||||||
|
|
||||||
|
import { buildPaymentTermPersistenceMappers } from "./payment-term-persistence-mappers.di";
|
||||||
|
import { buildPaymentTermRepository } from "./payment-term-repositories.di";
|
||||||
|
|
||||||
|
export type PaymentTermsInternalDeps = {
|
||||||
|
repository: IPaymentTermRepository;
|
||||||
|
useCases: {
|
||||||
|
listPaymentTerms: () => ListPaymentTermsUseCase;
|
||||||
|
getPaymentTermById: () => GetPaymentTermByIdUseCase;
|
||||||
|
createPaymentTerm: () => CreatePaymentTermUseCase;
|
||||||
|
updatePaymentTermById: () => UpdatePaymentTermByIdUseCase;
|
||||||
|
deletePaymentTermById: () => DeletePaymentTermByIdUseCase;
|
||||||
|
disablePaymentTermById: () => DisablePaymentTermByIdUseCase;
|
||||||
|
enablePaymentTermById: () => EnablePaymentTermByIdUseCase;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildPaymentTermsDependencies = (params: ModuleParams): PaymentTermsInternalDeps => {
|
||||||
|
const { database } = params;
|
||||||
|
|
||||||
|
const transactionManager = buildTransactionManager(database as Sequelize);
|
||||||
|
const persistenceMappers = buildPaymentTermPersistenceMappers();
|
||||||
|
|
||||||
|
const repository = buildPaymentTermRepository({ database, mappers: persistenceMappers });
|
||||||
|
|
||||||
|
const inputMappers = buildPaymentTermInputMappers();
|
||||||
|
|
||||||
|
const finder = buildPaymentTermFinder({ repository });
|
||||||
|
const creator = buildPaymentTermCreator({ repository });
|
||||||
|
const updater = buildPaymentTermUpdater({ repository });
|
||||||
|
const deleter = buildPaymentTermDeleter({ repository });
|
||||||
|
const statusChanger = buildPaymentTermStatusChanger({ repository });
|
||||||
|
|
||||||
|
const snapshotBuilders = buildPaymentTermSnapshotBuilders();
|
||||||
|
return {
|
||||||
|
repository,
|
||||||
|
useCases: {
|
||||||
|
listPaymentTerms: () =>
|
||||||
|
new ListPaymentTermsUseCase(finder, snapshotBuilders.summary, transactionManager),
|
||||||
|
|
||||||
|
getPaymentTermById: () =>
|
||||||
|
new GetPaymentTermByIdUseCase(finder, snapshotBuilders.full, transactionManager),
|
||||||
|
|
||||||
|
createPaymentTerm: () =>
|
||||||
|
new CreatePaymentTermUseCase({
|
||||||
|
dtoMapper: inputMappers.createInputMapper,
|
||||||
|
creator,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
|
updatePaymentTermById: () =>
|
||||||
|
new UpdatePaymentTermByIdUseCase({
|
||||||
|
updater,
|
||||||
|
finder,
|
||||||
|
dtoMapper: inputMappers.updateInputMapper,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
|
deletePaymentTermById: () =>
|
||||||
|
new DeletePaymentTermByIdUseCase({
|
||||||
|
deleter,
|
||||||
|
finder,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
|
disablePaymentTermById: () =>
|
||||||
|
new DisablePaymentTermByIdUseCase({
|
||||||
|
finder,
|
||||||
|
changer: statusChanger,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
|
enablePaymentTermById: () =>
|
||||||
|
new EnablePaymentTermByIdUseCase({
|
||||||
|
finder,
|
||||||
|
changer: statusChanger,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildPaymentTermsPublicServices = (
|
||||||
|
_params: SetupParams,
|
||||||
|
deps: PaymentTermsInternalDeps
|
||||||
|
): { finder: PaymentTermFinder } => {
|
||||||
|
return {
|
||||||
|
finder: new PaymentTermFinder(deps.repository),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import type { CreatePaymentTermRequestDTO } from "@erp/catalogs/common";
|
||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { CreatePaymentTermUseCase } from "../../../../application/payment-terms";
|
||||||
|
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
|
||||||
|
|
||||||
|
export class CreatePaymentTermController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: CreatePaymentTermUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentTermsApiErrorMapper;
|
||||||
|
|
||||||
|
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 CreatePaymentTermRequestDTO;
|
||||||
|
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 { DeletePaymentTermByIdUseCase } from "../../../../application/payment-terms";
|
||||||
|
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
|
||||||
|
|
||||||
|
export class DeletePaymentTermByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: DeletePaymentTermByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentTermsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_term_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ payment_term_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 { DisablePaymentTermByIdUseCase } from "../../../../application/payment-terms";
|
||||||
|
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
|
||||||
|
|
||||||
|
export class DisablePaymentTermByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: DisablePaymentTermByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentTermsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_term_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ payment_term_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 { EnablePaymentTermByIdUseCase } from "../../../../application/payment-terms";
|
||||||
|
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
|
||||||
|
|
||||||
|
export class EnablePaymentTermByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: EnablePaymentTermByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentTermsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_term_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ payment_term_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 { GetPaymentTermByIdUseCase } from "../../../../application/payment-terms";
|
||||||
|
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
|
||||||
|
|
||||||
|
export class GetPaymentTermByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: GetPaymentTermByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentTermsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_term_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ payment_term_id, companyId });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data: unknown) => this.ok(data),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./create-payment-term.controller";
|
||||||
|
export * from "./delete-payment-term-by-id.controller";
|
||||||
|
export * from "./disable-payment-term-by-id.controller";
|
||||||
|
export * from "./enable-payment-term-by-id.controller";
|
||||||
|
export * from "./get-payment-term-by-id.controller";
|
||||||
|
export * from "./list-payment-terms.controller";
|
||||||
|
export * from "./update-payment-term-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 { ListPaymentTermsUseCase } from "../../../../application/payment-terms";
|
||||||
|
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
|
||||||
|
|
||||||
|
export class ListPaymentTermsController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: ListPaymentTermsUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentTermsApiErrorMapper;
|
||||||
|
|
||||||
|
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, "name", "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 { UpdatePaymentTermByIdRequestDTO } from "@erp/catalogs/common";
|
||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { UpdatePaymentTermByIdUseCase } from "../../../../application/payment-terms";
|
||||||
|
import { paymentTermsApiErrorMapper } from "../payment-terms-api-error-mapper";
|
||||||
|
|
||||||
|
export class UpdatePaymentTermByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: UpdatePaymentTermByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentTermsApiErrorMapper;
|
||||||
|
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_term_id } = this.req.params;
|
||||||
|
if (!payment_term_id) {
|
||||||
|
return this.invalidInputError("Payment term ID missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
const dto = this.req.body as UpdatePaymentTermByIdRequestDTO;
|
||||||
|
const result = await this.useCase.execute({ payment_term_id, companyId, dto });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data: unknown) => this.ok(data),
|
||||||
|
(err: Error) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./payment-terms.routes";
|
||||||
@ -0,0 +1,182 @@
|
|||||||
|
import {
|
||||||
|
ApiErrorMapper,
|
||||||
|
ConflictApiError,
|
||||||
|
type ErrorToApiRule,
|
||||||
|
NotFoundApiError,
|
||||||
|
ValidationApiError,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type InvalidPaymentTermDescriptionError,
|
||||||
|
type InvalidPaymentTermDueDaysError,
|
||||||
|
type InvalidPaymentTermDueRulesError,
|
||||||
|
type InvalidPaymentTermIdError,
|
||||||
|
type InvalidPaymentTermNameError,
|
||||||
|
type InvalidPaymentTermPercentageError,
|
||||||
|
type PaymentTermCannotBeDeletedError,
|
||||||
|
type PaymentTermCannotBeDisabledError,
|
||||||
|
type PaymentTermCannotBeEnabledError,
|
||||||
|
type PaymentTermCannotBeUpdatedError,
|
||||||
|
type PaymentTermDueDaysDuplicatedError,
|
||||||
|
type PaymentTermDueRuleMismatch,
|
||||||
|
type PaymentTermNotFoundError,
|
||||||
|
type PaymentTermPercentageSumMismatchError,
|
||||||
|
isInvalidPaymentTermDescriptionError,
|
||||||
|
isInvalidPaymentTermDueDaysError,
|
||||||
|
isInvalidPaymentTermDueRulesError,
|
||||||
|
isInvalidPaymentTermIdError,
|
||||||
|
isInvalidPaymentTermNameError,
|
||||||
|
isInvalidPaymentTermPercentageError,
|
||||||
|
isPaymentTermCannotBeDeletedError,
|
||||||
|
isPaymentTermCannotBeDisabledError,
|
||||||
|
isPaymentTermCannotBeEnabledError,
|
||||||
|
isPaymentTermCannotBeUpdatedError,
|
||||||
|
isPaymentTermDueDaysDuplicatedError,
|
||||||
|
isPaymentTermDueRuleMismatch,
|
||||||
|
isPaymentTermNotFoundError,
|
||||||
|
isPaymentTermPercentageSumMismatchError,
|
||||||
|
} from "../../../domain/payment-terms";
|
||||||
|
|
||||||
|
const invalidPaymentTermIdRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidPaymentTermIdError,
|
||||||
|
build: (error) =>
|
||||||
|
new ConflictApiError(
|
||||||
|
(error as InvalidPaymentTermIdError).message ||
|
||||||
|
"Payment term with the provided id already exists."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidPaymentTermNameRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidPaymentTermNameError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidPaymentTermNameError).message || "Payment term name is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidPaymentTermDescriptionRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidPaymentTermDescriptionError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidPaymentTermDescriptionError).message ||
|
||||||
|
"Payment term description is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidPaymentTermDueDaysRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidPaymentTermDueDaysError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidPaymentTermDueDaysError).message || "Payment term due days are invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidPaymentTermPercentageRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidPaymentTermPercentageError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidPaymentTermPercentageError).message || "Payment term percentage is invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidPaymentTermDueRulesRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isInvalidPaymentTermDueRulesError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as InvalidPaymentTermDueRulesError).message || "Payment term due rules are invalid."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentTermDueDaysDuplicatedRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentTermDueDaysDuplicatedError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentTermDueDaysDuplicatedError).message ||
|
||||||
|
"Payment term due days are duplicated."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentTermPercentageSumMismatchRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentTermPercentageSumMismatchError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentTermPercentageSumMismatchError).message ||
|
||||||
|
"Payment term due rule percentages must sum 100.00."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentTermNotFoundRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentTermNotFoundError,
|
||||||
|
build: (error) =>
|
||||||
|
new NotFoundApiError((error as PaymentTermNotFoundError).message || "Payment term not found."),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentTermCannotBeDeletedRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentTermCannotBeDeletedError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentTermCannotBeDeletedError).message || "Payment term cannot be deleted."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentTermCannotBeDisabledRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentTermCannotBeDisabledError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentTermCannotBeDisabledError).message || "Payment term cannot be disabled."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentTermCannotBeEnabledRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentTermCannotBeEnabledError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentTermCannotBeEnabledError).message || "Payment term cannot be enabled."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentTermCannotBeUpdatedRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentTermCannotBeUpdatedError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentTermCannotBeUpdatedError).message || "Payment term cannot be updated."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentTermDueRuleMismatchRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentTermDueRuleMismatch,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentTermDueRuleMismatch).message || "Payment term due rule is mismatched."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const paymentTermsApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
||||||
|
.register(invalidPaymentTermIdRule)
|
||||||
|
.register(invalidPaymentTermNameRule)
|
||||||
|
.register(invalidPaymentTermDescriptionRule)
|
||||||
|
.register(invalidPaymentTermDueDaysRule)
|
||||||
|
.register(invalidPaymentTermPercentageRule)
|
||||||
|
.register(invalidPaymentTermDueRulesRule)
|
||||||
|
.register(paymentTermDueDaysDuplicatedRule)
|
||||||
|
.register(paymentTermPercentageSumMismatchRule)
|
||||||
|
.register(paymentTermNotFoundRule)
|
||||||
|
.register(paymentTermCannotBeDeletedRule)
|
||||||
|
.register(paymentTermCannotBeDisabledRule)
|
||||||
|
.register(paymentTermCannotBeEnabledRule)
|
||||||
|
.register(paymentTermCannotBeUpdatedRule)
|
||||||
|
.register(paymentTermDueRuleMismatchRule);
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
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 {
|
||||||
|
CreatePaymentTermRequestSchema,
|
||||||
|
DeletePaymentTermByIdRequestSchema,
|
||||||
|
GetPaymentTermByIdRequestSchema,
|
||||||
|
ListPaymentTermsRequestSchema,
|
||||||
|
UpdatePaymentTermByIdParamsRequestSchema,
|
||||||
|
UpdatePaymentTermByIdRequestSchema,
|
||||||
|
} from "../../../../common";
|
||||||
|
import type { CatalogsInternalDeps } from "../../di";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreatePaymentTermController,
|
||||||
|
DeletePaymentTermByIdController,
|
||||||
|
DisablePaymentTermByIdController,
|
||||||
|
EnablePaymentTermByIdController,
|
||||||
|
GetPaymentTermByIdController,
|
||||||
|
ListPaymentTermsController,
|
||||||
|
UpdatePaymentTermByIdController,
|
||||||
|
} from "./controllers";
|
||||||
|
|
||||||
|
export const paymentTermsRouter = (params: StartParams) => {
|
||||||
|
const { app, config, getInternal } = params;
|
||||||
|
const deps = getInternal<CatalogsInternalDeps>("catalogs").paymentTerms;
|
||||||
|
|
||||||
|
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(ListPaymentTermsRequestSchema, "query"), (req, res, next) => {
|
||||||
|
const controller = new ListPaymentTermsController(deps.useCases.listPaymentTerms());
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/", validateRequest(CreatePaymentTermRequestSchema, "body"), (req, res, next) => {
|
||||||
|
const controller = new CreatePaymentTermController(deps.useCases.createPaymentTerm());
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:payment_term_id",
|
||||||
|
validateRequest(GetPaymentTermByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new GetPaymentTermByIdController(deps.useCases.getPaymentTermById());
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:payment_term_id",
|
||||||
|
validateRequest(DeletePaymentTermByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new DeletePaymentTermByIdController(deps.useCases.deletePaymentTermById());
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put(
|
||||||
|
"/:payment_term_id",
|
||||||
|
validateRequest(UpdatePaymentTermByIdParamsRequestSchema, "params"),
|
||||||
|
validateRequest(UpdatePaymentTermByIdRequestSchema, "body"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new UpdatePaymentTermByIdController(deps.useCases.updatePaymentTermById());
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:payment_term_id/disable",
|
||||||
|
validateRequest(GetPaymentTermByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new DisablePaymentTermByIdController(
|
||||||
|
deps.useCases.disablePaymentTermById()
|
||||||
|
);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:payment_term_id/enable",
|
||||||
|
validateRequest(GetPaymentTermByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new EnablePaymentTermByIdController(deps.useCases.enablePaymentTermById());
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(`${config.server.apiBasePath}/catalogs/payment-terms`, router);
|
||||||
|
};
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./express";
|
||||||
|
export * from "./persistence";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize";
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
export * from "./mappers";
|
||||||
|
export * from "./models";
|
||||||
|
export * from "./repositories";
|
||||||
|
|
||||||
|
import paymentTermModelInit from "./models/sequelize-payment-term.model";
|
||||||
|
import paymentTermDueRuleModelInit from "./models/sequelize-payment-term-due-rule.model";
|
||||||
|
|
||||||
|
export const paymentTermModels = [paymentTermModelInit, paymentTermDueRuleModelInit];
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./sequelize-payment-term-domain.mapper";
|
||||||
|
export * from "./sequelize-payment-term-due-rule-domain.mapper";
|
||||||
|
export * from "./sequelize-payment-term-summary.mapper";
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
||||||
|
import {
|
||||||
|
Name,
|
||||||
|
TextValue,
|
||||||
|
UniqueID,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
maybeFromNullableResult,
|
||||||
|
maybeToNullable,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { PaymentTerm, PaymentTermDueRules } from "../../../../../domain";
|
||||||
|
import type { PaymentTermCreationAttributes, PaymentTermModel } from "../models";
|
||||||
|
|
||||||
|
import { SequelizePaymentTermDueRuleDomainMapper } from "./sequelize-payment-term-due-rule-domain.mapper";
|
||||||
|
|
||||||
|
export class SequelizePaymentTermDomainMapper extends SequelizeDomainMapper<
|
||||||
|
PaymentTermModel,
|
||||||
|
PaymentTermCreationAttributes,
|
||||||
|
PaymentTerm
|
||||||
|
> {
|
||||||
|
private _dueRulesMapper: SequelizePaymentTermDueRuleDomainMapper;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._dueRulesMapper = new SequelizePaymentTermDueRuleDomainMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToDomain(raw: PaymentTermModel, params?: MapperParamsType): Result<PaymentTerm, Error> {
|
||||||
|
try {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
const idResult = UniqueID.create(raw.id, true);
|
||||||
|
if (idResult.isFailure) {
|
||||||
|
return Result.fail(idResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
||||||
|
const name = extractOrPushError(Name.create(raw.name), "name", errors);
|
||||||
|
const description = extractOrPushError(
|
||||||
|
maybeFromNullableResult(raw.description, (value) => TextValue.create(value)),
|
||||||
|
"description",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const dueCollectionResults = this._dueRulesMapper.mapToDomainCollection(
|
||||||
|
raw.due_rules,
|
||||||
|
raw.due_rules.length,
|
||||||
|
{
|
||||||
|
errors,
|
||||||
|
parent: raw,
|
||||||
|
...params,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dueCollectionResults.isFailure) {
|
||||||
|
errors.push({ path: "due_rules", message: dueCollectionResults.error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("PaymentTerm mapping failed [mapToDTO]", errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6) Construcción del agregado (Dominio)
|
||||||
|
const dueRulesResult = PaymentTermDueRules.create({
|
||||||
|
rules: dueCollectionResults.data.getAll(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const paymentTerm = PaymentTerm.rehydrate(
|
||||||
|
{
|
||||||
|
companyId: companyId!,
|
||||||
|
name: name!,
|
||||||
|
description: description!,
|
||||||
|
isActive: raw.is_active,
|
||||||
|
isSystem: raw.is_system,
|
||||||
|
},
|
||||||
|
dueRulesResult,
|
||||||
|
idResult.data
|
||||||
|
);
|
||||||
|
|
||||||
|
return Result.ok(paymentTerm);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToPersistence(
|
||||||
|
source: PaymentTerm,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<PaymentTermCreationAttributes, Error> {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
// 1) Items
|
||||||
|
const dueRulesResult = this._dueRulesMapper.mapToPersistenceArray(source.dueRules, {
|
||||||
|
errors,
|
||||||
|
parent: source,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dueRulesResult.isFailure) {
|
||||||
|
errors.push({
|
||||||
|
path: "dueRules",
|
||||||
|
message: dueRulesResult.error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("Payment term mapping to persistence failed", errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dueRules = dueRulesResult.data;
|
||||||
|
|
||||||
|
return Result.ok<PaymentTermCreationAttributes>({
|
||||||
|
id: source.id.toPrimitive(),
|
||||||
|
company_id: source.companyId.toPrimitive(),
|
||||||
|
name: source.name.toPrimitive(),
|
||||||
|
description: maybeToNullable(source.description, (value: TextValue) => value.toPrimitive()),
|
||||||
|
is_active: source.isActive,
|
||||||
|
is_system: source.isSystem,
|
||||||
|
due_rules: dueRules,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
||||||
|
import {
|
||||||
|
UniqueID,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type IPaymentTermCreateProps,
|
||||||
|
type PaymentTerm,
|
||||||
|
PaymentTermDueDays,
|
||||||
|
PaymentTermDueRule,
|
||||||
|
PaymentTermPercentage,
|
||||||
|
} from "../../../../../domain/payment-terms";
|
||||||
|
import type { PaymentTermDueRuleCreationAttributes, PaymentTermDueRuleModel } from "../models";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapper para payment_term_due_rules
|
||||||
|
*
|
||||||
|
* Domina estructuras:
|
||||||
|
* {
|
||||||
|
* dueDays: PaymentTermDueDays
|
||||||
|
* percentage: PaymentTermPercentage
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Cada fila = una regla de vencimiento (cuándo pagar qué porcentaje).
|
||||||
|
*/
|
||||||
|
export class SequelizePaymentTermDueRuleDomainMapper extends SequelizeDomainMapper<
|
||||||
|
PaymentTermDueRuleModel,
|
||||||
|
PaymentTermDueRuleCreationAttributes,
|
||||||
|
PaymentTermDueRule
|
||||||
|
> {
|
||||||
|
public mapToDomain(
|
||||||
|
source: PaymentTermDueRuleModel,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<PaymentTermDueRule, Error> {
|
||||||
|
const { errors, index } = params as {
|
||||||
|
index: number;
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
|
parent: Partial<IPaymentTermCreateProps>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dueId = extractOrPushError(
|
||||||
|
UniqueID.create(source.due_id),
|
||||||
|
`due_rules[${index}].due_id`,
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const dueDaysResult = extractOrPushError(
|
||||||
|
PaymentTermDueDays.create(source.due_days),
|
||||||
|
`due_rules${index}.due_days`,
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const percentageResult = extractOrPushError(
|
||||||
|
PaymentTermPercentage.create({
|
||||||
|
value: Number(source.percentage_value ?? 0),
|
||||||
|
scale: Number(source.percentage_scale ?? 2),
|
||||||
|
}),
|
||||||
|
`due_rules${index}.percentage`,
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
// Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("Payment term due rule mapping failed [mapToDomain]", errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dueRuleResult = PaymentTermDueRule.rehydrate(
|
||||||
|
{
|
||||||
|
dueDays: dueDaysResult!,
|
||||||
|
percentage: percentageResult!,
|
||||||
|
},
|
||||||
|
dueId!
|
||||||
|
);
|
||||||
|
|
||||||
|
return Result.ok(dueRuleResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToPersistence(
|
||||||
|
source: PaymentTermDueRule,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<PaymentTermDueRuleCreationAttributes, Error> {
|
||||||
|
const { errors, index, parent } = params as {
|
||||||
|
index: number;
|
||||||
|
parent: PaymentTerm;
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const dto: PaymentTermDueRuleCreationAttributes = {
|
||||||
|
due_id: source.id.toPrimitive(),
|
||||||
|
payment_term_id: parent.id.toPrimitive(),
|
||||||
|
due_days: source.dueDays.toPrimitive(),
|
||||||
|
percentage_value: source.percentage.value,
|
||||||
|
percentage_scale: source.percentage.scale,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
import { type MapperParamsType, SequelizeQueryMapper } from "@erp/core/api";
|
||||||
|
import {
|
||||||
|
Name,
|
||||||
|
UniqueID,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentTermSummary } from "../../../../../application/payment-terms";
|
||||||
|
import type { PaymentTermModel } from "../models";
|
||||||
|
|
||||||
|
import { SequelizePaymentTermDueRuleDomainMapper } from "./sequelize-payment-term-due-rule-domain.mapper";
|
||||||
|
|
||||||
|
export class SequelizePaymentTermSummaryMapper extends SequelizeQueryMapper<
|
||||||
|
PaymentTermModel,
|
||||||
|
PaymentTermSummary
|
||||||
|
> {
|
||||||
|
private _dueRulesMapper: SequelizePaymentTermDueRuleDomainMapper;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._dueRulesMapper = new SequelizePaymentTermDueRuleDomainMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToReadModel(
|
||||||
|
raw: PaymentTermModel,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<PaymentTermSummary, Error> {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
||||||
|
const id = extractOrPushError(UniqueID.create(raw.id), "id", errors);
|
||||||
|
const name = extractOrPushError(Name.create(raw.name), "name", errors);
|
||||||
|
|
||||||
|
const dueCollectionResults = this._dueRulesMapper.mapToDomainCollection(
|
||||||
|
raw.due_rules,
|
||||||
|
raw.due_rules.length,
|
||||||
|
{
|
||||||
|
errors,
|
||||||
|
parent: raw,
|
||||||
|
...params,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dueCollectionResults.isFailure) {
|
||||||
|
errors.push({ path: "due_rules", message: dueCollectionResults.error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("PaymentTerm mapping failed [mapToDTO]", errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dueRules = dueCollectionResults.data.getAll();
|
||||||
|
|
||||||
|
return Result.ok<PaymentTermSummary>({
|
||||||
|
id: id!,
|
||||||
|
companyId: companyId!,
|
||||||
|
name: name!,
|
||||||
|
isActive: raw.is_active,
|
||||||
|
isSystem: raw.is_system,
|
||||||
|
dueRules,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./sequelize-payment-term.model";
|
||||||
|
export * from "./sequelize-payment-term-due-rule.model";
|
||||||
|
export * from "./sequelize-payment-term-due-rule.model";
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
import {
|
||||||
|
DataTypes,
|
||||||
|
type InferAttributes,
|
||||||
|
type InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
type NonAttribute,
|
||||||
|
type Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
|
||||||
|
import type { PaymentTermModel } from "./sequelize-payment-term.model";
|
||||||
|
|
||||||
|
export type PaymentTermDueRuleCreationAttributes = InferCreationAttributes<
|
||||||
|
PaymentTermDueRuleModel,
|
||||||
|
{ omit: "payment_term" }
|
||||||
|
>;
|
||||||
|
|
||||||
|
export class PaymentTermDueRuleModel extends Model<
|
||||||
|
InferAttributes<PaymentTermDueRuleModel>,
|
||||||
|
InferCreationAttributes<PaymentTermDueRuleModel, { omit: "payment_term" }>
|
||||||
|
> {
|
||||||
|
declare due_id: string;
|
||||||
|
declare payment_term_id: string;
|
||||||
|
declare due_days: number;
|
||||||
|
declare percentage_value: number;
|
||||||
|
declare percentage_scale: number;
|
||||||
|
|
||||||
|
// Relaciones
|
||||||
|
declare payment_term: NonAttribute<PaymentTermModel>;
|
||||||
|
|
||||||
|
static associate(database: Sequelize) {
|
||||||
|
const models = database.models;
|
||||||
|
const requiredModels = ["PaymentTermDueRuleModel", "PaymentTermModel"];
|
||||||
|
|
||||||
|
// Comprobamos que los modelos existan
|
||||||
|
for (const name of requiredModels) {
|
||||||
|
if (!models[name]) {
|
||||||
|
throw new Error(`[PaymentTermDueRuleModel.associate] Missing model: ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { PaymentTermDueRuleModel, PaymentTermModel } = models;
|
||||||
|
|
||||||
|
PaymentTermDueRuleModel.belongsTo(PaymentTermModel, {
|
||||||
|
as: "payment_term",
|
||||||
|
foreignKey: "payment_term_id",
|
||||||
|
targetKey: "id",
|
||||||
|
constraints: true,
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static hooks(_database: Sequelize) {
|
||||||
|
// No hooks required for due rule persistence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (database: Sequelize) => {
|
||||||
|
PaymentTermDueRuleModel.init(
|
||||||
|
{
|
||||||
|
due_id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
},
|
||||||
|
payment_term_id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
due_days: {
|
||||||
|
type: DataTypes.SMALLINT.UNSIGNED,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
percentage_value: {
|
||||||
|
type: DataTypes.SMALLINT.UNSIGNED,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
percentage_scale: {
|
||||||
|
type: DataTypes.SMALLINT.UNSIGNED,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize: database,
|
||||||
|
modelName: "PaymentTermDueRuleModel",
|
||||||
|
tableName: "payment_term_due_rules",
|
||||||
|
|
||||||
|
underscored: true,
|
||||||
|
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
name: "idx_payment_term_due_rules_payment_term",
|
||||||
|
fields: ["payment_term_id"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
defaultScope: {},
|
||||||
|
scopes: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return PaymentTermDueRuleModel;
|
||||||
|
};
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
import {
|
||||||
|
type CreationOptional,
|
||||||
|
DataTypes,
|
||||||
|
type InferAttributes,
|
||||||
|
type InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
type NonAttribute,
|
||||||
|
type Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
PaymentTermDueRuleCreationAttributes,
|
||||||
|
PaymentTermDueRuleModel,
|
||||||
|
} from "./sequelize-payment-term-due-rule.model";
|
||||||
|
|
||||||
|
export type PaymentTermCreationAttributes = InferCreationAttributes<
|
||||||
|
PaymentTermModel,
|
||||||
|
{ omit: "due_rules" }
|
||||||
|
> & {
|
||||||
|
due_rules?: PaymentTermDueRuleCreationAttributes[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export class PaymentTermModel extends Model<
|
||||||
|
InferAttributes<PaymentTermModel>,
|
||||||
|
InferCreationAttributes<PaymentTermModel, { omit: "due_rules" }>
|
||||||
|
> {
|
||||||
|
declare id: string;
|
||||||
|
declare company_id: string;
|
||||||
|
declare name: string;
|
||||||
|
declare description: CreationOptional<string | null>;
|
||||||
|
declare is_active: boolean;
|
||||||
|
declare is_system: boolean;
|
||||||
|
|
||||||
|
// Relaciones
|
||||||
|
declare due_rules: NonAttribute<PaymentTermDueRuleModel[]>;
|
||||||
|
|
||||||
|
static associate(database: Sequelize) {
|
||||||
|
const models = database.models;
|
||||||
|
const requiredModels = ["PaymentTermDueRuleModel", "PaymentTermModel"];
|
||||||
|
|
||||||
|
// Comprobamos que los modelos existan
|
||||||
|
for (const name of requiredModels) {
|
||||||
|
if (!models[name]) {
|
||||||
|
throw new Error(`[PaymentTermModel.associate] Missing model: ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Los modelos existen
|
||||||
|
const { PaymentTermDueRuleModel } = models;
|
||||||
|
|
||||||
|
PaymentTermModel.hasMany(PaymentTermDueRuleModel, {
|
||||||
|
as: "due_rules",
|
||||||
|
foreignKey: "payment_term_id",
|
||||||
|
sourceKey: "id",
|
||||||
|
constraints: true,
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
onUpdate: "CASCADE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static hooks(_database: Sequelize) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (database: Sequelize) => {
|
||||||
|
PaymentTermModel.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
company_id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
is_active: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
is_system: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize: database,
|
||||||
|
modelName: "PaymentTermModel",
|
||||||
|
tableName: "payment_terms",
|
||||||
|
|
||||||
|
underscored: true,
|
||||||
|
paranoid: true,
|
||||||
|
timestamps: true,
|
||||||
|
|
||||||
|
createdAt: "created_at",
|
||||||
|
updatedAt: "updated_at",
|
||||||
|
deletedAt: "deleted_at",
|
||||||
|
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
name: "idx_payment_terms_company",
|
||||||
|
fields: ["company_id", "deleted_at", "name"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
whereMergeStrategy: "and",
|
||||||
|
defaultScope: {},
|
||||||
|
scopes: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return PaymentTermModel;
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize-payment-term.repository";
|
||||||
@ -0,0 +1,171 @@
|
|||||||
|
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 { PaymentTermSummary } from "../../../../../application/payment-terms/models/payment-term-summary.model";
|
||||||
|
import type { IPaymentTermRepository } from "../../../../../application/payment-terms/repositories/payment-term-repository.interface";
|
||||||
|
import type { PaymentTerm } from "../../../../../domain";
|
||||||
|
import type {
|
||||||
|
SequelizePaymentTermDomainMapper,
|
||||||
|
SequelizePaymentTermSummaryMapper,
|
||||||
|
} from "../mappers";
|
||||||
|
import { PaymentTermModel } from "../models";
|
||||||
|
|
||||||
|
export class SequelizePaymentTermRepository
|
||||||
|
extends SequelizeRepository<PaymentTerm>
|
||||||
|
implements IPaymentTermRepository
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly domainMapper: SequelizePaymentTermDomainMapper,
|
||||||
|
private readonly summaryMapper: SequelizePaymentTermSummaryMapper,
|
||||||
|
database: Sequelize
|
||||||
|
) {
|
||||||
|
super({ database });
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(paymentTerm: PaymentTerm, transaction?: Transaction): Promise<Result<void, Error>> {
|
||||||
|
try {
|
||||||
|
const dtoResult = this.domainMapper.mapToPersistence(paymentTerm);
|
||||||
|
if (dtoResult.isFailure) {
|
||||||
|
return Result.fail(dtoResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await PaymentTermModel.create(dtoResult.data, { include: [{ all: true }], transaction });
|
||||||
|
return Result.ok();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(paymentTerm: PaymentTerm, transaction?: Transaction): Promise<Result<void, Error>> {
|
||||||
|
try {
|
||||||
|
const dtoResult = this.domainMapper.mapToPersistence(paymentTerm);
|
||||||
|
if (dtoResult.isFailure) {
|
||||||
|
return Result.fail(dtoResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, ...payload } = dtoResult.data;
|
||||||
|
const [affected] = await PaymentTermModel.update(payload, {
|
||||||
|
where: { id },
|
||||||
|
transaction,
|
||||||
|
individualHooks: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (affected === 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new InfrastructureRepositoryError("Concurrency conflict or payment term not found")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async existsByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const count = await PaymentTermModel.count({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
return Result.ok(Boolean(count > 0));
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<PaymentTerm, Error>> {
|
||||||
|
try {
|
||||||
|
const row = await PaymentTermModel.findOne({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
include: [{ all: true }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return Result.fail(new EntityNotFoundError("PaymentTerm", "id", id.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.domainMapper.mapToDomain(row);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByCriteriaInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
criteria: Criteria,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<Collection<PaymentTermSummary>, Error>> {
|
||||||
|
try {
|
||||||
|
const criteriaConverter = new CriteriaToSequelizeConverter();
|
||||||
|
const query = criteriaConverter.convert(criteria, {
|
||||||
|
searchableFields: [],
|
||||||
|
sortableFields: ["name"],
|
||||||
|
enableFullText: true,
|
||||||
|
database: this.database,
|
||||||
|
strictMode: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
query.where = {
|
||||||
|
...query.where,
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
deleted_at: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [rows, count] = await Promise.all([
|
||||||
|
PaymentTermModel.findAll({
|
||||||
|
...query,
|
||||||
|
transaction,
|
||||||
|
include: [{ all: true }],
|
||||||
|
}),
|
||||||
|
PaymentTermModel.count({
|
||||||
|
where: query.where,
|
||||||
|
distinct: true,
|
||||||
|
transaction,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return this.summaryMapper.mapToReadModelCollection(rows, count);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const deleted = await PaymentTermModel.destroy({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deleted === 0) {
|
||||||
|
return Result.fail(new EntityNotFoundError("PaymentTerm", "id", id.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(true);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
|
export * from "./payment-terms";
|
||||||
|
|||||||
3
modules/catalogs/src/common/dto/payment-terms/index.ts
Normal file
3
modules/catalogs/src/common/dto/payment-terms/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./request";
|
||||||
|
export * from "./response";
|
||||||
|
export * from "./shared";
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { PaymentTermDueRuleSchema } from "../shared/payment-term-due-rule.dto";
|
||||||
|
|
||||||
|
export const CreatePaymentTermRequestSchema = z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
is_active: z.boolean(),
|
||||||
|
due_rules: z.array(PaymentTermDueRuleSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CreatePaymentTermRequestDTO = z.infer<typeof CreatePaymentTermRequestSchema>;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const DeletePaymentTermByIdRequestSchema = z.object({
|
||||||
|
payment_term_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DeletePaymentTermByIdRequestDTO = z.infer<typeof DeletePaymentTermByIdRequestSchema>;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const GetPaymentTermByIdRequestSchema = z.object({
|
||||||
|
payment_term_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GetPaymentTermByIdRequestDTO = z.infer<typeof GetPaymentTermByIdRequestSchema>;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./create-payment-term.request.dto";
|
||||||
|
export * from "./delete-payment-term-by-id.request.dto";
|
||||||
|
export * from "./get-payment-term-by-id.request.dto";
|
||||||
|
export * from "./list-payment-terms.request.dto";
|
||||||
|
export * from "./update-payment-term-by-id.request.dto";
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user