PROFORMAS UPDATE
This commit is contained in:
parent
982ed7d562
commit
bd209374bc
@ -3,4 +3,5 @@ export * from "./proforma-finder.di";
|
|||||||
export * from "./proforma-input-mappers.di";
|
export * from "./proforma-input-mappers.di";
|
||||||
export * from "./proforma-issuer.di";
|
export * from "./proforma-issuer.di";
|
||||||
export * from "./proforma-snapshot-builders.di";
|
export * from "./proforma-snapshot-builders.di";
|
||||||
|
export * from "./proforma-updater.di";
|
||||||
export * from "./proforma-use-cases.di";
|
export * from "./proforma-use-cases.di";
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
import type { ICatalogs } from "@erp/core/api";
|
import type { ICatalogs } from "@erp/core/api";
|
||||||
|
|
||||||
import { CreateProformaInputMapper, type ICreateProformaInputMapper } from "../mappers";
|
import {
|
||||||
|
CreateProformaInputMapper,
|
||||||
|
type ICreateProformaInputMapper,
|
||||||
|
type IUpdateProformaInputMapper,
|
||||||
|
UpdateProformaInputMapper,
|
||||||
|
} from "../mappers";
|
||||||
|
|
||||||
export interface IProformaInputMappers {
|
export interface IProformaInputMappers {
|
||||||
createInputMapper: ICreateProformaInputMapper;
|
createInputMapper: ICreateProformaInputMapper;
|
||||||
|
updateInputMapper: IUpdateProformaInputMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildProformaInputMappers = (catalogs: ICatalogs): IProformaInputMappers => {
|
export const buildProformaInputMappers = (catalogs: ICatalogs): IProformaInputMappers => {
|
||||||
const { taxCatalog } = catalogs;
|
const { taxCatalog } = catalogs;
|
||||||
|
|
||||||
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
|
// Mappers el DTO a las props validadas (ProformaProps) y luego construir agregado
|
||||||
const createInputMapper = new CreateProformaInputMapper({ taxCatalog });
|
const createInputMapper = new CreateProformaInputMapper({ taxCatalog });
|
||||||
//const updateProformaInputMapper = new UpdateProformaInputMapper();
|
const updateInputMapper = new UpdateProformaInputMapper();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createInputMapper,
|
createInputMapper,
|
||||||
|
updateInputMapper,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
import { type IProformaUpdater, ProformaUpdater } from "../services";
|
||||||
|
|
||||||
|
export const buildProformaUpdater = (params: {
|
||||||
|
repository: IProformaRepository;
|
||||||
|
}): IProformaUpdater => {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new ProformaUpdater({
|
||||||
|
repository,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,11 +1,12 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
|
||||||
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
||||||
import type { ICreateProformaInputMapper } from "../mappers";
|
import type { ICreateProformaInputMapper, IUpdateProformaInputMapper } from "../mappers";
|
||||||
import type {
|
import type {
|
||||||
IProformaCreator,
|
IProformaCreator,
|
||||||
IProformaFinder,
|
IProformaFinder,
|
||||||
IProformaIssuer,
|
IProformaIssuer,
|
||||||
|
IProformaUpdater,
|
||||||
ProformaDocumentGeneratorService,
|
ProformaDocumentGeneratorService,
|
||||||
} from "../services";
|
} from "../services";
|
||||||
import type {
|
import type {
|
||||||
@ -20,6 +21,7 @@ import {
|
|||||||
ListProformasUseCase,
|
ListProformasUseCase,
|
||||||
ReportProformaUseCase,
|
ReportProformaUseCase,
|
||||||
} from "../use-cases";
|
} from "../use-cases";
|
||||||
|
import { UpdateProformaUseCase } from "../use-cases/update-proforma.use-case";
|
||||||
|
|
||||||
export function buildGetProformaByIdUseCase(deps: {
|
export function buildGetProformaByIdUseCase(deps: {
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
@ -93,18 +95,25 @@ export function buildIssueProformaUseCase(deps: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*export function buildUpdateProformaUseCase(deps: {
|
export function buildUpdateProformaUseCase(deps: {
|
||||||
finder: IProformaFinder;
|
updater: IProformaUpdater;
|
||||||
|
dtoMapper: IUpdateProformaInputMapper;
|
||||||
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
}) {
|
}) {
|
||||||
return new UpdateProformaUseCase(deps.finder, deps.fullSnapshotBuilder);
|
return new UpdateProformaUseCase({
|
||||||
|
dtoMapper: deps.dtoMapper,
|
||||||
|
updater: deps.updater,
|
||||||
|
fullSnapshotBuilder: deps.fullSnapshotBuilder,
|
||||||
|
transactionManager: deps.transactionManager,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
export function buildDeleteProformaUseCase(deps: { finder: IProformaFinder }) {
|
export function buildDeleteProformaUseCase(deps: { finder: IProformaFinder }) {
|
||||||
return new DeleteProformaUseCase(deps.finder);
|
return new DeleteProformaUseCase(deps.finder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function buildChangeStatusProformaUseCase(deps: {
|
export function buildChangeStatusProformaUseCase(deps: {
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { InvoiceSerie, type ProformaPatchProps } from "../../../domain";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* UpdateProformaPropsMapper
|
* UpdateProformaPropsMapper
|
||||||
* Convierte el DTO a las props validadas (CustomerInvoiceProps).
|
* Convierte el DTO a las props validadas (ProformaInvoiceProps).
|
||||||
* No construye directamente el agregado.
|
* No construye directamente el agregado.
|
||||||
* Tri-estado:
|
* Tri-estado:
|
||||||
* - campo omitido → no se cambia
|
* - campo omitido → no se cambia
|
||||||
@ -29,105 +29,114 @@ import { InvoiceSerie, type ProformaPatchProps } from "../../../domain";
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function UpdateProformaInputMapper(dto: UpdateProformaByIdRequestDTO) {
|
export interface IUpdateProformaInputMapper {
|
||||||
try {
|
map(
|
||||||
const errors: ValidationErrorDetail[] = [];
|
dto: UpdateProformaByIdRequestDTO,
|
||||||
const props: ProformaPatchProps = {};
|
params: { companyId: UniqueID }
|
||||||
|
): Result<ProformaPatchProps>;
|
||||||
|
}
|
||||||
|
|
||||||
toPatchField(dto.series).ifSet((series) => {
|
export class UpdateProformaInputMapper implements IUpdateProformaInputMapper {
|
||||||
props.series = extractOrPushError(
|
public map(dto: UpdateProformaByIdRequestDTO, params: { companyId: UniqueID }) {
|
||||||
maybeFromNullableResult(series, (value) => InvoiceSerie.create(value)),
|
try {
|
||||||
"reference",
|
const errors: ValidationErrorDetail[] = [];
|
||||||
errors
|
const props: ProformaPatchProps = {};
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.invoice_date).ifSet((invoice_date) => {
|
toPatchField(dto.series).ifSet((series) => {
|
||||||
if (isNullishOrEmpty(invoice_date)) {
|
props.series = extractOrPushError(
|
||||||
errors.push({ path: "invoice_date", message: "Invoice date cannot be empty" });
|
maybeFromNullableResult(series, (value) => InvoiceSerie.create(value)),
|
||||||
return;
|
"reference",
|
||||||
}
|
errors
|
||||||
props.invoiceDate = extractOrPushError(
|
);
|
||||||
UtcDate.createFromISO(invoice_date!),
|
});
|
||||||
"invoice_date",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.operation_date).ifSet((operation_date) => {
|
toPatchField(dto.invoice_date).ifSet((invoice_date) => {
|
||||||
props.operationDate = extractOrPushError(
|
if (isNullishOrEmpty(invoice_date)) {
|
||||||
maybeFromNullableResult(operation_date, (value) => UtcDate.createFromISO(value)),
|
errors.push({ path: "invoice_date", message: "Invoice date cannot be empty" });
|
||||||
"operation_date",
|
return;
|
||||||
errors
|
}
|
||||||
);
|
props.invoiceDate = extractOrPushError(
|
||||||
});
|
UtcDate.createFromISO(invoice_date!),
|
||||||
|
"invoice_date",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
toPatchField(dto.customer_id).ifSet((customer_id) => {
|
toPatchField(dto.operation_date).ifSet((operation_date) => {
|
||||||
if (isNullishOrEmpty(customer_id)) {
|
props.operationDate = extractOrPushError(
|
||||||
errors.push({ path: "customer_id", message: "Customer cannot be empty" });
|
maybeFromNullableResult(operation_date, (value) => UtcDate.createFromISO(value)),
|
||||||
return;
|
"operation_date",
|
||||||
}
|
errors
|
||||||
props.customerId = extractOrPushError(UniqueID.create(customer_id!), "customer_id", errors);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
toPatchField(dto.reference).ifSet((reference) => {
|
toPatchField(dto.customer_id).ifSet((customer_id) => {
|
||||||
props.reference = extractOrPushError(
|
if (isNullishOrEmpty(customer_id)) {
|
||||||
maybeFromNullableResult(reference, (value) => Result.ok(String(value))),
|
errors.push({ path: "customer_id", message: "Proforma cannot be empty" });
|
||||||
"reference",
|
return;
|
||||||
errors
|
}
|
||||||
);
|
props.customerId = extractOrPushError(UniqueID.create(customer_id!), "customer_id", errors);
|
||||||
});
|
});
|
||||||
|
|
||||||
toPatchField(dto.description).ifSet((description) => {
|
toPatchField(dto.reference).ifSet((reference) => {
|
||||||
props.description = extractOrPushError(
|
props.reference = extractOrPushError(
|
||||||
maybeFromNullableResult(description, (value) => Result.ok(String(value))),
|
maybeFromNullableResult(reference, (value) => Result.ok(String(value))),
|
||||||
"description",
|
"reference",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
toPatchField(dto.notes).ifSet((notes) => {
|
toPatchField(dto.description).ifSet((description) => {
|
||||||
props.notes = extractOrPushError(
|
props.description = extractOrPushError(
|
||||||
maybeFromNullableResult(notes, (value) => TextValue.create(value)),
|
maybeFromNullableResult(description, (value) => Result.ok(String(value))),
|
||||||
"notes",
|
"description",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
toPatchField(dto.language_code).ifSet((languageCode) => {
|
toPatchField(dto.notes).ifSet((notes) => {
|
||||||
if (isNullishOrEmpty(languageCode)) {
|
props.notes = extractOrPushError(
|
||||||
errors.push({ path: "language_code", message: "Language code cannot be empty" });
|
maybeFromNullableResult(notes, (value) => TextValue.create(value)),
|
||||||
return;
|
"notes",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.language_code).ifSet((languageCode) => {
|
||||||
|
if (isNullishOrEmpty(languageCode)) {
|
||||||
|
errors.push({ path: "language_code", message: "Language code cannot be empty" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.languageCode = extractOrPushError(
|
||||||
|
LanguageCode.create(languageCode!),
|
||||||
|
"language_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.currency_code).ifSet((currencyCode) => {
|
||||||
|
if (isNullishOrEmpty(currencyCode)) {
|
||||||
|
errors.push({ path: "currency_code", message: "Currency code cannot be empty" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.currencyCode = extractOrPushError(
|
||||||
|
CurrencyCode.create(currencyCode!),
|
||||||
|
"currency_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(
|
||||||
|
new ValidationErrorCollection("Proforma invoice props mapping failed (update)", errors)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
props.languageCode = extractOrPushError(
|
return Result.ok(props);
|
||||||
LanguageCode.create(languageCode!),
|
} catch (err: unknown) {
|
||||||
"language_code",
|
return Result.fail(new DomainError("Proforma invoice props mapping failed", { cause: err }));
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
toPatchField(dto.currency_code).ifSet((currencyCode) => {
|
|
||||||
if (isNullishOrEmpty(currencyCode)) {
|
|
||||||
errors.push({ path: "currency_code", message: "Currency code cannot be empty" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
props.currencyCode = extractOrPushError(
|
|
||||||
CurrencyCode.create(currencyCode!),
|
|
||||||
"currency_code",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice props mapping failed (update)", errors)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(props);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,3 +6,4 @@ export * from "./proforma-finder";
|
|||||||
export * from "./proforma-issuer";
|
export * from "./proforma-issuer";
|
||||||
export * from "./proforma-number-generator.interface";
|
export * from "./proforma-number-generator.interface";
|
||||||
export * from "./proforma-public-services.interface";
|
export * from "./proforma-public-services.interface";
|
||||||
|
export * from "./proforma-updater";
|
||||||
|
|||||||
@ -0,0 +1,64 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { Proforma, ProformaPatchProps } from "../../../domain";
|
||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface IProformaUpdater {
|
||||||
|
update(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
props: ProformaPatchProps;
|
||||||
|
transaction: unknown;
|
||||||
|
}): Promise<Result<Proforma, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProformaUpdaterDeps = {
|
||||||
|
repository: IProformaRepository;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ProformaUpdater implements IProformaUpdater {
|
||||||
|
private readonly repository: IProformaRepository;
|
||||||
|
|
||||||
|
constructor(deps: ProformaUpdaterDeps) {
|
||||||
|
this.repository = deps.repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
props: ProformaPatchProps;
|
||||||
|
transaction: unknown;
|
||||||
|
}): Promise<Result<Proforma, Error>> {
|
||||||
|
const { companyId, id, props, transaction } = params;
|
||||||
|
|
||||||
|
console.log("props => ", props);
|
||||||
|
|
||||||
|
// Recuperar agregado existente
|
||||||
|
const existingResult = await this.repository.getByIdInCompany(companyId, id, transaction);
|
||||||
|
|
||||||
|
if (existingResult.isFailure) {
|
||||||
|
return Result.fail(existingResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proforma = existingResult.data;
|
||||||
|
|
||||||
|
// Aplicar cambios en el agregado
|
||||||
|
const updateResult = proforma.update(props);
|
||||||
|
|
||||||
|
if (updateResult.isFailure) {
|
||||||
|
return Result.fail(updateResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(proforma.operationDate);
|
||||||
|
|
||||||
|
// Persistir cambios
|
||||||
|
const saveResult = await this.repository.update(proforma, transaction);
|
||||||
|
|
||||||
|
if (saveResult.isFailure) {
|
||||||
|
return Result.fail(saveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(proforma);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,4 +5,4 @@ export * from "./get-proforma-by-id.use-case";
|
|||||||
export * from "./issue-proforma.use-case";
|
export * from "./issue-proforma.use-case";
|
||||||
export * from "./list-proformas.use-case";
|
export * from "./list-proformas.use-case";
|
||||||
export * from "./report-proforma.use-case";
|
export * from "./report-proforma.use-case";
|
||||||
//export * from "./update-proforma";
|
export * from "./update-proforma.use-case";
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export class IssueProformaUseCase {
|
|||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
// 1. Recuperamos la issuedinvoice
|
// 1. Recuperamos la proforma
|
||||||
const proformaResult = await this.finder.findProformaById(
|
const proformaResult = await this.finder.findProformaById(
|
||||||
companyId,
|
companyId,
|
||||||
proformaId,
|
proformaId,
|
||||||
|
|||||||
@ -0,0 +1,124 @@
|
|||||||
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { UpdateProformaByIdRequestDTO } from "../../../../common";
|
||||||
|
import type { ProformaPatchProps } from "../../../domain";
|
||||||
|
import type { IUpdateProformaInputMapper } from "../mappers";
|
||||||
|
import type { IProformaUpdater } from "../services";
|
||||||
|
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
type UpdateProformaUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
proforma_id: string;
|
||||||
|
dto: UpdateProformaByIdRequestDTO;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateProformaUseCaseDeps = {
|
||||||
|
dtoMapper: IUpdateProformaInputMapper;
|
||||||
|
updater: IProformaUpdater;
|
||||||
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UpdateProformaUseCase {
|
||||||
|
private readonly dtoMapper: IUpdateProformaInputMapper;
|
||||||
|
private readonly updater: IProformaUpdater;
|
||||||
|
private readonly fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
|
private readonly transactionManager: ITransactionManager;
|
||||||
|
|
||||||
|
constructor(deps: UpdateProformaUseCaseDeps) {
|
||||||
|
this.dtoMapper = deps.dtoMapper;
|
||||||
|
this.updater = deps.updater;
|
||||||
|
this.fullSnapshotBuilder = deps.fullSnapshotBuilder;
|
||||||
|
this.transactionManager = deps.transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public execute(params: UpdateProformaUseCaseInput) {
|
||||||
|
const { companyId, proforma_id, dto } = params;
|
||||||
|
|
||||||
|
const proformaIdOrError = UniqueID.create(proforma_id);
|
||||||
|
if (proformaIdOrError.isFailure) {
|
||||||
|
return Result.fail(proformaIdOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proformaId = proformaIdOrError.data;
|
||||||
|
|
||||||
|
// Mapear DTO → props de dominio
|
||||||
|
const patchPropsResult = this.dtoMapper.map(dto, { companyId });
|
||||||
|
if (patchPropsResult.isFailure) {
|
||||||
|
return patchPropsResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const patchProps: ProformaPatchProps = patchPropsResult.data;
|
||||||
|
|
||||||
|
console.log("dto => ", dto);
|
||||||
|
console.log("patchProps => ", patchProps);
|
||||||
|
|
||||||
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
const updateResult = await this.updater.update({
|
||||||
|
companyId,
|
||||||
|
id: proformaId,
|
||||||
|
props: patchProps,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updateResult.isFailure) {
|
||||||
|
return Result.fail(updateResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(this.fullSnapshotBuilder.toOutput(updateResult.data));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
const presenter = this.presenterRegistry.getPresenter({
|
||||||
|
resource: "proforma",
|
||||||
|
projection: "FULL",
|
||||||
|
}) as ProformaFullPresenter;
|
||||||
|
|
||||||
|
// Mapear DTO → props de dominio
|
||||||
|
const patchPropsResult = mapDTOToUpdateProformaInvoicePatchProps(dto);
|
||||||
|
if (patchPropsResult.isFailure) {
|
||||||
|
return Result.fail(patchPropsResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const patchProps: ProformaPatchProps = patchPropsResult.data;
|
||||||
|
|
||||||
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
|
try {
|
||||||
|
const updatedInvoice = await this.service.patchProformaByIdInCompany(
|
||||||
|
companyId,
|
||||||
|
invoiceId,
|
||||||
|
patchProps,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updatedInvoice.isFailure) {
|
||||||
|
return Result.fail(updatedInvoice.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoiceOrError = await this.service.updateProformaInCompany(
|
||||||
|
companyId,
|
||||||
|
updatedInvoice.data,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
if (invoiceOrError.isFailure) return Result.fail(invoiceOrError.error);
|
||||||
|
|
||||||
|
const invoice = invoiceOrError.data;
|
||||||
|
const dto = presenter.toOutput(invoice);
|
||||||
|
return Result.ok(dto);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./update-proforma.use-case";
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import type { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
|
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { UpdateProformaByIdRequestDTO } from "../../../../../common";
|
|
||||||
import type { ProformaPatchProps } from "../../../../domain";
|
|
||||||
import type { CustomerInvoiceApplicationService } from "../../../services/customer-invoice-application.service";
|
|
||||||
import type { ProformaFullPresenter } from "../../../snapshot-builders";
|
|
||||||
|
|
||||||
type UpdateProformaUseCaseInput = {
|
|
||||||
companyId: UniqueID;
|
|
||||||
proforma_id: string;
|
|
||||||
dto: UpdateProformaByIdRequestDTO;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class UpdateProformaUseCase {
|
|
||||||
constructor(
|
|
||||||
private readonly service: CustomerInvoiceApplicationService,
|
|
||||||
private readonly transactionManager: ITransactionManager,
|
|
||||||
private readonly presenterRegistry: IPresenterRegistry
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public execute(params: UpdateProformaUseCaseInput) {
|
|
||||||
const { companyId, proforma_id, dto } = params;
|
|
||||||
|
|
||||||
const idOrError = UniqueID.create(proforma_id);
|
|
||||||
if (idOrError.isFailure) {
|
|
||||||
return Result.fail(idOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoiceId = idOrError.data;
|
|
||||||
const presenter = this.presenterRegistry.getPresenter({
|
|
||||||
resource: "proforma",
|
|
||||||
projection: "FULL",
|
|
||||||
}) as ProformaFullPresenter;
|
|
||||||
|
|
||||||
// Mapear DTO → props de dominio
|
|
||||||
const patchPropsResult = mapDTOToUpdateCustomerInvoicePatchProps(dto);
|
|
||||||
if (patchPropsResult.isFailure) {
|
|
||||||
return Result.fail(patchPropsResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const patchProps: ProformaPatchProps = patchPropsResult.data;
|
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: unknown) => {
|
|
||||||
try {
|
|
||||||
const updatedInvoice = await this.service.patchProformaByIdInCompany(
|
|
||||||
companyId,
|
|
||||||
invoiceId,
|
|
||||||
patchProps,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updatedInvoice.isFailure) {
|
|
||||||
return Result.fail(updatedInvoice.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoiceOrError = await this.service.updateProformaInCompany(
|
|
||||||
companyId,
|
|
||||||
updatedInvoice.data,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
if (invoiceOrError.isFailure) return Result.fail(invoiceOrError.error);
|
|
||||||
|
|
||||||
const invoice = invoiceOrError.data;
|
|
||||||
const dto = presenter.toOutput(invoice);
|
|
||||||
return Result.ok(dto);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
return Result.fail(error as Error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
type IssueProformaUseCase,
|
type IssueProformaUseCase,
|
||||||
type ListProformasUseCase,
|
type ListProformasUseCase,
|
||||||
type ReportProformaUseCase,
|
type ReportProformaUseCase,
|
||||||
|
type UpdateProformaUseCase,
|
||||||
buildCreateProformaUseCase,
|
buildCreateProformaUseCase,
|
||||||
buildGetProformaByIdUseCase,
|
buildGetProformaByIdUseCase,
|
||||||
buildIssueProformaUseCase,
|
buildIssueProformaUseCase,
|
||||||
@ -17,7 +18,9 @@ import {
|
|||||||
buildProformaIssuer,
|
buildProformaIssuer,
|
||||||
buildProformaSnapshotBuilders,
|
buildProformaSnapshotBuilders,
|
||||||
buildProformaToIssuedInvoicePropsConverter,
|
buildProformaToIssuedInvoicePropsConverter,
|
||||||
|
buildProformaUpdater,
|
||||||
buildReportProformaUseCase,
|
buildReportProformaUseCase,
|
||||||
|
buildUpdateProformaUseCase,
|
||||||
} from "../../../application";
|
} from "../../../application";
|
||||||
|
|
||||||
import { buildProformaDocumentService } from "./proforma-documents.di";
|
import { buildProformaDocumentService } from "./proforma-documents.di";
|
||||||
@ -34,9 +37,9 @@ export type ProformasInternalDeps = {
|
|||||||
issueProforma: (publicServices: {
|
issueProforma: (publicServices: {
|
||||||
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
||||||
}) => IssueProformaUseCase;
|
}) => IssueProformaUseCase;
|
||||||
|
updateProforma: () => UpdateProformaUseCase;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
updateProforma: () => UpdateProformaUseCase;
|
|
||||||
deleteProforma: () => DeleteProformaUseCase;
|
deleteProforma: () => DeleteProformaUseCase;
|
||||||
issueProforma: () => IssueProformaUseCase;
|
issueProforma: () => IssueProformaUseCase;
|
||||||
changeStatusProforma: () => ChangeStatusProformaUseCase;*/
|
changeStatusProforma: () => ChangeStatusProformaUseCase;*/
|
||||||
@ -65,6 +68,8 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
repository,
|
repository,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updater = buildProformaUpdater({ repository });
|
||||||
|
|
||||||
const snapshotBuilders = buildProformaSnapshotBuilders();
|
const snapshotBuilders = buildProformaSnapshotBuilders();
|
||||||
const documentGeneratorPipeline = buildProformaDocumentService(params);
|
const documentGeneratorPipeline = buildProformaDocumentService(params);
|
||||||
|
|
||||||
@ -102,6 +107,14 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
updateProforma: () =>
|
||||||
|
buildUpdateProformaUseCase({
|
||||||
|
updater,
|
||||||
|
dtoMapper: inputMappers.updateInputMapper,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
issueProforma: (publicServices: { issuedInvoiceServices: IIssuedInvoicePublicServices }) =>
|
issueProforma: (publicServices: { issuedInvoiceServices: IIssuedInvoicePublicServices }) =>
|
||||||
buildIssueProformaUseCase({
|
buildIssueProformaUseCase({
|
||||||
publicServices,
|
publicServices,
|
||||||
|
|||||||
@ -17,10 +17,13 @@ import {
|
|||||||
ListProformasRequestSchema,
|
ListProformasRequestSchema,
|
||||||
ReportProformaByIdParamsRequestSchema,
|
ReportProformaByIdParamsRequestSchema,
|
||||||
ReportProformaByIdQueryRequestSchema,
|
ReportProformaByIdQueryRequestSchema,
|
||||||
|
UpdateProformaByIdParamsRequestSchema,
|
||||||
|
UpdateProformaByIdRequestSchema,
|
||||||
} from "../../../../common";
|
} from "../../../../common";
|
||||||
import type { IIssuedInvoicePublicServices } from "../../../application";
|
import type { IIssuedInvoicePublicServices } from "../../../application";
|
||||||
|
|
||||||
import { CreateProformaController } from "./controllers/create-proforma.controller";
|
import { CreateProformaController } from "./controllers/create-proforma.controller";
|
||||||
|
import { UpdateProformaController } from "./controllers/update-proforma.controller";
|
||||||
|
|
||||||
export const proformasRouter = (params: StartParams) => {
|
export const proformasRouter = (params: StartParams) => {
|
||||||
const { app, config, getService, getInternal } = params;
|
const { app, config, getService, getInternal } = params;
|
||||||
@ -102,7 +105,6 @@ export const proformasRouter = (params: StartParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
|
||||||
router.put(
|
router.put(
|
||||||
"/:proforma_id",
|
"/:proforma_id",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
@ -110,13 +112,13 @@ export const proformasRouter = (params: StartParams) => {
|
|||||||
validateRequest(UpdateProformaByIdParamsRequestSchema, "params"),
|
validateRequest(UpdateProformaByIdParamsRequestSchema, "params"),
|
||||||
validateRequest(UpdateProformaByIdRequestSchema, "body"),
|
validateRequest(UpdateProformaByIdRequestSchema, "body"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.update_proforma();
|
const useCase = deps.useCases.updateProforma();
|
||||||
const controller = new UpdateProformaController(useCase);
|
const controller = new UpdateProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
/*router.delete(
|
||||||
"/:proforma_id",
|
"/:proforma_id",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,9 @@ const ProformasListPage = lazy(() =>
|
|||||||
import("./proformas/create").then((m) => ({ default: m.ProformaCreatePage }))
|
import("./proformas/create").then((m) => ({ default: m.ProformaCreatePage }))
|
||||||
);*/
|
);*/
|
||||||
|
|
||||||
/*const InvoiceUpdatePage = lazy(() =>
|
const ProformaUpdatePage = lazy(() =>
|
||||||
import("./pages").then((m) => ({ default: m.InvoiceUpdatePage }))
|
import("./proformas/update").then((m) => ({ default: m.ProformaUpdatePage }))
|
||||||
);*/
|
);
|
||||||
|
|
||||||
const IssuedInvoicesLayout = lazy(() =>
|
const IssuedInvoicesLayout = lazy(() =>
|
||||||
import("./issued-invoices/ui").then((m) => ({ default: m.IssuedInvoicesLayout }))
|
import("./issued-invoices/ui").then((m) => ({ default: m.IssuedInvoicesLayout }))
|
||||||
@ -39,7 +39,7 @@ export const CustomerInvoiceRoutes = (params: ModuleClientParams): RouteObject[]
|
|||||||
{ path: "", index: true, element: <ProformasListPage /> }, // index
|
{ path: "", index: true, element: <ProformasListPage /> }, // index
|
||||||
{ path: "list", element: <ProformasListPage /> },
|
{ path: "list", element: <ProformasListPage /> },
|
||||||
//{ path: "create", element: <ProformaCreatePage /> },
|
//{ path: "create", element: <ProformaCreatePage /> },
|
||||||
//{ path: ":id/edit", element: <InvoiceUpdatePage /> },
|
{ path: ":id/edit", element: <ProformaUpdatePage /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -55,7 +55,7 @@ export const CustomerInvoiceRoutes = (params: ModuleClientParams): RouteObject[]
|
|||||||
/*
|
/*
|
||||||
{ path: "create", element: <CustomerInvoicesList /> },
|
{ path: "create", element: <CustomerInvoicesList /> },
|
||||||
{ path: ":id", element: <CustomerInvoicesList /> },
|
{ path: ":id", element: <CustomerInvoicesList /> },
|
||||||
{ path: ":id/edit", element: <CustomerInvoicesList /> },
|
|
||||||
{ path: ":id/delete", element: <CustomerInvoicesList /> },
|
{ path: ":id/delete", element: <CustomerInvoicesList /> },
|
||||||
{ path: ":id/view", element: <CustomerInvoicesList /> },
|
{ path: ":id/view", element: <CustomerInvoicesList /> },
|
||||||
{ path: ":id/print", element: <CustomerInvoicesList /> },
|
{ path: ":id/print", element: <CustomerInvoicesList /> },
|
||||||
|
|||||||
@ -2,4 +2,3 @@ export * from "../proformas";
|
|||||||
|
|
||||||
export * from "./create";
|
export * from "./create";
|
||||||
export * from "./list";
|
export * from "./list";
|
||||||
export * from "./update";
|
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
export * from "./proforma-create-form.entity";
|
export * from "./proforma-create-form.entity";
|
||||||
export * from "./proforma-create-form.schema";
|
export * from "./proforma-create-form.schema";
|
||||||
export * from "./proforma-create-form-default";
|
export * from "./proforma-create-form-default.entity";
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./proforma-to-proforma-update-form.adapter";
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import type { Proforma } from "../../shared";
|
||||||
|
import type { ProformaUpdateForm } from "../entities";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapea un cliente a un formulario de actualización de cliente.
|
||||||
|
*
|
||||||
|
* @param proforma
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpdateForm => {
|
||||||
|
return {
|
||||||
|
series: proforma.series ?? "",
|
||||||
|
|
||||||
|
invoiceDate: proforma.invoiceDate ?? "",
|
||||||
|
operationDate: proforma.operationDate ?? "",
|
||||||
|
|
||||||
|
customerId: proforma.customerId ?? "",
|
||||||
|
|
||||||
|
description: proforma.description ?? "",
|
||||||
|
reference: proforma.reference ?? "",
|
||||||
|
notes: proforma.notes ?? "",
|
||||||
|
|
||||||
|
languageCode: proforma.languageCode ?? "es",
|
||||||
|
currencyCode: proforma.currencyCode ?? "EUR",
|
||||||
|
|
||||||
|
globalDiscountPercentage: proforma.globalDiscountPercentage ?? 0,
|
||||||
|
|
||||||
|
paymentMethod: proforma.paymentMethod ?? "",
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from "./use-proforma-update-page.controller";
|
export * from "./use-update-proforma-controller";
|
||||||
|
export * from "./use-update-proforma-page-controller";
|
||||||
|
|||||||
@ -0,0 +1,171 @@
|
|||||||
|
import { useHookForm } from "@erp/core/hooks";
|
||||||
|
import { showErrorToast, showSuccessToast, showWarningToast } from "@repo/rdx-ui/helpers";
|
||||||
|
import { useEffect, useId, useMemo } from "react";
|
||||||
|
import type { FieldErrors } from "react-hook-form";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../i18n";
|
||||||
|
import type { UpdateProformaByIdParams } from "../../shared";
|
||||||
|
import type { Proforma } from "../../shared/entities";
|
||||||
|
import { useProformaGetQuery, useProformaUpdateMutation } from "../../shared/hooks";
|
||||||
|
import { mapProformaToProformaUpdateForm } from "../adapters";
|
||||||
|
import {
|
||||||
|
type ProformaUpdateForm,
|
||||||
|
ProformaUpdateFormSchema,
|
||||||
|
defaultProformaUpdateForm,
|
||||||
|
} from "../entities";
|
||||||
|
import {
|
||||||
|
buildProformaUpdatePatch,
|
||||||
|
buildUpdateProformaByIdParams,
|
||||||
|
focusFirstProformaUpdateError,
|
||||||
|
} from "../utils";
|
||||||
|
|
||||||
|
export interface UseUpdateProformaControllerOptions {
|
||||||
|
onUpdated?(updated: Proforma): void;
|
||||||
|
successToasts?: boolean;
|
||||||
|
|
||||||
|
onError?(error: Error, params: UpdateProformaByIdParams): void;
|
||||||
|
errorToasts?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateProformaController = (
|
||||||
|
proformaId?: string,
|
||||||
|
options?: UseUpdateProformaControllerOptions
|
||||||
|
) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const formId = useId();
|
||||||
|
|
||||||
|
// 1) Estado de carga de la proforma (query)
|
||||||
|
const {
|
||||||
|
data: proformaData,
|
||||||
|
isLoading,
|
||||||
|
isError: isLoadError,
|
||||||
|
error: loadError,
|
||||||
|
} = useProformaGetQuery({ id: proformaId, enabled: Boolean(proformaId) });
|
||||||
|
|
||||||
|
// 2) Estado de creación (mutación)
|
||||||
|
const {
|
||||||
|
mutateAsync,
|
||||||
|
isPending: isUpdating,
|
||||||
|
isError: isUpdateError,
|
||||||
|
error: updateError,
|
||||||
|
} = useProformaUpdateMutation();
|
||||||
|
|
||||||
|
const initialValues = useMemo<ProformaUpdateForm>(() => {
|
||||||
|
if (!proformaData) return defaultProformaUpdateForm;
|
||||||
|
|
||||||
|
return mapProformaToProformaUpdateForm(proformaData);
|
||||||
|
}, [proformaData]);
|
||||||
|
|
||||||
|
// 3) Form hook
|
||||||
|
const form = useHookForm<ProformaUpdateForm>({
|
||||||
|
resolverSchema: ProformaUpdateFormSchema,
|
||||||
|
initialValues,
|
||||||
|
disabled: isLoading || isUpdating,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!proformaData) return;
|
||||||
|
|
||||||
|
console.log("Reseteando form con datos de la proforma:", proformaData);
|
||||||
|
form.reset(mapProformaToProformaUpdateForm(proformaData), {
|
||||||
|
keepDirty: false, // <-- importante: no marca el form como "dirty" al cargar los datos reales
|
||||||
|
});
|
||||||
|
}, [proformaData, form]);
|
||||||
|
|
||||||
|
/** Handlers */
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
const initialData = proformaData
|
||||||
|
? mapProformaToProformaUpdateForm(proformaData)
|
||||||
|
: defaultProformaUpdateForm;
|
||||||
|
|
||||||
|
form.reset(initialData, { keepDirty: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitHandler = form.handleSubmit(
|
||||||
|
async (formData: ProformaUpdateForm) => {
|
||||||
|
if (!proformaId) {
|
||||||
|
showErrorToast(t("proformas.update.error.title"), t("proformas.update.error.missing_id"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousData = proformaData;
|
||||||
|
|
||||||
|
const patchData = buildProformaUpdatePatch(formData, form.formState.dirtyFields);
|
||||||
|
const params = buildUpdateProformaByIdParams(proformaId, patchData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Enviamos cambios al servidor
|
||||||
|
const updated = await mutateAsync(params);
|
||||||
|
|
||||||
|
// Ha ido bien -> actualizamos form con datos reales
|
||||||
|
// keepDirty = false -> deja el formulario sin cambios sin tener que esperar al siguiente render.
|
||||||
|
form.reset(mapProformaToProformaUpdateForm(updated), {
|
||||||
|
keepDirty: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options?.successToasts !== false) {
|
||||||
|
showSuccessToast(
|
||||||
|
t("proformas.update.success.title"),
|
||||||
|
t("proformas.update.success.message")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
options?.onUpdated?.(updated);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const normalizedError =
|
||||||
|
error instanceof Error ? error : new Error(t("pages.update.error.unknown"));
|
||||||
|
|
||||||
|
form.reset(
|
||||||
|
previousData ? mapProformaToProformaUpdateForm(previousData) : defaultProformaUpdateForm,
|
||||||
|
{ keepDirty: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options?.errorToasts !== false) {
|
||||||
|
showErrorToast(t("proformas.update.error.title"), normalizedError.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
options?.onError?.(normalizedError, params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(errors: FieldErrors<ProformaUpdateForm>) => {
|
||||||
|
focusFirstProformaUpdateError(errors);
|
||||||
|
|
||||||
|
showWarningToast(
|
||||||
|
t("proformas.update.validation.title"),
|
||||||
|
t("proformas.update.validation.message")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Evento onSubmit ya preparado para el <form>
|
||||||
|
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.stopPropagation(); // <-- evita que el submit se propage por los padre en el árbol DOM
|
||||||
|
submitHandler(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// form
|
||||||
|
formId,
|
||||||
|
form,
|
||||||
|
|
||||||
|
// handlers del form
|
||||||
|
onSubmit,
|
||||||
|
resetForm,
|
||||||
|
|
||||||
|
// carga de datos
|
||||||
|
proformaData,
|
||||||
|
isLoading,
|
||||||
|
isLoadError,
|
||||||
|
loadError,
|
||||||
|
|
||||||
|
// mutation
|
||||||
|
isUpdating,
|
||||||
|
isUpdateError,
|
||||||
|
updateError,
|
||||||
|
|
||||||
|
// No devolver FormProvider, así el controller es más
|
||||||
|
// flexible y reusable (p.ej. para un modal)
|
||||||
|
// FormProvider,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { useUrlParamId } from "@erp/core/hooks";
|
||||||
|
|
||||||
|
import { useUpdateProformaController } from "./use-update-proforma-controller";
|
||||||
|
|
||||||
|
export const useUpdateProformaPageController = () => {
|
||||||
|
const proformaId = useUrlParamId();
|
||||||
|
|
||||||
|
const updateCtrl = useUpdateProformaController(proformaId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateCtrl,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
|
export * from "./proforma-item-update-form.entity";
|
||||||
export * from "./proforma-update-form.entity";
|
export * from "./proforma-update-form.entity";
|
||||||
export * from "./proforma-update-form.schema";
|
export * from "./proforma-update-form.schema";
|
||||||
export * from "./proforma-update-form-defaults";
|
export * from "./proforma-update-form-default.entity";
|
||||||
export * from "./proforma-update-patch.entity";
|
export * from "./proforma-update-patch.entity";
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import type { ProformaUpdateForm } from ".";
|
||||||
|
|
||||||
|
export const defaultProformaUpdateForm: ProformaUpdateForm = {
|
||||||
|
series: "",
|
||||||
|
|
||||||
|
invoiceDate: "",
|
||||||
|
operationDate: "",
|
||||||
|
|
||||||
|
customerId: "",
|
||||||
|
|
||||||
|
description: "",
|
||||||
|
reference: "",
|
||||||
|
notes: "",
|
||||||
|
|
||||||
|
languageCode: "es",
|
||||||
|
currencyCode: "EUR",
|
||||||
|
|
||||||
|
paymentMethod: "",
|
||||||
|
|
||||||
|
globalDiscountPercentage: 0,
|
||||||
|
};
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import type { CustomerUpdateForm } from "./proforma-update-form.entity";
|
|
||||||
|
|
||||||
export const defaultCustomerUpdateForm: CustomerUpdateForm = {
|
|
||||||
reference: "",
|
|
||||||
isCompany: true,
|
|
||||||
name: "",
|
|
||||||
tradeName: "",
|
|
||||||
tin: "",
|
|
||||||
|
|
||||||
defaultTaxes: [],
|
|
||||||
|
|
||||||
street: "",
|
|
||||||
street2: "",
|
|
||||||
city: "",
|
|
||||||
province: "",
|
|
||||||
postalCode: "",
|
|
||||||
country: "es",
|
|
||||||
|
|
||||||
primaryEmail: "",
|
|
||||||
secondaryEmail: "",
|
|
||||||
primaryPhone: "",
|
|
||||||
secondaryPhone: "",
|
|
||||||
primaryMobile: "",
|
|
||||||
secondaryMobile: "",
|
|
||||||
|
|
||||||
fax: "",
|
|
||||||
website: "",
|
|
||||||
|
|
||||||
legalRecord: "",
|
|
||||||
|
|
||||||
languageCode: "es",
|
|
||||||
currencyCode: "EUR",
|
|
||||||
};
|
|
||||||
@ -13,8 +13,6 @@
|
|||||||
* - sin detalles impuestos por el widget
|
* - sin detalles impuestos por el widget
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ProformaItemForm } from "../../shared/entities";
|
|
||||||
|
|
||||||
export interface ProformaUpdateForm {
|
export interface ProformaUpdateForm {
|
||||||
series: string;
|
series: string;
|
||||||
|
|
||||||
@ -23,6 +21,7 @@ export interface ProformaUpdateForm {
|
|||||||
|
|
||||||
customerId: string;
|
customerId: string;
|
||||||
|
|
||||||
|
description: string;
|
||||||
reference: string;
|
reference: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
|
|
||||||
@ -33,5 +32,5 @@ export interface ProformaUpdateForm {
|
|||||||
|
|
||||||
paymentMethod: string;
|
paymentMethod: string;
|
||||||
|
|
||||||
items: ProformaItemForm[];
|
//items: ProformaItemForm[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,29 @@ import { z } from "zod/v4";
|
|||||||
* - sin detalles impuestos por el widget
|
* - sin detalles impuestos por el widget
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const ProformaItemFormSchema = z.object({
|
export const ProformaUpdateFormSchema = z.object({
|
||||||
|
series: z.string().default(""),
|
||||||
|
|
||||||
|
invoiceDate: z.string().default(""),
|
||||||
|
operationDate: z.string().default(""),
|
||||||
|
|
||||||
|
customerId: z.string().default(""),
|
||||||
|
|
||||||
|
description: z.string().default(""),
|
||||||
|
reference: z.string().default(""),
|
||||||
|
notes: z.string().default(""),
|
||||||
|
|
||||||
|
languageCode: z.string().min(1, "Debe indicar un idioma").default("es"),
|
||||||
|
currencyCode: z.string().min(1, "Debe indicar una moneda").default("EUR"),
|
||||||
|
|
||||||
|
globalDiscountPercentage: z.number().default(0),
|
||||||
|
|
||||||
|
paymentMethod: z.string().default(""),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ProformaUpdateFormSchemaType = z.infer<typeof ProformaUpdateFormSchema>;
|
||||||
|
|
||||||
|
const ProformaItemFormSchema = z.object({
|
||||||
description: z.string().max(2000).optional().default(""),
|
description: z.string().max(2000).optional().default(""),
|
||||||
quantity: z.any(), //NumericStringSchema.optional(),
|
quantity: z.any(), //NumericStringSchema.optional(),
|
||||||
unit_amount: z.any(), //NumericStringSchema.optional(),
|
unit_amount: z.any(), //NumericStringSchema.optional(),
|
||||||
@ -30,7 +52,7 @@ export const ProformaItemFormSchema = z.object({
|
|||||||
total_amount: z.number(),
|
total_amount: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ProformaFormSchema = z.object({
|
const ProformaFormSchema = z.object({
|
||||||
invoice_number: z.string().optional(),
|
invoice_number: z.string().optional(),
|
||||||
series: z.string().optional(),
|
series: z.string().optional(),
|
||||||
|
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export * from "./types";
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
import { z } from "zod/v4";
|
|
||||||
|
|
||||||
export const ProformaItemFormSchema = z.object({
|
|
||||||
description: z.string().max(2000).optional().default(""),
|
|
||||||
quantity: z.any(), //NumericStringSchema.optional(),
|
|
||||||
unit_amount: z.any(), //NumericStringSchema.optional(),
|
|
||||||
|
|
||||||
subtotal_amount: z.any(), //z.number(),
|
|
||||||
discount_percentage: z.any(), //NumericStringSchema.optional(),
|
|
||||||
discount_amount: z.number(),
|
|
||||||
taxable_amount: z.number(),
|
|
||||||
|
|
||||||
tax_codes: z.array(z.string()).default([]),
|
|
||||||
|
|
||||||
taxes_amount: z.number(),
|
|
||||||
total_amount: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ProformaFormSchema = z.object({
|
|
||||||
invoice_number: z.string().optional(),
|
|
||||||
series: z.string().optional(),
|
|
||||||
|
|
||||||
invoice_date: z.string().optional(),
|
|
||||||
operation_date: z.string().optional(),
|
|
||||||
|
|
||||||
customer_id: z.string().optional(),
|
|
||||||
recipient: z
|
|
||||||
.object({
|
|
||||||
id: z.string().optional(),
|
|
||||||
name: z.string().optional(),
|
|
||||||
tin: z.string().optional(),
|
|
||||||
street: z.string().optional(),
|
|
||||||
street2: z.string().optional(),
|
|
||||||
city: z.string().optional(),
|
|
||||||
province: z.string().optional(),
|
|
||||||
postal_code: z.string().optional(),
|
|
||||||
country: z.string().optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
|
|
||||||
reference: z.string().optional(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
notes: z.string().optional(),
|
|
||||||
|
|
||||||
language_code: z
|
|
||||||
.string({
|
|
||||||
error: "El idioma es obligatorio",
|
|
||||||
})
|
|
||||||
.min(1, "Debe indicar un idioma")
|
|
||||||
.toUpperCase() // asegura mayúsculas
|
|
||||||
.default("es"),
|
|
||||||
|
|
||||||
currency_code: z
|
|
||||||
.string({
|
|
||||||
error: "La moneda es obligatoria",
|
|
||||||
})
|
|
||||||
.min(1, "La moneda no puede estar vacía")
|
|
||||||
.toUpperCase() // asegura mayúsculas
|
|
||||||
.default("EUR"),
|
|
||||||
|
|
||||||
taxes: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
tax_code: z.string(),
|
|
||||||
tax_label: z.string(),
|
|
||||||
taxable_amount: z.number(),
|
|
||||||
taxes_amount: z.number(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
|
|
||||||
items: z.array(ProformaItemFormSchema).optional(),
|
|
||||||
|
|
||||||
subtotal_amount: z.number(),
|
|
||||||
items_discount_amount: z.number(),
|
|
||||||
discount_percentage: z.number(),
|
|
||||||
discount_amount: z.number(),
|
|
||||||
taxable_amount: z.number(),
|
|
||||||
taxes_amount: z.number(),
|
|
||||||
total_amount: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ProformaFormData = z.infer<typeof ProformaFormSchema>;
|
|
||||||
export type ProformaItemFormData = z.infer<typeof ProformaItemFormSchema>;
|
|
||||||
|
|
||||||
export const defaultProformaItemFormData: ProformaItemFormData = {
|
|
||||||
description: "",
|
|
||||||
quantity: "",
|
|
||||||
unit_amount: "",
|
|
||||||
subtotal_amount: 0,
|
|
||||||
discount_percentage: "",
|
|
||||||
discount_amount: 0,
|
|
||||||
taxable_amount: 0,
|
|
||||||
tax_codes: ["iva_21"],
|
|
||||||
taxes_amount: 0,
|
|
||||||
total_amount: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultProformaFormData: ProformaFormData = {
|
|
||||||
invoice_number: "",
|
|
||||||
series: "",
|
|
||||||
|
|
||||||
invoice_date: "",
|
|
||||||
operation_date: "",
|
|
||||||
|
|
||||||
reference: "",
|
|
||||||
description: "",
|
|
||||||
notes: "",
|
|
||||||
|
|
||||||
language_code: "es",
|
|
||||||
currency_code: "EUR",
|
|
||||||
|
|
||||||
items: [],
|
|
||||||
|
|
||||||
subtotal_amount: 0,
|
|
||||||
items_discount_amount: 0,
|
|
||||||
discount_amount: 0,
|
|
||||||
discount_percentage: 0,
|
|
||||||
taxable_amount: 0,
|
|
||||||
taxes_amount: 0,
|
|
||||||
total_amount: 0,
|
|
||||||
};
|
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./proforma-header-fields-card";
|
||||||
|
export * from "./proforma-update-editor";
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
import type { ProformaUpdateForm } from "../../entities";
|
||||||
|
|
||||||
|
export const ProformaHeaderFieldsCard = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { register, formState } = useFormContext<ProformaUpdateForm>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{t("proformas.update.header.title", "Cabecera")}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="series">
|
||||||
|
{t("proformas.fields.series", "Serie")}
|
||||||
|
</label>
|
||||||
|
<Input id="series" {...register("series")} />
|
||||||
|
<FieldError message={formState.errors.series?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="description">
|
||||||
|
{t("proformas.fields.description", "Descripción")}
|
||||||
|
</label>
|
||||||
|
<Input id="description" {...register("description")} />
|
||||||
|
<FieldError message={formState.errors.series?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="customerId">
|
||||||
|
{t("proformas.fields.customer", "Cliente")}
|
||||||
|
</label>
|
||||||
|
<Input id="customerId" {...register("customerId")} />
|
||||||
|
<FieldError message={formState.errors.customerId?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="invoiceDate">
|
||||||
|
{t("proformas.fields.invoice_date", "Fecha")}
|
||||||
|
</label>
|
||||||
|
<Input id="invoiceDate" type="date" {...register("invoiceDate")} />
|
||||||
|
<FieldError message={formState.errors.invoiceDate?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="operationDate">
|
||||||
|
{t("proformas.fields.operation_date", "Fecha operación")}
|
||||||
|
</label>
|
||||||
|
<Input id="operationDate" type="date" {...register("operationDate")} />
|
||||||
|
<FieldError message={formState.errors.operationDate?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="languageCode">
|
||||||
|
{t("proformas.fields.language", "Idioma")}
|
||||||
|
</label>
|
||||||
|
<Input id="languageCode" {...register("languageCode")} />
|
||||||
|
<FieldError message={formState.errors.languageCode?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="currencyCode">
|
||||||
|
{t("proformas.fields.currency", "Moneda")}
|
||||||
|
</label>
|
||||||
|
<Input id="currencyCode" {...register("currencyCode")} />
|
||||||
|
<FieldError message={formState.errors.currencyCode?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 md:col-span-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="reference">
|
||||||
|
{t("proformas.fields.reference", "Referencia")}
|
||||||
|
</label>
|
||||||
|
<Input id="reference" {...register("reference")} />
|
||||||
|
<FieldError message={formState.errors.reference?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 md:col-span-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="paymentMethod">
|
||||||
|
{t("proformas.fields.payment_method", "Forma de pago")}
|
||||||
|
</label>
|
||||||
|
<Input id="paymentMethod" {...register("paymentMethod")} />
|
||||||
|
<FieldError message={formState.errors.paymentMethod?.message} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 md:col-span-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="notes">
|
||||||
|
{t("proformas.fields.notes", "Notas")}
|
||||||
|
</label>
|
||||||
|
<Textarea id="notes" rows={5} {...register("notes")} />
|
||||||
|
<FieldError message={formState.errors.notes?.message} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type FieldErrorProps = {
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FieldError = ({ message }: FieldErrorProps) => {
|
||||||
|
if (!message) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <p className="text-sm text-destructive">{message}</p>;
|
||||||
|
};
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
// modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-update-editor.tsx
|
||||||
|
|
||||||
|
import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
|
||||||
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
import type { Proforma } from "../../../shared/entities";
|
||||||
|
|
||||||
|
import { ProformaHeaderFieldsCard } from "./proforma-header-fields-card";
|
||||||
|
|
||||||
|
type ProformaUpdateEditorProps = {
|
||||||
|
formId: string;
|
||||||
|
proforma?: Proforma;
|
||||||
|
isSubmitting: boolean;
|
||||||
|
onSubmit: React.FormEventHandler<HTMLFormElement>;
|
||||||
|
onReset: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProformaUpdateEditor = ({
|
||||||
|
formId,
|
||||||
|
proforma,
|
||||||
|
isSubmitting,
|
||||||
|
onSubmit,
|
||||||
|
onReset,
|
||||||
|
}: ProformaUpdateEditorProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContent className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<h1 className="text-xl font-semibold">
|
||||||
|
{t("proformas.update.page_title", "Editar proforma")}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{proforma?.reference ? (
|
||||||
|
<p className="text-sm text-muted-foreground">{proforma.reference}</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BackHistoryButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form className="space-y-6" id={formId} onSubmit={onSubmit}>
|
||||||
|
<ProformaHeaderFieldsCard />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
<Button disabled={isSubmitting} onClick={onReset} type="button" variant="outline">
|
||||||
|
{t("common.reset", "Restablecer")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button disabled={isSubmitting} type="submit">
|
||||||
|
{isSubmitting ? t("common.saving", "Guardando...") : t("common.save", "Guardar")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</AppContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./proforma-update-skeleton";
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
|
||||||
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
|
||||||
|
export const ProformaUpdateSkeleton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AppContent>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div aria-hidden="true" className="space-y-2">
|
||||||
|
<div className="h-7 w-64 rounded-md bg-muted animate-pulse" />
|
||||||
|
<div className="h-5 w-96 rounded-md bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<BackHistoryButton />
|
||||||
|
<Button aria-busy disabled>
|
||||||
|
{t("pages.update.submit")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div aria-hidden="true" className="mt-6 grid gap-4">
|
||||||
|
<div className="h-10 w-full rounded-md bg-muted animate-pulse" />
|
||||||
|
<div className="h-10 w-full rounded-md bg-muted animate-pulse" />
|
||||||
|
<div className="h-28 w-full rounded-md bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<span className="sr-only">{t("pages.update.loading", "Cargando proforma...")}</span>
|
||||||
|
</AppContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,38 +1,57 @@
|
|||||||
import { SpainTaxCatalogProvider } from "@erp/core";
|
import { SpainTaxCatalogProvider } from "@erp/core";
|
||||||
import { useUrlParamId } from "@erp/core/hooks";
|
import { ErrorAlert } from "@erp/core/components";
|
||||||
import { ErrorAlert } from "@erp/customers/components";
|
|
||||||
import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
|
import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { FormProvider } from "react-hook-form";
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import { useProformaUpdateController } from "../../controllers";
|
import { useUpdateProformaPageController } from "../../controllers/use-update-proforma-page-controller";
|
||||||
|
import { ProformaUpdateEditor } from "../blocks";
|
||||||
import { ProformaProvider } from "./context";
|
import { ProformaUpdateSkeleton } from "../components";
|
||||||
import { ProformaUpdateComp } from "./proforma-update-comp";
|
|
||||||
import { ProformaEditorSkeleton } from "./ui/components";
|
|
||||||
|
|
||||||
export const ProformaUpdatePage = () => {
|
export const ProformaUpdatePage = () => {
|
||||||
const initialProformaId = useUrlParamId();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const taxCatalog = useMemo(() => SpainTaxCatalogProvider(), []);
|
const taxCatalog = useMemo(() => SpainTaxCatalogProvider(), []);
|
||||||
|
|
||||||
const {
|
const { updateCtrl } = useUpdateProformaPageController();
|
||||||
form,
|
|
||||||
formId,
|
|
||||||
onSubmit,
|
|
||||||
resetForm,
|
|
||||||
|
|
||||||
proformaData,
|
if (updateCtrl.isLoading) {
|
||||||
isLoading,
|
return <ProformaUpdateSkeleton />;
|
||||||
isLoadError,
|
}
|
||||||
loadError,
|
|
||||||
|
|
||||||
isUpdating,
|
if (updateCtrl.isLoadError) {
|
||||||
isUpdateError,
|
return (
|
||||||
updateError,
|
<AppContent>
|
||||||
|
<ErrorAlert
|
||||||
|
message={
|
||||||
|
updateCtrl.loadError instanceof Error
|
||||||
|
? updateCtrl.loadError.message
|
||||||
|
: t("proformas.update.load_error.message", "Inténtalo de nuevo más tarde")
|
||||||
|
}
|
||||||
|
title={t("proformas.update.load_error.title", "No se pudo cargar la proforma")}
|
||||||
|
/>
|
||||||
|
|
||||||
FormProvider,
|
<div className="flex items-center justify-end">
|
||||||
} = useProformaUpdateController(initialProformaId, {});
|
<BackHistoryButton />
|
||||||
|
</div>
|
||||||
|
</AppContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...updateCtrl.form}>
|
||||||
|
<ProformaUpdateEditor
|
||||||
|
formId={updateCtrl.formId}
|
||||||
|
isSubmitting={updateCtrl.isUpdating}
|
||||||
|
onReset={updateCtrl.resetForm}
|
||||||
|
onSubmit={updateCtrl.onSubmit}
|
||||||
|
proforma={updateCtrl.proforma}
|
||||||
|
/>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <ProformaEditorSkeleton />;
|
return <ProformaEditorSkeleton />;
|
||||||
@ -72,3 +91,4 @@ export const ProformaUpdatePage = () => {
|
|||||||
</ProformaProvider>
|
</ProformaProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { formHasAnyDirty, pickFormDirtyValues } from "@erp/core/client";
|
||||||
|
import type { FieldNamesMarkedBoolean } from "react-hook-form";
|
||||||
|
|
||||||
|
import type { ProformaUpdateForm, ProformaUpdatePatch } from "../entities";
|
||||||
|
|
||||||
|
export const buildProformaUpdatePatch = (
|
||||||
|
formData: ProformaUpdateForm,
|
||||||
|
dirtyFields: FieldNamesMarkedBoolean<ProformaUpdateForm>
|
||||||
|
): ProformaUpdatePatch => {
|
||||||
|
if (!formHasAnyDirty(dirtyFields)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return pickFormDirtyValues(formData, dirtyFields) as ProformaUpdatePatch;
|
||||||
|
};
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import type { UpdateProformaByIdParams } from "../../shared/api";
|
||||||
|
import type { ProformaUpdatePatch } from "../entities";
|
||||||
|
|
||||||
|
export const buildUpdateProformaByIdParams = (
|
||||||
|
id: string,
|
||||||
|
patch: ProformaUpdatePatch
|
||||||
|
): UpdateProformaByIdParams => {
|
||||||
|
if (!id) {
|
||||||
|
throw new Error("proformaId is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: UpdateProformaByIdParams["data"] = {
|
||||||
|
series: patch.series,
|
||||||
|
|
||||||
|
invoice_date: patch.invoiceDate,
|
||||||
|
operation_date: patch.operationDate,
|
||||||
|
|
||||||
|
customer_id: patch.customerId,
|
||||||
|
|
||||||
|
reference: patch.reference,
|
||||||
|
description: patch.description,
|
||||||
|
notes: patch.notes,
|
||||||
|
|
||||||
|
language_code: patch.languageCode,
|
||||||
|
currency_code: patch.currencyCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
...Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import type { FieldErrors } from "react-hook-form";
|
||||||
|
|
||||||
|
import type { ProformaUpdateForm } from "../entities";
|
||||||
|
|
||||||
|
export const focusFirstProformaUpdateError = (errors: FieldErrors<ProformaUpdateForm>) => {
|
||||||
|
const firstKey = Object.keys(errors)[0] as keyof ProformaUpdateForm | undefined;
|
||||||
|
|
||||||
|
if (!firstKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector<HTMLElement>(`[name="${String(firstKey)}"]`)?.focus();
|
||||||
|
};
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./build-proforma-update-patch";
|
||||||
|
export * from "./build-update-proforma-by-id-params";
|
||||||
|
export * from "./focus-first-proforma-update-error";
|
||||||
@ -1,3 +1,3 @@
|
|||||||
export * from "./customer-create-form.entity";
|
export * from "./customer-create-form.entity";
|
||||||
export * from "./customer-create-form.schema";
|
export * from "./customer-create-form.schema";
|
||||||
export * from "./customer-create-form-default";
|
export * from "./customer-create-form-default.entity";
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { formHasAnyDirty } from "@erp/core/client";
|
|
||||||
import { useHookForm } from "@erp/core/hooks";
|
import { useHookForm } from "@erp/core/hooks";
|
||||||
import { showErrorToast, showSuccessToast, showWarningToast } from "@repo/rdx-ui/helpers";
|
import { showErrorToast, showSuccessToast, showWarningToast } from "@repo/rdx-ui/helpers";
|
||||||
import { useEffect, useId, useMemo } from "react";
|
import { useEffect, useId, useMemo } from "react";
|
||||||
@ -18,6 +17,7 @@ import {
|
|||||||
defaultCustomerUpdateForm,
|
defaultCustomerUpdateForm,
|
||||||
} from "../entities";
|
} from "../entities";
|
||||||
import { buildCustomerUpdatePatch, buildUpdateCustomerByIdParams } from "../utils";
|
import { buildCustomerUpdatePatch, buildUpdateCustomerByIdParams } from "../utils";
|
||||||
|
import { focusFirstCustomerUpdateError } from "../utils/focus-first-customer-update-error";
|
||||||
|
|
||||||
export interface UseCustomerUpdateControllerOptions {
|
export interface UseCustomerUpdateControllerOptions {
|
||||||
onUpdated?(updated: Customer): void;
|
onUpdated?(updated: Customer): void;
|
||||||
@ -84,23 +84,16 @@ export const useCustomerUpdateController = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submitHandler = form.handleSubmit(
|
const submitHandler = form.handleSubmit(
|
||||||
async (formData) => {
|
async (formData: CustomerUpdateForm) => {
|
||||||
if (!customerId) {
|
if (!customerId) {
|
||||||
showErrorToast(t("pages.update.error.title"), "Falta el ID del cliente");
|
showErrorToast(t("pages.update.error.title"), "Falta el ID del cliente");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { dirtyFields } = form.formState;
|
|
||||||
|
|
||||||
if (!formHasAnyDirty(dirtyFields)) {
|
|
||||||
showWarningToast(t("pages.update.error.no_changes"), "No hay cambios para guardar");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousData = customerData;
|
const previousData = customerData;
|
||||||
|
|
||||||
const patchData = buildCustomerUpdatePatch(formData, dirtyFields);
|
const patchData = buildCustomerUpdatePatch(formData, form.formState.dirtyFields);
|
||||||
const params: UpdateCustomerByIdParams = buildUpdateCustomerByIdParams(customerId, patchData);
|
const params = buildUpdateCustomerByIdParams(customerId, patchData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Enviamos cambios al servidor
|
// Enviamos cambios al servidor
|
||||||
@ -137,11 +130,7 @@ export const useCustomerUpdateController = (
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
(errors: FieldErrors<CustomerUpdateForm>) => {
|
(errors: FieldErrors<CustomerUpdateForm>) => {
|
||||||
const firstKey = Object.keys(errors)[0] as keyof CustomerUpdateForm | undefined;
|
focusFirstCustomerUpdateError(errors);
|
||||||
|
|
||||||
if (firstKey) {
|
|
||||||
document.querySelector<HTMLElement>(`[name="${String(firstKey)}"]`)?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
showWarningToast(
|
showWarningToast(
|
||||||
t("forms.validation.title", "Revisa los campos"),
|
t("forms.validation.title", "Revisa los campos"),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export * from "./customer-update-form.entity";
|
export * from "./customer-update-form.entity";
|
||||||
export * from "./customer-update-form.schema";
|
export * from "./customer-update-form.schema";
|
||||||
export * from "./customer-update-form-defaults";
|
export * from "./customer-update-form-default.entity";
|
||||||
export * from "./customer-update-patch.entity";
|
export * from "./customer-update-patch.entity";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { pickFormDirtyValues } from "@erp/core/client";
|
import { formHasAnyDirty, pickFormDirtyValues } from "@erp/core/client";
|
||||||
import type { FieldNamesMarkedBoolean } from "react-hook-form";
|
import type { FieldNamesMarkedBoolean } from "react-hook-form";
|
||||||
|
|
||||||
import type { CustomerUpdateForm, CustomerUpdatePatch } from "../entities";
|
import type { CustomerUpdateForm, CustomerUpdatePatch } from "../entities";
|
||||||
@ -16,5 +16,8 @@ export const buildCustomerUpdatePatch = (
|
|||||||
formData: CustomerUpdateForm,
|
formData: CustomerUpdateForm,
|
||||||
dirtyFields: FieldNamesMarkedBoolean<CustomerUpdateForm>
|
dirtyFields: FieldNamesMarkedBoolean<CustomerUpdateForm>
|
||||||
): CustomerUpdatePatch => {
|
): CustomerUpdatePatch => {
|
||||||
|
if (!formHasAnyDirty(dirtyFields)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return pickFormDirtyValues(formData, dirtyFields) as CustomerUpdatePatch;
|
return pickFormDirtyValues(formData, dirtyFields) as CustomerUpdatePatch;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,10 +23,6 @@ export const buildUpdateCustomerByIdParams = (
|
|||||||
id: string,
|
id: string,
|
||||||
patchData: CustomerUpdatePatch
|
patchData: CustomerUpdatePatch
|
||||||
): UpdateCustomerByIdParams => {
|
): UpdateCustomerByIdParams => {
|
||||||
if (!id) {
|
|
||||||
throw new Error("customerId is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
data: patchData,
|
data: patchData,
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import type { FieldErrors } from "react-hook-form";
|
||||||
|
|
||||||
|
import type { CustomerUpdateForm } from "../entities";
|
||||||
|
|
||||||
|
export const focusFirstCustomerUpdateError = (errors: FieldErrors<CustomerUpdateForm>) => {
|
||||||
|
const firstKey = Object.keys(errors)[0] as keyof CustomerUpdateForm | undefined;
|
||||||
|
|
||||||
|
if (!firstKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector<HTMLElement>(`[name="${String(firstKey)}"]`)?.focus();
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user