Archivado de proformas
This commit is contained in:
parent
718e8b13d9
commit
eb8cbd97f2
33
docs/customer-invoices/proforma-archive-contract.md
Normal file
33
docs/customer-invoices/proforma-archive-contract.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Proforma archive contract
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Archive uses `archived_at` and does not change `status`.
|
||||||
|
- Only `draft` and `rejected` proformas can be archived in V1.
|
||||||
|
- Only `draft` and `rejected` archived proformas can be unarchived in V1.
|
||||||
|
- Archived proformas remain available through `GET /proformas/:id`.
|
||||||
|
- Deleted proformas remain excluded from every flow.
|
||||||
|
- Numbering is never reused and `document_series.next_number` is untouched.
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
- `PATCH /proformas/:proforma_id/archive`
|
||||||
|
- `PATCH /proformas/:proforma_id/unarchive`
|
||||||
|
|
||||||
|
Both endpoints:
|
||||||
|
|
||||||
|
- return `200` with the updated proforma snapshot
|
||||||
|
- return `404` when the proforma does not exist, belongs to another company, or is deleted
|
||||||
|
- return `409` when the state is not allowed, when the proforma is already archived/non-archived, or when it is linked to an issued invoice
|
||||||
|
|
||||||
|
## Listing behavior
|
||||||
|
|
||||||
|
- `GET /proformas` excludes archived proformas by default
|
||||||
|
- `GET /proformas?archived=false` lists only active proformas
|
||||||
|
- `GET /proformas?archived=true` lists only archived proformas
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `archived` is a visibility dimension, not a business status
|
||||||
|
- V1 does not support archiving `sent`, `approved`, or `issued`
|
||||||
|
- V1 does not add `archived_by` or `archive_reason`
|
||||||
@ -151,3 +151,7 @@ Si devuelve filas:
|
|||||||
|
|
||||||
- `payment_term_id` aparece ya en contratos de transporte, pero el dominio/snapshot de proformas sigue sin soporte funcional completo en esta fase
|
- `payment_term_id` aparece ya en contratos de transporte, pero el dominio/snapshot de proformas sigue sin soporte funcional completo en esta fase
|
||||||
- no se introduce un motor fiscal nuevo en este ajuste
|
- no se introduce un motor fiscal nuevo en este ajuste
|
||||||
|
## Archivado
|
||||||
|
|
||||||
|
- una proforma creada nace con `archived_at = NULL`
|
||||||
|
- el archivado posterior no altera `status`
|
||||||
|
|||||||
@ -10,6 +10,8 @@ Definir el borrado funcional de proformas sin romper trazabilidad ni numeracion.
|
|||||||
- `sent`, `approved`, `rejected` e `issued` devuelven conflicto
|
- `sent`, `approved`, `rejected` e `issued` devuelven conflicto
|
||||||
- `rejected` no se borra; queda reservado para una futura operacion de archivado
|
- `rejected` no se borra; queda reservado para una futura operacion de archivado
|
||||||
- el borrado es logico mediante `deleted_at`
|
- el borrado es logico mediante `deleted_at`
|
||||||
|
- las proformas archivadas no se exponen para borrado en UI V1
|
||||||
|
- si una proforma draft esta archivada, debe desarchivarse antes de borrarla
|
||||||
- la numeracion no se reutiliza
|
- la numeracion no se reutiliza
|
||||||
- no se decrementa `document_series.next_number`
|
- no se decrementa `document_series.next_number`
|
||||||
- no se borran fisicamente `proforma_items` ni `proforma_taxes`
|
- no se borran fisicamente `proforma_items` ni `proforma_taxes`
|
||||||
|
|||||||
@ -169,6 +169,7 @@ Preparar el esquema fisico para separar:
|
|||||||
|
|
||||||
- `DELETE /proformas/:proforma_id` vuelve a estar operativo sobre persistencia V2
|
- `DELETE /proformas/:proforma_id` vuelve a estar operativo sobre persistencia V2
|
||||||
- el borrado es logico mediante `deleted_at`
|
- el borrado es logico mediante `deleted_at`
|
||||||
|
- el archivado es logico mediante `archived_at`
|
||||||
- solo `draft` puede borrarse
|
- solo `draft` puede borrarse
|
||||||
- `rejected` sigue fuera del alcance y queda reservado para archivado futuro
|
- `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 validacion de borrado comprueba tambien `linked_invoice_id` y `issued_invoices.source_proforma_id`
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE proformas
|
||||||
|
ADD COLUMN archived_at DATETIME NULL;
|
||||||
9
docs/customer-invoices/sql/validate-proforma-archive.sql
Normal file
9
docs/customer-invoices/sql/validate-proforma-archive.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
SELECT id, proforma_reference, status, archived_at
|
||||||
|
FROM proformas
|
||||||
|
WHERE archived_at IS NOT NULL
|
||||||
|
AND status NOT IN ('draft', 'rejected');
|
||||||
|
|
||||||
|
SELECT id, proforma_reference, status, archived_at, deleted_at
|
||||||
|
FROM proformas
|
||||||
|
WHERE archived_at IS NOT NULL
|
||||||
|
AND deleted_at IS NOT NULL;
|
||||||
@ -76,7 +76,7 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
|
|||||||
companyId: proforma.companyId,
|
companyId: proforma.companyId,
|
||||||
status: InvoiceStatus.issued(),
|
status: InvoiceStatus.issued(),
|
||||||
|
|
||||||
series: proforma.series,
|
series: proforma.targetInvoiceSeriesCode,
|
||||||
linkedProformaId: proforma.id,
|
linkedProformaId: proforma.id,
|
||||||
|
|
||||||
// La fecha de factura debe reflejar la emisión, no la fecha original de la proforma.
|
// La fecha de factura debe reflejar la emisión, no la fecha original de la proforma.
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./proforma-archiver.di";
|
||||||
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-deleter.di";
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
import { type IProformaArchiver, ProformaArchiver } from "../services";
|
||||||
|
|
||||||
|
export function buildProformaArchiver(deps: {
|
||||||
|
repository: IProformaRepository;
|
||||||
|
}): IProformaArchiver {
|
||||||
|
return new ProformaArchiver({
|
||||||
|
repository: deps.repository,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
|||||||
import type { ICreateProformaInputMapper, IUpdateProformaInputMapper } from "../mappers";
|
import type { ICreateProformaInputMapper, IUpdateProformaInputMapper } from "../mappers";
|
||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
import type {
|
import type {
|
||||||
|
IProformaArchiver,
|
||||||
ICompanyReportProfileFinder,
|
ICompanyReportProfileFinder,
|
||||||
IProformaDeleter,
|
IProformaDeleter,
|
||||||
IProformaCreator,
|
IProformaCreator,
|
||||||
@ -22,6 +23,7 @@ import type {
|
|||||||
IProformaSummarySnapshotBuilder,
|
IProformaSummarySnapshotBuilder,
|
||||||
} from "../snapshot-builders";
|
} from "../snapshot-builders";
|
||||||
import {
|
import {
|
||||||
|
ArchiveProformaByIdUseCase,
|
||||||
ChangeStatusProformaUseCase,
|
ChangeStatusProformaUseCase,
|
||||||
CreateProformaUseCase,
|
CreateProformaUseCase,
|
||||||
DeleteProformaByIdUseCase,
|
DeleteProformaByIdUseCase,
|
||||||
@ -30,6 +32,7 @@ import {
|
|||||||
ListProformasUseCase,
|
ListProformasUseCase,
|
||||||
PreviewProformaReportUseCase,
|
PreviewProformaReportUseCase,
|
||||||
ReportProformaPdfUseCase,
|
ReportProformaPdfUseCase,
|
||||||
|
UnarchiveProformaByIdUseCase,
|
||||||
UpdateProformaByIdUseCase,
|
UpdateProformaByIdUseCase,
|
||||||
} from "../use-cases";
|
} from "../use-cases";
|
||||||
|
|
||||||
@ -175,6 +178,38 @@ export function buildDeleteProformaByIdUseCase(deps: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildArchiveProformaByIdUseCase(deps: {
|
||||||
|
archiver: IProformaArchiver;
|
||||||
|
finder: IProformaFinder;
|
||||||
|
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
||||||
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}) {
|
||||||
|
return new ArchiveProformaByIdUseCase({
|
||||||
|
archiver: deps.archiver,
|
||||||
|
finder: deps.finder,
|
||||||
|
fullReadModelAssembler: deps.fullReadModelAssembler,
|
||||||
|
fullSnapshotBuilder: deps.fullSnapshotBuilder,
|
||||||
|
transactionManager: deps.transactionManager,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildUnarchiveProformaByIdUseCase(deps: {
|
||||||
|
archiver: IProformaArchiver;
|
||||||
|
finder: IProformaFinder;
|
||||||
|
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
||||||
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}) {
|
||||||
|
return new UnarchiveProformaByIdUseCase({
|
||||||
|
archiver: deps.archiver,
|
||||||
|
finder: deps.finder,
|
||||||
|
fullReadModelAssembler: deps.fullReadModelAssembler,
|
||||||
|
fullSnapshotBuilder: deps.fullSnapshotBuilder,
|
||||||
|
transactionManager: deps.transactionManager,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function buildChangeStatusProformaUseCase(deps: {
|
export function buildChangeStatusProformaUseCase(deps: {
|
||||||
statusChanger: IProformaStatusChanger;
|
statusChanger: IProformaStatusChanger;
|
||||||
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export type ProformaSummary = {
|
|||||||
isProforma: boolean;
|
isProforma: boolean;
|
||||||
proformaReference: InvoiceNumber;
|
proformaReference: InvoiceNumber;
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
|
archivedAt: Maybe<UtcDate>;
|
||||||
series: Maybe<InvoiceSerie>;
|
series: Maybe<InvoiceSerie>;
|
||||||
|
|
||||||
proformaDate: UtcDate;
|
proformaDate: UtcDate;
|
||||||
|
|||||||
@ -22,6 +22,12 @@ export interface IProformaRepository {
|
|||||||
transaction: unknown
|
transaction: unknown
|
||||||
): Promise<Result<Proforma, Error>>;
|
): Promise<Result<Proforma, Error>>;
|
||||||
|
|
||||||
|
getByIdIncludingArchivedInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: unknown
|
||||||
|
): Promise<Result<Proforma, Error>>;
|
||||||
|
|
||||||
findByCriteriaInCompany(
|
findByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
@ -34,6 +40,18 @@ export interface IProformaRepository {
|
|||||||
transaction: unknown
|
transaction: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
archiveByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: unknown
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
|
unarchiveByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: unknown
|
||||||
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
existsIssuedInvoiceBySourceProformaIdInCompany(
|
existsIssuedInvoiceBySourceProformaIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./proforma-archiver";
|
||||||
export * from "./assemblers";
|
export * from "./assemblers";
|
||||||
export * from "./catalog-resolver";
|
export * from "./catalog-resolver";
|
||||||
export * from "./company-report-profile-finder";
|
export * from "./company-report-profile-finder";
|
||||||
|
|||||||
@ -0,0 +1,106 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ProformaCannotBeArchivedError,
|
||||||
|
ProformaCannotBeUnarchivedError,
|
||||||
|
} from "../../../domain";
|
||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface IProformaArchiver {
|
||||||
|
archive(params: { companyId: UniqueID; id: UniqueID; transaction: unknown }): Promise<Result<void, Error>>;
|
||||||
|
unarchive(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
transaction: unknown;
|
||||||
|
}): Promise<Result<void, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProformaArchiver implements IProformaArchiver {
|
||||||
|
public constructor(private readonly deps: { repository: IProformaRepository }) {}
|
||||||
|
|
||||||
|
public async archive(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
transaction: unknown;
|
||||||
|
}): Promise<Result<void, Error>> {
|
||||||
|
const proformaResult = await this.deps.repository.getByIdIncludingArchivedInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (proformaResult.isFailure) {
|
||||||
|
return Result.fail(proformaResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proforma = proformaResult.data;
|
||||||
|
const canArchiveResult = proforma.ensureCanBeArchived();
|
||||||
|
|
||||||
|
if (canArchiveResult.isFailure) {
|
||||||
|
return Result.fail(canArchiveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistenceResult = await this.deps.repository.archiveByIdInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (persistenceResult.isFailure) {
|
||||||
|
return Result.fail(persistenceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!persistenceResult.data) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeArchivedError(params.id.toString(), "Proforma is already archived.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async unarchive(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
transaction: unknown;
|
||||||
|
}): Promise<Result<void, Error>> {
|
||||||
|
const proformaResult = await this.deps.repository.getByIdIncludingArchivedInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (proformaResult.isFailure) {
|
||||||
|
return Result.fail(proformaResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proforma = proformaResult.data;
|
||||||
|
const canUnarchiveResult = proforma.ensureCanBeUnarchived();
|
||||||
|
|
||||||
|
if (canUnarchiveResult.isFailure) {
|
||||||
|
return Result.fail(canUnarchiveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistenceResult = await this.deps.repository.unarchiveByIdInCompany(
|
||||||
|
params.companyId,
|
||||||
|
params.id,
|
||||||
|
params.transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (persistenceResult.isFailure) {
|
||||||
|
return Result.fail(persistenceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!persistenceResult.data) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeUnarchivedError(
|
||||||
|
params.id.toString(),
|
||||||
|
"Proforma is not archived."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -105,10 +105,11 @@ export class ProformaCreator implements IProformaCreator {
|
|||||||
{
|
{
|
||||||
...resolvedPropsResult.data,
|
...resolvedPropsResult.data,
|
||||||
companyId,
|
companyId,
|
||||||
|
archivedAt: Maybe.none(),
|
||||||
documentSeriesId: Maybe.some(numberResult.data.documentSeriesId),
|
documentSeriesId: Maybe.some(numberResult.data.documentSeriesId),
|
||||||
proformaNumber: Maybe.some(proformaNumberResult.data),
|
proformaNumber: Maybe.some(proformaNumberResult.data),
|
||||||
proformaReference,
|
proformaReference,
|
||||||
series: resolvedPropsResult.data.targetInvoiceSeriesCode,
|
targetInvoiceSeriesCode: resolvedPropsResult.data.targetInvoiceSeriesCode,
|
||||||
},
|
},
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
|
|||||||
@ -13,6 +13,12 @@ export interface IProformaFinder {
|
|||||||
transaction?: unknown
|
transaction?: unknown
|
||||||
): Promise<Result<Proforma, Error>>;
|
): Promise<Result<Proforma, Error>>;
|
||||||
|
|
||||||
|
findProformaByIdIncludingArchived(
|
||||||
|
companyId: UniqueID,
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<Proforma, Error>>;
|
||||||
|
|
||||||
proformaExists(
|
proformaExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
@ -37,6 +43,14 @@ export class ProformaFinder implements IProformaFinder {
|
|||||||
return this.repository.getByIdInCompany(companyId, proformaId, transaction);
|
return this.repository.getByIdInCompany(companyId, proformaId, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findProformaByIdIncludingArchived(
|
||||||
|
companyId: UniqueID,
|
||||||
|
proformaId: UniqueID,
|
||||||
|
transaction?: unknown
|
||||||
|
): Promise<Result<Proforma, Error>> {
|
||||||
|
return this.repository.getByIdIncludingArchivedInCompany(companyId, proformaId, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
async proformaExists(
|
async proformaExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export class ProformaUpdater implements IProformaUpdater {
|
|||||||
|
|
||||||
const resolvedPatch = await this.resolvePatchProps({
|
const resolvedPatch = await this.resolvePatchProps({
|
||||||
companyId,
|
companyId,
|
||||||
currentInvoiceDate: proforma.invoiceDate,
|
currentInvoiceDate: proforma.proformaDate,
|
||||||
currentTaxConfig: proforma.taxConfig,
|
currentTaxConfig: proforma.taxConfig,
|
||||||
patch: patchProps,
|
patch: patchProps,
|
||||||
transaction,
|
transaction,
|
||||||
@ -168,7 +168,7 @@ export class ProformaUpdater implements IProformaUpdater {
|
|||||||
if (patch.items === undefined) {
|
if (patch.items === undefined) {
|
||||||
const resolvedPatch: ProformaPatchProps = {
|
const resolvedPatch: ProformaPatchProps = {
|
||||||
...patch,
|
...patch,
|
||||||
series: patch.targetInvoiceSeriesCode,
|
targetInvoiceSeriesCode: patch.targetInvoiceSeriesCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (taxConfigResult.data !== undefined) {
|
if (taxConfigResult.data !== undefined) {
|
||||||
@ -200,7 +200,7 @@ export class ProformaUpdater implements IProformaUpdater {
|
|||||||
|
|
||||||
const resolvedPatch: ProformaPatchProps = {
|
const resolvedPatch: ProformaPatchProps = {
|
||||||
...patch,
|
...patch,
|
||||||
series: patch.targetInvoiceSeriesCode,
|
targetInvoiceSeriesCode: patch.targetInvoiceSeriesCode,
|
||||||
items: resolvedItems,
|
items: resolvedItems,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -42,10 +42,13 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
|
|||||||
|
|
||||||
proforma_reference: proforma.proformaReference.toString(),
|
proforma_reference: proforma.proformaReference.toString(),
|
||||||
status: proforma.status.toPrimitive() as ProformaFullSnapshot["status"],
|
status: proforma.status.toPrimitive() as ProformaFullSnapshot["status"],
|
||||||
target_invoice_series_code: maybeToNullable(proforma.series, (value) => value.toString()),
|
archived_at: maybeToNullable(proforma.archivedAt, (value) => value.toISOString()),
|
||||||
series: maybeToNullable(proforma.series, (value) => value.toString()),
|
target_invoice_series_code: maybeToNullable(proforma.targetInvoiceSeriesCode, (value) =>
|
||||||
|
value.toString()
|
||||||
|
),
|
||||||
|
series: maybeToNullable(proforma.targetInvoiceSeriesCode, (value) => value.toString()),
|
||||||
|
|
||||||
proforma_date: proforma.invoiceDate.toDateString(),
|
proforma_date: proforma.proformaDate.toDateString(),
|
||||||
operation_date: maybeToNullable(proforma.operationDate, (value) => value.toDateString()),
|
operation_date: maybeToNullable(proforma.operationDate, (value) => value.toDateString()),
|
||||||
|
|
||||||
reference: maybeToNullable(proforma.reference, (value) => value.toString()),
|
reference: maybeToNullable(proforma.reference, (value) => value.toString()),
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export class ProformaSummarySnapshotBuilder implements IProformaSummarySnapshotB
|
|||||||
|
|
||||||
proforma_reference: proforma.proformaReference.toString(),
|
proforma_reference: proforma.proformaReference.toString(),
|
||||||
status: proforma.status.toPrimitive() as ProformaSummaryDTO["status"],
|
status: proforma.status.toPrimitive() as ProformaSummaryDTO["status"],
|
||||||
|
archived_at: maybeToNullable(proforma.archivedAt, (value) => value.toISOString()),
|
||||||
target_invoice_series_code: maybeToNullable(proforma.series, (value) => value.toString()),
|
target_invoice_series_code: maybeToNullable(proforma.series, (value) => value.toString()),
|
||||||
series: maybeToNullable(proforma.series, (value) => value.toString()),
|
series: maybeToNullable(proforma.series, (value) => value.toString()),
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IProformaArchiver, IProformaFinder, IProformaFullReadModelAssembler } from "../services";
|
||||||
|
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
type ArchiveProformaByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
proforma_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ArchiveProformaByIdUseCase {
|
||||||
|
public constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
archiver: IProformaArchiver;
|
||||||
|
finder: IProformaFinder;
|
||||||
|
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
||||||
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: ArchiveProformaByIdUseCaseInput) {
|
||||||
|
const proformaIdResult = UniqueID.create(params.proforma_id);
|
||||||
|
|
||||||
|
if (proformaIdResult.isFailure) {
|
||||||
|
return Result.fail(proformaIdResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.deps.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
const proformaId = proformaIdResult.data;
|
||||||
|
const archiveResult = await this.deps.archiver.archive({
|
||||||
|
companyId: params.companyId,
|
||||||
|
id: proformaId,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (archiveResult.isFailure) {
|
||||||
|
return Result.fail(archiveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proformaResult = await this.deps.finder.findProformaByIdIncludingArchived(
|
||||||
|
params.companyId,
|
||||||
|
proformaId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (proformaResult.isFailure) {
|
||||||
|
return Result.fail(proformaResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const readModelResult = await this.deps.fullReadModelAssembler.assemble({
|
||||||
|
companyId: params.companyId,
|
||||||
|
proforma: proformaResult.data,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (readModelResult.isFailure) {
|
||||||
|
return Result.fail(readModelResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(readModelResult.data));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./archive-proforma-by-id.use-case";
|
||||||
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-by-id.use-case";
|
export * from "./delete-proforma-by-id.use-case";
|
||||||
@ -6,4 +7,5 @@ export * from "./issue-proforma.use-case";
|
|||||||
export * from "./list-proformas.use-case";
|
export * from "./list-proformas.use-case";
|
||||||
export * from "./preview-proforma-report.use-case";
|
export * from "./preview-proforma-report.use-case";
|
||||||
export * from "./report-proforma-pdf.use-case";
|
export * from "./report-proforma-pdf.use-case";
|
||||||
|
export * from "./unarchive-proforma-by-id.use-case";
|
||||||
export * from "./update-proforma-by-id.use-case";
|
export * from "./update-proforma-by-id.use-case";
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IProformaArchiver, IProformaFinder, IProformaFullReadModelAssembler } from "../services";
|
||||||
|
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
type UnarchiveProformaByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
proforma_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UnarchiveProformaByIdUseCase {
|
||||||
|
public constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
archiver: IProformaArchiver;
|
||||||
|
finder: IProformaFinder;
|
||||||
|
fullReadModelAssembler: IProformaFullReadModelAssembler;
|
||||||
|
fullSnapshotBuilder: IProformaFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: UnarchiveProformaByIdUseCaseInput) {
|
||||||
|
const proformaIdResult = UniqueID.create(params.proforma_id);
|
||||||
|
|
||||||
|
if (proformaIdResult.isFailure) {
|
||||||
|
return Result.fail(proformaIdResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.deps.transactionManager.complete(async (transaction) => {
|
||||||
|
try {
|
||||||
|
const proformaId = proformaIdResult.data;
|
||||||
|
const unarchiveResult = await this.deps.archiver.unarchive({
|
||||||
|
companyId: params.companyId,
|
||||||
|
id: proformaId,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (unarchiveResult.isFailure) {
|
||||||
|
return Result.fail(unarchiveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proformaResult = await this.deps.finder.findProformaById(
|
||||||
|
params.companyId,
|
||||||
|
proformaId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (proformaResult.isFailure) {
|
||||||
|
return Result.fail(proformaResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const readModelResult = await this.deps.fullReadModelAssembler.assemble({
|
||||||
|
companyId: params.companyId,
|
||||||
|
proforma: proformaResult.data,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (readModelResult.isFailure) {
|
||||||
|
return Result.fail(readModelResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(readModelResult.data));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,7 +26,17 @@ import {
|
|||||||
type ProformaItemPatchProps,
|
type ProformaItemPatchProps,
|
||||||
ProformaItems,
|
ProformaItems,
|
||||||
} from "../entities";
|
} from "../entities";
|
||||||
import { InvalidProformaTransitionError, ProformaItemMismatch } from "../errors";
|
import {
|
||||||
|
InvalidProformaTransitionError,
|
||||||
|
ProformaCannotBeArchivedError,
|
||||||
|
ProformaCannotBeDeletedError,
|
||||||
|
ProformaCannotBeUnarchivedError,
|
||||||
|
ProformaItemMismatch,
|
||||||
|
} from "../errors";
|
||||||
|
import {
|
||||||
|
canArchiveProformaStatus,
|
||||||
|
canUnarchiveProformaStatus,
|
||||||
|
} from "../policies/proforma-archive-policy";
|
||||||
import type { IProformaTaxTotals, ProformaCalculationContext } from "../services";
|
import type { IProformaTaxTotals, ProformaCalculationContext } from "../services";
|
||||||
import { canManuallyTransitionProformaStatus } from "../services";
|
import { canManuallyTransitionProformaStatus } from "../services";
|
||||||
import {
|
import {
|
||||||
@ -34,16 +44,16 @@ 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;
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
|
archivedAt: Maybe<UtcDate>;
|
||||||
|
|
||||||
documentSeriesId: Maybe<string>;
|
documentSeriesId: Maybe<string>;
|
||||||
proformaNumber: Maybe<InvoiceNumber>;
|
proformaNumber: Maybe<InvoiceNumber>;
|
||||||
proformaReference: InvoiceNumber;
|
proformaReference: InvoiceNumber;
|
||||||
series: Maybe<InvoiceSerie>;
|
targetInvoiceSeriesCode: Maybe<InvoiceSerie>;
|
||||||
|
|
||||||
proformaDate: UtcDate;
|
proformaDate: UtcDate;
|
||||||
operationDate: Maybe<UtcDate>;
|
operationDate: Maybe<UtcDate>;
|
||||||
@ -92,13 +102,14 @@ export interface IProformaTotals {
|
|||||||
export interface IProforma {
|
export interface IProforma {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
|
archivedAt: Maybe<UtcDate>;
|
||||||
|
|
||||||
documentSeriesId: Maybe<string>;
|
documentSeriesId: Maybe<string>;
|
||||||
proformaNumber: Maybe<InvoiceNumber>;
|
proformaNumber: Maybe<InvoiceNumber>;
|
||||||
series: Maybe<InvoiceSerie>;
|
targetInvoiceSeriesCode: Maybe<InvoiceSerie>;
|
||||||
proformaReference: InvoiceNumber;
|
proformaReference: InvoiceNumber;
|
||||||
|
|
||||||
invoiceDate: UtcDate;
|
proformaDate: UtcDate;
|
||||||
operationDate: Maybe<UtcDate>;
|
operationDate: Maybe<UtcDate>;
|
||||||
|
|
||||||
customerId: UniqueID;
|
customerId: UniqueID;
|
||||||
@ -243,8 +254,12 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return this.props.status;
|
return this.props.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get series(): Maybe<InvoiceSerie> {
|
public get targetInvoiceSeriesCode(): Maybe<InvoiceSerie> {
|
||||||
return this.props.series;
|
return this.props.targetInvoiceSeriesCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get archivedAt(): Maybe<UtcDate> {
|
||||||
|
return this.props.archivedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get documentSeriesId(): Maybe<string> {
|
public get documentSeriesId(): Maybe<string> {
|
||||||
@ -259,7 +274,7 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
return this.props.proformaReference;
|
return this.props.proformaReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get invoiceDate(): UtcDate {
|
public get proformaDate(): UtcDate {
|
||||||
return this.props.proformaDate;
|
return this.props.proformaDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,9 +416,52 @@ export class Proforma extends AggregateRoot<ProformaInternalProps> implements IP
|
|||||||
|
|
||||||
if (this.linkedInvoiceId.isSome()) {
|
if (this.linkedInvoiceId.isSome()) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new ProformaCannotBeDeletedError(
|
new ProformaCannotBeDeletedError(this.id.toString(), "Issued proformas cannot be deleted.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ensureCanBeArchived(): Result<void, Error> {
|
||||||
|
if (!canArchiveProformaStatus(this.status)) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeArchivedError(
|
||||||
this.id.toString(),
|
this.id.toString(),
|
||||||
"Issued proformas cannot be deleted."
|
this.status.isIssued()
|
||||||
|
? "Issued proformas cannot be archived."
|
||||||
|
: "Only draft or rejected proformas can be archived."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.linkedInvoiceId.isSome()) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeArchivedError(
|
||||||
|
this.id.toString(),
|
||||||
|
"Issued proformas cannot be archived."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ensureCanBeUnarchived(): Result<void, Error> {
|
||||||
|
if (!canUnarchiveProformaStatus(this.status)) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeUnarchivedError(
|
||||||
|
this.id.toString(),
|
||||||
|
"Only draft or rejected proformas can be unarchived."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.linkedInvoiceId.isSome()) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeUnarchivedError(
|
||||||
|
this.id.toString(),
|
||||||
|
"Issued proformas cannot be unarchived."
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
export * from "./proforma-cannot-be-archived-error";
|
||||||
export * from "./customer-invoice-id-already-exits-error";
|
export * from "./customer-invoice-id-already-exits-error";
|
||||||
export * from "./entity-is-not-proforma-error";
|
export * from "./entity-is-not-proforma-error";
|
||||||
export * from "./invalid-proforma-transition-error";
|
export * from "./invalid-proforma-transition-error";
|
||||||
|
export * from "./proforma-cannot-be-unarchived-error";
|
||||||
export * from "./proforma-cannot-be-converted-to-invoice-error";
|
export * from "./proforma-cannot-be-converted-to-invoice-error";
|
||||||
export * from "./proforma-cannot-be-deleted-error";
|
export * from "./proforma-cannot-be-deleted-error";
|
||||||
export * from "./proforma-item-not-valid-error";
|
export * from "./proforma-item-not-valid-error";
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { DomainError } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
export class ProformaCannotBeArchivedError extends DomainError {
|
||||||
|
public constructor(
|
||||||
|
public readonly proformaId: string,
|
||||||
|
message = "Proforma cannot be archived."
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = "ProformaCannotBeArchivedError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isProformaCannotBeArchivedError = (
|
||||||
|
e: unknown
|
||||||
|
): e is ProformaCannotBeArchivedError => e instanceof ProformaCannotBeArchivedError;
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { DomainError } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
export class ProformaCannotBeUnarchivedError extends DomainError {
|
||||||
|
public constructor(
|
||||||
|
public readonly proformaId: string,
|
||||||
|
message = "Proforma cannot be unarchived."
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = "ProformaCannotBeUnarchivedError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isProformaCannotBeUnarchivedError = (
|
||||||
|
e: unknown
|
||||||
|
): e is ProformaCannotBeUnarchivedError => e instanceof ProformaCannotBeUnarchivedError;
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { type InvoiceStatus, INVOICE_STATUS } from "../../common/value-objects";
|
||||||
|
|
||||||
|
export const ARCHIVABLE_PROFORMA_STATUSES = [
|
||||||
|
INVOICE_STATUS.DRAFT,
|
||||||
|
INVOICE_STATUS.REJECTED,
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function canArchiveProformaStatus(status: InvoiceStatus): boolean {
|
||||||
|
return ARCHIVABLE_PROFORMA_STATUSES.includes(
|
||||||
|
status.toPrimitive() as (typeof ARCHIVABLE_PROFORMA_STATUSES)[number]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canUnarchiveProformaStatus(status: InvoiceStatus): boolean {
|
||||||
|
return canArchiveProformaStatus(status);
|
||||||
|
}
|
||||||
@ -34,6 +34,7 @@ export class ProformaModel extends Model<
|
|||||||
declare id: string;
|
declare id: string;
|
||||||
declare company_id: string;
|
declare company_id: string;
|
||||||
declare status: string;
|
declare status: string;
|
||||||
|
declare archived_at: CreationOptional<string | null>;
|
||||||
|
|
||||||
declare document_series_id: CreationOptional<string | null>;
|
declare document_series_id: CreationOptional<string | null>;
|
||||||
declare proforma_number: CreationOptional<string | null>;
|
declare proforma_number: CreationOptional<string | null>;
|
||||||
@ -179,6 +180,11 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: "draft",
|
defaultValue: "draft",
|
||||||
},
|
},
|
||||||
|
archived_at: {
|
||||||
|
type: new DataTypes.DATEONLY(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
document_series_id: {
|
document_series_id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -458,7 +464,12 @@ export default (database: Sequelize) => {
|
|||||||
indexes: [
|
indexes: [
|
||||||
{
|
{
|
||||||
name: "idx_proformas_company_date",
|
name: "idx_proformas_company_date",
|
||||||
fields: ["company_id", "deleted_at", { name: "proforma_date", order: "DESC" }],
|
fields: [
|
||||||
|
"company_id",
|
||||||
|
"deleted_at",
|
||||||
|
"archived_at",
|
||||||
|
{ name: "proforma_date", order: "DESC" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "idx_proformas_company",
|
name: "idx_proformas_company",
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type { DocumentSeriesPublicServicesType } from "@erp/document-series/api"
|
|||||||
|
|
||||||
import type { ICompanyPublicServices } from "../../../../../../companies/src/api";
|
import type { ICompanyPublicServices } from "../../../../../../companies/src/api";
|
||||||
import {
|
import {
|
||||||
|
type ArchiveProformaByIdUseCase,
|
||||||
type ChangeStatusProformaUseCase,
|
type ChangeStatusProformaUseCase,
|
||||||
type CreateProformaUseCase,
|
type CreateProformaUseCase,
|
||||||
type DeleteProformaByIdUseCase,
|
type DeleteProformaByIdUseCase,
|
||||||
@ -13,7 +14,9 @@ import {
|
|||||||
type ListProformasUseCase,
|
type ListProformasUseCase,
|
||||||
type PreviewProformaReportUseCase,
|
type PreviewProformaReportUseCase,
|
||||||
type ReportProformaPdfUseCase,
|
type ReportProformaPdfUseCase,
|
||||||
|
type UnarchiveProformaByIdUseCase,
|
||||||
type UpdateProformaByIdUseCase,
|
type UpdateProformaByIdUseCase,
|
||||||
|
buildArchiveProformaByIdUseCase,
|
||||||
buildChangeStatusProformaUseCase,
|
buildChangeStatusProformaUseCase,
|
||||||
buildCreateProformaUseCase,
|
buildCreateProformaUseCase,
|
||||||
buildDeleteProformaByIdUseCase,
|
buildDeleteProformaByIdUseCase,
|
||||||
@ -21,6 +24,7 @@ import {
|
|||||||
buildIssueProformaUseCase,
|
buildIssueProformaUseCase,
|
||||||
buildListProformasUseCase,
|
buildListProformasUseCase,
|
||||||
buildPreviewProformaReportUseCase,
|
buildPreviewProformaReportUseCase,
|
||||||
|
buildProformaArchiver,
|
||||||
buildProformaCatalogResolvers,
|
buildProformaCatalogResolvers,
|
||||||
buildProformaCreator,
|
buildProformaCreator,
|
||||||
buildProformaDeleter,
|
buildProformaDeleter,
|
||||||
@ -34,6 +38,7 @@ import {
|
|||||||
buildProformaToIssuedInvoicePropsConverter,
|
buildProformaToIssuedInvoicePropsConverter,
|
||||||
buildProformaUpdater,
|
buildProformaUpdater,
|
||||||
buildReportProformaPdfUseCase,
|
buildReportProformaPdfUseCase,
|
||||||
|
buildUnarchiveProformaByIdUseCase,
|
||||||
buildUpdateProformaUseCase,
|
buildUpdateProformaUseCase,
|
||||||
} from "../../../application";
|
} from "../../../application";
|
||||||
import { CompanyReportProfileFinder } from "../adapters/company-report-profile-finder";
|
import { CompanyReportProfileFinder } from "../adapters/company-report-profile-finder";
|
||||||
@ -57,6 +62,8 @@ export type ProformasInternalDeps = {
|
|||||||
updateProforma: () => UpdateProformaByIdUseCase;
|
updateProforma: () => UpdateProformaByIdUseCase;
|
||||||
changeStatusProforma: () => ChangeStatusProformaUseCase;
|
changeStatusProforma: () => ChangeStatusProformaUseCase;
|
||||||
deleteProforma: () => DeleteProformaByIdUseCase;
|
deleteProforma: () => DeleteProformaByIdUseCase;
|
||||||
|
archiveProforma: () => ArchiveProformaByIdUseCase;
|
||||||
|
unarchiveProforma: () => UnarchiveProformaByIdUseCase;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,6 +129,9 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
const deleter = buildProformaDeleter({
|
const deleter = buildProformaDeleter({
|
||||||
repository,
|
repository,
|
||||||
});
|
});
|
||||||
|
const archiver = buildProformaArchiver({
|
||||||
|
repository,
|
||||||
|
});
|
||||||
|
|
||||||
const statusChanger = buildProformaStatusChanger({
|
const statusChanger = buildProformaStatusChanger({
|
||||||
repository,
|
repository,
|
||||||
@ -185,6 +195,24 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
archiveProforma: () =>
|
||||||
|
buildArchiveProformaByIdUseCase({
|
||||||
|
archiver,
|
||||||
|
finder,
|
||||||
|
fullReadModelAssembler: readModelAssemblers.full,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
|
unarchiveProforma: () =>
|
||||||
|
buildUnarchiveProformaByIdUseCase({
|
||||||
|
archiver,
|
||||||
|
finder,
|
||||||
|
fullReadModelAssembler: readModelAssemblers.full,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
reportProformaPdf: () =>
|
reportProformaPdf: () =>
|
||||||
buildReportProformaPdfUseCase({
|
buildReportProformaPdfUseCase({
|
||||||
finder,
|
finder,
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { ArchiveProformaByIdUseCase } from "../../../../application";
|
||||||
|
import { proformasApiErrorMapper } from "../proformas-api-error-mapper";
|
||||||
|
|
||||||
|
export class ArchiveProformaByIdController extends ExpressController {
|
||||||
|
public constructor(private readonly useCase: ArchiveProformaByIdUseCase) {
|
||||||
|
super();
|
||||||
|
this.errorMapper = proformasApiErrorMapper;
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { proforma_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ companyId, proforma_id });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data) => this.ok(data),
|
||||||
|
(err) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./archive-proforma-by-id.controller";
|
||||||
//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";
|
||||||
@ -6,4 +7,5 @@ export * from "./issue-proforma.controller";
|
|||||||
export * from "./list-proformas.controller";
|
export * from "./list-proformas.controller";
|
||||||
export * from "./preview-proforma-report.controller";
|
export * from "./preview-proforma-report.controller";
|
||||||
export * from "./report-proforma-pdf.controller";
|
export * from "./report-proforma-pdf.controller";
|
||||||
|
export * from "./unarchive-proforma-by-id.controller";
|
||||||
export * from "./update-proforma.controller";
|
export * from "./update-proforma.controller";
|
||||||
|
|||||||
@ -28,8 +28,13 @@ export class ListProformasController extends ExpressController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { q: quicksearch, filters, pageSize, pageNumber } = this.criteria.toPrimitives();
|
const { q: quicksearch, filters, pageSize, pageNumber } = this.criteria.toPrimitives();
|
||||||
|
const archivedParam = this.req.query.archived;
|
||||||
|
const archivedFilter =
|
||||||
|
archivedParam === "true"
|
||||||
|
? [{ field: "archived_at", operator: "NOT_NULL", value: "true" }]
|
||||||
|
: [{ field: "archived_at", operator: "NULL", value: "true" }];
|
||||||
return Criteria.fromPrimitives(
|
return Criteria.fromPrimitives(
|
||||||
filters,
|
[...filters, ...archivedFilter],
|
||||||
"invoice_date",
|
"invoice_date",
|
||||||
"DESC",
|
"DESC",
|
||||||
pageSize,
|
pageSize,
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { UnarchiveProformaByIdUseCase } from "../../../../application";
|
||||||
|
import { proformasApiErrorMapper } from "../proformas-api-error-mapper";
|
||||||
|
|
||||||
|
export class UnarchiveProformaByIdController extends ExpressController {
|
||||||
|
public constructor(private readonly useCase: UnarchiveProformaByIdUseCase) {
|
||||||
|
super();
|
||||||
|
this.errorMapper = proformasApiErrorMapper;
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { proforma_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ companyId, proforma_id });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data) => this.ok(data),
|
||||||
|
(err) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,6 +32,8 @@ import {
|
|||||||
isInvalidInvoiceSeriesNextNumberError,
|
isInvalidInvoiceSeriesNextNumberError,
|
||||||
isInvalidInvoiceSeriesPaddingLengthError,
|
isInvalidInvoiceSeriesPaddingLengthError,
|
||||||
isInvalidProformaTransitionError,
|
isInvalidProformaTransitionError,
|
||||||
|
isProformaCannotBeArchivedError,
|
||||||
|
isProformaCannotBeUnarchivedError,
|
||||||
isProformaCannotBeConvertedToInvoiceError,
|
isProformaCannotBeConvertedToInvoiceError,
|
||||||
isProformaCannotBeDeletedError,
|
isProformaCannotBeDeletedError,
|
||||||
isProformaItemMismatch,
|
isProformaItemMismatch,
|
||||||
@ -95,6 +97,18 @@ const proformaCannotBeDeletedRule: ErrorToApiRule = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const proformaCannotBeArchivedRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: (e) => isProformaCannotBeArchivedError(e),
|
||||||
|
build: (e) => new ConflictApiError((e as Error).message || "Proforma cannot be archived."),
|
||||||
|
};
|
||||||
|
|
||||||
|
const proformaCannotBeUnarchivedRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: (e) => isProformaCannotBeUnarchivedError(e),
|
||||||
|
build: (e) => new ConflictApiError((e as Error).message || "Proforma cannot be unarchived."),
|
||||||
|
};
|
||||||
|
|
||||||
const companyReportProfileNotFoundRule: ErrorToApiRule = {
|
const companyReportProfileNotFoundRule: ErrorToApiRule = {
|
||||||
priority: 130,
|
priority: 130,
|
||||||
matches: (e) => isCompanyReportProfileNotFoundError(e),
|
matches: (e) => isCompanyReportProfileNotFoundError(e),
|
||||||
@ -210,5 +224,7 @@ export const proformasApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
|||||||
.register(proformaItemMismatchError)
|
.register(proformaItemMismatchError)
|
||||||
.register(entityIsNotProformaError)
|
.register(entityIsNotProformaError)
|
||||||
.register(proformaConversionRule)
|
.register(proformaConversionRule)
|
||||||
|
.register(proformaCannotBeUnarchivedRule)
|
||||||
|
.register(proformaCannotBeArchivedRule)
|
||||||
.register(proformaCannotBeDeletedRule)
|
.register(proformaCannotBeDeletedRule)
|
||||||
.register(proformaTransitionRule);
|
.register(proformaTransitionRule);
|
||||||
|
|||||||
@ -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 {
|
||||||
|
ArchiveProformaByIdController,
|
||||||
DeleteProformaController,
|
DeleteProformaController,
|
||||||
GetProformaByIdController,
|
GetProformaByIdController,
|
||||||
IssueProformaController,
|
IssueProformaController,
|
||||||
@ -10,9 +11,11 @@ import {
|
|||||||
PreviewProformaReportController,
|
PreviewProformaReportController,
|
||||||
type ProformasInternalDeps,
|
type ProformasInternalDeps,
|
||||||
ReportProformaPdfController,
|
ReportProformaPdfController,
|
||||||
|
UnarchiveProformaByIdController,
|
||||||
} from "..";
|
} from "..";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ArchiveProformaByIdParamsRequestSchema,
|
||||||
ChangeStatusProformaByIdParamsRequestSchema,
|
ChangeStatusProformaByIdParamsRequestSchema,
|
||||||
ChangeStatusProformaByIdRequestSchema,
|
ChangeStatusProformaByIdRequestSchema,
|
||||||
CreateProformaRequestSchema,
|
CreateProformaRequestSchema,
|
||||||
@ -23,6 +26,7 @@ import {
|
|||||||
ListProformasRequestSchema,
|
ListProformasRequestSchema,
|
||||||
PreviewProformaReportByIdParamsRequestSchema,
|
PreviewProformaReportByIdParamsRequestSchema,
|
||||||
ReportProformaPdfByIdParamsRequestSchema,
|
ReportProformaPdfByIdParamsRequestSchema,
|
||||||
|
UnarchiveProformaByIdParamsRequestSchema,
|
||||||
UpdateProformaByIdParamsRequestSchema,
|
UpdateProformaByIdParamsRequestSchema,
|
||||||
UpdateProformaByIdRequestSchema,
|
UpdateProformaByIdRequestSchema,
|
||||||
} from "../../../../common";
|
} from "../../../../common";
|
||||||
@ -129,6 +133,26 @@ export const proformasRouter = (params: StartParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:proforma_id/archive",
|
||||||
|
validateRequest(ArchiveProformaByIdParamsRequestSchema, "params"),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const useCase = deps.useCases.archiveProforma();
|
||||||
|
const controller = new ArchiveProformaByIdController(useCase);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:proforma_id/unarchive",
|
||||||
|
validateRequest(UnarchiveProformaByIdParamsRequestSchema, "params"),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const useCase = deps.useCases.unarchiveProforma();
|
||||||
|
const controller = new UnarchiveProformaByIdController(useCase);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:proforma_id/status",
|
"/:proforma_id/status",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|||||||
@ -18,9 +18,9 @@ import {
|
|||||||
InvoiceSerie,
|
InvoiceSerie,
|
||||||
InvoiceStatus,
|
InvoiceStatus,
|
||||||
Proforma,
|
Proforma,
|
||||||
ProformaTaxConfig,
|
|
||||||
type ProformaInternalProps,
|
type ProformaInternalProps,
|
||||||
ProformaItems,
|
ProformaItems,
|
||||||
|
ProformaTaxConfig,
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type {
|
import type {
|
||||||
CustomerInvoiceCreationAttributes,
|
CustomerInvoiceCreationAttributes,
|
||||||
@ -267,7 +267,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
status: attributes.status!,
|
status: attributes.status!,
|
||||||
documentSeriesId: Maybe.none(),
|
documentSeriesId: Maybe.none(),
|
||||||
proformaNumber: Maybe.some(attributes.invoiceNumber!),
|
proformaNumber: Maybe.some(attributes.invoiceNumber!),
|
||||||
series: attributes.series!,
|
targetInvoiceSeriesCode: attributes.series!,
|
||||||
proformaReference: attributes.invoiceNumber!,
|
proformaReference: attributes.invoiceNumber!,
|
||||||
proformaDate: attributes.invoiceDate!,
|
proformaDate: attributes.invoiceDate!,
|
||||||
operationDate: attributes.operationDate!,
|
operationDate: attributes.operationDate!,
|
||||||
@ -363,9 +363,9 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
status: source.status.toPrimitive(),
|
status: source.status.toPrimitive(),
|
||||||
proforma_id: null,
|
proforma_id: null,
|
||||||
|
|
||||||
series: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
series: maybeToNullable(source.targetInvoiceSeriesCode, (v) => v.toPrimitive()),
|
||||||
invoice_number: source.proformaReference.toPrimitive(),
|
invoice_number: source.proformaReference.toPrimitive(),
|
||||||
invoice_date: source.invoiceDate.toPrimitive(),
|
invoice_date: source.proformaDate.toPrimitive(),
|
||||||
operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()),
|
operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()),
|
||||||
language_code: source.languageCode.toPrimitive(),
|
language_code: source.languageCode.toPrimitive(),
|
||||||
currency_code: source.currencyCode.toPrimitive(),
|
currency_code: source.currencyCode.toPrimitive(),
|
||||||
@ -384,7 +384,10 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
uses_equivalence_surcharge: source.taxConfig.usesEquivalenceSurcharge,
|
uses_equivalence_surcharge: source.taxConfig.usesEquivalenceSurcharge,
|
||||||
default_rec_code: maybeToNullable(source.taxConfig.defaultRecCode, (value) => value),
|
default_rec_code: maybeToNullable(source.taxConfig.defaultRecCode, (value) => value),
|
||||||
uses_retention: source.taxConfig.usesRetention,
|
uses_retention: source.taxConfig.usesRetention,
|
||||||
default_retention_code: maybeToNullable(source.taxConfig.defaultRetentionCode, (value) => value),
|
default_retention_code: maybeToNullable(
|
||||||
|
source.taxConfig.defaultRetentionCode,
|
||||||
|
(value) => value
|
||||||
|
),
|
||||||
|
|
||||||
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
subtotal_amount_value: allAmounts.subtotalAmount.value,
|
||||||
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
|
||||||
|
|||||||
@ -18,9 +18,9 @@ import {
|
|||||||
InvoiceSerie,
|
InvoiceSerie,
|
||||||
InvoiceStatus,
|
InvoiceStatus,
|
||||||
Proforma,
|
Proforma,
|
||||||
ProformaTaxConfig,
|
|
||||||
type ProformaInternalProps,
|
type ProformaInternalProps,
|
||||||
ProformaItems,
|
ProformaItems,
|
||||||
|
ProformaTaxConfig,
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type { ProformaCreationAttributes, ProformaModel } from "../../../../../common";
|
import type { ProformaCreationAttributes, ProformaModel } from "../../../../../common";
|
||||||
|
|
||||||
@ -47,16 +47,21 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
private _mapAttributesToDomain(raw: ProformaModel, params?: MapperParamsType) {
|
private _mapAttributesToDomain(raw: ProformaModel, params?: MapperParamsType) {
|
||||||
const { errors } = params as { errors: ValidationErrorDetail[] };
|
const { errors } = params as { errors: ValidationErrorDetail[] };
|
||||||
|
|
||||||
const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
|
const proformaId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
|
||||||
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
||||||
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
||||||
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
||||||
|
const archivedAt = extractOrPushError(
|
||||||
|
maybeFromNullableResult(raw.archived_at, (value) => UtcDate.createFromISO(value)),
|
||||||
|
"archived_at",
|
||||||
|
errors
|
||||||
|
);
|
||||||
const documentSeriesId = extractOrPushError(
|
const documentSeriesId = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.document_series_id, (value) => Result.ok(String(value))),
|
maybeFromNullableResult(raw.document_series_id, (value) => Result.ok(String(value))),
|
||||||
"document_series_id",
|
"document_series_id",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const series = extractOrPushError(
|
const targetInvoiceSeriesCode = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.target_invoice_series_code, (value) =>
|
maybeFromNullableResult(raw.target_invoice_series_code, (value) =>
|
||||||
InvoiceSerie.create(value)
|
InvoiceSerie.create(value)
|
||||||
),
|
),
|
||||||
@ -68,12 +73,12 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
"proforma_number",
|
"proforma_number",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const invoiceNumber = extractOrPushError(
|
const proformaReference = extractOrPushError(
|
||||||
InvoiceNumber.create(raw.proforma_reference),
|
InvoiceNumber.create(raw.proforma_reference),
|
||||||
"proforma_reference",
|
"proforma_reference",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
const invoiceDate = extractOrPushError(
|
const proformaDate = extractOrPushError(
|
||||||
UtcDate.createFromISO(raw.proforma_date),
|
UtcDate.createFromISO(raw.proforma_date),
|
||||||
"proforma_date",
|
"proforma_date",
|
||||||
errors
|
errors
|
||||||
@ -156,15 +161,16 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
invoiceId,
|
proformaId,
|
||||||
companyId,
|
companyId,
|
||||||
customerId,
|
customerId,
|
||||||
status,
|
status,
|
||||||
|
archivedAt,
|
||||||
documentSeriesId,
|
documentSeriesId,
|
||||||
proformaNumber,
|
proformaNumber,
|
||||||
series,
|
targetInvoiceSeriesCode,
|
||||||
invoiceNumber,
|
proformaReference,
|
||||||
invoiceDate,
|
proformaDate,
|
||||||
operationDate,
|
operationDate,
|
||||||
reference,
|
reference,
|
||||||
description,
|
description,
|
||||||
@ -183,6 +189,7 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
try {
|
try {
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
const attributes = this._mapAttributesToDomain(raw, { errors, ...params });
|
const attributes = this._mapAttributesToDomain(raw, { errors, ...params });
|
||||||
|
|
||||||
const recipientResult = this._recipientMapper.mapToDomain(raw, {
|
const recipientResult = this._recipientMapper.mapToDomain(raw, {
|
||||||
errors,
|
errors,
|
||||||
parent: attributes,
|
parent: attributes,
|
||||||
@ -211,11 +218,12 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
const invoiceProps: ProformaInternalProps = {
|
const invoiceProps: ProformaInternalProps = {
|
||||||
companyId: attributes.companyId!,
|
companyId: attributes.companyId!,
|
||||||
status: attributes.status!,
|
status: attributes.status!,
|
||||||
|
archivedAt: attributes.archivedAt!,
|
||||||
documentSeriesId: attributes.documentSeriesId!,
|
documentSeriesId: attributes.documentSeriesId!,
|
||||||
proformaNumber: attributes.proformaNumber!,
|
proformaNumber: attributes.proformaNumber!,
|
||||||
series: attributes.series!,
|
targetInvoiceSeriesCode: attributes.targetInvoiceSeriesCode!,
|
||||||
proformaReference: attributes.invoiceNumber!,
|
proformaReference: attributes.proformaReference!,
|
||||||
proformaDate: attributes.invoiceDate!,
|
proformaDate: attributes.proformaDate!,
|
||||||
operationDate: attributes.operationDate!,
|
operationDate: attributes.operationDate!,
|
||||||
customerId: attributes.customerId!,
|
customerId: attributes.customerId!,
|
||||||
recipient: recipientResult.data,
|
recipient: recipientResult.data,
|
||||||
@ -231,7 +239,7 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
linkedInvoiceId: attributes.linkedInvoiceId!,
|
linkedInvoiceId: attributes.linkedInvoiceId!,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Result.ok(Proforma.rehydrate(invoiceProps, items, attributes.invoiceId!));
|
return Result.ok(Proforma.rehydrate(invoiceProps, items, attributes.proformaId!));
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return Result.fail(err as Error);
|
return Result.fail(err as Error);
|
||||||
}
|
}
|
||||||
@ -276,12 +284,15 @@ export class SequelizeProformaV2DomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
// Flags / estado / serie / número
|
// Flags / estado / serie / número
|
||||||
status: source.status.toPrimitive(),
|
status: source.status.toPrimitive(),
|
||||||
|
archived_at: maybeToNullable(source.archivedAt, (value) => value.toPrimitive()),
|
||||||
document_series_id: maybeToNullable(source.documentSeriesId, (value) => value),
|
document_series_id: maybeToNullable(source.documentSeriesId, (value) => value),
|
||||||
proforma_number: maybeToNullable(source.proformaNumber, (value) => value.toPrimitive()),
|
proforma_number: maybeToNullable(source.proformaNumber, (value) => value.toPrimitive()),
|
||||||
proforma_reference: source.proformaReference.toPrimitive(),
|
proforma_reference: source.proformaReference.toPrimitive(),
|
||||||
proforma_date: source.invoiceDate.toPrimitive(),
|
proforma_date: source.proformaDate.toPrimitive(),
|
||||||
operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()),
|
operation_date: maybeToNullable(source.operationDate, (v) => v.toPrimitive()),
|
||||||
target_invoice_series_code: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
target_invoice_series_code: maybeToNullable(source.targetInvoiceSeriesCode, (v) =>
|
||||||
|
v.toPrimitive()
|
||||||
|
),
|
||||||
language_code: source.languageCode.toPrimitive(),
|
language_code: source.languageCode.toPrimitive(),
|
||||||
currency_code: source.currencyCode.toPrimitive(),
|
currency_code: source.currencyCode.toPrimitive(),
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,7 @@ export class SequelizeProformaV2SummaryMapper extends SequelizeQueryMapper<
|
|||||||
companyId: attributes.companyId!,
|
companyId: attributes.companyId!,
|
||||||
isProforma: true,
|
isProforma: true,
|
||||||
status: attributes.status!,
|
status: attributes.status!,
|
||||||
|
archivedAt: attributes.archivedAt!,
|
||||||
series: attributes.series!,
|
series: attributes.series!,
|
||||||
proformaReference: attributes.proformaReference!,
|
proformaReference: attributes.proformaReference!,
|
||||||
proformaDate: attributes.proformaDate!,
|
proformaDate: attributes.proformaDate!,
|
||||||
@ -87,6 +88,11 @@ export class SequelizeProformaV2SummaryMapper extends SequelizeQueryMapper<
|
|||||||
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
||||||
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
||||||
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
||||||
|
const archivedAt = extractOrPushError(
|
||||||
|
maybeFromNullableResult(raw.archived_at, (value) => UtcDate.create(value.toISOString())),
|
||||||
|
"archived_at",
|
||||||
|
errors
|
||||||
|
);
|
||||||
const series = extractOrPushError(
|
const series = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.target_invoice_series_code, (value) =>
|
maybeFromNullableResult(raw.target_invoice_series_code, (value) =>
|
||||||
InvoiceSerie.create(value)
|
InvoiceSerie.create(value)
|
||||||
@ -168,6 +174,7 @@ export class SequelizeProformaV2SummaryMapper extends SequelizeQueryMapper<
|
|||||||
companyId,
|
companyId,
|
||||||
customerId,
|
customerId,
|
||||||
status,
|
status,
|
||||||
|
archivedAt,
|
||||||
series,
|
series,
|
||||||
proformaReference,
|
proformaReference,
|
||||||
proformaDate,
|
proformaDate,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
normalizeOrder,
|
normalizeOrder,
|
||||||
} from "@repo/rdx-criteria/server";
|
} from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { type Collection, Result } from "@repo/rdx-utils";
|
import { type Collection, DateHelper, Result } from "@repo/rdx-utils";
|
||||||
import {
|
import {
|
||||||
type CountOptions,
|
type CountOptions,
|
||||||
type FindOptions,
|
type FindOptions,
|
||||||
@ -140,6 +140,56 @@ export class SequelizeProformaRepositoryV2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async archiveByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const [affected] = await ProformaModel.update(
|
||||||
|
{ archived_at: DateHelper.buildTodayIsoDate() },
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
id: id.toString(),
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
archived_at: null,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Result.ok(affected > 0);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async unarchiveByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
try {
|
||||||
|
const [affected] = await ProformaModel.update(
|
||||||
|
{ archived_at: null },
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
id: id.toString(),
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
archived_at: {
|
||||||
|
[Op.ne]: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Result.ok(affected > 0);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return Result.fail(translateSequelizeError(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async existsIssuedInvoiceBySourceProformaIdInCompany(
|
async existsIssuedInvoiceBySourceProformaIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
@ -222,6 +272,21 @@ export class SequelizeProformaRepositoryV2
|
|||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
options: FindOptions<InferAttributes<ProformaModel>> = {}
|
options: FindOptions<InferAttributes<ProformaModel>> = {}
|
||||||
|
): Promise<Result<Proforma, Error>> {
|
||||||
|
return this.getByIdIncludingArchivedInCompany(companyId, id, transaction, {
|
||||||
|
...options,
|
||||||
|
where: {
|
||||||
|
archived_at: null,
|
||||||
|
...(options.where ?? {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByIdIncludingArchivedInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
id: UniqueID,
|
||||||
|
transaction: Transaction,
|
||||||
|
options: FindOptions<InferAttributes<ProformaModel>> = {}
|
||||||
): Promise<Result<Proforma, Error>> {
|
): Promise<Result<Proforma, Error>> {
|
||||||
const { CustomerModel } = this.database.models;
|
const { CustomerModel } = this.database.models;
|
||||||
|
|
||||||
@ -330,6 +395,7 @@ export class SequelizeProformaRepositoryV2
|
|||||||
|
|
||||||
const baseWhere: WhereOptions<InferAttributes<ProformaModel>> = {
|
const baseWhere: WhereOptions<InferAttributes<ProformaModel>> = {
|
||||||
company_id: companyId.toString(),
|
company_id: companyId.toString(),
|
||||||
|
archived_at: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const ArchiveProformaByIdParamsRequestSchema = z.object({
|
||||||
|
proforma_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ArchiveProformaByIdRequestDTO = z.infer<typeof ArchiveProformaByIdParamsRequestSchema>;
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./archive-proforma-by-id.request.dto";
|
||||||
export * from "./change-status-proforma-by-id.request.dto";
|
export * from "./change-status-proforma-by-id.request.dto";
|
||||||
export * from "./create-proforma.request.dto";
|
export * from "./create-proforma.request.dto";
|
||||||
export * from "./delete-proforma-by-id.request.dto";
|
export * from "./delete-proforma-by-id.request.dto";
|
||||||
@ -6,4 +7,5 @@ export * from "./issue-proforma-by-id.request.dto";
|
|||||||
export * from "./list-proformas.request.dto";
|
export * from "./list-proformas.request.dto";
|
||||||
export * from "./preview-proforma-report-by-id.request.dto";
|
export * from "./preview-proforma-report-by-id.request.dto";
|
||||||
export * from "./report-proforma-pdf-by-id.request.dto";
|
export * from "./report-proforma-pdf-by-id.request.dto";
|
||||||
|
export * from "./unarchive-proforma-by-id.request.dto";
|
||||||
export * from "./update-proforma-by-id.request.dto";
|
export * from "./update-proforma-by-id.request.dto";
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { CriteriaSchema } from "@erp/core";
|
import { CriteriaSchema } from "@erp/core";
|
||||||
import type { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
export const ListProformasRequestSchema = CriteriaSchema;
|
export const ListProformasRequestSchema = CriteriaSchema.extend({
|
||||||
|
archived: z.union([z.literal("true"), z.literal("false")]).optional(),
|
||||||
|
});
|
||||||
export type ProformasListRequestDTO = z.infer<typeof ListProformasRequestSchema>;
|
export type ProformasListRequestDTO = z.infer<typeof ListProformasRequestSchema>;
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const UnarchiveProformaByIdParamsRequestSchema = z.object({
|
||||||
|
proforma_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UnarchiveProformaByIdRequestDTO = z.infer<
|
||||||
|
typeof UnarchiveProformaByIdParamsRequestSchema
|
||||||
|
>;
|
||||||
@ -27,6 +27,7 @@ export const GetProformaByIdResponseSchema = z.object({
|
|||||||
|
|
||||||
proforma_reference: z.string(),
|
proforma_reference: z.string(),
|
||||||
status: ProformaStatusSchema,
|
status: ProformaStatusSchema,
|
||||||
|
archived_at: z.iso.datetime().nullable(),
|
||||||
target_invoice_series_code: z.string().nullable(),
|
target_invoice_series_code: z.string().nullable(),
|
||||||
series: z.string().nullable(),
|
series: z.string().nullable(),
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const ProformaSummarySchema = z.object({
|
|||||||
|
|
||||||
proforma_reference: z.string(),
|
proforma_reference: z.string(),
|
||||||
status: ProformaStatusSchema,
|
status: ProformaStatusSchema,
|
||||||
|
archived_at: z.iso.datetime().nullable(),
|
||||||
target_invoice_series_code: z.string().nullable(),
|
target_invoice_series_code: z.string().nullable(),
|
||||||
series: z.string().nullable(),
|
series: z.string().nullable(),
|
||||||
|
|
||||||
|
|||||||
@ -96,6 +96,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"proformas": {
|
"proformas": {
|
||||||
|
"archive": {
|
||||||
|
"confirm_title": "Archive proforma",
|
||||||
|
"confirm_description": "The proforma will be hidden from the main list but kept for historical lookup.",
|
||||||
|
"success_title": "Proforma archived",
|
||||||
|
"success_description": "The proforma was archived successfully.",
|
||||||
|
"error_title": "The proforma could not be archived",
|
||||||
|
"error_conflict_message": "This proforma cannot be archived in its current status.",
|
||||||
|
"error_unknown_message": "An unexpected error occurred while archiving the proforma."
|
||||||
|
},
|
||||||
|
"unarchive": {
|
||||||
|
"confirm_title": "Unarchive proforma",
|
||||||
|
"confirm_description": "The proforma will appear again in the main list.",
|
||||||
|
"success_title": "Proforma unarchived",
|
||||||
|
"success_description": "The proforma was unarchived successfully.",
|
||||||
|
"error_title": "The proforma could not be unarchived",
|
||||||
|
"error_conflict_message": "This proforma cannot be unarchived in its current status.",
|
||||||
|
"error_unknown_message": "An unexpected error occurred while unarchiving the proforma."
|
||||||
|
},
|
||||||
"issue_proforma_dialog": {
|
"issue_proforma_dialog": {
|
||||||
"title": "Issue to invoice",
|
"title": "Issue to invoice",
|
||||||
"description": "Are you sure you want to issue an invoice from proforma {{reference}}?",
|
"description": "Are you sure you want to issue an invoice from proforma {{reference}}?",
|
||||||
|
|||||||
@ -96,6 +96,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"proformas": {
|
"proformas": {
|
||||||
|
"archive": {
|
||||||
|
"confirm_title": "Archivar proforma",
|
||||||
|
"confirm_description": "La proforma se ocultará del listado principal, pero seguirá conservada para consulta histórica.",
|
||||||
|
"success_title": "Proforma archivada",
|
||||||
|
"success_description": "La proforma se ha archivado correctamente.",
|
||||||
|
"error_title": "No se pudo archivar la proforma",
|
||||||
|
"error_conflict_message": "No se puede archivar una proforma en este estado.",
|
||||||
|
"error_unknown_message": "Ha ocurrido un error inesperado al archivar la proforma."
|
||||||
|
},
|
||||||
|
"unarchive": {
|
||||||
|
"confirm_title": "Desarchivar proforma",
|
||||||
|
"confirm_description": "La proforma volverá a aparecer en el listado principal.",
|
||||||
|
"success_title": "Proforma desarchivada",
|
||||||
|
"success_description": "La proforma se ha desarchivado correctamente.",
|
||||||
|
"error_title": "No se pudo desarchivar la proforma",
|
||||||
|
"error_conflict_message": "No se puede desarchivar una proforma en este estado.",
|
||||||
|
"error_unknown_message": "Ha ocurrido un error inesperado al desarchivar la proforma."
|
||||||
|
},
|
||||||
"issue_proforma_dialog": {
|
"issue_proforma_dialog": {
|
||||||
"title": "Emitir a factura",
|
"title": "Emitir a factura",
|
||||||
"description": "¿Seguro que quieres emitir la factura desde la proforma {{reference}}?",
|
"description": "¿Seguro que quieres emitir la factura desde la proforma {{reference}}?",
|
||||||
|
|||||||
@ -1,21 +1,30 @@
|
|||||||
|
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
|
||||||
import type { RightPanelMode } from "@repo/rdx-ui/hooks";
|
import type { RightPanelMode } from "@repo/rdx-ui/hooks";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../i18n";
|
||||||
import { useChangeProformaStatusDialogController } from "../../change-status";
|
import { useChangeProformaStatusDialogController } from "../../change-status";
|
||||||
import { useDeleteProformaDialogController } from "../../delete";
|
import { useDeleteProformaDialogController } from "../../delete";
|
||||||
import { useDownloadProformaPDFController } from "../../download-pdf/controller";
|
import { useDownloadProformaPDFController } from "../../download-pdf/controller";
|
||||||
import { useIssueProformaDialogController } from "../../issue-proforma";
|
import { useIssueProformaDialogController } from "../../issue-proforma";
|
||||||
import type { ProformaListRow } from "../../shared";
|
import {
|
||||||
|
type ProformaListRow,
|
||||||
|
useProformaArchiveMutation,
|
||||||
|
useProformaUnarchiveMutation,
|
||||||
|
} from "../../shared";
|
||||||
|
|
||||||
import { useListProformasController } from "./use-list-proformas.controller";
|
import { useListProformasController } from "./use-list-proformas.controller";
|
||||||
import { useProformaSummaryPanelController } from "./use-proforma-summary-panel.controller";
|
import { useProformaSummaryPanelController } from "./use-proforma-summary-panel.controller";
|
||||||
|
|
||||||
export const useListProformasPageController = () => {
|
export const useListProformasPageController = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const listCtrl = useListProformasController();
|
const listCtrl = useListProformasController();
|
||||||
const deleteDialogCtrl = useDeleteProformaDialogController();
|
const deleteDialogCtrl = useDeleteProformaDialogController();
|
||||||
const issueDialogCtrl = useIssueProformaDialogController();
|
const issueDialogCtrl = useIssueProformaDialogController();
|
||||||
const changeStatusDialogCtrl = useChangeProformaStatusDialogController();
|
const changeStatusDialogCtrl = useChangeProformaStatusDialogController();
|
||||||
|
const archiveMutation = useProformaArchiveMutation();
|
||||||
|
const unarchiveMutation = useProformaUnarchiveMutation();
|
||||||
|
|
||||||
const downloadPDFCtrl = useDownloadProformaPDFController();
|
const downloadPDFCtrl = useDownloadProformaPDFController();
|
||||||
|
|
||||||
@ -37,6 +46,61 @@ export const useListProformasPageController = () => {
|
|||||||
initialOpen: proformaId !== "",
|
initialOpen: proformaId !== "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleArchive = useCallback(
|
||||||
|
async (proforma: ProformaListRow) => {
|
||||||
|
const confirmed = window.confirm(
|
||||||
|
`${t("proformas.archive.confirm_title")}\n\n${t("proformas.archive.confirm_description")}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await archiveMutation.mutateAsync({ proformaId: proforma.id });
|
||||||
|
showSuccessToast(
|
||||||
|
t("proformas.archive.success_title"),
|
||||||
|
t("proformas.archive.success_description")
|
||||||
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(error);
|
||||||
|
const status = (error as { status?: number } | undefined)?.status;
|
||||||
|
showErrorToast(
|
||||||
|
t("proformas.archive.error_title"),
|
||||||
|
status === 409
|
||||||
|
? t("proformas.archive.error_conflict_message")
|
||||||
|
: t("proformas.archive.error_unknown_message")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[archiveMutation, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleUnarchive = useCallback(
|
||||||
|
async (proforma: ProformaListRow) => {
|
||||||
|
const confirmed = window.confirm(
|
||||||
|
`${t("proformas.unarchive.confirm_title")}\n\n${t("proformas.unarchive.confirm_description")}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await unarchiveMutation.mutateAsync({ proformaId: proforma.id });
|
||||||
|
showSuccessToast(
|
||||||
|
t("proformas.unarchive.success_title"),
|
||||||
|
t("proformas.unarchive.success_description")
|
||||||
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const status = (error as { status?: number } | undefined)?.status;
|
||||||
|
showErrorToast(
|
||||||
|
t("proformas.unarchive.error_title"),
|
||||||
|
status === 409
|
||||||
|
? t("proformas.unarchive.error_conflict_message")
|
||||||
|
: t("proformas.unarchive.error_unknown_message")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[t, unarchiveMutation]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listCtrl,
|
listCtrl,
|
||||||
panelCtrl,
|
panelCtrl,
|
||||||
@ -46,7 +110,9 @@ export const useListProformasPageController = () => {
|
|||||||
changeStatusDialogCtrl,
|
changeStatusDialogCtrl,
|
||||||
|
|
||||||
downloadPDFCtrl,
|
downloadPDFCtrl,
|
||||||
|
handleArchive,
|
||||||
handleDownloadPDF,
|
handleDownloadPDF,
|
||||||
|
handleUnarchive,
|
||||||
pdfDownloadingId: downloadPDFCtrl.loadingId,
|
pdfDownloadingId: downloadPDFCtrl.loadingId,
|
||||||
isPDFDownloading: downloadPDFCtrl.isLoading,
|
isPDFDownloading: downloadPDFCtrl.isLoading,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,10 +17,12 @@ import {
|
|||||||
} from "../../shared";
|
} from "../../shared";
|
||||||
|
|
||||||
type ProformaListStatusFilter = "all" | ProformaStatus;
|
type ProformaListStatusFilter = "all" | ProformaStatus;
|
||||||
|
type ProformaListArchivedFilter = "false" | "true";
|
||||||
|
|
||||||
// Datos por defecto mientras se carga la consulta o en caso de error.
|
// Datos por defecto mientras se carga la consulta o en caso de error.
|
||||||
const EMPTY_PROFORMAS_LIST: ProformaList = {
|
const EMPTY_PROFORMAS_LIST: ProformaList = {
|
||||||
items: [],
|
items: [],
|
||||||
|
archived: false,
|
||||||
page: INITIAL_PAGE_INDEX,
|
page: INITIAL_PAGE_INDEX,
|
||||||
perPage: INITIAL_PAGE_SIZE,
|
perPage: INITIAL_PAGE_SIZE,
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
@ -52,9 +54,12 @@ const isSortDirection = (value: string | null): value is DataTableSortDirection
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useListProformasController = () => {
|
export const useListProformasController = () => {
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [statusFilter, setStatusFilter] = useState<ProformaListStatusFilter>("all");
|
const [statusFilter, setStatusFilter] = useState<ProformaListStatusFilter>("all");
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [archivedFilter, setArchivedFilter] = useState<ProformaListArchivedFilter>(
|
||||||
|
(searchParams.get("archived") as ProformaListArchivedFilter | null) ?? "false"
|
||||||
|
);
|
||||||
|
|
||||||
const tablePreferences = useDataTablePreferences({
|
const tablePreferences = useDataTablePreferences({
|
||||||
storageKey: "proformas:list:grid",
|
storageKey: "proformas:list:grid",
|
||||||
@ -110,6 +115,7 @@ export const useListProformasController = () => {
|
|||||||
const criteria = useMemo<NonNullable<ListProformasByCriteriaParams["criteria"]>>(
|
const criteria = useMemo<NonNullable<ListProformasByCriteriaParams["criteria"]>>(
|
||||||
() => ({
|
() => ({
|
||||||
q: debouncedSearch || "",
|
q: debouncedSearch || "",
|
||||||
|
archived: archivedFilter,
|
||||||
pageNumber: pageIndex,
|
pageNumber: pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
orderBy,
|
orderBy,
|
||||||
@ -119,7 +125,7 @@ export const useListProformasController = () => {
|
|||||||
? []
|
? []
|
||||||
: [{ field: "status", operator: "EQUALS", value: statusFilter }],
|
: [{ field: "status", operator: "EQUALS", value: statusFilter }],
|
||||||
}),
|
}),
|
||||||
[debouncedSearch, pageIndex, pageSize, orderBy, order, statusFilter]
|
[archivedFilter, debouncedSearch, pageIndex, pageSize, orderBy, order, statusFilter]
|
||||||
);
|
);
|
||||||
|
|
||||||
const query = useProformasListQuery({ criteria });
|
const query = useProformasListQuery({ criteria });
|
||||||
@ -143,6 +149,26 @@ export const useListProformasController = () => {
|
|||||||
[setSearchParams]
|
[setSearchParams]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setArchivedFilterValue = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
const nextValue = value === "true" ? "true" : "false";
|
||||||
|
|
||||||
|
setArchivedFilter((prev) => {
|
||||||
|
if (prev === nextValue) return prev;
|
||||||
|
|
||||||
|
setSearchParams((prevParams) => {
|
||||||
|
const params = new URLSearchParams(prevParams);
|
||||||
|
params.set("page", String(INITIAL_PAGE_INDEX + 1));
|
||||||
|
params.set("archived", nextValue);
|
||||||
|
return params;
|
||||||
|
});
|
||||||
|
|
||||||
|
return nextValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setSearchParams]
|
||||||
|
);
|
||||||
|
|
||||||
const setSearchValue = useCallback(
|
const setSearchValue = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
const nextValue = value.trim().replace(/\s+/g, " ");
|
const nextValue = value.trim().replace(/\s+/g, " ");
|
||||||
@ -238,6 +264,8 @@ export const useListProformasController = () => {
|
|||||||
sort,
|
sort,
|
||||||
setSort: setSortValue,
|
setSort: setSortValue,
|
||||||
|
|
||||||
|
archivedFilter,
|
||||||
|
setArchivedFilter: setArchivedFilterValue,
|
||||||
statusFilter,
|
statusFilter,
|
||||||
setStatusFilter: setStatusFilterValue,
|
setStatusFilter: setStatusFilterValue,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,9 +15,11 @@ import {
|
|||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
FileDownIcon,
|
FileDownIcon,
|
||||||
FileTextIcon,
|
FileTextIcon,
|
||||||
|
FolderArchiveIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
RefreshCwIcon,
|
RefreshCwIcon,
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
|
Undo2Icon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
@ -34,6 +36,8 @@ type GridActionHandlers = {
|
|||||||
onPreviewClick?: (proforma: ProformaListRow) => void;
|
onPreviewClick?: (proforma: ProformaListRow) => void;
|
||||||
onEditClick?: (proforma: ProformaListRow) => void;
|
onEditClick?: (proforma: ProformaListRow) => void;
|
||||||
onIssueClick?: (proforma: ProformaListRow) => void;
|
onIssueClick?: (proforma: ProformaListRow) => void;
|
||||||
|
onArchiveClick?: (proforma: ProformaListRow) => void;
|
||||||
|
onUnarchiveClick?: (proforma: ProformaListRow) => void;
|
||||||
onDeleteClick?: (proforma: ProformaListRow) => void;
|
onDeleteClick?: (proforma: ProformaListRow) => void;
|
||||||
onChangeStatusClick?: (proforma: ProformaListRow, nextStatus: ProformaStatus) => void;
|
onChangeStatusClick?: (proforma: ProformaListRow, nextStatus: ProformaStatus) => void;
|
||||||
onLinkedInvoiceClick?: (proforma: ProformaListRow) => void;
|
onLinkedInvoiceClick?: (proforma: ProformaListRow) => void;
|
||||||
@ -278,6 +282,14 @@ export function useProformasGridColumns(
|
|||||||
const isDraft = proforma.status === PROFORMA_STATUS.DRAFT;
|
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 isArchivable =
|
||||||
|
!proforma.isArchived &&
|
||||||
|
(proforma.status === PROFORMA_STATUS.DRAFT ||
|
||||||
|
proforma.status === PROFORMA_STATUS.REJECTED);
|
||||||
|
const isUnarchivable =
|
||||||
|
proforma.isArchived &&
|
||||||
|
(proforma.status === PROFORMA_STATUS.DRAFT ||
|
||||||
|
proforma.status === PROFORMA_STATUS.REJECTED);
|
||||||
const availableTransitions =
|
const availableTransitions =
|
||||||
PROFORMA_STATUS_TRANSITIONS[proforma.status as ProformaStatus] ?? [];
|
PROFORMA_STATUS_TRANSITIONS[proforma.status as ProformaStatus] ?? [];
|
||||||
|
|
||||||
@ -313,6 +325,54 @@ export function useProformasGridColumns(
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isArchivable && actionHandlers.onArchiveClick && (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
className="size-7 cursor-pointer text-muted-foreground hover:text-primary"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
actionHandlers.onArchiveClick?.(proforma);
|
||||||
|
}}
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<FolderArchiveIcon className="size-4" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipContent>Archivar</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isUnarchivable && actionHandlers.onUnarchiveClick && (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
className="size-7 cursor-pointer text-muted-foreground hover:text-primary"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
actionHandlers.onUnarchiveClick?.(proforma);
|
||||||
|
}}
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<Undo2Icon className="size-4" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipContent>Desarchivar</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Cambiar estado */}
|
{/* Cambiar estado */}
|
||||||
{!isIssued && availableTransitions.length && actionHandlers.onChangeStatusClick && (
|
{!isIssued && availableTransitions.length && actionHandlers.onChangeStatusClick && (
|
||||||
<TooltipProvider key={availableTransitions[0]}>
|
<TooltipProvider key={availableTransitions[0]}>
|
||||||
@ -397,7 +457,7 @@ export function useProformasGridColumns(
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
{/* Eliminar */}
|
{/* Eliminar */}
|
||||||
{isDraft && actionHandlers.onDeleteClick && (
|
{isDraft && !proforma.isArchived && actionHandlers.onDeleteClick && (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
|
|||||||
@ -50,7 +50,9 @@ export const ListProformasPage = () => {
|
|||||||
deleteDialogCtrl,
|
deleteDialogCtrl,
|
||||||
issueDialogCtrl,
|
issueDialogCtrl,
|
||||||
changeStatusDialogCtrl,
|
changeStatusDialogCtrl,
|
||||||
|
handleArchive,
|
||||||
handleDownloadPDF,
|
handleDownloadPDF,
|
||||||
|
handleUnarchive,
|
||||||
isPDFDownloading: isPdfDownloading,
|
isPDFDownloading: isPdfDownloading,
|
||||||
pdfDownloadingId,
|
pdfDownloadingId,
|
||||||
} = useListProformasPageController();
|
} = useListProformasPageController();
|
||||||
@ -68,10 +70,12 @@ export const ListProformasPage = () => {
|
|||||||
onEditClick: (proforma) => handleEditClick(proforma.id),
|
onEditClick: (proforma) => handleEditClick(proforma.id),
|
||||||
onIssueClick: (proformaRow) =>
|
onIssueClick: (proformaRow) =>
|
||||||
issueDialogCtrl.openDialog(prepareIssueProformaTarget(proformaRow)),
|
issueDialogCtrl.openDialog(prepareIssueProformaTarget(proformaRow)),
|
||||||
|
onArchiveClick: handleArchive,
|
||||||
onDeleteClick: (proformaRow: ProformaListRow) =>
|
onDeleteClick: (proformaRow: ProformaListRow) =>
|
||||||
deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow)),
|
deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow)),
|
||||||
onChangeStatusClick: (proforma) =>
|
onChangeStatusClick: (proforma) =>
|
||||||
changeStatusDialogCtrl.openDialog(prepareChangeProformaStatusTargets(proforma)),
|
changeStatusDialogCtrl.openDialog(prepareChangeProformaStatusTargets(proforma)),
|
||||||
|
onUnarchiveClick: handleUnarchive,
|
||||||
|
|
||||||
onDownloadPdf: handleDownloadPDF,
|
onDownloadPdf: handleDownloadPDF,
|
||||||
pdfDownloadingId,
|
pdfDownloadingId,
|
||||||
@ -89,6 +93,20 @@ export const ListProformasPage = () => {
|
|||||||
value={listCtrl.search}
|
value={listCtrl.search}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => listCtrl.setArchivedFilter(value ?? "false")}
|
||||||
|
value={listCtrl.archivedFilter}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full sm:w-40">
|
||||||
|
<FilterIcon aria-hidden className="mr-2 size-4" />
|
||||||
|
<SelectValue placeholder="Archivadas" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="false">Activas</SelectItem>
|
||||||
|
<SelectItem value="true">Archivadas</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={(value) => listCtrl.setStatusFilter(value ?? "all")}
|
onValueChange={(value) => listCtrl.setStatusFilter(value ?? "all")}
|
||||||
value={listCtrl.statusFilter}
|
value={listCtrl.statusFilter}
|
||||||
|
|||||||
@ -27,6 +27,8 @@ export const GetProformaByIdAdapter = {
|
|||||||
|
|
||||||
proformaReference: dto.proforma_reference,
|
proformaReference: dto.proforma_reference,
|
||||||
status: dto.status as ProformaStatus,
|
status: dto.status as ProformaStatus,
|
||||||
|
archivedAt: dto.archived_at,
|
||||||
|
isArchived: dto.archived_at !== null,
|
||||||
targetInvoiceSeriesCode: dto.target_invoice_series_code,
|
targetInvoiceSeriesCode: dto.target_invoice_series_code,
|
||||||
series: dto.target_invoice_series_code ?? dto.series,
|
series: dto.target_invoice_series_code ?? dto.series,
|
||||||
|
|
||||||
@ -41,6 +43,7 @@ export const GetProformaByIdAdapter = {
|
|||||||
currencyCode: dto.currency_code,
|
currencyCode: dto.currency_code,
|
||||||
|
|
||||||
customerId: dto.customer_id,
|
customerId: dto.customer_id,
|
||||||
|
linkedInvoiceId: dto.linked_invoice_id,
|
||||||
recipient: mapRecipient(dto),
|
recipient: mapRecipient(dto),
|
||||||
taxConfig: {
|
taxConfig: {
|
||||||
taxMode: dto.tax_config.tax_mode === "per_line" ? "perLine" : "single",
|
taxMode: dto.tax_config.tax_mode === "per_line" ? "perLine" : "single",
|
||||||
|
|||||||
@ -19,7 +19,9 @@ import type { ProformaList, ProformaListRow, ProformaStatus } from "../entities"
|
|||||||
|
|
||||||
export const ListProformasAdapter = {
|
export const ListProformasAdapter = {
|
||||||
fromDto(dto: ListProformasResult, context?: unknown): ProformaList {
|
fromDto(dto: ListProformasResult, context?: unknown): ProformaList {
|
||||||
|
const archived = (context as { archived?: boolean } | undefined)?.archived ?? false;
|
||||||
return {
|
return {
|
||||||
|
archived,
|
||||||
page: dto.page,
|
page: dto.page,
|
||||||
perPage: dto.per_page,
|
perPage: dto.per_page,
|
||||||
totalPages: dto.total_pages,
|
totalPages: dto.total_pages,
|
||||||
@ -50,6 +52,8 @@ const ProformaListRowAdapter = {
|
|||||||
|
|
||||||
proformaReference: dto.proforma_reference,
|
proformaReference: dto.proforma_reference,
|
||||||
status: dto.status as ProformaStatus,
|
status: dto.status as ProformaStatus,
|
||||||
|
archivedAt: dto.archived_at,
|
||||||
|
isArchived: dto.archived_at !== null,
|
||||||
targetInvoiceSeriesCode: dto.target_invoice_series_code,
|
targetInvoiceSeriesCode: dto.target_invoice_series_code,
|
||||||
series: dto.target_invoice_series_code ?? dto.series,
|
series: dto.target_invoice_series_code ?? dto.series,
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,8 @@ export const ProformaToListRowPatchAdapter = {
|
|||||||
id: proforma.id,
|
id: proforma.id,
|
||||||
proformaReference: proforma.proformaReference,
|
proformaReference: proforma.proformaReference,
|
||||||
status: proforma.status,
|
status: proforma.status,
|
||||||
|
archivedAt: proforma.archivedAt,
|
||||||
|
isArchived: proforma.isArchived,
|
||||||
series: proforma.series,
|
series: proforma.series,
|
||||||
proformaDate: proforma.proformaDate,
|
proformaDate: proforma.proformaDate,
|
||||||
operationDate: proforma.operationDate,
|
operationDate: proforma.operationDate,
|
||||||
@ -44,6 +46,7 @@ export const ProformaToListRowPatchAdapter = {
|
|||||||
taxableAmount: proforma.taxableAmount,
|
taxableAmount: proforma.taxableAmount,
|
||||||
taxesAmount: proforma.taxesAmount,
|
taxesAmount: proforma.taxesAmount,
|
||||||
totalAmount: proforma.totalAmount,
|
totalAmount: proforma.totalAmount,
|
||||||
|
linkedInvoiceId: proforma.linkedInvoiceId ?? null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { GetProformaByIdResponseDTO } from "../../../../common";
|
||||||
|
|
||||||
|
export interface ArchiveProformaByIdParams {
|
||||||
|
proformaId: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ArchiveProformaByIdResult = GetProformaByIdResponseDTO;
|
||||||
|
|
||||||
|
export const archiveProformaById = (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: ArchiveProformaByIdParams
|
||||||
|
): Promise<ArchiveProformaByIdResult> => {
|
||||||
|
const { proformaId, signal } = params;
|
||||||
|
|
||||||
|
if (!proformaId) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.custom<unknown, ArchiveProformaByIdResult>({
|
||||||
|
path: `proformas/${proformaId}/archive`,
|
||||||
|
method: "patch",
|
||||||
|
data: undefined,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./archive-proforma-by-id.api";
|
||||||
export * from "./change-proforma-status-by-id.api";
|
export * from "./change-proforma-status-by-id.api";
|
||||||
export * from "./create-proforma.api";
|
export * from "./create-proforma.api";
|
||||||
export * from "./delete-proforma-by-id.api";
|
export * from "./delete-proforma-by-id.api";
|
||||||
@ -5,4 +6,5 @@ export * from "./get-proforma-by-id.api";
|
|||||||
export * from "./issue-proforma-by-id.api";
|
export * from "./issue-proforma-by-id.api";
|
||||||
export * from "./list-document-series.api";
|
export * from "./list-document-series.api";
|
||||||
export * from "./list-proformas-by-criteria.api";
|
export * from "./list-proformas-by-criteria.api";
|
||||||
|
export * from "./unarchive-proforma-by-id.api";
|
||||||
export * from "./update-proforma-by-id.api";
|
export * from "./update-proforma-by-id.api";
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import type { ListProformasResponseDTO } from "../../../../common";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export type ListProformasByCriteriaParams = {
|
export type ListProformasByCriteriaParams = {
|
||||||
criteria?: CriteriaDTO;
|
criteria?: CriteriaDTO & { archived?: "true" | "false" };
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import type { IDataSource } from "@erp/core/client";
|
||||||
|
|
||||||
|
import type { GetProformaByIdResponseDTO } from "../../../../common";
|
||||||
|
|
||||||
|
export interface UnarchiveProformaByIdParams {
|
||||||
|
proformaId: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UnarchiveProformaByIdResult = GetProformaByIdResponseDTO;
|
||||||
|
|
||||||
|
export const unarchiveProformaById = (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
params: UnarchiveProformaByIdParams
|
||||||
|
): Promise<UnarchiveProformaByIdResult> => {
|
||||||
|
const { proformaId, signal } = params;
|
||||||
|
|
||||||
|
if (!proformaId) throw new Error("proformaId is required");
|
||||||
|
|
||||||
|
return dataSource.custom<UnarchiveProformaByIdResult>(`proformas/${proformaId}/unarchive`, {
|
||||||
|
method: "PATCH",
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -14,6 +14,8 @@ export interface ProformaListRow {
|
|||||||
|
|
||||||
proformaReference: string;
|
proformaReference: string;
|
||||||
status: ProformaStatus;
|
status: ProformaStatus;
|
||||||
|
archivedAt: string | null;
|
||||||
|
isArchived: boolean;
|
||||||
targetInvoiceSeriesCode: string | null;
|
targetInvoiceSeriesCode: string | null;
|
||||||
series: string | null;
|
series: string | null;
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type { ProformaListRow } from "./proforma-list-row.entity";
|
|||||||
|
|
||||||
export interface ProformaList {
|
export interface ProformaList {
|
||||||
items: ProformaListRow[];
|
items: ProformaListRow[];
|
||||||
|
archived: boolean;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
totalItems: number;
|
totalItems: number;
|
||||||
page: number;
|
page: number;
|
||||||
|
|||||||
@ -25,6 +25,8 @@ export interface Proforma {
|
|||||||
|
|
||||||
proformaReference: string;
|
proformaReference: string;
|
||||||
status: ProformaStatus;
|
status: ProformaStatus;
|
||||||
|
archivedAt: string | null;
|
||||||
|
isArchived: boolean;
|
||||||
targetInvoiceSeriesCode: string | null;
|
targetInvoiceSeriesCode: string | null;
|
||||||
series: string | null;
|
series: string | null;
|
||||||
|
|
||||||
@ -61,6 +63,7 @@ export interface Proforma {
|
|||||||
taxesAmount: number;
|
taxesAmount: number;
|
||||||
|
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
|
linkedInvoiceId?: string | null;
|
||||||
|
|
||||||
items: ProformaItem[];
|
items: ProformaItem[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
export * from "./keys";
|
export * from "./keys";
|
||||||
export * from "./use-document-series-query";
|
export * from "./use-document-series-query";
|
||||||
|
export * from "./use-proforma-archive-mutation";
|
||||||
export * from "./use-proforma-change-status-mutation";
|
export * from "./use-proforma-change-status-mutation";
|
||||||
export * from "./use-proforma-create-mutation";
|
export * from "./use-proforma-create-mutation";
|
||||||
export * from "./use-proforma-delete-mutation";
|
export * from "./use-proforma-delete-mutation";
|
||||||
export * from "./use-proforma-get-query";
|
export * from "./use-proforma-get-query";
|
||||||
export * from "./use-proforma-issue-mutation";
|
export * from "./use-proforma-issue-mutation";
|
||||||
|
export * from "./use-proforma-unarchive-mutation";
|
||||||
export * from "./use-proforma-update-mutation";
|
export * from "./use-proforma-update-mutation";
|
||||||
export * from "./use-proformas-list-query";
|
export * from "./use-proformas-list-query";
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export const LIST_PROFORMAS_QUERY_KEY = (criteria?: ProformasListRequestDTO): Qu
|
|||||||
[
|
[
|
||||||
...LIST_PROFORMAS_QUERY_KEY_PREFIX,
|
...LIST_PROFORMAS_QUERY_KEY_PREFIX,
|
||||||
{
|
{
|
||||||
|
archived: criteria?.archived ?? "false",
|
||||||
pageNumber: criteria?.pageNumber ?? 1,
|
pageNumber: criteria?.pageNumber ?? 1,
|
||||||
pageSize: criteria?.pageSize ?? 5,
|
pageSize: criteria?.pageSize ?? 5,
|
||||||
q: criteria?.q ?? "",
|
q: criteria?.q ?? "",
|
||||||
@ -39,6 +40,8 @@ export const PROFORMA_QUERY_KEY = (proformaId?: string): QueryKey => [
|
|||||||
export const CREATE_PROFORMA_MUTATION_KEY = ["proformas:create"] as const;
|
export const CREATE_PROFORMA_MUTATION_KEY = ["proformas:create"] as const;
|
||||||
export const UPDATE_PROFORMA_MUTATION_KEY = ["proformas:update"] as const;
|
export const UPDATE_PROFORMA_MUTATION_KEY = ["proformas:update"] as const;
|
||||||
export const DELETE_PROFORMA_MUTATION_KEY = ["proformas:delete"] as const;
|
export const DELETE_PROFORMA_MUTATION_KEY = ["proformas:delete"] as const;
|
||||||
|
export const ARCHIVE_PROFORMA_MUTATION_KEY = ["proformas:archive"] as const;
|
||||||
|
export const UNARCHIVE_PROFORMA_MUTATION_KEY = ["proformas:unarchive"] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operaciones de dominio
|
* Operaciones de dominio
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { GetProformaByIdAdapter } from "../adapters";
|
||||||
|
import { type ArchiveProformaByIdParams, archiveProformaById } from "../api";
|
||||||
|
import type { Proforma } from "../entities";
|
||||||
|
|
||||||
|
import { ARCHIVE_PROFORMA_MUTATION_KEY } from "./keys";
|
||||||
|
import { syncUpdatedProformaCaches } from "./proforma-cache-strategy";
|
||||||
|
|
||||||
|
export const useProformaArchiveMutation = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ARCHIVE_PROFORMA_MUTATION_KEY,
|
||||||
|
mutationFn: async (params: ArchiveProformaByIdParams) => {
|
||||||
|
const dto = await archiveProformaById(dataSource, params);
|
||||||
|
return GetProformaByIdAdapter.fromDto(dto);
|
||||||
|
},
|
||||||
|
onSuccess: async (proforma) => {
|
||||||
|
await syncUpdatedProformaCaches(queryClient, proforma);
|
||||||
|
},
|
||||||
|
}) as ReturnType<typeof useMutation<Proforma, DefaultError, ArchiveProformaByIdParams>>;
|
||||||
|
};
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { useDataSource } from "@erp/core/hooks";
|
||||||
|
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { GetProformaByIdAdapter } from "../adapters";
|
||||||
|
import { type UnarchiveProformaByIdParams, unarchiveProformaById } from "../api";
|
||||||
|
|
||||||
|
import { UNARCHIVE_PROFORMA_MUTATION_KEY } from "./keys";
|
||||||
|
import { syncUpdatedProformaCaches } from "./proforma-cache-strategy";
|
||||||
|
|
||||||
|
export const useProformaUnarchiveMutation = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: UNARCHIVE_PROFORMA_MUTATION_KEY,
|
||||||
|
mutationFn: async (params: UnarchiveProformaByIdParams) => {
|
||||||
|
const dto = await unarchiveProformaById(dataSource, params);
|
||||||
|
return GetProformaByIdAdapter.fromDto(dto);
|
||||||
|
},
|
||||||
|
onSuccess: async (proforma) => {
|
||||||
|
await syncUpdatedProformaCaches(queryClient, proforma);
|
||||||
|
},
|
||||||
|
}) as ReturnType<
|
||||||
|
typeof useMutation<import("../entities").Proforma, DefaultError, UnarchiveProformaByIdParams>
|
||||||
|
>;
|
||||||
|
};
|
||||||
@ -10,7 +10,7 @@ import { LIST_PROFORMAS_QUERY_KEY } from "./keys";
|
|||||||
|
|
||||||
export interface ProformasListQueryOptions {
|
export interface ProformasListQueryOptions {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
criteria?: Partial<CriteriaDTO>;
|
criteria?: Partial<CriteriaDTO> & { archived?: "true" | "false" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProformasListQuery = (
|
export const useProformasListQuery = (
|
||||||
@ -24,7 +24,7 @@ export const useProformasListQuery = (
|
|||||||
queryKey: LIST_PROFORMAS_QUERY_KEY(criteria),
|
queryKey: LIST_PROFORMAS_QUERY_KEY(criteria),
|
||||||
queryFn: async ({ signal }) => {
|
queryFn: async ({ signal }) => {
|
||||||
const dto = await getListProformasByCriteria(dataSource, { signal, criteria });
|
const dto = await getListProformasByCriteria(dataSource, { signal, criteria });
|
||||||
return ListProformasAdapter.fromDto(dto);
|
return ListProformasAdapter.fromDto(dto, { archived: criteria.archived === "true" });
|
||||||
},
|
},
|
||||||
enabled,
|
enabled,
|
||||||
placeholderData: (previousData) => previousData, // Mantiene la página anterior durante refetch por cambio de criteria
|
placeholderData: (previousData) => previousData, // Mantiene la página anterior durante refetch por cambio de criteria
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user