This commit is contained in:
David Arranz 2026-04-15 11:30:55 +02:00
parent 00b892b91b
commit 71b4dc1d06
8 changed files with 55 additions and 20 deletions

View File

@ -39,6 +39,7 @@
"@erp/core": "workspace:*", "@erp/core": "workspace:*",
"@erp/customers": "workspace:*", "@erp/customers": "workspace:*",
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
"@lglab/react-qr-code": "^1.4.10",
"@repo/i18next": "workspace:*", "@repo/i18next": "workspace:*",
"@repo/rdx-criteria": "workspace:*", "@repo/rdx-criteria": "workspace:*",
"@repo/rdx-ddd": "workspace:*", "@repo/rdx-ddd": "workspace:*",

View File

@ -1,3 +1,4 @@
import { UtcDate } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils"; import { Maybe, Result } from "@repo/rdx-utils";
import { import {
@ -102,7 +103,7 @@ export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoic
invoiceNumber: proforma.invoiceNumber, invoiceNumber: proforma.invoiceNumber,
invoiceDate: proforma.invoiceDate, invoiceDate: UtcDate.today(), // La fecha de la factura es la fecha de emisión, no la de la proforma
operationDate: proforma.operationDate, operationDate: proforma.operationDate,
description: proforma.description, description: proforma.description,

View File

@ -1,6 +1,6 @@
import type { Tax } from "@erp/core/api"; import type { Tax } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd"; import { ValueObject } from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils"; import { Maybe, Result } from "@repo/rdx-utils";
import { ItemAmount } from "../../common/value-objects"; import { ItemAmount } from "../../common/value-objects";
@ -32,6 +32,19 @@ export class ProformaItemTaxes
return Result.ok(new ProformaItemTaxes(props)); return Result.ok(new ProformaItemTaxes(props));
} }
/**
* Crea una instancia "vacía" (sin impuestos)
* - Evita duplicación de lógica de construcción
* - Centraliza el concepto de "sin impuestos"
*/
static empty(): ProformaItemTaxes {
return new ProformaItemTaxes({
iva: Maybe.none(),
rec: Maybe.none(),
retention: Maybe.none(),
});
}
toKey(): string { toKey(): string {
const ivaCode = this.props.iva.match( const ivaCode = this.props.iva.match(
(iva) => iva.code, (iva) => iva.code,

View File

@ -283,12 +283,6 @@ export class IssuedInvoiceRepository
}, },
]; ];
// Reemplazar findAndCountAll por findAll + count (más control y mejor rendimiento)
/*const { rows, count } = await CustomerInvoiceModel.findAndCountAll({
...query,
transaction,
});*/
const [rows, count] = await Promise.all([ const [rows, count] = await Promise.all([
CustomerInvoiceModel.findAll({ CustomerInvoiceModel.findAll({
...query, ...query,

View File

@ -13,7 +13,7 @@ import {
import { import {
CreateProformaRequestSchema, CreateProformaRequestSchema,
GetProformaByIdRequestSchema, GetProformaByIdRequestSchema,
IssueProformaByIdResponseSchema, IssueProformaByIdParamsRequestSchema,
ListProformasRequestSchema, ListProformasRequestSchema,
ReportProformaByIdParamsRequestSchema, ReportProformaByIdParamsRequestSchema,
ReportProformaByIdQueryRequestSchema, ReportProformaByIdQueryRequestSchema,
@ -150,7 +150,7 @@ export const proformasRouter = (params: StartParams) => {
"/:proforma_id/issue", "/:proforma_id/issue",
//checkTabContext, //checkTabContext,
validateRequest(IssueProformaByIdResponseSchema, "params"), validateRequest(IssueProformaByIdParamsRequestSchema, "params"),
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
const useCase = deps.useCases.issueProforma(publicServices); const useCase = deps.useCases.issueProforma(publicServices);

View File

@ -3,12 +3,12 @@ import { useMemo, useState } from "react";
import type { import type {
IssuedInvoiceList, IssuedInvoiceList,
IssuedInvoiceStatus,
ListIssuedInvoicesByCriteriaParams, ListIssuedInvoicesByCriteriaParams,
VerifactuRecordStatus,
} from "../../shared"; } from "../../shared";
import { useIssuedInvoiceListQuery } from "../../shared/"; import { useIssuedInvoiceListQuery } from "../../shared/";
type IssuedInvoiceListStatusFilter = "all" | IssuedInvoiceStatus; type VerifactuRecordListStatusFilter = "all" | VerifactuRecordStatus;
const EMPTY_ISSUED_INVOICES_LIST: IssuedInvoiceList = { const EMPTY_ISSUED_INVOICES_LIST: IssuedInvoiceList = {
items: [], items: [],
@ -22,7 +22,8 @@ export const useIssuedInvoiceListController = () => {
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(5); const [pageSize, setPageSize] = useState(5);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState<IssuedInvoiceListStatusFilter>("all"); const [verifactuStatusFilter, setVerifactuStatusFilter] =
useState<VerifactuRecordListStatusFilter>("all");
const debouncedSearch = useDebounce(search, 300); const debouncedSearch = useDebounce(search, 300);
@ -33,17 +34,20 @@ export const useIssuedInvoiceListController = () => {
pageSize, pageSize,
order: "desc", order: "desc",
orderBy: "invoice_date", orderBy: "invoice_date",
filters: status === "all" ? [] : [{ field: "status", operator: "eq", value: status }], filters:
verifactuStatusFilter === "all"
? []
: [{ field: "verifactu.status", operator: "eq", value: verifactuStatusFilter }],
}), }),
[debouncedSearch, pageIndex, pageSize, status] [debouncedSearch, pageIndex, pageSize, verifactuStatusFilter]
); );
const query = useIssuedInvoiceListQuery({ criteria }); const query = useIssuedInvoiceListQuery({ criteria });
const setStatusFilterValue = (value: string) => { const setStatusFilterValue = (value: string) => {
const nextValue = (value || "all") as IssuedInvoiceListStatusFilter; const nextValue = (value || "all") as VerifactuRecordListStatusFilter;
setStatusFilter((prev) => { setVerifactuStatusFilter((prev) => {
if (prev === nextValue) return prev; if (prev === nextValue) return prev;
// Sólo si la búsqueda realmente cambia, // Sólo si la búsqueda realmente cambia,
@ -95,7 +99,7 @@ export const useIssuedInvoiceListController = () => {
search, search,
setSearchValue, setSearchValue,
statusFilter, statusFilter: verifactuStatusFilter,
setStatusFilter: setStatusFilterValue, setStatusFilter: setStatusFilterValue,
}; };
}; };

View File

@ -1,4 +1,5 @@
import { formatDate } from "@erp/core/client"; import { formatDate } from "@erp/core/client";
import { ReactQRCode } from "@lglab/react-qr-code";
import { DataTableColumnHeader } from "@repo/rdx-ui/components"; import { DataTableColumnHeader } from "@repo/rdx-ui/components";
import { import {
Button, Button,
@ -16,7 +17,6 @@ import {
import type { ColumnDef } from "@tanstack/react-table"; import type { ColumnDef } from "@tanstack/react-table";
import { DownloadIcon, FileDownIcon, MailIcon, MoreVerticalIcon, QrCodeIcon } from "lucide-react"; import { DownloadIcon, FileDownIcon, MailIcon, MoreVerticalIcon, QrCodeIcon } from "lucide-react";
import * as React from "react"; import * as React from "react";
import QrCode from "react-qr-code";
import { useTranslation } from "../../../../../i18n"; import { useTranslation } from "../../../../../i18n";
import type { IssuedInvoiceListRow } from "../../../../shared"; import type { IssuedInvoiceListRow } from "../../../../shared";
@ -115,7 +115,17 @@ export function useIssuedInvoicesGridColumns(
} }
/> />
<TooltipContent className="m-0 p-3"> <TooltipContent className="m-0 p-3">
<QrCode className="bg-white p-8" value={verifactu.url} /> <div className="border-2 border-black">
<ReactQRCode
background="#fff"
dataModulesSettings={{ color: "#000" }}
finderPatternInnerSettings={{ color: "#000" }}
finderPatternOuterSettings={{ color: "#000" }}
marginSize={8}
size={256}
value={verifactu.url}
/>
</div>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
)} )}

View File

@ -489,6 +489,9 @@ importers:
'@hookform/resolvers': '@hookform/resolvers':
specifier: ^5.2.2 specifier: ^5.2.2
version: 5.2.2(react-hook-form@7.72.1(react@19.2.5)) version: 5.2.2(react-hook-form@7.72.1(react@19.2.5))
'@lglab/react-qr-code':
specifier: ^1.4.10
version: 1.4.10(react@19.2.5)
'@repo/i18next': '@repo/i18next':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/i18n version: link:../../packages/i18n
@ -2226,6 +2229,11 @@ packages:
'@jridgewell/trace-mapping@0.3.9': '@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@lglab/react-qr-code@1.4.10':
resolution: {integrity: sha512-B+VOFfvnx+NqVaJqDbh1glHEU/rJR/3XtINcvM3qC9DW292GbaLNoa2OvRlXHYUQdsWcdzNzOcPzT39P1O8uJg==}
peerDependencies:
react: ^18 || ^19
'@modelcontextprotocol/sdk@1.29.0': '@modelcontextprotocol/sdk@1.29.0':
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -8015,6 +8023,10 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
'@lglab/react-qr-code@1.4.10(react@19.2.5)':
dependencies:
react: 19.2.5
'@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)':
dependencies: dependencies:
'@hono/node-server': 1.19.13(hono@4.12.12) '@hono/node-server': 1.19.13(hono@4.12.12)