Creacion de rutas para nuevas facturas, asi como nuevas rutinas para el filtro de datos en facturas de exportacion
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import traceback
|
||||
from typing import Optional, List, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
|
||||
from . import models, schemas
|
||||
|
||||
|
||||
class InvoiceService:
|
||||
"""Service for Invoice Header operations"""
|
||||
|
||||
@@ -55,7 +55,6 @@ class InvoiceService:
|
||||
models.InvoiceComplianceMx.pedimento.ilike(
|
||||
f"%{filters['pedimento']}%")
|
||||
)
|
||||
|
||||
if not filters.get("invoice_type") and filters.get("operation_type") == "exp":
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.operation_type != "REPAR")
|
||||
@@ -72,73 +71,118 @@ class InvoiceService:
|
||||
company_id: int
|
||||
) -> models.InvoiceHeader:
|
||||
"""Create a new invoice with all related data"""
|
||||
# Extract nested data
|
||||
compliance_data = invoice_data.compliance_mx
|
||||
financials_data = invoice_data.financials
|
||||
logistics_data = invoice_data.logistics or []
|
||||
details_data = invoice_data.details or []
|
||||
collections_data = invoice_data.collections or []
|
||||
|
||||
|
||||
def clean_dict(data_dict: dict) -> dict:
|
||||
cleaned = {}
|
||||
for key, value in data_dict.items():
|
||||
|
||||
if key == 'customs_agent':
|
||||
key = 'customs_broker_id'
|
||||
elif key == 'provider':
|
||||
key = 'provider_id'
|
||||
|
||||
|
||||
if isinstance(value, str) and not value.strip():
|
||||
cleaned[key] = None
|
||||
|
||||
elif value == 0 and (key.endswith('_id') or key == 'remesa'):
|
||||
cleaned[key] = None
|
||||
else:
|
||||
cleaned[key] = value
|
||||
return cleaned
|
||||
|
||||
|
||||
# Create main invoice header
|
||||
invoice_dict = invoice_data.model_dump(
|
||||
exclude={"compliance_mx", "financials",
|
||||
"logistics", "details", "collections"}
|
||||
)
|
||||
invoice_dict["tenant_id"] = tenant_id
|
||||
invoice_dict["company_id"] = company_id
|
||||
try:
|
||||
# Extract nested data
|
||||
compliance_data = invoice_data.compliance_mx
|
||||
financials_data = invoice_data.financials
|
||||
logistics_data = invoice_data.logistics or []
|
||||
details_data = invoice_data.details or []
|
||||
collections_data = invoice_data.collections or []
|
||||
|
||||
new_invoice = models.InvoiceHeader(**invoice_dict)
|
||||
db.add(new_invoice)
|
||||
db.flush() # Flush to get the invoice ID
|
||||
# Create main invoice header
|
||||
raw_invoice_dict = invoice_data.model_dump(
|
||||
exclude={"compliance_mx", "financials",
|
||||
"logistics", "details", "collections"}
|
||||
)
|
||||
invoice_dict = clean_dict(raw_invoice_dict)
|
||||
invoice_dict["tenant_id"] = tenant_id
|
||||
invoice_dict["company_id"] = company_id
|
||||
|
||||
# Create compliance_mx if provided
|
||||
if compliance_data:
|
||||
compliance_dict = compliance_data.model_dump()
|
||||
compliance_dict["invoice_id"] = new_invoice.id
|
||||
compliance_dict["tenant_id"] = tenant_id
|
||||
compliance_dict["company_id"] = company_id
|
||||
new_compliance = models.InvoiceComplianceMx(**compliance_dict)
|
||||
db.add(new_compliance)
|
||||
new_invoice = models.InvoiceHeader(**invoice_dict)
|
||||
db.add(new_invoice)
|
||||
db.flush() # Flush to get the invoice ID
|
||||
|
||||
# Create financials if provided
|
||||
if financials_data:
|
||||
financials_dict = financials_data.model_dump()
|
||||
financials_dict["invoice_id"] = new_invoice.id
|
||||
financials_dict["tenant_id"] = tenant_id
|
||||
financials_dict["company_id"] = company_id
|
||||
new_financials = models.InvoiceFinancials(**financials_dict)
|
||||
db.add(new_financials)
|
||||
# Create compliance_mx if provided
|
||||
if compliance_data:
|
||||
raw_comp_dict = compliance_data.model_dump()
|
||||
# Pasamos los datos por la lavadora para arreglar pedimento, aduana, etc.
|
||||
compliance_dict = clean_dict(raw_comp_dict)
|
||||
|
||||
compliance_dict["invoice_id"] = new_invoice.id
|
||||
compliance_dict["tenant_id"] = tenant_id
|
||||
compliance_dict["company_id"] = company_id
|
||||
|
||||
new_compliance = models.InvoiceComplianceMx(**compliance_dict)
|
||||
db.add(new_compliance)
|
||||
|
||||
# Create logistics entries
|
||||
for logistics_item in logistics_data:
|
||||
logistics_dict = logistics_item.model_dump()
|
||||
logistics_dict["invoice_id"] = new_invoice.id
|
||||
logistics_dict["tenant_id"] = tenant_id
|
||||
logistics_dict["company_id"] = company_id
|
||||
new_logistics = models.InvoiceLogistics(**logistics_dict)
|
||||
db.add(new_logistics)
|
||||
# Create financials if provided
|
||||
if financials_data:
|
||||
raw_fin_dict = financials_data.model_dump()
|
||||
financials_dict = clean_dict(raw_fin_dict)
|
||||
|
||||
financials_dict["invoice_id"] = new_invoice.id
|
||||
financials_dict["tenant_id"] = tenant_id
|
||||
financials_dict["company_id"] = company_id
|
||||
|
||||
new_financials = models.InvoiceFinancials(**financials_dict)
|
||||
db.add(new_financials)
|
||||
|
||||
# Create sales details
|
||||
for detail_item in details_data:
|
||||
detail_dict = detail_item.model_dump()
|
||||
detail_dict["invoice_id"] = new_invoice.id
|
||||
detail_dict["tenant_id"] = tenant_id
|
||||
detail_dict["company_id"] = company_id
|
||||
new_detail = models.InvoiceSalesDetails(**detail_dict)
|
||||
db.add(new_detail)
|
||||
# Create logistics entries
|
||||
for logistics_item in logistics_data:
|
||||
raw_log_dict = logistics_item.model_dump()
|
||||
logistics_dict = clean_dict(raw_log_dict)
|
||||
|
||||
logistics_dict["invoice_id"] = new_invoice.id
|
||||
logistics_dict["tenant_id"] = tenant_id
|
||||
logistics_dict["company_id"] = company_id
|
||||
new_logistics = models.InvoiceLogistics(**logistics_dict)
|
||||
db.add(new_logistics)
|
||||
|
||||
# Create collections
|
||||
for collection_item in collections_data:
|
||||
collection_dict = collection_item.model_dump()
|
||||
collection_dict["invoice_id"] = new_invoice.id
|
||||
collection_dict["tenant_id"] = tenant_id
|
||||
collection_dict["company_id"] = company_id
|
||||
new_collection = models.InvoiceCollections(**collection_dict)
|
||||
db.add(new_collection)
|
||||
# Create sales details
|
||||
for detail_item in details_data:
|
||||
raw_det_dict = detail_item.model_dump()
|
||||
detail_dict = clean_dict(raw_det_dict)
|
||||
|
||||
detail_dict["invoice_id"] = new_invoice.id
|
||||
detail_dict["tenant_id"] = tenant_id
|
||||
detail_dict["company_id"] = company_id
|
||||
new_detail = models.InvoiceSalesDetails(**detail_dict)
|
||||
db.add(new_detail)
|
||||
|
||||
db.commit()
|
||||
db.refresh(new_invoice)
|
||||
return new_invoice
|
||||
# Create collections
|
||||
for collection_item in collections_data:
|
||||
raw_col_dict = collection_item.model_dump()
|
||||
collection_dict = clean_dict(raw_col_dict)
|
||||
|
||||
collection_dict["invoice_id"] = new_invoice.id
|
||||
collection_dict["tenant_id"] = tenant_id
|
||||
collection_dict["company_id"] = company_id
|
||||
new_collection = models.InvoiceCollections(**collection_dict)
|
||||
db.add(new_collection)
|
||||
|
||||
db.commit()
|
||||
db.refresh(new_invoice)
|
||||
return new_invoice
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print("\n\n🔥 ERROR AL GUARDAR FACTURA 🔥")
|
||||
print(f"Error: {str(e)}")
|
||||
traceback.print_exc() # Esto imprime el error real en la consola
|
||||
print("--------------------------------\n")
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
@@ -148,7 +192,8 @@ class InvoiceService:
|
||||
invoice_data: schemas.InvoiceHeaderUpdate,
|
||||
company_id: int
|
||||
) -> Optional[models.InvoiceHeader]:
|
||||
"""Update an existing invoice and its related data"""
|
||||
# ... (El resto de tu código update se queda igual) ...
|
||||
# (Te recomiendo implementar clean_dict aquí también si tienes problemas al editar)
|
||||
invoice = InvoiceService.get_by_id(
|
||||
db, invoice_id, tenant_id, company_id)
|
||||
if not invoice:
|
||||
@@ -167,9 +212,14 @@ class InvoiceService:
|
||||
if invoice_data.compliance_mx is not None:
|
||||
if invoice.compliance_mx:
|
||||
for key, value in invoice_data.compliance_mx.model_dump(exclude_unset=True).items():
|
||||
# Parche rápido para update
|
||||
if value == "": value = None
|
||||
setattr(invoice.compliance_mx, key, value)
|
||||
else:
|
||||
compliance_dict = invoice_data.compliance_mx.model_dump()
|
||||
# Aplicar limpieza manual si es necesario
|
||||
if 'customs_agent' in compliance_dict: compliance_dict['customs_broker_id'] = compliance_dict.pop('customs_agent')
|
||||
|
||||
compliance_dict["invoice_id"] = invoice.id
|
||||
compliance_dict["tenant_id"] = tenant_id
|
||||
compliance_dict["company_id"] = company_id
|
||||
@@ -180,6 +230,7 @@ class InvoiceService:
|
||||
if invoice_data.financials is not None:
|
||||
if invoice.financials:
|
||||
for key, value in invoice_data.financials.model_dump(exclude_unset=True).items():
|
||||
if value == "": value = None
|
||||
setattr(invoice.financials, key, value)
|
||||
else:
|
||||
financials_dict = invoice_data.financials.model_dump()
|
||||
@@ -189,9 +240,6 @@ class InvoiceService:
|
||||
new_financials = models.InvoiceFinancials(**financials_dict)
|
||||
db.add(new_financials)
|
||||
|
||||
# Note: For logistics, details, and collections, we're not handling updates here
|
||||
# as they are typically managed through separate endpoints for complex operations
|
||||
|
||||
db.commit()
|
||||
db.refresh(invoice)
|
||||
return invoice
|
||||
@@ -205,172 +253,4 @@ class InvoiceService:
|
||||
db.delete(invoice)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class InvoiceLogisticsService:
|
||||
"""Service for Invoice Logistics operations"""
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, logistics_id: int, invoice_id: int) -> Optional[models.InvoiceLogistics]:
|
||||
"""Get a logistics entry by ID"""
|
||||
return (
|
||||
db.query(models.InvoiceLogistics)
|
||||
.filter(
|
||||
models.InvoiceLogistics.logistics_id == logistics_id,
|
||||
models.InvoiceLogistics.invoice_id == invoice_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_all_by_invoice(db: Session, invoice_id: int) -> List[models.InvoiceLogistics]:
|
||||
"""Get all logistics entries for an invoice"""
|
||||
return (
|
||||
db.query(models.InvoiceLogistics)
|
||||
.filter(models.InvoiceLogistics.invoice_id == invoice_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
logistics_data: schemas.InvoiceLogisticsCreate,
|
||||
invoice_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> models.InvoiceLogistics:
|
||||
"""Create a new logistics entry"""
|
||||
logistics_dict = logistics_data.model_dump()
|
||||
logistics_dict["invoice_id"] = invoice_id
|
||||
logistics_dict["tenant_id"] = tenant_id
|
||||
logistics_dict["company_id"] = company_id
|
||||
|
||||
new_logistics = models.InvoiceLogistics(**logistics_dict)
|
||||
db.add(new_logistics)
|
||||
db.commit()
|
||||
db.refresh(new_logistics)
|
||||
return new_logistics
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, logistics_id: int, invoice_id: int) -> bool:
|
||||
"""Delete a logistics entry"""
|
||||
logistics = InvoiceLogisticsService.get_by_id(
|
||||
db, logistics_id, invoice_id)
|
||||
if logistics:
|
||||
db.delete(logistics)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class InvoiceSalesDetailsService:
|
||||
"""Service for Invoice Sales Details operations"""
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, detail_id: int, invoice_id: int) -> Optional[models.InvoiceSalesDetails]:
|
||||
"""Get a sales detail entry by ID"""
|
||||
return (
|
||||
db.query(models.InvoiceSalesDetails)
|
||||
.filter(
|
||||
models.InvoiceSalesDetails.detail_id == detail_id,
|
||||
models.InvoiceSalesDetails.invoice_id == invoice_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_all_by_invoice(db: Session, invoice_id: int) -> List[models.InvoiceSalesDetails]:
|
||||
"""Get all sales details for an invoice"""
|
||||
return (
|
||||
db.query(models.InvoiceSalesDetails)
|
||||
.filter(models.InvoiceSalesDetails.invoice_id == invoice_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
detail_data: schemas.InvoiceSalesDetailsCreate,
|
||||
invoice_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> models.InvoiceSalesDetails:
|
||||
"""Create a new sales detail entry"""
|
||||
detail_dict = detail_data.model_dump()
|
||||
detail_dict["invoice_id"] = invoice_id
|
||||
detail_dict["tenant_id"] = tenant_id
|
||||
detail_dict["company_id"] = company_id
|
||||
|
||||
new_detail = models.InvoiceSalesDetails(**detail_dict)
|
||||
db.add(new_detail)
|
||||
db.commit()
|
||||
db.refresh(new_detail)
|
||||
return new_detail
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, detail_id: int, invoice_id: int) -> bool:
|
||||
"""Delete a sales detail entry"""
|
||||
detail = InvoiceSalesDetailsService.get_by_id(
|
||||
db, detail_id, invoice_id)
|
||||
if detail:
|
||||
db.delete(detail)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class InvoiceCollectionsService:
|
||||
"""Service for Invoice Collections operations"""
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, collection_id: int, invoice_id: int) -> Optional[models.InvoiceCollections]:
|
||||
"""Get a collection entry by ID"""
|
||||
return (
|
||||
db.query(models.InvoiceCollections)
|
||||
.filter(
|
||||
models.InvoiceCollections.collection_id == collection_id,
|
||||
models.InvoiceCollections.invoice_id == invoice_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_all_by_invoice(db: Session, invoice_id: int) -> List[models.InvoiceCollections]:
|
||||
"""Get all collections for an invoice"""
|
||||
return (
|
||||
db.query(models.InvoiceCollections)
|
||||
.filter(models.InvoiceCollections.invoice_id == invoice_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
collection_data: schemas.InvoiceCollectionsCreate,
|
||||
invoice_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int
|
||||
) -> models.InvoiceCollections:
|
||||
"""Create a new collection entry"""
|
||||
collection_dict = collection_data.model_dump()
|
||||
collection_dict["invoice_id"] = invoice_id
|
||||
collection_dict["tenant_id"] = tenant_id
|
||||
collection_dict["company_id"] = company_id
|
||||
|
||||
new_collection = models.InvoiceCollections(**collection_dict)
|
||||
db.add(new_collection)
|
||||
db.commit()
|
||||
db.refresh(new_collection)
|
||||
return new_collection
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, collection_id: int, invoice_id: int) -> bool:
|
||||
"""Delete a collection entry"""
|
||||
collection = InvoiceCollectionsService.get_by_id(
|
||||
db, collection_id, invoice_id)
|
||||
if collection:
|
||||
db.delete(collection)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
return False
|
||||
1190
estructura.txt
Normal file
1190
estructura.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -315,7 +315,7 @@
|
||||
</script>
|
||||
|
||||
<Dialog.Root {open} onOpenChange={handleOpenChange}>
|
||||
<Dialog.Content class="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Content class="sm:max-w-full max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>
|
||||
{isEditing ? "Editar Factura" : "Nueva Factura"}
|
||||
|
||||
@@ -42,9 +42,9 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
};
|
||||
}
|
||||
|
||||
// Obtener filtro de tipo de operación
|
||||
const operationType = 'exp'
|
||||
const invoiceType = 'exp'
|
||||
// Obtener filtros de la URL
|
||||
const operationType = 'exp';
|
||||
const invoiceType = url.searchParams.get('invoice_type') || '';
|
||||
|
||||
// Construir parámetros de consulta
|
||||
const params = new URLSearchParams({
|
||||
@@ -53,16 +53,15 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
page_size: '50'
|
||||
});
|
||||
|
||||
// Agregar filtro de tipo si existe y no es 'all'
|
||||
// Agregar filtro de tipo de operación si existe y no es 'all'
|
||||
if (operationType && operationType !== 'all') {
|
||||
params.append('operation_type', operationType);
|
||||
}
|
||||
|
||||
// Agregar filtro de invoice_type si existe
|
||||
if (invoiceType) {
|
||||
// Agregar filtro de invoice_type si existe y no está vacío
|
||||
if (invoiceType && 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()}`,
|
||||
@@ -81,7 +80,7 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
error: 'Error al cargar facturas',
|
||||
companies: parentData.companies || [],
|
||||
currentCompanyId: companyId,
|
||||
operationType: operationType || 'all',
|
||||
operationType: operationType || 'exp',
|
||||
invoiceType: invoiceType || null
|
||||
};
|
||||
}
|
||||
@@ -95,7 +94,7 @@ 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 || 'exp',
|
||||
invoiceType: invoiceType || null
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -107,7 +106,7 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||
page_size: 50,
|
||||
error: 'Error al cargar facturas',
|
||||
companies: parentData.companies || [],
|
||||
operationType: 'all',
|
||||
operationType: 'exp',
|
||||
invoiceType: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@
|
||||
Gestiona las facturas de importación y exportación
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Button href="/dashboard/invoices/exportacion/exportacion/new">
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nueva Factura
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { invoicesApi, type CreateInvoiceData } from "$lib/api/dashboard/a76/invoices";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle, ArrowLeft, Save } from 'lucide-svelte';
|
||||
|
||||
// DATOS FIJOS PARA ESTA CARPETA (EXPORTACIÓN)
|
||||
const FIXED_OP_TYPE = "exp";
|
||||
|
||||
// ESTADO DEL FORMULARIO
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
let formData = $state({
|
||||
// Header fields
|
||||
operation_type: FIXED_OP_TYPE as "imp" | "exp",
|
||||
invoice_type: "", // Se llena con el Select
|
||||
invoice_number: "",
|
||||
project_number: "",
|
||||
purchase_order: "",
|
||||
related_doc_id: null as number | null,
|
||||
invoice_date: new Date().toISOString().split('T')[0], // Fecha de hoy
|
||||
traffic_light_status: "",
|
||||
observation_es: "",
|
||||
observation_en: "",
|
||||
comments_status: "",
|
||||
cfdi_uuid: "",
|
||||
path_pdf: "",
|
||||
path_xml: "",
|
||||
// Compliance MX fields
|
||||
pedimento: "",
|
||||
pedimento_code: "",
|
||||
remesa: null as number | null,
|
||||
aduana: "",
|
||||
customs_broker_id: "",
|
||||
provider_id: "",
|
||||
sold_to_id: "",
|
||||
shipped_to_id: "",
|
||||
shipped_by_id: "",
|
||||
is_mixed: false,
|
||||
waste_type: "",
|
||||
appendix_17: null as number | null,
|
||||
edocument: "",
|
||||
// Financials fields
|
||||
currency: "MXN",
|
||||
exchange_rate: null as number | null,
|
||||
value_mn: null as number | null,
|
||||
value_me: null as number | null,
|
||||
customs_value_mn: null as number | null,
|
||||
freight: null as number | null,
|
||||
insurance: null as number | null,
|
||||
iva_mn: null as number | null,
|
||||
iva_factor: null as number | null,
|
||||
total_quantity: null as number | null,
|
||||
gross_weight: null as number | null,
|
||||
net_weight: null as number | null,
|
||||
bundle_count: null as number | null
|
||||
});
|
||||
|
||||
// 👇 TU LISTA DE TIPOS DE EXPORTACIÓN
|
||||
const INVOICE_TYPES_OPTIONS = [
|
||||
{ value: "DONAC", label: "DONAC - DONACION" },
|
||||
{ value: "EXDEF", label: "EXDEF - EXPORTACION DEFINITIVA" },
|
||||
{ value: "MATDE", label: "MATDE - MATERIA PRIMA O MATERIAL DEVUELTO" },
|
||||
{ value: "NODES", label: "NODES - NO HACE DESCARGA" },
|
||||
{ value: "PTERM", label: "PTERM - PRODUCTO TERMINADO Y VIRTUALES" },
|
||||
{ value: "SCRAP", label: "SCRAP - SCRAP" },
|
||||
{ value: "VEMEX", label: "VEMEX - VENTAS EN MEXICO" },
|
||||
{ value: "VIRTU", label: "VIRTU - VIRTUALES" },
|
||||
];
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!companyStore.activeCompany?.id) {
|
||||
error = "Error: No se detecta la compañía activa.";
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
// Construimos el Payload
|
||||
const payload: CreateInvoiceData = {
|
||||
// Header (Usamos el valor del Select para invoice_type)
|
||||
operation_type: FIXED_OP_TYPE,
|
||||
invoice_type: formData.invoice_type,
|
||||
invoice_number: formData.invoice_number,
|
||||
project_number: formData.project_number,
|
||||
purchase_order: formData.purchase_order,
|
||||
related_doc_id: formData.related_doc_id,
|
||||
invoice_date: formData.invoice_date,
|
||||
traffic_light_status: formData.traffic_light_status,
|
||||
observation_es: formData.observation_es,
|
||||
observation_en: formData.observation_en,
|
||||
comments_status: formData.comments_status,
|
||||
cfdi_uuid: formData.cfdi_uuid,
|
||||
path_pdf: formData.path_pdf,
|
||||
path_xml: formData.path_xml,
|
||||
|
||||
// Compliance (Agrupado)
|
||||
compliance_mx: {
|
||||
pedimento: formData.pedimento,
|
||||
pedimento_code: formData.pedimento_code,
|
||||
remesa: formData.remesa,
|
||||
aduana: formData.aduana,
|
||||
customs_broker_id: formData.customs_broker_id,
|
||||
provider_id: formData.provider_id,
|
||||
sold_to_id: formData.sold_to_id,
|
||||
shipped_to_id: formData.shipped_to_id,
|
||||
shipped_by_id: formData.shipped_by_id,
|
||||
is_mixed: formData.is_mixed,
|
||||
waste_type: formData.waste_type,
|
||||
appendix_17: formData.appendix_17,
|
||||
edocument: formData.edocument
|
||||
},
|
||||
|
||||
// Financials (Agrupado)
|
||||
financials: {
|
||||
currency: formData.currency,
|
||||
exchange_rate: formData.exchange_rate,
|
||||
value_mn: formData.value_mn,
|
||||
value_me: formData.value_me,
|
||||
customs_value_mn: formData.customs_value_mn,
|
||||
freight: formData.freight,
|
||||
insurance: formData.insurance,
|
||||
iva_mn: formData.iva_mn,
|
||||
iva_factor: formData.iva_factor,
|
||||
total_quantity: formData.total_quantity,
|
||||
gross_weight: formData.gross_weight,
|
||||
net_weight: formData.net_weight,
|
||||
bundle_count: formData.bundle_count
|
||||
}
|
||||
};
|
||||
|
||||
const response = await invoicesApi.create(companyStore.activeCompany.id, payload);
|
||||
|
||||
if (response.error) {
|
||||
if (response.status === 401) {
|
||||
error = 'Sesión expirada. Recargando...';
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ÉXITO: Volvemos a la lista
|
||||
goto('/dashboard/invoices/exportacion/exportacion');
|
||||
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : "Error al guardar";
|
||||
console.error("Error saving:", e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full mx-auto py-6 px-4 space-y-6 pb-48">
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" href="/dashboard/invoices/exportacion/exportacion">
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Nueva Factura Exportación</h1>
|
||||
<p class="text-muted-foreground">Ingresa los datos para registrar la operación.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="p-4 rounded-md bg-destructive/10 text-destructive border border-destructive/20 text-sm font-medium">
|
||||
⚠️ {error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-6">
|
||||
<Card.Root>
|
||||
<Card.Content class="p-6">
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
|
||||
<Tabs.Content value="general" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Operación</Label>
|
||||
<div class="px-3 py-2 bg-muted rounded-md text-sm font-medium">Exportación</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Factura</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.invoice_type}
|
||||
onValueChange={(v) => formData.invoice_type = v}
|
||||
>
|
||||
<Select.Trigger>
|
||||
{#if formData.invoice_type}
|
||||
{INVOICE_TYPES_OPTIONS.find(t => t.value === formData.invoice_type)?.label}
|
||||
{:else}
|
||||
Seleccionar tipo
|
||||
{/if}
|
||||
</Select.Trigger>
|
||||
|
||||
<Select.Content class="max-h-[300px] overflow-y-auto">
|
||||
{#each INVOICE_TYPES_OPTIONS as type}
|
||||
<Select.Item value={type.value}>
|
||||
{type.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="invoice_number">Número de Factura *</Label>
|
||||
<Input id="invoice_number" bind:value={formData.invoice_number} required placeholder="Número de factura" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="project_number">Número de Proyecto</Label>
|
||||
<Input id="project_number" bind:value={formData.project_number} placeholder="Número de proyecto" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="purchase_order">Orden de Compra</Label>
|
||||
<Input id="purchase_order" bind:value={formData.purchase_order} placeholder="Orden de compra" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="invoice_date">Fecha de Factura *</Label>
|
||||
<Input id="invoice_date" type="date" bind:value={formData.invoice_date} required />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="traffic_light_status">Semáforo</Label>
|
||||
<Input id="traffic_light_status" bind:value={formData.traffic_light_status} placeholder="Estado del semáforo" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="cfdi_uuid">CFDI UUID</Label>
|
||||
<Input id="cfdi_uuid" bind:value={formData.cfdi_uuid} placeholder="UUID del CFDI" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 pt-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="observation_es">Observaciones (Español)</Label>
|
||||
<Input id="observation_es" bind:value={formData.observation_es} placeholder="Observaciones en español" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="observation_en">Observaciones (Inglés)</Label>
|
||||
<Input id="observation_en" bind:value={formData.observation_en} placeholder="Observaciones en inglés" />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="compliance" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento">Pedimento</Label>
|
||||
<Input id="pedimento" bind:value={formData.pedimento} placeholder="Número de pedimento" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_code">Código de Pedimento</Label>
|
||||
<Input id="pedimento_code" bind:value={formData.pedimento_code} placeholder="R1, K1, etc." />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="remesa">Remesa</Label>
|
||||
<Input id="remesa" type="number" bind:value={formData.remesa} placeholder="Número de remesa" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="aduana">Aduana</Label>
|
||||
<Input id="aduana" bind:value={formData.aduana} placeholder="Código de aduana" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="customs_broker_id">Agente Aduanal</Label>
|
||||
<Input id="customs_broker_id" bind:value={formData.customs_broker_id} placeholder="ID del agente aduanal" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="provider_id">Proveedor</Label>
|
||||
<Input id="provider_id" bind:value={formData.provider_id} placeholder="ID del proveedor" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edocument">E-Document</Label>
|
||||
<Input id="edocument" bind:value={formData.edocument} placeholder="Número de e-document" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 flex items-center gap-2 pt-8">
|
||||
<input id="is_mixed" type="checkbox" bind:checked={formData.is_mixed} class="h-4 w-4" />
|
||||
<Label for="is_mixed" class="!mt-0">Operación Mixta</Label>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="financials" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="currency">Moneda</Label>
|
||||
<Input id="currency" bind:value={formData.currency} placeholder="MXN, USD, etc." />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="exchange_rate">Tipo de Cambio</Label>
|
||||
<Input id="exchange_rate" type="number" step="0.000001" bind:value={formData.exchange_rate} placeholder="Tipo de cambio" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="value_mn">Valor MN</Label>
|
||||
<Input id="value_mn" type="number" step="0.01" bind:value={formData.value_mn} placeholder="Valor MN" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="value_me">Valor ME</Label>
|
||||
<Input id="value_me" type="number" step="0.01" bind:value={formData.value_me} placeholder="Valor ME" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="customs_value_mn">Valor Aduana MN</Label>
|
||||
<Input id="customs_value_mn" type="number" step="0.01" bind:value={formData.customs_value_mn} placeholder="Valor Aduana MN" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="freight">Flete</Label>
|
||||
<Input id="freight" type="number" step="0.01" bind:value={formData.freight} placeholder="Costo Flete" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="insurance">Seguro</Label>
|
||||
<Input id="insurance" type="number" step="0.01" bind:value={formData.insurance} placeholder="Costo Seguro" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="total_quantity">Cantidad Total</Label>
|
||||
<Input id="total_quantity" type="number" step="0.01" bind:value={formData.total_quantity} placeholder="Cantidad Total" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="gross_weight">Peso Bruto</Label>
|
||||
<Input id="gross_weight" type="number" step="0.01" bind:value={formData.gross_weight} placeholder="Peso Bruto" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="net_weight">Peso Neto</Label>
|
||||
<Input id="net_weight" type="number" step="0.01" bind:value={formData.net_weight} placeholder="Peso Neto" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="bundle_count">Número de Bultos</Label>
|
||||
<Input id="bundle_count" type="number" bind:value={formData.bundle_count} placeholder="Num. Bultos" />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.List class="grid grid-cols-3 fixed bottom-24 left-1/2 -translate-x-1/2 w-[90%] max-w-lg z-40 shadow-xl bg-background border rounded-xl">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
</Tabs.Root>
|
||||
|
||||
</Card.Content>
|
||||
|
||||
</Card.Root>
|
||||
|
||||
<div class="fixed bottom-0 right-0 left-0 md:left-64 p-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="w-full mx-auto flex justify-end gap-4 px-4">
|
||||
<Button variant="outline" href="/dashboard/invoices/exportacion/exportacion">
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
<Save class="mr-2 h-4 w-4" />
|
||||
Guardar Factura
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -231,8 +231,7 @@
|
||||
}
|
||||
|
||||
function handleCreateClick() {
|
||||
selectedInvoice = null;
|
||||
showCreateDialog = true;
|
||||
goto('/dashboard/invoices/importacion/reparacion/new');
|
||||
}
|
||||
|
||||
function handleView(invoice: Invoice) {
|
||||
@@ -267,7 +266,7 @@
|
||||
Gestiona las facturas de importación y exportación
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Button href="/dashboard/invoices/exportacion/reparacion/new">
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nueva Factura
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { invoicesApi, type CreateInvoiceData } from "$lib/api/dashboard/a76/invoices";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle, ArrowLeft, Save } from 'lucide-svelte';
|
||||
|
||||
// DATOS FIJOS PARA ESTA CARPETA (DEFINITIVA)
|
||||
const FIXED_OP_TYPE = "exp";
|
||||
const FIXED_INV_TYPE = "REPAR";
|
||||
|
||||
// ESTADO DEL FORMULARIO
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
let formData = $state({
|
||||
// Header fields
|
||||
operation_type: FIXED_OP_TYPE as "imp" | "exp",
|
||||
invoice_type: FIXED_INV_TYPE,
|
||||
invoice_number: "",
|
||||
project_number: "",
|
||||
purchase_order: "",
|
||||
related_doc_id: null as number | null,
|
||||
invoice_date: new Date().toISOString().split('T')[0], // Fecha de hoy por defecto
|
||||
traffic_light_status: "",
|
||||
observation_es: "",
|
||||
observation_en: "",
|
||||
comments_status: "",
|
||||
cfdi_uuid: "",
|
||||
path_pdf: "",
|
||||
path_xml: "",
|
||||
// Compliance MX fields
|
||||
pedimento: "",
|
||||
pedimento_code: "",
|
||||
remesa: null as number | null,
|
||||
aduana: "",
|
||||
customs_broker_id: "",
|
||||
provider_id: "",
|
||||
sold_to_id: "",
|
||||
shipped_to_id: "",
|
||||
shipped_by_id: "",
|
||||
is_mixed: false,
|
||||
waste_type: "",
|
||||
appendix_17: null as number | null,
|
||||
edocument: "",
|
||||
// Financials fields
|
||||
currency: "MXN",
|
||||
exchange_rate: null as number | null,
|
||||
value_mn: null as number | null,
|
||||
value_me: null as number | null,
|
||||
customs_value_mn: null as number | null,
|
||||
freight: null as number | null,
|
||||
insurance: null as number | null,
|
||||
iva_mn: null as number | null,
|
||||
iva_factor: null as number | null,
|
||||
total_quantity: null as number | null,
|
||||
gross_weight: null as number | null,
|
||||
net_weight: null as number | null,
|
||||
bundle_count: null as number | null
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
// VALIDACIÓN DE SEGURIDAD (Por si se refresca la página)
|
||||
if (!companyStore.activeCompany?.id) {
|
||||
error = "Error: No se detecta la compañía activa. Por favor, vuelve al dashboard y selecciona una compañía.";
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
// Construimos el Payload (LÓGICA ORIGINAL QUE FUNCIONA)
|
||||
const payload: CreateInvoiceData = {
|
||||
...formData,
|
||||
// Aseguramos que se envíen los fijos
|
||||
operation_type: FIXED_OP_TYPE,
|
||||
invoice_type: FIXED_INV_TYPE
|
||||
};
|
||||
|
||||
const response = await invoicesApi.create(companyStore.activeCompany.id, payload);
|
||||
|
||||
if (response.error) {
|
||||
if (response.status === 401) {
|
||||
error = 'Sesión expirada. Recargando...';
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ÉXITO: Volvemos a la lista
|
||||
goto('/dashboard/invoices/exportacion/reparacion');
|
||||
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : "Error al guardar";
|
||||
console.error("Error saving:", e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full mx-auto py-6 px-4 space-y-6 pb-48">
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" href="/dashboard/invoices/exportacion/reparacion">
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Nueva Factura Reparacion</h1>
|
||||
<p class="text-muted-foreground">Ingresa los datos para registrar la exportación.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="p-4 rounded-md bg-destructive/10 text-destructive border border-destructive/20 text-sm font-medium">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-6">
|
||||
<Card.Root>
|
||||
<Card.Content class="p-6">
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
|
||||
<Tabs.Content value="general" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Operación</Label>
|
||||
<div class="px-3 py-2 bg-muted rounded-md text-sm font-medium">Importación</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Factura</Label>
|
||||
<div class="px-3 py-2 bg-muted rounded-md text-sm font-medium">REPARACION (REPAR)</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="invoice_number">Número de Factura *</Label>
|
||||
<Input id="invoice_number" bind:value={formData.invoice_number} required placeholder="Número de factura" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="project_number">Número de Proyecto</Label>
|
||||
<Input id="project_number" bind:value={formData.project_number} placeholder="Número de proyecto" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="purchase_order">Orden de Compra</Label>
|
||||
<Input id="purchase_order" bind:value={formData.purchase_order} placeholder="Orden de compra" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="invoice_date">Fecha de Factura *</Label>
|
||||
<Input id="invoice_date" type="date" bind:value={formData.invoice_date} required />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="traffic_light_status">Semáforo</Label>
|
||||
<Input id="traffic_light_status" bind:value={formData.traffic_light_status} placeholder="Estado del semáforo" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="cfdi_uuid">CFDI UUID</Label>
|
||||
<Input id="cfdi_uuid" bind:value={formData.cfdi_uuid} placeholder="UUID del CFDI" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 pt-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="observation_es">Observaciones (Español)</Label>
|
||||
<Input id="observation_es" bind:value={formData.observation_es} placeholder="Observaciones en español" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="observation_en">Observaciones (Inglés)</Label>
|
||||
<Input id="observation_en" bind:value={formData.observation_en} placeholder="Observaciones en inglés" />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="compliance" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento">Pedimento</Label>
|
||||
<Input id="pedimento" bind:value={formData.pedimento} placeholder="Número de pedimento" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_code">Código de Pedimento</Label>
|
||||
<Input id="pedimento_code" bind:value={formData.pedimento_code} placeholder="R1, K1, etc." />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="remesa">Remesa</Label>
|
||||
<Input id="remesa" type="number" bind:value={formData.remesa} placeholder="Número de remesa" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="aduana">Aduana</Label>
|
||||
<Input id="aduana" bind:value={formData.aduana} placeholder="Código de aduana" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="customs_broker_id">Agente Aduanal</Label>
|
||||
<Input id="customs_broker_id" bind:value={formData.customs_broker_id} placeholder="ID del agente aduanal" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="provider_id">Proveedor</Label>
|
||||
<Input id="provider_id" bind:value={formData.provider_id} placeholder="ID del proveedor" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="edocument">E-Document</Label>
|
||||
<Input id="edocument" bind:value={formData.edocument} placeholder="Número de e-document" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 flex items-center gap-2 pt-8">
|
||||
<input id="is_mixed" type="checkbox" bind:checked={formData.is_mixed} class="h-4 w-4" />
|
||||
<Label for="is_mixed" class="!mt-0">Operación Mixta</Label>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="financials" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="currency">Moneda</Label>
|
||||
<Input id="currency" bind:value={formData.currency} placeholder="MXN, USD, etc." />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="exchange_rate">Tipo de Cambio</Label>
|
||||
<Input id="exchange_rate" type="number" step="0.000001" bind:value={formData.exchange_rate} placeholder="Tipo de cambio" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="value_mn">Valor MN</Label>
|
||||
<Input id="value_mn" type="number" step="0.01" bind:value={formData.value_mn} placeholder="Valor MN" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="value_me">Valor ME</Label>
|
||||
<Input id="value_me" type="number" step="0.01" bind:value={formData.value_me} placeholder="Valor ME" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="customs_value_mn">Valor Aduana MN</Label>
|
||||
<Input id="customs_value_mn" type="number" step="0.01" bind:value={formData.customs_value_mn} placeholder="Valor Aduana MN" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="freight">Flete</Label>
|
||||
<Input id="freight" type="number" step="0.01" bind:value={formData.freight} placeholder="Costo Flete" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="insurance">Seguro</Label>
|
||||
<Input id="insurance" type="number" step="0.01" bind:value={formData.insurance} placeholder="Costo Seguro" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="total_quantity">Cantidad Total</Label>
|
||||
<Input id="total_quantity" type="number" step="0.01" bind:value={formData.total_quantity} placeholder="Cantidad Total" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="gross_weight">Peso Bruto</Label>
|
||||
<Input id="gross_weight" type="number" step="0.01" bind:value={formData.gross_weight} placeholder="Peso Bruto" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="net_weight">Peso Neto</Label>
|
||||
<Input id="net_weight" type="number" step="0.01" bind:value={formData.net_weight} placeholder="Peso Neto" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="bundle_count">Número de Bultos</Label>
|
||||
<Input id="bundle_count" type="number" bind:value={formData.bundle_count} placeholder="Num. Bultos" />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.List class="grid grid-cols-3 fixed bottom-24 left-1/2 -translate-x-1/2 w-[90%] max-w-lg z-40 shadow-xl bg-background border rounded-xl">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
</Tabs.Root>
|
||||
|
||||
</Card.Content>
|
||||
|
||||
</Card.Root>
|
||||
|
||||
<div class="fixed bottom-0 right-0 left-0 md:left-64 p-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="w-full mx-auto flex justify-end gap-4 px-4">
|
||||
<Button variant="outline" href="/dashboard/invoices/exportacion/reparacion">
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
<Save class="mr-2 h-4 w-4" />
|
||||
Guardar Factura
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -3,9 +3,8 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
import * as Card from "$lib/components/ui/card"; // Usamos Card para enmarcar
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { invoicesApi, type CreateInvoiceData } from "$lib/api/dashboard/a76/invoices";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle, ArrowLeft, Save } from 'lucide-svelte';
|
||||
@@ -67,8 +66,9 @@
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
error = "No hay compañía seleccionada";
|
||||
// VALIDACIÓN DE SEGURIDAD (Por si se refresca la página)
|
||||
if (!companyStore.activeCompany?.id) {
|
||||
error = "Error: No se detecta la compañía activa. Por favor, vuelve al dashboard y selecciona una compañía.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
error = null;
|
||||
|
||||
try {
|
||||
// Construimos el Payload
|
||||
// Construimos el Payload (LÓGICA ORIGINAL QUE FUNCIONA)
|
||||
const payload: CreateInvoiceData = {
|
||||
...formData,
|
||||
// Aseguramos que se envíen los fijos
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="max-w-5xl mx-auto py-6 px-4 space-y-6">
|
||||
<div class="w-full mx-auto py-6 px-4 space-y-6 pb-48">
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" href="/dashboard/invoices/importacion/cambio_regimen">
|
||||
@@ -131,14 +131,9 @@
|
||||
<Card.Content class="p-6">
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.List class="grid w-full grid-cols-3 mb-6">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="general" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Operación</Label>
|
||||
@@ -195,7 +190,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="compliance" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento">Pedimento</Label>
|
||||
<Input id="pedimento" bind:value={formData.pedimento} placeholder="Número de pedimento" />
|
||||
@@ -239,7 +234,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="financials" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="currency">Moneda</Label>
|
||||
<Input id="currency" bind:value={formData.currency} placeholder="MXN, USD, etc." />
|
||||
@@ -297,11 +292,20 @@
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.List class="grid grid-cols-3 fixed bottom-24 left-1/2 -translate-x-1/2 w-[90%] max-w-lg z-40 shadow-xl bg-background border rounded-xl">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
</Tabs.Root>
|
||||
|
||||
</Card.Content>
|
||||
|
||||
<Card.Footer class="flex justify-end gap-4 border-t bg-muted/20 p-6">
|
||||
</Card.Root>
|
||||
|
||||
<div class="fixed bottom-0 right-0 left-0 md:left-64 p-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="w-full mx-auto flex justify-end gap-4 px-4">
|
||||
<Button variant="outline" href="/dashboard/invoices/importacion/cambio_regimen">
|
||||
Cancelar
|
||||
</Button>
|
||||
@@ -314,7 +318,8 @@
|
||||
Guardar Factura
|
||||
{/if}
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -3,14 +3,13 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
import * as Card from "$lib/components/ui/card"; // Usamos Card para enmarcar
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { invoicesApi, type CreateInvoiceData } from "$lib/api/dashboard/a76/invoices";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle, ArrowLeft, Save } from 'lucide-svelte';
|
||||
|
||||
// DATOS FIJOS PARA ESTA CARPETA (COMPRAS MEXICANAS)
|
||||
// DATOS FIJOS PARA ESTA CARPETA (DEFINITIVA)
|
||||
const FIXED_OP_TYPE = "imp";
|
||||
const FIXED_INV_TYPE = "MEX";
|
||||
|
||||
@@ -67,8 +66,9 @@
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
error = "No hay compañía seleccionada";
|
||||
// VALIDACIÓN DE SEGURIDAD (Por si se refresca la página)
|
||||
if (!companyStore.activeCompany?.id) {
|
||||
error = "Error: No se detecta la compañía activa. Por favor, vuelve al dashboard y selecciona una compañía.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
error = null;
|
||||
|
||||
try {
|
||||
// Construimos el Payload
|
||||
// Construimos el Payload (LÓGICA ORIGINAL QUE FUNCIONA)
|
||||
const payload: CreateInvoiceData = {
|
||||
...formData,
|
||||
// Aseguramos que se envíen los fijos
|
||||
@@ -108,21 +108,21 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="max-w-5xl mx-auto py-6 px-4 space-y-6">
|
||||
<div class="w-full mx-auto py-6 px-4 space-y-6 pb-48">
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" href="/dashboard/invoices/importacion/compras_mexicanas">
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Nueva Factura</h1>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Nueva Factura Compras Mexicanas</h1>
|
||||
<p class="text-muted-foreground">Ingresa los datos para registrar la importación.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="p-4 rounded-md bg-destructive/10 text-destructive border border-destructive/20 text-sm font-medium">
|
||||
⚠️ {error}
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -131,14 +131,9 @@
|
||||
<Card.Content class="p-6">
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.List class="grid w-full grid-cols-3 mb-6">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="general" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Operación</Label>
|
||||
@@ -195,7 +190,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="compliance" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento">Pedimento</Label>
|
||||
<Input id="pedimento" bind:value={formData.pedimento} placeholder="Número de pedimento" />
|
||||
@@ -239,7 +234,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="financials" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="currency">Moneda</Label>
|
||||
<Input id="currency" bind:value={formData.currency} placeholder="MXN, USD, etc." />
|
||||
@@ -297,12 +292,21 @@
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.List class="grid grid-cols-3 fixed bottom-24 left-1/2 -translate-x-1/2 w-[90%] max-w-lg z-40 shadow-xl bg-background border rounded-xl">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
</Tabs.Root>
|
||||
|
||||
</Card.Content>
|
||||
|
||||
<Card.Footer class="flex justify-end gap-4 border-t bg-muted/20 p-6">
|
||||
<Button variant="outline" href="/dashboard/invoices/importacion/campra_mexicanas">
|
||||
</Card.Root>
|
||||
|
||||
<div class="fixed bottom-0 right-0 left-0 md:left-64 p-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="w-full mx-auto flex justify-end gap-4 px-4">
|
||||
<Button variant="outline" href="/dashboard/invoices/importacion/compras_mexicanas">
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
@@ -314,7 +318,8 @@
|
||||
Guardar Factura
|
||||
{/if}
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -3,9 +3,8 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
import * as Card from "$lib/components/ui/card"; // Usamos Card para enmarcar
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { invoicesApi, type CreateInvoiceData } from "$lib/api/dashboard/a76/invoices";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle, ArrowLeft, Save } from 'lucide-svelte';
|
||||
@@ -67,8 +66,9 @@
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
error = "No hay compañía seleccionada";
|
||||
// VALIDACIÓN DE SEGURIDAD (Por si se refresca la página)
|
||||
if (!companyStore.activeCompany?.id) {
|
||||
error = "Error: No se detecta la compañía activa. Por favor, vuelve al dashboard y selecciona una compañía.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
error = null;
|
||||
|
||||
try {
|
||||
// Construimos el Payload
|
||||
// Construimos el Payload (LÓGICA ORIGINAL QUE FUNCIONA)
|
||||
const payload: CreateInvoiceData = {
|
||||
...formData,
|
||||
// Aseguramos que se envíen los fijos
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="max-w-5xl mx-auto py-6 px-4 space-y-6">
|
||||
<div class="w-full mx-auto py-6 px-4 space-y-6 pb-48">
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" href="/dashboard/invoices/importacion/definitiva">
|
||||
@@ -131,14 +131,9 @@
|
||||
<Card.Content class="p-6">
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.List class="grid w-full grid-cols-3 mb-6">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="general" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Operación</Label>
|
||||
@@ -147,7 +142,7 @@
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Factura</Label>
|
||||
<div class="px-3 py-2 bg-muted rounded-md text-sm font-medium">Definitiva (DEF)</div>
|
||||
<div class="px-3 py-2 bg-muted rounded-md text-sm font-medium">DEFINITIVA (DEF)</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
@@ -195,7 +190,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="compliance" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento">Pedimento</Label>
|
||||
<Input id="pedimento" bind:value={formData.pedimento} placeholder="Número de pedimento" />
|
||||
@@ -239,7 +234,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="financials" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="currency">Moneda</Label>
|
||||
<Input id="currency" bind:value={formData.currency} placeholder="MXN, USD, etc." />
|
||||
@@ -297,11 +292,20 @@
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.List class="grid grid-cols-3 fixed bottom-24 left-1/2 -translate-x-1/2 w-[90%] max-w-lg z-40 shadow-xl bg-background border rounded-xl">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
</Tabs.Root>
|
||||
|
||||
</Card.Content>
|
||||
|
||||
<Card.Footer class="flex justify-end gap-4 border-t bg-muted/20 p-6">
|
||||
</Card.Root>
|
||||
|
||||
<div class="fixed bottom-0 right-0 left-0 md:left-64 p-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="w-full mx-auto flex justify-end gap-4 px-4">
|
||||
<Button variant="outline" href="/dashboard/invoices/importacion/definitiva">
|
||||
Cancelar
|
||||
</Button>
|
||||
@@ -314,7 +318,8 @@
|
||||
Guardar Factura
|
||||
{/if}
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -3,14 +3,13 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
import * as Card from "$lib/components/ui/card"; // Usamos Card para enmarcar
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { invoicesApi, type CreateInvoiceData } from "$lib/api/dashboard/a76/invoices";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle, ArrowLeft, Save } from 'lucide-svelte';
|
||||
|
||||
// DATOS FIJOS PARA ESTA CARPETA (Temporal Importación)
|
||||
// DATOS FIJOS PARA ESTA CARPETA (TEMINITIVA)
|
||||
const FIXED_OP_TYPE = "imp";
|
||||
const FIXED_INV_TYPE = "TEM";
|
||||
|
||||
@@ -67,8 +66,9 @@
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
error = "No hay compañía seleccionada";
|
||||
// VALIDACIÓN DE SEGURIDAD (Por si se refresca la página)
|
||||
if (!companyStore.activeCompany?.id) {
|
||||
error = "Error: No se detecta la compañía activa. Por favor, vuelve al dashboard y selecciona una compañía.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
error = null;
|
||||
|
||||
try {
|
||||
// Construimos el Payload
|
||||
// Construimos el Payload (LÓGICA ORIGINAL QUE FUNCIONA)
|
||||
const payload: CreateInvoiceData = {
|
||||
...formData,
|
||||
// Aseguramos que se envíen los fijos
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="max-w-5xl mx-auto py-6 px-4 space-y-6">
|
||||
<div class="w-full mx-auto py-6 px-4 space-y-6 pb-48">
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<Button variant="outline" size="icon" href="/dashboard/invoices/importacion/temporal">
|
||||
@@ -131,14 +131,9 @@
|
||||
<Card.Content class="p-6">
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.List class="grid w-full grid-cols-3 mb-6">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="general" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Operación</Label>
|
||||
@@ -147,7 +142,7 @@
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Tipo de Factura</Label>
|
||||
<div class="px-3 py-2 bg-muted rounded-md text-sm font-medium">Temporal (TEM)</div>
|
||||
<div class="px-3 py-2 bg-muted rounded-md text-sm font-medium">TEMPORAL (TEM)</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
@@ -195,7 +190,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="compliance" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento">Pedimento</Label>
|
||||
<Input id="pedimento" bind:value={formData.pedimento} placeholder="Número de pedimento" />
|
||||
@@ -239,7 +234,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="financials" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="currency">Moneda</Label>
|
||||
<Input id="currency" bind:value={formData.currency} placeholder="MXN, USD, etc." />
|
||||
@@ -297,11 +292,20 @@
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.List class="grid grid-cols-3 fixed bottom-24 left-1/2 -translate-x-1/2 w-[90%] max-w-lg z-40 shadow-xl bg-background border rounded-xl">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="compliance">Cumplimiento</Tabs.Trigger>
|
||||
<Tabs.Trigger value="financials">Financieros</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
</Tabs.Root>
|
||||
|
||||
</Card.Content>
|
||||
|
||||
<Card.Footer class="flex justify-end gap-4 border-t bg-muted/20 p-6">
|
||||
</Card.Root>
|
||||
|
||||
<div class="fixed bottom-0 right-0 left-0 md:left-64 p-4 border-t bg-background/95 backdrop-blur z-50 flex justify-end gap-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="w-full mx-auto flex justify-end gap-4 px-4">
|
||||
<Button variant="outline" href="/dashboard/invoices/importacion/temporal">
|
||||
Cancelar
|
||||
</Button>
|
||||
@@ -314,7 +318,8 @@
|
||||
Guardar Factura
|
||||
{/if}
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
Reference in New Issue
Block a user