Files
plantillas-proyectos/backend/api/v1/modules/a76/invoices/schemas.py
2026-02-23 13:05:02 -06:00

637 lines
25 KiB
Python

from typing import Literal, Optional, List
from datetime import datetime, date
from decimal import Decimal
from pydantic import BaseModel, Field, field_validator
from .models import (
DestinationOriginCove,
OperationType,
Currency,
TransportType,
WeightUnit,
)
# --- Base Schemas ---
class InvoiceHeaderBase(BaseModel):
"""Base fields for Invoice Header"""
system: Optional[str] = Field(None, max_length=12, description="System of origin")
operation_type: Optional[OperationType] = Field(
..., description="Operation type: imp/exp/sm/ctm"
)
@field_validator("operation_type", mode="before")
@classmethod
def normalize_operation_type(cls, v):
"""Accept DB string (e.g. 'IMP') and coerce to enum value ('imp')."""
if isinstance(v, str):
return v.lower() if v else v
return v
invoice_type: Optional[str] = Field(
None, max_length=5, description="Invoice type key"
)
document_type: Optional[str] = Field(
None, max_length=3, description="Document type (Regimen Aduanero)"
)
invoice_number: Optional[str] = Field(
None, max_length=100, description="Invoice number"
)
project_number: Optional[str] = Field(
None, max_length=14, description="Project number"
)
purchase_order: Optional[str] = Field(
None, max_length=50, description="Purchase order"
)
related_doc_id: Optional[int] = Field(
None, description="Related document ID for rectifications"
)
alternate_invoice: Optional[str] = Field(
None, max_length=99, description="Alternate invoice"
)
invoice_ref: Optional[str] = Field(
None, max_length=19, description="Invoice reference"
)
proforma_number: Optional[str] = Field(
None, max_length=20, description="Proforma number"
)
invoice_date: date = Field(..., description="Invoice date")
emission_date: Optional[date] = Field(None, description="Emission date")
is_updated: bool = Field(False, description="Status")
updated_date: Optional[datetime] = Field(None, description="Update date")
who_updated: Optional[str] = Field(None, max_length=20, description="Who updated")
capture_user: Optional[str] = Field(None, max_length=20, description="Capture user")
traffic_light_status: Optional[str] = Field(
None, max_length=50, description="Traffic light status"
)
process_log: Optional[str] = Field(
None, max_length=300, description="Processing log"
)
is_updated_rec: Optional[int] = Field(None, description="Reception status")
is_updated_rep: Optional[str] = Field(
None, max_length=2, description="Report status"
)
observation_es: Optional[str] = Field(None, description="Observations in Spanish")
observation_en: Optional[str] = Field(None, description="Observations in English")
comments_status: Optional[str] = Field(None, description="Comments status")
vu_observations: Optional[str] = Field(
None, max_length=500, description="VUCEM observations"
)
cfdi_uuid: Optional[str] = Field(None, max_length=100, description="CFDI UUID")
path_pdf: Optional[str] = Field(
None, max_length=500, description="Path to PDF file"
)
path_xml: Optional[str] = Field(
None, max_length=500, description="Path to XML file"
)
subcompany: Optional[str] = Field(None, max_length=5, description="Subcompany")
party_count: Optional[int] = Field(None, description="Quantity of parties")
generate_id: Optional[bool] = Field(False, description="Generate ID")
generate_desc_parties: Optional[str] = Field(
None, max_length=12, description="Generate description of parties"
)
apply_manual_discount: Optional[bool] = Field(
False, description="Apply manual discount"
)
is_bulk: Optional[bool] = Field(None, description="Is bulk")
download_substance: Optional[bool] = Field(None, description="Download substance")
download_class: Optional[bool] = Field(None, description="Download class")
download_def: Optional[bool] = Field(None, description="Definitive download")
payment_terms: Optional[str] = Field(
None, max_length=200, description="Payment terms"
)
handling_fees: Optional[Decimal] = Field(None, description="Handling fees")
option_iv18: Optional[str] = Field(None, max_length=50, description="Option IV18")
enajenation_goods: Optional[bool] = Field(None, description="Enajenation of goods")
class InvoiceComplianceMxBase(BaseModel):
"""Base fields for Compliance MX"""
pedimento_id: Optional[int] = Field(None, description="Pedimento id")
pedimento_r1: Optional[int] = Field(None, description="Pedimento id (R1)")
pedimento_k1: Optional[int] = Field(None, description="Pedimento id (K1)")
remesa: Optional[int] = Field(None, description="Remesa")
aduana: Optional[str] = Field(None, max_length=5, description="Customs office")
port_of_entry: Optional[str] = Field(
None, max_length=6, description="Port of entry"
)
destination: Optional[str] = Field(
None, max_length=3, description="Destination code"
)
manifest_number: Optional[str] = Field(
None, max_length=15, description="Manifest number"
)
provider_header: Optional[str] = Field(None, max_length=20, description="Provider header")
provider_id: Optional[int] = Field(None, description="Provider ID")
sold_to_header: Optional[str] = Field(None, max_length=20, description="Sold to header")
sold_to_id: Optional[int] = Field(None, description="Sold to ID")
shipped_to_header: Optional[str] = Field(None, max_length=20, description="Shipped to header")
shipped_to_id: Optional[int] = Field(None, description="Shipped to ID")
shipped_by_header: Optional[str] = Field(
None, max_length=20, description="Shipped by header"
)
shipped_by_id: Optional[int] = Field(None, description="Shipped by ID")
customs_broker_id: Optional[int] = Field(None, description="Customs broker ID")
customs_broker_us_id: Optional[int] = Field(
None, description="US customs broker ID"
)
broker_invoice_num: Optional[str] = Field(
None, max_length=20, description="Broker invoice number"
)
broker_invoice_date: Optional[date] = Field(None, description="Broker invoice date")
is_mixed: Optional[bool] = Field(False, description="Is mixed operation")
waste_type: Optional[str] = Field(None, max_length=1, description="Waste type")
scrap_type: Optional[str] = Field(None, max_length=1, description="Scrap type")
appendix_17: Optional[int] = Field(None, description="Appendix 17")
is_regime_change: Optional[bool] = Field(False, description="Is regime change")
which_exchange_rate: Optional[str] = Field(
None, max_length=5, description="Which exchange rate"
)
rule_3121_parties_ii: Optional[bool] = Field(
False, description="Is Rule 3.1.21 Parties II"
)
value_method: Optional[str] = Field(None, max_length=2, description="Value method")
act_value: Optional[str] = Field(None, max_length=5, description="Act value")
is_pedimento_pending: bool = Field(..., description="Is pedimento pending")
is_owner_of_goods: Optional[bool] = Field(False, description="Is owner of goods")
generate_balances: Optional[bool] = Field(False, description="Generate balances")
was_reviewed_by_company: Optional[bool] = Field(
None, description="Was reviewed by company"
)
edocument: Optional[str] = Field(None, max_length=50, description="E-document")
electronic_signature: Optional[str] = Field(
None, max_length=999, description="Electronic signature"
)
certificate_number: Optional[str] = Field(
None, max_length=99, description="Certificate number"
)
niu_number: Optional[str] = Field(None, max_length=19, description="NIU number")
bill_of_lading_count: Optional[str] = Field(
None, max_length=12, description="Bill of lading count"
)
addendum_vu: Optional[str] = Field(
None, max_length=204, description="VUCEM addendum"
)
origin_destination_cove: Optional[DestinationOriginCove] = Field(
"franja_front_norte", max_length=20, description="Origin/Destination COVE"
)
vucem_operation_num: Optional[str] = Field(
None, max_length=19, description="VUCEM operation number"
)
customs_person_line: Optional[int] = Field(None, description="Customs person line")
contingency_mode: Optional[bool] = Field(None, description="Contingency mode")
enclosure: Optional[str] = Field(None, max_length=4, description="Enclosure")
guide_type_to_identify: Optional[str] = Field(
None, max_length=1, description="Guide type to identify"
)
location: Optional[str] = Field(None, max_length=200, description="Location")
dot_code: Optional[str] = Field(None, max_length=20, description="DOT code")
subdivision: Optional[str] = Field(None, max_length=20, description="Subdivision")
acts_as: Optional[str] = Field(None, max_length=20, description="Acts as")
movement_type: Optional[str] = Field(
None, max_length=31, description="Movement type"
)
office_document: Optional[str] = Field(
None, max_length=30, description="Office document"
)
reason_export: Optional[str] = Field(
None, max_length=1, description="Reason for export"
)
signature_key: Optional[str] = Field(
None, max_length=100, description="Signature key"
)
sem_id: Optional[int] = Field(None, description="SEM ID")
class InvoiceFinancialsBase(BaseModel):
"""Base fields for Financials"""
currency: Optional[Currency] = Field(None, max_length=7, description="Currency code")
currency_type: Optional[str] = Field("USD", description="Currency type")
exchange_rate: Optional[Decimal] = Field(0.00, description="Exchange rate")
exchange_rate_mm: Optional[Decimal] = Field(
None, description="Exchange rate currency to currency"
)
value_mn: Optional[Decimal] = Field(None, description="Value in MXN")
value_me: Optional[Decimal] = Field(None, description="Value in foreign currency")
value_mc: Optional[Decimal] = Field(None, description="Value in third currency")
customs_value_mn: Optional[Decimal] = Field(
None, description="Customs value in MXN"
)
customs_value_me: Optional[Decimal] = Field(
None, description="Customs value in foreign currency"
)
raw_material_value_mn: Optional[Decimal] = Field(
None, description="Raw material value in MXN"
)
raw_material_value_me: Optional[Decimal] = Field(
None, description="Raw material value in foreign currency"
)
aggregate_value_mn: Optional[Decimal] = Field(
None, description="Aggregate value in MXN"
)
aggregate_value_me: Optional[Decimal] = Field(
None, description="Aggregate value in foreign currency"
)
aggregate_value_mc: Optional[Decimal] = Field(
None, description="Aggregate value in third currency"
)
mexican_value_mn: Optional[Decimal] = Field(
None, description="Mexican merchandise value in MXN"
)
mexican_value_me: Optional[Decimal] = Field(
None, description="Mexican merchandise value in foreign currency"
)
mexican_value_mc: Optional[Decimal] = Field(
None, description="Mexican merchandise value in third currency"
)
national_packaging_mn: Optional[Decimal] = Field(
None, description="National packaging in MXN"
)
national_packaging_me: Optional[Decimal] = Field(
None, description="National packaging in foreign currency"
)
national_packaging_mc: Optional[Decimal] = Field(
None, description="National packaging in third currency"
)
freight: Optional[Decimal] = Field(None, description="Freight cost")
insurance: Optional[Decimal] = Field(None, description="Insurance cost")
insurance_value: Optional[Decimal] = Field(None, description="Insurance value")
packaging: Optional[Decimal] = Field(None, description="Packaging")
other_increments: Optional[Decimal] = Field(None, description="Other increments")
other_deductibles: Optional[Decimal] = Field(None, description="Other deductibles")
total_increments_mn: Optional[Decimal] = Field(
None, description="Total increments in MXN"
)
total_increments_me: Optional[Decimal] = Field(
None, description="Total increments in foreign currency"
)
iva_mn: Optional[Decimal] = Field(None, description="IVA in MXN")
iva_me: Optional[Decimal] = Field(None, description="IVA in foreign currency")
iva_mc: Optional[Decimal] = Field(None, description="IVA in third currency")
iva_factor: Optional[Decimal] = Field(None, description="IVA factor")
tax_value_me: Optional[Decimal] = Field(
None, description="Tax value in foreign currency"
)
seal_value_2500: Optional[bool] = Field(None, description="Seal value 2500")
total_quantity: Optional[Decimal] = Field(None, description="Total quantity")
gross_weight: Optional[Decimal] = Field(None, description="Gross weight")
net_weight: Optional[Decimal] = Field(None, description="Net weight")
bundle_count: Optional[int] = Field(None, description="Bundle count")
weight_factor: Optional[Decimal] = Field(None, description="Weight factor")
class InvoiceLogisticsBase(BaseModel):
"""Base fields for Logistics"""
carrier_id: Optional[str] = Field(None, max_length=10, description="Carrier ID")
transport_id: Optional[str] = Field(None, max_length=10, description="Transport ID")
transport_us_id: Optional[str] = Field(
None, max_length=10, description="US transport ID"
)
transport_type: TransportType = Field(
"none", max_length=15, description="Transport type"
)
transport_num: Optional[str] = Field(
None, max_length=20, description="Transport number"
)
transport_mode: Optional[str] = Field(
30, max_length=15, description="Transport mode"
)
driver_name: Optional[str] = Field(None, max_length=80, description="Driver name")
is_rail: Optional[bool] = Field(False, description="Is rail transport")
rail_id: Optional[str] = Field(None, max_length=31, description="Rail ID")
vehicle_num: Optional[str] = Field(
None, max_length=20, description="Vehicle number"
)
license_plate: Optional[str] = Field(
None, max_length=20, description="License plate"
)
license_plate_complete: Optional[str] = Field(
None, max_length=40, description="Complete license plate"
)
trailer_num: Optional[str] = Field(
None, max_length=20, description="Trailer number"
)
seal_number: Optional[str] = Field(None, max_length=15, description="Seal number")
guide_number: Optional[str] = Field(None, max_length=20, description="Guide number")
bill_number: Optional[str] = Field(None, max_length=15, description="Bill number")
reference_number: Optional[str] = Field(
None, max_length=14, description="Reference number"
)
shipment_number: Optional[str] = Field(
None, max_length=19, description="Shipment number"
)
incoterm: Optional[str] = Field(None, max_length=5, description="Incoterm")
identifier_1: Optional[str] = Field(None, max_length=2, description="Identifier 1")
complement_1: Optional[str] = Field(None, max_length=30, description="Complement 1")
identifier_2: Optional[str] = Field(None, max_length=2, description="Identifier 2")
complement_2: Optional[str] = Field(None, max_length=30, description="Complement 2")
weight_type: WeightUnit = Field(
default="kgs", max_length=3, description="Weight type"
)
@field_validator("weight_type", mode="before")
@classmethod
def normalize_weight_type(cls, v):
if isinstance(v, str):
return v.lower()
return v
container_types: Optional[str] = Field(
None, max_length=500, description="Container types"
)
vehicle_data: Optional[str] = Field(
None, max_length=500, description="Vehicle data"
)
origin_location: Optional[str] = Field(
None, max_length=200, description="Origin location"
)
destination_location: Optional[str] = Field(
None, max_length=200, description="Destination location"
)
transport_itinerary: Optional[str] = Field(
None, max_length=1000, description="Transport itinerary"
)
destination_goods: Optional[str] = Field(
None, max_length=50, description="Destination of goods"
)
entry_exit_date: Optional[date] = Field(None, description="Entry/Exit date")
delivery_date: Optional[date] = Field(None, description="Delivery date")
delivered_status: Optional[bool] = Field(False, description="Delivered status")
received_by: Optional[str] = Field(None, max_length=50, description="Received by")
payment_date: Optional[date] = Field(None, description="Payment date")
payment_receipt_num: Optional[str] = Field(
None, max_length=20, description="Payment receipt number"
)
is_ctm_process: Optional[bool] = Field(False, description="Is CTM process")
# Continuation Tab Fields
equipment_reviewed: Optional[bool] = Field(False, description="Equipment reviewed")
is_subdivision: Optional[bool] = Field(False, description="Is subdivision")
acts_as_cd: Optional[bool] = Field(False, description="Acts as CD")
pedimento_arrived: Optional[bool] = Field(False, description="Pedimento arrived")
green_light_mx: Optional[bool] = Field(False, description="Green light Mexico")
green_light_us: Optional[bool] = Field(False, description="Green light USA")
red_light_mx: Optional[bool] = Field(False, description="Red light Mexico")
red_light_us: Optional[bool] = Field(False, description="Red light USA")
class InvoiceSalesDetailsBase(BaseModel):
"""Base fields for Sales Details"""
line_number: int = Field(..., description="Line number")
sales_order: Optional[str] = Field(None, max_length=20, description="Sales order")
colors_description: Optional[str] = Field(
None, max_length=49, description="Colors description"
)
square_color_code: Optional[str] = Field(
None, max_length=1, description="Square color code"
)
line_bundles: Optional[int] = Field(None, description="Line bundles count")
class InvoiceCollectionsBase(BaseModel):
"""Base fields for Collections"""
line_number: int = Field(..., description="Line number")
invoice_number: Optional[str] = Field(
None, max_length=15, description="Invoice number"
)
concept: Optional[str] = Field(None, max_length=100, description="Concept")
# --- Create Schemas ---
class InvoiceComplianceMxCreate(InvoiceComplianceMxBase):
"""Schema for creating Compliance MX"""
pass
class InvoiceFinancialsCreate(InvoiceFinancialsBase):
"""Schema for creating Financials"""
pass
class InvoiceLogisticsCreate(InvoiceLogisticsBase):
"""Schema for creating Logistics"""
pass
class InvoiceSalesDetailsCreate(InvoiceSalesDetailsBase):
"""Schema for creating Sales Details"""
pass
class InvoiceCollectionsCreate(InvoiceCollectionsBase):
"""Schema for creating Collections"""
pass
class InvoiceHeaderCreate(InvoiceHeaderBase):
"""Schema for creating Invoice Header with nested relations"""
compliance_mx: Optional[InvoiceComplianceMxCreate] = None
financials: Optional[InvoiceFinancialsCreate] = None
logistics: Optional[InvoiceLogisticsCreate] = None
details: Optional[List[InvoiceSalesDetailsCreate]] = None
collections: Optional[List[InvoiceCollectionsCreate]] = None
# --- Update Schemas ---
class InvoiceComplianceMxUpdate(InvoiceComplianceMxBase):
"""Schema for updating Compliance MX"""
is_pedimento_pending: Optional[bool] = None
class InvoiceFinancialsUpdate(InvoiceFinancialsBase):
"""Schema for updating Financials"""
pass
class InvoiceLogisticsUpdate(InvoiceLogisticsBase):
"""Schema for updating Logistics"""
pass
class InvoiceSalesDetailsUpdate(InvoiceSalesDetailsBase):
"""Schema for updating Sales Details"""
line_number: Optional[int] = None
class InvoiceCollectionsUpdate(InvoiceCollectionsBase):
"""Schema for updating Collections"""
line_number: Optional[int] = None
class InvoiceHeaderUpdate(InvoiceHeaderBase):
"""Schema for updating Invoice Header with nested relations"""
id: int
document_type: Optional[str] = None
invoice_date: Optional[date] = None
operation_type: Optional[OperationType] = None
compliance_mx: Optional[InvoiceComplianceMxUpdate] = None
financials: Optional[InvoiceFinancialsUpdate] = None
logistics: Optional[InvoiceLogisticsUpdate] = None
details: Optional[List[InvoiceSalesDetailsUpdate]] = None
collections: Optional[List[InvoiceCollectionsUpdate]] = None
# --- Response Schemas ---
class PedimentoBasicInfo(BaseModel):
"""Basic Pedimento information for Invoice response"""
pedimento_number: Optional[str] = None
pedimento_code: Optional[str] = None
customs_office: Optional[str] = None
license: Optional[str] = None
year: Optional[str] = None
class Config:
from_attributes = True
class InvoiceComplianceMxResponse(InvoiceComplianceMxBase):
"""Schema for Compliance MX response"""
invoice_id: int
pedimento: Optional[PedimentoBasicInfo] = None
class Config:
from_attributes = True
class InvoiceFinancialsResponse(InvoiceFinancialsBase):
"""Schema for Financials response"""
id: int
invoice_id: int
class Config:
from_attributes = True
class InvoiceLogisticsResponse(InvoiceLogisticsBase):
"""Schema for Logistics response"""
id: int
invoice_id: int
class Config:
from_attributes = True
class InvoiceSalesDetailsResponse(InvoiceSalesDetailsBase):
"""Schema for Sales Details response"""
id: int
invoice_id: int
class Config:
from_attributes = True
class InvoiceCollectionsResponse(InvoiceCollectionsBase):
"""Schema for Collections response"""
id: int
invoice_id: int
class Config:
from_attributes = True
class InvoiceHeaderResponse(InvoiceHeaderBase):
"""Schema for Invoice Header response with nested relations"""
id: int
capture_date: datetime
compliance_mx: Optional[InvoiceComplianceMxResponse] = None
financials: Optional[InvoiceFinancialsResponse] = None
logistics: Optional[InvoiceLogisticsResponse] = None
details: Optional[List[InvoiceSalesDetailsResponse]] = []
collections: Optional[List[InvoiceCollectionsResponse]] = []
class Config:
from_attributes = True
class InvoiceHeaderListResponse(BaseModel):
items: List[InvoiceHeaderResponse]
total: int
page: int
# --- Consolidated Response Schemas ---
# Import necessary DTOs from other modules
from api.v1.modules.public.reference_data.invoice_types.dto import InvoiceTypeDTO
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
from api.v1.modules.public.reference_data.currency_types.dto import CurrencyTypeDTO
from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO
from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
# TODO: Check if these paths are correct or need adjustment based on actual file locations
# Using Any for now for potentially complex or unverified paths to avoid immediate ImportErrors
# Detailed verification is needed for:
# - TransporterDTO
# - VehicleDTO
# - DriverDTO
# - TrailerDTO
# - SealResponseDTO
# - PedimentoDTO
class InvoiceCatalogsResponse(BaseModel):
"""Consolidated response for all catalogs needed in Invoice Create/Edit views"""
invoice_types: List[InvoiceTypeDTO] = []
customs_brokers: List[CustomsBrokerResponseDTO] = []
clients: List[ClientProviderResponseDTO] = []
providers: List[ClientProviderResponseDTO] = []
currency_types: List[CurrencyTypeDTO] = []
transport_types: List[TransportTypeDTO] = []
transporters: List[dict] = [] # Placeholder, refine with actual DTO
vehicles: List[dict] = [] # Placeholder, refine with actual DTO
drivers: List[dict] = [] # Placeholder, refine with actual DTO
trailers: List[dict] = [] # Placeholder, refine with actual DTO
customs_sections: List[CustomsSectionDTO] = []
code_pedimento_regimens: List[CodePedimentoRegimenDTO] = []
seals: List[dict] = [] # Placeholder, refine with actual DTO
incoterms: List[IncotermDTO] = []
pedimentos: List[dict] = [] # Placeholder, refine with actual DTO
transport_modes: List[TransportModeDTO] = []
default_settings: Optional[dict] = None
class InvoiceCreationResponse(InvoiceCatalogsResponse):
"""Response for Invoice Creation View"""
is_create: bool = True
invoice: Optional[dict] = None # Should be null for creation
invoice_id: Optional[int] = None
filters: Optional[dict] = None # Pre-filled filters if any
class InvoiceEditionResponse(InvoiceCatalogsResponse):
"""Response for Invoice Edition View"""
is_create: bool = False
invoice: InvoiceHeaderResponse
invoice_id: int
filters: Optional[dict] = None