feat(tax-definitions): add comprehensive tests and implement CRUD operations for tax definitions
- Introduced unit tests for input mappers and snapshot builders to ensure correct mapping and serialization of tax definitions. - Implemented status change use cases for enabling and disabling tax definitions with appropriate error handling. - Added API endpoints for creating, deleting, enabling, and disabling tax definitions, along with corresponding hooks for React. - Developed entity structures for tax definitions and their lists, ensuring proper data handling in the application. - Enhanced validation and error handling in mutation hooks for creating and updating tax definitions.
This commit is contained in:
parent
85973c6096
commit
aed24d5354
@ -0,0 +1,144 @@
|
||||
import { EntityNotFoundError } from "@erp/core/api";
|
||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { CountryCode, CountryRegionCode, TextValue, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
TaxCalculationBehavior,
|
||||
TaxDefinition,
|
||||
TaxDefinitionCode,
|
||||
TaxDefinitionName,
|
||||
TaxFamily,
|
||||
TaxRate,
|
||||
TaxScope,
|
||||
} from "../../../domain";
|
||||
import type { TaxDefinitionSummary } from "../models";
|
||||
import type { ITaxDefinitionRepository } from "../repositories";
|
||||
|
||||
const valueOf = <T>(result: Result<T, Error>): T => {
|
||||
if (result.isFailure) throw result.error;
|
||||
return result.data;
|
||||
};
|
||||
|
||||
export const buildTaxDefinition = (params?: {
|
||||
companyId?: UniqueID;
|
||||
id?: UniqueID;
|
||||
isActive?: boolean;
|
||||
isSystem?: boolean;
|
||||
allowedSurchargeCodes?: string[] | null;
|
||||
}) => {
|
||||
const companyId = params?.companyId ?? UniqueID.generateNewID();
|
||||
const allowedSurchargeCodes = params?.allowedSurchargeCodes;
|
||||
|
||||
return valueOf(
|
||||
TaxDefinition.create(
|
||||
{
|
||||
companyId,
|
||||
code: valueOf(TaxDefinitionCode.create("iva_21")),
|
||||
name: valueOf(TaxDefinitionName.create("IVA general")),
|
||||
description: Maybe.some(valueOf(TextValue.create("IVA aplicable"))),
|
||||
rate: valueOf(TaxRate.create({ value: 2100, scale: 2 })),
|
||||
taxFamily: valueOf(TaxFamily.create("iva")),
|
||||
calculationBehavior: valueOf(TaxCalculationBehavior.create("additive")),
|
||||
jurisdictionCountryCode: valueOf(CountryCode.create("ES")),
|
||||
jurisdictionRegionCode: Maybe.some(valueOf(CountryRegionCode.create("ES-M"))),
|
||||
taxScope: valueOf(TaxScope.create("domestic")),
|
||||
invoiceNote: Maybe.some(valueOf(TextValue.create("Factura con IVA"))),
|
||||
allowedSurchargeCodes:
|
||||
allowedSurchargeCodes === null
|
||||
? Maybe.none()
|
||||
: Maybe.some(
|
||||
(allowedSurchargeCodes ?? ["rec_5_2", "rec_1_4"]).map((code) =>
|
||||
valueOf(TaxDefinitionCode.create(code))
|
||||
)
|
||||
),
|
||||
isSystem: params?.isSystem ?? false,
|
||||
isActive: params?.isActive ?? true,
|
||||
validFrom: Maybe.some(valueOf(UtcDate.createFromISO("2026-01-01"))),
|
||||
validTo: Maybe.some(valueOf(UtcDate.createFromISO("2026-12-31"))),
|
||||
},
|
||||
params?.id ?? UniqueID.generateNewID()
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export class InMemoryTaxDefinitionRepository implements ITaxDefinitionRepository {
|
||||
private readonly items = new Map<string, TaxDefinition>();
|
||||
private readonly deleted = new Set<string>();
|
||||
public updateCalls = 0;
|
||||
public deleteCalls = 0;
|
||||
|
||||
async create(taxDefinition: TaxDefinition): Promise<Result<void, Error>> {
|
||||
this.items.set(taxDefinition.id.toString(), taxDefinition);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
async update(taxDefinition: TaxDefinition): Promise<Result<void, Error>> {
|
||||
this.updateCalls += 1;
|
||||
this.items.set(taxDefinition.id.toString(), taxDefinition);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
async deleteByIdInCompany(companyId: UniqueID, id: UniqueID): Promise<Result<boolean, Error>> {
|
||||
this.deleteCalls += 1;
|
||||
const found = await this.getByIdInCompany(companyId, id);
|
||||
if (found.isFailure) return Result.fail(found.error);
|
||||
this.deleted.add(id.toString());
|
||||
return Result.ok(true);
|
||||
}
|
||||
|
||||
async existsByIdInCompany(companyId: UniqueID, id: UniqueID): Promise<Result<boolean, Error>> {
|
||||
const item = this.items.get(id.toString());
|
||||
return Result.ok(
|
||||
item !== undefined &&
|
||||
!this.deleted.has(id.toString()) &&
|
||||
item.companyId.toString() === companyId.toString()
|
||||
);
|
||||
}
|
||||
|
||||
async getByIdInCompany(companyId: UniqueID, id: UniqueID): Promise<Result<TaxDefinition, Error>> {
|
||||
const item = this.items.get(id.toString());
|
||||
if (
|
||||
item === undefined ||
|
||||
this.deleted.has(id.toString()) ||
|
||||
item.companyId.toString() !== companyId.toString()
|
||||
) {
|
||||
return Result.fail(new EntityNotFoundError("TaxDefinition", "id", id.toString()));
|
||||
}
|
||||
return Result.ok(item);
|
||||
}
|
||||
|
||||
async getByCodeInCompany(
|
||||
companyId: UniqueID,
|
||||
code: TaxDefinitionCode
|
||||
): Promise<Result<TaxDefinition, Error>> {
|
||||
const item = [...this.items.values()].find(
|
||||
(candidate) =>
|
||||
!this.deleted.has(candidate.id.toString()) &&
|
||||
candidate.companyId.toString() === companyId.toString() &&
|
||||
candidate.code.toPrimitive() === code.toPrimitive()
|
||||
);
|
||||
return item === undefined
|
||||
? Result.fail(new EntityNotFoundError("TaxDefinition", "code", code.toPrimitive()))
|
||||
: Result.ok(item);
|
||||
}
|
||||
|
||||
async findActiveByCodeInCompany(): Promise<Result<Maybe<TaxDefinition>, Error>> {
|
||||
return Result.ok(Maybe.none());
|
||||
}
|
||||
|
||||
async findActiveByCodesInCompany(): Promise<Result<Collection<TaxDefinition>, Error>> {
|
||||
return Result.ok(new Collection<TaxDefinition>([]));
|
||||
}
|
||||
|
||||
async findByCodesInCompany(): Promise<Result<Collection<TaxDefinition>, Error>> {
|
||||
return Result.ok(new Collection<TaxDefinition>([]));
|
||||
}
|
||||
|
||||
async findByCriteriaInCompany(
|
||||
_companyId: UniqueID,
|
||||
_criteria: Criteria
|
||||
): Promise<Result<Collection<TaxDefinitionSummary>, Error>> {
|
||||
return Result.ok(new Collection<TaxDefinitionSummary>([]));
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
import type { CreateTaxDefinitionRequestDTO } from "@erp/catalogs/common";
|
||||
import {
|
||||
DomainError,
|
||||
TextValue,
|
||||
CountryCode, CountryRegionCode, TextValue, UtcDate,
|
||||
UniqueID,
|
||||
ValidationErrorCollection,
|
||||
type ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
extractOrPushError, maybeFromNullableResult,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { ITaxDefinitionCreateProps } from "../../../domain";
|
||||
import { TaxCalculationBehavior, TaxDefinitionCode, TaxDefinitionName, TaxFamily, TaxRate, TaxScope, type ITaxDefinitionCreateProps } from "../../../domain";
|
||||
|
||||
export interface ICreateTaxDefinitionInputMapper {
|
||||
map(
|
||||
@ -30,15 +30,43 @@ export class CreateTaxDefinitionInputMapper implements ICreateTaxDefinitionInput
|
||||
|
||||
const code = extractOrPushError(TaxDefinitionCode.create(dto.code), "code", errors);
|
||||
|
||||
const name = extractOrPushError(TextValue.create(dto.name), "name", errors);
|
||||
const name = extractOrPushError(TaxDefinitionName.create(dto.name), "name", errors);
|
||||
|
||||
const description = extractOrPushError(
|
||||
TextValue.create(dto.description),
|
||||
maybeFromNullableResult(dto.description, (value) => TextValue.create(value)),
|
||||
"description",
|
||||
errors
|
||||
);
|
||||
|
||||
const isActive = extractOrPushError(Result.ok(dto.is_active), "is_active", errors);
|
||||
const rate = extractOrPushError(TaxRate.create({ value: Number(dto.rate.value), scale: Number(dto.rate.scale) }), "rate", errors);
|
||||
const taxFamily = extractOrPushError(TaxFamily.create(dto.tax_family), "tax_family", errors);
|
||||
const calculationBehavior = extractOrPushError(TaxCalculationBehavior.create(dto.calculation_behavior), "calculation_behavior", errors);
|
||||
const jurisdictionCountryCode = extractOrPushError(CountryCode.create(dto.jurisdiction_country_code), "jurisdiction_country_code", errors);
|
||||
const jurisdictionRegionCode = extractOrPushError(maybeFromNullableResult(dto.jurisdiction_region_code, (value) => CountryRegionCode.create(value)), "jurisdiction_region_code", errors);
|
||||
const taxScope = extractOrPushError(TaxScope.create(dto.tax_scope), "tax_scope", errors);
|
||||
const invoiceNote = extractOrPushError(maybeFromNullableResult(dto.invoice_note, (value) => TextValue.create(value)), "invoice_note", errors);
|
||||
const allowedSurchargeCodes = extractOrPushError(
|
||||
maybeFromNullableResult(dto.allowed_surcharge_codes, (values) => {
|
||||
const codes: TaxDefinitionCode[] = [];
|
||||
|
||||
for (const value of values) {
|
||||
const codeResult = TaxDefinitionCode.create(value);
|
||||
|
||||
if (codeResult.isFailure) {
|
||||
return Result.fail(codeResult.error);
|
||||
}
|
||||
|
||||
codes.push(codeResult.data);
|
||||
}
|
||||
|
||||
return Result.ok(codes);
|
||||
}),
|
||||
"allowed_surcharge_codes",
|
||||
errors
|
||||
);
|
||||
const validFrom = extractOrPushError(maybeFromNullableResult(dto.valid_from, (value) => UtcDate.createFromISO(value)), "valid_from", errors);
|
||||
const validTo = extractOrPushError(maybeFromNullableResult(dto.valid_to, (value) => UtcDate.createFromISO(value)), "valid_to", errors);
|
||||
|
||||
this.throwIfValidationErrors(errors);
|
||||
|
||||
@ -47,18 +75,10 @@ export class CreateTaxDefinitionInputMapper implements ICreateTaxDefinitionInput
|
||||
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,
|
||||
rate: rate!, taxFamily: taxFamily!, calculationBehavior: calculationBehavior!, jurisdictionCountryCode: jurisdictionCountryCode!, jurisdictionRegionCode: jurisdictionRegionCode!, taxScope: taxScope!, invoiceNote: invoiceNote!, allowedSurchargeCodes: allowedSurchargeCodes!,
|
||||
isSystem: dto.is_system ?? false,
|
||||
isActive: isActive ?? true,
|
||||
validFrom: dto.valid_from as any,
|
||||
validTo: dto.valid_to as any,
|
||||
validFrom: validFrom!, validTo: validTo!,
|
||||
};
|
||||
|
||||
return Result.ok({ id: taxDefinitionId!, props });
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { CreateTaxDefinitionRequestDTO } from "@erp/catalogs/common";
|
||||
import { CreateTaxDefinitionInputMapper } from "./create-tax-definition-input.mapper";
|
||||
import { UpdateTaxDefinitionByIdInputMapper } from "./update-tax-definition-by-id-input.mapper";
|
||||
|
||||
const dto = {
|
||||
id: UniqueID.generateNewID().toString(), code: "iva_21", name: "IVA 21", description: "General",
|
||||
rate: { value: "2100", scale: "2" }, tax_family: "iva", calculation_behavior: "additive", jurisdiction_country_code: "ES", jurisdiction_region_code: "ES-M", tax_scope: "domestic", invoice_note: "Nota", allowed_surcharge_codes: ["rec_5_2", "rec_1_4"], is_active: true, valid_from: "2026-01-01", valid_to: "2026-12-31",
|
||||
} satisfies CreateTaxDefinitionRequestDTO;
|
||||
|
||||
test("create mapper materializa VOs y conserva la escala", () => {
|
||||
const result = new CreateTaxDefinitionInputMapper().map(dto, { companyId: UniqueID.generateNewID() });
|
||||
assert.equal(result.isSuccess, true);
|
||||
const props = result.data.props;
|
||||
assert.deepEqual(props.rate.toPrimitive(), { value: 2100, scale: 2 });
|
||||
assert.equal(props.taxFamily.toPrimitive(), "iva");
|
||||
assert.deepEqual(props.allowedSurchargeCodes.unwrap().map((code) => code.toPrimitive()), ["rec_5_2", "rec_1_4"]);
|
||||
});
|
||||
|
||||
test("update mapper conserva undefined y representa null como limpieza", () => {
|
||||
const mapper = new UpdateTaxDefinitionByIdInputMapper();
|
||||
const partial = mapper.map({ name: "Nuevo" });
|
||||
assert.equal(partial.isSuccess, true);
|
||||
assert.equal(partial.data.description, undefined);
|
||||
assert.equal(partial.data.rate, undefined);
|
||||
const cleared = mapper.map({ description: null, invoice_note: null, allowed_surcharge_codes: null, valid_from: null, valid_to: null });
|
||||
assert.equal(cleared.isSuccess, true);
|
||||
assert.equal(cleared.data.description?.isNone(), true);
|
||||
assert.equal(cleared.data.allowedSurchargeCodes?.isNone(), true);
|
||||
});
|
||||
@ -1,14 +1,14 @@
|
||||
import type { UpdateTaxDefinitionByIdRequestDTO } from "@erp/catalogs/common";
|
||||
import {
|
||||
DomainError,
|
||||
TextValue,
|
||||
TextValue, UtcDate,
|
||||
ValidationErrorCollection,
|
||||
type ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Maybe, Result, toPatchField } from "@repo/rdx-utils";
|
||||
|
||||
import type { TaxDefinitionPatchProps } from "../../../domain";
|
||||
import { TaxDefinitionCode, TaxDefinitionName, TaxRate, type TaxDefinitionPatchProps } from "../../../domain";
|
||||
|
||||
export interface IUpdateTaxDefinitionByIdInputMapper {
|
||||
map(dto: UpdateTaxDefinitionByIdRequestDTO): Result<TaxDefinitionPatchProps, Error>;
|
||||
@ -19,26 +19,56 @@ export class UpdateTaxDefinitionByIdInputMapper implements IUpdateTaxDefinitionB
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
|
||||
try {
|
||||
const name = extractOrPushError(TextValue.create(dto.name), "name", errors);
|
||||
const description = extractOrPushError(
|
||||
TextValue.create(dto.description),
|
||||
"description",
|
||||
errors
|
||||
);
|
||||
const props: TaxDefinitionPatchProps = {};
|
||||
toPatchField(dto.name).ifSet((value) => { props.name = extractOrPushError(TaxDefinitionName.create(value), "name", errors); });
|
||||
if (dto.description === null) {
|
||||
props.description = Maybe.none();
|
||||
} else if (dto.description !== undefined) {
|
||||
const descriptionResult = TextValue.create(dto.description);
|
||||
if (descriptionResult.isFailure) return Result.fail(descriptionResult.error);
|
||||
props.description = Maybe.some(descriptionResult.data);
|
||||
}
|
||||
toPatchField(dto.rate).ifSet((value) => { props.rate = extractOrPushError(TaxRate.create({ value: Number(value.value), scale: Number(value.scale) }), "rate", errors); });
|
||||
if (dto.invoice_note === null) {
|
||||
props.invoiceNote = Maybe.none();
|
||||
} else if (dto.invoice_note !== undefined) {
|
||||
const invoiceNoteResult = TextValue.create(dto.invoice_note);
|
||||
if (invoiceNoteResult.isFailure) return Result.fail(invoiceNoteResult.error);
|
||||
props.invoiceNote = Maybe.some(invoiceNoteResult.data);
|
||||
}
|
||||
if (dto.allowed_surcharge_codes === null) {
|
||||
props.allowedSurchargeCodes = Maybe.none();
|
||||
} else if (dto.allowed_surcharge_codes !== undefined) {
|
||||
const codes: TaxDefinitionCode[] = [];
|
||||
|
||||
for (const value of dto.allowed_surcharge_codes) {
|
||||
const codeResult = TaxDefinitionCode.create(value);
|
||||
|
||||
if (codeResult.isFailure) return Result.fail(codeResult.error);
|
||||
|
||||
codes.push(codeResult.data);
|
||||
}
|
||||
|
||||
props.allowedSurchargeCodes = Maybe.some(codes);
|
||||
}
|
||||
if (dto.valid_from === null) {
|
||||
props.validFrom = Maybe.none();
|
||||
} else if (dto.valid_from !== undefined) {
|
||||
const validFromResult = UtcDate.createFromISO(dto.valid_from);
|
||||
if (validFromResult.isFailure) return Result.fail(validFromResult.error);
|
||||
props.validFrom = Maybe.some(validFromResult.data);
|
||||
}
|
||||
if (dto.valid_to === null) {
|
||||
props.validTo = Maybe.none();
|
||||
} else if (dto.valid_to !== undefined) {
|
||||
const validToResult = UtcDate.createFromISO(dto.valid_to);
|
||||
if (validToResult.isFailure) return Result.fail(validToResult.error);
|
||||
props.validTo = Maybe.some(validToResult.data);
|
||||
}
|
||||
toPatchField(dto.is_active).ifSet((value) => { props.isActive = value; });
|
||||
|
||||
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(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { TaxDefinitionCode, TaxRate } from "@erp/catalogs/api/domain";
|
||||
import type { TextValue, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import type { TaxCalculationBehavior, TaxDefinitionCode, TaxFamily, TaxRate, TaxScope } from "@erp/catalogs/api/domain";
|
||||
import type { CountryCode, CountryRegionCode, TextValue, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import type { Maybe } from "@repo/rdx-utils";
|
||||
|
||||
export type TaxDefinitionDetail = {
|
||||
@ -9,13 +9,13 @@ export type TaxDefinitionDetail = {
|
||||
name: TextValue;
|
||||
description: Maybe<TextValue>;
|
||||
rate: TaxRate;
|
||||
taxFamily: string;
|
||||
calculationBehavior: string;
|
||||
jurisdictionCountryCode: string;
|
||||
jurisdictionRegionCode: Maybe<string>;
|
||||
taxScope: string;
|
||||
taxFamily: TaxFamily;
|
||||
calculationBehavior: TaxCalculationBehavior;
|
||||
jurisdictionCountryCode: CountryCode;
|
||||
jurisdictionRegionCode: Maybe<CountryRegionCode>;
|
||||
taxScope: TaxScope;
|
||||
invoiceNote: Maybe<TextValue>;
|
||||
allowedSurchargeCodes: Maybe<string[]>;
|
||||
allowedSurchargeCodes: Maybe<TaxDefinitionCode[]>;
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
validFrom: Maybe<UtcDate>;
|
||||
|
||||
@ -2,14 +2,15 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { ITaxDefinitionRepository } from "../repositories";
|
||||
import type { TaxDefinition } from "../../../domain";
|
||||
|
||||
export interface ITaxDefinitionStatusChanger {
|
||||
enable(companyId: UniqueID, id: UniqueID, transaction?: unknown): Promise<Result<boolean, Error>>;
|
||||
enable(companyId: UniqueID, id: UniqueID, transaction?: unknown): Promise<Result<TaxDefinition, Error>>;
|
||||
disable(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction?: unknown
|
||||
): Promise<Result<boolean, Error>>;
|
||||
): Promise<Result<TaxDefinition, Error>>;
|
||||
}
|
||||
|
||||
export class TaxDefinitionStatusChanger implements ITaxDefinitionStatusChanger {
|
||||
@ -34,7 +35,7 @@ export class TaxDefinitionStatusChanger implements ITaxDefinitionStatusChanger {
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(changeResult.data);
|
||||
return Result.ok(taxDefinition);
|
||||
}
|
||||
|
||||
public async disable(companyId: UniqueID, id: UniqueID, transaction?: unknown) {
|
||||
@ -56,6 +57,6 @@ export class TaxDefinitionStatusChanger implements ITaxDefinitionStatusChanger {
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(changeResult.data);
|
||||
return Result.ok(taxDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { TaxDefinitionDetailSchema } from "@erp/catalogs/common";
|
||||
|
||||
import { buildTaxDefinition } from "../../__tests__/tax-definition-test-support";
|
||||
import { TaxDefinitionFullSnapshotBuilder } from "./full-tax-definition-snapshot.builder";
|
||||
|
||||
const builder = new TaxDefinitionFullSnapshotBuilder();
|
||||
|
||||
test("serializa una definición completa como TaxDefinitionDetailDTO", () => {
|
||||
const snapshot = builder.toOutput(buildTaxDefinition());
|
||||
const parsed = TaxDefinitionDetailSchema.parse(snapshot);
|
||||
|
||||
assert.deepEqual(parsed.rate, { value: "2100", scale: "2" });
|
||||
assert.equal(parsed.tax_family, "iva");
|
||||
assert.equal(parsed.calculation_behavior, "additive");
|
||||
assert.equal(parsed.tax_scope, "domestic");
|
||||
assert.equal(parsed.jurisdiction_country_code, "ES");
|
||||
assert.equal(parsed.jurisdiction_region_code, "ES-M");
|
||||
assert.deepEqual(parsed.allowed_surcharge_codes, ["rec_5_2", "rec_1_4"]);
|
||||
});
|
||||
|
||||
test("serializa allowedSurchargeCodes ninguno, uno y varios como strings", () => {
|
||||
const none = TaxDefinitionDetailSchema.parse(
|
||||
builder.toOutput(buildTaxDefinition({ allowedSurchargeCodes: null }))
|
||||
);
|
||||
const one = TaxDefinitionDetailSchema.parse(
|
||||
builder.toOutput(buildTaxDefinition({ allowedSurchargeCodes: ["rec_5_2"] }))
|
||||
);
|
||||
const many = TaxDefinitionDetailSchema.parse(
|
||||
builder.toOutput(buildTaxDefinition({ allowedSurchargeCodes: ["rec_5_2", "rec_1_4"] }))
|
||||
);
|
||||
|
||||
assert.equal(none.allowed_surcharge_codes, null);
|
||||
assert.deepEqual(one.allowed_surcharge_codes, ["rec_5_2"]);
|
||||
assert.deepEqual(many.allowed_surcharge_codes, ["rec_5_2", "rec_1_4"]);
|
||||
});
|
||||
@ -1,12 +1,12 @@
|
||||
import type { ISnapshotBuilder } from "@erp/core/api";
|
||||
|
||||
import type { TaxDefinitionDetail } from "../../models";
|
||||
import type { TaxDefinitionDetailDTO } from "@erp/catalogs/common";
|
||||
import type { TaxDefinition } from "../../../../domain";
|
||||
|
||||
export interface ITaxDefinitionFullSnapshotBuilder
|
||||
extends ISnapshotBuilder<TaxDefinitionDetail, TaxDefinitionDetailDTO> {}
|
||||
extends ISnapshotBuilder<TaxDefinition, TaxDefinitionDetailDTO> {}
|
||||
|
||||
export class TaxDefinitionFullSnapshotBuilder implements ITaxDefinitionFullSnapshotBuilder {
|
||||
public toOutput(src: TaxDefinitionDetail): TaxDefinitionDetailDTO {
|
||||
public toOutput(src: TaxDefinition): TaxDefinitionDetailDTO {
|
||||
return {
|
||||
id: src.id.toString(),
|
||||
company_id: src.companyId.toString(),
|
||||
@ -16,21 +16,24 @@ export class TaxDefinitionFullSnapshotBuilder implements ITaxDefinitionFullSnaps
|
||||
(v) => v.toPrimitive(),
|
||||
() => null
|
||||
),
|
||||
rate: (src.rate as any).toPrimitive(),
|
||||
tax_family: src.taxFamily,
|
||||
calculation_behavior: src.calculationBehavior,
|
||||
jurisdiction_country_code: src.jurisdictionCountryCode,
|
||||
rate: {
|
||||
value: String(src.rate.toPrimitive().value),
|
||||
scale: String(src.rate.toPrimitive().scale),
|
||||
},
|
||||
tax_family: src.taxFamily.toPrimitive(),
|
||||
calculation_behavior: src.calculationBehavior.toPrimitive(),
|
||||
jurisdiction_country_code: src.jurisdictionCountryCode.toPrimitive(),
|
||||
jurisdiction_region_code: src.jurisdictionRegionCode.match(
|
||||
(v) => v,
|
||||
(v) => v.toPrimitive(),
|
||||
() => null
|
||||
),
|
||||
tax_scope: src.taxScope,
|
||||
tax_scope: src.taxScope.toPrimitive(),
|
||||
invoice_note: src.invoiceNote.match(
|
||||
(v) => v.toPrimitive(),
|
||||
() => null
|
||||
),
|
||||
allowed_surcharge_codes: src.allowedSurchargeCodes.match(
|
||||
(arr) => arr,
|
||||
(arr) => arr.map((code) => code.toPrimitive()),
|
||||
() => null
|
||||
),
|
||||
is_system: src.isSystem,
|
||||
@ -43,6 +46,6 @@ export class TaxDefinitionFullSnapshotBuilder implements ITaxDefinitionFullSnaps
|
||||
(d) => d.toPrimitive(),
|
||||
() => null
|
||||
),
|
||||
} as unknown as TaxDefinitionDetailDTO;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { TaxDefinitionDetailSchema } from "@erp/catalogs/common";
|
||||
|
||||
import { TaxDefinitionCannotBeDisabledError } from "../../domain";
|
||||
|
||||
import {
|
||||
InMemoryTaxDefinitionRepository,
|
||||
buildTaxDefinition,
|
||||
} from "./__tests__/tax-definition-test-support";
|
||||
import { TaxDefinitionStatusChanger } from "./services";
|
||||
import { TaxDefinitionFullSnapshotBuilder } from "./snapshot-builders";
|
||||
import { DisableTaxDefinitionByIdUseCase, EnableTaxDefinitionByIdUseCase } from "./use-cases";
|
||||
|
||||
const buildStatusFlows = (repository: InMemoryTaxDefinitionRepository) => {
|
||||
const statusChanger = new TaxDefinitionStatusChanger(repository);
|
||||
const fullSnapshotBuilder = new TaxDefinitionFullSnapshotBuilder();
|
||||
return {
|
||||
enable: new EnableTaxDefinitionByIdUseCase({ statusChanger, fullSnapshotBuilder }),
|
||||
disable: new DisableTaxDefinitionByIdUseCase({ statusChanger, fullSnapshotBuilder }),
|
||||
};
|
||||
};
|
||||
|
||||
test("enable de una definición no-system inactiva devuelve TaxDefinitionDetailDTO activa", async () => {
|
||||
const repository = new InMemoryTaxDefinitionRepository();
|
||||
const definition = buildTaxDefinition({ isActive: false });
|
||||
await repository.create(definition);
|
||||
|
||||
const result = await buildStatusFlows(repository).enable.execute(
|
||||
definition.companyId,
|
||||
definition.id
|
||||
);
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
const output = TaxDefinitionDetailSchema.parse(result.data);
|
||||
assert.equal(output.is_active, true);
|
||||
assert.equal(definition.isActive, true);
|
||||
assert.equal(repository.updateCalls, 1);
|
||||
});
|
||||
|
||||
test("disable de una definición no-system activa devuelve TaxDefinitionDetailDTO inactiva", async () => {
|
||||
const repository = new InMemoryTaxDefinitionRepository();
|
||||
const definition = buildTaxDefinition({ isActive: true });
|
||||
await repository.create(definition);
|
||||
|
||||
const result = await buildStatusFlows(repository).disable.execute(
|
||||
definition.companyId,
|
||||
definition.id
|
||||
);
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
const output = TaxDefinitionDetailSchema.parse(result.data);
|
||||
assert.equal(output.is_active, false);
|
||||
assert.equal(definition.isActive, false);
|
||||
assert.equal(repository.updateCalls, 1);
|
||||
});
|
||||
|
||||
test("enable de una definición system activa es idempotente y devuelve TaxDefinitionDetailDTO", async () => {
|
||||
const repository = new InMemoryTaxDefinitionRepository();
|
||||
const definition = buildTaxDefinition({ isSystem: true, isActive: true });
|
||||
await repository.create(definition);
|
||||
|
||||
const result = await buildStatusFlows(repository).enable.execute(
|
||||
definition.companyId,
|
||||
definition.id
|
||||
);
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
assert.equal(TaxDefinitionDetailSchema.parse(result.data).is_active, true);
|
||||
assert.equal(definition.isActive, true);
|
||||
assert.equal(repository.updateCalls, 0);
|
||||
});
|
||||
|
||||
test("disable de una definición system se rechaza y mantiene el estado", async () => {
|
||||
const repository = new InMemoryTaxDefinitionRepository();
|
||||
const definition = buildTaxDefinition({ isSystem: true, isActive: true });
|
||||
await repository.create(definition);
|
||||
|
||||
const result = await buildStatusFlows(repository).disable.execute(
|
||||
definition.companyId,
|
||||
definition.id
|
||||
);
|
||||
|
||||
assert.equal(result.isFailure, true);
|
||||
assert.ok(result.error instanceof TaxDefinitionCannotBeDisabledError);
|
||||
assert.equal(definition.isActive, true);
|
||||
assert.equal(repository.updateCalls, 0);
|
||||
});
|
||||
@ -0,0 +1,49 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { TaxDefinitionCannotBeDeletedError } from "../../../domain";
|
||||
import {
|
||||
InMemoryTaxDefinitionRepository,
|
||||
buildTaxDefinition,
|
||||
} from "../__tests__/tax-definition-test-support";
|
||||
import { TaxDefinitionDeleter, TaxDefinitionFinder } from "../services";
|
||||
|
||||
import { DeleteTaxDefinitionByIdUseCase } from "./delete-tax-definition-by-id.use-case";
|
||||
|
||||
const buildUseCase = (repository: InMemoryTaxDefinitionRepository) =>
|
||||
new DeleteTaxDefinitionByIdUseCase({
|
||||
finder: new TaxDefinitionFinder(repository),
|
||||
deleter: new TaxDefinitionDeleter(repository),
|
||||
});
|
||||
|
||||
test("delete rechaza una definición system sin ejecutar repository.delete", async () => {
|
||||
const repository = new InMemoryTaxDefinitionRepository();
|
||||
const definition = buildTaxDefinition({ isSystem: true });
|
||||
await repository.create(definition);
|
||||
|
||||
const result = await buildUseCase(repository).execute(definition.companyId, definition.id);
|
||||
|
||||
assert.equal(result.isFailure, true);
|
||||
assert.ok(result.error instanceof TaxDefinitionCannotBeDeletedError);
|
||||
assert.equal(repository.deleteCalls, 0);
|
||||
assert.equal(
|
||||
(await repository.getByIdInCompany(definition.companyId, definition.id)).isSuccess,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test("delete permite una definición non-system y la elimina del repositorio", async () => {
|
||||
const repository = new InMemoryTaxDefinitionRepository();
|
||||
const definition = buildTaxDefinition({ isSystem: false });
|
||||
await repository.create(definition);
|
||||
|
||||
const result = await buildUseCase(repository).execute(definition.companyId, definition.id);
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
assert.equal(result.data, true);
|
||||
assert.equal(repository.deleteCalls, 1);
|
||||
assert.equal(
|
||||
(await repository.getByIdInCompany(definition.companyId, definition.id)).isFailure,
|
||||
true
|
||||
);
|
||||
});
|
||||
@ -1,16 +1,28 @@
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { ITaxDefinitionDeleter } from "../services";
|
||||
import { TaxDefinitionCannotBeDeletedError } from "../../../domain";
|
||||
import type { ITaxDefinitionDeleter, ITaxDefinitionFinder } from "../services";
|
||||
|
||||
type DeleteTaxDefinitionDeps = {
|
||||
deleter: ITaxDefinitionDeleter;
|
||||
finder: ITaxDefinitionFinder;
|
||||
};
|
||||
|
||||
export class DeleteTaxDefinitionByIdUseCase {
|
||||
constructor(private readonly deps: DeleteTaxDefinitionDeps) {}
|
||||
|
||||
public async execute(companyId: UniqueID, id: UniqueID) {
|
||||
const found = await this.deps.finder.getByIdInCompany(companyId, id);
|
||||
if (found.isFailure) {
|
||||
return found;
|
||||
}
|
||||
if (found.data.isSystem) {
|
||||
return Result.fail(
|
||||
new TaxDefinitionCannotBeDeletedError("System tax definitions cannot be deleted.")
|
||||
);
|
||||
}
|
||||
|
||||
const result = await this.deps.deleter.deleteByIdInCompany(companyId, id);
|
||||
if (result.isFailure) {
|
||||
return result;
|
||||
|
||||
@ -2,9 +2,11 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { ITaxDefinitionStatusChanger } from "../services";
|
||||
import type { ITaxDefinitionFullSnapshotBuilder } from "../snapshot-builders";
|
||||
|
||||
type DisableTaxDefinitionDeps = {
|
||||
statusChanger: ITaxDefinitionStatusChanger;
|
||||
fullSnapshotBuilder: ITaxDefinitionFullSnapshotBuilder;
|
||||
};
|
||||
|
||||
export class DisableTaxDefinitionByIdUseCase {
|
||||
@ -16,6 +18,6 @@ export class DisableTaxDefinitionByIdUseCase {
|
||||
return result;
|
||||
}
|
||||
|
||||
return Result.ok(result.data);
|
||||
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(result.data));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,11 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { ITaxDefinitionStatusChanger } from "../services";
|
||||
import type { ITaxDefinitionFullSnapshotBuilder } from "../snapshot-builders";
|
||||
|
||||
type EnableTaxDefinitionDeps = {
|
||||
statusChanger: ITaxDefinitionStatusChanger;
|
||||
fullSnapshotBuilder: ITaxDefinitionFullSnapshotBuilder;
|
||||
};
|
||||
|
||||
export class EnableTaxDefinitionByIdUseCase {
|
||||
@ -16,6 +18,6 @@ export class EnableTaxDefinitionByIdUseCase {
|
||||
return result;
|
||||
}
|
||||
|
||||
return Result.ok(result.data);
|
||||
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(result.data));
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +105,8 @@ export const isTaxDefinitionCannotBeEnabledError = (
|
||||
export class TaxDefinitionCannotBeDisabledError extends DomainError {
|
||||
public readonly code = "TAX_DEFINITION_CANNOT_BE_DISABLED" as const;
|
||||
}
|
||||
export class TaxDefinitionCannotBeUpdatedError extends DomainError { public readonly code = "TAX_DEFINITION_CANNOT_BE_UPDATED" as const; }
|
||||
export class TaxDefinitionCannotBeDeletedError extends DomainError { public readonly code = "TAX_DEFINITION_CANNOT_BE_DELETED" as const; }
|
||||
|
||||
export const isTaxDefinitionCannotBeDisabledError = (
|
||||
e: unknown
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { CountryCode, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import { Maybe } from "@repo/rdx-utils";
|
||||
import { TaxCalculationBehavior, TaxDefinition, TaxDefinitionCode, TaxDefinitionName, TaxFamily, TaxRate, TaxScope } from ".";
|
||||
|
||||
const create = (params?: { from?: string; to?: string; system?: boolean }) => TaxDefinition.create({
|
||||
companyId: UniqueID.generateNewID(), code: TaxDefinitionCode.create("iva_21").data,
|
||||
name: TaxDefinitionName.create("IVA 21").data, description: Maybe.none(),
|
||||
rate: TaxRate.create({ value: 2100, scale: 2 }).data, taxFamily: TaxFamily.create("iva").data,
|
||||
calculationBehavior: TaxCalculationBehavior.create("additive").data,
|
||||
jurisdictionCountryCode: CountryCode.create("ES").data, jurisdictionRegionCode: Maybe.none(), taxScope: TaxScope.create("domestic").data,
|
||||
invoiceNote: Maybe.none(), allowedSurchargeCodes: Maybe.none(), isSystem: params?.system ?? false, isActive: true,
|
||||
validFrom: params?.from ? Maybe.some(UtcDate.createFromISO(params.from).data) : Maybe.none(), validTo: params?.to ? Maybe.some(UtcDate.createFromISO(params.to).data) : Maybe.none(),
|
||||
}).data;
|
||||
|
||||
test("acepta vigencias iguales y rechaza rangos invertidos sin mutar", () => {
|
||||
assert.ok(create({ from: "2026-01-01", to: "2026-01-01" }));
|
||||
const item = create({ from: "2026-01-01", to: "2026-12-31" });
|
||||
const result = item.update({ validFrom: Maybe.some(UtcDate.createFromISO("2027-01-01").data) });
|
||||
assert.equal(result.isFailure, true);
|
||||
assert.equal(item.validFrom.unwrap().toPrimitive(), "2026-01-01");
|
||||
});
|
||||
|
||||
test("protege update y disable de definiciones system, pero enable es idempotente", () => {
|
||||
const item = create({ system: true });
|
||||
assert.equal(item.update({ name: TaxDefinitionName.create("Otro").data }).isFailure, true);
|
||||
assert.equal(item.disable().isFailure, true);
|
||||
assert.equal(item.enable().isSuccess, true);
|
||||
});
|
||||
|
||||
test("permite actualizar una definición non-system", () => {
|
||||
const item = create();
|
||||
const updatedName = TaxDefinitionName.create("IVA reducido");
|
||||
assert.equal(updatedName.isSuccess, true);
|
||||
|
||||
const result = item.update({ name: updatedName.data });
|
||||
|
||||
assert.equal(result.isSuccess, true);
|
||||
assert.equal(result.data, true);
|
||||
assert.equal(item.name.toPrimitive(), "IVA reducido");
|
||||
});
|
||||
@ -12,6 +12,8 @@ import type { TaxCalculationBehavior } from "./calculation-behavior";
|
||||
import {
|
||||
InvalidTaxDefinitionAllowedSurchargeCodesError,
|
||||
InvalidTaxDefinitionValidityPeriodError,
|
||||
TaxDefinitionCannotBeDisabledError,
|
||||
TaxDefinitionCannotBeUpdatedError,
|
||||
} from "./errors";
|
||||
import type { TaxDefinitionCode } from "./tax-definition-code";
|
||||
import type { TaxDefinitionName } from "./tax-definition-name";
|
||||
@ -226,11 +228,27 @@ export class TaxDefinition extends AggregateRoot<TaxDefinitionInternalProps> {
|
||||
}
|
||||
|
||||
public update(patchProps: TaxDefinitionPatchProps): Result<boolean, Error> {
|
||||
if (this.isSystem) {
|
||||
return Result.fail(
|
||||
new TaxDefinitionCannotBeUpdatedError("System tax definitions cannot be updated.")
|
||||
);
|
||||
}
|
||||
const validationResult = TaxDefinition.validatePatchProps(patchProps);
|
||||
if (validationResult.isFailure) {
|
||||
return Result.fail(validationResult.error);
|
||||
}
|
||||
|
||||
const nextValidFrom = patchProps.validFrom ?? this.validFrom;
|
||||
const nextValidTo = patchProps.validTo ?? this.validTo;
|
||||
if (
|
||||
nextValidFrom.isSome() &&
|
||||
nextValidTo.isSome() &&
|
||||
nextValidFrom.unwrap().toPrimitive() > nextValidTo.unwrap().toPrimitive()
|
||||
) {
|
||||
return Result.fail(
|
||||
new InvalidTaxDefinitionValidityPeriodError("validFrom must be <= validTo")
|
||||
);
|
||||
}
|
||||
let hasChanges = false;
|
||||
|
||||
// Only allow updating mutable fields
|
||||
@ -290,6 +308,12 @@ export class TaxDefinition extends AggregateRoot<TaxDefinitionInternalProps> {
|
||||
}
|
||||
|
||||
public disable(): Result<boolean, Error> {
|
||||
if (this.isSystem) {
|
||||
return Result.fail(
|
||||
new TaxDefinitionCannotBeDisabledError("System tax definitions cannot be disabled.")
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.isActive) {
|
||||
return Result.ok(false);
|
||||
}
|
||||
|
||||
@ -95,16 +95,19 @@ export const buildTaxDefinitionsDependencies = (
|
||||
deleteTaxDefinitionById: () =>
|
||||
new DeleteTaxDefinitionByIdUseCase({
|
||||
deleter,
|
||||
finder,
|
||||
}),
|
||||
|
||||
disableTaxDefinitionById: () =>
|
||||
new DisableTaxDefinitionByIdUseCase({
|
||||
statusChanger,
|
||||
fullSnapshotBuilder,
|
||||
}),
|
||||
|
||||
enableTaxDefinitionById: () =>
|
||||
new EnableTaxDefinitionByIdUseCase({
|
||||
statusChanger,
|
||||
fullSnapshotBuilder,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import type { TaxDefinitionDetailDTO } from "../../../../common";
|
||||
import { GetTaxDefinitionByIdAdapter } from "./get-tax-definition-by-id.adapter";
|
||||
|
||||
test("adapta el detalle completo a camelCase y conserva nullables", () => {
|
||||
const dto: TaxDefinitionDetailDTO = {
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
company_id: "22222222-2222-4222-8222-222222222222",
|
||||
code: "iva_21", name: "IVA 21", description: null,
|
||||
rate: { value: "2100", scale: "2" }, tax_family: "iva", calculation_behavior: "additive",
|
||||
jurisdiction_country_code: "ES", jurisdiction_region_code: null, tax_scope: "domestic",
|
||||
invoice_note: null, allowed_surcharge_codes: ["rec_5_2", "rec_1_4"],
|
||||
is_system: true, is_active: false, valid_from: "2026-01-01", valid_to: null,
|
||||
};
|
||||
|
||||
assert.deepEqual(GetTaxDefinitionByIdAdapter.fromDto(dto), {
|
||||
id: dto.id, companyId: dto.company_id, code: "iva_21", name: "IVA 21", description: null,
|
||||
rate: 21, taxFamily: "iva", calculationBehavior: "additive",
|
||||
jurisdictionCountryCode: "ES", jurisdictionRegionCode: null, taxScope: "domestic",
|
||||
invoiceNote: null, allowedSurchargeCodes: ["rec_5_2", "rec_1_4"],
|
||||
isSystem: true, isActive: false, validFrom: "2026-01-01", validTo: null,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
import { Percentage } from "@repo/rdx-ddd";
|
||||
|
||||
import type { TaxDefinitionDetailDTO } from "../../../../common";
|
||||
import type { TaxDefinition } from "../entities";
|
||||
|
||||
const percentageDtoToNumber = (dto: TaxDefinitionDetailDTO["rate"]): number => {
|
||||
const result = Percentage.fromObjectString(dto);
|
||||
if (result.isFailure) {
|
||||
throw result.error;
|
||||
}
|
||||
return result.data.toNumber();
|
||||
};
|
||||
|
||||
export const GetTaxDefinitionByIdAdapter = {
|
||||
fromDto(dto: TaxDefinitionDetailDTO): TaxDefinition {
|
||||
return {
|
||||
id: dto.id,
|
||||
companyId: dto.company_id,
|
||||
code: dto.code,
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
rate: percentageDtoToNumber(dto.rate),
|
||||
taxFamily: dto.tax_family,
|
||||
calculationBehavior: dto.calculation_behavior,
|
||||
jurisdictionCountryCode: dto.jurisdiction_country_code,
|
||||
jurisdictionRegionCode: dto.jurisdiction_region_code,
|
||||
taxScope: dto.tax_scope,
|
||||
invoiceNote: dto.invoice_note,
|
||||
allowedSurchargeCodes: dto.allowed_surcharge_codes,
|
||||
isSystem: dto.is_system,
|
||||
isActive: dto.is_active,
|
||||
validFrom: dto.valid_from,
|
||||
validTo: dto.valid_to,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./get-tax-definition-by-id.adapter";
|
||||
export * from "./list-tax-definitions.adapter";
|
||||
@ -0,0 +1,23 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import type { ListTaxDefinitionsResponseDTO } from "../../../../common";
|
||||
import { ListTaxDefinitionsAdapter } from "./list-tax-definitions.adapter";
|
||||
|
||||
test("adapta paginación y filas ligeras preservando flags", () => {
|
||||
const dto: ListTaxDefinitionsResponseDTO = {
|
||||
page: 1, per_page: 25, total_pages: 3, total_items: 52,
|
||||
metadata: { entity: "tax_definitions", criteria: { q: "iva", filters: [], orderBy: "code", orderType: "ASC", pageSize: 25, pageNumber: 1 } },
|
||||
items: [
|
||||
{ id: "11111111-1111-4111-8111-111111111111", company_id: "22222222-2222-4222-8222-222222222222", code: "iva_21", name: "IVA 21", is_active: true, is_system: false },
|
||||
{ id: "33333333-3333-4333-8333-333333333333", company_id: "22222222-2222-4222-8222-222222222222", code: "iva_10", name: "IVA 10", is_active: false, is_system: true },
|
||||
],
|
||||
};
|
||||
|
||||
assert.deepEqual(ListTaxDefinitionsAdapter.fromDto(dto), {
|
||||
page: 1, perPage: 25, totalPages: 3, totalItems: 52,
|
||||
items: [
|
||||
{ id: dto.items[0].id, companyId: dto.items[0].company_id, code: "iva_21", name: "IVA 21", isActive: true, isSystem: false },
|
||||
{ id: dto.items[1].id, companyId: dto.items[1].company_id, code: "iva_10", name: "IVA 10", isActive: false, isSystem: true },
|
||||
],
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,17 @@
|
||||
import type { ListTaxDefinitionsResponseDTO } from "../../../../common";
|
||||
import type { ListTaxDefinitionsResult } from "../api";
|
||||
import type { TaxDefinitionList, TaxDefinitionListRow } from "../entities";
|
||||
|
||||
type TaxDefinitionListItemDTO = ListTaxDefinitionsResponseDTO["items"][number];
|
||||
|
||||
const TaxDefinitionListRowAdapter = {
|
||||
fromDto(dto: TaxDefinitionListItemDTO): TaxDefinitionListRow {
|
||||
return { id: dto.id, companyId: dto.company_id, code: dto.code, name: dto.name, isSystem: dto.is_system, isActive: dto.is_active };
|
||||
},
|
||||
};
|
||||
|
||||
export const ListTaxDefinitionsAdapter = {
|
||||
fromDto(dto: ListTaxDefinitionsResult): TaxDefinitionList {
|
||||
return { page: dto.page, perPage: dto.per_page, totalPages: dto.total_pages, totalItems: dto.total_items, items: dto.items.map(TaxDefinitionListRowAdapter.fromDto) };
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
import type { CreateTaxDefinitionRequestDTO, CreateTaxDefinitionResponseDTO } from "../../../../common";
|
||||
|
||||
export interface CreateTaxDefinitionParams { id: string; data: CreateTaxDefinitionRequestDTO; }
|
||||
export type CreateTaxDefinitionResult = CreateTaxDefinitionResponseDTO;
|
||||
|
||||
export const createTaxDefinition = (dataSource: IDataSource, params: CreateTaxDefinitionParams) =>
|
||||
dataSource.createOne<CreateTaxDefinitionRequestDTO, CreateTaxDefinitionResponseDTO>("catalogs/tax-definitions", params.data);
|
||||
@ -0,0 +1,7 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
export interface DeleteTaxDefinitionByIdParams { taxDefinitionId: string; }
|
||||
export type DeleteTaxDefinitionByIdResult = void;
|
||||
|
||||
export const deleteTaxDefinitionById = (dataSource: IDataSource, params: DeleteTaxDefinitionByIdParams) =>
|
||||
dataSource.deleteOne("catalogs/tax-definitions", params.taxDefinitionId);
|
||||
@ -0,0 +1,8 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
import type { DisableTaxDefinitionByIdResponseDTO } from "../../../../common";
|
||||
|
||||
export interface DisableTaxDefinitionByIdParams { taxDefinitionId: string; }
|
||||
export type DisableTaxDefinitionByIdResult = DisableTaxDefinitionByIdResponseDTO;
|
||||
|
||||
export const disableTaxDefinitionById = (dataSource: IDataSource, params: DisableTaxDefinitionByIdParams) =>
|
||||
dataSource.custom<undefined, DisableTaxDefinitionByIdResponseDTO>({ method: "patch", path: `catalogs/tax-definitions/${params.taxDefinitionId}/disable`, data: undefined });
|
||||
@ -0,0 +1,8 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
import type { EnableTaxDefinitionByIdResponseDTO } from "../../../../common";
|
||||
|
||||
export interface EnableTaxDefinitionByIdParams { taxDefinitionId: string; }
|
||||
export type EnableTaxDefinitionByIdResult = EnableTaxDefinitionByIdResponseDTO;
|
||||
|
||||
export const enableTaxDefinitionById = (dataSource: IDataSource, params: EnableTaxDefinitionByIdParams) =>
|
||||
dataSource.custom<undefined, EnableTaxDefinitionByIdResponseDTO>({ method: "patch", path: `catalogs/tax-definitions/${params.taxDefinitionId}/enable`, data: undefined });
|
||||
@ -0,0 +1,7 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
import type { GetTaxDefinitionByIdResponseDTO } from "../../../../common";
|
||||
|
||||
export interface GetTaxDefinitionByIdParams { taxDefinitionId: string; signal?: AbortSignal; }
|
||||
|
||||
export const getTaxDefinitionById = (dataSource: IDataSource, params: GetTaxDefinitionByIdParams) =>
|
||||
dataSource.getOne<GetTaxDefinitionByIdResponseDTO>("catalogs/tax-definitions", params.taxDefinitionId, { signal: params.signal });
|
||||
@ -0,0 +1,7 @@
|
||||
export * from "./create-tax-definition.api";
|
||||
export * from "./delete-tax-definition-by-id.api";
|
||||
export * from "./disable-tax-definition-by-id.api";
|
||||
export * from "./enable-tax-definition-by-id.api";
|
||||
export * from "./get-tax-definition-by-id.api";
|
||||
export * from "./list-tax-definitions.api";
|
||||
export * from "./update-tax-definition-by-id.api";
|
||||
@ -0,0 +1,12 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
import type { ListTaxDefinitionsResponseDTO } from "../../../../common";
|
||||
|
||||
export interface ListTaxDefinitionsParams { criteria?: Partial<CriteriaDTO>; signal?: AbortSignal; }
|
||||
export type ListTaxDefinitionsResult = ListTaxDefinitionsResponseDTO;
|
||||
|
||||
export const listTaxDefinitions = (dataSource: IDataSource, params: ListTaxDefinitionsParams) =>
|
||||
dataSource.getList<ListTaxDefinitionsResponseDTO>("catalogs/tax-definitions", {
|
||||
signal: params.signal,
|
||||
...params.criteria,
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
import type { UpdateTaxDefinitionByIdRequestDTO, UpdateTaxDefinitionByIdResponseDTO } from "../../../../common";
|
||||
|
||||
export interface UpdateTaxDefinitionByIdParams { taxDefinitionId: string; data: UpdateTaxDefinitionByIdRequestDTO; }
|
||||
export type UpdateTaxDefinitionByIdResult = UpdateTaxDefinitionByIdResponseDTO;
|
||||
|
||||
export const updateTaxDefinitionById = (dataSource: IDataSource, params: UpdateTaxDefinitionByIdParams) =>
|
||||
dataSource.updateOne<UpdateTaxDefinitionByIdRequestDTO, UpdateTaxDefinitionByIdResponseDTO>("catalogs/tax-definitions", params.taxDefinitionId, params.data);
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./tax-definition.entity";
|
||||
export * from "./tax-definition-list.entity";
|
||||
export * from "./tax-definition-list-row.entity";
|
||||
@ -0,0 +1,8 @@
|
||||
export interface TaxDefinitionListRow {
|
||||
id: string;
|
||||
companyId: string;
|
||||
code: string;
|
||||
name: string;
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import type { TaxDefinitionListRow } from "./tax-definition-list-row.entity";
|
||||
|
||||
export interface TaxDefinitionList {
|
||||
items: TaxDefinitionListRow[];
|
||||
page: number;
|
||||
perPage: number;
|
||||
totalPages: number;
|
||||
totalItems: number;
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
export type TaxDefinitionFamily =
|
||||
| "iva" | "igic" | "ipsi" | "equivalence_surcharge" | "withholding" | "vat"
|
||||
| "gst" | "sales_tax" | "reverse_charge" | "exempt" | "not_subject" | "custom";
|
||||
export type TaxCalculationBehavior = "additive" | "subtractive" | "neutral";
|
||||
export type TaxScope = "domestic" | "intra_eu" | "export" | "import" | "international";
|
||||
|
||||
export interface TaxDefinition {
|
||||
id: string;
|
||||
companyId: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
rate: number;
|
||||
taxFamily: TaxDefinitionFamily;
|
||||
calculationBehavior: TaxCalculationBehavior;
|
||||
jurisdictionCountryCode: string;
|
||||
jurisdictionRegionCode: string | null;
|
||||
taxScope: TaxScope;
|
||||
invoiceNote: string | null;
|
||||
allowedSurchargeCodes: string[] | null;
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
validFrom: string | null;
|
||||
validTo: string | null;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export * from "./keys";
|
||||
export * from "./use-tax-definition-create-mutation";
|
||||
export * from "./use-tax-definition-delete-mutation";
|
||||
export * from "./use-tax-definition-detail-query";
|
||||
export * from "./use-tax-definition-disable-mutation";
|
||||
export * from "./use-tax-definition-enable-mutation";
|
||||
export * from "./use-tax-definition-list-query";
|
||||
export * from "./use-tax-definition-update-mutation";
|
||||
@ -0,0 +1,33 @@
|
||||
import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria";
|
||||
import type { QueryKey } from "@tanstack/react-query";
|
||||
import type { ListTaxDefinitionsRequestDTO } from "../../../../common";
|
||||
|
||||
export const LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX = ["tax-definitions"] as const;
|
||||
export const TAX_DEFINITIONS_DETAIL_QUERY_KEY_PREFIX = ["tax-definitions:detail"] as const;
|
||||
|
||||
export const LIST_TAX_DEFINITIONS_QUERY_KEY = (criteria?: Partial<ListTaxDefinitionsRequestDTO>): QueryKey => [
|
||||
...LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX,
|
||||
{
|
||||
pageNumber: criteria?.pageNumber ?? INITIAL_PAGE_INDEX,
|
||||
pageSize: criteria?.pageSize ?? INITIAL_PAGE_SIZE,
|
||||
q: criteria?.q ?? "",
|
||||
filters: criteria?.filters ?? [],
|
||||
orderBy: criteria?.orderBy ?? "",
|
||||
order: criteria?.order ?? "",
|
||||
},
|
||||
] as const;
|
||||
export const TAX_DEFINITION_QUERY_KEY = (taxDefinitionId?: string): QueryKey => [
|
||||
...TAX_DEFINITIONS_DETAIL_QUERY_KEY_PREFIX,
|
||||
{ taxDefinitionId },
|
||||
];
|
||||
export const CREATE_TAX_DEFINITION_MUTATION_KEY = ["tax-definitions:create"] as const;
|
||||
export const UPDATE_TAX_DEFINITION_MUTATION_KEY = ["tax-definitions:update"] as const;
|
||||
export const DELETE_TAX_DEFINITION_MUTATION_KEY = ["tax-definitions:delete"] as const;
|
||||
export const ENABLE_TAX_DEFINITION_MUTATION_KEY = ["tax-definitions:enable"] as const;
|
||||
export const DISABLE_TAX_DEFINITION_MUTATION_KEY = ["tax-definitions:disable"] as const;
|
||||
|
||||
export const taxDefinitionKeys = {
|
||||
all: LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX,
|
||||
list: (criteria?: Partial<ListTaxDefinitionsRequestDTO>) => LIST_TAX_DEFINITIONS_QUERY_KEY(criteria),
|
||||
detail: (taxDefinitionId?: string) => TAX_DEFINITION_QUERY_KEY(taxDefinitionId),
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
import type { z } from "zod/v4";
|
||||
import type { ValidationErrorDetail } from "@repo/rdx-ddd";
|
||||
|
||||
export const toValidationErrors = (error: z.ZodError): ValidationErrorDetail[] =>
|
||||
error.issues.map((issue) => ({ path: issue.path.join("."), message: issue.message }));
|
||||
@ -0,0 +1,27 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { CreateTaxDefinitionRequestSchema } from "../../../../common";
|
||||
import { GetTaxDefinitionByIdAdapter } from "../adapters";
|
||||
import { createTaxDefinition, type CreateTaxDefinitionParams } from "../api";
|
||||
import type { TaxDefinition } from "../entities";
|
||||
import { CREATE_TAX_DEFINITION_MUTATION_KEY, LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX, TAX_DEFINITION_QUERY_KEY } from "./keys";
|
||||
import { toValidationErrors } from "./to-validation-errors";
|
||||
|
||||
export const useTaxDefinitionCreateMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
return useMutation<TaxDefinition, DefaultError, CreateTaxDefinitionParams>({
|
||||
mutationKey: CREATE_TAX_DEFINITION_MUTATION_KEY,
|
||||
mutationFn: async (params) => {
|
||||
if (!params.id) throw new Error("taxDefinitionId is required");
|
||||
const validation = CreateTaxDefinitionRequestSchema.safeParse(params.data);
|
||||
if (!validation.success) throw new ValidationErrorCollection("Validation failed", toValidationErrors(validation.error));
|
||||
return GetTaxDefinitionByIdAdapter.fromDto(await createTaxDefinition(dataSource, params));
|
||||
},
|
||||
onSuccess: async (taxDefinition) => {
|
||||
queryClient.setQueryData(TAX_DEFINITION_QUERY_KEY(taxDefinition.id), taxDefinition);
|
||||
await queryClient.invalidateQueries({ queryKey: LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX });
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { deleteTaxDefinitionById, type DeleteTaxDefinitionByIdParams } from "../api";
|
||||
import { DELETE_TAX_DEFINITION_MUTATION_KEY, LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX, TAX_DEFINITION_QUERY_KEY } from "./keys";
|
||||
|
||||
export const useTaxDefinitionDeleteMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
return useMutation<void, DefaultError, DeleteTaxDefinitionByIdParams>({
|
||||
mutationKey: DELETE_TAX_DEFINITION_MUTATION_KEY,
|
||||
mutationFn: async (params) => { await deleteTaxDefinitionById(dataSource, params); },
|
||||
onSuccess: async (_result, params) => {
|
||||
queryClient.removeQueries({ queryKey: TAX_DEFINITION_QUERY_KEY(params.taxDefinitionId) });
|
||||
await queryClient.invalidateQueries({ queryKey: LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX });
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||
import { GetTaxDefinitionByIdAdapter } from "../adapters";
|
||||
import { getTaxDefinitionById } from "../api";
|
||||
import type { TaxDefinition } from "../entities";
|
||||
import { TAX_DEFINITION_QUERY_KEY } from "./keys";
|
||||
|
||||
export const useTaxDefinitionDetailQuery = (taxDefinitionId?: string, options?: { enabled?: boolean }): UseQueryResult<TaxDefinition, DefaultError> => {
|
||||
const dataSource = useDataSource();
|
||||
const enabled = (options?.enabled ?? true) && Boolean(taxDefinitionId);
|
||||
return useQuery<TaxDefinition, DefaultError>({
|
||||
queryKey: TAX_DEFINITION_QUERY_KEY(taxDefinitionId),
|
||||
enabled,
|
||||
queryFn: async ({ signal }) => GetTaxDefinitionByIdAdapter.fromDto(await getTaxDefinitionById(dataSource, { taxDefinitionId: taxDefinitionId!, signal })),
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { GetTaxDefinitionByIdAdapter } from "../adapters";
|
||||
import { disableTaxDefinitionById, type DisableTaxDefinitionByIdParams } from "../api";
|
||||
import type { TaxDefinition } from "../entities";
|
||||
import { DISABLE_TAX_DEFINITION_MUTATION_KEY, LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX, TAX_DEFINITION_QUERY_KEY } from "./keys";
|
||||
|
||||
export const useTaxDefinitionDisableMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
return useMutation<TaxDefinition, DefaultError, DisableTaxDefinitionByIdParams>({
|
||||
mutationKey: DISABLE_TAX_DEFINITION_MUTATION_KEY,
|
||||
mutationFn: async (params) => GetTaxDefinitionByIdAdapter.fromDto(await disableTaxDefinitionById(dataSource, params)),
|
||||
onSuccess: async (taxDefinition) => {
|
||||
queryClient.setQueryData(TAX_DEFINITION_QUERY_KEY(taxDefinition.id), taxDefinition);
|
||||
await queryClient.invalidateQueries({ queryKey: LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX });
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { GetTaxDefinitionByIdAdapter } from "../adapters";
|
||||
import { enableTaxDefinitionById, type EnableTaxDefinitionByIdParams } from "../api";
|
||||
import type { TaxDefinition } from "../entities";
|
||||
import { ENABLE_TAX_DEFINITION_MUTATION_KEY, LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX, TAX_DEFINITION_QUERY_KEY } from "./keys";
|
||||
|
||||
export const useTaxDefinitionEnableMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
return useMutation<TaxDefinition, DefaultError, EnableTaxDefinitionByIdParams>({
|
||||
mutationKey: ENABLE_TAX_DEFINITION_MUTATION_KEY,
|
||||
mutationFn: async (params) => GetTaxDefinitionByIdAdapter.fromDto(await enableTaxDefinitionById(dataSource, params)),
|
||||
onSuccess: async (taxDefinition) => {
|
||||
queryClient.setQueryData(TAX_DEFINITION_QUERY_KEY(taxDefinition.id), taxDefinition);
|
||||
await queryClient.invalidateQueries({ queryKey: LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX });
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,20 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||
import { ListTaxDefinitionsAdapter } from "../adapters";
|
||||
import { listTaxDefinitions } from "../api";
|
||||
import type { TaxDefinitionList } from "../entities";
|
||||
import { LIST_TAX_DEFINITIONS_QUERY_KEY } from "./keys";
|
||||
|
||||
export interface TaxDefinitionListQueryOptions { enabled?: boolean; criteria?: Partial<CriteriaDTO>; }
|
||||
|
||||
export const useTaxDefinitionListQuery = (options?: TaxDefinitionListQueryOptions): UseQueryResult<TaxDefinitionList, DefaultError> => {
|
||||
const dataSource = useDataSource();
|
||||
const criteria = options?.criteria ?? {};
|
||||
return useQuery<TaxDefinitionList, DefaultError>({
|
||||
queryKey: LIST_TAX_DEFINITIONS_QUERY_KEY(criteria),
|
||||
enabled: options?.enabled ?? true,
|
||||
queryFn: async ({ signal }) => ListTaxDefinitionsAdapter.fromDto(await listTaxDefinitions(dataSource, { signal, criteria })),
|
||||
placeholderData: (previousData) => previousData,
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { UpdateTaxDefinitionByIdRequestSchema } from "../../../../common";
|
||||
import { GetTaxDefinitionByIdAdapter } from "../adapters";
|
||||
import { type UpdateTaxDefinitionByIdParams, updateTaxDefinitionById } from "../api";
|
||||
import type { TaxDefinition } from "../entities";
|
||||
import { LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX, TAX_DEFINITION_QUERY_KEY, UPDATE_TAX_DEFINITION_MUTATION_KEY } from "./keys";
|
||||
import { toValidationErrors } from "./to-validation-errors";
|
||||
|
||||
export const useTaxDefinitionUpdateMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
return useMutation<TaxDefinition, DefaultError, UpdateTaxDefinitionByIdParams>({
|
||||
mutationKey: UPDATE_TAX_DEFINITION_MUTATION_KEY,
|
||||
mutationFn: async (params) => {
|
||||
if (!params.taxDefinitionId) throw new Error("taxDefinitionId is required");
|
||||
const validation = UpdateTaxDefinitionByIdRequestSchema.safeParse(params.data);
|
||||
if (!validation.success) throw new ValidationErrorCollection("Validation failed", toValidationErrors(validation.error));
|
||||
return GetTaxDefinitionByIdAdapter.fromDto(await updateTaxDefinitionById(dataSource, params));
|
||||
},
|
||||
onSuccess: async (taxDefinition) => {
|
||||
queryClient.setQueryData(TAX_DEFINITION_QUERY_KEY(taxDefinition.id), taxDefinition);
|
||||
await queryClient.invalidateQueries({ queryKey: LIST_TAX_DEFINITIONS_QUERY_KEY_PREFIX });
|
||||
},
|
||||
});
|
||||
};
|
||||
4
modules/catalogs/src/web/tax-definitions/shared/index.ts
Normal file
4
modules/catalogs/src/web/tax-definitions/shared/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./adapters";
|
||||
export * from "./api";
|
||||
export * from "./entities";
|
||||
export * from "./hooks";
|
||||
Loading…
Reference in New Issue
Block a user