Refactor 'download' field to 'discharge' across models and services
- Renamed the 'download' field to 'discharge' in the FaLineItem model and updated related services and schemas accordingly. - Adjusted frontend components to reflect the new 'discharge' terminology. - Ensured consistency in filtering and validation logic across the application to use the updated field name.
This commit is contained in:
@@ -70,7 +70,7 @@ class FaLineItem(Base, TenantScopedMixin, TimestampMixin):
|
||||
subitem_number: Mapped[Optional[int]] = mapped_column(Integer) # SUBPARTIDA
|
||||
|
||||
# Special flags
|
||||
download: Mapped[Optional[bool]] = mapped_column(Boolean) # DESCARGA
|
||||
discharge: Mapped[Optional[bool]] = mapped_column(Boolean) # DESCARGA
|
||||
own_equipment: Mapped[Optional[bool]] = mapped_column(Boolean) # EQUIPOPROPIO
|
||||
omit_annex31: Mapped[Optional[bool]] = mapped_column(Boolean) # OMITITENANEXO31
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ class FaLineItemService:
|
||||
query = query.filter(
|
||||
FaLineItem.own_equipment == filters["own_equipment"]
|
||||
)
|
||||
if filters.get("download") is not None:
|
||||
query = query.filter(FaLineItem.download == filters["download"])
|
||||
if filters.get("discharge") is not None:
|
||||
query = query.filter(FaLineItem.discharge == filters["discharge"])
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
@@ -146,7 +146,7 @@ class FaLineItemService:
|
||||
search_invoice=fa_line_item_data.search_invoice,
|
||||
search_line=fa_line_item_data.search_line,
|
||||
search_type=fa_line_item_data.search_type,
|
||||
download=fa_line_item_data.download,
|
||||
discharge=fa_line_item_data.discharge,
|
||||
own_equipment=fa_line_item_data.own_equipment,
|
||||
omit_annex31=fa_line_item_data.omit_annex31,
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ from .sub_process.review_rule_octave import (
|
||||
valida_imp_regla_octava,
|
||||
descuenta_cupo_r_octava,
|
||||
)
|
||||
from .sub_process.review_uma import revisa_uma
|
||||
from ...common.process.review_uma import revisa_uma
|
||||
from .sub_process.assing_values import assign_values_lines, assign_values_invoice
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ def pre_validators(db: Session, invoice: InvoiceHeader, tenant_id: str, company_
|
||||
errors.add_required_error("compliance_mx.sold_to_id")
|
||||
|
||||
if not invoice.compliance_mx.shipped_to_id:
|
||||
errors.add_required_error("compliance_mx.shipped_by_id")
|
||||
errors.add_required_error("compliance_mx.shipped_to_id")
|
||||
|
||||
if not invoice.compliance_mx.customs_broker_id:
|
||||
errors.add_required_error("compliance_mx.customs_broker_id")
|
||||
|
||||
@@ -6,13 +6,13 @@ from typing import Optional, List
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.v1.modules.a76.general_catalogs.fractions.previous_fractions.models import PreviousFraction
|
||||
from api.v1.modules.a76.general_catalogs.unit_conversions.models import UnitConversion
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.items.models import LineItem
|
||||
from api.v1.modules.a76.rule_octave.balances.models import OctaveBalance
|
||||
from api.v1.modules.a76.rule_octave.country.models import CountryRuleOct
|
||||
from api.v1.modules.a76.rule_octave.fractions.models import FractionRuleOctave
|
||||
from api.v1.modules.a76.rule_octave.permissions.models import OctavePermission
|
||||
from ....common.process.review_equivalence import _get_unit_equivalence
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
# Clarion date 80354 ≈ 2010-05-31 (see previous_fractions/models.py)
|
||||
@@ -50,51 +50,6 @@ class OctavePermitEntry:
|
||||
# Helpers
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _get_unit_equivalence(
|
||||
db: Session,
|
||||
from_unit: str,
|
||||
to_unit: str,
|
||||
tenant_id: str,
|
||||
company_id: str,
|
||||
) -> tuple[str, Decimal]:
|
||||
"""
|
||||
Busca una conversión entre dos unidades de medida.
|
||||
Paridad: REVEQUIVALENCIA (Clarion SCAII).
|
||||
|
||||
Retorna (multi_divide, factor_conv):
|
||||
- ('M', factor) → multiplicar cantidad por factor
|
||||
- ('D', factor) → dividir cantidad por factor
|
||||
- ('', 0) → no existe equivalencia
|
||||
"""
|
||||
conv = (
|
||||
db.query(UnitConversion)
|
||||
.filter(
|
||||
UnitConversion.tenant_id == tenant_id,
|
||||
UnitConversion.company_id == company_id,
|
||||
UnitConversion.from_unit_code == from_unit,
|
||||
UnitConversion.to_unit_code == to_unit,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if conv and conv.conversion_factor:
|
||||
return "M", conv.conversion_factor
|
||||
|
||||
conv_inv = (
|
||||
db.query(UnitConversion)
|
||||
.filter(
|
||||
UnitConversion.tenant_id == tenant_id,
|
||||
UnitConversion.company_id == company_id,
|
||||
UnitConversion.from_unit_code == to_unit,
|
||||
UnitConversion.to_unit_code == from_unit,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if conv_inv and conv_inv.conversion_factor:
|
||||
return "D", conv_inv.conversion_factor
|
||||
|
||||
return "", Decimal(0)
|
||||
|
||||
|
||||
def _previous_fraction_exists(
|
||||
db: Session,
|
||||
tenant_id: str,
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasureCustoms
|
||||
from api.v1.modules.a76.items.models import LineItem
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
from .review_rule_octave import _get_unit_equivalence
|
||||
|
||||
|
||||
def revisa_uma(
|
||||
db: Session,
|
||||
line: LineItem,
|
||||
tenant_id: str,
|
||||
company_id: str,
|
||||
errors: ErrorCollector,
|
||||
) -> None:
|
||||
"""
|
||||
Revisa la unidad de medida americana (UMA) de aduana para una partida.
|
||||
Paridad: REVISA_UMA (Clarion SCAII).
|
||||
|
||||
Flujo:
|
||||
1. Verifica que la UM de la partida exista en el catálogo GUnimedida.
|
||||
2. Verifica que esa UM tenga asignada una Clave_AMex (UM aduana mexicana).
|
||||
3. Verifica que la Clave_AMex exista en el catálogo GUMAduana.
|
||||
4. Verifica que el registro de aduana tenga una UnidadSCAII (a76_unit_code).
|
||||
5. Calcula la cantidad UMA:
|
||||
- Si la UM de la partida coincide con UnidadSCAII → cantidad directa.
|
||||
- Si no → busca conversión en el catálogo de equivalencias.
|
||||
6. Escribe CantImpoUMA y ClaveUMA de regreso en la partida.
|
||||
"""
|
||||
uom = line.unit_of_measure_info
|
||||
line_um_code = uom.code if uom else ""
|
||||
|
||||
# ── 1. Verificar existencia de la UM en el catálogo ──────────────────────
|
||||
if uom is None:
|
||||
errors.add_error(
|
||||
field=f"line[{line.line_number}].unit_of_measure",
|
||||
message=(
|
||||
f"No existe la unidad de medida {line_um_code}"
|
||||
" en el catálogo de Unidades de Medida."
|
||||
),
|
||||
solution=["Capturarla en el catálogo de Unidades de Medida."],
|
||||
code="UNIMEDIDA",
|
||||
)
|
||||
return
|
||||
|
||||
# ── 2. Verificar que tenga Clave_AMex asignada ───────────────────────────
|
||||
customs_code = uom.customs_code or ""
|
||||
if not customs_code:
|
||||
errors.add_error(
|
||||
field=f"line[{line.line_number}].unit_of_measure",
|
||||
message=(
|
||||
f"No existe la unidad de medida de la aduana para la unidad de medida "
|
||||
f"{line_um_code} en el catálogo de Unidades de Medida."
|
||||
),
|
||||
solution=["Asignar la U.M.A en el catálogo de Unidades de Medida."],
|
||||
code="UNIMEDIDA",
|
||||
)
|
||||
return
|
||||
|
||||
# ── 3. Buscar la UM en el catálogo de Aduana Mex (GUMAduana) ─────────────
|
||||
customs_uom = (
|
||||
db.query(UnitOfMeasureCustoms)
|
||||
.filter(UnitOfMeasureCustoms.code == customs_code)
|
||||
.first()
|
||||
)
|
||||
|
||||
if customs_uom is None:
|
||||
errors.add_error(
|
||||
field=f"line[{line.line_number}].unit_of_measure",
|
||||
message=(
|
||||
f"No existe la unidad de medida {customs_code}"
|
||||
" en el catálogo de Unidades de Medida de la Aduana Mex."
|
||||
),
|
||||
solution=["Actualizar sus Catálogos Fijos o llamar a su proveedor."],
|
||||
code="UNIMEDIDA",
|
||||
)
|
||||
return
|
||||
|
||||
# ── 4. Verificar que tenga UnidadSCAII asignada ──────────────────────────
|
||||
scaii_unit = customs_uom.a76_unit_code or ""
|
||||
if not scaii_unit:
|
||||
errors.add_error(
|
||||
field=f"line[{line.line_number}].unit_of_measure",
|
||||
message=(
|
||||
f"No tiene asignada la U.M. Equivalente SCAII la unidad de medida "
|
||||
f"{customs_code} en el catálogo de Unidades de Medida de la Aduana Mex."
|
||||
),
|
||||
solution=["Actualizar sus Catálogos Fijos o llamar a su proveedor."],
|
||||
code="UNIMEDIDA",
|
||||
)
|
||||
return
|
||||
|
||||
# ── 5. Calcular cantidad UMA ──────────────────────────────────────────────
|
||||
line_qty = Decimal(str(line.quantity.quantity or 0)) if line.quantity else Decimal(0)
|
||||
cant_uma = Decimal(0)
|
||||
|
||||
if line_um_code == scaii_unit:
|
||||
cant_uma = line_qty
|
||||
else:
|
||||
multi_divide, factor_conv = _get_unit_equivalence(
|
||||
db, line_um_code, scaii_unit, tenant_id, company_id
|
||||
)
|
||||
|
||||
if multi_divide == "M":
|
||||
cant_uma = line_qty * factor_conv
|
||||
elif multi_divide == "D":
|
||||
cant_uma = line_qty / factor_conv if factor_conv else Decimal(0)
|
||||
else:
|
||||
errors.add_error(
|
||||
field=f"line[{line.line_number}].unit_of_measure",
|
||||
message=(
|
||||
f"No hay equivalencia entre la U.M. Partida: {line_um_code}"
|
||||
f" y la U.M. Aduana {scaii_unit}."
|
||||
),
|
||||
solution=[
|
||||
"Capturar su equivalencia en el Catálogo de Conversiones "
|
||||
"o configurar la U.M.Aduana correcta en la U.M. Comercial."
|
||||
],
|
||||
code="EQUIVALENCIA",
|
||||
)
|
||||
return
|
||||
|
||||
# ── 6. Escribir resultados en la partida ──────────────────────────────────
|
||||
if line.quantity:
|
||||
line.quantity.quantity_uma = cant_uma
|
||||
line.uma_key = customs_code
|
||||
@@ -451,6 +451,8 @@ class InvoiceCollectionsCreate(InvoiceCollectionsBase):
|
||||
class InvoiceHeaderCreate(InvoiceHeaderBase):
|
||||
"""Schema for creating Invoice Header with nested relations"""
|
||||
|
||||
status: Optional[InvoiceStatus] = Field(InvoiceStatus.PENDING, description="Status: pending, processed, reversed")
|
||||
|
||||
compliance_mx: Optional[InvoiceComplianceMxCreate] = None
|
||||
financials: Optional[InvoiceFinancialsCreate] = None
|
||||
logistics: Optional[InvoiceLogisticsCreate] = None
|
||||
|
||||
@@ -95,7 +95,7 @@ def calculate_values(
|
||||
|
||||
Traduce el CALCULOS ROUTINE de Clarion:
|
||||
- Busca la factura de importación por fa_data.search_invoice (TEM → DEF como fallback)
|
||||
- Copia clase, unidad de medida, fracción (si fa_data.download), país, tipo fracción,
|
||||
- Copia clase, unidad de medida, fracción (si fa_data.discharge), país, tipo fracción,
|
||||
bultos y descripción inglés desde la línea de importación encontrada
|
||||
- Calcula valores en moneda (USD/MXN/MC) según la moneda de la factura
|
||||
"""
|
||||
@@ -168,8 +168,8 @@ def calculate_values(
|
||||
line.class_id = import_line.class_id
|
||||
line.unit_of_measure = import_line.unit_of_measure
|
||||
|
||||
# Fracción: copiar solo si fa_data.download == True (≡ ColumnaV != '')
|
||||
if fa_data and fa_data.download and import_line.customs:
|
||||
# Fracción: copiar solo si fa_data.discharge == True (≡ ColumnaV != '')
|
||||
if fa_data and fa_data.discharge and import_line.customs:
|
||||
line.customs.fraction = import_line.customs.fraction
|
||||
|
||||
if import_line.customs:
|
||||
|
||||
@@ -222,7 +222,7 @@ def validate_common(
|
||||
)
|
||||
elif (
|
||||
# Col. H: valida unidad de medida sólo cuando hay descarga
|
||||
fa_data.download is True
|
||||
fa_data.discharge is True
|
||||
and line.unit_of_measure
|
||||
and import_line.unit_of_measure
|
||||
and line.unit_of_measure != import_line.unit_of_measure
|
||||
|
||||
@@ -83,11 +83,11 @@ def validate_create(
|
||||
errors.add_required_error(field=f"line[{line_number}].fa_data.movement_type_import")
|
||||
|
||||
# Col. F: ¿Descarga la línea? (DescargaPartida) — obligatorio
|
||||
if fa_data.download is None:
|
||||
errors.add_required_error(field=f"line[{line_number}].fa_data.download")
|
||||
if fa_data.discharge is None:
|
||||
errors.add_required_error(field=f"line[{line_number}].fa_data.discharge")
|
||||
|
||||
# Col. D / E: Factura y Línea de Impo — obligatorios sólo si hay descarga
|
||||
if fa_data.download is True:
|
||||
if fa_data.discharge is True:
|
||||
if not fa_data.search_invoice:
|
||||
errors.add_required_error(field=f"line[{line_number}].fa_data.search_invoice")
|
||||
if not fa_data.search_line:
|
||||
|
||||
@@ -207,8 +207,8 @@ def validate_update(
|
||||
fa_data.movement_type_import = existing_fa_data.movement_type_import
|
||||
|
||||
# Col. F: ¿Descarga la línea? (Descarga)
|
||||
if fa_data.download is None:
|
||||
fa_data.download = existing_fa_data.download
|
||||
if fa_data.discharge is None:
|
||||
fa_data.discharge = existing_fa_data.discharge
|
||||
|
||||
# Col. D: Factura de Importación — obligatoria sólo si hay descarga
|
||||
if not fa_data.search_invoice:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from typing import Optional
|
||||
from sqlalchemy import ForeignKey, Integer, String
|
||||
from sqlalchemy import Boolean, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
@@ -16,9 +16,9 @@ class Serie(Base, TenantScopedMixin, TimestampMixin):
|
||||
serial_numbers: Mapped[Optional[str]] = mapped_column(String(50)) # SERIEEXPO
|
||||
model: Mapped[Optional[str]] = mapped_column(String(50)) # MODELOEXPO
|
||||
sub_model: Mapped[Optional[str]] = mapped_column(String(50)) # SUBMODELOEXPO
|
||||
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA
|
||||
expo_brad: Mapped[Optional[str]] = mapped_column(String(50)) # MARCAEXPO
|
||||
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCAEXPO
|
||||
number_id: Mapped[Optional[str]] = mapped_column(String(25)) # NUMIDEXPO
|
||||
discharge: Mapped[Optional[bool]] = mapped_column(Boolean) # MARCA
|
||||
|
||||
|
||||
|
||||
@@ -433,11 +433,11 @@ class RepairImportQueries:
|
||||
@staticmethod
|
||||
def build_aggregated_query(db_name: str, where_str: str, discharge_clause: str = "") -> str:
|
||||
"""Build optimized query for NORMAL mode (grouped by invoice with totals)."""
|
||||
# Use fa_item_lines.download field (true = discharged, false = not discharged)
|
||||
# Use fa_item_lines.discharge field (true = discharged, false = not discharged)
|
||||
if "SiDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = true"
|
||||
discharge_filter = "AND fil.discharge = true"
|
||||
elif "NoDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = false"
|
||||
discharge_filter = "AND fil.discharge = false"
|
||||
else:
|
||||
discharge_filter = ""
|
||||
|
||||
@@ -514,11 +514,11 @@ class RepairImportQueries:
|
||||
@staticmethod
|
||||
def build_main_query(db_name: str, where_str: str, discharge_clause: str = "") -> str:
|
||||
"""Build main SQL query for repair import data."""
|
||||
# Use fa_item_lines.download field (true = discharged, false = not discharged)
|
||||
# Use fa_item_lines.discharge field (true = discharged, false = not discharged)
|
||||
if "SiDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = true"
|
||||
discharge_filter = "AND fil.discharge = true"
|
||||
elif "NoDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = false"
|
||||
discharge_filter = "AND fil.discharge = false"
|
||||
else:
|
||||
discharge_filter = ""
|
||||
return f"""
|
||||
@@ -609,11 +609,11 @@ class RepairImportQueries:
|
||||
@staticmethod
|
||||
def build_totals_query(db_name: str, discharge_clause: str = "") -> str:
|
||||
"""Build query to get totals for a repair import invoice."""
|
||||
# Use fa_item_lines.download field (true = discharged, false = not discharged)
|
||||
# Use fa_item_lines.discharge field (true = discharged, false = not discharged)
|
||||
if "SiDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = true"
|
||||
discharge_filter = "AND fil.discharge = true"
|
||||
elif "NoDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = false"
|
||||
discharge_filter = "AND fil.discharge = false"
|
||||
else:
|
||||
discharge_filter = ""
|
||||
return f"""
|
||||
@@ -818,11 +818,11 @@ class ExportQueries:
|
||||
|
||||
Only sums partidas where is_subitem is false (main partidas, not sub-items).
|
||||
"""
|
||||
# Use fa_item_lines.download field (true = discharged, false = not discharged)
|
||||
# Use fa_item_lines.discharge field (true = discharged, false = not discharged)
|
||||
if "SiDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = true"
|
||||
discharge_filter = "AND fil.discharge = true"
|
||||
elif "NoDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = false"
|
||||
discharge_filter = "AND fil.discharge = false"
|
||||
else:
|
||||
discharge_filter = ""
|
||||
return f"""
|
||||
@@ -861,11 +861,11 @@ class ExportRepairQueries:
|
||||
@staticmethod
|
||||
def build_aggregated_query(db_name: str, where_str: str, discharge_clause: str = "") -> str:
|
||||
"""Build optimized query for NORMAL mode (grouped by invoice with totals)."""
|
||||
# Use fa_item_lines.download field (true = discharged, false = not discharged)
|
||||
# Use fa_item_lines.discharge field (true = discharged, false = not discharged)
|
||||
if "SiDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = true"
|
||||
discharge_filter = "AND fil.discharge = true"
|
||||
elif "NoDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = false"
|
||||
discharge_filter = "AND fil.discharge = false"
|
||||
else:
|
||||
discharge_filter = ""
|
||||
|
||||
@@ -1033,11 +1033,11 @@ class ExportRepairQueries:
|
||||
@staticmethod
|
||||
def build_totals_query(db_name: str, discharge_clause: str = "") -> str:
|
||||
"""Build query to get totals for an export repair invoice."""
|
||||
# Use fa_item_lines.download field (true = discharged, false = not discharged)
|
||||
# Use fa_item_lines.discharge field (true = discharged, false = not discharged)
|
||||
if "SiDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = true"
|
||||
discharge_filter = "AND fil.discharge = true"
|
||||
elif "NoDes" in discharge_clause:
|
||||
discharge_filter = "AND fil.download = false"
|
||||
discharge_filter = "AND fil.discharge = false"
|
||||
else:
|
||||
discharge_filter = ""
|
||||
return f"""
|
||||
|
||||
@@ -127,7 +127,7 @@ export interface FaLineItem {
|
||||
subitem_number?: number;
|
||||
|
||||
// Special flags
|
||||
download?: boolean;
|
||||
discharge?: boolean;
|
||||
own_equipment?: boolean;
|
||||
omit_annex31?: boolean;
|
||||
|
||||
|
||||
@@ -263,10 +263,10 @@
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-medium text-muted-foreground">Genera Descarga?</Label>
|
||||
<RadioGroup
|
||||
value={editingItem.fa_data?.download === true ? 'si' : 'no'}
|
||||
value={editingItem.fa_data?.discharge === true ? 'si' : 'no'}
|
||||
onValueChange={(v) => {
|
||||
editingItem.fa_data = editingItem.fa_data || {};
|
||||
editingItem.fa_data.download = v === 'si';
|
||||
editingItem.fa_data.discharge = v === 'si';
|
||||
}}
|
||||
class="flex gap-4"
|
||||
>
|
||||
@@ -355,10 +355,10 @@
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-medium text-muted-foreground">Genera Descarga?</Label>
|
||||
<RadioGroup
|
||||
value={editingItem.fa_data?.download === true ? 'si' : 'no'}
|
||||
value={editingItem.fa_data?.discharge === true ? 'si' : 'no'}
|
||||
onValueChange={(v) => {
|
||||
editingItem.fa_data = editingItem.fa_data || {};
|
||||
editingItem.fa_data.download = v === 'si';
|
||||
editingItem.fa_data.discharge = v === 'si';
|
||||
}}
|
||||
class="flex gap-4"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user