Borrado de proformas draft
This commit is contained in:
parent
69bfbbb615
commit
718e8b13d9
@ -99,6 +99,12 @@ Adaptando nombres exactos al contrato publico actual:
|
|||||||
- al existir lineas valoradas, `update` recalcula `taxes` y `totals`
|
- al existir lineas valoradas, `update` recalcula `taxes` y `totals`
|
||||||
- `issue` genera la `issued_invoice` materializada usando `target_invoice_series_code` si existe y, si no, la default activa de `issued_invoice`
|
- `issue` genera la `issued_invoice` materializada usando `target_invoice_series_code` si existe y, si no, la default activa de `issued_invoice`
|
||||||
|
|
||||||
|
## Relacion con borrado
|
||||||
|
|
||||||
|
- una proforma creada en `draft` puede borrarse logicamente mas adelante
|
||||||
|
- ese borrado no modifica `tax_config`, impuestos, totales ni numeracion historica
|
||||||
|
- la referencia visible consumida por create no vuelve a reutilizarse aunque la proforma se borre
|
||||||
|
|
||||||
## Wording de UI
|
## Wording de UI
|
||||||
|
|
||||||
La UI de create debe hablar de:
|
La UI de create debe hablar de:
|
||||||
|
|||||||
40
docs/customer-invoices/proforma-delete-contract.md
Normal file
40
docs/customer-invoices/proforma-delete-contract.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Proforma Delete Contract
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Definir el borrado funcional de proformas sin romper trazabilidad ni numeracion.
|
||||||
|
|
||||||
|
## Reglas funcionales
|
||||||
|
|
||||||
|
- solo se pueden borrar proformas en estado `draft`
|
||||||
|
- `sent`, `approved`, `rejected` e `issued` devuelven conflicto
|
||||||
|
- `rejected` no se borra; queda reservado para una futura operacion de archivado
|
||||||
|
- el borrado es logico mediante `deleted_at`
|
||||||
|
- la numeracion no se reutiliza
|
||||||
|
- no se decrementa `document_series.next_number`
|
||||||
|
- no se borran fisicamente `proforma_items` ni `proforma_taxes`
|
||||||
|
|
||||||
|
## Validaciones defensivas
|
||||||
|
|
||||||
|
- si `linked_invoice_id` tiene valor, el borrado se bloquea
|
||||||
|
- si existe `issued_invoices.source_proforma_id = proforma.id`, el borrado se bloquea
|
||||||
|
|
||||||
|
## Contrato REST
|
||||||
|
|
||||||
|
- endpoint: `DELETE /proformas/:proforma_id`
|
||||||
|
- respuesta satisfactoria: `204 No Content`
|
||||||
|
- `404 Not Found` si no existe, no pertenece a la empresa activa o ya esta borrada
|
||||||
|
- `409 Conflict` si existe pero no cumple las reglas de borrado
|
||||||
|
|
||||||
|
## Efecto operativo
|
||||||
|
|
||||||
|
- `GET /proformas/:id` debe tratar la proforma borrada como inexistente
|
||||||
|
- `GET /proformas` no debe incluir proformas borradas
|
||||||
|
- `PUT /proformas/:id` no debe editar proformas borradas
|
||||||
|
- `POST /proformas/:id/issue` no debe emitir proformas borradas
|
||||||
|
|
||||||
|
## UI
|
||||||
|
|
||||||
|
- la accion `Eliminar` solo debe mostrarse para proformas `draft`
|
||||||
|
- la confirmacion debe advertir que la referencia no se reutilizara
|
||||||
|
- en esta fase la accion solo esta disponible en el listado
|
||||||
@ -74,6 +74,13 @@ En proformas:
|
|||||||
- la UI nunca debe consumir numeracion directa desde `POST /document-series/assign-next`
|
- la UI nunca debe consumir numeracion directa desde `POST /document-series/assign-next`
|
||||||
- los impuestos y totales reales se recalculan cuando la proforma ya tiene lineas en update
|
- los impuestos y totales reales se recalculan cuando la proforma ya tiene lineas en update
|
||||||
|
|
||||||
|
## Regla de borrado y numeracion
|
||||||
|
|
||||||
|
- borrar una proforma draft no libera ni reutiliza `proforma_reference`
|
||||||
|
- el siguiente alta consume el siguiente numero disponible de `document-series`
|
||||||
|
- no se decrementa `document_series.next_number`
|
||||||
|
- las proformas `rejected` no se borran; quedan para una futura operacion de archivado
|
||||||
|
|
||||||
## Validacion defensiva
|
## Validacion defensiva
|
||||||
|
|
||||||
Si el request informa `target_invoice_series_code`:
|
Si el request informa `target_invoice_series_code`:
|
||||||
|
|||||||
@ -165,6 +165,15 @@ Preparar el esquema fisico para separar:
|
|||||||
- update sigue siendo el punto donde se anaden lineas y se materializan impuestos/totales reales
|
- update sigue siendo el punto donde se anaden lineas y se materializan impuestos/totales reales
|
||||||
- la UI deja de prometer que la fiscalidad queda "aplicada" en create y pasa a hablar de valores iniciales
|
- la UI deja de prometer que la fiscalidad queda "aplicada" en create y pasa a hablar de valores iniciales
|
||||||
|
|
||||||
|
## Estado Fase 2G
|
||||||
|
|
||||||
|
- `DELETE /proformas/:proforma_id` vuelve a estar operativo sobre persistencia V2
|
||||||
|
- el borrado es logico mediante `deleted_at`
|
||||||
|
- solo `draft` puede borrarse
|
||||||
|
- `rejected` sigue fuera del alcance y queda reservado para archivado futuro
|
||||||
|
- la validacion de borrado comprueba tambien `linked_invoice_id` y `issued_invoices.source_proforma_id`
|
||||||
|
- la numeracion de `document-series` no se reutiliza ni se decrementa tras un borrado
|
||||||
|
|
||||||
## Rollback recomendado
|
## Rollback recomendado
|
||||||
|
|
||||||
- no aplicar nada en produccion en esta fase
|
- no aplicar nada en produccion en esta fase
|
||||||
|
|||||||
@ -10,6 +10,7 @@ Este directorio mezcla SQL vigentes de soporte con SQL historicos/transicionales
|
|||||||
- `add-proforma-tax-config-columns.sql`
|
- `add-proforma-tax-config-columns.sql`
|
||||||
- `fix-proforma-series-semantics.sql`
|
- `fix-proforma-series-semantics.sql`
|
||||||
- `validate-proforma-tax-config.sql`
|
- `validate-proforma-tax-config.sql`
|
||||||
|
- `validate-proforma-soft-delete.sql`
|
||||||
|
|
||||||
## SQL históricos / transicionales
|
## SQL históricos / transicionales
|
||||||
|
|
||||||
|
|||||||
14
docs/customer-invoices/sql/validate-proforma-soft-delete.sql
Normal file
14
docs/customer-invoices/sql/validate-proforma-soft-delete.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
SELECT id, proforma_reference, status, deleted_at
|
||||||
|
FROM proformas
|
||||||
|
WHERE deleted_at IS NOT NULL
|
||||||
|
AND status <> 'draft';
|
||||||
|
|
||||||
|
SELECT id, proforma_reference, deleted_at
|
||||||
|
FROM proformas
|
||||||
|
WHERE deleted_at IS NOT NULL
|
||||||
|
ORDER BY deleted_at DESC
|
||||||
|
LIMIT 20;
|
||||||
|
|
||||||
|
SELECT ds.document_type, ds.code, ds.next_number
|
||||||
|
FROM document_series ds
|
||||||
|
WHERE ds.document_type = 'proforma';
|
||||||
@ -1,5 +1,6 @@
|
|||||||
export * from "./proforma-catalog-resolvers.di";
|
export * from "./proforma-catalog-resolvers.di";
|
||||||
export * from "./proforma-creator.di";
|
export * from "./proforma-creator.di";
|
||||||
|
export * from "./proforma-deleter.di";
|
||||||
export * from "./proforma-finder.di";
|
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";
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { type IProformaDeleter, ProformaDeleter } from "../services";
|
||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
|
||||||
|
export function buildProformaDeleter(deps: { repository: IProformaRepository }): IProformaDeleter {
|
||||||
|
return new ProformaDeleter({
|
||||||
|
repository: deps.repository,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import type { ICreateProformaInputMapper, IUpdateProformaInputMapper } from "../
|
|||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
import type {
|
import type {
|
||||||
ICompanyReportProfileFinder,
|
ICompanyReportProfileFinder,
|
||||||
|
IProformaDeleter,
|
||||||
IProformaCreator,
|
IProformaCreator,
|
||||||
IProformaFinder,
|
IProformaFinder,
|
||||||
IProformaFullReadModelAssembler,
|
IProformaFullReadModelAssembler,
|
||||||
@ -23,6 +24,7 @@ import type {
|
|||||||
import {
|
import {
|
||||||
ChangeStatusProformaUseCase,
|
ChangeStatusProformaUseCase,
|
||||||
CreateProformaUseCase,
|
CreateProformaUseCase,
|
||||||
|
DeleteProformaByIdUseCase,
|
||||||
GetProformaByIdUseCase,
|
GetProformaByIdUseCase,
|
||||||
IssueProformaUseCase,
|
IssueProformaUseCase,
|
||||||
ListProformasUseCase,
|
ListProformasUseCase,
|
||||||
@ -163,10 +165,15 @@ export function buildUpdateProformaUseCase(deps: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
export function buildDeleteProformaByIdUseCase(deps: {
|
||||||
export function buildDeleteProformaUseCase(deps: { finder: IProformaFinder }) {
|
deleter: IProformaDeleter;
|
||||||
return new DeleteProformaUseCase(deps.finder);
|
transactionManager: ITransactionManager;
|
||||||
}*/
|
}) {
|
||||||
|
return new DeleteProformaByIdUseCase({
|
||||||
|
deleter: deps.deleter,
|
||||||
|
transactionManager: deps.transactionManager,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function buildChangeStatusProformaUseCase(deps: {
|
export function buildChangeStatusProformaUseCase(deps: {
|
||||||
statusChanger: IProformaStatusChanger;
|
statusChanger: IProformaStatusChanger;
|
||||||
|
|||||||
@ -34,6 +34,12 @@ export interface IProformaRepository {
|
|||||||
transaction: unknown
|
transaction: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
existsIssuedInvoiceBySourceProformaIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: unknown
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
updateStatusByIdInCompany(
|
updateStatusByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export * from "./company-report-profile-finder";
|
|||||||
export * from "./proforma-creator";
|
export * from "./proforma-creator";
|
||||||
export * from "./proforma-document-generator.interface";
|
export * from "./proforma-document-generator.interface";
|
||||||
export * from "./proforma-document-properties-factory";
|
export * from "./proforma-document-properties-factory";
|
||||||
|
export * from "./proforma-deleter";
|
||||||
export * from "./proforma-finder";
|
export * from "./proforma-finder";
|
||||||
export * from "./proforma-issuer";
|
export * from "./proforma-issuer";
|
||||||
export * from "./proforma-number-generator.interface";
|
export * from "./proforma-number-generator.interface";
|
||||||
|
|||||||
@ -0,0 +1,73 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { ProformaCannotBeDeletedError } from "../../../domain";
|
||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface IProformaDeleter {
|
||||||
|
delete(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
transaction: unknown;
|
||||||
|
}): Promise<Result<void, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProformaDeleter implements IProformaDeleter {
|
||||||
|
public constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
repository: IProformaRepository;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async delete(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
transaction: unknown;
|
||||||
|
}): Promise<Result<void, Error>> {
|
||||||
|
const proformaResult = await this.deps.repository.getByIdInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (proformaResult.isFailure) {
|
||||||
|
return Result.fail(proformaResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proforma = proformaResult.data;
|
||||||
|
const canDeleteResult = proforma.ensureCanBeDeleted();
|
||||||
|
|
||||||
|
if (canDeleteResult.isFailure) {
|
||||||
|
return Result.fail(canDeleteResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasIssuedInvoiceResult =
|
||||||
|
await this.deps.repository.existsIssuedInvoiceBySourceProformaIdInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasIssuedInvoiceResult.isFailure) {
|
||||||
|
return Result.fail(hasIssuedInvoiceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasIssuedInvoiceResult.data) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeDeletedError(params.id.toString(), "Issued proformas cannot be deleted.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteResult = await this.deps.repository.deleteByIdInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deleteResult.isFailure) {
|
||||||
|
return Result.fail(deleteResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IProformaDeleter } from "../services";
|
||||||
|
|
||||||
|
type DeleteProformaByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
proforma_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DeleteProformaByIdUseCase {
|
||||||
|
public constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
deleter: IProformaDeleter;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: DeleteProformaByIdUseCaseInput) {
|
||||||
|
const proformaIdResult = UniqueID.create(params.proforma_id);
|
||||||
|
|
||||||
|
if (proformaIdResult.isFailure) {
|
||||||
|
return Result.fail(proformaIdResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.deps.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
return await this.deps.deleter.delete({
|
||||||
|
companyId: params.companyId,
|
||||||
|
id: proformaIdResult.data,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
export * from "./change-status-proforma.use-case";
|
export * from "./change-status-proforma.use-case";
|
||||||
export * from "./create-proforma.use-case";
|
export * from "./create-proforma.use-case";
|
||||||
//export * from "./delete-proforma.use-case";
|
export * from "./delete-proforma-by-id.use-case";
|
||||||
export * from "./get-proforma-by-id.use-case";
|
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";
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
type ProformaRecipient,
|
type ProformaRecipient,
|
||||||
type ProformaTaxConfig,
|
type ProformaTaxConfig,
|
||||||
} from "../value-objects";
|
} from "../value-objects";
|
||||||
|
import { ProformaCannotBeDeletedError } from "../errors";
|
||||||
|
|
||||||
export interface IProformaCreateProps {
|
export interface IProformaCreateProps {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
@ -386,6 +387,30 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return Result.ok();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ensureCanBeDeleted(): Result<void, Error> {
|
||||||
|
if (!this.status.isDraft()) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeDeletedError(
|
||||||
|
this.id.toString(),
|
||||||
|
this.status.isIssued()
|
||||||
|
? "Issued proformas cannot be deleted."
|
||||||
|
: "Only draft proformas can be deleted."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.linkedInvoiceId.isSome()) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeDeletedError(
|
||||||
|
this.id.toString(),
|
||||||
|
"Issued proformas cannot be deleted."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
private validateCanBeIssued(): Result<void, Error> {
|
private validateCanBeIssued(): Result<void, Error> {
|
||||||
if (this.description.isNone()) {
|
if (this.description.isNone()) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
|
|||||||
@ -8,5 +8,6 @@ export class ProformaCannotBeDeletedError extends DomainError {
|
|||||||
this.name = "ProformaCannotBeDeletedError";
|
this.name = "ProformaCannotBeDeletedError";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isProformaCannotBeDeletedError = (e: unknown): e is ProformaCannotBeDeletedError =>
|
export const isProformaCannotBeDeletedError = (e: unknown): e is ProformaCannotBeDeletedError =>
|
||||||
e instanceof ProformaCannotBeDeletedError;
|
e instanceof ProformaCannotBeDeletedError;
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type { ICompanyPublicServices } from "../../../../../../companies/src/api
|
|||||||
import {
|
import {
|
||||||
type ChangeStatusProformaUseCase,
|
type ChangeStatusProformaUseCase,
|
||||||
type CreateProformaUseCase,
|
type CreateProformaUseCase,
|
||||||
|
type DeleteProformaByIdUseCase,
|
||||||
type GetProformaByIdUseCase,
|
type GetProformaByIdUseCase,
|
||||||
type IIssuedInvoicePublicServices,
|
type IIssuedInvoicePublicServices,
|
||||||
type IssueProformaUseCase,
|
type IssueProformaUseCase,
|
||||||
@ -15,12 +16,14 @@ import {
|
|||||||
type UpdateProformaByIdUseCase,
|
type UpdateProformaByIdUseCase,
|
||||||
buildChangeStatusProformaUseCase,
|
buildChangeStatusProformaUseCase,
|
||||||
buildCreateProformaUseCase,
|
buildCreateProformaUseCase,
|
||||||
|
buildDeleteProformaByIdUseCase,
|
||||||
buildGetProformaByIdUseCase,
|
buildGetProformaByIdUseCase,
|
||||||
buildIssueProformaUseCase,
|
buildIssueProformaUseCase,
|
||||||
buildListProformasUseCase,
|
buildListProformasUseCase,
|
||||||
buildPreviewProformaReportUseCase,
|
buildPreviewProformaReportUseCase,
|
||||||
buildProformaCatalogResolvers,
|
buildProformaCatalogResolvers,
|
||||||
buildProformaCreator,
|
buildProformaCreator,
|
||||||
|
buildProformaDeleter,
|
||||||
buildProformaFinder,
|
buildProformaFinder,
|
||||||
buildProformaInputMappers,
|
buildProformaInputMappers,
|
||||||
buildProformaIssueReadModelAssembler,
|
buildProformaIssueReadModelAssembler,
|
||||||
@ -53,11 +56,7 @@ export type ProformasInternalDeps = {
|
|||||||
}) => IssueProformaUseCase;
|
}) => IssueProformaUseCase;
|
||||||
updateProforma: () => UpdateProformaByIdUseCase;
|
updateProforma: () => UpdateProformaByIdUseCase;
|
||||||
changeStatusProforma: () => ChangeStatusProformaUseCase;
|
changeStatusProforma: () => ChangeStatusProformaUseCase;
|
||||||
|
deleteProforma: () => DeleteProformaByIdUseCase;
|
||||||
/*
|
|
||||||
deleteProforma: () => DeleteProformaUseCase;
|
|
||||||
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -120,6 +119,9 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
paymentResolver: catalogResolvers.paymentResolver,
|
paymentResolver: catalogResolvers.paymentResolver,
|
||||||
targetInvoiceSeriesValidator,
|
targetInvoiceSeriesValidator,
|
||||||
});
|
});
|
||||||
|
const deleter = buildProformaDeleter({
|
||||||
|
repository,
|
||||||
|
});
|
||||||
|
|
||||||
const statusChanger = buildProformaStatusChanger({
|
const statusChanger = buildProformaStatusChanger({
|
||||||
repository,
|
repository,
|
||||||
@ -177,6 +179,12 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
deleteProforma: () =>
|
||||||
|
buildDeleteProformaByIdUseCase({
|
||||||
|
deleter,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
reportProformaPdf: () =>
|
reportProformaPdf: () =>
|
||||||
buildReportProformaPdfUseCase({
|
buildReportProformaPdfUseCase({
|
||||||
finder,
|
finder,
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { DeleteProformaUseCase } from "../../../../application/index.ts";
|
import type { DeleteProformaByIdUseCase } from "../../../../application/index.ts";
|
||||||
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
|
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
|
||||||
|
|
||||||
export class DeleteProformaController extends ExpressController {
|
export class DeleteProformaController extends ExpressController {
|
||||||
public constructor(private readonly useCase: DeleteProformaUseCase) {
|
public constructor(private readonly useCase: DeleteProformaByIdUseCase) {
|
||||||
super();
|
super();
|
||||||
this.errorMapper = proformasApiErrorMapper;
|
this.errorMapper = proformasApiErrorMapper;
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export class DeleteProformaController extends ExpressController {
|
|||||||
const result = await this.useCase.execute({ proforma_id, companyId });
|
const result = await this.useCase.execute({ proforma_id, companyId });
|
||||||
|
|
||||||
return result.match(
|
return result.match(
|
||||||
(data) => this.ok(data),
|
() => this.noContent(),
|
||||||
(err) => this.handleError(err)
|
(err) => this.handleError(err)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//export * from "./change-status-proforma.controller";
|
//export * from "./change-status-proforma.controller";
|
||||||
//export * from "./create-proforma.controller";
|
//export * from "./create-proforma.controller";
|
||||||
//export * from "./delete-proforma.controller";
|
export * from "./delete-proforma.controller";
|
||||||
export * from "./get-proforma-by-id.controller";
|
export * from "./get-proforma-by-id.controller";
|
||||||
export * from "./issue-proforma.controller";
|
export * from "./issue-proforma.controller";
|
||||||
export * from "./list-proformas.controller";
|
export * from "./list-proformas.controller";
|
||||||
|
|||||||
@ -90,8 +90,8 @@ const proformaCannotBeDeletedRule: ErrorToApiRule = {
|
|||||||
priority: 120,
|
priority: 120,
|
||||||
matches: (e) => isProformaCannotBeDeletedError(e),
|
matches: (e) => isProformaCannotBeDeletedError(e),
|
||||||
build: (e) =>
|
build: (e) =>
|
||||||
new ValidationApiError(
|
new ConflictApiError(
|
||||||
(e as ProformaCannotBeConvertedToInvoiceError).message || "Proforma cannot be deleted."
|
(e as Error).message || "Proforma cannot be deleted."
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { requireIdentityTenant } from "@erp/identity/api";
|
|||||||
import { type NextFunction, type Request, type Response, Router } from "express";
|
import { type NextFunction, type Request, type Response, Router } from "express";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DeleteProformaController,
|
||||||
GetProformaByIdController,
|
GetProformaByIdController,
|
||||||
IssueProformaController,
|
IssueProformaController,
|
||||||
ListProformasController,
|
ListProformasController,
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
ChangeStatusProformaByIdParamsRequestSchema,
|
ChangeStatusProformaByIdParamsRequestSchema,
|
||||||
ChangeStatusProformaByIdRequestSchema,
|
ChangeStatusProformaByIdRequestSchema,
|
||||||
CreateProformaRequestSchema,
|
CreateProformaRequestSchema,
|
||||||
|
DeleteProformaByIdParamsRequestSchema,
|
||||||
GetProformaByIdRequestSchema,
|
GetProformaByIdRequestSchema,
|
||||||
IssueProformaByIdParamsRequestSchema,
|
IssueProformaByIdParamsRequestSchema,
|
||||||
LegacyIssueProformaByIdParamsRequestSchema,
|
LegacyIssueProformaByIdParamsRequestSchema,
|
||||||
@ -117,17 +119,15 @@ export const proformasRouter = (params: StartParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/*router.delete(
|
router.delete(
|
||||||
"/:proforma_id",
|
"/:proforma_id",
|
||||||
//checkTabContext,
|
|
||||||
|
|
||||||
validateRequest(DeleteProformaByIdParamsRequestSchema, "params"),
|
validateRequest(DeleteProformaByIdParamsRequestSchema, "params"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.delete_proforma();
|
const useCase = deps.useCases.deleteProforma();
|
||||||
const controller = new DeleteProformaController(useCase);
|
const controller = new DeleteProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);*/
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:proforma_id/status",
|
"/:proforma_id/status",
|
||||||
|
|||||||
@ -140,6 +140,26 @@ export class SequelizeProformaRepositoryV2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async existsIssuedInvoiceBySourceProformaIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const count = await IssuedInvoiceModel.count({
|
||||||
|
where: {
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
source_proforma_id: id.toString(),
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result.ok(count > 0);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateStatusByIdInCompany(
|
async updateStatusByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import {
|
|||||||
CustomerInvoiceItemModel,
|
CustomerInvoiceItemModel,
|
||||||
CustomerInvoiceModel,
|
CustomerInvoiceModel,
|
||||||
CustomerInvoiceTaxModel,
|
CustomerInvoiceTaxModel,
|
||||||
|
IssuedInvoiceModel,
|
||||||
} from "../../../../common";
|
} from "../../../../common";
|
||||||
import type { SequelizeProformaDomainMapper, SequelizeProformaSummaryMapper } from "../mappers";
|
import type { SequelizeProformaDomainMapper, SequelizeProformaSummaryMapper } from "../mappers";
|
||||||
|
|
||||||
@ -214,6 +215,26 @@ export class ProformaRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async existsIssuedInvoiceBySourceProformaIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const count = await IssuedInvoiceModel.count({
|
||||||
|
where: {
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
source_proforma_id: id.toString(),
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result.ok(count > 0);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Actualiza el "status" de una proforma
|
* Actualiza el "status" de una proforma
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { z } from "zod/v4";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const DeleteProformaByIdParamsRequestSchema = z.object({
|
export const DeleteProformaByIdParamsRequestSchema = z.object({
|
||||||
proforma_id: z.string(),
|
proforma_id: z.uuid(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type DeleteProformaByIdRequestDTO = z.infer<typeof DeleteProformaByIdParamsRequestSchema>;
|
export type DeleteProformaByIdRequestDTO = z.infer<typeof DeleteProformaByIdParamsRequestSchema>;
|
||||||
|
|||||||
@ -110,16 +110,23 @@
|
|||||||
"unknown_error": "An unexpected error occurred"
|
"unknown_error": "An unexpected error occurred"
|
||||||
},
|
},
|
||||||
"delete_proforma_dialog": {
|
"delete_proforma_dialog": {
|
||||||
"single_title": "Delete proforma {{reference}}",
|
"single_title": "Delete proforma",
|
||||||
"single_description": "Are you sure you want to delete this proforma? This action cannot be undone.",
|
"single_description": "This proforma will be deleted. Its reference will not be reused.",
|
||||||
"second_confirm_single_title": "Additional confirmation",
|
"multiple_title": "Delete proformas",
|
||||||
"second_confirm_single_description": "You are about to delete the proforma. This action cannot be undone. Are you sure you want to continue?",
|
"multiple_description": "{{count}} draft proformas will be deleted. Their references will not be reused.",
|
||||||
"multiple_description": "Are you sure you want to delete the {{total}} selected proformas? This action cannot be undone.",
|
"warning": "This action does not delete issued invoices and does not reuse numbering.",
|
||||||
"confirm_delete": "Confirm deletion",
|
"confirm_delete": "Confirm deletion",
|
||||||
"deleting": "Deleting...",
|
"deleting": "Deleting...",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"continue": "Delete",
|
"list_item": "Proforma {{reference}}",
|
||||||
"list_item": "Proforma {{reference}}"
|
"success_title": "Proforma deleted",
|
||||||
|
"success_single_message": "Proforma {{reference}} has been deleted successfully.",
|
||||||
|
"success_multiple_message": "{{count}} proformas have been deleted successfully.",
|
||||||
|
"error_title": "Unable to delete the proforma",
|
||||||
|
"error_single_message": "The selected proforma could not be deleted.",
|
||||||
|
"error_multiple_message": "{{count}} proformas could not be deleted.",
|
||||||
|
"error_conflict_message": "Only draft proformas can be deleted.",
|
||||||
|
"error_not_found_message": "The proforma no longer exists or is not available."
|
||||||
},
|
},
|
||||||
"change_proforma_status_dialog": {
|
"change_proforma_status_dialog": {
|
||||||
"title": "Change proforma status",
|
"title": "Change proforma status",
|
||||||
@ -190,6 +197,9 @@
|
|||||||
"title": "Delete customer proforma",
|
"title": "Delete customer proforma",
|
||||||
"description": "Delete the selected customer proforma"
|
"description": "Delete the selected customer proforma"
|
||||||
},
|
},
|
||||||
|
"delete.error_title": "Unable to delete the proforma",
|
||||||
|
"delete.error_unexpected_message": "An unexpected error occurred while deleting the proforma."
|
||||||
|
,
|
||||||
"view": {
|
"view": {
|
||||||
"title": "View customer proforma",
|
"title": "View customer proforma",
|
||||||
"description": "View the details of the selected customer proforma"
|
"description": "View the details of the selected customer proforma"
|
||||||
|
|||||||
@ -111,16 +111,23 @@
|
|||||||
"cancel": "Cancelar"
|
"cancel": "Cancelar"
|
||||||
},
|
},
|
||||||
"delete_proforma_dialog": {
|
"delete_proforma_dialog": {
|
||||||
"single_title": "Eliminar proforma {{reference}}",
|
"single_title": "Eliminar proforma",
|
||||||
"single_description": "¿Seguro que deseas eliminar esta proforma? Esta acción no se puede deshacer.",
|
"single_description": "Se eliminará la proforma. Su referencia no se reutilizará.",
|
||||||
"second_confirm_single_title": "Confirmación adicional",
|
"multiple_title": "Eliminar proformas",
|
||||||
"second_confirm_single_description": "Estás a punto de borrar la proforma. Esta acción masiva no se puede deshacer. ¿Seguro que quieres continuar?",
|
"multiple_description": "Se eliminarán {{count}} proformas draft. Sus referencias no se reutilizarán.",
|
||||||
"multiple_description": "¿Seguro que deseas eliminar las {{total}} proformas seleccionadas? Esta acción no se puede deshacer.",
|
"warning": "Esta acción no elimina facturas emitidas ni reutiliza numeración.",
|
||||||
"confirm_delete": "Confirmar eliminación",
|
"confirm_delete": "Confirmar eliminación",
|
||||||
"deleting": "Eliminando...",
|
"deleting": "Eliminando...",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"continue": "Eliminar",
|
"list_item": "Proforma {{reference}}",
|
||||||
"list_item": "Proforma {{reference}}"
|
"success_title": "Proforma eliminada",
|
||||||
|
"success_single_message": "La proforma {{reference}} se ha eliminado correctamente.",
|
||||||
|
"success_multiple_message": "Se han eliminado {{count}} proformas correctamente.",
|
||||||
|
"error_title": "No se pudo eliminar la proforma",
|
||||||
|
"error_single_message": "La proforma seleccionada no se ha podido eliminar.",
|
||||||
|
"error_multiple_message": "{{count}} proformas no se han podido eliminar.",
|
||||||
|
"error_conflict_message": "Solo se pueden eliminar proformas en borrador.",
|
||||||
|
"error_not_found_message": "La proforma ya no existe o no está disponible."
|
||||||
},
|
},
|
||||||
"change_proforma_status_dialog": {
|
"change_proforma_status_dialog": {
|
||||||
"title": "Cambiar estado de la proforma",
|
"title": "Cambiar estado de la proforma",
|
||||||
@ -191,6 +198,9 @@
|
|||||||
"title": "Eliminar proforma",
|
"title": "Eliminar proforma",
|
||||||
"description": "Eliminar la proforma seleccionada"
|
"description": "Eliminar la proforma seleccionada"
|
||||||
},
|
},
|
||||||
|
"delete.error_title": "No se pudo eliminar la proforma",
|
||||||
|
"delete.error_unexpected_message": "Ha ocurrido un error inesperado al eliminar la proforma."
|
||||||
|
,
|
||||||
"view": {
|
"view": {
|
||||||
"title": "Ver proforma",
|
"title": "Ver proforma",
|
||||||
"description": "Ver los detalles de la proforma seleccionada"
|
"description": "Ver los detalles de la proforma seleccionada"
|
||||||
|
|||||||
@ -6,23 +6,20 @@ import { useTranslation } from "../../../i18n";
|
|||||||
import type { DeleteProformaByIdParams } from "../../shared";
|
import type { DeleteProformaByIdParams } from "../../shared";
|
||||||
import { useProformaDeleteMutation } from "../../shared";
|
import { useProformaDeleteMutation } from "../../shared";
|
||||||
import type { DeleteProformaTarget } from "../entities";
|
import type { DeleteProformaTarget } from "../entities";
|
||||||
|
type DeleteErrorWithStatus = Error & { status?: number };
|
||||||
type ConfirmStep = "initial" | "second";
|
|
||||||
|
|
||||||
interface DeleteProformaDialogState {
|
interface DeleteProformaDialogState {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
targets: DeleteProformaTarget[];
|
targets: DeleteProformaTarget[];
|
||||||
confirmStep: ConfirmStep;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_STATE: DeleteProformaDialogState = {
|
const INITIAL_STATE: DeleteProformaDialogState = {
|
||||||
open: false,
|
open: false,
|
||||||
targets: [],
|
targets: [],
|
||||||
confirmStep: "initial",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildDeleteParams = (target: DeleteProformaTarget): DeleteProformaByIdParams => ({
|
const buildDeleteParams = (target: DeleteProformaTarget): DeleteProformaByIdParams => ({
|
||||||
id: target.id,
|
proformaId: target.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getProformaLabel = (target: DeleteProformaTarget) => target.reference || `#${target.id}`;
|
const getProformaLabel = (target: DeleteProformaTarget) => target.reference || `#${target.id}`;
|
||||||
@ -39,7 +36,6 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
setState({
|
setState({
|
||||||
open: true,
|
open: true,
|
||||||
targets,
|
targets,
|
||||||
confirmStep: "initial",
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -48,13 +44,6 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
setState(INITIAL_STATE);
|
setState(INITIAL_STATE);
|
||||||
}, [deleteMutation.isPending]);
|
}, [deleteMutation.isPending]);
|
||||||
|
|
||||||
const moveToSecondConfirmStep = React.useCallback(() => {
|
|
||||||
setState((current) => ({
|
|
||||||
...current,
|
|
||||||
confirmStep: "second",
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const notifySuccess = React.useCallback(
|
const notifySuccess = React.useCallback(
|
||||||
(targets: DeleteProformaTarget[]) => {
|
(targets: DeleteProformaTarget[]) => {
|
||||||
if (targets.length === 1) {
|
if (targets.length === 1) {
|
||||||
@ -77,12 +66,38 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
[t]
|
[t]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getDeleteErrorMessage = React.useCallback(
|
||||||
|
(error: unknown) => {
|
||||||
|
const status = (error as DeleteErrorWithStatus | undefined)?.status;
|
||||||
|
|
||||||
|
if (status === 404) {
|
||||||
|
return t(
|
||||||
|
"proformas.delete_proforma_dialog.error_not_found_message",
|
||||||
|
"La proforma ya no existe o no está disponible."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 409) {
|
||||||
|
return t(
|
||||||
|
"proformas.delete_proforma_dialog.error_conflict_message",
|
||||||
|
"Solo se pueden eliminar proformas en borrador."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t(
|
||||||
|
"proformas.delete.error_unexpected_message",
|
||||||
|
"Ha ocurrido un error inesperado al eliminar la proforma."
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
|
||||||
const notifyPartialError = React.useCallback(
|
const notifyPartialError = React.useCallback(
|
||||||
(targets: DeleteProformaTarget[], errorCount: number) => {
|
(targets: DeleteProformaTarget[], errorCount: number, firstError?: unknown) => {
|
||||||
if (targets.length === 1) {
|
if (targets.length === 1) {
|
||||||
showErrorToast(
|
showErrorToast(
|
||||||
t("proformas.delete_proforma_dialog.error_title"),
|
t("proformas.delete_proforma_dialog.error_title"),
|
||||||
t("proformas.delete_proforma_dialog.error_single_message")
|
getDeleteErrorMessage(firstError)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -94,7 +109,7 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[t]
|
[getDeleteErrorMessage, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const submitDelete = React.useCallback(async () => {
|
const submitDelete = React.useCallback(async () => {
|
||||||
@ -106,6 +121,9 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
|
|
||||||
const successCount = results.filter((result) => result.status === "fulfilled").length;
|
const successCount = results.filter((result) => result.status === "fulfilled").length;
|
||||||
const errorCount = results.length - successCount;
|
const errorCount = results.length - successCount;
|
||||||
|
const firstRejectedResult = results.find((result) => result.status === "rejected");
|
||||||
|
const firstError =
|
||||||
|
firstRejectedResult?.status === "rejected" ? firstRejectedResult.reason : undefined;
|
||||||
|
|
||||||
if (successCount > 0) {
|
if (successCount > 0) {
|
||||||
const fulfilledTargets = targets.filter((_, index) => results[index]?.status === "fulfilled");
|
const fulfilledTargets = targets.filter((_, index) => results[index]?.status === "fulfilled");
|
||||||
@ -116,13 +134,13 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rejectedTargets.length > 0) {
|
if (rejectedTargets.length > 0) {
|
||||||
notifyPartialError(targets, rejectedTargets.length);
|
notifyPartialError(targets, rejectedTargets.length, firstError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorCount > 0) {
|
if (errorCount > 0) {
|
||||||
notifyPartialError(targets, errorCount);
|
notifyPartialError(targets, errorCount, firstError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,11 +152,6 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.confirmStep === "initial") {
|
|
||||||
moveToSecondConfirmStep();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await submitDelete();
|
await submitDelete();
|
||||||
} catch {
|
} catch {
|
||||||
@ -147,13 +160,12 @@ export const useDeleteProformaDialogController = () => {
|
|||||||
t("proformas.delete.error_unexpected_message")
|
t("proformas.delete.error_unexpected_message")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [deleteMutation.isPending, moveToSecondConfirmStep, state, submitDelete, t]);
|
}, [deleteMutation.isPending, state, submitDelete, t]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
open: state.open,
|
open: state.open,
|
||||||
targets: state.targets,
|
targets: state.targets,
|
||||||
isSubmitting: deleteMutation.isPending,
|
isSubmitting: deleteMutation.isPending,
|
||||||
isSecondConfirmStep: state.confirmStep === "second",
|
|
||||||
isBulkDelete: state.targets.length > 1,
|
isBulkDelete: state.targets.length > 1,
|
||||||
|
|
||||||
openDialog,
|
openDialog,
|
||||||
|
|||||||
@ -18,7 +18,6 @@ interface DeleteProformaDialogProps {
|
|||||||
targets: DeleteProformaTarget[];
|
targets: DeleteProformaTarget[];
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
isSecondConfirmStep: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTargetLabel = (target: DeleteProformaTarget) => target.reference || `#${target.id}`;
|
const getTargetLabel = (target: DeleteProformaTarget) => target.reference || `#${target.id}`;
|
||||||
@ -29,7 +28,6 @@ export const DeleteProformaDialog = ({
|
|||||||
targets,
|
targets,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
isSecondConfirmStep,
|
|
||||||
}: DeleteProformaDialogProps) => {
|
}: DeleteProformaDialogProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -37,33 +35,19 @@ export const DeleteProformaDialog = ({
|
|||||||
const isSingle = total === 1;
|
const isSingle = total === 1;
|
||||||
const firstTarget = targets[0];
|
const firstTarget = targets[0];
|
||||||
|
|
||||||
const title = isSecondConfirmStep
|
const title = isSingle
|
||||||
? isSingle
|
? t("proformas.delete_proforma_dialog.single_title", {
|
||||||
? t("proformas.delete_proforma_dialog.second_confirm_single_title", {
|
reference: getTargetLabel(firstTarget),
|
||||||
reference: getTargetLabel(firstTarget),
|
})
|
||||||
})
|
: t("proformas.delete_proforma_dialog.multiple_title", {
|
||||||
: t("proformas.delete_proforma_dialog.second_confirm_multiple_title", {
|
count: total,
|
||||||
count: total,
|
});
|
||||||
})
|
|
||||||
: isSingle
|
|
||||||
? t("proformas.delete_proforma_dialog.single_title", {
|
|
||||||
reference: getTargetLabel(firstTarget),
|
|
||||||
})
|
|
||||||
: t("proformas.delete_proforma_dialog.multiple_title", {
|
|
||||||
count: total,
|
|
||||||
});
|
|
||||||
|
|
||||||
const description = isSecondConfirmStep
|
const description = isSingle
|
||||||
? isSingle
|
? t("proformas.delete_proforma_dialog.single_description")
|
||||||
? t("proformas.delete_proforma_dialog.second_confirm_single_description")
|
: t("proformas.delete_proforma_dialog.multiple_description", {
|
||||||
: t("proformas.delete_proforma_dialog.second_confirm_multiple_description", {
|
count: total,
|
||||||
count: total,
|
});
|
||||||
})
|
|
||||||
: isSingle
|
|
||||||
? t("proformas.delete_proforma_dialog.single_description")
|
|
||||||
: t("proformas.delete_proforma_dialog.multiple_description", {
|
|
||||||
count: total,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
@ -79,7 +63,11 @@ export const DeleteProformaDialog = ({
|
|||||||
<AlertDialogDescription>{description}</AlertDialogDescription>
|
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
|
|
||||||
{!isSecondConfirmStep && total > 1 ? (
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("proformas.delete_proforma_dialog.warning")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{total > 1 ? (
|
||||||
<div className="mt-4 max-h-48 overflow-y-auto rounded-md border p-3 text-sm">
|
<div className="mt-4 max-h-48 overflow-y-auto rounded-md border p-3 text-sm">
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
@ -104,10 +92,8 @@ export const DeleteProformaDialog = ({
|
|||||||
<Spinner className="mr-2 size-4" />
|
<Spinner className="mr-2 size-4" />
|
||||||
{t("proformas.delete_proforma_dialog.deleting")}
|
{t("proformas.delete_proforma_dialog.deleting")}
|
||||||
</>
|
</>
|
||||||
) : isSecondConfirmStep ? (
|
|
||||||
t("proformas.delete_proforma_dialog.confirm_delete")
|
|
||||||
) : (
|
) : (
|
||||||
t("proformas.delete_proforma_dialog.continue")
|
t("proformas.delete_proforma_dialog.confirm_delete")
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|||||||
@ -275,6 +275,7 @@ export function useProformasGridColumns(
|
|||||||
|
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const proforma = row.original;
|
const proforma = row.original;
|
||||||
|
const isDraft = proforma.status === PROFORMA_STATUS.DRAFT;
|
||||||
const isIssued = proforma.status === PROFORMA_STATUS.ISSUED;
|
const isIssued = proforma.status === PROFORMA_STATUS.ISSUED;
|
||||||
const isApproved = proforma.status === PROFORMA_STATUS.APPROVED;
|
const isApproved = proforma.status === PROFORMA_STATUS.APPROVED;
|
||||||
const availableTransitions =
|
const availableTransitions =
|
||||||
@ -396,7 +397,7 @@ export function useProformasGridColumns(
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
{/* Eliminar */}
|
{/* Eliminar */}
|
||||||
{!isIssued && actionHandlers.onDeleteClick && (
|
{isDraft && actionHandlers.onDeleteClick && (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
|
|||||||
@ -332,7 +332,6 @@ export const ListProformasPage = () => {
|
|||||||
|
|
||||||
{/* Eliminar */}
|
{/* Eliminar */}
|
||||||
<DeleteProformaDialog
|
<DeleteProformaDialog
|
||||||
isSecondConfirmStep={deleteDialogCtrl.isSecondConfirmStep}
|
|
||||||
isSubmitting={deleteDialogCtrl.isSubmitting}
|
isSubmitting={deleteDialogCtrl.isSubmitting}
|
||||||
onConfirm={deleteDialogCtrl.confirmDelete}
|
onConfirm={deleteDialogCtrl.confirmDelete}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import type { IDataSource } from "@erp/core/client";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export interface DeleteProformaByIdParams {
|
export interface DeleteProformaByIdParams {
|
||||||
id: string;
|
proformaId: string;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,9 +20,9 @@ export const deleteProformaById = (
|
|||||||
dataSource: IDataSource,
|
dataSource: IDataSource,
|
||||||
params: DeleteProformaByIdParams
|
params: DeleteProformaByIdParams
|
||||||
): Promise<DeleteProformaByIdResult> => {
|
): Promise<DeleteProformaByIdResult> => {
|
||||||
const { id, signal } = params;
|
const { proformaId, signal } = params;
|
||||||
|
|
||||||
if (!id) throw new Error("proformaId is required");
|
if (!proformaId) throw new Error("proformaId is required");
|
||||||
|
|
||||||
return dataSource.deleteOne("proformas", id, { signal });
|
return dataSource.deleteOne("proformas", proformaId, { signal });
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,27 +18,32 @@ export const useProformaDeleteMutation = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const dataSource = useDataSource();
|
const dataSource = useDataSource();
|
||||||
|
|
||||||
return useMutation<{ id: string }, DefaultError, DeleteProformaByIdParams, DeleteProformaContext>(
|
return useMutation<
|
||||||
|
{ proformaId: string },
|
||||||
|
DefaultError,
|
||||||
|
DeleteProformaByIdParams,
|
||||||
|
DeleteProformaContext
|
||||||
|
>(
|
||||||
{
|
{
|
||||||
mutationKey: DELETE_PROFORMA_MUTATION_KEY,
|
mutationKey: DELETE_PROFORMA_MUTATION_KEY,
|
||||||
mutationFn: async ({ id, signal }) => {
|
mutationFn: async ({ proformaId, signal }) => {
|
||||||
if (!id) {
|
if (!proformaId) {
|
||||||
throw new Error("proformaId is required");
|
throw new Error("proformaId is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteProformaById(dataSource, { id, signal });
|
await deleteProformaById(dataSource, { proformaId, signal });
|
||||||
return { id };
|
return { proformaId };
|
||||||
},
|
},
|
||||||
onMutate: async ({ id }) => {
|
onMutate: async ({ proformaId }) => {
|
||||||
return prepareDeleteProformaOptimisticUpdate(queryClient, id);
|
return prepareDeleteProformaOptimisticUpdate(queryClient, proformaId);
|
||||||
},
|
},
|
||||||
|
|
||||||
onError: (_error, _params, context) => {
|
onError: (_error, _params, context) => {
|
||||||
rollbackDeleteProformaOptimisticUpdate(queryClient, context);
|
rollbackDeleteProformaOptimisticUpdate(queryClient, context);
|
||||||
},
|
},
|
||||||
|
|
||||||
onSettled: async (_data, _error, { id }) => {
|
onSettled: async (_data, _error, { proformaId }) => {
|
||||||
await finalizeDeletedProformaCaches(queryClient, id);
|
await finalizeDeletedProformaCaches(queryClient, proformaId);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user