diff --git a/backend/api/v1/modules/a76/app_settings/schemas.py b/backend/api/v1/modules/a76/app_settings/schemas.py index 13dbf0da..a15d8e22 100644 --- a/backend/api/v1/modules/a76/app_settings/schemas.py +++ b/backend/api/v1/modules/a76/app_settings/schemas.py @@ -991,6 +991,26 @@ class SettingsPayload(BaseModel): qsisimpo: Optional[QSisImpoSettings] = None qsisimporep: Optional[QSisImpoRepSettings] = None qsisexpo: Optional[QSisExpoSettings] = None + + # Unified Invoice Settings + invoices: Optional["InvoiceSettingsMap"] = None + +class InvoiceSettingsData(BaseModel): + """Container for form-specific invoice settings""" + InvoiceTopFieldsFormData: Optional[Dict[str, Any]] = None + generalFormData: Optional[Dict[str, Any]] = None + observationFormData: Optional[Dict[str, Any]] = None + itemsFormData: Optional[Dict[str, Any]] = None + othersFormData: Optional[Dict[str, Any]] = None + continuationFormData: Optional[Dict[str, Any]] = None + +class InvoiceSettingsMap(BaseModel): + """ + Map of invoice settings indexed by operation_type (imp/exp) + and then by invoice_type. + Example: {"imp": {"factura_importacion": {...}}} + """ + types: Optional[Dict[str, Dict[str, InvoiceSettingsData]]] = None class AppSettingRequest(BaseModel): tenant_id: Optional[int] = None diff --git a/backend/api/v1/modules/a76/invoice_settings/services.py b/backend/api/v1/modules/a76/invoice_settings/services.py index 996fcac7..4e477142 100644 --- a/backend/api/v1/modules/a76/invoice_settings/services.py +++ b/backend/api/v1/modules/a76/invoice_settings/services.py @@ -1,8 +1,6 @@ -from typing import List, Optional +from typing import List, Optional, Any, Dict from sqlalchemy.orm import Session -from sqlalchemy import select -from fastapi import HTTPException -from api.v1.modules.a76.invoice_settings.models import InvoiceSettings +from api.v1.modules.a76.app_settings.service import AppSettingsService from api.v1.modules.a76.invoice_settings.dto import InvoiceSettingsRequest, OperationType def get_settings( @@ -11,60 +9,90 @@ def get_settings( company_id: int, invoice_type: str, operation_type: OperationType -) -> Optional[InvoiceSettings]: - """Retrieve settings for a specific context""" - stmt = select(InvoiceSettings).where( - InvoiceSettings.tenant_id == tenant_id, - InvoiceSettings.company_id == company_id, - InvoiceSettings.invoice_type == invoice_type, - InvoiceSettings.operation_type == operation_type.value - ) - return db.execute(stmt).scalar_one_or_none() +) -> Optional[Dict[str, Any]]: + """Retrieve settings for a specific context from app_settings""" + # Use AppSettingsService to get the unifed settings + app_settings = AppSettingsService.get_resolved_settings(db, tenant_id, company_id) + if not app_settings: + return None + + # Navigate to: invoices -> types -> {operation_type} -> {invoice_type} + invoices = app_settings.get("invoices", {}) + types_map = invoices.get("types", {}) + op_map = types_map.get(operation_type.value, {}) + settings_payload = op_map.get(invoice_type) + + if settings_payload is None: + return None + + return { + "id": 0, # Virtual ID for compatibility + "tenant_id": tenant_id, + "company_id": company_id, + "invoice_type": invoice_type, + "operation_type": operation_type, + "settings": settings_payload + } def list_settings( db: Session, tenant_id: int, company_id: int -) -> List[InvoiceSettings]: - """List all settings for a company""" - stmt = select(InvoiceSettings).where( - InvoiceSettings.tenant_id == tenant_id, - InvoiceSettings.company_id == company_id - ) - return db.execute(stmt).scalars().all() +) -> List[Dict[str, Any]]: + """List all settings for a company from app_settings""" + app_settings = AppSettingsService.get_resolved_settings(db, tenant_id, company_id) + if not app_settings: + return [] + + invoices = app_settings.get("invoices", {}) + types_map = invoices.get("types", {}) + + results = [] + for op_val, op_map in types_map.items(): + for inv_type, settings_payload in op_map.items(): + results.append({ + "id": 0, + "tenant_id": tenant_id, + "company_id": company_id, + "invoice_type": inv_type, + "operation_type": op_val, + "settings": settings_payload + }) + return results def upsert_settings( db: Session, tenant_id: int, company_id: int, settings_data: InvoiceSettingsRequest -) -> InvoiceSettings: - """Create or update settings""" - # Check if exists - existing = get_settings( - db, - tenant_id, - company_id, - settings_data.invoice_type, - settings_data.operation_type - ) +) -> Dict[str, Any]: + """Create or update settings in app_settings""" + # Construct the nested structure for AppSettingsService.upsert_settings + # We use deep_merge in AppSettingsService, so we just send the branch we want to update + payload = { + "invoices": { + "types": { + settings_data.operation_type.value: { + settings_data.invoice_type: settings_data.settings + } + } + } + } - if existing: - existing.settings = settings_data.settings - db.commit() - db.refresh(existing) - return existing - - # Create new - new_settings = InvoiceSettings( + # Save using the unified service + AppSettingsService.upsert_settings( + db, tenant_id=tenant_id, company_id=company_id, - invoice_type=settings_data.invoice_type, - operation_type=settings_data.operation_type.value, - settings=settings_data.settings + settings=payload ) - db.add(new_settings) - db.commit() - db.refresh(new_settings) - return new_settings + # Return the same structure as get_settings for consistency + return { + "id": 0, + "tenant_id": tenant_id, + "company_id": company_id, + "invoice_type": settings_data.invoice_type, + "operation_type": settings_data.operation_type, + "settings": settings_data.settings + } diff --git a/frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte index 0e5ff3e3..4e8fb53c 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte @@ -13,13 +13,15 @@ formData = $bindable(), exists = $bindable(), operationType = undefined, - invoiceType = undefined + invoiceType = undefined, + isSettings = false }: { invoice: Invoice | null; formData?: any; exists?: boolean; operationType?: number; invoiceType?: string; + isSettings?: boolean; } = $props(); if (!formData && invoice) { @@ -90,6 +92,15 @@ }; exists = false; } + + $effect.pre(() => { + if (formData) { + if (formData.es_ferrocarril === undefined) formData.es_ferrocarril = 'no'; + if (formData.is_mixed === undefined) formData.is_mixed = false; + if (formData.reason_export === undefined) formData.reason_export = '1'; + } + }); + let showPortModal = $state(false); function handlePortSelect(section: any) { @@ -119,7 +130,7 @@
- + formData.es_ferrocarril = v} class="flex gap-4">
@@ -203,10 +214,10 @@ {#if operationType === 1 || invoiceType === 'CR'}
-
+
- -
+ formData.reason_export = v} class="flex flex-wrap gap-4"> +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte index 6edc6d3d..64a1da37 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte @@ -33,7 +33,8 @@ operationType = undefined, defaultOperationType = undefined, exchangeRate = undefined, - invoiceType = undefined + invoiceType = undefined, + isSettings = false }: { invoice: Invoice | null; formData?: any; @@ -54,6 +55,7 @@ operationType?: number | null; exchangeRate?: number | null; invoiceType?: string; + isSettings?: boolean; } = $props(); let showManifestModal = $state(false); @@ -88,7 +90,7 @@ // RIGHT fields currency_type: invoice.financials?.currency_type || '', currency: invoice.financials?.currency || 'foreign', // foreign, local, manual - exchange_rate: invoice.financials?.exchange_rate || null, // Added exchange_rate + exchange_rate: invoice.financials?.exchange_rate || null, weight_type: 'kgs', iva_factor: invoice.financials?.iva_factor || null, carrier_id: invoice.logistics?.carrier_id || null, @@ -120,7 +122,7 @@ // RIGHT fields currency_type: '', currency: 'foreign', // foreign, local, manual - exchange_rate: null, // Added exchange_rate + exchange_rate: null, weight_type: 'kgs', iva_factor: null, carrier_id: null, @@ -135,33 +137,20 @@ electronic_signature: '' }; } - } else { - // Si formData ya existe, asegurar que tiene valores por defecto - if (formData.currency === undefined) { - formData.currency = 'foreign'; + } + + $effect.pre(() => { + if (formData) { + if (formData.currency === undefined) formData.currency = 'foreign'; + if (!formData.provider_header) formData.provider_header = 'proveedor'; + if (!formData.sold_to_header) formData.sold_to_header = 'consignado_a'; + if (!formData.shipped_to_header) formData.shipped_to_header = 'enviado_a'; + if (!formData.shipped_by_header) formData.shipped_by_header = 'enviado_por'; + if (formData.manifest_number === undefined) formData.manifest_number = ''; + if (formData.code_signature === undefined) formData.code_signature = ''; + if (formData.electronic_signature === undefined) formData.electronic_signature = ''; } - if (!formData.provider_header) { - formData.provider_header = 'proveedor'; - } - if (!formData.sold_to_header) { - formData.sold_to_header = 'consignado_a'; - } - if (!formData.shipped_to_header) { - formData.shipped_to_header = 'enviado_a'; - } - if (!formData.shipped_by_header) { - formData.shipped_by_header = 'enviado_por'; - } - if (formData.manifest_number === undefined) { - formData.manifest_number = ''; - } - if (formData.code_signature === undefined) { - formData.code_signature = ''; - } - if (formData.electronic_signature === undefined) { - formData.electronic_signature = ''; - } - } + }); // Opciones de tipo de peso const weightTypeOptions = [ @@ -378,7 +367,7 @@ {/each} - * + {#if !isSettings}*{/if} - * + {#if !isSettings}*{/if} - * + {#if !isSettings}*{/if}
Agente Aduanal Mex: {#if !isSettings}*{/if} { + if (formData) { + if ( + !formData.operation_type && + defaultOperationType !== undefined && + defaultOperationType !== null + ) { + formData.operation_type = defaultOperationType; + } + if (formData.iva_factor === undefined) + formData.iva_factor = invoice?.financials?.iva_factor || ''; + if (formData.alternate_invoice === undefined) + formData.alternate_invoice = invoice?.alternate_invoice || ''; } - // Ensure new fields exist if formData was created before - if (formData.iva_factor === undefined) - formData.iva_factor = invoice?.financials?.iva_factor || ''; - if (formData.alternate_invoice === undefined) - formData.alternate_invoice = invoice?.alternate_invoice || ''; - } + }); + // Filter invoice types based on operation type let filteredInvoiceTypes = $derived( invoiceTypes.filter((type) => { @@ -253,34 +255,36 @@
{/if} -
- - -
+ {#if !isSettings} +
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
+ {/if} {#if invoiceType === 'MEX'}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte index ba0387b7..63e1e2fb 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte @@ -15,7 +15,8 @@ legends = [], enclosure = [], operationType = undefined, - invoiceType = undefined + invoiceType = undefined, + isSettings = false }: { invoice: Invoice | null; formData?: any; @@ -27,6 +28,7 @@ enclosure?: any[]; operationType?: number; invoiceType?: string; + isSettings?: boolean; } = $props(); let selectedLegendCode = $state(''); diff --git a/frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte index f53d6c1f..e68ec0f2 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte @@ -16,7 +16,8 @@ exists = $bindable(), transportModes = [], operationType = undefined, - invoiceType = undefined + invoiceType = undefined, + isSettings = false }: { invoice: Invoice | null; formData?: any; @@ -24,6 +25,7 @@ transportModes?: any[]; operationType?: number; invoiceType?: string; + isSettings?: boolean; } = $props(); if (!formData && invoice) { @@ -96,6 +98,13 @@ exists = false; } + $effect.pre(() => { + if (formData) { + if (formData.transport_mode === undefined) formData.transport_mode = 'TRUCK'; + if (formData.is_mixed === undefined) formData.is_mixed = false; + } + }); + // Campos que no están en el backend let rfc = $state(''); let curp = $state(''); diff --git a/frontend/src/lib/components/dashboard/invoices/settings/sscr-settings-form.svelte b/frontend/src/lib/components/dashboard/invoices/settings/sscr-settings-form.svelte new file mode 100644 index 00000000..06205d22 --- /dev/null +++ b/frontend/src/lib/components/dashboard/invoices/settings/sscr-settings-form.svelte @@ -0,0 +1,727 @@ + + +
+ +
+ + General + General 2 + Fact. Mexicana + Fact. Americana + Desperdicio + Packing + +
+ + + + + +
+

Reporte de descarga según el tipo de factura

+

Incluye en la columna descripción:

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+

Número de Decimales en los Campos

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+

Formas del Cálculo de Valores y V.A.

+
+ +
+ + formData.BaseCostoMPoTotal = v} class="flex gap-4"> +
+ + +
+
+ + +
+
+
+ + +
+ + formData.CalVATotAgreMP = v} + class="flex flex-col gap-2" + > +
+ + +
+
+ + +
+
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + formData.LocCombinacionParametro = v} class="flex flex-wrap gap-3"> + {#each ['F1','F2','F3','F4','F5','F6'] as opt} +
+ + +
+ {/each} +
+
+
+ + +
+

Opciones de Descarga al Actualizar Factura

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + +
+ +
+

Mínimos límites para actualizar facturas

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+

Máximos límites para actualizar facturas

+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+ + +
+ + +
+

Otras Opciones

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + +
+

Imprimir por

+ formData.PorParteClaseMex = v} class="flex gap-4"> +
+ + +
+
+ + +
+
+
+ + +
+ +
+

Código de Barras

+
+ + +
+
+ + +
+
+ + +
+ + +

Mostrar en la Factura

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

Incluir en la Columna de Descripción

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+

Parte Complementaria de:

+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+

Ocultar en Factura

+
+
+ + +
+
+ + +
+
+
+ + +
+ +