.
This commit is contained in:
parent
cea1ab2766
commit
dc6382dd41
@ -16,8 +16,7 @@
|
|||||||
"./common": "./src/common/index.ts",
|
"./common": "./src/common/index.ts",
|
||||||
"./api": "./src/api/index.ts",
|
"./api": "./src/api/index.ts",
|
||||||
"./client": "./src/web/manifest.ts",
|
"./client": "./src/web/manifest.ts",
|
||||||
"./client/payment-methods": "./src/web/payment-methods/index.ts",
|
"./client/*": "./src/web/*/index.ts"
|
||||||
"./client/payment-terms": "./src/web/payment-terms/index.ts"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
export * from "./payment-terms";
|
export * from "./payment-terms";
|
||||||
|
export * from "./services";
|
||||||
|
export * from "./tax-definitions";
|
||||||
export * from "./tax-regimes";
|
export * from "./tax-regimes";
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
import type { IPaymentMethodPublicServices } from "../payment-methods";
|
||||||
|
import type { IPaymentTermPublicServices } from "../payment-terms";
|
||||||
|
import type { ITaxRegimePublicServices } from "../tax-regimes";
|
||||||
|
|
||||||
|
export type { IPaymentMethodPublicServices } from "../payment-methods";
|
||||||
|
export type { IPaymentTermPublicServices } from "../payment-terms";
|
||||||
|
export type { ITaxRegimePublicServices } from "../tax-regimes";
|
||||||
|
|
||||||
|
export interface ICatalogServicesContext {
|
||||||
|
transaction: unknown;
|
||||||
|
companyId: UniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICatalogPublicServices {
|
||||||
|
paymentMethods: IPaymentMethodPublicServices;
|
||||||
|
paymentTerms: IPaymentTermPublicServices;
|
||||||
|
taxRegimes: ITaxRegimePublicServices;
|
||||||
|
}
|
||||||
1
modules/catalogs/src/api/application/services/index.ts
Normal file
1
modules/catalogs/src/api/application/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './catalog-public-services.interface';
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
export * from "./mappers";
|
||||||
|
export * from "./models";
|
||||||
|
export * from "./repositories";
|
||||||
|
export * from "./services";
|
||||||
|
export * from "./snapshot-builders";
|
||||||
|
export * from "./use-cases";
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
import type { CreateTaxDefinitionRequestDTO } from "@erp/catalogs/common";
|
||||||
|
import {
|
||||||
|
DomainError,
|
||||||
|
TextValue,
|
||||||
|
UniqueID,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionCreateProps, TaxDefinitionCode } from "../../../domain";
|
||||||
|
|
||||||
|
export interface ICreateTaxDefinitionInputMapper {
|
||||||
|
map(
|
||||||
|
dto: CreateTaxDefinitionRequestDTO,
|
||||||
|
params: { companyId: UniqueID }
|
||||||
|
): Result<{ id: UniqueID; props: ITaxDefinitionCreateProps }, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateTaxDefinitionInputMapper implements ICreateTaxDefinitionInputMapper {
|
||||||
|
public map(
|
||||||
|
dto: CreateTaxDefinitionRequestDTO,
|
||||||
|
params: { companyId: UniqueID }
|
||||||
|
): Result<{ id: UniqueID; props: ITaxDefinitionCreateProps }, Error> {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const taxDefinitionId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||||
|
|
||||||
|
const code = extractOrPushError(TaxDefinitionCode.create(dto.code), "code", errors);
|
||||||
|
|
||||||
|
const name = extractOrPushError(TextValue.create(dto.name), "name", errors);
|
||||||
|
|
||||||
|
const description = extractOrPushError(
|
||||||
|
TextValue.create(dto.description),
|
||||||
|
"description",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const isActive = extractOrPushError(Result.ok(dto.is_active), "is_active", errors);
|
||||||
|
|
||||||
|
this.throwIfValidationErrors(errors);
|
||||||
|
|
||||||
|
const props: ITaxDefinitionCreateProps = {
|
||||||
|
companyId: params.companyId,
|
||||||
|
code: code!,
|
||||||
|
name: name!,
|
||||||
|
description: description!,
|
||||||
|
rate: dto.rate as any,
|
||||||
|
taxFamily: dto.tax_family as any,
|
||||||
|
calculationBehavior: dto.calculation_behavior as any,
|
||||||
|
jurisdictionCountryCode: dto.jurisdiction_country_code as any,
|
||||||
|
jurisdictionRegionCode: dto.jurisdiction_region_code as any,
|
||||||
|
taxScope: dto.tax_scope as any,
|
||||||
|
invoiceNote: dto.invoice_note as any,
|
||||||
|
allowedSurchargeCodes: dto.allowed_surcharge_codes as any,
|
||||||
|
isSystem: dto.is_system ?? false,
|
||||||
|
isActive: isActive ?? true,
|
||||||
|
validFrom: dto.valid_from as any,
|
||||||
|
validTo: dto.valid_to as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok({ id: taxDefinitionId!, props });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(new DomainError("Tax definition props mapping failed", { cause: err }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new ValidationErrorCollection("Tax definition props mapping failed", errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./create-tax-definition-input.mapper";
|
||||||
|
export * from "./update-tax-definition-by-id-input.mapper";
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import type { UpdateTaxDefinitionByIdRequestDTO } from "@erp/catalogs/common";
|
||||||
|
import {
|
||||||
|
DomainError,
|
||||||
|
TextValue,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxDefinitionPatchProps } from "../../../domain";
|
||||||
|
|
||||||
|
export interface IUpdateTaxDefinitionByIdInputMapper {
|
||||||
|
map(dto: UpdateTaxDefinitionByIdRequestDTO): Result<TaxDefinitionPatchProps, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateTaxDefinitionByIdInputMapper implements IUpdateTaxDefinitionByIdInputMapper {
|
||||||
|
public map(dto: UpdateTaxDefinitionByIdRequestDTO): Result<TaxDefinitionPatchProps, Error> {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const name = extractOrPushError(TextValue.create(dto.name), "name", errors);
|
||||||
|
const description = extractOrPushError(
|
||||||
|
TextValue.create(dto.description),
|
||||||
|
"description",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
this.throwIfValidationErrors(errors);
|
||||||
|
|
||||||
|
const props: TaxDefinitionPatchProps = {
|
||||||
|
name: name ?? undefined,
|
||||||
|
description: description ?? undefined,
|
||||||
|
invoiceNote: dto.invoice_note as any,
|
||||||
|
rate: dto.rate as any,
|
||||||
|
allowedSurchargeCodes: dto.allowed_surcharge_codes as any,
|
||||||
|
isActive: dto.is_active,
|
||||||
|
validFrom: dto.valid_from as any,
|
||||||
|
validTo: dto.valid_to as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok(props);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(
|
||||||
|
new DomainError("Tax definition update props mapping failed", { cause: err })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private throwIfValidationErrors(errors: ValidationErrorDetail[]): void {
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new ValidationErrorCollection("Tax definition update props mapping failed", errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./tax-definition-detail.model";
|
||||||
|
export * from "./tax-definition-summary.model";
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import type { TaxDefinitionCode, TaxRate } from "@erp/catalogs/api/domain";
|
||||||
|
import type { TextValue, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
|
import type { Maybe } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
export type TaxDefinitionDetail = {
|
||||||
|
id: UniqueID;
|
||||||
|
companyId: UniqueID;
|
||||||
|
code: TaxDefinitionCode;
|
||||||
|
name: TextValue;
|
||||||
|
description: Maybe<TextValue>;
|
||||||
|
rate: TaxRate;
|
||||||
|
taxFamily: string;
|
||||||
|
calculationBehavior: string;
|
||||||
|
jurisdictionCountryCode: string;
|
||||||
|
jurisdictionRegionCode: Maybe<string>;
|
||||||
|
taxScope: string;
|
||||||
|
invoiceNote: Maybe<TextValue>;
|
||||||
|
allowedSurchargeCodes: Maybe<string[]>;
|
||||||
|
isSystem: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
validFrom: Maybe<UtcDate>;
|
||||||
|
validTo: Maybe<UtcDate>;
|
||||||
|
};
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import type { TaxDefinitionCode } from "@erp/catalogs/api/domain";
|
||||||
|
import type { TextValue, UniqueID } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
export type TaxDefinitionSummary = {
|
||||||
|
id: UniqueID;
|
||||||
|
companyId: UniqueID;
|
||||||
|
code: TaxDefinitionCode;
|
||||||
|
name: TextValue;
|
||||||
|
isActive: boolean;
|
||||||
|
isSystem: boolean;
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './tax-definition-repository.interface';
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Collection, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxDefinition, TaxDefinitionCode } from "../../../domain";
|
||||||
|
import type { TaxDefinitionSummary } from "../../tax-definitions/models";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionRepository {
|
||||||
|
create(taxDefinition: TaxDefinition, transaction?: unknown): Promise<Result<void, Error>>;
|
||||||
|
update(taxDefinition: TaxDefinition, 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<TaxDefinition, Error>>;
|
||||||
|
|
||||||
|
getByCodeInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
code: TaxDefinitionCode,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<TaxDefinition, Error>>;
|
||||||
|
|
||||||
|
findByCriteriaInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
criteria: Criteria,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<Collection<TaxDefinitionSummary>, Error>>;
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./tax-definition-creator.service";
|
||||||
|
export * from "./tax-definition-deleter.service";
|
||||||
|
export * from "./tax-definition-finder.service";
|
||||||
|
export * from "./tax-definition-status-changer.service";
|
||||||
|
export * from "./tax-definition-updater.service";
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { type ITaxDefinitionCreateProps, TaxDefinition } from "../../../domain";
|
||||||
|
import type { ITaxDefinitionRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionCreatorParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
props: ITaxDefinitionCreateProps;
|
||||||
|
transaction: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITaxDefinitionCreator {
|
||||||
|
create(params: ITaxDefinitionCreatorParams): Promise<Result<TaxDefinition, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TaxDefinitionCreator implements ITaxDefinitionCreator {
|
||||||
|
constructor(private readonly repository: ITaxDefinitionRepository) {}
|
||||||
|
|
||||||
|
public async create(params: ITaxDefinitionCreatorParams): Promise<Result<TaxDefinition, Error>> {
|
||||||
|
const { companyId, id, props, transaction } = params;
|
||||||
|
|
||||||
|
const taxDefinitionResult = TaxDefinition.create({ ...props, companyId }, id);
|
||||||
|
if (taxDefinitionResult.isFailure) {
|
||||||
|
return taxDefinitionResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxDefinition = taxDefinitionResult.data;
|
||||||
|
const saveResult = await this.repository.create(taxDefinition, transaction);
|
||||||
|
if (saveResult.isFailure) {
|
||||||
|
return Result.fail(saveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(taxDefinition);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionDeleter {
|
||||||
|
deleteByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TaxDefinitionDeleter implements ITaxDefinitionDeleter {
|
||||||
|
constructor(private readonly repository: ITaxDefinitionRepository) {}
|
||||||
|
|
||||||
|
public async deleteByIdInCompany(companyId: UniqueID, id: UniqueID, transaction?: unknown) {
|
||||||
|
return this.repository.deleteByIdInCompany(companyId, id, transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxDefinition } from "../../../domain";
|
||||||
|
import type { ITaxDefinitionRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionFinder {
|
||||||
|
getByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<TaxDefinition, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TaxDefinitionFinder implements ITaxDefinitionFinder {
|
||||||
|
constructor(private readonly repository: ITaxDefinitionRepository) {}
|
||||||
|
|
||||||
|
public async getByIdInCompany(companyId: UniqueID, id: UniqueID, transaction?: unknown) {
|
||||||
|
return this.repository.getByIdInCompany(companyId, id, transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionStatusChanger {
|
||||||
|
enable(companyId: UniqueID, id: UniqueID, transaction?: unknown): Promise<Result<boolean, Error>>;
|
||||||
|
disable(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TaxDefinitionStatusChanger implements ITaxDefinitionStatusChanger {
|
||||||
|
constructor(private readonly repository: ITaxDefinitionRepository) {}
|
||||||
|
|
||||||
|
public async enable(companyId: UniqueID, id: UniqueID, transaction?: unknown) {
|
||||||
|
const existing = await this.repository.getByIdInCompany(companyId, id, transaction);
|
||||||
|
if (existing.isFailure) return Result.fail(existing.error);
|
||||||
|
|
||||||
|
const taxDefinition = existing.data;
|
||||||
|
const changeResult = taxDefinition.enable();
|
||||||
|
if (changeResult.isFailure) return Result.fail(changeResult.error);
|
||||||
|
|
||||||
|
if (changeResult.data) {
|
||||||
|
const saveResult = await this.repository.update(taxDefinition, transaction);
|
||||||
|
if (saveResult.isFailure) return Result.fail(saveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(changeResult.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async disable(companyId: UniqueID, id: UniqueID, transaction?: unknown) {
|
||||||
|
const existing = await this.repository.getByIdInCompany(companyId, id, transaction);
|
||||||
|
if (existing.isFailure) return Result.fail(existing.error);
|
||||||
|
|
||||||
|
const taxDefinition = existing.data;
|
||||||
|
const changeResult = taxDefinition.disable();
|
||||||
|
if (changeResult.isFailure) return Result.fail(changeResult.error);
|
||||||
|
|
||||||
|
if (changeResult.data) {
|
||||||
|
const saveResult = await this.repository.update(taxDefinition, transaction);
|
||||||
|
if (saveResult.isFailure) return Result.fail(saveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(changeResult.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxDefinition, TaxDefinitionPatchProps } from "../../../domain";
|
||||||
|
import type { ITaxDefinitionRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionUpdaterParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
patch: TaxDefinitionPatchProps;
|
||||||
|
transaction: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITaxDefinitionUpdater {
|
||||||
|
update(params: ITaxDefinitionUpdaterParams): Promise<Result<TaxDefinition, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TaxDefinitionUpdater implements ITaxDefinitionUpdater {
|
||||||
|
constructor(private readonly repository: ITaxDefinitionRepository) {}
|
||||||
|
|
||||||
|
public async update(params: ITaxDefinitionUpdaterParams): Promise<Result<TaxDefinition, Error>> {
|
||||||
|
const { companyId, id, patch, transaction } = params;
|
||||||
|
|
||||||
|
const existingResult = await this.repository.getByIdInCompany(companyId, id, transaction);
|
||||||
|
if (existingResult.isFailure) return Result.fail(existingResult.error);
|
||||||
|
|
||||||
|
const taxDefinition = existingResult.data;
|
||||||
|
|
||||||
|
const updateResult = taxDefinition.update(patch);
|
||||||
|
if (updateResult.isFailure) return Result.fail(updateResult.error);
|
||||||
|
|
||||||
|
if (updateResult.data) {
|
||||||
|
const saveResult = await this.repository.update(taxDefinition, transaction);
|
||||||
|
if (saveResult.isFailure) return Result.fail(saveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(taxDefinition);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import type { TaxDefinitionDetailDTO } from "@erp/catalogs/common";
|
||||||
|
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { TaxDefinitionDetail } from "../../models";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionFullSnapshotBuilder
|
||||||
|
extends ISnapshotBuilder<TaxDefinitionDetail, TaxDefinitionDetailDTO> {}
|
||||||
|
|
||||||
|
export class TaxDefinitionFullSnapshotBuilder implements ITaxDefinitionFullSnapshotBuilder {
|
||||||
|
public toOutput(src: TaxDefinitionDetail): TaxDefinitionDetailDTO {
|
||||||
|
return {
|
||||||
|
id: src.id.toString(),
|
||||||
|
company_id: src.companyId.toString(),
|
||||||
|
code: src.code.toPrimitive(),
|
||||||
|
name: src.name.toPrimitive(),
|
||||||
|
description: src.description.match(
|
||||||
|
(v) => v.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
rate: (src.rate as any).toPrimitive(),
|
||||||
|
tax_family: src.taxFamily,
|
||||||
|
calculation_behavior: src.calculationBehavior,
|
||||||
|
jurisdiction_country_code: src.jurisdictionCountryCode,
|
||||||
|
jurisdiction_region_code: src.jurisdictionRegionCode.match(
|
||||||
|
(v) => v,
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
tax_scope: src.taxScope,
|
||||||
|
invoice_note: src.invoiceNote.match(
|
||||||
|
(v) => v.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
allowed_surcharge_codes: src.allowedSurchargeCodes.match(
|
||||||
|
(arr) => arr,
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
is_system: src.isSystem,
|
||||||
|
is_active: src.isActive,
|
||||||
|
valid_from: src.validFrom.match(
|
||||||
|
(d) => d.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
valid_to: src.validTo.match(
|
||||||
|
(d) => d.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
} as unknown as TaxDefinitionDetailDTO;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./full-tax-definition-snapshot.builder";
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./full";
|
||||||
|
export * from "./summary";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./summary-tax-definition-snapshot.builder";
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import type { TaxDefinitionSummaryDTO } from "@erp/catalogs/common";
|
||||||
|
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { TaxDefinitionSummary } from "../../models";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionSummarySnapshotBuilder
|
||||||
|
extends ISnapshotBuilder<TaxDefinitionSummary, TaxDefinitionSummaryDTO> {}
|
||||||
|
|
||||||
|
export class TaxDefinitionSummarySnapshotBuilder implements ITaxDefinitionSummarySnapshotBuilder {
|
||||||
|
public toOutput(src: TaxDefinitionSummary): TaxDefinitionSummaryDTO {
|
||||||
|
return {
|
||||||
|
id: src.id.toString(),
|
||||||
|
company_id: src.companyId.toString(),
|
||||||
|
code: src.code.toPrimitive(),
|
||||||
|
name: src.name.toPrimitive(),
|
||||||
|
is_active: src.isActive,
|
||||||
|
is_system: src.isSystem,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import type { CreateTaxDefinitionRequestDTO } 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 { ICreateTaxDefinitionInputMapper } from "../mappers";
|
||||||
|
import type { ITaxDefinitionCreator } from "../services";
|
||||||
|
import type { ITaxDefinitionFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type CreateTaxDefinitionUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
dto: CreateTaxDefinitionRequestDTO;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CreateTaxDefinitionUseCaseDeps = {
|
||||||
|
dtoMapper: ICreateTaxDefinitionInputMapper;
|
||||||
|
creator: ITaxDefinitionCreator;
|
||||||
|
fullSnapshotBuilder: ITaxDefinitionFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CreateTaxDefinitionUseCase {
|
||||||
|
constructor(private readonly deps: CreateTaxDefinitionUseCaseDeps) {}
|
||||||
|
|
||||||
|
public execute(params: CreateTaxDefinitionUseCaseInput) {
|
||||||
|
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 as any);
|
||||||
|
return Result.ok(snapshot);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionDeleter } from "../services";
|
||||||
|
|
||||||
|
type DeleteTaxDefinitionDeps = {
|
||||||
|
deleter: ITaxDefinitionDeleter;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DeleteTaxDefinitionByIdUseCase {
|
||||||
|
constructor(private readonly deps: DeleteTaxDefinitionDeps) {}
|
||||||
|
|
||||||
|
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||||
|
const result = await this.deps.deleter.deleteByIdInCompany(companyId, id);
|
||||||
|
if (result.isFailure) return result;
|
||||||
|
|
||||||
|
return Result.ok(result.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionStatusChanger } from "../services";
|
||||||
|
|
||||||
|
type DisableTaxDefinitionDeps = {
|
||||||
|
statusChanger: ITaxDefinitionStatusChanger;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DisableTaxDefinitionByIdUseCase {
|
||||||
|
constructor(private readonly deps: DisableTaxDefinitionDeps) {}
|
||||||
|
|
||||||
|
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||||
|
const result = await this.deps.statusChanger.disable(companyId, id);
|
||||||
|
if (result.isFailure) return result;
|
||||||
|
|
||||||
|
return Result.ok(result.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionStatusChanger } from "../services";
|
||||||
|
|
||||||
|
type EnableTaxDefinitionDeps = {
|
||||||
|
statusChanger: ITaxDefinitionStatusChanger;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EnableTaxDefinitionByIdUseCase {
|
||||||
|
constructor(private readonly deps: EnableTaxDefinitionDeps) {}
|
||||||
|
|
||||||
|
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||||
|
const result = await this.deps.statusChanger.enable(companyId, id);
|
||||||
|
if (result.isFailure) return result;
|
||||||
|
|
||||||
|
return Result.ok(result.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionFinder } from "../services";
|
||||||
|
import type { ITaxDefinitionFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
type GetTaxDefinitionByIdUseCaseDeps = {
|
||||||
|
finder: ITaxDefinitionFinder;
|
||||||
|
fullSnapshotBuilder: ITaxDefinitionFullSnapshotBuilder;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class GetTaxDefinitionByIdUseCase {
|
||||||
|
constructor(private readonly deps: GetTaxDefinitionByIdUseCaseDeps) {}
|
||||||
|
|
||||||
|
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||||
|
const found = await this.deps.finder.getByIdInCompany(companyId, id);
|
||||||
|
if (found.isFailure) return found;
|
||||||
|
|
||||||
|
const snapshot = this.deps.fullSnapshotBuilder.toOutput(found.data as any);
|
||||||
|
return Result.ok(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./create-tax-definition.use-case";
|
||||||
|
export * from "./delete-tax-definition-by-id.use-case";
|
||||||
|
export * from "./disable-tax-definition-by-id.use-case";
|
||||||
|
export * from "./enable-tax-definition-by-id.use-case";
|
||||||
|
export * from "./get-tax-definition-by-id.use-case";
|
||||||
|
export * from "./list-tax-definitions.use-case";
|
||||||
|
export * from "./update-tax-definition-by-id.use-case";
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionRepository } from "../repositories";
|
||||||
|
import type { ITaxDefinitionSummarySnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
type ListTaxDefinitionsUseCaseDeps = {
|
||||||
|
repository: ITaxDefinitionRepository;
|
||||||
|
summarySnapshotBuilder: ITaxDefinitionSummarySnapshotBuilder;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ListTaxDefinitionsUseCase {
|
||||||
|
constructor(private readonly deps: ListTaxDefinitionsUseCaseDeps) {}
|
||||||
|
|
||||||
|
public async execute(companyId: UniqueID, criteria: Criteria) {
|
||||||
|
const found = await this.deps.repository.findByCriteriaInCompany(companyId, criteria);
|
||||||
|
if (found.isFailure) return found;
|
||||||
|
|
||||||
|
const collection = found.data.map((item) => this.deps.summarySnapshotBuilder.toOutput(item));
|
||||||
|
return Result.ok(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IUpdateTaxDefinitionByIdInputMapper } from "../mappers";
|
||||||
|
import type { ITaxDefinitionUpdater } from "../services";
|
||||||
|
import type { ITaxDefinitionFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
type UpdateTaxDefinitionByIdDeps = {
|
||||||
|
dtoMapper: IUpdateTaxDefinitionByIdInputMapper;
|
||||||
|
updater: ITaxDefinitionUpdater;
|
||||||
|
fullSnapshotBuilder: ITaxDefinitionFullSnapshotBuilder;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateTaxDefinitionByIdUseCaseInput = {
|
||||||
|
id: UniqueID;
|
||||||
|
dto: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UpdateTaxDefinitionByIdUseCase {
|
||||||
|
constructor(private readonly deps: UpdateTaxDefinitionByIdDeps) {}
|
||||||
|
|
||||||
|
public async execute(companyId: UniqueID, params: UpdateTaxDefinitionByIdUseCaseInput) {
|
||||||
|
const mapped = this.deps.dtoMapper.map(params.dto as any);
|
||||||
|
if (mapped.isFailure) return mapped;
|
||||||
|
|
||||||
|
const result = await this.deps.updater.update({
|
||||||
|
companyId,
|
||||||
|
id: params.id,
|
||||||
|
patch: mapped.data,
|
||||||
|
transaction: undefined,
|
||||||
|
});
|
||||||
|
if (result.isFailure) return result;
|
||||||
|
|
||||||
|
const snapshot = this.deps.fullSnapshotBuilder.toOutput(result.data as any);
|
||||||
|
return Result.ok(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
export * from "./payment-terms";
|
export * from "./payment-terms";
|
||||||
|
export * from "./tax-definitions";
|
||||||
export * from "./tax-regimes";
|
export * from "./tax-regimes";
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvalidTaxDefinitionCalculationBehaviorError } from "./errors";
|
||||||
|
|
||||||
|
export type TaxCalculationBehaviorType = "additive" | "subtractive" | "neutral";
|
||||||
|
|
||||||
|
export class TaxCalculationBehavior {
|
||||||
|
private constructor(private readonly value: TaxCalculationBehaviorType) {}
|
||||||
|
|
||||||
|
public static create(value: string): Result<TaxCalculationBehavior, Error> {
|
||||||
|
const v = value?.toString() ?? "";
|
||||||
|
const allowed: TaxCalculationBehaviorType[] = ["additive", "subtractive", "neutral"];
|
||||||
|
|
||||||
|
if (!allowed.includes(v as TaxCalculationBehaviorType)) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionCalculationBehaviorError("Invalid calculation behavior")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new TaxCalculationBehavior(v as TaxCalculationBehaviorType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPersistence(value: string): TaxCalculationBehavior {
|
||||||
|
return new TaxCalculationBehavior(value as TaxCalculationBehaviorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): TaxCalculationBehaviorType {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
129
modules/catalogs/src/api/domain/tax-definitions/errors.ts
Normal file
129
modules/catalogs/src/api/domain/tax-definitions/errors.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { DomainError } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionIdError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_ID" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionIdError = (e: unknown): e is InvalidTaxDefinitionIdError =>
|
||||||
|
e instanceof InvalidTaxDefinitionIdError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionCompanyIdError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_COMPANY_ID" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionCompanyIdError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionCompanyIdError => e instanceof InvalidTaxDefinitionCompanyIdError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionCodeError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_CODE" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionCodeError = (e: unknown): e is InvalidTaxDefinitionCodeError =>
|
||||||
|
e instanceof InvalidTaxDefinitionCodeError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionNameError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_NAME" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionNameError = (e: unknown): e is InvalidTaxDefinitionNameError =>
|
||||||
|
e instanceof InvalidTaxDefinitionNameError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionDescriptionError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_DESCRIPTION" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionDescriptionError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionDescriptionError => e instanceof InvalidTaxDefinitionDescriptionError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionRateError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_RATE" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionRateError = (e: unknown): e is InvalidTaxDefinitionRateError =>
|
||||||
|
e instanceof InvalidTaxDefinitionRateError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionFamilyError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_FAMILY" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionFamilyError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionFamilyError => e instanceof InvalidTaxDefinitionFamilyError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionCalculationBehaviorError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_CALCULATION_BEHAVIOR" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionCalculationBehaviorError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionCalculationBehaviorError =>
|
||||||
|
e instanceof InvalidTaxDefinitionCalculationBehaviorError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionJurisdictionCountryCodeError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_JURISDICTION_COUNTRY_CODE" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionJurisdictionCountryCodeError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionJurisdictionCountryCodeError =>
|
||||||
|
e instanceof InvalidTaxDefinitionJurisdictionCountryCodeError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionJurisdictionRegionCodeError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_JURISDICTION_REGION_CODE" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionJurisdictionRegionCodeError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionJurisdictionRegionCodeError =>
|
||||||
|
e instanceof InvalidTaxDefinitionJurisdictionRegionCodeError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionScopeError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_SCOPE" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionScopeError = (e: unknown): e is InvalidTaxDefinitionScopeError =>
|
||||||
|
e instanceof InvalidTaxDefinitionScopeError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionInvoiceNoteError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_INVOICE_NOTE" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionInvoiceNoteError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionInvoiceNoteError => e instanceof InvalidTaxDefinitionInvoiceNoteError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionAllowedSurchargeCodesError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_ALLOWED_SURCHARGE_CODES" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionAllowedSurchargeCodesError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionAllowedSurchargeCodesError =>
|
||||||
|
e instanceof InvalidTaxDefinitionAllowedSurchargeCodesError;
|
||||||
|
|
||||||
|
export class InvalidTaxDefinitionValidityPeriodError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_INVALID_VALIDITY_PERIOD" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInvalidTaxDefinitionValidityPeriodError = (
|
||||||
|
e: unknown
|
||||||
|
): e is InvalidTaxDefinitionValidityPeriodError =>
|
||||||
|
e instanceof InvalidTaxDefinitionValidityPeriodError;
|
||||||
|
|
||||||
|
export class TaxDefinitionCannotBeEnabledError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_CANNOT_BE_ENABLED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isTaxDefinitionCannotBeEnabledError = (
|
||||||
|
e: unknown
|
||||||
|
): e is TaxDefinitionCannotBeEnabledError => e instanceof TaxDefinitionCannotBeEnabledError;
|
||||||
|
|
||||||
|
export class TaxDefinitionCannotBeDisabledError extends DomainError {
|
||||||
|
public readonly code = "TAX_DEFINITION_CANNOT_BE_DISABLED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isTaxDefinitionCannotBeDisabledError = (
|
||||||
|
e: unknown
|
||||||
|
): e is TaxDefinitionCannotBeDisabledError => e instanceof TaxDefinitionCannotBeDisabledError;
|
||||||
10
modules/catalogs/src/api/domain/tax-definitions/index.ts
Normal file
10
modules/catalogs/src/api/domain/tax-definitions/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export * from "./calculation-behavior";
|
||||||
|
export * from "./errors";
|
||||||
|
export * from "./jurisdiction-country-code";
|
||||||
|
export * from "./jurisdiction-region-code";
|
||||||
|
export * from "./tax-definition.aggregate";
|
||||||
|
export * from "./tax-definition-code";
|
||||||
|
export * from "./tax-definition-name";
|
||||||
|
export * from "./tax-family";
|
||||||
|
export * from "./tax-rate";
|
||||||
|
export * from "./tax-scope";
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvalidTaxDefinitionJurisdictionCountryCodeError } from "./errors";
|
||||||
|
|
||||||
|
export class TaxJurisdictionCountryCode {
|
||||||
|
private constructor(private readonly value: string) {}
|
||||||
|
|
||||||
|
public static create(code: string): Result<TaxJurisdictionCountryCode, Error> {
|
||||||
|
const trimmed = code?.trim() ?? "";
|
||||||
|
if (trimmed.length !== 2) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionJurisdictionCountryCodeError("Country code must be 2 letters")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = trimmed.toUpperCase();
|
||||||
|
const regex = /^[A-Z]{2}$/;
|
||||||
|
if (!regex.test(normalized)) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionJurisdictionCountryCodeError(
|
||||||
|
"Country code must be 2 uppercase letters"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We allow two-letter codes including 'EU' to support union-level definitions like reverse charge.
|
||||||
|
return Result.ok(new TaxJurisdictionCountryCode(normalized));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPersistence(code: string): TaxJurisdictionCountryCode {
|
||||||
|
return new TaxJurisdictionCountryCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvalidTaxDefinitionJurisdictionRegionCodeError } from "./errors";
|
||||||
|
|
||||||
|
export class TaxJurisdictionRegionCode {
|
||||||
|
private constructor(private readonly value: string) {}
|
||||||
|
|
||||||
|
public static create(code: string): Result<TaxJurisdictionRegionCode, Error> {
|
||||||
|
const trimmed = code?.trim() ?? "";
|
||||||
|
if (trimmed.length === 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionJurisdictionRegionCodeError("Region code cannot be empty")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = trimmed.toUpperCase();
|
||||||
|
|
||||||
|
// basic pattern: CC-... (we won't validate full ISO-3166-2 list)
|
||||||
|
const regex = /^[A-Z]{2}-[A-Z0-9-]+$/;
|
||||||
|
if (!regex.test(normalized)) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionJurisdictionRegionCodeError(
|
||||||
|
"Region code must follow ISO-3166-2 pattern COUNTRY-REGION"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new TaxJurisdictionRegionCode(normalized));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPersistence(code: string): TaxJurisdictionRegionCode {
|
||||||
|
return new TaxJurisdictionRegionCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvalidTaxDefinitionCodeError } from "./errors";
|
||||||
|
|
||||||
|
export class TaxDefinitionCode {
|
||||||
|
private constructor(private readonly value: string) {}
|
||||||
|
|
||||||
|
public static create(code: string): Result<TaxDefinitionCode, Error> {
|
||||||
|
const trimmed = code?.trim() ?? "";
|
||||||
|
if (trimmed.length === 0) {
|
||||||
|
return Result.fail(new InvalidTaxDefinitionCodeError("Tax definition code cannot be empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = trimmed.toLowerCase();
|
||||||
|
const regex = /^[a-z0-9][a-z0-9_]*$/;
|
||||||
|
if (!regex.test(normalized)) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionCodeError("Tax definition code must match ^[a-z0-9][a-z0-9_]*$")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new TaxDefinitionCode(normalized));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPersistence(code: string): TaxDefinitionCode {
|
||||||
|
return new TaxDefinitionCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvalidTaxDefinitionNameError } from "./errors";
|
||||||
|
|
||||||
|
export class TaxDefinitionName {
|
||||||
|
private constructor(private readonly value: string) {}
|
||||||
|
|
||||||
|
public static create(name: string): Result<TaxDefinitionName, Error> {
|
||||||
|
const trimmed = name?.trim() ?? "";
|
||||||
|
if (trimmed.length === 0) {
|
||||||
|
return Result.fail(new InvalidTaxDefinitionNameError("Tax definition name cannot be empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// follow existing patterns: no extra length validation here
|
||||||
|
return Result.ok(new TaxDefinitionName(trimmed));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPersistence(name: string): TaxDefinitionName {
|
||||||
|
return new TaxDefinitionName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,369 @@
|
|||||||
|
import { AggregateRoot, type TextValue, type UniqueID, type UtcDate } from "@repo/rdx-ddd";
|
||||||
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxCalculationBehavior } from "./calculation-behavior";
|
||||||
|
import {
|
||||||
|
InvalidTaxDefinitionAllowedSurchargeCodesError,
|
||||||
|
InvalidTaxDefinitionJurisdictionRegionCodeError,
|
||||||
|
InvalidTaxDefinitionValidityPeriodError,
|
||||||
|
} from "./errors";
|
||||||
|
import type { TaxJurisdictionCountryCode } from "./jurisdiction-country-code";
|
||||||
|
import type { TaxJurisdictionRegionCode } from "./jurisdiction-region-code";
|
||||||
|
import type { TaxDefinitionCode } from "./tax-definition-code";
|
||||||
|
import type { TaxDefinitionName } from "./tax-definition-name";
|
||||||
|
import type { TaxFamily } from "./tax-family";
|
||||||
|
import type { TaxRate } from "./tax-rate";
|
||||||
|
import type { TaxScope } from "./tax-scope";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionCreateProps {
|
||||||
|
companyId: UniqueID;
|
||||||
|
code: TaxDefinitionCode;
|
||||||
|
name: TaxDefinitionName;
|
||||||
|
description: Maybe<TextValue>;
|
||||||
|
rate: TaxRate;
|
||||||
|
taxFamily: TaxFamily;
|
||||||
|
calculationBehavior: TaxCalculationBehavior;
|
||||||
|
jurisdictionCountryCode: TaxJurisdictionCountryCode;
|
||||||
|
jurisdictionRegionCode: Maybe<TaxJurisdictionRegionCode>;
|
||||||
|
taxScope: TaxScope;
|
||||||
|
invoiceNote: Maybe<TextValue>; // Texto fiscal que aparece en el documento
|
||||||
|
allowedSurchargeCodes: Maybe<TaxDefinitionCode[]>;
|
||||||
|
isSystem: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
validFrom: Maybe<UtcDate>;
|
||||||
|
validTo: Maybe<UtcDate>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TaxDefinitionPatchProps = Partial<{
|
||||||
|
name: TaxDefinitionName;
|
||||||
|
description: Maybe<TextValue>;
|
||||||
|
rate: TaxRate;
|
||||||
|
invoiceNote: Maybe<TextValue>;
|
||||||
|
allowedSurchargeCodes: Maybe<TaxDefinitionCode[]>;
|
||||||
|
isActive: boolean;
|
||||||
|
validFrom: Maybe<UtcDate>;
|
||||||
|
validTo: Maybe<UtcDate>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type TaxDefinitionInternalProps = ITaxDefinitionCreateProps;
|
||||||
|
|
||||||
|
export class TaxDefinition extends AggregateRoot<TaxDefinitionInternalProps> {
|
||||||
|
protected constructor(props: TaxDefinitionInternalProps, id?: UniqueID) {
|
||||||
|
super(props, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(
|
||||||
|
props: ITaxDefinitionCreateProps,
|
||||||
|
id?: UniqueID
|
||||||
|
): Result<TaxDefinition, Error> {
|
||||||
|
const validationResult = TaxDefinition.validateCreateProps(props);
|
||||||
|
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(validationResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxDefinition = new TaxDefinition(props, id);
|
||||||
|
|
||||||
|
return Result.ok(taxDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static rehydrate(props: TaxDefinitionInternalProps, id: UniqueID): TaxDefinition {
|
||||||
|
return new TaxDefinition(props, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static validateCreateProps(props: ITaxDefinitionCreateProps): Result<void, Error> {
|
||||||
|
if (!props.companyId) {
|
||||||
|
return Result.fail(new Error("Tax definition company ID is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.code) {
|
||||||
|
return Result.fail(new Error("Tax definition code is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.name) {
|
||||||
|
return Result.fail(new Error("Tax definition name is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.rate) {
|
||||||
|
return Result.fail(new Error("Tax definition rate is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.taxFamily) {
|
||||||
|
return Result.fail(new Error("Tax definition family is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.calculationBehavior) {
|
||||||
|
return Result.fail(new Error("Tax definition calculation behavior is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.jurisdictionCountryCode) {
|
||||||
|
return Result.fail(new Error("Tax definition jurisdiction country code is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.taxScope) {
|
||||||
|
return Result.fail(new Error("Tax definition scope is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// allowedSurchargeCodes rules
|
||||||
|
if (props.allowedSurchargeCodes && props.allowedSurchargeCodes.isSome()) {
|
||||||
|
const arr = props.allowedSurchargeCodes.unwrap();
|
||||||
|
// duplicates
|
||||||
|
const primitives = arr.map((c) => c.toPrimitive());
|
||||||
|
const unique = Array.from(new Set(primitives));
|
||||||
|
if (unique.length !== primitives.length) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionAllowedSurchargeCodesError("Duplicate surcharge codes")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cannot reference itself
|
||||||
|
if (primitives.includes(props.code.toPrimitive())) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionAllowedSurchargeCodesError(
|
||||||
|
"Surcharge codes cannot contain the tax's own code"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// only allowed for 'iva'
|
||||||
|
if (props.taxFamily.toPrimitive() !== "iva") {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionAllowedSurchargeCodesError(
|
||||||
|
"Allowed surcharge codes only valid for 'iva' family"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validFrom/validTo coherence
|
||||||
|
if (props.validFrom && props.validFrom.isSome() && props.validTo && props.validTo.isSome()) {
|
||||||
|
const from = props.validFrom.unwrap();
|
||||||
|
const to = props.validTo.unwrap();
|
||||||
|
if (from.toPrimitive() > to.toPrimitive()) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionValidityPeriodError("validFrom must be <= validTo")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// region coherence with country
|
||||||
|
if (props.jurisdictionRegionCode && props.jurisdictionRegionCode.isSome()) {
|
||||||
|
const region = props.jurisdictionRegionCode.unwrap().toPrimitive();
|
||||||
|
const country = props.jurisdictionCountryCode.toPrimitive();
|
||||||
|
if (!region.startsWith(`${country}-`)) {
|
||||||
|
return Result.fail(
|
||||||
|
new InvalidTaxDefinitionJurisdictionRegionCodeError(
|
||||||
|
"Region code must start with country code"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static validatePatchProps(patchProps: TaxDefinitionPatchProps): Result<void, Error> {
|
||||||
|
if (Object.keys(patchProps).length === 0) {
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get companyId(): UniqueID {
|
||||||
|
return this.props.companyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get code(): TaxDefinitionCode {
|
||||||
|
return this.props.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name(): TaxDefinitionName {
|
||||||
|
return this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get description(): Maybe<TextValue> {
|
||||||
|
return this.props.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get rate(): TaxRate {
|
||||||
|
return this.props.rate as TaxRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get taxFamily(): TaxFamily {
|
||||||
|
return this.props.taxFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get calculationBehavior(): TaxCalculationBehavior {
|
||||||
|
return this.props.calculationBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get jurisdictionCountryCode(): TaxJurisdictionCountryCode {
|
||||||
|
return this.props.jurisdictionCountryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get jurisdictionRegionCode(): Maybe<TaxJurisdictionRegionCode> {
|
||||||
|
return this.props.jurisdictionRegionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get taxScope(): TaxScope {
|
||||||
|
return this.props.taxScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get invoiceNote(): Maybe<TextValue> {
|
||||||
|
return this.props.invoiceNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get allowedSurchargeCodes(): Maybe<TaxDefinitionCode[]> {
|
||||||
|
return this.props.allowedSurchargeCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isSystem(): boolean {
|
||||||
|
return this.props.isSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isActive(): boolean {
|
||||||
|
return this.props.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get validFrom(): Maybe<UtcDate> {
|
||||||
|
return this.props.validFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get validTo(): Maybe<UtcDate> {
|
||||||
|
return this.props.validTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(patchProps: TaxDefinitionPatchProps): Result<boolean, Error> {
|
||||||
|
const validationResult = TaxDefinition.validatePatchProps(patchProps);
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(validationResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasChanges = false;
|
||||||
|
|
||||||
|
// Only allow updating mutable fields
|
||||||
|
if (
|
||||||
|
patchProps.name !== undefined &&
|
||||||
|
this.props.name.toPrimitive() !== patchProps.name.toPrimitive()
|
||||||
|
) {
|
||||||
|
this.props.name = patchProps.name;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
patchProps.description !== undefined &&
|
||||||
|
!TaxDefinition.sameDescription(this.props.description, patchProps.description)
|
||||||
|
) {
|
||||||
|
this.props.description = patchProps.description;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
patchProps.rate !== undefined &&
|
||||||
|
this.props.rate.toPrimitive() !== patchProps.rate.toPrimitive()
|
||||||
|
) {
|
||||||
|
this.props.rate = patchProps.rate as unknown as any;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
patchProps.invoiceNote !== undefined &&
|
||||||
|
!TaxDefinition.sameDescription(this.props.invoiceNote, patchProps.invoiceNote)
|
||||||
|
) {
|
||||||
|
this.props.invoiceNote = patchProps.invoiceNote;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchProps.allowedSurchargeCodes !== undefined) {
|
||||||
|
this.props.allowedSurchargeCodes = patchProps.allowedSurchargeCodes;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchProps.isActive !== undefined && this.props.isActive !== patchProps.isActive) {
|
||||||
|
this.props.isActive = patchProps.isActive;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchProps.validFrom !== undefined) {
|
||||||
|
this.props.validFrom = patchProps.validFrom;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchProps.validTo !== undefined) {
|
||||||
|
this.props.validTo = patchProps.validTo;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(hasChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public disable(): Result<boolean, Error> {
|
||||||
|
if (!this.isActive) {
|
||||||
|
return Result.ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.isActive = false;
|
||||||
|
return Result.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enable(): Result<boolean, Error> {
|
||||||
|
if (this.isActive) {
|
||||||
|
return Result.ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.isActive = true;
|
||||||
|
return Result.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id.toPrimitive(),
|
||||||
|
company_id: this.companyId.toPrimitive(),
|
||||||
|
code: this.code.toPrimitive(),
|
||||||
|
name: this.name.toPrimitive(),
|
||||||
|
description: this.description.match(
|
||||||
|
(v) => v.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
rate: { value: this.rate.toPrimitive(), scale: (this.rate as any).scale ?? 2 },
|
||||||
|
tax_family: this.taxFamily.toPrimitive(),
|
||||||
|
calculation_behavior: this.calculationBehavior.toPrimitive(),
|
||||||
|
jurisdiction_country_code: this.jurisdictionCountryCode.toPrimitive(),
|
||||||
|
jurisdiction_region_code: this.jurisdictionRegionCode.match(
|
||||||
|
(v) => v.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
tax_scope: this.taxScope.toPrimitive(),
|
||||||
|
invoice_note: this.invoiceNote.match(
|
||||||
|
(v) => v.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
allowed_surcharge_codes: this.allowedSurchargeCodes.match(
|
||||||
|
(arr) => arr.map((c) => c.toPrimitive()),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
is_system: this.isSystem,
|
||||||
|
is_active: this.isActive,
|
||||||
|
valid_from: this.validFrom.match(
|
||||||
|
(d) => d.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
valid_to: this.validTo.match(
|
||||||
|
(d) => d.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static sameDescription(current: Maybe<TextValue>, next: Maybe<TextValue>): boolean {
|
||||||
|
return current.match(
|
||||||
|
(currentValue) =>
|
||||||
|
next.match(
|
||||||
|
(nextValue) => currentValue.toPrimitive() === nextValue.toPrimitive(),
|
||||||
|
() => false
|
||||||
|
),
|
||||||
|
() => next.isNone()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvalidTaxDefinitionFamilyError } from "./errors";
|
||||||
|
|
||||||
|
export type TaxFamilyType =
|
||||||
|
| "iva"
|
||||||
|
| "igic"
|
||||||
|
| "ipsi"
|
||||||
|
| "equivalence_surcharge"
|
||||||
|
| "withholding"
|
||||||
|
| "vat"
|
||||||
|
| "gst"
|
||||||
|
| "sales_tax"
|
||||||
|
| "reverse_charge"
|
||||||
|
| "exempt"
|
||||||
|
| "not_subject"
|
||||||
|
| "custom";
|
||||||
|
|
||||||
|
export class TaxFamily {
|
||||||
|
private constructor(private readonly value: TaxFamilyType) {}
|
||||||
|
|
||||||
|
public static create(value: string): Result<TaxFamily, Error> {
|
||||||
|
const v = value?.toString() ?? "";
|
||||||
|
const allowed: TaxFamilyType[] = [
|
||||||
|
"iva",
|
||||||
|
"igic",
|
||||||
|
"ipsi",
|
||||||
|
"equivalence_surcharge",
|
||||||
|
"withholding",
|
||||||
|
"vat",
|
||||||
|
"gst",
|
||||||
|
"sales_tax",
|
||||||
|
"reverse_charge",
|
||||||
|
"exempt",
|
||||||
|
"not_subject",
|
||||||
|
"custom",
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!allowed.includes(v as TaxFamilyType)) {
|
||||||
|
return Result.fail(new InvalidTaxDefinitionFamilyError("Invalid tax family"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new TaxFamily(v as TaxFamilyType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPersistence(value: string): TaxFamily {
|
||||||
|
return new TaxFamily(value as TaxFamilyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): TaxFamilyType {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
modules/catalogs/src/api/domain/tax-definitions/tax-rate.ts
Normal file
40
modules/catalogs/src/api/domain/tax-definitions/tax-rate.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Percentage, type PercentageProps, ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvalidTaxDefinitionRateError } from "./errors";
|
||||||
|
|
||||||
|
type TaxRateProps = PercentageProps;
|
||||||
|
|
||||||
|
export class TaxRate extends Percentage {
|
||||||
|
static DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
|
static create({ value, scale }: TaxRateProps): Result<Percentage, Error> {
|
||||||
|
if (scale && scale !== TaxRate.DEFAULT_SCALE) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("InvalidScale", [
|
||||||
|
{ message: `TaxRate scale must be ${TaxRate.DEFAULT_SCALE}` },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic range validation: 0.00 .. 100.00 (value expressed with scale 2 => 0 .. 10000)
|
||||||
|
if (value < 0) {
|
||||||
|
return Result.fail(new InvalidTaxDefinitionRateError("Tax rate must be >= 0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value > 100 * 10 ** TaxRate.DEFAULT_SCALE) {
|
||||||
|
return Result.fail(new InvalidTaxDefinitionRateError("Tax rate must be <= 100.00"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(
|
||||||
|
new TaxRate({
|
||||||
|
value,
|
||||||
|
scale: TaxRate.DEFAULT_SCALE,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static zero() {
|
||||||
|
return TaxRate.create({ value: 0 }).data;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
modules/catalogs/src/api/domain/tax-definitions/tax-scope.ts
Normal file
32
modules/catalogs/src/api/domain/tax-definitions/tax-scope.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { InvalidTaxDefinitionScopeError } from "./errors";
|
||||||
|
|
||||||
|
export type TaxScopeType = "domestic" | "intra_eu" | "export" | "import" | "international";
|
||||||
|
|
||||||
|
export class TaxScope {
|
||||||
|
private constructor(private readonly value: TaxScopeType) {}
|
||||||
|
|
||||||
|
public static create(value: string): Result<TaxScope, Error> {
|
||||||
|
const v = value?.toString() ?? "";
|
||||||
|
const allowed: TaxScopeType[] = ["domestic", "intra_eu", "export", "import", "international"];
|
||||||
|
|
||||||
|
if (!allowed.includes(v as TaxScopeType)) {
|
||||||
|
return Result.fail(new InvalidTaxDefinitionScopeError("Invalid tax scope"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new TaxScope(v as TaxScopeType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPersistence(value: string): TaxScope {
|
||||||
|
return new TaxScope(value as TaxScopeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): TaxScopeType {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,35 +13,62 @@ import {
|
|||||||
buildCatalogsPublicServices,
|
buildCatalogsPublicServices,
|
||||||
} from "./infrastructure/di/catalogs.di";
|
} from "./infrastructure/di/catalogs.di";
|
||||||
|
|
||||||
export * from "./infrastructure/payment-methods/persistence/sequelize";
|
export * from "./application/services/catalog-public-services.interface"; // <- exportamos la interfaz de los servicios públicos para que otros módulos puedan usarla en sus dependencias
|
||||||
|
|
||||||
|
//export * from "./infrastructure/payment-methods/persistence/sequelize"; <- ???
|
||||||
|
|
||||||
export const catalogsAPIModule: IModuleServer = {
|
export const catalogsAPIModule: IModuleServer = {
|
||||||
name: "catalogs",
|
name: "catalogs",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fase de SETUP
|
||||||
|
* ----------------
|
||||||
|
* - Construye el dominio (una sola vez)
|
||||||
|
* - Define qué expone el módulo
|
||||||
|
* - NO conecta infraestructura
|
||||||
|
*/
|
||||||
async setup(params) {
|
async setup(params) {
|
||||||
|
const { env: ENV, app, database, baseRoutePath: API_BASE_PATH, logger } = params;
|
||||||
|
|
||||||
|
// 1) Dominio interno
|
||||||
const internal = buildCatalogsDependencies(params);
|
const internal = buildCatalogsDependencies(params);
|
||||||
|
|
||||||
|
// 2) Servicios públicos (Application Services)
|
||||||
const publicServices = buildCatalogsPublicServices(params, internal);
|
const publicServices = buildCatalogsPublicServices(params, internal);
|
||||||
|
|
||||||
params.logger.info("🚀 Catalogs module dependencies registered", {
|
logger.info("🚀 Catalogs module dependencies registered", {
|
||||||
label: this.name,
|
label: this.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// Modelos Sequelize del módulo
|
||||||
models: [...paymentMethodModels, ...paymentTermModels, ...taxRegimeModels],
|
models: [...paymentMethodModels, ...paymentTermModels, ...taxRegimeModels],
|
||||||
|
|
||||||
|
// Servicios expuestos a otros módulos
|
||||||
services: {
|
services: {
|
||||||
paymentMethod: publicServices.paymentMethods,
|
paymentMethods: publicServices.paymentMethods,
|
||||||
paymentTerms: publicServices.paymentTerms,
|
paymentTerms: publicServices.paymentTerms,
|
||||||
taxRegimes: publicServices.taxRegimes,
|
taxRegimes: publicServices.taxRegimes,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Implementación privada del módulo
|
||||||
internal,
|
internal,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fase de START
|
||||||
|
* -------------
|
||||||
|
* - Conecta el módulo al runtime
|
||||||
|
* - Puede usar servicios e internals ya construidos
|
||||||
|
* - NO construye dominio
|
||||||
|
*/
|
||||||
async start(params) {
|
async start(params) {
|
||||||
const { logger } = params;
|
const { logger } = params;
|
||||||
|
|
||||||
|
// Registro de rutas HTTP
|
||||||
paymentMethodsRouter(params);
|
paymentMethodsRouter(params);
|
||||||
paymentTermsRouter(params);
|
paymentTermsRouter(params);
|
||||||
taxRegimesRouter(params);
|
taxRegimesRouter(params);
|
||||||
@ -50,6 +77,14 @@ export const catalogsAPIModule: IModuleServer = {
|
|||||||
label: this.name,
|
label: this.name,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warmup opcional (si lo necesitas en el futuro)
|
||||||
|
* ----------------------------------------------
|
||||||
|
* warmup(params) {
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
export default catalogsAPIModule;
|
export default catalogsAPIModule;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
export * from "./payment-terms";
|
export * from "./payment-terms";
|
||||||
|
export * from "./tax-definitions";
|
||||||
export * from "./tax-regimes";
|
export * from "./tax-regimes";
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./tax-definition-persistence-mappers.di";
|
||||||
|
export * from "./tax-definition-repositories.di";
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import {
|
||||||
|
SequelizeTaxDefinitionDomainMapper,
|
||||||
|
SequelizeTaxDefinitionSummaryMapper,
|
||||||
|
} from "../persistence";
|
||||||
|
|
||||||
|
export interface ITaxDefinitionPersistenceMappers {
|
||||||
|
domainMapper: SequelizeTaxDefinitionDomainMapper;
|
||||||
|
listMapper: SequelizeTaxDefinitionSummaryMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildTaxDefinitionPersistenceMappers = (): ITaxDefinitionPersistenceMappers => {
|
||||||
|
const domainMapper = new SequelizeTaxDefinitionDomainMapper();
|
||||||
|
const listMapper = new SequelizeTaxDefinitionSummaryMapper();
|
||||||
|
|
||||||
|
return { domainMapper, listMapper };
|
||||||
|
};
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
import { SequelizeTaxDefinitionRepository } from "../persistence";
|
||||||
|
|
||||||
|
import type { ITaxDefinitionPersistenceMappers } from "./tax-definition-persistence-mappers.di";
|
||||||
|
|
||||||
|
export const buildTaxDefinitionRepository = (params: {
|
||||||
|
database: Sequelize;
|
||||||
|
mappers: ITaxDefinitionPersistenceMappers;
|
||||||
|
}) => {
|
||||||
|
const { database, mappers } = params;
|
||||||
|
return new SequelizeTaxDefinitionRepository(mappers.domainMapper, mappers.listMapper, database);
|
||||||
|
};
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./di";
|
||||||
|
export * from "./persistence";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize";
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
export * from "./mappers";
|
||||||
|
export * from "./models";
|
||||||
|
export * from "./repositories";
|
||||||
|
|
||||||
|
import taxDefinitionModelInit from "./models/sequelize-tax-definition.model";
|
||||||
|
|
||||||
|
export const taxDefinitionModels = [taxDefinitionModelInit];
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./sequelize-tax-definition-domain.mapper";
|
||||||
|
export * from "./sequelize-tax-definition-summary.mapper";
|
||||||
@ -0,0 +1,157 @@
|
|||||||
|
import { SequelizeQueryMapper } from "@erp/core/api";
|
||||||
|
import {
|
||||||
|
TextValue,
|
||||||
|
UniqueID,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
maybeFromNullableResult,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TaxCalculationBehavior,
|
||||||
|
TaxDefinition,
|
||||||
|
TaxDefinitionCode as TaxDefinitionCodeVO,
|
||||||
|
TaxDefinitionName as TaxDefinitionNameVO,
|
||||||
|
TaxFamily,
|
||||||
|
TaxJurisdictionCountryCode,
|
||||||
|
TaxJurisdictionRegionCode,
|
||||||
|
TaxRate as TaxRateVO,
|
||||||
|
TaxScope,
|
||||||
|
} from "../../../../../domain";
|
||||||
|
import type { TaxDefinitionModel } from "../models";
|
||||||
|
|
||||||
|
export class SequelizeTaxDefinitionDomainMapper extends SequelizeQueryMapper<
|
||||||
|
TaxDefinitionModel,
|
||||||
|
TaxDefinition
|
||||||
|
> {
|
||||||
|
public mapToDomain(raw: TaxDefinitionModel): Result<TaxDefinition, 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 code = extractOrPushError(TaxDefinitionCodeVO.create(raw.code), "code", errors);
|
||||||
|
const name = extractOrPushError(TaxDefinitionNameVO.create(raw.name), "name", errors);
|
||||||
|
|
||||||
|
const description = maybeFromNullableResult(raw.description, (v) => TextValue.create(v));
|
||||||
|
|
||||||
|
const rate = extractOrPushError(
|
||||||
|
TaxRateVO.create({ value: raw.rate_value, scale: raw.rate_scale }),
|
||||||
|
"rate",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const taxFamily = extractOrPushError(TaxFamily.create(raw.tax_family), "tax_family", errors);
|
||||||
|
|
||||||
|
const calculationBehavior = extractOrPushError(
|
||||||
|
TaxCalculationBehavior.create(raw.calculation_behavior),
|
||||||
|
"calculation_behavior",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const jurisdictionCountryCode = extractOrPushError(
|
||||||
|
TaxJurisdictionCountryCode.create(raw.jurisdiction_country_code),
|
||||||
|
"jurisdiction_country_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const jurisdictionRegionCode = maybeFromNullableResult(raw.jurisdiction_region_code, (v) =>
|
||||||
|
TaxJurisdictionRegionCode.create(v)
|
||||||
|
);
|
||||||
|
|
||||||
|
const taxScope = extractOrPushError(TaxScope.create(raw.tax_scope), "tax_scope", errors);
|
||||||
|
|
||||||
|
const invoiceNote = maybeFromNullableResult(raw.invoice_note, (v) => TextValue.create(v));
|
||||||
|
|
||||||
|
const allowedSurchargeCodes = maybeFromNullableResult(raw.allowed_surcharge_codes, (v) => {
|
||||||
|
if (!Array.isArray(v)) return Result.fail(new Error("Invalid allowed_surcharge_codes"));
|
||||||
|
|
||||||
|
const arr: any[] = [];
|
||||||
|
for (const el of v) {
|
||||||
|
const r = TaxDefinitionCodeVO.create(String(el));
|
||||||
|
if (r.isFailure) return Result.fail(new Error("Invalid allowed_surcharge_codes element"));
|
||||||
|
arr.push(r.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(arr);
|
||||||
|
});
|
||||||
|
|
||||||
|
// valid from / to
|
||||||
|
const validFrom = maybeFromNullableResult(raw.valid_from, (v) => Result.ok(String(v)));
|
||||||
|
const validTo = maybeFromNullableResult(raw.valid_to, (v) => Result.ok(String(v)));
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("TaxDefinition mapping failed [mapToDomain]", errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainOrError = TaxDefinition.rehydrate(
|
||||||
|
{
|
||||||
|
companyId: companyId!,
|
||||||
|
code: code!,
|
||||||
|
name: name!,
|
||||||
|
description: description,
|
||||||
|
rate: rate!,
|
||||||
|
taxFamily: taxFamily!,
|
||||||
|
calculationBehavior: calculationBehavior!,
|
||||||
|
jurisdictionCountryCode: jurisdictionCountryCode!,
|
||||||
|
jurisdictionRegionCode: jurisdictionRegionCode,
|
||||||
|
taxScope: taxScope!,
|
||||||
|
invoiceNote: invoiceNote,
|
||||||
|
allowedSurchargeCodes: allowedSurchargeCodes,
|
||||||
|
isSystem: raw.is_system,
|
||||||
|
isActive: raw.is_active,
|
||||||
|
validFrom: validFrom,
|
||||||
|
validTo: validTo,
|
||||||
|
} as any,
|
||||||
|
id!
|
||||||
|
);
|
||||||
|
|
||||||
|
return Result.ok(domainOrError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToPersistence(domain: TaxDefinition): Result<Record<string, unknown>, Error> {
|
||||||
|
const dto: Record<string, unknown> = {
|
||||||
|
id: domain.id.toPrimitive(),
|
||||||
|
company_id: domain.companyId.toPrimitive(),
|
||||||
|
code: domain.code.toPrimitive(),
|
||||||
|
name: domain.name.toPrimitive(),
|
||||||
|
description: domain.description.match(
|
||||||
|
(v) => v.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
rate_value: domain.rate.toPrimitive(),
|
||||||
|
rate_scale: (domain.rate as any).scale ?? 2,
|
||||||
|
tax_family: domain.taxFamily.toPrimitive(),
|
||||||
|
calculation_behavior: domain.calculationBehavior.toPrimitive(),
|
||||||
|
jurisdiction_country_code: domain.jurisdictionCountryCode.toPrimitive(),
|
||||||
|
jurisdiction_region_code: domain.jurisdictionRegionCode.match(
|
||||||
|
(v) => v.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
tax_scope: domain.taxScope.toPrimitive(),
|
||||||
|
invoice_note: domain.invoiceNote.match(
|
||||||
|
(v) => v.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
allowed_surcharge_codes: domain.allowedSurchargeCodes.match(
|
||||||
|
(arr) => arr.map((c) => c.toPrimitive()),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
is_system: domain.isSystem,
|
||||||
|
is_active: domain.isActive,
|
||||||
|
valid_from: domain.validFrom.match(
|
||||||
|
(d) => d.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
valid_to: domain.validTo.match(
|
||||||
|
(d) => d.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import { type MapperParamsType, SequelizeQueryMapper } from "@erp/core/api";
|
||||||
|
import {
|
||||||
|
TextValue,
|
||||||
|
UniqueID,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
type ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { TaxDefinitionSummary } from "../../../../../application";
|
||||||
|
import { TaxDefinitionCode } from "../../../../../domain";
|
||||||
|
import type { TaxDefinitionModel } from "../models";
|
||||||
|
|
||||||
|
export class SequelizeTaxDefinitionSummaryMapper extends SequelizeQueryMapper<
|
||||||
|
TaxDefinitionModel,
|
||||||
|
TaxDefinitionSummary
|
||||||
|
> {
|
||||||
|
public mapToReadModel(
|
||||||
|
raw: TaxDefinitionModel,
|
||||||
|
_params?: MapperParamsType
|
||||||
|
): Result<TaxDefinitionSummary, 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 code = extractOrPushError(TaxDefinitionCode.create(raw.code), "code", errors);
|
||||||
|
const name = extractOrPushError(TextValue.create(raw.name), "name", errors);
|
||||||
|
|
||||||
|
const isActive = raw.is_active;
|
||||||
|
const isSystem = raw.is_system;
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("TaxDefinition mapping failed [mapToDTO]", errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<TaxDefinitionSummary>({
|
||||||
|
id: id!,
|
||||||
|
companyId: companyId!,
|
||||||
|
code: code!,
|
||||||
|
name: name!,
|
||||||
|
isActive,
|
||||||
|
isSystem,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,171 @@
|
|||||||
|
import {
|
||||||
|
type CreationOptional,
|
||||||
|
DataTypes,
|
||||||
|
type InferAttributes,
|
||||||
|
type InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
type Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
|
||||||
|
export type TaxDefinitionCreationAttributes = InferCreationAttributes<TaxDefinitionModel, {}> & {};
|
||||||
|
|
||||||
|
export class TaxDefinitionModel extends Model<
|
||||||
|
InferAttributes<TaxDefinitionModel>,
|
||||||
|
InferCreationAttributes<TaxDefinitionModel>
|
||||||
|
> {
|
||||||
|
declare id: string;
|
||||||
|
declare company_id: string;
|
||||||
|
declare code: string;
|
||||||
|
declare name: string;
|
||||||
|
declare description: CreationOptional<string | null>;
|
||||||
|
|
||||||
|
declare rate_value: number;
|
||||||
|
declare rate_scale: number;
|
||||||
|
|
||||||
|
declare tax_family: string;
|
||||||
|
declare calculation_behavior: string;
|
||||||
|
declare jurisdiction_country_code: string;
|
||||||
|
declare jurisdiction_region_code: CreationOptional<string | null>;
|
||||||
|
declare tax_scope: string;
|
||||||
|
|
||||||
|
declare invoice_note: CreationOptional<string | null>;
|
||||||
|
declare allowed_surcharge_codes: CreationOptional<object | null>;
|
||||||
|
|
||||||
|
declare is_system: boolean;
|
||||||
|
declare is_active: boolean;
|
||||||
|
|
||||||
|
declare valid_from: CreationOptional<string | null>;
|
||||||
|
declare valid_to: CreationOptional<string | null>;
|
||||||
|
|
||||||
|
static associate(_database: Sequelize) {}
|
||||||
|
|
||||||
|
static hooks(_database: Sequelize) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (database: Sequelize) => {
|
||||||
|
TaxDefinitionModel.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
company_id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
type: DataTypes.STRING(40),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
rate_value: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
rate_scale: {
|
||||||
|
type: DataTypes.SMALLINT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
tax_family: {
|
||||||
|
type: DataTypes.STRING(40),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
calculation_behavior: {
|
||||||
|
type: DataTypes.STRING(40),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
jurisdiction_country_code: {
|
||||||
|
type: DataTypes.STRING(8),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
jurisdiction_region_code: {
|
||||||
|
type: DataTypes.STRING(16),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
tax_scope: {
|
||||||
|
type: DataTypes.STRING(40),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
invoice_note: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
allowed_surcharge_codes: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
is_system: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
is_active: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
valid_from: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
valid_to: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize: database,
|
||||||
|
modelName: "TaxDefinitionModel",
|
||||||
|
tableName: "tax_definitions",
|
||||||
|
|
||||||
|
underscored: true,
|
||||||
|
paranoid: true,
|
||||||
|
timestamps: true,
|
||||||
|
|
||||||
|
createdAt: "created_at",
|
||||||
|
updatedAt: "updated_at",
|
||||||
|
deletedAt: "deleted_at",
|
||||||
|
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
name: "idx_tax_definitions_company_code",
|
||||||
|
fields: ["company_id", "code", "deleted_at"],
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
whereMergeStrategy: "and",
|
||||||
|
defaultScope: {},
|
||||||
|
scopes: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return TaxDefinitionModel;
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize-tax-definition.repository";
|
||||||
@ -0,0 +1,174 @@
|
|||||||
|
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 { ITaxDefinitionRepository, TaxDefinitionSummary } from "../../../../../application";
|
||||||
|
import type { TaxDefinition, TaxDefinitionCode } from "../../../../../domain";
|
||||||
|
import type {
|
||||||
|
SequelizeTaxDefinitionDomainMapper,
|
||||||
|
SequelizeTaxDefinitionSummaryMapper,
|
||||||
|
} from "../mappers";
|
||||||
|
import { TaxDefinitionModel } from "../models";
|
||||||
|
|
||||||
|
export class SequelizeTaxDefinitionRepository
|
||||||
|
extends SequelizeRepository<TaxDefinition>
|
||||||
|
implements ITaxDefinitionRepository
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly domainMapper: SequelizeTaxDefinitionDomainMapper,
|
||||||
|
private readonly summaryMapper: SequelizeTaxDefinitionSummaryMapper,
|
||||||
|
database: Sequelize
|
||||||
|
) {
|
||||||
|
super({ database });
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
taxDefinition: TaxDefinition,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<void, Error>> {
|
||||||
|
try {
|
||||||
|
const dtoResult = this.domainMapper.mapToPersistence(taxDefinition);
|
||||||
|
if (dtoResult.isFailure) return Result.fail(dtoResult.error);
|
||||||
|
|
||||||
|
await TaxDefinitionModel.create(dtoResult.data, { transaction });
|
||||||
|
return Result.ok();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
taxDefinition: TaxDefinition,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<void, Error>> {
|
||||||
|
try {
|
||||||
|
const dtoResult = this.domainMapper.mapToPersistence(taxDefinition);
|
||||||
|
if (dtoResult.isFailure) return Result.fail(dtoResult.error);
|
||||||
|
|
||||||
|
const { id, ...payload } = dtoResult.data as any;
|
||||||
|
const [affected] = await TaxDefinitionModel.update(payload, {
|
||||||
|
where: { id },
|
||||||
|
transaction,
|
||||||
|
individualHooks: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (affected === 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new InfrastructureRepositoryError("Concurrency conflict or tax definition 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 TaxDefinitionModel.count({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
return Result.ok(Boolean(count > 0));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<TaxDefinition, Error>> {
|
||||||
|
try {
|
||||||
|
const row = await TaxDefinitionModel.findOne({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
if (!row) return Result.fail(new EntityNotFoundError("TaxDefinition", "id", id.toString()));
|
||||||
|
|
||||||
|
return this.domainMapper.mapToDomain(row);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByCodeInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
code: TaxDefinitionCode,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<TaxDefinition, Error>> {
|
||||||
|
try {
|
||||||
|
const row = await TaxDefinitionModel.findOne({
|
||||||
|
where: { code: code.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
if (!row)
|
||||||
|
return Result.fail(new EntityNotFoundError("TaxDefinition", "code", code.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<TaxDefinitionSummary>, Error>> {
|
||||||
|
try {
|
||||||
|
const criteriaConverter = new CriteriaToSequelizeConverter();
|
||||||
|
const query = criteriaConverter.convert(criteria, {
|
||||||
|
mappings: {
|
||||||
|
isActive: "is_active",
|
||||||
|
},
|
||||||
|
searchableFields: ["code", "name", "description", "invoice_note"],
|
||||||
|
sortableFields: ["code", "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([
|
||||||
|
TaxDefinitionModel.findAll({ ...query, transaction }),
|
||||||
|
TaxDefinitionModel.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 TaxDefinitionModel.destroy({
|
||||||
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
if (deleted === 0)
|
||||||
|
return Result.fail(new EntityNotFoundError("TaxDefinition", "id", id.toString()));
|
||||||
|
return Result.ok(true);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,3 +4,4 @@ export {
|
|||||||
} from "./manifest";
|
} from "./manifest";
|
||||||
export * from "./payment-methods";
|
export * from "./payment-methods";
|
||||||
export * from "./payment-terms";
|
export * from "./payment-terms";
|
||||||
|
export * from "./tax-regimes";
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export const CatalogsModuleManifest: IModuleClient = {
|
|||||||
version: MODULE_VERSION,
|
version: MODULE_VERSION,
|
||||||
dependencies: ["auth", "Core"],
|
dependencies: ["auth", "Core"],
|
||||||
protected: true,
|
protected: true,
|
||||||
layout: "app",
|
layout: "app-sidebar",
|
||||||
|
|
||||||
routes: (params: ModuleClientParams) => {
|
routes: (params: ModuleClientParams) => {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
2
modules/catalogs/src/web/tax-regimes/index.ts
Normal file
2
modules/catalogs/src/web/tax-regimes/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./shared";
|
||||||
|
export * from "./utils";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./list-tax-regimes.adapter";
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import type { ListTaxRegimesResponseDTO } from "../../../../common";
|
||||||
|
import type { ListTaxRegimesResult } from "../api";
|
||||||
|
import type { TaxRegimeList, TaxRegimeListRow } from "../entities";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adaptador para transformar los datos de la API de ListTaxRegimesResult
|
||||||
|
* a la entidad TaxRegimeList utilizada en la aplicación.
|
||||||
|
* Reglas de adaptación:
|
||||||
|
* - page, per_page, total_pages, total_items se asignan directamente.
|
||||||
|
* - items se transforma utilizando TaxRegimeListRowAdapter para cada elemento.
|
||||||
|
*
|
||||||
|
* @param pageDto - lista de proformas desde la API.
|
||||||
|
* @param context - Contexto adicional opcional para la adaptación.
|
||||||
|
* @returns {TaxRegimeList} Objeto adaptado a PaymentMehodList.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const ListTaxRegimesAdapter = {
|
||||||
|
fromDto(dto: ListTaxRegimesResult, context?: unknown): TaxRegimeList {
|
||||||
|
return {
|
||||||
|
page: dto.page,
|
||||||
|
perPage: dto.per_page,
|
||||||
|
totalPages: dto.total_pages,
|
||||||
|
totalItems: dto.total_items,
|
||||||
|
items: dto.items.map((rowDto) => TaxRegimeListRowAdapter.fromDto(rowDto, context)),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adaptador para transformar los items de la API de ListPaymentMehodsResult a la entidad PaymentMehodListRow.
|
||||||
|
* Reglas de adaptación:
|
||||||
|
* - id, company_id se asignan directamente.
|
||||||
|
*
|
||||||
|
* @param rowDto - item de proforma desde la API.
|
||||||
|
* @param context - Contexto adicional opcional para la adaptación.
|
||||||
|
* @returns {TaxRegimeListRow} Objeto adaptado a PaymentMehodListRow.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ListTaxRegimesItemOutput = ListTaxRegimesResponseDTO["items"][number];
|
||||||
|
|
||||||
|
const TaxRegimeListRowAdapter = {
|
||||||
|
fromDto(dto: ListTaxRegimesItemOutput, context?: unknown): TaxRegimeListRow {
|
||||||
|
return {
|
||||||
|
id: dto.id,
|
||||||
|
companyId: dto.company_id,
|
||||||
|
|
||||||
|
code: dto.code,
|
||||||
|
description: dto.description,
|
||||||
|
|
||||||
|
isSystem: dto.is_system,
|
||||||
|
isActive: dto.is_active,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
1
modules/catalogs/src/web/tax-regimes/shared/api/index.ts
Normal file
1
modules/catalogs/src/web/tax-regimes/shared/api/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./list-tax-regimes-by-criteria.api";
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import type { CriteriaDTO } from "@erp/core";
|
||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { ListTaxRegimesResponseDTO } from "../../../../common";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recupera una lista de regímenes fiscales del sistema utilizando la
|
||||||
|
* fuente de datos proporcionada y los criterios de búsqueda especificados.
|
||||||
|
*
|
||||||
|
* @param dataSource - La fuente de datos para interactuar con la API.
|
||||||
|
* @param params - Los parámetros necesarios para listar los regímenes fiscales, incluyendo los criterios de búsqueda.
|
||||||
|
* @returns Una promesa que resuelve con una lista de regímenes fiscales que cumplen con los criterios especificados.
|
||||||
|
* @throws Error si la recuperación de la lista de regímenes fiscales falla.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ListTaxRegimesByCriteriaParams = {
|
||||||
|
criteria?: CriteriaDTO;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListTaxRegimesResult = ListTaxRegimesResponseDTO;
|
||||||
|
|
||||||
|
export function getListTaxRegimesByCriteria(
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: ListTaxRegimesByCriteriaParams
|
||||||
|
): Promise<ListTaxRegimesResult> {
|
||||||
|
const { criteria, signal } = params || {
|
||||||
|
criteria: {
|
||||||
|
page: 1,
|
||||||
|
per_page: 9999,
|
||||||
|
},
|
||||||
|
signal: undefined,
|
||||||
|
};
|
||||||
|
return dataSource.getList<ListTaxRegimesResponseDTO>("catalogs/tax-regimes", {
|
||||||
|
signal,
|
||||||
|
...criteria,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./tax-regime.entity";
|
||||||
|
export * from "./tax-regime-list.entity";
|
||||||
|
export * from "./tax-regime-list-row.entity";
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Interface que representa una fila de la lista de
|
||||||
|
* regímenes fiscales en el sistema, adaptada desde la respuesta de la API.
|
||||||
|
* Contiene los campos justos para mostrar
|
||||||
|
* la información básica de cada régimen fiscal en la lista.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface TaxRegimeListRow {
|
||||||
|
id: string;
|
||||||
|
companyId: string;
|
||||||
|
|
||||||
|
code: string;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
isSystem: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import type { TaxRegimeListRow } from "./tax-regime-list-row.entity";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface que representa la respuesta paginada de una lista de regímenes fiscales,
|
||||||
|
* adaptada desde la respuesta de la API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface TaxRegimeList {
|
||||||
|
items: TaxRegimeListRow[];
|
||||||
|
totalPages: number;
|
||||||
|
totalItems: number;
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
export interface TaxRegime {
|
||||||
|
id: string;
|
||||||
|
companyId: string;
|
||||||
|
|
||||||
|
code: string;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
isSystem: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./use-tax-regime-list-query";
|
||||||
44
modules/catalogs/src/web/tax-regimes/shared/hooks/keys.ts
Normal file
44
modules/catalogs/src/web/tax-regimes/shared/hooks/keys.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import type { QueryKey } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import type { ListTaxRegimesRequestDTO } from "../../../../common";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefijo base para listados
|
||||||
|
*/
|
||||||
|
export const LIST_TAX_REGIMES_QUERY_KEY_PREFIX = ["tax-regimes"] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query key para listado de tax regimes
|
||||||
|
*/
|
||||||
|
export const LIST_TAX_REGIMES_QUERY_KEY = (criteria?: ListTaxRegimesRequestDTO): QueryKey =>
|
||||||
|
[
|
||||||
|
...LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||||
|
{
|
||||||
|
pageNumber: criteria?.pageNumber ?? 1,
|
||||||
|
pageSize: criteria?.pageSize ?? 5,
|
||||||
|
q: criteria?.q ?? "",
|
||||||
|
filters: criteria?.filters ?? [],
|
||||||
|
orderBy: criteria?.orderBy ?? "",
|
||||||
|
order: criteria?.order ?? "",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query key para detalle de tax regime
|
||||||
|
*/
|
||||||
|
export const TAX_REGIMES_DETAIL_QUERY_KEY_PREFIX = ["tax-regimes:detail"] as const;
|
||||||
|
export const TAX_REGIME_QUERY_KEY = (taxRegimeId?: string): QueryKey => [
|
||||||
|
...TAX_REGIMES_DETAIL_QUERY_KEY_PREFIX,
|
||||||
|
{ taxRegimeId },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keys para mutaciones
|
||||||
|
*/
|
||||||
|
export const CREATE_TAX_REGIME_MUTATION_KEY = ["tax-regimes:create"] as const;
|
||||||
|
export const UPDATE_TAX_REGIME_MUTATION_KEY = ["tax-regimes:update"] as const;
|
||||||
|
export const DELETE_TAX_REGIME_MUTATION_KEY = ["tax-regimes:delete"] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operaciones de dominio
|
||||||
|
*/
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import type { CriteriaDTO } from "@erp/core";
|
||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { ListTaxRegimesAdapter } from "../adapters";
|
||||||
|
import { getListTaxRegimesByCriteria } from "../api";
|
||||||
|
import type { TaxRegimeList } from "../entities";
|
||||||
|
|
||||||
|
import { LIST_TAX_REGIMES_QUERY_KEY } from "./keys";
|
||||||
|
|
||||||
|
export interface TaxRegimesListQueryOptions {
|
||||||
|
enabled?: boolean;
|
||||||
|
criteria?: Partial<CriteriaDTO>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTaxRegimesListQuery = (
|
||||||
|
options?: TaxRegimesListQueryOptions
|
||||||
|
): UseQueryResult<TaxRegimeList, DefaultError> => {
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const enabled = options?.enabled ?? true;
|
||||||
|
const criteria = options?.criteria ?? {};
|
||||||
|
|
||||||
|
return useQuery<TaxRegimeList, DefaultError>({
|
||||||
|
queryKey: LIST_TAX_REGIMES_QUERY_KEY(criteria),
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const dto = await getListTaxRegimesByCriteria(dataSource, { signal, criteria });
|
||||||
|
return ListTaxRegimesAdapter.fromDto(dto);
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
placeholderData: (previousData) => previousData, // Mantiene la página anterior durante refetch por cambio de criteria
|
||||||
|
});
|
||||||
|
};
|
||||||
2
modules/catalogs/src/web/tax-regimes/shared/index.ts
Normal file
2
modules/catalogs/src/web/tax-regimes/shared/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./entities";
|
||||||
|
export * from "./hooks";
|
||||||
1
modules/catalogs/src/web/tax-regimes/utils/index.ts
Normal file
1
modules/catalogs/src/web/tax-regimes/utils/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./tax-regime-options.utils";
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { SelectFieldItem } from "@repo/rdx-ui/components";
|
||||||
|
|
||||||
|
import type { TaxRegimeListRow } from "../shared";
|
||||||
|
|
||||||
|
export const getTaxRegimeOptions = (taxRegimes: TaxRegimeListRow[]): SelectFieldItem[] => {
|
||||||
|
return taxRegimes.map((taxRegime) => ({
|
||||||
|
value: taxRegime.id,
|
||||||
|
label: taxRegime.description,
|
||||||
|
}));
|
||||||
|
};
|
||||||
@ -180,6 +180,14 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.tax_regime_code).ifSet((taxRegimeCode) => {
|
||||||
|
proformaPatchProps.taxRegimeCode = extractOrPushError(
|
||||||
|
maybeFromNullableResult(taxRegimeCode, (value) => Result.ok(String(value))),
|
||||||
|
"tax_regime_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
if (dto.items !== undefined) {
|
if (dto.items !== undefined) {
|
||||||
proformaPatchProps.items = this.mapItemsProps(dto.items, { errors });
|
proformaPatchProps.items = this.mapItemsProps(dto.items, { errors });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export interface IProformaCreateProps {
|
|||||||
linkedInvoiceId: Maybe<UniqueID>;
|
linkedInvoiceId: Maybe<UniqueID>;
|
||||||
|
|
||||||
paymentMethodId: Maybe<UniqueID>;
|
paymentMethodId: Maybe<UniqueID>;
|
||||||
|
taxRegimeCode: Maybe<string>;
|
||||||
|
|
||||||
items: IProformaItemCreateProps[];
|
items: IProformaItemCreateProps[];
|
||||||
globalDiscountPercentage: DiscountPercentage;
|
globalDiscountPercentage: DiscountPercentage;
|
||||||
@ -100,6 +101,7 @@ export interface IProforma {
|
|||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
paymentMethodId: Maybe<UniqueID>;
|
paymentMethodId: Maybe<UniqueID>;
|
||||||
|
taxRegimeCode: Maybe<string>;
|
||||||
|
|
||||||
linkedInvoiceId: Maybe<UniqueID>;
|
linkedInvoiceId: Maybe<UniqueID>;
|
||||||
|
|
||||||
@ -262,6 +264,10 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return this.props.paymentMethodId;
|
return this.props.paymentMethodId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get taxRegimeCode(): Maybe<string> {
|
||||||
|
return this.props.taxRegimeCode;
|
||||||
|
}
|
||||||
|
|
||||||
public get linkedInvoiceId(): Maybe<UniqueID> {
|
public get linkedInvoiceId(): Maybe<UniqueID> {
|
||||||
return this.props.linkedInvoiceId;
|
return this.props.linkedInvoiceId;
|
||||||
}
|
}
|
||||||
@ -290,6 +296,10 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return this.paymentMethodId.isSome();
|
return this.paymentMethodId.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get hasTaxRegime() {
|
||||||
|
return this.taxRegimeCode.isSome();
|
||||||
|
}
|
||||||
|
|
||||||
public issue(): Result<void, Error> {
|
public issue(): Result<void, Error> {
|
||||||
// Antes de cambiar el estado de la proforma,
|
// Antes de cambiar el estado de la proforma,
|
||||||
// comprobamos que se cumplen las condiciones
|
// comprobamos que se cumplen las condiciones
|
||||||
|
|||||||
@ -64,6 +64,10 @@ export class CustomerInvoiceModel extends Model<
|
|||||||
declare payment_method_id: CreationOptional<string | null>;
|
declare payment_method_id: CreationOptional<string | null>;
|
||||||
declare payment_method_description: CreationOptional<string | null>;
|
declare payment_method_description: CreationOptional<string | null>;
|
||||||
|
|
||||||
|
// Tax regime
|
||||||
|
declare tax_regime_code: CreationOptional<string | null>;
|
||||||
|
declare tax_regime_description: CreationOptional<string | null>;
|
||||||
|
|
||||||
// Subtotal
|
// Subtotal
|
||||||
declare subtotal_amount_value: number;
|
declare subtotal_amount_value: number;
|
||||||
declare subtotal_amount_scale: number;
|
declare subtotal_amount_scale: number;
|
||||||
@ -310,6 +314,16 @@ export default (database: Sequelize) => {
|
|||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
tax_regime_code: {
|
||||||
|
type: DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
tax_regime_description: {
|
||||||
|
type: new DataTypes.STRING(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
|
||||||
subtotal_amount_value: {
|
subtotal_amount_value: {
|
||||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { type ModuleParams, buildCatalogs, buildTransactionManager } from "@erp/core/api";
|
import type { ICatalogPublicServices } from "@erp/catalogs/api";
|
||||||
|
import { type ModuleParams, buildTransactionManager } from "@erp/core/api";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type ChangeStatusProformaUseCase,
|
type ChangeStatusProformaUseCase,
|
||||||
@ -50,11 +51,11 @@ export type ProformasInternalDeps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function buildProformasDependencies(params: ModuleParams): ProformasInternalDeps {
|
export function buildProformasDependencies(params: ModuleParams): ProformasInternalDeps {
|
||||||
const { database } = params;
|
const { database, getService } = params;
|
||||||
|
const catalogs = getService<ICatalogPublicServices>("catalogs");
|
||||||
|
|
||||||
// Infrastructure
|
// Infrastructure
|
||||||
const transactionManager = buildTransactionManager(database);
|
const transactionManager = buildTransactionManager(database);
|
||||||
const catalogs = buildCatalogs();
|
|
||||||
const persistenceMappers = buildProformaPersistenceMappers(catalogs);
|
const persistenceMappers = buildProformaPersistenceMappers(catalogs);
|
||||||
|
|
||||||
const repository = buildProformaRepository({ database, mappers: persistenceMappers });
|
const repository = buildProformaRepository({ database, mappers: persistenceMappers });
|
||||||
|
|||||||
@ -1,260 +0,0 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
|
||||||
import { DiscountPercentage } from "@erp/core/api";
|
|
||||||
import {
|
|
||||||
CurrencyCode,
|
|
||||||
DomainError,
|
|
||||||
LanguageCode,
|
|
||||||
Percentage,
|
|
||||||
TextValue,
|
|
||||||
UniqueID,
|
|
||||||
UtcDate,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../../common";
|
|
||||||
import {
|
|
||||||
type IProformaItemCreateProps,
|
|
||||||
InvoiceNumber,
|
|
||||||
InvoicePaymentMethod,
|
|
||||||
type InvoiceRecipient,
|
|
||||||
InvoiceSerie,
|
|
||||||
InvoiceStatus,
|
|
||||||
IssuedInvoiceItem,
|
|
||||||
type IssuedInvoiceItemProps,
|
|
||||||
ItemAmount,
|
|
||||||
ItemDescription,
|
|
||||||
ItemQuantity,
|
|
||||||
type ProformaCreateProps,
|
|
||||||
} from "../../../../domain";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CreateProformaPropsMapper
|
|
||||||
* Convierte el DTO a las props validadas (CustomerProps).
|
|
||||||
* No construye directamente el agregado.
|
|
||||||
*
|
|
||||||
* @param dto - DTO con los datos de la factura de cliente
|
|
||||||
* @returns
|
|
||||||
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class CreateProformaRequestMapper {
|
|
||||||
private readonly taxCatalog: JsonTaxCatalogProvider;
|
|
||||||
private errors: ValidationErrorDetail[] = [];
|
|
||||||
private languageCode?: LanguageCode;
|
|
||||||
private currencyCode?: CurrencyCode;
|
|
||||||
|
|
||||||
constructor(params: { taxCatalog: JsonTaxCatalogProvider }) {
|
|
||||||
this.taxCatalog = params.taxCatalog;
|
|
||||||
this.errors = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public map(dto: CreateProformaRequestDTO, params: { companyId: UniqueID }) {
|
|
||||||
const { companyId } = params;
|
|
||||||
try {
|
|
||||||
this.errors = [];
|
|
||||||
|
|
||||||
const defaultStatus = InvoiceStatus.draft();
|
|
||||||
|
|
||||||
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
|
|
||||||
|
|
||||||
const customerId = extractOrPushError(
|
|
||||||
UniqueID.create(dto.customer_id),
|
|
||||||
"customer_id",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const recipient = Maybe.none<InvoiceRecipient>();
|
|
||||||
|
|
||||||
const proformaNumber = extractOrPushError(
|
|
||||||
InvoiceNumber.create(dto.invoice_number),
|
|
||||||
"invoice_number",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const series = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.series, (value) => InvoiceSerie.create(value)),
|
|
||||||
"series",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const invoiceDate = extractOrPushError(
|
|
||||||
UtcDate.createFromISO(dto.invoice_date),
|
|
||||||
"invoice_date",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const operationDate = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.operation_date, (value) => UtcDate.createFromISO(value)),
|
|
||||||
"operation_date",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const reference = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.reference, (value) => Result.ok(String(value))),
|
|
||||||
"reference",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const description = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.reference, (value) => Result.ok(String(value))),
|
|
||||||
"description",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const notes = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.notes, (value) => TextValue.create(value)),
|
|
||||||
"notes",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
this.languageCode = extractOrPushError(
|
|
||||||
LanguageCode.create(dto.language_code),
|
|
||||||
"language_code",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
this.currencyCode = extractOrPushError(
|
|
||||||
CurrencyCode.create(dto.currency_code),
|
|
||||||
"currency_code",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const paymentMethod = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.payment_method, (value) =>
|
|
||||||
InvoicePaymentMethod.create({ paymentDescription: value })
|
|
||||||
),
|
|
||||||
"payment_method",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const globalDiscountPercentage = extractOrPushError(
|
|
||||||
Percentage.create({
|
|
||||||
value: Number(dto.global_discount_percentage.value),
|
|
||||||
scale: Number(dto.global_discount_percentage.scale),
|
|
||||||
}),
|
|
||||||
"discount_percentage",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const items = this.mapItems(dto.items);
|
|
||||||
|
|
||||||
if (this.errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice props mapping failed", this.errors)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const proformaProps: Omit<ProformaCreateProps, "items"> & { items: IProformaItemCreateProps[] } = {
|
|
||||||
companyId,
|
|
||||||
status: defaultStatus!,
|
|
||||||
|
|
||||||
invoiceNumber: proformaNumber!,
|
|
||||||
series: series!,
|
|
||||||
|
|
||||||
invoiceDate: invoiceDate!,
|
|
||||||
operationDate: operationDate!,
|
|
||||||
|
|
||||||
customerId: customerId!,
|
|
||||||
recipient: recipient!,
|
|
||||||
|
|
||||||
reference: reference!,
|
|
||||||
description: description!,
|
|
||||||
notes: notes!,
|
|
||||||
|
|
||||||
languageCode: this.languageCode!,
|
|
||||||
currencyCode: this.currencyCode!,
|
|
||||||
|
|
||||||
paymentMethod: paymentMethod!,
|
|
||||||
|
|
||||||
globalDiscountPercentage: globalDiscountPercentage!,
|
|
||||||
|
|
||||||
items:
|
|
||||||
};
|
|
||||||
|
|
||||||
return Result.ok({ id: proformaId!, props: proformaProps });
|
|
||||||
} catch (err: unknown) {
|
|
||||||
return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private mapItems(items: CreateProformaItemRequestDTO[]): IProformaItemCreateProps[] {
|
|
||||||
const proformaItems = CustomerInvoiceItems.create({
|
|
||||||
currencyCode: this.currencyCode!,
|
|
||||||
languageCode: this.languageCode!,
|
|
||||||
items: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
items.forEach((item, index) => {
|
|
||||||
const description = extractOrPushError(
|
|
||||||
maybeFromNullableResult(item.description, (value) => ItemDescription.create(value)),
|
|
||||||
"description",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const quantity = extractOrPushError(
|
|
||||||
maybeFromNullableResult(item.quantity, (value) => ItemQuantity.create(value)),
|
|
||||||
"quantity",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const unitAmount = extractOrPushError(
|
|
||||||
maybeFromNullableResult(item.unit_amount, (value) => ItemAmount.create(value)),
|
|
||||||
"unit_amount",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const discountPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableResult(item.item_discount_percentage, (value) =>
|
|
||||||
DiscountPercentage.create(value)
|
|
||||||
),
|
|
||||||
"discount_percentage",
|
|
||||||
this.errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxes = this.mapTaxes(item, index);
|
|
||||||
|
|
||||||
const itemProps: IssuedInvoiceItemProps = {
|
|
||||||
currencyCode: this.currencyCode!,
|
|
||||||
languageCode: this.languageCode!,
|
|
||||||
description: description!,
|
|
||||||
quantity: quantity!,
|
|
||||||
unitAmount: unitAmount!,
|
|
||||||
itemDiscountPercentage: discountPercentage!,
|
|
||||||
taxes: taxes,
|
|
||||||
};
|
|
||||||
|
|
||||||
const itemResult = IssuedInvoiceItem.create(itemProps);
|
|
||||||
if (itemResult.isSuccess) {
|
|
||||||
proformaItems.add(itemResult.data);
|
|
||||||
} else {
|
|
||||||
this.errors.push({
|
|
||||||
path: `items[${index}]`,
|
|
||||||
message: itemResult.error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return proformaItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
private mapTaxes(item: CreateProformaItemRequestDTO, itemIndex: number) {
|
|
||||||
const taxes = ItemTaxes.create([]);
|
|
||||||
|
|
||||||
item.taxes.split(",").forEach((tax_code, taxIndex) => {
|
|
||||||
const taxResult = Tax.createFromCode(tax_code, this.taxCatalog);
|
|
||||||
if (taxResult.isSuccess) {
|
|
||||||
taxes.add(taxResult.data);
|
|
||||||
} else {
|
|
||||||
this.errors.push({
|
|
||||||
path: `items[${itemIndex}].taxes[${taxIndex}]`,
|
|
||||||
message: taxResult.error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return taxes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth/api";
|
import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth/api";
|
||||||
|
import type { ICatalogPublicServices } from "@erp/catalogs/api";
|
||||||
import { type RequestWithAuth, type StartParams, validateRequest } from "@erp/core/api";
|
import { type RequestWithAuth, type StartParams, validateRequest } from "@erp/core/api";
|
||||||
import { type NextFunction, type Request, type Response, Router } from "express";
|
import { type NextFunction, type Request, type Response, Router } from "express";
|
||||||
|
|
||||||
@ -34,9 +35,11 @@ export const proformasRouter = (params: StartParams) => {
|
|||||||
const deps = getInternal<ProformasInternalDeps>("customer-invoices", "proformas");
|
const deps = getInternal<ProformasInternalDeps>("customer-invoices", "proformas");
|
||||||
|
|
||||||
const issuedInvoicesServices = getService<IIssuedInvoicePublicServices>("self:issuedInvoices");
|
const issuedInvoicesServices = getService<IIssuedInvoicePublicServices>("self:issuedInvoices");
|
||||||
|
const catalogServices = getService<ICatalogPublicServices>("self:catalogs");
|
||||||
|
|
||||||
const publicServices = {
|
const publicServices = {
|
||||||
issuedInvoiceServices: issuedInvoicesServices,
|
issuedInvoiceServices: issuedInvoicesServices,
|
||||||
|
catalogServices: catalogServices,
|
||||||
};
|
};
|
||||||
|
|
||||||
const router: Router = Router({ mergeParams: true });
|
const router: Router = Router({ mergeParams: true });
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { JsonPaymentCatalogProvider } from "@erp/core";
|
import type { IPaymentMethodPublicServices, ITaxRegimePublicServices } from "@erp/catalogs/api";
|
||||||
import { DiscountPercentage, type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
import { DiscountPercentage, type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
@ -40,18 +40,21 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
private _recipientMapper: SequelizeProformaRecipientDomainMapper;
|
private _recipientMapper: SequelizeProformaRecipientDomainMapper;
|
||||||
private _taxesMapper: SequelizeProformaTaxesDomainMapper;
|
private _taxesMapper: SequelizeProformaTaxesDomainMapper;
|
||||||
|
|
||||||
private _paymentCatalog: JsonPaymentCatalogProvider;
|
private _paymentMethodCatalog: IPaymentMethodPublicServices;
|
||||||
|
private _taxRegimeCatalog: ITaxRegimePublicServices;
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
constructor(params: MapperParamsType) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const { paymentCatalog } = params as {
|
const { paymentCatalog, taxRegimeCatalog } = params as {
|
||||||
paymentCatalog: JsonPaymentCatalogProvider;
|
paymentCatalog: IPaymentMethodPublicServices;
|
||||||
|
taxRegimeCatalog: ITaxRegimePublicServices;
|
||||||
};
|
};
|
||||||
|
|
||||||
this._paymentCatalog = paymentCatalog;
|
this._paymentMethodCatalog = paymentCatalog;
|
||||||
|
this._taxRegimeCatalog = taxRegimeCatalog;
|
||||||
|
|
||||||
if (!this._paymentCatalog) {
|
if (!this._paymentMethodCatalog) {
|
||||||
throw new Error('paymentCatalog not defined ("SequelizeProformaDomainMapper")');
|
throw new Error('paymentCatalog not defined ("SequelizeProformaDomainMapper")');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +171,13 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Tax regime code
|
||||||
|
const taxRegimeCode = extractOrPushError(
|
||||||
|
maybeFromNullableResult(raw.tax_regime_code, (value) => Result.ok(String(value))),
|
||||||
|
"tax_regime_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
// % descuento global (VO)
|
// % descuento global (VO)
|
||||||
const globalDiscountPercentage = extractOrPushError(
|
const globalDiscountPercentage = extractOrPushError(
|
||||||
DiscountPercentage.create({
|
DiscountPercentage.create({
|
||||||
@ -198,6 +208,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
languageCode,
|
languageCode,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
paymentMethodId,
|
paymentMethodId,
|
||||||
|
taxRegimeCode,
|
||||||
|
|
||||||
globalDiscountPercentage,
|
globalDiscountPercentage,
|
||||||
linkedInvoiceId,
|
linkedInvoiceId,
|
||||||
@ -267,6 +278,8 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
paymentMethodId: attributes.paymentMethodId!,
|
paymentMethodId: attributes.paymentMethodId!,
|
||||||
|
|
||||||
|
taxRegimeCode: attributes.taxRegimeCode!,
|
||||||
|
|
||||||
linkedInvoiceId: attributes.linkedInvoiceId!, // El id de la factura emitida (linked_invoice) se asigna al hacer issue() desde la proforma, no viene en el modelo de persistencia.
|
linkedInvoiceId: attributes.linkedInvoiceId!, // El id de la factura emitida (linked_invoice) se asigna al hacer issue() desde la proforma, no viene en el modelo de persistencia.
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -328,7 +341,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
if (source.hasPaymentMethod) {
|
if (source.hasPaymentMethod) {
|
||||||
const paymentId = source.paymentMethodId.unwrap();
|
const paymentId = source.paymentMethodId.unwrap();
|
||||||
const paymentOrNot = this._paymentCatalog.findById(paymentId.toString());
|
const paymentOrNot = this._paymentMethodCatalog.findById(paymentId.toString());
|
||||||
|
|
||||||
if (paymentOrNot.isSome()) {
|
if (paymentOrNot.isSome()) {
|
||||||
const paymentItem = paymentOrNot.unwrap();
|
const paymentItem = paymentOrNot.unwrap();
|
||||||
@ -340,7 +353,27 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5) Si hubo errores de mapeo, devolvemos colección de validación
|
// 5) Tax regime
|
||||||
|
let taxRegime: {
|
||||||
|
code: string | null;
|
||||||
|
description: string | null;
|
||||||
|
} = { code: null, description: null };
|
||||||
|
|
||||||
|
if (source.hasTaxRegime) {
|
||||||
|
const taxRegimeCode = source.taxRegimeCode.unwrap();
|
||||||
|
const taxRegimeOrNot = this._taxRegimeCatalog.findBy(taxRegimeCode.toString());
|
||||||
|
|
||||||
|
if (taxRegimeOrNot.isSome()) {
|
||||||
|
const taxRegimeItem = taxRegimeOrNot.unwrap();
|
||||||
|
|
||||||
|
taxRegime = {
|
||||||
|
code: taxRegimeItem.code ?? null,
|
||||||
|
description: taxRegimeItem.description ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6) Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
|
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
|
||||||
@ -376,6 +409,9 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
payment_method_id: payment.id,
|
payment_method_id: payment.id,
|
||||||
payment_method_description: payment.description,
|
payment_method_description: payment.description,
|
||||||
|
|
||||||
|
tax_regime_code: taxRegime.code,
|
||||||
|
tax_regime_description: taxRegime.description,
|
||||||
|
|
||||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export const UpdateProformaByIdRequestSchema = z.object({
|
|||||||
|
|
||||||
payment_method_id: z.uuid().nullable().optional(),
|
payment_method_id: z.uuid().nullable().optional(),
|
||||||
payment_term_id: z.uuid().nullable().optional(),
|
payment_term_id: z.uuid().nullable().optional(),
|
||||||
|
tax_regime_code: z.string().nullable().optional(),
|
||||||
|
|
||||||
// retención como código??? retencion_15
|
// retención como código??? retencion_15
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
IssuedInvoiceRecipientSummarySchema,
|
IssuedInvoiceRecipientSummarySchema,
|
||||||
IssuedInvoiceStatusSchema,
|
IssuedInvoiceStatusSchema,
|
||||||
PaymentMethodRefSchema,
|
PaymentMethodRefSchema,
|
||||||
|
TaxRegimeRefSchema,
|
||||||
TaxesBreakdownSchema,
|
TaxesBreakdownSchema,
|
||||||
VerifactuRecordSchema,
|
VerifactuRecordSchema,
|
||||||
} from "../../shared";
|
} from "../../shared";
|
||||||
@ -45,6 +46,8 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
|||||||
|
|
||||||
payment_method: PaymentMethodRefSchema.nullable(),
|
payment_method: PaymentMethodRefSchema.nullable(),
|
||||||
|
|
||||||
|
tax_regime: TaxRegimeRefSchema.nullable(),
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
|
|
||||||
items_discount_amount: MoneySchema,
|
items_discount_amount: MoneySchema,
|
||||||
|
|||||||
@ -8,7 +8,12 @@ import {
|
|||||||
} from "@erp/core";
|
} from "@erp/core";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { PaymentMethodRefSchema, PaymentTermRefSchema, TaxesBreakdownSchema } from "../../shared";
|
import {
|
||||||
|
PaymentMethodRefSchema,
|
||||||
|
PaymentTermRefSchema,
|
||||||
|
TaxRegimeRefSchema,
|
||||||
|
TaxesBreakdownSchema,
|
||||||
|
} from "../../shared";
|
||||||
import {
|
import {
|
||||||
ProformaItemDetailSchema,
|
ProformaItemDetailSchema,
|
||||||
ProformaRecipientSummarySchema,
|
ProformaRecipientSummarySchema,
|
||||||
@ -43,6 +48,8 @@ export const GetProformaByIdResponseSchema = z.object({
|
|||||||
payment_method: PaymentMethodRefSchema.nullable(),
|
payment_method: PaymentMethodRefSchema.nullable(),
|
||||||
payment_term: PaymentTermRefSchema.nullable(),
|
payment_term: PaymentTermRefSchema.nullable(),
|
||||||
|
|
||||||
|
tax_regime: TaxRegimeRefSchema.nullable(),
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
items_discount_amount: MoneySchema,
|
items_discount_amount: MoneySchema,
|
||||||
global_discount_percentage: PercentageSchema,
|
global_discount_percentage: PercentageSchema,
|
||||||
|
|||||||
@ -4,4 +4,5 @@ export * from "./payment-method-ref.dto";
|
|||||||
export * from "./payment-term-ref.dto";
|
export * from "./payment-term-ref.dto";
|
||||||
export * from "./proforma";
|
export * from "./proforma";
|
||||||
export * from "./tax-combination-code.dto";
|
export * from "./tax-combination-code.dto";
|
||||||
|
export * from "./tax-regime-ref.dto";
|
||||||
export * from "./taxes-breakdown.dto";
|
export * from "./taxes-breakdown.dto";
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const TaxRegimeRefSchema = z.object({
|
||||||
|
code: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TaxRegimeRefDTO = z.infer<typeof TaxRegimeRefSchema>;
|
||||||
@ -46,6 +46,8 @@ export const GetProformaByIdAdapter = {
|
|||||||
paymentMethodId: dto.payment_method?.id ?? null,
|
paymentMethodId: dto.payment_method?.id ?? null,
|
||||||
paymentTermId: dto.payment_term?.id ?? null,
|
paymentTermId: dto.payment_term?.id ?? null,
|
||||||
|
|
||||||
|
taxRegimeCode: dto.tax_regime?.code ?? null,
|
||||||
|
|
||||||
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
|
subtotalAmount: MoneyDTOHelper.toNumber(dto.subtotal_amount),
|
||||||
|
|
||||||
itemsDiscountAmount: MoneyDTOHelper.toNumber(dto.items_discount_amount),
|
itemsDiscountAmount: MoneyDTOHelper.toNumber(dto.items_discount_amount),
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export interface Proforma {
|
|||||||
customerId: string;
|
customerId: string;
|
||||||
recipient: ProformaRecipient;
|
recipient: ProformaRecipient;
|
||||||
|
|
||||||
|
taxRegimeCode: string | null;
|
||||||
taxes: ProformaTaxSummary[];
|
taxes: ProformaTaxSummary[];
|
||||||
|
|
||||||
paymentMethodId: string | null;
|
paymentMethodId: string | null;
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpd
|
|||||||
proforma.globalDiscountPercentage ?? proformaDefaults.globalDiscountPercentage,
|
proforma.globalDiscountPercentage ?? proformaDefaults.globalDiscountPercentage,
|
||||||
|
|
||||||
taxMode: fiscalDefaults.taxMode,
|
taxMode: fiscalDefaults.taxMode,
|
||||||
taxRegimeCode: "01", //taxRegimeCode: proforma.taxRegimeCode ?? proformaDefaults.taxRegimeCode, // TODO: implementar en API
|
taxRegimeCode: proforma.taxRegimeCode ?? proformaDefaults.taxRegimeCode,
|
||||||
|
|
||||||
hasTaxPercentage:
|
hasTaxPercentage:
|
||||||
fiscalDefaults.taxMode === "single" && fiscalDefaults.defaultTaxPercentage !== null,
|
fiscalDefaults.taxMode === "single" && fiscalDefaults.defaultTaxPercentage !== null,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useRef } from "react";
|
import { getTaxRegimeOptions, useTaxRegimesListQuery } from "@erp/catalogs/client/tax-regimes";
|
||||||
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { type UseFormReturn, useWatch } from "react-hook-form";
|
import { type UseFormReturn, useWatch } from "react-hook-form";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -6,34 +7,15 @@ import {
|
|||||||
type ProformaTaxPercentageOption,
|
type ProformaTaxPercentageOption,
|
||||||
getProformaRecPercentage,
|
getProformaRecPercentage,
|
||||||
} from "../../shared";
|
} from "../../shared";
|
||||||
import type { ProformaTaxMode, ProformaUpdateForm } from "../entities";
|
import type { ProformaUpdateForm } from "../entities";
|
||||||
|
|
||||||
interface UseUpdateProformaTaxControllerParams {
|
interface UseUpdateProformaTaxControllerParams {
|
||||||
form: UseFormReturn<ProformaUpdateForm>;
|
form: UseFormReturn<ProformaUpdateForm>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseUpdateProformaTaxControllerResult {
|
export type UseUpdateProformaTaxControllerResult = ReturnType<
|
||||||
taxMode: ProformaTaxMode;
|
typeof useUpdateProformaTaxController
|
||||||
|
>;
|
||||||
hasTaxPercentage: boolean;
|
|
||||||
taxPercentage: number | null;
|
|
||||||
|
|
||||||
hasRecPercentage: boolean;
|
|
||||||
recPercentage: number | null;
|
|
||||||
|
|
||||||
hasRetentionPercentage: boolean;
|
|
||||||
retentionPercentage: number | null;
|
|
||||||
|
|
||||||
usesSingleTax: boolean;
|
|
||||||
usesPerLineTax: boolean;
|
|
||||||
|
|
||||||
enablePerLineTaxes: () => void;
|
|
||||||
disablePerLineTaxes: () => void;
|
|
||||||
|
|
||||||
updateTaxPercentage: (newTaxPercentage: ProformaTaxPercentageOption) => void;
|
|
||||||
updateRecPercentage: (enabled: boolean) => void;
|
|
||||||
updateRetentionPercentage: (newRetentionPercentage: ProformaRetentionPercentageOption) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolveRecPercentage = (
|
const resolveRecPercentage = (
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
@ -46,9 +28,7 @@ const resolveRecPercentage = (
|
|||||||
return getProformaRecPercentage(taxPercentage as ProformaTaxPercentageOption);
|
return getProformaRecPercentage(taxPercentage as ProformaTaxPercentageOption);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateProformaTaxController = ({
|
export const useUpdateProformaTaxController = ({ form }: UseUpdateProformaTaxControllerParams) => {
|
||||||
form,
|
|
||||||
}: UseUpdateProformaTaxControllerParams): UseUpdateProformaTaxControllerResult => {
|
|
||||||
const { control, getValues, setValue } = form;
|
const { control, getValues, setValue } = form;
|
||||||
|
|
||||||
const taxMode = useWatch({ control, name: "taxMode" });
|
const taxMode = useWatch({ control, name: "taxMode" });
|
||||||
@ -64,6 +44,18 @@ export const useUpdateProformaTaxController = ({
|
|||||||
|
|
||||||
const hasMountedRef = useRef(false);
|
const hasMountedRef = useRef(false);
|
||||||
|
|
||||||
|
const taxRegimesQuery = useTaxRegimesListQuery({
|
||||||
|
criteria: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: "isActive",
|
||||||
|
operator: "EQUALS",
|
||||||
|
value: "true",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (taxMode !== "single") return;
|
if (taxMode !== "single") return;
|
||||||
|
|
||||||
@ -180,6 +172,10 @@ export const useUpdateProformaTaxController = ({
|
|||||||
[setValue]
|
[setValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const taxRegimeOptions = useMemo(() => {
|
||||||
|
return getTaxRegimeOptions(taxRegimesQuery.data?.items ?? []);
|
||||||
|
}, [taxRegimesQuery.data?.items]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
taxMode,
|
taxMode,
|
||||||
|
|
||||||
@ -201,5 +197,7 @@ export const useUpdateProformaTaxController = ({
|
|||||||
updateTaxPercentage,
|
updateTaxPercentage,
|
||||||
updateRecPercentage,
|
updateRecPercentage,
|
||||||
updateRetentionPercentage,
|
updateRetentionPercentage,
|
||||||
|
|
||||||
|
taxRegimeOptions,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
export interface ProformaItemUpdatePatch {
|
export interface ProformaItemUpdatePatch {
|
||||||
id: string;
|
id: string;
|
||||||
position: number;
|
position: number;
|
||||||
isValued: boolean;
|
|
||||||
|
|
||||||
description: string | null;
|
description: string | null;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-editor.tsx
|
// modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-editor.tsx
|
||||||
|
|
||||||
import type { CustomerSelectionOption } from "@erp/customers";
|
import type { CustomerSelectionOption } from "@erp/customers";
|
||||||
import { PercentageField } from "@repo/rdx-ui/components";
|
|
||||||
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
|
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
|
||||||
@ -14,7 +13,6 @@ import type {
|
|||||||
UseUpdateProformaTaxControllerResult,
|
UseUpdateProformaTaxControllerResult,
|
||||||
UseUpdateProformaTotalsControllerResult,
|
UseUpdateProformaTotalsControllerResult,
|
||||||
} from "../../controllers";
|
} from "../../controllers";
|
||||||
import { NewProformaTotalsSummary } from "../blocks/new-proforma-totals-summary";
|
|
||||||
|
|
||||||
import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor";
|
import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor";
|
||||||
import { ProformaUpdateItemsEditor } from "./proforma-update-items-editor";
|
import { ProformaUpdateItemsEditor } from "./proforma-update-items-editor";
|
||||||
@ -89,24 +87,10 @@ export const ProformaUpdateEditorForm = ({
|
|||||||
selectedCustomer={selectedCustomer}
|
selectedCustomer={selectedCustomer}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProformaUpdateTaxEditor className="2xl:col-span-1" taxCtrl={taxCtrl} />
|
<ProformaUpdateTaxEditor
|
||||||
|
className="2xl:col-span-1"
|
||||||
<NewProformaTotalsSummary
|
taxCtrl={taxCtrl}
|
||||||
className="hidden"
|
taxRegimeOptions={taxCtrl.taxRegimeOptions}
|
||||||
currency={currencyCode}
|
|
||||||
globalDiscountField={
|
|
||||||
<PercentageField
|
|
||||||
className="md:col-span-4 md:col-start-1"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
inputClassName="bg-background"
|
|
||||||
label={t("proformas.update.totals.globalDiscountPercentage", "Descuento global")}
|
|
||||||
name="globalDiscountPercentage"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
layout="vertical"
|
|
||||||
showRec={taxCtrl.hasRecPercentage}
|
|
||||||
showRetention={taxCtrl.hasRetentionPercentage}
|
|
||||||
totals={totalsCtrl.totals}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProformaUpdatePaymentEditor
|
<ProformaUpdatePaymentEditor
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {
|
|||||||
FormSectionCard,
|
FormSectionCard,
|
||||||
FormSectionGrid,
|
FormSectionGrid,
|
||||||
SelectField,
|
SelectField,
|
||||||
|
type SelectFieldItem,
|
||||||
SwitchField,
|
SwitchField,
|
||||||
} from "@repo/rdx-ui/components";
|
} from "@repo/rdx-ui/components";
|
||||||
import { PercentageHelper } from "@repo/rdx-utils";
|
import { PercentageHelper } from "@repo/rdx-utils";
|
||||||
@ -19,6 +20,7 @@ import type { UseUpdateProformaTaxControllerResult } from "../../controllers";
|
|||||||
|
|
||||||
interface ProformaUpdateTaxEditorProps {
|
interface ProformaUpdateTaxEditorProps {
|
||||||
taxCtrl: UseUpdateProformaTaxControllerResult;
|
taxCtrl: UseUpdateProformaTaxControllerResult;
|
||||||
|
taxRegimeOptions: SelectFieldItem[];
|
||||||
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
@ -28,6 +30,7 @@ interface ProformaUpdateTaxEditorProps {
|
|||||||
|
|
||||||
export const ProformaUpdateTaxEditor = ({
|
export const ProformaUpdateTaxEditor = ({
|
||||||
taxCtrl,
|
taxCtrl,
|
||||||
|
taxRegimeOptions,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
className,
|
className,
|
||||||
@ -50,60 +53,7 @@ export const ProformaUpdateTaxEditor = ({
|
|||||||
className="col-span-full"
|
className="col-span-full"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
inputClassName="bg-background"
|
inputClassName="bg-background"
|
||||||
items={[
|
items={taxRegimeOptions}
|
||||||
{ value: "01", label: "01: Operación de régimen general." },
|
|
||||||
{ value: "02", label: "02: Exportación." },
|
|
||||||
{
|
|
||||||
value: "03",
|
|
||||||
label:
|
|
||||||
"03: Operaciones a las que se aplique el régimen especial de bienes usados, objetos de arte, antigüedades y objetos de colección.",
|
|
||||||
},
|
|
||||||
{ value: "04", label: "04: Régimen especial del oro de inversión." },
|
|
||||||
{ value: "05", label: "05: Régimen especial de las agencias de viajes." },
|
|
||||||
{
|
|
||||||
value: "06",
|
|
||||||
label: "06: Régimen especial grupo de entidades en IVA o IGIC (Nivel Avanzado)",
|
|
||||||
},
|
|
||||||
{ value: "07", label: "07: Régimen especial del criterio de caja." },
|
|
||||||
{ value: "08", label: "08: Operaciones sujetas al IPSI/IVA o IGIC." },
|
|
||||||
{
|
|
||||||
value: "09",
|
|
||||||
label:
|
|
||||||
"09: Facturación de las prestaciones de servicios de agencias de viaje que actúan como mediadoras en nombre y por cuenta ajena (D.A.4ª RD1619/2012)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "10",
|
|
||||||
label:
|
|
||||||
"10: Cobros por cuenta de terceros de honorarios profesionales o de derechos derivados de la propiedad industrial, de autor u otros por cuenta de sus socios, asociados o colegiados efectuados por sociedades, asociaciones, colegios profesionales u otras entidades que realicen estas funciones de cobro.",
|
|
||||||
},
|
|
||||||
{ value: "11", label: "11: Operaciones de arrendamiento de local de negocio." },
|
|
||||||
{
|
|
||||||
value: "14",
|
|
||||||
label:
|
|
||||||
"14: Factura con IVA o IGIC pendiente de devengo en certificaciones de obra cuyo destinatario sea una Administración Pública.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "15",
|
|
||||||
label:
|
|
||||||
"15: Factura con IVA o IGIC pendiente de devengo en operaciones de tracto sucesivo.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "17",
|
|
||||||
label:
|
|
||||||
"17: Operación acogida a alguno de los regímenes previstos en el Capítulo XI del Título IX (OSS e IOSS) o régimen especial de comerciante minorista",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "18",
|
|
||||||
label:
|
|
||||||
"18: Recargo de equivalencia o régimen especial del pequeño empresario o profesional.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "19",
|
|
||||||
label:
|
|
||||||
"19: Operaciones de actividades incluidas en el Régimen Especial de Agricultura, Ganadería y Pesca (REAGYP) u operaciones interiores exentas por aplicación artículo 25 Ley 19/1994",
|
|
||||||
},
|
|
||||||
{ value: "20", label: "20: Régimen simplificado" },
|
|
||||||
]}
|
|
||||||
label={t("form_fields.proformas.tax_regime_code.label", "Régimen fiscal")}
|
label={t("form_fields.proformas.tax_regime_code.label", "Régimen fiscal")}
|
||||||
name="taxRegimeCode"
|
name="taxRegimeCode"
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
|
|||||||
@ -38,6 +38,8 @@ export const buildUpdateProformaByIdParams = (
|
|||||||
throw new Error("proformaId is required");
|
throw new Error("proformaId is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(patch);
|
||||||
|
|
||||||
const data: UpdateProformaByIdParams["data"] = {};
|
const data: UpdateProformaByIdParams["data"] = {};
|
||||||
|
|
||||||
if (ObjectHelper.hasOwn(patch, "series")) {
|
if (ObjectHelper.hasOwn(patch, "series")) {
|
||||||
@ -84,6 +86,10 @@ export const buildUpdateProformaByIdParams = (
|
|||||||
data.payment_term_id = patch.paymentTermId;
|
data.payment_term_id = patch.paymentTermId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ObjectHelper.hasOwn(patch, "taxRegimeCode")) {
|
||||||
|
data.tax_regime_code = patch.taxRegimeCode;
|
||||||
|
}
|
||||||
|
|
||||||
if (ObjectHelper.hasOwn(patch, "globalDiscountPercentage")) {
|
if (ObjectHelper.hasOwn(patch, "globalDiscountPercentage")) {
|
||||||
data.global_discount_percentage = PercentageDTOHelper.fromNumber(
|
data.global_discount_percentage = PercentageDTOHelper.fromNumber(
|
||||||
patch.globalDiscountPercentage!,
|
patch.globalDiscountPercentage!,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user