feature/partidas-impo-bug-fix
This commit is contained in:
@@ -9,6 +9,9 @@ from ...common.fractions import search_fraction_preference
|
||||
from ...common.common_validators import item_exists
|
||||
from ...models import LineItem
|
||||
from ...line_customs.models import FractionType, LineCustom
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import (
|
||||
USTariffFraction,
|
||||
)
|
||||
from api.v1.modules.a76.items.schemas import LineItemCreate
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
@@ -22,6 +25,8 @@ from api.v1.modules.public.reference_data.valuation_methods.models import (
|
||||
from api.v1.modules.a76.parts.models import Part
|
||||
from api.v1.modules.a76.general_catalogs.company.models import Company
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def validate_common(
|
||||
db: Session,
|
||||
@@ -284,18 +289,75 @@ def validate_common(
|
||||
)
|
||||
|
||||
if line.customs.american_fraction:
|
||||
american_fraction_exists = db.query(
|
||||
exists().where(
|
||||
LineCustom.american_fraction == line.customs.american_fraction
|
||||
def _normalize_american_fraction_code(raw_code: str) -> list[str]:
|
||||
"""
|
||||
Attempts to map user input to the canonical USTariffFraction.code.
|
||||
|
||||
The catalog commonly stores dotted HTS codes (e.g. 3802.20.00.00),
|
||||
but users may paste/enter digits-only or use different separators.
|
||||
"""
|
||||
|
||||
normalized_raw = (raw_code or "").strip()
|
||||
if not normalized_raw:
|
||||
return []
|
||||
|
||||
digits_only = re.sub(r"[.\s\-]", "", normalized_raw)
|
||||
|
||||
candidates: list[str] = []
|
||||
|
||||
# 1) Exact input
|
||||
candidates.append(normalized_raw)
|
||||
|
||||
# 2) Canonical with dots if length matches common patterns
|
||||
if len(digits_only) == 10:
|
||||
candidates.append(
|
||||
f"{digits_only[:4]}.{digits_only[4:6]}.{digits_only[6:8]}.{digits_only[8:10]}"
|
||||
)
|
||||
elif len(digits_only) == 8:
|
||||
candidates.append(
|
||||
f"{digits_only[:4]}.{digits_only[4:6]}.{digits_only[6:8]}"
|
||||
)
|
||||
|
||||
# 3) Digits-only (if catalog stores without dots)
|
||||
candidates.append(digits_only)
|
||||
|
||||
# De-duplicate while preserving order
|
||||
seen: set[str] = set()
|
||||
deduped: list[str] = []
|
||||
for c in candidates:
|
||||
if not c or c in seen:
|
||||
continue
|
||||
seen.add(c)
|
||||
deduped.append(c)
|
||||
return deduped
|
||||
|
||||
raw_american_fraction = str(line.customs.american_fraction)
|
||||
candidates = _normalize_american_fraction_code(raw_american_fraction)
|
||||
|
||||
us_fraction: USTariffFraction | None = None
|
||||
for candidate in candidates:
|
||||
us_fraction = (
|
||||
db.query(USTariffFraction)
|
||||
.filter(
|
||||
USTariffFraction.code == candidate,
|
||||
USTariffFraction.tenant_id == tenant_id,
|
||||
USTariffFraction.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
).scalar()
|
||||
if not american_fraction_exists:
|
||||
if us_fraction:
|
||||
break
|
||||
|
||||
if not us_fraction:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.american_fraction",
|
||||
message="La fracción americana especificada no existe.",
|
||||
solution=["Proporciona una fracción americana valida."],
|
||||
code="AMERICAN_FRACTION_NOT_FOUND",
|
||||
)
|
||||
else:
|
||||
# Keep canonical value so downstream validators can use it safely.
|
||||
line.customs.american_fraction = us_fraction.code
|
||||
|
||||
if line.order:
|
||||
if len(line.order) > 20:
|
||||
|
||||
@@ -50,6 +50,84 @@
|
||||
// Track previous class_id to detect changes
|
||||
let previousClassId = $state<number | undefined>(undefined);
|
||||
|
||||
function normalizeFractionForBackend(raw: unknown) {
|
||||
if (raw === null || raw === undefined) return undefined;
|
||||
return String(raw).replace(/\./g, '').trim();
|
||||
}
|
||||
|
||||
async function getUnitByCode(unitCode: string) {
|
||||
const activeCompanyId = companyStore?.activeCompany?.id;
|
||||
if (!unitCode || !activeCompanyId) return undefined;
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api-sveltekit/units-of-measure?code=${encodeURIComponent(unitCode)}&page=1&page_size=20`,
|
||||
{
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
}
|
||||
);
|
||||
if (!res.ok) return undefined;
|
||||
const data = await res.json();
|
||||
const units = data?.items || data?.data || data;
|
||||
if (Array.isArray(units) && units.length > 0) return units[0];
|
||||
} catch {
|
||||
// Ignore unit lookup failures and let the user fix manually.
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function applyClassDefaults(classItem: any) {
|
||||
if (!classItem) return;
|
||||
|
||||
// Class display fields
|
||||
(lineItem as any).class_code = classItem.class_code;
|
||||
(lineItem as any).class_unit_of_measure = classItem.unit_of_measure;
|
||||
(lineItem as any).class_description = classItem.description_es || classItem.description_en;
|
||||
|
||||
// Descriptions
|
||||
if ((lineItem as any).description) {
|
||||
const desc = (lineItem as any).description;
|
||||
// Use != null (instead of truthy) to support valid empty strings
|
||||
if (classItem.description_es != null) desc.description_spanish = classItem.description_es;
|
||||
if (classItem.description_en != null) desc.description_english = classItem.description_en;
|
||||
}
|
||||
|
||||
// U.M. (display + internal FK id)
|
||||
const classUnitCode = classItem.unit_of_measure;
|
||||
if (classUnitCode != null) {
|
||||
// Always set display code if missing.
|
||||
if (!(lineItem as any).unit_code) (lineItem as any).unit_code = classUnitCode;
|
||||
|
||||
// Only resolve and override the FK if it isn't set yet.
|
||||
if (!lineItem.unit_of_measure) {
|
||||
const unit = await getUnitByCode(String(classUnitCode));
|
||||
if (unit) {
|
||||
lineItem.unit_of_measure = unit.id;
|
||||
(lineItem as any).unit_code = unit.code;
|
||||
(lineItem as any).unit_description = unit.description || unit.description_en;
|
||||
quantities.unit_of_measure = unit.code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fracción arancelaria (Mex / SCAII)
|
||||
if (!customs.fraction && classItem.fraction != null) {
|
||||
customs.fraction = normalizeFractionForBackend(classItem.fraction);
|
||||
}
|
||||
|
||||
// Tipo de fracción
|
||||
if (!customs.fraction_type && classItem.import_tariff_type) {
|
||||
customs.fraction_type = classItem.import_tariff_type;
|
||||
}
|
||||
|
||||
// Fracción americana (HTS / US)
|
||||
if (!customs.american_fraction && classItem.us_fraction) {
|
||||
customs.american_fraction = classItem.us_fraction;
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for class_id changes and update descriptions automatically
|
||||
$effect(() => {
|
||||
const currentClassId = lineItem.class_id;
|
||||
@@ -72,20 +150,8 @@
|
||||
const classItem = classes.find((c: any) => c.id === currentClassId);
|
||||
|
||||
if (classItem) {
|
||||
// Store the code and description in the lineItem for display
|
||||
(lineItem as any).class_code = classItem.class_code;
|
||||
(lineItem as any).class_unit_of_measure = classItem.unit_of_measure;
|
||||
(lineItem as any).class_description = classItem.description_es || classItem.description_en;
|
||||
|
||||
// Update description fields if description object exists
|
||||
if ((lineItem as any).description) {
|
||||
if (classItem.description_es) {
|
||||
(lineItem as any).description.description_spanish = classItem.description_es;
|
||||
}
|
||||
if (classItem.description_en) {
|
||||
(lineItem as any).description.description_english = classItem.description_en;
|
||||
}
|
||||
}
|
||||
// Apply defaults derived from catalog class selection.
|
||||
void applyClassDefaults(classItem);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -96,20 +162,7 @@
|
||||
|
||||
function handleClassSelect(classItem: any) {
|
||||
lineItem.class_id = classItem.id;
|
||||
// Store the unit of measure and description for display
|
||||
(lineItem as any).class_unit_of_measure = classItem.unit_of_measure;
|
||||
(lineItem as any).class_code = classItem.class_code;
|
||||
(lineItem as any).class_description = classItem.description_es || classItem.description_en;
|
||||
|
||||
// Update description fields if description object exists
|
||||
if ((lineItem as any).description) {
|
||||
if (classItem.description_es) {
|
||||
(lineItem as any).description.description_spanish = classItem.description_es;
|
||||
}
|
||||
if (classItem.description_en) {
|
||||
(lineItem as any).description.description_english = classItem.description_en;
|
||||
}
|
||||
}
|
||||
void applyClassDefaults(classItem);
|
||||
};
|
||||
|
||||
function handleUnitSelect(unit: any) {
|
||||
|
||||
@@ -37,10 +37,29 @@
|
||||
});
|
||||
|
||||
// Calculate total package weight
|
||||
const totalPackageWeight = $derived.by(() => {
|
||||
const qty = quantities.package_quantity || 0;
|
||||
const weightPerUnit = package_weight_unit || 0;
|
||||
return (qty * weightPerUnit).toFixed(4);
|
||||
const totalPackageWeightNum = $derived.by(() => {
|
||||
const qty = Number(quantities.package_quantity ?? 0);
|
||||
const weightPerUnit = Number(package_weight_unit ?? 0);
|
||||
return qty * weightPerUnit;
|
||||
});
|
||||
|
||||
const totalPackageWeight = $derived.by(() => totalPackageWeightNum.toFixed(4));
|
||||
const computedGrossWeight = $derived.by(() => {
|
||||
const net = Number(quantities.net_weight ?? 0);
|
||||
return net + totalPackageWeightNum;
|
||||
});
|
||||
|
||||
// Auto-calculate gross weight = net weight + weight of packages (if gross wasn't provided)
|
||||
$effect(() => {
|
||||
const netWeight = quantities.net_weight;
|
||||
if (netWeight === null || netWeight === undefined) return;
|
||||
|
||||
const currentGross = quantities.gross_weight;
|
||||
const shouldAutoFill = currentGross === null || currentGross === undefined || Number(currentGross) === 0;
|
||||
if (!shouldAutoFill) return;
|
||||
|
||||
// Keep precision stable enough for the UI inputs.
|
||||
quantities.gross_weight = Number(computedGrossWeight.toFixed(8));
|
||||
});
|
||||
|
||||
// Load or sync package data when package_id exists
|
||||
|
||||
Reference in New Issue
Block a user