diff --git a/modules/customer-invoices/src/api/application/use-cases/issue-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/issue-customer-invoice.use-case.ts index ed9174ba..5796abe7 100644 --- a/modules/customer-invoices/src/api/application/use-cases/issue-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/issue-customer-invoice.use-case.ts @@ -1,8 +1,8 @@ import { ITransactionManager } from "@erp/core/api"; import { UniqueID, UtcDate } from "@repo/rdx-ddd"; -import { Maybe, Result } from "@repo/rdx-utils"; -import { InvalidProformaStatusError } from "../../domain"; -import { StatusInvoiceIsApprovedSpecification } from "../../domain/specs"; +import { Result } from "@repo/rdx-utils"; +import { ProformaCannotBeConvertedToInvoiceError } from "../../domain"; +import { ProformaCanTranstionToIssuedSpecification } from "../../domain/specs"; import { CustomerInvoiceApplicationService } from "../customer-invoice-application.service"; type IssueCustomerInvoiceUseCaseInput = { @@ -52,9 +52,9 @@ export class IssueCustomerInvoiceUseCase { const proforma = proformaResult.data; /** 2. Comprobamos que la proforma origen está aprovada para generar la factura */ - const isApprovedSpec = new StatusInvoiceIsApprovedSpecification(); - if (!(await isApprovedSpec.isSatisfiedBy(proforma))) { - return Result.fail(new InvalidProformaStatusError(proformaId.toString())); + const isOk = new ProformaCanTranstionToIssuedSpecification(); + if (!(await isOk.isSatisfiedBy(proforma))) { + return Result.fail(new ProformaCannotBeConvertedToInvoiceError(proformaId.toString())); } /** 3. Generar nueva factura */ @@ -71,7 +71,7 @@ export class IssueCustomerInvoiceUseCase { // props base obtenidas del agregado proforma const issuedInvoiceOrError = this.service.buildIssueInvoiceInCompany(companyId, proforma, { - invoiceNumber: Maybe.some(newIssueNumber), + invoiceNumber: newIssueNumber, invoiceDate: UtcDate.today(), }); diff --git a/modules/customer-invoices/src/api/domain/specs/index.ts b/modules/customer-invoices/src/api/domain/specs/index.ts index 37fb90f8..dc3b63c7 100644 --- a/modules/customer-invoices/src/api/domain/specs/index.ts +++ b/modules/customer-invoices/src/api/domain/specs/index.ts @@ -1 +1 @@ -export * from "./status-invoice-is-approved.specification"; +export * from "./proforma-can-transtion-to-issued.specification"; diff --git a/modules/customer-invoices/src/api/domain/specs/proforma-can-transtion-to-issued.specification.ts b/modules/customer-invoices/src/api/domain/specs/proforma-can-transtion-to-issued.specification.ts new file mode 100644 index 00000000..62fa6deb --- /dev/null +++ b/modules/customer-invoices/src/api/domain/specs/proforma-can-transtion-to-issued.specification.ts @@ -0,0 +1,9 @@ +import { CompositeSpecification } from "@repo/rdx-ddd"; +import { CustomerInvoice } from "../aggregates"; +import { INVOICE_STATUS } from "../value-objects"; + +export class ProformaCanTranstionToIssuedSpecification extends CompositeSpecification { + public async isSatisfiedBy(proforma: CustomerInvoice): Promise { + return proforma.isProforma && proforma.canTransitionTo(INVOICE_STATUS.ISSUED); + } +} diff --git a/modules/customer-invoices/src/api/domain/specs/status-invoice-is-approved.specification.ts b/modules/customer-invoices/src/api/domain/specs/status-invoice-is-approved.specification.ts deleted file mode 100644 index 87189689..00000000 --- a/modules/customer-invoices/src/api/domain/specs/status-invoice-is-approved.specification.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { CompositeSpecification } from "@repo/rdx-ddd"; -import { CustomerInvoice } from "../aggregates"; - -export class StatusInvoiceIsApprovedSpecification extends CompositeSpecification { - public async isSatisfiedBy(invoice: CustomerInvoice): Promise { - return invoice.status.isApproved(); - } -} diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts index 5fa59b4d..61d4708b 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-status.ts @@ -11,9 +11,8 @@ export enum INVOICE_STATUS { APPROVED = "approved", // <- Proforma REJECTED = "rejected", // <- Proforma - // status === issued <- (si is_proforma === true) => Es una proforma (histórica) - // status === issued <- (si is_proforma === false) => Factura y enviará/enviada a Veri*Factu - + // status === "issued" <- (si is_proforma === true) => Es una proforma (histórica) + // status === "issued" <- (si is_proforma === false) => Factura y enviará/enviada a Veri*Factu ISSUED = "issued", } export class CustomerInvoiceStatus extends ValueObject { @@ -93,15 +92,6 @@ export class CustomerInvoiceStatus extends ValueObject { - if (!this.canTransitionTo(nextStatus)) { - return Result.fail( - new Error(`Transición no permitida de ${this.props.value} a ${nextStatus}`) - ); - } - return CustomerInvoiceStatus.create(nextStatus); - } - toString() { return String(this.props.value); } diff --git a/modules/customer-invoices/src/web/components/editor/items/use-items-columns.tsx b/modules/customer-invoices/src/web/components/editor/items/use-items-columns.tsx index 05a8b964..2b8dd837 100644 --- a/modules/customer-invoices/src/web/components/editor/items/use-items-columns.tsx +++ b/modules/customer-invoices/src/web/components/editor/items/use-items-columns.tsx @@ -1,20 +1,19 @@ -import { DataTableColumnHeader } from '@repo/rdx-ui/components'; +import { DataTableColumnHeader } from "@repo/rdx-ui/components"; import { InputGroup, InputGroupTextarea } from "@repo/shadcn-ui/components"; import { cn } from "@repo/shadcn-ui/lib/utils"; import type { ColumnDef } from "@tanstack/react-table"; import * as React from "react"; import { Controller, useFormContext } from "react-hook-form"; -import { useInvoiceContext } from '../../../context'; -import { CustomerInvoiceTaxesMultiSelect } from '../../customer-invoice-taxes-multi-select'; -import { AmountInputField } from './amount-input-field'; -import { HoverCardTotalsSummary } from './hover-card-total-summary'; -import { ItemDataTableRowActions } from './items-data-table-row-actions'; -import { PercentageInputField } from './percentage-input-field'; -import { QuantityInputField } from './quantity-input-field'; - +import { useInvoiceContext } from "../../../context"; +import { CustomerInvoiceTaxesMultiSelect } from "../../customer-invoice-taxes-multi-select"; +import { AmountInputField } from "./amount-input-field"; +import { HoverCardTotalsSummary } from "./hover-card-total-summary"; +import { ItemDataTableRowActions } from "./items-data-table-row-actions"; +import { PercentageInputField } from "./percentage-input-field"; +import { QuantityInputField } from "./quantity-input-field"; export interface InvoiceItemFormData { - id: string; // ← mapea RHF field.id aquí + id: string; // ← mapea RHF field.id aquí description: string; quantity: number | ""; unit_amount: number | ""; @@ -22,53 +21,62 @@ export interface InvoiceItemFormData { tax_codes: string[]; total_amount: number | ""; // readonly calculado } -export interface InvoiceFormData { items: InvoiceItemFormData[] } +export interface InvoiceFormData { + items: InvoiceItemFormData[]; +} export function useItemsColumns(): ColumnDef[] { const { t, readOnly, currency_code, language_code } = useInvoiceContext(); const { control } = useFormContext(); // Atención: Memoizar siempre para evitar reconstrucciones y resets de estado de tabla - return React.useMemo[]>(() => [ - { - id: 'position', - header: ({ column }) => ( - - ), - cell: ({ row }) => row.index + 1, - enableSorting: false, - size: 32, - }, - { - accessorKey: "description", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( - ( - - { - const el = e.currentTarget; - el.style.height = "auto"; - el.style.height = `${el.scrollHeight}px`; - }} - className={cn( - "min-w-[12rem] max-w-[46rem] w-full resize-none bg-transparent border-dashed transition", - "focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-background focus-visible:border-solid", - "focus:resize-y" - )} - data-cell-focus /> - {/* + return React.useMemo[]>( + () => [ + { + id: "position", + header: ({ column }) => ( + + ), + cell: ({ row }) => row.index + 1, + enableSorting: false, + size: 32, + }, + { + accessorKey: "description", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + ( + + { + const el = e.currentTarget; + el.style.height = "auto"; + el.style.height = `${el.scrollHeight}px`; + }} + className={cn( + "min-w-[12rem] max-w-[46rem] w-full resize-none bg-transparent border-dashed transition", + "focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-background focus-visible:border-solid", + "focus:resize-y" + )} + data-cell-focus + /> + {/* Line 1, Column 1 [] { Send */} - - - )} - /> - ), - enableSorting: false, - size: 480, minSize: 240, maxSize: 768, - }, - { - accessorKey: "quantity", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( - - ), - enableSorting: false, - size: 52, minSize: 48, maxSize: 64, - }, - { - accessorKey: "unit_amount", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( - - ), - enableSorting: false, - size: 120, minSize: 100, maxSize: 160, - }, - { - accessorKey: "discount_percentage", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( - - ), - enableSorting: false, - size: 40, minSize: 40 - }, - { - accessorKey: "tax_codes", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( - ( - - )} - /> - ), - enableSorting: false, - size: 240, minSize: 232, maxSize: 320, - }, - { - accessorKey: "total_amount", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( - + + )} + /> + ), + enableSorting: false, + size: 480, + minSize: 240, + maxSize: 768, + }, + { + accessorKey: "quantity", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + + ), + enableSorting: false, + size: 52, + minSize: 48, + maxSize: 64, + }, + { + accessorKey: "unit_amount", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( - - ), - enableSorting: false, - size: 120, minSize: 100, maxSize: 160, - }, - { - id: "actions", - header: ({ column }) => ( - - ), - cell: ({ row, table }) => , - }, - ], [t, readOnly, control, currency_code, language_code,]); + ), + enableSorting: false, + size: 120, + minSize: 100, + maxSize: 160, + }, + { + accessorKey: "discount_percentage", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + + ), + enableSorting: false, + size: 40, + minSize: 40, + }, + { + accessorKey: "taxable_amount", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + + ), + enableSorting: false, + size: 120, + minSize: 100, + maxSize: 160, + }, + { + accessorKey: "tax_codes", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + ( + + )} + /> + ), + enableSorting: false, + size: 120, + minSize: 130, + maxSize: 180, + }, + { + accessorKey: "total_amount", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + + + + ), + enableSorting: false, + size: 120, + minSize: 100, + maxSize: 160, + }, + { + id: "actions", + header: ({ column }) => ( + + ), + cell: ({ row, table }) => , + }, + ], + [t, readOnly, control, currency_code, language_code] + ); }