feat: add verifactu status management and enhance issued invoices UI components
This commit is contained in:
parent
19452c3187
commit
451a1013d0
@ -182,6 +182,7 @@ export class SequelizeIssuedInvoiceRepositoryV2
|
||||
reference: { type: "root", column: "reference" },
|
||||
description: { type: "root", column: "description" },
|
||||
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"],
|
||||
fullTextTableAlias: "IssuedInvoiceModel",
|
||||
|
||||
@ -292,6 +292,11 @@ export class IssuedInvoiceRepository
|
||||
type: "root",
|
||||
column: "recipient_name",
|
||||
},
|
||||
verifactu_status: {
|
||||
type: "association",
|
||||
association: "verifactu",
|
||||
column: "estado",
|
||||
},
|
||||
},
|
||||
sortableFields: [
|
||||
"recipient_name",
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
import { getUserErrorMessage } from "@erp/core/client";
|
||||
import { useDebounce } from "@erp/core/hooks";
|
||||
import { useDataSource, useDebounce } from "@erp/core/hooks";
|
||||
import { useQueries } from "@tanstack/react-query";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type {
|
||||
IssuedInvoiceList,
|
||||
IssuedInvoiceStatusCounts,
|
||||
ListIssuedInvoicesByCriteriaParams,
|
||||
VerifactuRecordStatus,
|
||||
} from "../../shared";
|
||||
import {
|
||||
EMPTY_ISSUED_INVOICE_STATUS_COUNTS,
|
||||
getListIssuedInvoicesByCriteria,
|
||||
} from "../../shared";
|
||||
import { useIssuedInvoiceListQuery } from "../../shared/";
|
||||
|
||||
type VerifactuRecordListStatusFilter = "all" | VerifactuRecordStatus;
|
||||
@ -17,9 +23,26 @@ const EMPTY_ISSUED_INVOICES_LIST: IssuedInvoiceList = {
|
||||
perPage: 5,
|
||||
totalPages: 0,
|
||||
totalItems: 0,
|
||||
statusCounts: EMPTY_ISSUED_INVOICE_STATUS_COUNTS,
|
||||
};
|
||||
|
||||
const STATUS_COUNT_OPTIONS: ReadonlyArray<{
|
||||
value: VerifactuRecordStatus;
|
||||
countKey: keyof Omit<IssuedInvoiceStatusCounts, "all">;
|
||||
}> = [
|
||||
{ value: "Pendiente", countKey: "pendiente" },
|
||||
{ value: "Correcto", countKey: "correcto" },
|
||||
{ value: "Aceptado con errores", countKey: "aceptadoConErrores" },
|
||||
{ value: "Incorrecto", countKey: "incorrecto" },
|
||||
{ value: "Duplicado", countKey: "duplicado" },
|
||||
{ value: "Anulado", countKey: "anulado" },
|
||||
{ value: "Factura inexistente", countKey: "facturaInexistente" },
|
||||
{ value: "No registrado", countKey: "rechazado" },
|
||||
{ value: "Error servidor AEAT", countKey: "errorServidorAeat" },
|
||||
];
|
||||
|
||||
export const useIssuedInvoiceListController = () => {
|
||||
const dataSource = useDataSource();
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
const [search, setSearch] = useState("");
|
||||
@ -38,14 +61,50 @@ export const useIssuedInvoiceListController = () => {
|
||||
filters:
|
||||
verifactuStatusFilter === "all"
|
||||
? []
|
||||
: [{ field: "verifactu.status", operator: "EQUALS", value: verifactuStatusFilter }],
|
||||
: [{ field: "verifactu_status", operator: "EQUALS", value: verifactuStatusFilter }],
|
||||
}),
|
||||
[debouncedSearch, pageIndex, pageSize, verifactuStatusFilter]
|
||||
);
|
||||
|
||||
const query = useIssuedInvoiceListQuery({ criteria });
|
||||
const statusCountQueries = useQueries({
|
||||
queries: STATUS_COUNT_OPTIONS.map((statusOption) => ({
|
||||
queryKey: ["issued_invoices", "status_count", debouncedSearch || "", statusOption.value],
|
||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||
const response = await getListIssuedInvoicesByCriteria(dataSource, {
|
||||
signal,
|
||||
criteria: {
|
||||
q: debouncedSearch || "",
|
||||
pageNumber: 0,
|
||||
pageSize: 1,
|
||||
filters: [
|
||||
{
|
||||
field: "verifactu_status",
|
||||
operator: "EQUALS",
|
||||
value: statusOption.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return response.total_items;
|
||||
},
|
||||
})),
|
||||
});
|
||||
|
||||
const errorMessage = query.error ? getUserErrorMessage(query.error) : null;
|
||||
const statusCounts = useMemo<IssuedInvoiceStatusCounts>(() => {
|
||||
const counts = {
|
||||
...EMPTY_ISSUED_INVOICE_STATUS_COUNTS,
|
||||
all: query.data?.totalItems ?? 0,
|
||||
};
|
||||
|
||||
STATUS_COUNT_OPTIONS.forEach((statusOption, index) => {
|
||||
counts[statusOption.countKey] = statusCountQueries[index]?.data ?? 0;
|
||||
});
|
||||
|
||||
return counts;
|
||||
}, [query.data?.totalItems, statusCountQueries]);
|
||||
|
||||
const setStatusFilterValue = (value: string) => {
|
||||
const nextValue = (value || "all") as VerifactuRecordListStatusFilter;
|
||||
@ -85,7 +144,10 @@ export const useIssuedInvoiceListController = () => {
|
||||
};
|
||||
|
||||
return {
|
||||
data: query.data ?? EMPTY_ISSUED_INVOICES_LIST,
|
||||
data: {
|
||||
...(query.data ?? EMPTY_ISSUED_INVOICES_LIST),
|
||||
statusCounts,
|
||||
},
|
||||
isLoading: query.isLoading,
|
||||
isFetching: query.isFetching,
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./issued-invoices-grid-empty";
|
||||
export * from "./issued-invoices-status-segmented-filter";
|
||||
export * from "./verifactu-status-badge";
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
import { Card, CardContent } from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import { ChartNoAxesColumnIncreasingIcon } from "lucide-react";
|
||||
|
||||
export const IssuedInvoicesEmptyGrid = ({ className }: React.ComponentProps<"div">) => {
|
||||
return (
|
||||
<Card className={cn("w-full max-w-lg", className)}>
|
||||
<CardContent>
|
||||
<div className="rounded-md border border-dashed p-6 text-center">
|
||||
<ChartNoAxesColumnIncreasingIcon className="text-muted-foreground mx-auto size-12" />
|
||||
<p className="mt-2 text-sm font-medium">No data to show</p>
|
||||
<p className="text-muted-foreground mt-1 text-sm">May take 15 minutes for data to load</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div
|
||||
className={cn(
|
||||
"rounded border border-dashed border-border bg-muted/30 px-4 py-10 text-center",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<ChartNoAxesColumnIncreasingIcon className="mx-auto size-12 text-muted-foreground" />
|
||||
<p className="mt-3 text-sm font-medium">Todavia no hay facturas emitidas para mostrar</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Cuando emitas proformas aprobadas, apareceran aqui con su estado en Verifactu.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,183 @@
|
||||
import { Badge, ToggleGroup, ToggleGroupItem } from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import {
|
||||
AlertTriangleIcon,
|
||||
BanIcon,
|
||||
BugIcon,
|
||||
CheckCheckIcon,
|
||||
CheckIcon,
|
||||
Clock3Icon,
|
||||
CopyIcon,
|
||||
FileQuestionIcon,
|
||||
XCircleIcon,
|
||||
} from "lucide-react";
|
||||
import type { ElementType } from "react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import type { IssuedInvoiceStatusCounts, VerifactuRecordStatus } from "../../../shared";
|
||||
|
||||
export type IssuedInvoiceStatusFilter = "all" | VerifactuRecordStatus;
|
||||
|
||||
export type IssuedInvoicesStatusSegmentedFilterProps = {
|
||||
value: IssuedInvoiceStatusFilter;
|
||||
onValueChange: (value: IssuedInvoiceStatusFilter) => void;
|
||||
counts: IssuedInvoiceStatusCounts;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const ISSUED_INVOICE_STATUS_FILTER_OPTIONS = [
|
||||
{
|
||||
value: "all",
|
||||
label: "Todas",
|
||||
icon: null,
|
||||
countKey: "all",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-primary data-[state=on]:bg-primary data-[state=on]:text-primary-foreground",
|
||||
},
|
||||
{
|
||||
value: "Pendiente",
|
||||
label: "Pendiente",
|
||||
icon: Clock3Icon,
|
||||
countKey: "pendiente",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-slate-300 data-[state=on]:bg-slate-100 data-[state=on]:text-slate-700",
|
||||
},
|
||||
{
|
||||
value: "Correcto",
|
||||
label: "Correcto",
|
||||
icon: CheckCheckIcon,
|
||||
countKey: "correcto",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-blue-300 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700",
|
||||
},
|
||||
{
|
||||
value: "Aceptado con errores",
|
||||
label: "Aceptado con errores",
|
||||
icon: CheckIcon,
|
||||
countKey: "aceptadoConErrores",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-amber-300 data-[state=on]:bg-amber-50 data-[state=on]:text-amber-700",
|
||||
},
|
||||
{
|
||||
value: "Incorrecto",
|
||||
label: "Incorrecto",
|
||||
icon: XCircleIcon,
|
||||
countKey: "incorrecto",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-red-300 data-[state=on]:bg-red-50 data-[state=on]:text-red-700",
|
||||
},
|
||||
{
|
||||
value: "Duplicado",
|
||||
label: "Duplicado",
|
||||
icon: CopyIcon,
|
||||
countKey: "duplicado",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-yellow-300 data-[state=on]:bg-yellow-50 data-[state=on]:text-yellow-700",
|
||||
},
|
||||
{
|
||||
value: "Anulado",
|
||||
label: "Anulado",
|
||||
icon: BanIcon,
|
||||
countKey: "anulado",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-emerald-300 data-[state=on]:bg-emerald-50 data-[state=on]:text-emerald-700",
|
||||
},
|
||||
{
|
||||
value: "Factura inexistente",
|
||||
label: "Factura inexistente",
|
||||
icon: FileQuestionIcon,
|
||||
countKey: "facturaInexistente",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-rose-300 data-[state=on]:bg-rose-50 data-[state=on]:text-rose-700",
|
||||
},
|
||||
{
|
||||
value: "No registrado",
|
||||
label: "No registrado",
|
||||
icon: AlertTriangleIcon,
|
||||
countKey: "rechazado",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-orange-300 data-[state=on]:bg-orange-50 data-[state=on]:text-orange-700",
|
||||
},
|
||||
{
|
||||
value: "Error servidor AEAT",
|
||||
label: "Error servidor AEAT",
|
||||
icon: BugIcon,
|
||||
countKey: "errorServidorAeat",
|
||||
activeClassName:
|
||||
"data-[state=on]:border-red-300 data-[state=on]:bg-red-50 data-[state=on]:text-red-700",
|
||||
},
|
||||
] as const satisfies readonly {
|
||||
value: IssuedInvoiceStatusFilter;
|
||||
label: string;
|
||||
icon: ElementType | null;
|
||||
countKey: keyof IssuedInvoiceStatusCounts;
|
||||
activeClassName: string;
|
||||
}[];
|
||||
|
||||
const isIssuedInvoiceStatusFilter = (value: string): value is IssuedInvoiceStatusFilter => {
|
||||
return ISSUED_INVOICE_STATUS_FILTER_OPTIONS.some((option) => option.value === value);
|
||||
};
|
||||
|
||||
export const IssuedInvoicesStatusSegmentedFilter = ({
|
||||
value,
|
||||
onValueChange,
|
||||
counts,
|
||||
disabled = false,
|
||||
}: IssuedInvoicesStatusSegmentedFilterProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleValueChange = (nextValue: string) => {
|
||||
if (!(nextValue && isIssuedInvoiceStatusFilter(nextValue)) || nextValue === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
onValueChange(nextValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<ToggleGroup
|
||||
aria-label={t("pages.issued_invoices.list.filters.status.label", "Filtrar por estado")}
|
||||
className="flex w-full flex-wrap justify-start gap-1"
|
||||
disabled={disabled}
|
||||
onValueChange={handleValueChange}
|
||||
size="sm"
|
||||
spacing={1}
|
||||
type="single"
|
||||
value={value}
|
||||
variant="outline"
|
||||
>
|
||||
{ISSUED_INVOICE_STATUS_FILTER_OPTIONS.map((option) => (
|
||||
<ToggleGroupItem
|
||||
className={cn(
|
||||
"group h-auto min-w-fit rounded-full border-transparent px-3 py-2 text-xs shadow-none transition-colors sm:text-sm",
|
||||
"text-muted-foreground hover:border-border hover:bg-muted/60 hover:text-foreground",
|
||||
"data-[state=on]:shadow-none",
|
||||
option.activeClassName
|
||||
)}
|
||||
key={option.value}
|
||||
onPointerDown={(event) => {
|
||||
if (option.value === value) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
value={option.value}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{option.icon ? <option.icon className="size-3.5" /> : null}
|
||||
<span>{t(`catalog.issued_invoices.status.${option.value.toLowerCase()}.label`, option.label)}</span>
|
||||
<Badge
|
||||
className={cn(
|
||||
"min-w-5 rounded-full px-1.5 text-[11px] font-semibold tabular-nums",
|
||||
"bg-muted text-muted-foreground",
|
||||
"group-data-[state=on]:bg-white/20 group-data-[state=on]:text-current"
|
||||
)}
|
||||
variant="secondary"
|
||||
>
|
||||
{counts[option.countKey]}
|
||||
</Badge>
|
||||
</span>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
);
|
||||
};
|
||||
@ -1,24 +1,17 @@
|
||||
import { ErrorState, PageHeader, SimpleSearchInput } from "@erp/core/components";
|
||||
import { LogoVerifactu } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
Button,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
import { FilterIcon, PlusIcon } from "lucide-react";
|
||||
import { Alert, AlertDescription, AlertTitle, Button } from "@repo/shadcn-ui/components";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import { useIssuedInvoiceListPageController } from "../../controllers";
|
||||
import { IssuedInvoicesGrid, useIssuedInvoicesGridColumns } from "../blocks";
|
||||
import { IssuedInvoicesEmptyGrid } from "../components";
|
||||
import {
|
||||
IssuedInvoicesEmptyGrid,
|
||||
IssuedInvoicesStatusSegmentedFilter,
|
||||
} from "../components";
|
||||
|
||||
export const ListIssuedInvoicesPage = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -96,77 +89,45 @@ export const ListIssuedInvoicesPage = () => {
|
||||
<LogoVerifactu className="text-green-800 w-50" color="" />
|
||||
</Alert>
|
||||
|
||||
{/* Search and filters */}
|
||||
{listCtrl.data.totalItems === 0 && <IssuedInvoicesEmptyGrid className="mx-auto" />}
|
||||
|
||||
{listCtrl.data.totalItems !== 0 && (
|
||||
<>
|
||||
<div className="min-h-0 flex-1 overflow-auto">
|
||||
<IssuedInvoicesGrid
|
||||
columns={columns}
|
||||
data={listCtrl.data}
|
||||
fetching={listCtrl.isFetching}
|
||||
loading={listCtrl.isLoading}
|
||||
onPageChange={listCtrl.setPageIndex}
|
||||
onPageSizeChange={listCtrl.setPageSize}
|
||||
// acciones rápidas del grid → page controller
|
||||
//onRowClick={(id) => navigate(`/issuedInvoices/${id}`)}
|
||||
pageIndex={listCtrl.pageIndex}
|
||||
pageSize={listCtrl.pageSize}
|
||||
searching={isSearchPending}
|
||||
topContent={
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="min-h-0 flex-1 overflow-auto">
|
||||
<IssuedInvoicesGrid
|
||||
columns={columns}
|
||||
data={listCtrl.data}
|
||||
fetching={listCtrl.isFetching}
|
||||
loading={listCtrl.isLoading}
|
||||
onPageChange={listCtrl.setPageIndex}
|
||||
onPageSizeChange={listCtrl.setPageSize}
|
||||
// acciones rápidas del grid → page controller
|
||||
//onRowClick={(id) => navigate(`/issuedInvoices/${id}`)}
|
||||
pageIndex={listCtrl.pageIndex}
|
||||
pageSize={listCtrl.pageSize}
|
||||
searching={isSearchPending}
|
||||
topContent={
|
||||
<div className="space-y-5">
|
||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div className="w-full">
|
||||
<SimpleSearchInput
|
||||
loading={listCtrl.isLoading}
|
||||
onPendingChange={setIsSearchPending}
|
||||
onSearchChange={listCtrl.setSearchValue}
|
||||
placeholder="Buscar por numero, cliente o CIF..."
|
||||
value={listCtrl.search}
|
||||
/>
|
||||
|
||||
<Select
|
||||
onValueChange={(value) => listCtrl.setStatusFilter(value ?? "all")}
|
||||
value={listCtrl.statusFilter}
|
||||
>
|
||||
<SelectTrigger className="w-full sm:w-48">
|
||||
<FilterIcon aria-hidden className="mr-2 size-4" />
|
||||
<SelectValue placeholder={t("filters.status")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">
|
||||
{t("catalog.issued_invoices.status.all.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="Correcto">
|
||||
{t("catalog.issued_invoices.status.correcto.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="Pendiente">
|
||||
{t("catalog.issued_invoices.status.pendiente.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="aceptado_con_error">
|
||||
{t("catalog.issued_invoices.status.aceptado_con_error.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="incorrecto">
|
||||
{t("catalog.issued_invoices.status.incorrecto.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="duplicado">
|
||||
{t("catalog.issued_invoices.status.duplicado.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="anulado">
|
||||
{t("catalog.issued_invoices.status.anulado.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="factura_inexistente">
|
||||
{t("catalog.issued_invoices.status.duplicado.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="rechazado">
|
||||
{t("catalog.issued_invoices.status.rechazado.label")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<IssuedInvoicesStatusSegmentedFilter
|
||||
counts={listCtrl.data.statusCounts}
|
||||
disabled={listCtrl.isLoading}
|
||||
onValueChange={listCtrl.setStatusFilter}
|
||||
value={listCtrl.statusFilter}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{listCtrl.data.totalItems === 0 ? <IssuedInvoicesEmptyGrid /> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@ import { MoneyDTOHelper } from "@erp/core";
|
||||
import { MoneyHelper } from "@repo/rdx-utils";
|
||||
|
||||
import type { ListIssuedInvoicesResponseDTO } from "../../../../common";
|
||||
import { EMPTY_ISSUED_INVOICE_STATUS_COUNTS } from "../entities";
|
||||
import type { IssuedInvoiceList, IssuedInvoiceListRow, IssuedInvoiceStatus } from "../entities";
|
||||
import type { VerifactuRecordStatus } from "../entities/verifactu-record-status.entity";
|
||||
|
||||
@ -13,6 +14,7 @@ export const ListIssuedInvoicesAdapter = {
|
||||
perPage: dto.per_page,
|
||||
totalPages: dto.total_pages,
|
||||
totalItems: dto.total_items,
|
||||
statusCounts: EMPTY_ISSUED_INVOICE_STATUS_COUNTS,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./issued-invoice-list.entity";
|
||||
export * from "./issued-invoice-list-row.entity";
|
||||
export * from "./issued-invoice-status.entity";
|
||||
export * from "./issued-invoice-status-counts.entity";
|
||||
export * from "./verifactu-record-status.entity";
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { IssuedInvoiceListRow } from "./issued-invoice-list-row.entity";
|
||||
import type { IssuedInvoiceStatusCounts } from "./issued-invoice-status-counts.entity";
|
||||
|
||||
/**
|
||||
* Interface que representa la respuesta paginada de una lista de proformas,
|
||||
@ -11,4 +12,5 @@ export interface IssuedInvoiceList {
|
||||
totalItems: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
statusCounts: IssuedInvoiceStatusCounts;
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
export interface IssuedInvoiceStatusCounts {
|
||||
all: number;
|
||||
pendiente: number;
|
||||
correcto: number;
|
||||
aceptadoConErrores: number;
|
||||
incorrecto: number;
|
||||
duplicado: number;
|
||||
anulado: number;
|
||||
facturaInexistente: number;
|
||||
rechazado: number;
|
||||
errorServidorAeat: number;
|
||||
}
|
||||
|
||||
export const EMPTY_ISSUED_INVOICE_STATUS_COUNTS: IssuedInvoiceStatusCounts = {
|
||||
all: 0,
|
||||
pendiente: 0,
|
||||
correcto: 0,
|
||||
aceptadoConErrores: 0,
|
||||
incorrecto: 0,
|
||||
duplicado: 0,
|
||||
anulado: 0,
|
||||
facturaInexistente: 0,
|
||||
rechazado: 0,
|
||||
errorServidorAeat: 0,
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user