feat: add sorting functionality and new fields for issued invoices
This commit is contained in:
parent
9c1f98df0c
commit
bd630b29dd
@ -185,7 +185,15 @@ export class SequelizeIssuedInvoiceRepositoryV2
|
|||||||
recipient_name: { type: "root", column: "customer_name" },
|
recipient_name: { type: "root", column: "customer_name" },
|
||||||
verifactu_status: { type: "association", association: "verifactu", column: "estado" },
|
verifactu_status: { type: "association", association: "verifactu", column: "estado" },
|
||||||
},
|
},
|
||||||
sortableFields: ["recipient_name", "invoice_number", "invoice_date", "id", "created_at", "serie"],
|
sortableFields: [
|
||||||
|
"recipient_name",
|
||||||
|
"invoice_number",
|
||||||
|
"invoice_reference",
|
||||||
|
"invoice_date",
|
||||||
|
"id",
|
||||||
|
"created_at",
|
||||||
|
"serie",
|
||||||
|
],
|
||||||
fullTextTableAlias: "IssuedInvoiceModel",
|
fullTextTableAlias: "IssuedInvoiceModel",
|
||||||
enableFullText: true,
|
enableFullText: true,
|
||||||
database: this.database,
|
database: this.database,
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_ISSUED_INVOICE_API_SORT_FIELD,
|
||||||
|
DEFAULT_ISSUED_INVOICE_LIST_SORT,
|
||||||
|
ISSUED_INVOICE_LIST_SORT_FIELDS,
|
||||||
|
isIssuedInvoiceListSortField,
|
||||||
|
isSortDirection,
|
||||||
|
} from "./issued-invoice-list-sorting";
|
||||||
|
|
||||||
|
test("mantiene el mismo contrato semántico de ordenación que usa el grid", () => {
|
||||||
|
assert.deepEqual(ISSUED_INVOICE_LIST_SORT_FIELDS, {
|
||||||
|
invoiceReference: "invoice_reference",
|
||||||
|
recipientName: "recipient_name",
|
||||||
|
invoiceDate: "invoice_date",
|
||||||
|
});
|
||||||
|
assert.deepEqual(DEFAULT_ISSUED_INVOICE_LIST_SORT, {
|
||||||
|
field: "invoiceDate",
|
||||||
|
direction: "desc",
|
||||||
|
});
|
||||||
|
assert.equal(DEFAULT_ISSUED_INVOICE_API_SORT_FIELD, "invoice_date");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("solo acepta campos y direcciones de orden válidos", () => {
|
||||||
|
assert.equal(isIssuedInvoiceListSortField("invoiceReference"), true);
|
||||||
|
assert.equal(isIssuedInvoiceListSortField("invoiceDate"), true);
|
||||||
|
assert.equal(isIssuedInvoiceListSortField("invoice_reference"), false);
|
||||||
|
assert.equal(isIssuedInvoiceListSortField(null), false);
|
||||||
|
|
||||||
|
assert.equal(isSortDirection("asc"), true);
|
||||||
|
assert.equal(isSortDirection("desc"), true);
|
||||||
|
assert.equal(isSortDirection("DESC"), false);
|
||||||
|
assert.equal(isSortDirection(null), false);
|
||||||
|
});
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import type { DataTableSort, DataTableSortDirection } from "@repo/rdx-ui/components";
|
||||||
|
|
||||||
|
export const ISSUED_INVOICE_LIST_SORT_FIELDS = {
|
||||||
|
invoiceReference: "invoice_reference",
|
||||||
|
recipientName: "recipient_name",
|
||||||
|
invoiceDate: "invoice_date",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type IssuedInvoiceListSortField = keyof typeof ISSUED_INVOICE_LIST_SORT_FIELDS;
|
||||||
|
|
||||||
|
export const DEFAULT_ISSUED_INVOICE_LIST_SORT = {
|
||||||
|
field: "invoiceDate",
|
||||||
|
direction: "desc",
|
||||||
|
} satisfies DataTableSort;
|
||||||
|
|
||||||
|
export const DEFAULT_ISSUED_INVOICE_API_SORT_FIELD = ISSUED_INVOICE_LIST_SORT_FIELDS.invoiceDate;
|
||||||
|
|
||||||
|
export const isIssuedInvoiceListSortField = (
|
||||||
|
value: string | null
|
||||||
|
): value is IssuedInvoiceListSortField => {
|
||||||
|
return value !== null && value in ISSUED_INVOICE_LIST_SORT_FIELDS;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isSortDirection = (value: string | null): value is DataTableSortDirection => {
|
||||||
|
return value === "asc" || value === "desc";
|
||||||
|
};
|
||||||
@ -1,6 +1,13 @@
|
|||||||
import { useDataSource, useDebounce } from "@erp/core/hooks";
|
import { useDataSource, useDebounce } from "@erp/core/hooks";
|
||||||
|
import { INITIAL_PAGE_INDEX } from "@repo/rdx-criteria";
|
||||||
|
import {
|
||||||
|
type DataTableSort,
|
||||||
|
useDataTablePreferences,
|
||||||
|
} from "@repo/rdx-ui/components";
|
||||||
|
import { NumberHelper } from "@repo/rdx-utils";
|
||||||
import { useQueries } from "@tanstack/react-query";
|
import { useQueries } from "@tanstack/react-query";
|
||||||
import { useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
IssuedInvoiceList,
|
IssuedInvoiceList,
|
||||||
@ -13,12 +20,20 @@ import {
|
|||||||
getListIssuedInvoicesByCriteria,
|
getListIssuedInvoicesByCriteria,
|
||||||
} from "../../shared";
|
} from "../../shared";
|
||||||
import { useIssuedInvoiceListQuery } from "../../shared/";
|
import { useIssuedInvoiceListQuery } from "../../shared/";
|
||||||
|
import {
|
||||||
|
DEFAULT_ISSUED_INVOICE_API_SORT_FIELD,
|
||||||
|
DEFAULT_ISSUED_INVOICE_LIST_SORT,
|
||||||
|
type IssuedInvoiceListSortField,
|
||||||
|
ISSUED_INVOICE_LIST_SORT_FIELDS,
|
||||||
|
isIssuedInvoiceListSortField,
|
||||||
|
isSortDirection,
|
||||||
|
} from "./issued-invoice-list-sorting";
|
||||||
|
|
||||||
type VerifactuRecordListStatusFilter = "all" | VerifactuRecordStatus;
|
type VerifactuRecordListStatusFilter = "all" | VerifactuRecordStatus;
|
||||||
|
|
||||||
const EMPTY_ISSUED_INVOICES_LIST: IssuedInvoiceList = {
|
const EMPTY_ISSUED_INVOICES_LIST: IssuedInvoiceList = {
|
||||||
items: [],
|
items: [],
|
||||||
page: 0,
|
page: INITIAL_PAGE_INDEX,
|
||||||
perPage: 5,
|
perPage: 5,
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
totalItems: 0,
|
totalItems: 0,
|
||||||
@ -42,27 +57,74 @@ const STATUS_COUNT_OPTIONS: ReadonlyArray<{
|
|||||||
|
|
||||||
export const useIssuedInvoiceListController = () => {
|
export const useIssuedInvoiceListController = () => {
|
||||||
const dataSource = useDataSource();
|
const dataSource = useDataSource();
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [pageSize, setPageSize] = useState(5);
|
const [search, setSearch] = useState(searchParams.get("q") ?? "");
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const [verifactuStatusFilter, setVerifactuStatusFilter] =
|
const [verifactuStatusFilter, setVerifactuStatusFilter] =
|
||||||
useState<VerifactuRecordListStatusFilter>("all");
|
useState<VerifactuRecordListStatusFilter>(
|
||||||
|
(searchParams.get("status") as VerifactuRecordListStatusFilter | null) ?? "all"
|
||||||
|
);
|
||||||
|
|
||||||
|
const tablePreferences = useDataTablePreferences({
|
||||||
|
storageKey: "issued-invoices:list:grid",
|
||||||
|
defaultPageSize: EMPTY_ISSUED_INVOICES_LIST.perPage,
|
||||||
|
defaultSort: DEFAULT_ISSUED_INVOICE_LIST_SORT,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { pageSize: preferencesPageSize, setPageSize: setPageSizePreference } = tablePreferences;
|
||||||
|
const { sort: preferencesSorting, setSort: setSortPreference } = tablePreferences;
|
||||||
|
|
||||||
|
const urlPage = NumberHelper.parsePositiveInteger(
|
||||||
|
searchParams.get("page"),
|
||||||
|
INITIAL_PAGE_INDEX + 1
|
||||||
|
);
|
||||||
|
const pageIndex = urlPage - 1;
|
||||||
|
|
||||||
|
const urlPageSize = searchParams.get("pageSize");
|
||||||
|
const pageSize = urlPageSize
|
||||||
|
? NumberHelper.parsePositiveInteger(urlPageSize, preferencesPageSize)
|
||||||
|
: preferencesPageSize;
|
||||||
|
|
||||||
const debouncedSearch = useDebounce(search, 300);
|
const debouncedSearch = useDebounce(search, 300);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearch(searchParams.get("q") ?? "");
|
||||||
|
setVerifactuStatusFilter(
|
||||||
|
(searchParams.get("status") as VerifactuRecordListStatusFilter | null) ?? "all"
|
||||||
|
);
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
const urlSortFieldValue = searchParams.get("sortField");
|
||||||
|
const urlSortDirectionValue = searchParams.get("sortDirection");
|
||||||
|
|
||||||
|
const urlSort =
|
||||||
|
isIssuedInvoiceListSortField(urlSortFieldValue) && isSortDirection(urlSortDirectionValue)
|
||||||
|
? {
|
||||||
|
field: urlSortFieldValue,
|
||||||
|
direction: urlSortDirectionValue,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const sort = urlSort ?? preferencesSorting ?? DEFAULT_ISSUED_INVOICE_LIST_SORT;
|
||||||
|
|
||||||
|
const orderBy =
|
||||||
|
ISSUED_INVOICE_LIST_SORT_FIELDS[sort.field as IssuedInvoiceListSortField] ??
|
||||||
|
DEFAULT_ISSUED_INVOICE_API_SORT_FIELD;
|
||||||
|
|
||||||
|
const order = sort.direction;
|
||||||
|
|
||||||
const criteria = useMemo<NonNullable<ListIssuedInvoicesByCriteriaParams["criteria"]>>(
|
const criteria = useMemo<NonNullable<ListIssuedInvoicesByCriteriaParams["criteria"]>>(
|
||||||
() => ({
|
() => ({
|
||||||
q: debouncedSearch || "",
|
q: debouncedSearch || "",
|
||||||
pageNumber: pageIndex,
|
pageNumber: pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
order: "desc",
|
order,
|
||||||
orderBy: "invoice_date",
|
orderBy,
|
||||||
filters:
|
filters:
|
||||||
verifactuStatusFilter === "all"
|
verifactuStatusFilter === "all"
|
||||||
? []
|
? []
|
||||||
: [{ field: "verifactu_status", operator: "EQUALS", value: verifactuStatusFilter }],
|
: [{ field: "verifactu_status", operator: "EQUALS", value: verifactuStatusFilter }],
|
||||||
}),
|
}),
|
||||||
[debouncedSearch, pageIndex, pageSize, verifactuStatusFilter]
|
[debouncedSearch, order, orderBy, pageIndex, pageSize, verifactuStatusFilter]
|
||||||
);
|
);
|
||||||
|
|
||||||
const query = useIssuedInvoiceListQuery({ criteria });
|
const query = useIssuedInvoiceListQuery({ criteria });
|
||||||
@ -104,42 +166,86 @@ export const useIssuedInvoiceListController = () => {
|
|||||||
return counts;
|
return counts;
|
||||||
}, [query.data?.totalItems, statusCountQueries]);
|
}, [query.data?.totalItems, statusCountQueries]);
|
||||||
|
|
||||||
const setStatusFilterValue = (value: string) => {
|
const setStatusFilterValue = useCallback((value: string) => {
|
||||||
const nextValue = (value || "all") as VerifactuRecordListStatusFilter;
|
const nextValue = (value || "all") as VerifactuRecordListStatusFilter;
|
||||||
|
if (verifactuStatusFilter === nextValue) return;
|
||||||
|
|
||||||
setVerifactuStatusFilter((prev) => {
|
setVerifactuStatusFilter(nextValue);
|
||||||
if (prev === nextValue) return prev;
|
setSearchParams((prev) => {
|
||||||
|
const params = new URLSearchParams(prev);
|
||||||
|
params.set("page", String(INITIAL_PAGE_INDEX + 1));
|
||||||
|
|
||||||
// Sólo si la búsqueda realmente cambia,
|
if (nextValue === "all") {
|
||||||
// reseteamos la página a 0 para evitar inconsistencias
|
params.delete("status");
|
||||||
setPageIndex(0);
|
} else {
|
||||||
return nextValue;
|
params.set("status", nextValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
});
|
});
|
||||||
};
|
}, [setSearchParams, verifactuStatusFilter]);
|
||||||
|
|
||||||
const setSearchValue = (value: string) => {
|
const setSearchValue = useCallback((value: string) => {
|
||||||
const nextValue = value.trim().replace(/\s+/g, " ");
|
const nextValue = value.trim().replace(/\s+/g, " ");
|
||||||
|
if (search === nextValue) return;
|
||||||
|
|
||||||
setSearch((prev) => {
|
setSearch(nextValue);
|
||||||
if (prev === nextValue) return prev;
|
setSearchParams((prev) => {
|
||||||
|
const params = new URLSearchParams(prev);
|
||||||
|
params.set("page", String(INITIAL_PAGE_INDEX + 1));
|
||||||
|
|
||||||
// Sólo si la búsqueda realmente cambia,
|
if (nextValue) {
|
||||||
// reseteamos la página a 0 para evitar inconsistencias
|
params.set("q", nextValue);
|
||||||
setPageIndex(0);
|
} else {
|
||||||
return nextValue;
|
params.delete("q");
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
});
|
});
|
||||||
};
|
}, [search, setSearchParams]);
|
||||||
|
|
||||||
const setPageSizeValue = (value: number) => {
|
const setPageIndexValue = useCallback((newPageIndex: number) => {
|
||||||
setPageSize((prev) => {
|
const nextPage = newPageIndex + 1;
|
||||||
if (prev === value) return prev;
|
|
||||||
|
|
||||||
// Sólo si el tamaño de página realmente cambia,
|
setSearchParams((prev) => {
|
||||||
// reseteamos la página a 0 para evitar inconsistencias
|
const params = new URLSearchParams(prev);
|
||||||
setPageIndex(0);
|
params.set("page", String(nextPage));
|
||||||
return value;
|
return params;
|
||||||
});
|
});
|
||||||
};
|
}, [setSearchParams]);
|
||||||
|
|
||||||
|
const setPageSizeValue = useCallback((value: number) => {
|
||||||
|
if (pageSize === value) return;
|
||||||
|
|
||||||
|
setSearchParams((prev) => {
|
||||||
|
const params = new URLSearchParams(prev);
|
||||||
|
params.set("page", String(INITIAL_PAGE_INDEX + 1));
|
||||||
|
params.set("pageSize", String(value));
|
||||||
|
return params;
|
||||||
|
});
|
||||||
|
|
||||||
|
setPageSizePreference(value);
|
||||||
|
}, [pageSize, setPageSizePreference, setSearchParams]);
|
||||||
|
|
||||||
|
const setSortValue = useCallback((nextSort: DataTableSort) => {
|
||||||
|
if (!isIssuedInvoiceListSortField(nextSort.field)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sort.field === nextSort.field && sort.direction === nextSort.direction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchParams((prev) => {
|
||||||
|
const params = new URLSearchParams(prev);
|
||||||
|
params.set("sortField", nextSort.field);
|
||||||
|
params.set("sortDirection", nextSort.direction);
|
||||||
|
params.set("page", String(INITIAL_PAGE_INDEX + 1));
|
||||||
|
return params;
|
||||||
|
});
|
||||||
|
|
||||||
|
setSortPreference(nextSort);
|
||||||
|
}, [setSearchParams, setSortPreference, sort.direction, sort.field]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryData: query.data,
|
queryData: query.data,
|
||||||
@ -155,14 +261,18 @@ export const useIssuedInvoiceListController = () => {
|
|||||||
error: query.error,
|
error: query.error,
|
||||||
refetch: query.refetch,
|
refetch: query.refetch,
|
||||||
|
|
||||||
|
tablePreferences,
|
||||||
|
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
setPageIndex,
|
setPageIndex: setPageIndexValue,
|
||||||
setPageSize: setPageSizeValue,
|
setPageSize: setPageSizeValue,
|
||||||
|
|
||||||
search,
|
search,
|
||||||
setSearchValue,
|
setSearchValue,
|
||||||
|
|
||||||
|
sort,
|
||||||
|
setSort: setSortValue,
|
||||||
statusFilter: verifactuStatusFilter,
|
statusFilter: verifactuStatusFilter,
|
||||||
setStatusFilter: setStatusFilterValue,
|
setStatusFilter: setStatusFilterValue,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components";
|
import {
|
||||||
|
DataTable,
|
||||||
|
type DataTableSort,
|
||||||
|
SkeletonDataTable,
|
||||||
|
} from "@repo/rdx-ui/components";
|
||||||
import type { ColumnDef } from "@tanstack/react-table";
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
@ -19,6 +23,9 @@ interface IssuedInvoicesGridProps {
|
|||||||
onPageChange: (pageIndex: number) => void;
|
onPageChange: (pageIndex: number) => void;
|
||||||
onPageSizeChange: (size: number) => void;
|
onPageSizeChange: (size: number) => void;
|
||||||
|
|
||||||
|
sort: DataTableSort;
|
||||||
|
onSortChange?: (nextSort: DataTableSort) => void;
|
||||||
|
|
||||||
onRowClick?: (proformaId: string) => void;
|
onRowClick?: (proformaId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +40,8 @@ export const IssuedInvoicesGrid = ({
|
|||||||
pageSize,
|
pageSize,
|
||||||
onPageChange,
|
onPageChange,
|
||||||
onPageSizeChange,
|
onPageSizeChange,
|
||||||
|
sort,
|
||||||
|
onSortChange,
|
||||||
onRowClick,
|
onRowClick,
|
||||||
}: IssuedInvoicesGridProps) => {
|
}: IssuedInvoicesGridProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -109,11 +118,14 @@ export const IssuedInvoicesGrid = ({
|
|||||||
isFetching={fetching}
|
isFetching={fetching}
|
||||||
isSearching={searching}
|
isSearching={searching}
|
||||||
manualPagination
|
manualPagination
|
||||||
|
manualSorting
|
||||||
onPageChange={onPageChange}
|
onPageChange={onPageChange}
|
||||||
onPageSizeChange={onPageSizeChange}
|
onPageSizeChange={onPageSizeChange}
|
||||||
|
onSortChange={onSortChange}
|
||||||
onRowClick={onRowClick ? (row) => onRowClick(row.id) : undefined}
|
onRowClick={onRowClick ? (row) => onRowClick(row.id) : undefined}
|
||||||
pageIndex={pageIndex}
|
pageIndex={pageIndex}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
|
sort={sort}
|
||||||
topContent={topContent}
|
topContent={topContent}
|
||||||
totalItems={totalItems}
|
totalItems={totalItems}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import { renderToStaticMarkup } from "react-dom/server";
|
||||||
|
|
||||||
|
import { useIssuedInvoicesGridColumns } from "./use-issued-invoices-grid-columns";
|
||||||
|
|
||||||
|
const getColumns = () => {
|
||||||
|
let columns: ReturnType<typeof useIssuedInvoicesGridColumns> = [];
|
||||||
|
|
||||||
|
const Harness = () => {
|
||||||
|
columns = useIssuedInvoicesGridColumns();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderToStaticMarkup(<Harness />);
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
test("define un id estable y sortable para invoiceReference", () => {
|
||||||
|
const columns = getColumns();
|
||||||
|
const invoiceReferenceColumn = columns.find((column) => column.id === "invoiceReference");
|
||||||
|
const invoiceDateColumn = columns.find((column) => column.id === "invoiceDate");
|
||||||
|
|
||||||
|
assert.ok(invoiceReferenceColumn);
|
||||||
|
assert.equal(invoiceReferenceColumn.enableSorting, true);
|
||||||
|
assert.equal(invoiceReferenceColumn.accessorKey, "invoiceReference");
|
||||||
|
|
||||||
|
assert.ok(invoiceDateColumn);
|
||||||
|
assert.equal(invoiceDateColumn.enableSorting, true);
|
||||||
|
assert.equal(invoiceDateColumn.accessorKey, "invoiceDate");
|
||||||
|
});
|
||||||
@ -33,25 +33,20 @@ export function useIssuedInvoicesGridColumns(
|
|||||||
() => [
|
() => [
|
||||||
// Nº
|
// Nº
|
||||||
{
|
{
|
||||||
|
id: "invoiceReference",
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader
|
<DataTableColumnHeader
|
||||||
className="text-right tabular-nums justify-end"
|
className="text-right tabular-nums justify-end"
|
||||||
column={column}
|
column={column}
|
||||||
title={t("pages.issued_invoices.list.grid_columns.invoice_number")}
|
title={t("pages.issued_invoices.list.grid_columns.invoice_reference")}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
accessorKey: "invoiceReference",
|
accessorKey: "invoiceReference",
|
||||||
/*cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="text-right tabular-nums">
|
<div className="text-right tabular-nums">{row.original.invoiceReference}</div>
|
||||||
{row.original.series}
|
),
|
||||||
{row.original.invoiceNumber}
|
|
||||||
</div>
|
|
||||||
),*/
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
/*maxSize: 96,
|
|
||||||
size: 96,
|
|
||||||
minSize: 96,*/
|
|
||||||
meta: {
|
meta: {
|
||||||
title: t("pages.issued_invoices.list.grid_columns.invoice_reference"),
|
title: t("pages.issued_invoices.list.grid_columns.invoice_reference"),
|
||||||
cellClassName: "font-mono text-sm font-medium",
|
cellClassName: "font-mono text-sm font-medium",
|
||||||
@ -192,7 +187,8 @@ export function useIssuedInvoicesGridColumns(
|
|||||||
|
|
||||||
// Fecha factura
|
// Fecha factura
|
||||||
{
|
{
|
||||||
accessorKey: "invoice_date",
|
id: "invoiceDate",
|
||||||
|
accessorKey: "invoiceDate",
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader
|
<DataTableColumnHeader
|
||||||
className="text-left tabular-nums"
|
className="text-left tabular-nums"
|
||||||
@ -205,7 +201,7 @@ export function useIssuedInvoicesGridColumns(
|
|||||||
{DateHelper.format(row.original.invoiceDate)}
|
{DateHelper.format(row.original.invoiceDate)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
enableSorting: true,
|
||||||
size: 140,
|
size: 140,
|
||||||
minSize: 120,
|
minSize: 120,
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@ -143,10 +143,12 @@ export const ListIssuedInvoicesPage = () => {
|
|||||||
loading={listCtrl.isLoading}
|
loading={listCtrl.isLoading}
|
||||||
onPageChange={listCtrl.setPageIndex}
|
onPageChange={listCtrl.setPageIndex}
|
||||||
onPageSizeChange={listCtrl.setPageSize}
|
onPageSizeChange={listCtrl.setPageSize}
|
||||||
|
onSortChange={listCtrl.setSort}
|
||||||
onRowClick={handleOpenDetail}
|
onRowClick={handleOpenDetail}
|
||||||
pageIndex={listCtrl.pageIndex}
|
pageIndex={listCtrl.pageIndex}
|
||||||
pageSize={listCtrl.pageSize}
|
pageSize={listCtrl.pageSize}
|
||||||
searching={isSearchPending}
|
searching={isSearchPending}
|
||||||
|
sort={listCtrl.sort}
|
||||||
topContent={
|
topContent={
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||||
|
|||||||
@ -0,0 +1,64 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import { MoneyDTOHelper } from "@erp/core";
|
||||||
|
|
||||||
|
import type { ListIssuedInvoicesResponseDTO } from "../../../../common";
|
||||||
|
import { ListIssuedInvoicesAdapter } from "./list-issued-invoice.adapter";
|
||||||
|
|
||||||
|
const buildDto = (): ListIssuedInvoicesResponseDTO => ({
|
||||||
|
page: 0,
|
||||||
|
per_page: 5,
|
||||||
|
total_pages: 1,
|
||||||
|
total_items: 1,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "invoice-1",
|
||||||
|
company_id: "company-1",
|
||||||
|
document_series_id: "series-1",
|
||||||
|
invoice_series_code: "A",
|
||||||
|
invoice_number: "12",
|
||||||
|
invoice_reference: "A-12",
|
||||||
|
status: "issued",
|
||||||
|
series: "A",
|
||||||
|
invoice_date: "2026-08-10",
|
||||||
|
operation_date: "2026-08-10",
|
||||||
|
language_code: "es-ES",
|
||||||
|
currency_code: "EUR",
|
||||||
|
reference: "REF-12",
|
||||||
|
description: "Factura",
|
||||||
|
customer_id: "customer-1",
|
||||||
|
recipient: {
|
||||||
|
id: "recipient-1",
|
||||||
|
name: "Acme Labs",
|
||||||
|
tin: "B12345678",
|
||||||
|
street: "Calle Mayor 1",
|
||||||
|
street2: null,
|
||||||
|
city: "Madrid",
|
||||||
|
province: "Madrid",
|
||||||
|
postal_code: "28001",
|
||||||
|
country: "ES",
|
||||||
|
},
|
||||||
|
subtotal_amount: MoneyDTOHelper.fromNumber(100, "EUR"),
|
||||||
|
total_discount_amount: MoneyDTOHelper.fromNumber(5, "EUR"),
|
||||||
|
taxable_amount: MoneyDTOHelper.fromNumber(95, "EUR"),
|
||||||
|
taxes_amount: MoneyDTOHelper.fromNumber(19.95, "EUR"),
|
||||||
|
total_amount: MoneyDTOHelper.fromNumber(114.95, "EUR"),
|
||||||
|
verifactu: {
|
||||||
|
status: "Correcto",
|
||||||
|
url: "https://example.com/verifactu/A-12",
|
||||||
|
qr_code: "qr-code",
|
||||||
|
},
|
||||||
|
linked_proforma_id: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
test("expone invoiceReference en las filas del listado para el grid y la ordenación", () => {
|
||||||
|
const result = ListIssuedInvoicesAdapter.fromDto(buildDto());
|
||||||
|
|
||||||
|
assert.equal(result.items[0]?.invoiceReference, "A-12");
|
||||||
|
assert.equal(result.items[0]?.invoiceNumber, "12");
|
||||||
|
assert.equal(result.items[0]?.recipient.name, "Acme Labs");
|
||||||
|
assert.equal(result.items[0]?.totalAmountFmt.length > 0, true);
|
||||||
|
});
|
||||||
@ -27,6 +27,7 @@ const IssuedInvoiceListRowAdapter = {
|
|||||||
id: dto.id,
|
id: dto.id,
|
||||||
companyId: dto.company_id,
|
companyId: dto.company_id,
|
||||||
|
|
||||||
|
invoiceReference: dto.invoice_reference,
|
||||||
invoiceNumber: dto.invoice_number,
|
invoiceNumber: dto.invoice_number,
|
||||||
status: dto.status as IssuedInvoiceStatus,
|
status: dto.status as IssuedInvoiceStatus,
|
||||||
series: dto.series,
|
series: dto.series,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export interface IssuedInvoiceListRow {
|
|||||||
id: string;
|
id: string;
|
||||||
companyId: string;
|
companyId: string;
|
||||||
|
|
||||||
|
invoiceReference: string;
|
||||||
invoiceNumber: string;
|
invoiceNumber: string;
|
||||||
status: IssuedInvoiceStatus;
|
status: IssuedInvoiceStatus;
|
||||||
series: string;
|
series: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user