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" },
|
||||
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",
|
||||
enableFullText: true,
|
||||
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 { 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 { useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
import type {
|
||||
IssuedInvoiceList,
|
||||
@ -13,12 +20,20 @@ import {
|
||||
getListIssuedInvoicesByCriteria,
|
||||
} 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;
|
||||
|
||||
const EMPTY_ISSUED_INVOICES_LIST: IssuedInvoiceList = {
|
||||
items: [],
|
||||
page: 0,
|
||||
page: INITIAL_PAGE_INDEX,
|
||||
perPage: 5,
|
||||
totalPages: 0,
|
||||
totalItems: 0,
|
||||
@ -42,27 +57,74 @@ const STATUS_COUNT_OPTIONS: ReadonlyArray<{
|
||||
|
||||
export const useIssuedInvoiceListController = () => {
|
||||
const dataSource = useDataSource();
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
const [search, setSearch] = useState("");
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [search, setSearch] = useState(searchParams.get("q") ?? "");
|
||||
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);
|
||||
|
||||
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"]>>(
|
||||
() => ({
|
||||
q: debouncedSearch || "",
|
||||
pageNumber: pageIndex,
|
||||
pageSize,
|
||||
order: "desc",
|
||||
orderBy: "invoice_date",
|
||||
order,
|
||||
orderBy,
|
||||
filters:
|
||||
verifactuStatusFilter === "all"
|
||||
? []
|
||||
: [{ field: "verifactu_status", operator: "EQUALS", value: verifactuStatusFilter }],
|
||||
}),
|
||||
[debouncedSearch, pageIndex, pageSize, verifactuStatusFilter]
|
||||
[debouncedSearch, order, orderBy, pageIndex, pageSize, verifactuStatusFilter]
|
||||
);
|
||||
|
||||
const query = useIssuedInvoiceListQuery({ criteria });
|
||||
@ -104,42 +166,86 @@ export const useIssuedInvoiceListController = () => {
|
||||
return counts;
|
||||
}, [query.data?.totalItems, statusCountQueries]);
|
||||
|
||||
const setStatusFilterValue = (value: string) => {
|
||||
const setStatusFilterValue = useCallback((value: string) => {
|
||||
const nextValue = (value || "all") as VerifactuRecordListStatusFilter;
|
||||
if (verifactuStatusFilter === nextValue) return;
|
||||
|
||||
setVerifactuStatusFilter((prev) => {
|
||||
if (prev === nextValue) return prev;
|
||||
setVerifactuStatusFilter(nextValue);
|
||||
setSearchParams((prev) => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.set("page", String(INITIAL_PAGE_INDEX + 1));
|
||||
|
||||
// Sólo si la búsqueda realmente cambia,
|
||||
// reseteamos la página a 0 para evitar inconsistencias
|
||||
setPageIndex(0);
|
||||
return nextValue;
|
||||
if (nextValue === "all") {
|
||||
params.delete("status");
|
||||
} else {
|
||||
params.set("status", nextValue);
|
||||
}
|
||||
|
||||
return params;
|
||||
});
|
||||
};
|
||||
}, [setSearchParams, verifactuStatusFilter]);
|
||||
|
||||
const setSearchValue = (value: string) => {
|
||||
const setSearchValue = useCallback((value: string) => {
|
||||
const nextValue = value.trim().replace(/\s+/g, " ");
|
||||
if (search === nextValue) return;
|
||||
|
||||
setSearch((prev) => {
|
||||
if (prev === nextValue) return prev;
|
||||
setSearch(nextValue);
|
||||
setSearchParams((prev) => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.set("page", String(INITIAL_PAGE_INDEX + 1));
|
||||
|
||||
// Sólo si la búsqueda realmente cambia,
|
||||
// reseteamos la página a 0 para evitar inconsistencias
|
||||
setPageIndex(0);
|
||||
return nextValue;
|
||||
if (nextValue) {
|
||||
params.set("q", nextValue);
|
||||
} else {
|
||||
params.delete("q");
|
||||
}
|
||||
|
||||
return params;
|
||||
});
|
||||
};
|
||||
}, [search, setSearchParams]);
|
||||
|
||||
const setPageSizeValue = (value: number) => {
|
||||
setPageSize((prev) => {
|
||||
if (prev === value) return prev;
|
||||
const setPageIndexValue = useCallback((newPageIndex: number) => {
|
||||
const nextPage = newPageIndex + 1;
|
||||
|
||||
// Sólo si el tamaño de página realmente cambia,
|
||||
// reseteamos la página a 0 para evitar inconsistencias
|
||||
setPageIndex(0);
|
||||
return value;
|
||||
setSearchParams((prev) => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.set("page", String(nextPage));
|
||||
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 {
|
||||
queryData: query.data,
|
||||
@ -155,14 +261,18 @@ export const useIssuedInvoiceListController = () => {
|
||||
error: query.error,
|
||||
refetch: query.refetch,
|
||||
|
||||
tablePreferences,
|
||||
|
||||
pageIndex,
|
||||
pageSize,
|
||||
setPageIndex,
|
||||
setPageIndex: setPageIndexValue,
|
||||
setPageSize: setPageSizeValue,
|
||||
|
||||
search,
|
||||
setSearchValue,
|
||||
|
||||
sort,
|
||||
setSort: setSortValue,
|
||||
statusFilter: verifactuStatusFilter,
|
||||
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 * as React from "react";
|
||||
|
||||
@ -19,6 +23,9 @@ interface IssuedInvoicesGridProps {
|
||||
onPageChange: (pageIndex: number) => void;
|
||||
onPageSizeChange: (size: number) => void;
|
||||
|
||||
sort: DataTableSort;
|
||||
onSortChange?: (nextSort: DataTableSort) => void;
|
||||
|
||||
onRowClick?: (proformaId: string) => void;
|
||||
}
|
||||
|
||||
@ -33,6 +40,8 @@ export const IssuedInvoicesGrid = ({
|
||||
pageSize,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
sort,
|
||||
onSortChange,
|
||||
onRowClick,
|
||||
}: IssuedInvoicesGridProps) => {
|
||||
const { t } = useTranslation();
|
||||
@ -109,11 +118,14 @@ export const IssuedInvoicesGrid = ({
|
||||
isFetching={fetching}
|
||||
isSearching={searching}
|
||||
manualPagination
|
||||
manualSorting
|
||||
onPageChange={onPageChange}
|
||||
onPageSizeChange={onPageSizeChange}
|
||||
onSortChange={onSortChange}
|
||||
onRowClick={onRowClick ? (row) => onRowClick(row.id) : undefined}
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
sort={sort}
|
||||
topContent={topContent}
|
||||
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º
|
||||
{
|
||||
id: "invoiceReference",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader
|
||||
className="text-right tabular-nums justify-end"
|
||||
column={column}
|
||||
title={t("pages.issued_invoices.list.grid_columns.invoice_number")}
|
||||
title={t("pages.issued_invoices.list.grid_columns.invoice_reference")}
|
||||
/>
|
||||
),
|
||||
accessorKey: "invoiceReference",
|
||||
/*cell: ({ row }) => (
|
||||
<div className="text-right tabular-nums">
|
||||
{row.original.series}
|
||||
{row.original.invoiceNumber}
|
||||
</div>
|
||||
),*/
|
||||
cell: ({ row }) => (
|
||||
<div className="text-right tabular-nums">{row.original.invoiceReference}</div>
|
||||
),
|
||||
enableHiding: false,
|
||||
enableSorting: true,
|
||||
/*maxSize: 96,
|
||||
size: 96,
|
||||
minSize: 96,*/
|
||||
meta: {
|
||||
title: t("pages.issued_invoices.list.grid_columns.invoice_reference"),
|
||||
cellClassName: "font-mono text-sm font-medium",
|
||||
@ -192,7 +187,8 @@ export function useIssuedInvoicesGridColumns(
|
||||
|
||||
// Fecha factura
|
||||
{
|
||||
accessorKey: "invoice_date",
|
||||
id: "invoiceDate",
|
||||
accessorKey: "invoiceDate",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader
|
||||
className="text-left tabular-nums"
|
||||
@ -205,7 +201,7 @@ export function useIssuedInvoicesGridColumns(
|
||||
{DateHelper.format(row.original.invoiceDate)}
|
||||
</div>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableSorting: true,
|
||||
size: 140,
|
||||
minSize: 120,
|
||||
meta: {
|
||||
|
||||
@ -143,10 +143,12 @@ export const ListIssuedInvoicesPage = () => {
|
||||
loading={listCtrl.isLoading}
|
||||
onPageChange={listCtrl.setPageIndex}
|
||||
onPageSizeChange={listCtrl.setPageSize}
|
||||
onSortChange={listCtrl.setSort}
|
||||
onRowClick={handleOpenDetail}
|
||||
pageIndex={listCtrl.pageIndex}
|
||||
pageSize={listCtrl.pageSize}
|
||||
searching={isSearchPending}
|
||||
sort={listCtrl.sort}
|
||||
topContent={
|
||||
<div className="space-y-5">
|
||||
<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,
|
||||
companyId: dto.company_id,
|
||||
|
||||
invoiceReference: dto.invoice_reference,
|
||||
invoiceNumber: dto.invoice_number,
|
||||
status: dto.status as IssuedInvoiceStatus,
|
||||
series: dto.series,
|
||||
|
||||
@ -13,6 +13,7 @@ export interface IssuedInvoiceListRow {
|
||||
id: string;
|
||||
companyId: string;
|
||||
|
||||
invoiceReference: string;
|
||||
invoiceNumber: string;
|
||||
status: IssuedInvoiceStatus;
|
||||
series: string;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user