feat: Enhance invoice management with operation and invoice type filters, update dialog defaults, and modify routes for improved functionality
This commit is contained in:
@@ -15,7 +15,8 @@ ServiceType = TypeVar("ServiceType")
|
||||
|
||||
|
||||
class TenantCRUDRoutes(
|
||||
Generic[CreateSchemaType, UpdateSchemaType, ResponseSchemaType, ServiceType]
|
||||
Generic[CreateSchemaType, UpdateSchemaType,
|
||||
ResponseSchemaType, ServiceType]
|
||||
):
|
||||
"""
|
||||
Generic CRUD routes factory for tenant-scoped resources
|
||||
@@ -74,7 +75,8 @@ class TenantCRUDRoutes(
|
||||
prefix: str,
|
||||
tags: list[str],
|
||||
resource_name: str = "Resource",
|
||||
id_name: Optional[str] = None, # For parent resources (e.g., "pedimento_id")
|
||||
# For parent resources (e.g., "pedimento_id")
|
||||
id_name: Optional[str] = None,
|
||||
id_type: Type = int, # Type of the ID (int, str, etc.)
|
||||
parent_id_name: Optional[
|
||||
str
|
||||
@@ -128,9 +130,15 @@ class TenantCRUDRoutes(
|
||||
le=self.max_page_size,
|
||||
description="Page size",
|
||||
),
|
||||
status: Optional[str] = Query(None, description="Filter by status"),
|
||||
status: Optional[str] = Query(
|
||||
None, description="Filter by status"),
|
||||
operation_type: Optional[str] = Query(
|
||||
None, description="Filter by operation type"),
|
||||
invoice_type: Optional[str] = Query(
|
||||
None, description="Filter by invoice type"),
|
||||
db: Session = Depends(self.db_dependency),
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
current_user: Dict[str, Any] = Depends(
|
||||
self.auth_dependency),
|
||||
):
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user
|
||||
@@ -140,6 +148,10 @@ class TenantCRUDRoutes(
|
||||
filters = {}
|
||||
if status:
|
||||
filters["status"] = status
|
||||
if operation_type:
|
||||
filters["operation_type"] = operation_type
|
||||
if invoice_type:
|
||||
filters["invoice_type"] = invoice_type
|
||||
|
||||
items, total = self.service.get_all(
|
||||
db, tenant_id, company_id, skip, page_size, filters
|
||||
@@ -172,7 +184,8 @@ class TenantCRUDRoutes(
|
||||
description="Page size",
|
||||
),
|
||||
db: Session = Depends(self.db_dependency),
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
current_user: Dict[str, Any] = Depends(
|
||||
self.auth_dependency),
|
||||
):
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user
|
||||
@@ -211,7 +224,8 @@ class TenantCRUDRoutes(
|
||||
**path_params,
|
||||
):
|
||||
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user)
|
||||
parent_id = path_params.get(self.parent_id_name)
|
||||
|
||||
# Try method with 4 params (pedimento_id, tenant_id, company_id)
|
||||
@@ -225,7 +239,8 @@ class TenantCRUDRoutes(
|
||||
db, parent_id, tenant_id, company_id
|
||||
)
|
||||
else:
|
||||
resource = self.service.get(db, parent_id, tenant_id, company_id)
|
||||
resource = self.service.get(
|
||||
db, parent_id, tenant_id, company_id)
|
||||
|
||||
if not resource:
|
||||
raise HTTPException(
|
||||
@@ -249,7 +264,8 @@ class TenantCRUDRoutes(
|
||||
db: Session = Depends(self.db_dependency),
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
):
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user)
|
||||
|
||||
resource = self.service.get_by_id(
|
||||
db, resource_id, tenant_id, company_id
|
||||
@@ -264,10 +280,10 @@ class TenantCRUDRoutes(
|
||||
# POST route
|
||||
if self.parent_id_name:
|
||||
# Child resource - needs parent_id from path
|
||||
|
||||
|
||||
# Create a closure to capture the schema type
|
||||
create_schema = self.create_schema
|
||||
|
||||
|
||||
@self.router.post(
|
||||
"/",
|
||||
response_model=self.response_schema,
|
||||
@@ -281,17 +297,18 @@ class TenantCRUDRoutes(
|
||||
db: Session = Depends(self.db_dependency),
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
):
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user)
|
||||
|
||||
# For child resources, parent_id validation would go here
|
||||
resource = self.service.create(db, data, tenant_id, company_id)
|
||||
return resource
|
||||
else:
|
||||
# Parent resource - no parent_id needed
|
||||
|
||||
|
||||
# Create a closure to capture the schema type
|
||||
create_schema = self.create_schema
|
||||
|
||||
|
||||
@self.router.post(
|
||||
"/",
|
||||
response_model=self.response_schema,
|
||||
@@ -305,7 +322,8 @@ class TenantCRUDRoutes(
|
||||
db: Session = Depends(self.db_dependency),
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
):
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user)
|
||||
resource = self.service.create(db, data, tenant_id, company_id)
|
||||
return resource
|
||||
|
||||
@@ -314,10 +332,10 @@ class TenantCRUDRoutes(
|
||||
# For child resources: PUT / (parent_id comes from path)
|
||||
if self.parent_id_name:
|
||||
# Child resource
|
||||
|
||||
|
||||
# Create a closure to capture the schema type
|
||||
update_schema = self.update_schema
|
||||
|
||||
|
||||
@self.router.put(
|
||||
"/",
|
||||
response_model=self.response_schema,
|
||||
@@ -331,7 +349,8 @@ class TenantCRUDRoutes(
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
**path_params,
|
||||
):
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user)
|
||||
parent_id = path_params.get(self.parent_id_name)
|
||||
|
||||
resource = self.service.update(
|
||||
@@ -346,10 +365,10 @@ class TenantCRUDRoutes(
|
||||
|
||||
else:
|
||||
# Parent resource
|
||||
|
||||
|
||||
# Create a closure to capture the schema type
|
||||
update_schema = self.update_schema
|
||||
|
||||
|
||||
@self.router.put(
|
||||
f"/{{{self.id_name}}}",
|
||||
response_model=self.response_schema,
|
||||
@@ -366,7 +385,8 @@ class TenantCRUDRoutes(
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
):
|
||||
f"""Update {self.resource_name}"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user)
|
||||
|
||||
resource = self.service.update(
|
||||
db, resource_id, tenant_id, data, company_id
|
||||
@@ -395,10 +415,12 @@ class TenantCRUDRoutes(
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
**path_params,
|
||||
):
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user)
|
||||
parent_id = path_params.get(self.parent_id_name)
|
||||
|
||||
success = self.service.delete(db, parent_id, tenant_id, company_id)
|
||||
success = self.service.delete(
|
||||
db, parent_id, tenant_id, company_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
@@ -422,9 +444,11 @@ class TenantCRUDRoutes(
|
||||
db: Session = Depends(self.db_dependency),
|
||||
current_user: Dict[str, Any] = Depends(self.auth_dependency),
|
||||
):
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
tenant_id = validate_access_to_resource(
|
||||
db, company_id, current_user)
|
||||
|
||||
success = self.service.delete(db, resource_id, tenant_id, company_id)
|
||||
success = self.service.delete(
|
||||
db, resource_id, tenant_id, company_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
|
||||
@@ -44,6 +44,9 @@ class InvoiceService:
|
||||
if filters.get("operation_type"):
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.operation_type == filters["operation_type"])
|
||||
if filters.get("invoice_type"):
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.invoice_type == filters["invoice_type"])
|
||||
if filters.get("invoice_number"):
|
||||
query = query.filter(models.InvoiceHeader.invoice_number.ilike(
|
||||
f"%{filters['invoice_number']}%"))
|
||||
@@ -53,6 +56,10 @@ class InvoiceService:
|
||||
f"%{filters['pedimento']}%")
|
||||
)
|
||||
|
||||
if not filters.get("invoice_type") and filters.get("operation_type") == "exp":
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.operation_type != "REPAR")
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
@@ -8,5 +8,6 @@ class InvoiceTypeDTO(BaseModel):
|
||||
description: str
|
||||
note: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
operation: Optional[str] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user, has_role
|
||||
@@ -11,17 +11,28 @@ from .models import InvoiceType
|
||||
router = APIRouter(prefix="/invoice-types")
|
||||
|
||||
|
||||
@router.get("/", response_model=Dict[str, Any])
|
||||
@router.get("/", response_model=dict)
|
||||
def list_invoice_types(
|
||||
page: int = Query(1, ge=1, description="Número de página"),
|
||||
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
type: Optional[str] = Query(None, description="Filter by type"),
|
||||
operation: Optional[str] = Query(None, description="Filter by operation type (imp, exp, both)"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
skip = (page - 1) * page_size
|
||||
query = db.query(InvoiceType)
|
||||
items = query.offset(skip).limit(page_size).all()
|
||||
|
||||
# Filter by operation if provided
|
||||
if operation:
|
||||
query = query.filter(
|
||||
(InvoiceType.operation == operation) | (
|
||||
InvoiceType.operation == "both")
|
||||
)
|
||||
|
||||
if type == "imp" and operation == "CR":
|
||||
query = query.filter(InvoiceType.operation != "exp")
|
||||
|
||||
total = query.count()
|
||||
items = query.offset((page - 1) * page_size).limit(page_size).all()
|
||||
return {
|
||||
"items": [InvoiceTypeDTO.model_validate(obj) for obj in items],
|
||||
"total": total,
|
||||
|
||||
@@ -30,47 +30,47 @@ seed = [
|
||||
),
|
||||
|
||||
# === TIPOS DE EXPORTACION ===
|
||||
("DONAC", "DONACION", "", "both", "both"),
|
||||
("EXDEF", "EXPORTACION DEFINITIVA", "", "material", "both"),
|
||||
("DONAC", "DONACION", "", "both", "exp"),
|
||||
("EXDEF", "EXPORTACION DEFINITIVA", "", "material", "exp"),
|
||||
(
|
||||
"MATDE",
|
||||
"MATERIA PRIMA O MATERIAL DEVUELTO",
|
||||
"ESTE PROCESO CONSISTE EN SOLO DESCARGAR LAS PARTES DADAS DE ALTA EN MATERIALES QUE SON RETORNADAS SIN NINGUNA MODIFICACION (A1)",
|
||||
"material",
|
||||
"both",
|
||||
"exp",
|
||||
),
|
||||
(
|
||||
"NODES",
|
||||
"NO HACE DESCARGA",
|
||||
"ESTE PROCESO DE ACTUALIZACION CONSISTE EN EXPORTAR UNA MERCANCIA Y NO DESCARGAR, POR LO TANTO NO EXISTE REPORTE DE DESCARGAS Y NO AFECTA SALDOS.",
|
||||
"both",
|
||||
"both",
|
||||
"exp",
|
||||
),
|
||||
(
|
||||
"PTERM",
|
||||
"PRODUCTO TERMINADO Y VIRTUALES",
|
||||
"EL PRODUCTO TERMINADO Y VIRTUALES DESCARGARAN: 1) APARTIR DE LOS COMPONENTES DE CADA PRODUCTO TERMINADO REGISTRADO EN LAS PARTIDAS DE EXPORTACION. 2) POR PARTE, CON LAS OPCIONES DE PODER DESCARGAR POR SUSTITUTO Y POR CLASE EN CASO DE INSUFICIENCIAS DEL COMPONENTE.",
|
||||
"material",
|
||||
"both",
|
||||
"exp",
|
||||
),
|
||||
(
|
||||
"REPAR",
|
||||
"REPARACION",
|
||||
"PROCESO QUE CONSISTE EN DOS ETAPAS: 1) DESCARGA EL PRODUCTO DE REPARACION QUE SE IMPORTO PARA REPARA, 2) DESCARGA EL LISTADO DE COMPONENTES QUE SE AGREGO AL PRODUCTO DE REPARACION",
|
||||
"material",
|
||||
"both",
|
||||
"exp",
|
||||
),
|
||||
("SCRAP", "SCRAP", "", "both", "both"),
|
||||
("SCRAP", "SCRAP", "", "both", "exp"),
|
||||
(
|
||||
"VEMEX",
|
||||
"VENTAS EN MEXICO",
|
||||
"ESTE PROCESO CONSISTE EN LA VENTA EN EL MERCADO NACIONAL DE LOS PRODUCTOS.",
|
||||
"both",
|
||||
"both",
|
||||
"exp",
|
||||
),
|
||||
("VIRTU", "VIRTUALES", "", "material", "both"),
|
||||
("VIRTU", "VIRTUALES", "", "material", "exp"),
|
||||
|
||||
# === ACTIVOS FIJOS (AMBAS OPERACIONES) ===
|
||||
("AFIJO", "ACTIVO FIJO", "", "fixed asset", "both"),
|
||||
("REEXP", "REEXPEDICION", "", "fixed asset", "both"),
|
||||
("AFIJO", "ACTIVO FIJO", "", "fixed asset", "exp"),
|
||||
("REEXP", "REEXPEDICION", "", "fixed asset", "exp"),
|
||||
]
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface InvoiceType {
|
||||
description: string;
|
||||
note?: string;
|
||||
type?: string;
|
||||
operation?: string;
|
||||
}
|
||||
|
||||
export interface InvoiceTypeListResponse {
|
||||
@@ -40,11 +41,20 @@ export const invoiceTypesApi = {
|
||||
* Lista todos los tipos de factura con paginación
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param operation - Filtrar por tipo de operación (imp, exp)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<InvoiceTypeListResponse>(
|
||||
`/v1/public/refrence_data/invoice-types?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, operation?: string) => {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString()
|
||||
});
|
||||
if (operation) {
|
||||
params.append('operation', operation);
|
||||
}
|
||||
return api.get<InvoiceTypeListResponse>(
|
||||
`/v1/public/refrence_data/invoice-types?${params.toString()}`
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un tipo de factura por key
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="techGradient" x1="16" y1="16" x2="48" y2="48" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#00F2FE" /> <stop offset="100%" stop-color="#4FACFE" /> </linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="64" height="64" rx="18" fill="#0F172A"/>
|
||||
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32 14L46 26V40L32 52L18 40V26L32 14ZM32 20.5L23 28.2V35.8L32 43.5L41 35.8V28.2L32 20.5Z" fill="url(#techGradient)"/>
|
||||
|
||||
<path d="M32 20.5V30M32 34V43.5" stroke="#0F172A" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M23 35.8L32 30M41 35.8L32 30" stroke="#0F172A" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 781 B |
@@ -12,10 +12,14 @@
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = $bindable<Invoice | null>(null),
|
||||
defaultOperationType,
|
||||
defaultInvoiceType,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Invoice | null;
|
||||
defaultOperationType?: 'imp' | 'exp';
|
||||
defaultInvoiceType?: string;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
@@ -122,8 +126,8 @@
|
||||
|
||||
function resetForm() {
|
||||
formData = {
|
||||
operation_type: "imp",
|
||||
invoice_type: "",
|
||||
operation_type: defaultOperationType || "imp",
|
||||
invoice_type: defaultInvoiceType || "",
|
||||
invoice_number: "",
|
||||
project_number: "",
|
||||
purchase_order: "",
|
||||
|
||||
@@ -323,19 +323,19 @@ export function getSidebarData(): SidebarData {
|
||||
items: [
|
||||
{
|
||||
title: m["sidebar.import_invoices.temporary"](),
|
||||
url: "/dashboard/invoices",
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=TEM",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.definitive"](),
|
||||
url: "/dashboard/invoices",
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=DEF",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.mexican_purchases"](),
|
||||
url: "/dashboard/invoices",
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=MEX",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.regime_change"](),
|
||||
url: "/dashboard/invoices",
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=CR",
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -346,11 +346,11 @@ export function getSidebarData(): SidebarData {
|
||||
items: [
|
||||
{
|
||||
title: m["sidebar.export_invoices.exportation"](),
|
||||
url: "/dashboard/invoices",
|
||||
url: "/dashboard/invoices?operation_type=exp",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.export_invoices.repair"](),
|
||||
url: "/dashboard/invoices",
|
||||
url: "/dashboard/invoices?operation_type=exp&invoice_type=REPAR",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -44,6 +44,7 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
|
||||
// Obtener filtro de tipo de operación
|
||||
const operationType = url.searchParams.get('operation_type');
|
||||
const invoiceType = url.searchParams.get('invoice_type');
|
||||
|
||||
// Construir parámetros de consulta
|
||||
const params = new URLSearchParams({
|
||||
@@ -57,6 +58,11 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
params.append('operation_type', operationType);
|
||||
}
|
||||
|
||||
// Agregar filtro de invoice_type si existe
|
||||
if (invoiceType) {
|
||||
params.append('invoice_type', invoiceType);
|
||||
}
|
||||
|
||||
// Usar authenticatedFetch para manejar automáticamente el refresh de tokens
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/invoices?${params.toString()}`,
|
||||
@@ -75,7 +81,8 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
error: 'Error al cargar facturas',
|
||||
companies: parentData.companies || [],
|
||||
currentCompanyId: companyId,
|
||||
operationType: operationType || 'all'
|
||||
operationType: operationType || 'all',
|
||||
invoiceType: invoiceType || null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,7 +95,8 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
page_size: data.page_size || 50,
|
||||
companies: parentData.companies || [],
|
||||
currentCompanyId: companyId,
|
||||
operationType: operationType || 'all'
|
||||
operationType: operationType || 'all',
|
||||
invoiceType: invoiceType || null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading invoices:', error);
|
||||
@@ -98,7 +106,9 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
error: 'Error al cargar facturas',
|
||||
companies: parentData.companies || []
|
||||
companies: parentData.companies || [],
|
||||
operationType: 'all',
|
||||
invoiceType: null
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { invoicesApi, type Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
import { invoiceTypesApi, type InvoiceType } from '$lib/api/dashboard/refrence_data/invoice_types';
|
||||
import DataTable from '$lib/components/dashboard/invoices/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/invoices/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/invoices/create-edit-dialog.svelte';
|
||||
@@ -24,6 +25,7 @@
|
||||
companies: any[];
|
||||
currentCompanyId?: number;
|
||||
operationType?: string;
|
||||
invoiceType?: string | null;
|
||||
}
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -36,6 +38,9 @@
|
||||
|
||||
// Estado para el filtro de tipo (inicializado desde data del servidor)
|
||||
let selectedType = $state<string>(data.operationType || 'all');
|
||||
let selectedInvoiceType = $state<string | null>(data.invoiceType || null);
|
||||
let availableInvoiceTypes = $state<InvoiceType[]>([]);
|
||||
let loadingInvoiceTypes = $state(false);
|
||||
|
||||
// Actualizar URL cuando cambia el filtro
|
||||
function handleTypeChange(value: string) {
|
||||
@@ -43,12 +48,59 @@
|
||||
const url = new URL(window.location.href);
|
||||
if (value === 'all') {
|
||||
url.searchParams.delete('operation_type');
|
||||
url.searchParams.delete('invoice_type');
|
||||
selectedInvoiceType = null;
|
||||
} else {
|
||||
url.searchParams.set('operation_type', value);
|
||||
// Mantener invoice_type si existe
|
||||
if (selectedInvoiceType) {
|
||||
url.searchParams.set('invoice_type', selectedInvoiceType);
|
||||
}
|
||||
}
|
||||
goto(url.toString(), { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
// Actualizar URL cuando cambia el filtro de invoice_type
|
||||
function handleInvoiceTypeChange(value: string) {
|
||||
selectedInvoiceType = value === 'all' ? null : value;
|
||||
const url = new URL(window.location.href);
|
||||
if (!selectedInvoiceType) {
|
||||
url.searchParams.delete('invoice_type');
|
||||
} else {
|
||||
url.searchParams.set('invoice_type', selectedInvoiceType);
|
||||
}
|
||||
if (selectedType !== 'all') {
|
||||
url.searchParams.set('operation_type', selectedType);
|
||||
}
|
||||
goto(url.toString(), { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
// Cargar tipos de factura disponibles según operation_type
|
||||
async function loadInvoiceTypes(operationType: string) {
|
||||
if (operationType === 'all') {
|
||||
availableInvoiceTypes = [];
|
||||
return;
|
||||
}
|
||||
|
||||
loadingInvoiceTypes = true;
|
||||
try {
|
||||
const response = await invoiceTypesApi.list(1, 100, operationType);
|
||||
if (response.data) {
|
||||
availableInvoiceTypes = response.data.items;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading invoice types:', e);
|
||||
availableInvoiceTypes = [];
|
||||
} finally {
|
||||
loadingInvoiceTypes = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Efecto para cargar tipos de factura cuando cambia selectedType
|
||||
$effect(() => {
|
||||
loadInvoiceTypes(selectedType);
|
||||
});
|
||||
|
||||
// Sincronizar token de cookies a localStorage al montar el componente
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
@@ -120,6 +172,8 @@
|
||||
currentPage = data.page || 1;
|
||||
totalItems = data.total || 0;
|
||||
error = data.error || null;
|
||||
selectedInvoiceType = data.invoiceType || null;
|
||||
selectedType = data.operationType || 'all';
|
||||
});
|
||||
|
||||
async function loadMore() {
|
||||
@@ -129,11 +183,19 @@
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const filters: any = {};
|
||||
if (selectedType !== 'all') {
|
||||
filters.operation_type = selectedType;
|
||||
}
|
||||
if (selectedInvoiceType) {
|
||||
filters.invoice_type = selectedInvoiceType;
|
||||
}
|
||||
|
||||
const response = await invoicesApi.list(
|
||||
companyStore.activeCompany.id,
|
||||
currentPage + 1,
|
||||
pageSize,
|
||||
selectedType !== 'all' ? { operation_type: selectedType } : undefined
|
||||
Object.keys(filters).length > 0 ? filters : undefined
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
@@ -245,6 +307,41 @@
|
||||
<Select.Item value="exp">Exportación</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
|
||||
{#if selectedType !== 'all' && availableInvoiceTypes.length > 0}
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={selectedInvoiceType || 'all'}
|
||||
onValueChange={handleInvoiceTypeChange}
|
||||
disabled={loadingInvoiceTypes}
|
||||
>
|
||||
<Select.Trigger class="w-[250px]">
|
||||
<span class="truncate">
|
||||
{#if loadingInvoiceTypes}
|
||||
Cargando...
|
||||
{:else if selectedInvoiceType}
|
||||
{(() => {
|
||||
const found = availableInvoiceTypes.find(t => t.key === selectedInvoiceType);
|
||||
return found ? `${found.key} - ${found.description}` : selectedInvoiceType;
|
||||
})()}
|
||||
{:else}
|
||||
Todos los tipos
|
||||
{/if}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="all">Todos los tipos</Select.Item>
|
||||
{#each availableInvoiceTypes as invType}
|
||||
<Select.Item value={invType.key}>
|
||||
<span class="block truncate max-w-[300px]" title={`${invType.key} - ${invType.description}`}>
|
||||
{invType.key} - {invType.description}
|
||||
</span>
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{/if}
|
||||
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
@@ -268,6 +365,8 @@
|
||||
<CreateEditDialog
|
||||
bind:open={showCreateDialog}
|
||||
bind:item={selectedInvoice}
|
||||
defaultOperationType={selectedType !== 'all' ? selectedType as 'imp' | 'exp' : undefined}
|
||||
defaultInvoiceType={selectedInvoiceType || undefined}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user