feat: enhance invoice validation and logistics handling for improved data integrity
This commit is contained in:
@@ -5,6 +5,7 @@ from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRat
|
||||
from api.v1.modules.a76.clients_and_providers.models import ClientProvider
|
||||
from api.v1.modules.a76.customs_brokers.models import CustomsBroker
|
||||
from api.v1.modules.public.reference_data.incoterms.models import Incoterm
|
||||
from ....models import InvoiceComplianceMx
|
||||
from api.v1.modules.a76.items.models import Item
|
||||
from api.v1.modules.public.reference_data.currency_types.models import CurrencyType
|
||||
from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection
|
||||
@@ -39,7 +40,7 @@ def validate_common(
|
||||
)
|
||||
|
||||
if not invoice.compliance_mx.is_regime_change:
|
||||
if not pedimento.operation_type == 1:
|
||||
if not pedimento.operation_type == "imp":
|
||||
errors.add_error(
|
||||
field="compliance_mx.pedimento_id",
|
||||
message="El Pedimento seleccionado no corresponde a una Importación.",
|
||||
@@ -107,10 +108,24 @@ def validate_common(
|
||||
)
|
||||
|
||||
if pedimento.pedimento_type == "consolidated":
|
||||
if (
|
||||
invoice.invoice_date < pedimento.pedimento_dates.entry_date
|
||||
or invoice.invoice_date > pedimento.pedimento_dates.end_date
|
||||
):
|
||||
# Convertir invoice_date a date si es datetime para poder comparar
|
||||
invoice_date = (
|
||||
invoice.invoice_date.date()
|
||||
if hasattr(invoice.invoice_date, "date")
|
||||
else invoice.invoice_date
|
||||
)
|
||||
entry_date = (
|
||||
pedimento.pedimento_dates.entry_date.date()
|
||||
if hasattr(pedimento.pedimento_dates.entry_date, "date")
|
||||
else pedimento.pedimento_dates.entry_date
|
||||
)
|
||||
end_date = (
|
||||
pedimento.pedimento_dates.end_date.date()
|
||||
if hasattr(pedimento.pedimento_dates.end_date, "date")
|
||||
else pedimento.pedimento_dates.end_date
|
||||
)
|
||||
|
||||
if invoice_date < entry_date or invoice_date > end_date:
|
||||
errors.add_error(
|
||||
field="invoice_date",
|
||||
message=f"La Fecha de la Factura {invoice.invoice_date} no está dentro del rango de fechas del Pedimento {pedimento.customs_office}-{pedimento.license}-{pedimento.pedimento_number}.",
|
||||
@@ -139,23 +154,23 @@ def validate_common(
|
||||
)
|
||||
|
||||
duplicated_remesa = (
|
||||
db.query(Pedimentos)
|
||||
db.query(InvoiceComplianceMx)
|
||||
.filter(
|
||||
Pedimentos.remesa == invoice.compliance_mx.remesa,
|
||||
Pedimentos.id != invoice.compliance_mx.pedimento_id,
|
||||
Pedimentos.tenant_id == tenant_id,
|
||||
Pedimentos.company_id == company_id,
|
||||
InvoiceComplianceMx.remesa == invoice.compliance_mx.remesa,
|
||||
InvoiceComplianceMx.tenant_id == tenant_id,
|
||||
InvoiceComplianceMx.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if duplicated_remesa:
|
||||
errors.add_error(
|
||||
field="compliance_mx.remesa",
|
||||
message="El valor de Remesa ya está asociado a otro Pedimento.",
|
||||
solution=["Proporciona un valor único para Remesa"],
|
||||
code="DUPLICATE_VALUE",
|
||||
value=invoice.compliance_mx.remesa,
|
||||
)
|
||||
if duplicated_remesa and hasattr(invoice, "id"):
|
||||
if invoice.id != duplicated_remesa.invoice_id:
|
||||
errors.add_error(
|
||||
field="compliance_mx.remesa",
|
||||
message="El valor de Remesa ya está asociado a otro Pedimento.",
|
||||
solution=["Proporciona un valor único para Remesa"],
|
||||
code="DUPLICATE_VALUE",
|
||||
value=invoice.compliance_mx.remesa,
|
||||
)
|
||||
else:
|
||||
if not invoice.compliance_mx.is_pedimento_pending:
|
||||
errors.add_error(
|
||||
@@ -388,7 +403,11 @@ def validate_common(
|
||||
value=invoice.financials.currency,
|
||||
)
|
||||
|
||||
if invoice.financials.currency == "manual":
|
||||
if invoice.financials.currency == "foreign":
|
||||
invoice.financials.currency_type = "USD"
|
||||
elif invoice.financials.currency == "local":
|
||||
invoice.financials.currency_type = "MXN"
|
||||
elif invoice.financials.currency == "manual":
|
||||
if not invoice.financials.currency_type:
|
||||
errors.add_error(
|
||||
field="financials.currency_type",
|
||||
|
||||
@@ -228,7 +228,7 @@ export interface Invoice {
|
||||
enajenation_goods?: boolean | null;
|
||||
compliance_mx?: InvoiceComplianceMx | null;
|
||||
financials?: InvoiceFinancials | null;
|
||||
logistics?: InvoiceLogistics[];
|
||||
logistics?: InvoiceLogistics;
|
||||
details?: InvoiceSalesDetails[];
|
||||
collections?: InvoiceCollections[];
|
||||
}
|
||||
|
||||
@@ -18,6 +18,43 @@ function formatDate(date?: string | null): string {
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Invoice>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "operation_type",
|
||||
header: "Operación",
|
||||
cell: ({ row }) => {
|
||||
const operationType = row.original.operation_type;
|
||||
|
||||
const operationSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => {
|
||||
const { type } = getType();
|
||||
const isImport = type === 'imp';
|
||||
const colorClass = isImport ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200';
|
||||
const label = isImport ? 'Importación' : type === 'exp' ? 'Exportación' : '-';
|
||||
return {
|
||||
render: () =>
|
||||
`<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">
|
||||
${label}
|
||||
</span>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(operationSnippet, { type: operationType });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "invoice_type",
|
||||
header: "Tipo Factura",
|
||||
cell: ({ row }) => {
|
||||
const invoiceType = row.original.invoice_type;
|
||||
|
||||
const typeSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => {
|
||||
const { type } = getType();
|
||||
return {
|
||||
render: () =>
|
||||
`<div class="text-sm font-medium">${type || '-'}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(typeSnippet, { type: invoiceType });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "invoice_number",
|
||||
header: "Núm. Factura",
|
||||
@@ -113,7 +150,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Invoice>[] {
|
||||
},
|
||||
{
|
||||
accessorKey: "total_items",
|
||||
header: "Total Items",
|
||||
header: "Total Partidas",
|
||||
cell: ({ row }) => {
|
||||
// El total de items viene del conteo de details
|
||||
const totalItems = row.original.details?.length || 0;
|
||||
@@ -165,7 +202,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Invoice>[] {
|
||||
header: "Tipo Peso",
|
||||
cell: ({ row }) => {
|
||||
// weight_type está en logistics que es un array, tomamos el primer elemento
|
||||
const weightType = row.original.logistics?.[0]?.weight_type;
|
||||
const weightType = row.original.logistics?.weight_type;
|
||||
|
||||
const weightSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => {
|
||||
const { type } = getType();
|
||||
|
||||
@@ -71,11 +71,11 @@
|
||||
exchange_rate: invoice.financials?.exchange_rate || null, // Added exchange_rate
|
||||
weight_type: 'kgs',
|
||||
iva_factor: invoice.financials?.iva_factor || null,
|
||||
carrier_id: invoice.logistics?.[0]?.carrier_id || null,
|
||||
transport_id: invoice.logistics?.[0]?.transport_id || '',
|
||||
driver_name: invoice.logistics?.[0]?.driver_name || '',
|
||||
transport_type: invoice.logistics?.[0]?.transport_type || '',
|
||||
transport_num: invoice.logistics?.[0]?.vehicle_num || '',
|
||||
carrier_id: invoice.logistics?.carrier_id || null,
|
||||
transport_id: invoice.logistics?.transport_id || '',
|
||||
driver_name: invoice.logistics?.driver_name || '',
|
||||
transport_type: invoice.logistics?.transport_type || '',
|
||||
transport_num: invoice.logistics?.vehicle_num || '',
|
||||
aduana: invoice.compliance_mx?.aduana || '',
|
||||
document_type: invoice.document_type || '',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user