feauture/catalogo-clases-inventario-UME

This commit is contained in:
2026-06-02 11:12:38 -06:00
parent e612037668
commit e0b08dfa8a
8 changed files with 374 additions and 11 deletions

View File

@@ -0,0 +1,43 @@
"""add stock_unit_of_measure to classes
Revision ID: c8d9e0f1a2b3
Revises: b3c4d5e6f7a8
Create Date: 2026-06-02 12:00:00.000000
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = "c8d9e0f1a2b3"
down_revision: Union[str, None] = "b3c4d5e6f7a8"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column(
"classes",
sa.Column("stock_unit_of_measure", sa.String(length=5), nullable=True),
schema="a76",
)
op.create_foreign_key(
"fk_classes_stock_uom",
"classes",
"units_of_measure",
["stock_unit_of_measure", "tenant_id", "company_id"],
["code", "tenant_id", "company_id"],
source_schema="a76",
referent_schema="a76",
)
def downgrade() -> None:
op.drop_constraint(
"fk_classes_stock_uom",
"classes",
schema="a76",
type_="foreignkey",
)
op.drop_column("classes", "stock_unit_of_measure", schema="a76")

View File

@@ -28,6 +28,11 @@ class ClassCreateDTO(BaseModel):
unit_of_measure: str = Field( unit_of_measure: str = Field(
..., max_length=5, description="Unit of measure - U.M. comercial (required)" ..., max_length=5, description="Unit of measure - U.M. comercial (required)"
) )
stock_unit_of_measure: Optional[str] = Field(
None,
max_length=5,
description="UM Existencia - solo SCAII/inventory (UMEXISTENCIA)",
)
fraction: str = Field( fraction: str = Field(
..., max_length=20, description="Mexican tariff fraction (required)" ..., max_length=20, description="Mexican tariff fraction (required)"
) )
@@ -111,6 +116,11 @@ class ClassUpdateDTO(BaseModel):
unit_of_measure: Optional[str] = Field( unit_of_measure: Optional[str] = Field(
None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)" None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)"
) )
stock_unit_of_measure: Optional[str] = Field(
None,
max_length=5,
description="UM Existencia - solo SCAII/inventory (UMEXISTENCIA)",
)
fraction: Optional[str] = Field( fraction: Optional[str] = Field(
None, max_length=20, description="Mexican tariff fraction" None, max_length=20, description="Mexican tariff fraction"
) )
@@ -148,6 +158,7 @@ class ClassResponseDTO(BaseModel):
description_en: Optional[str] = None description_en: Optional[str] = None
material_key: Optional[str] = None material_key: Optional[str] = None
unit_of_measure: Optional[str] = None unit_of_measure: Optional[str] = None
stock_unit_of_measure: Optional[str] = None
fraction: Optional[str] = None fraction: Optional[str] = None
us_fraction: Optional[str] = None us_fraction: Optional[str] = None
sub_key: Optional[str] = None sub_key: Optional[str] = None
@@ -230,6 +241,7 @@ class ClassWithFADataResponse(BaseModel):
description_en: Optional[str] = None description_en: Optional[str] = None
material_key: Optional[str] = None material_key: Optional[str] = None
unit_of_measure: Optional[str] = None unit_of_measure: Optional[str] = None
stock_unit_of_measure: Optional[str] = None
fraction: Optional[str] = None fraction: Optional[str] = None
us_fraction: Optional[str] = None us_fraction: Optional[str] = None
sub_key: Optional[str] = None sub_key: Optional[str] = None

View File

@@ -42,6 +42,12 @@ class Class(Base, TenantScopedMixin, TimestampMixin):
["a76.units_of_measure.code", "a76.units_of_measure.tenant_id", ["a76.units_of_measure.code", "a76.units_of_measure.tenant_id",
"a76.units_of_measure.company_id"], "a76.units_of_measure.company_id"],
), ),
ForeignKeyConstraint(
["stock_unit_of_measure", "tenant_id", "company_id"],
["a76.units_of_measure.code", "a76.units_of_measure.tenant_id",
"a76.units_of_measure.company_id"],
name="fk_classes_stock_uom",
),
UniqueConstraint( UniqueConstraint(
"tenant_id", "tenant_id",
"company_id", "company_id",
@@ -69,6 +75,9 @@ class Class(Base, TenantScopedMixin, TimestampMixin):
unit_of_measure: Mapped[Optional[str]] = mapped_column( unit_of_measure: Mapped[Optional[str]] = mapped_column(
String(5) String(5)
) # UNIMED - homologated from UNIMEDIDA ) # UNIMED - homologated from UNIMEDIDA
stock_unit_of_measure: Mapped[Optional[str]] = mapped_column(
String(5)
) # UMEXISTENCIA - UM Existencia (SCAII/inventory)
# Tariff fractions # Tariff fractions
fraction: Mapped[Optional[str]] = mapped_column(String(20)) # FRACCION fraction: Mapped[Optional[str]] = mapped_column(String(20)) # FRACCION
@@ -97,7 +106,11 @@ class Class(Base, TenantScopedMixin, TimestampMixin):
) )
unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship( unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship(
foreign_keys=[unit_of_measure] foreign_keys=[unit_of_measure]
) )
stock_unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship(
foreign_keys=[stock_unit_of_measure],
viewonly=True,
)
# Inverse relationship with GParts that have this class # Inverse relationship with GParts that have this class
parts: Mapped[list["Part"]] = relationship( parts: Mapped[list["Part"]] = relationship(

View File

@@ -25,6 +25,51 @@ from .models import Class
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _validate_stock_uom_conversion(
db: Session,
tenant_id: int,
company_id: int,
system: str,
stock_unit_of_measure: Optional[str],
unit_of_measure: Optional[str],
) -> None:
"""SCAII: si UM Existencia ≠ UM Comercial, debe existir conversión en catálogo."""
if system != "inventory":
return
stock_um = (stock_unit_of_measure or "").strip().upper()
commercial_um = (unit_of_measure or "").strip().upper()
if not stock_um or not commercial_um or stock_um == commercial_um:
return
from api.v1.modules.a76.general_catalogs.unit_conversions.models import (
UnitConversion,
)
conversion_exists = (
db.query(UnitConversion)
.filter(
UnitConversion.tenant_id == tenant_id,
UnitConversion.company_id == company_id,
or_(
(UnitConversion.from_unit_code == stock_um)
& (UnitConversion.to_unit_code == commercial_um),
(UnitConversion.from_unit_code == commercial_um)
& (UnitConversion.to_unit_code == stock_um),
),
)
.first()
)
if not conversion_exists:
raise HTTPException(
status_code=422,
detail=(
f"No existe un factor de conversión para '{stock_um}''{commercial_um}'. "
"Configúrelo primero en el catálogo de Conversiones de UM."
),
)
class ClassService: class ClassService:
"""Servicio para gestión de clases SCAII y SCAF""" """Servicio para gestión de clases SCAII y SCAF"""
@@ -202,6 +247,7 @@ class ClassService:
"description_en": base_class.description_en, "description_en": base_class.description_en,
"material_key": base_class.material_key, "material_key": base_class.material_key,
"unit_of_measure": base_class.unit_of_measure, "unit_of_measure": base_class.unit_of_measure,
"stock_unit_of_measure": base_class.stock_unit_of_measure,
"fraction": base_class.fraction, "fraction": base_class.fraction,
"us_fraction": base_class.us_fraction, "us_fraction": base_class.us_fraction,
"sub_key": base_class.sub_key, "sub_key": base_class.sub_key,
@@ -273,7 +319,16 @@ class ClassService:
status_code=400, status_code=400,
detail=f"Material type '{data_dict['material_key']}' does not exist" detail=f"Material type '{data_dict['material_key']}' does not exist"
) )
_validate_stock_uom_conversion(
db,
tenant_id,
company_id,
data_dict.get("system", "fixed_asset"),
data_dict.get("stock_unit_of_measure"),
data_dict.get("unit_of_measure"),
)
class_obj = Class(**data_dict) class_obj = Class(**data_dict)
class_obj.tenant_id = tenant_id class_obj.tenant_id = tenant_id
class_obj.company_id = company_id class_obj.company_id = company_id
@@ -306,7 +361,23 @@ class ClassService:
return None return None
update_data = class_data.model_dump(exclude_unset=True) update_data = class_data.model_dump(exclude_unset=True)
effective_system = update_data.get("system", class_obj.system)
effective_stock_um = update_data.get(
"stock_unit_of_measure", class_obj.stock_unit_of_measure
)
effective_commercial_um = update_data.get(
"unit_of_measure", class_obj.unit_of_measure
)
_validate_stock_uom_conversion(
db,
tenant_id,
company_id,
effective_system,
effective_stock_um,
effective_commercial_um,
)
# Validate material_key exists if provided # Validate material_key exists if provided
if "material_key" in update_data and update_data["material_key"]: if "material_key" in update_data and update_data["material_key"]:
from api.v1.modules.public.reference_data.material_types.models import MaterialType from api.v1.modules.public.reference_data.material_types.models import MaterialType
@@ -438,7 +509,8 @@ class ClassService:
# Extract base class fields # Extract base class fields
base_fields = { base_fields = {
"class_code", "description_es", "description_en", "class_code", "description_es", "description_en",
"material_key", "unit_of_measure", "fraction", "us_fraction", "material_key", "unit_of_measure", "stock_unit_of_measure",
"fraction", "us_fraction",
"sub_key", "physical_review", "iva_exempt_fraction", "system" "sub_key", "physical_review", "iva_exempt_fraction", "system"
} }
base_data = {k: v for k, v in class_data.model_dump().items() if k in base_fields} base_data = {k: v for k, v in class_data.model_dump().items() if k in base_fields}
@@ -477,6 +549,7 @@ class ClassService:
"description_en": base_class.description_en, "description_en": base_class.description_en,
"material_key": base_class.material_key, "material_key": base_class.material_key,
"unit_of_measure": base_class.unit_of_measure, "unit_of_measure": base_class.unit_of_measure,
"stock_unit_of_measure": base_class.stock_unit_of_measure,
"fraction": base_class.fraction, "fraction": base_class.fraction,
"us_fraction": base_class.us_fraction, "us_fraction": base_class.us_fraction,
"sub_key": base_class.sub_key, "sub_key": base_class.sub_key,

View File

@@ -12,6 +12,7 @@ export interface A76Class {
description_en: string | null; description_en: string | null;
material_key: string | null; material_key: string | null;
unit_of_measure: string; unit_of_measure: string;
stock_unit_of_measure?: string | null;
fraction: string; fraction: string;
us_fraction: string; us_fraction: string;
sub_key: string; sub_key: string;
@@ -31,6 +32,7 @@ export interface A76ClassCreate {
description_en?: string | null; description_en?: string | null;
material_key?: string | null; material_key?: string | null;
unit_of_measure?: string | null; unit_of_measure?: string | null;
stock_unit_of_measure?: string | null;
fraction?: string | null; fraction?: string | null;
us_fraction?: string | null; us_fraction?: string | null;
sub_key?: string | null; sub_key?: string | null;

View File

@@ -8,8 +8,8 @@ import {
normalizeMexTariffDigitsStored normalizeMexTariffDigitsStored
} from "$lib/utils/mexican-tariff-fraction"; } from "$lib/utils/mexican-tariff-fraction";
export function createColumns(): ColumnDef<A76Class>[] { export function createColumns(options?: { includeStockUom?: boolean }): ColumnDef<A76Class>[] {
return [ const baseColumns: ColumnDef<A76Class>[] = [
{ {
id: "select", id: "select",
header: ({ table }) => { header: ({ table }) => {
@@ -129,6 +129,24 @@ export function createColumns(): ColumnDef<A76Class>[] {
return renderSnippet(umSnippet, { um: row.original.unit_of_measure }); return renderSnippet(umSnippet, { um: row.original.unit_of_measure });
} }
}, },
];
const stockUomColumn: ColumnDef<A76Class> = {
accessorKey: "stock_unit_of_measure",
header: "U.M. Existencia",
enableSorting: true,
cell: ({ row }) => {
const umSnippet = createRawSnippet<[{ um: string | null }]>((getProps) => {
const { um } = getProps();
return {
render: () => `<div class="text-muted-foreground">${um || '—'}</div>`
};
});
return renderSnippet(umSnippet, { um: row.original.stock_unit_of_measure ?? null });
}
};
const tailColumns: ColumnDef<A76Class>[] = [
{ {
accessorKey: "fraction", accessorKey: "fraction",
header: m['common.col_fraction'](), header: m['common.col_fraction'](),
@@ -162,6 +180,11 @@ export function createColumns(): ColumnDef<A76Class>[] {
} }
} }
]; ];
if (options?.includeStockUom) {
return [...baseColumns, stockUomColumn, ...tailColumns];
}
return [...baseColumns, ...tailColumns];
} }
export const columns = createColumns(); export const columns = createColumns();

View File

@@ -10,6 +10,7 @@
type MaterialType type MaterialType
} from '$lib/api/dashboard/reference_data/material_types'; } from '$lib/api/dashboard/reference_data/material_types';
import { unitsOfMeasureApi } from '$lib/api/dashboard/a76/units_of_measure'; import { unitsOfMeasureApi } from '$lib/api/dashboard/a76/units_of_measure';
import { unitConversionsApi } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
import { import {
getTariffFractions, getTariffFractions,
type TariffFraction type TariffFraction
@@ -70,6 +71,7 @@ const isInventoryMode = $derived(mode === 'inventory');
description_en: initialData?.description_en || '', description_en: initialData?.description_en || '',
material_key: initialData?.material_key || '', material_key: initialData?.material_key || '',
unit_of_measure: initialData?.unit_of_measure || '', unit_of_measure: initialData?.unit_of_measure || '',
stock_unit_of_measure: initialData?.stock_unit_of_measure || '',
fraction: initialData?.fraction || '', fraction: initialData?.fraction || '',
us_fraction: initialData?.us_fraction || '', us_fraction: initialData?.us_fraction || '',
annual_depreciation_rate: initialData?.annual_depreciation_rate || '', annual_depreciation_rate: initialData?.annual_depreciation_rate || '',
@@ -86,6 +88,7 @@ const isInventoryMode = $derived(mode === 'inventory');
// Campos calculados/display // Campos calculados/display
material_description: '', material_description: '',
unit_of_measure_description: '', unit_of_measure_description: '',
stock_unit_of_measure_description: '',
unit_measure_key: '', unit_measure_key: '',
fraction_umt: '', fraction_umt: '',
fraction_uma_key: '', fraction_uma_key: '',
@@ -115,6 +118,7 @@ const isInventoryMode = $derived(mode === 'inventory');
formData.description_en = snap.description_en ?? ''; formData.description_en = snap.description_en ?? '';
formData.material_key = snap.material_key ?? ''; formData.material_key = snap.material_key ?? '';
formData.unit_of_measure = snap.unit_of_measure ?? ''; formData.unit_of_measure = snap.unit_of_measure ?? '';
formData.stock_unit_of_measure = snap.stock_unit_of_measure ?? '';
const mxDigits = normalizeMexTariffDigitsStored( const mxDigits = normalizeMexTariffDigitsStored(
snap.import_tariff_code || snap.fraction || '' snap.import_tariff_code || snap.fraction || ''
); );
@@ -155,6 +159,7 @@ const isInventoryMode = $derived(mode === 'inventory');
formData.description_en = ''; formData.description_en = '';
formData.material_key = ''; formData.material_key = '';
formData.unit_of_measure = ''; formData.unit_of_measure = '';
formData.stock_unit_of_measure = '';
formData.fraction = ''; formData.fraction = '';
formData.us_fraction = ''; formData.us_fraction = '';
formData.annual_depreciation_rate = ''; formData.annual_depreciation_rate = '';
@@ -192,6 +197,9 @@ const isInventoryMode = $derived(mode === 'inventory');
let showUnitDialog = $state(false); let showUnitDialog = $state(false);
let searchUnit = $state(''); let searchUnit = $state('');
let unitPickerTarget = $state<'commercial' | 'stock'>('commercial');
let conversionExists = $state<boolean | null>(null);
let conversionCheckLoading = $state(false);
import TariffFractionSelector from '$lib/components/dashboard/goods/modales/TariffFractionSelector.svelte'; import TariffFractionSelector from '$lib/components/dashboard/goods/modales/TariffFractionSelector.svelte';
@@ -265,7 +273,76 @@ const isInventoryMode = $derived(mode === 'inventory');
: unitsOfMeasureData : unitsOfMeasureData
); );
const stockUmcDiffer = $derived.by(() => {
if (!isInventoryMode) return false;
const stock = formData.stock_unit_of_measure?.trim().toUpperCase();
const commercial = formData.unit_of_measure?.trim().toUpperCase();
return Boolean(stock && commercial && stock !== commercial);
});
// Funciones // Funciones
async function checkUnitConversionPair(): Promise<void> {
if (!stockUmcDiffer) {
conversionExists = null;
return;
}
const stockUm = formData.stock_unit_of_measure.trim().toUpperCase();
const commercialUm = formData.unit_of_measure.trim().toUpperCase();
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
conversionExists = false;
return;
}
conversionCheckLoading = true;
try {
const [fwd, rev] = await Promise.all([
unitConversionsApi.list(1, 10, companyId, {
from_unit_code: stockUm,
to_unit_code: commercialUm
}),
unitConversionsApi.list(1, 10, companyId, {
from_unit_code: commercialUm,
to_unit_code: stockUm
})
]);
const fwdMatch = fwd.items.some(
(c) =>
c.from_unit_code.toUpperCase() === stockUm &&
c.to_unit_code.toUpperCase() === commercialUm
);
const revMatch = rev.items.some(
(c) =>
c.from_unit_code.toUpperCase() === commercialUm &&
c.to_unit_code.toUpperCase() === stockUm
);
conversionExists = fwdMatch || revMatch;
} catch {
conversionExists = false;
} finally {
conversionCheckLoading = false;
}
}
$effect(() => {
if (!isInventoryMode) {
conversionExists = null;
return;
}
const stock = formData.stock_unit_of_measure;
const commercial = formData.unit_of_measure;
if (!stock?.trim() || !commercial?.trim()) {
conversionExists = null;
return;
}
if (stock.trim().toUpperCase() === commercial.trim().toUpperCase()) {
conversionExists = null;
return;
}
void checkUnitConversionPair();
});
async function loadUnitsOfMeasure() { async function loadUnitsOfMeasure() {
if (unitsOfMeasureData.length > 0) return; if (unitsOfMeasureData.length > 0) return;
try { try {
@@ -346,15 +423,31 @@ const isInventoryMode = $derived(mode === 'inventory');
} }
async function openUnitOfMeasureSearch() { async function openUnitOfMeasureSearch() {
unitPickerTarget = 'commercial';
showUnitDialog = true;
searchUnit = '';
await loadUnitsOfMeasure();
}
async function openStockUomSearch() {
unitPickerTarget = 'stock';
showUnitDialog = true; showUnitDialog = true;
searchUnit = ''; searchUnit = '';
await loadUnitsOfMeasure(); await loadUnitsOfMeasure();
} }
function selectUnit(unit: UnitOfMeasure) { function selectUnit(unit: UnitOfMeasure) {
formData.unit_of_measure = unit.code; if (unitPickerTarget === 'stock') {
formData.unit_of_measure_description = unit.description; formData.stock_unit_of_measure = unit.code;
formData.unit_measure_key = unit.claveMexicana; formData.stock_unit_of_measure_description = unit.description;
if (showErrors) validateField('stock_unit_of_measure');
} else {
formData.unit_of_measure = unit.code;
formData.unit_of_measure_description = unit.description;
formData.unit_measure_key = unit.claveMexicana;
if (showErrors && isInventoryMode) validateField('stock_unit_of_measure');
validateField('unit_of_measure');
}
showUnitDialog = false; showUnitDialog = false;
searchUnit = ''; searchUnit = '';
} }
@@ -379,6 +472,24 @@ const isInventoryMode = $derived(mode === 'inventory');
} }
} }
async function handleStockUomBlur() {
if (!isInventoryMode) return;
validateField('stock_unit_of_measure');
const code = formData.stock_unit_of_measure?.trim().toUpperCase();
if (!code) {
formData.stock_unit_of_measure_description = '';
return;
}
await loadUnitsOfMeasure();
const found = unitsOfMeasureData.find((u) => u.code.toUpperCase() === code);
if (found) {
formData.stock_unit_of_measure = found.code;
formData.stock_unit_of_measure_description = found.description;
} else {
formData.stock_unit_of_measure_description = '(Código no encontrado)';
}
}
// Fix 2: Máscara para Fracción Americana (formato 0000.00.00.00 = 13 chars) // Fix 2: Máscara para Fracción Americana (formato 0000.00.00.00 = 13 chars)
function formatUSFraction(event: Event) { function formatUSFraction(event: Event) {
const input = event.target as HTMLInputElement; const input = event.target as HTMLInputElement;
@@ -544,6 +655,18 @@ const isInventoryMode = $derived(mode === 'inventory');
searchCartaPorte = ''; searchCartaPorte = '';
} }
function validateStockUnitOfMeasureField(errors: Record<string, string>) {
if (!formData.stock_unit_of_measure?.trim()) {
errors.stock_unit_of_measure = 'La unidad de medida de existencia es obligatoria';
return;
}
delete errors.stock_unit_of_measure;
if (stockUmcDiffer && conversionExists === false) {
errors.stock_unit_of_measure =
'No existe factor de conversión para este par. Configúrelo en el catálogo de Conversiones de UM.';
}
}
// Función de validación // Función de validación
function validateForm(): boolean { function validateForm(): boolean {
const errors: Record<string, string> = {}; const errors: Record<string, string> = {};
@@ -566,6 +689,13 @@ const isInventoryMode = $derived(mode === 'inventory');
errors.unit_of_measure = 'La unidad de medida comercial es obligatoria'; errors.unit_of_measure = 'La unidad de medida comercial es obligatoria';
} }
if (isInventoryMode && !formData.stock_unit_of_measure?.trim()) {
errors.stock_unit_of_measure = 'La unidad de medida de existencia es obligatoria';
} else if (isInventoryMode && stockUmcDiffer && conversionExists === false) {
errors.stock_unit_of_measure =
'No existe factor de conversión para este par. Configúrelo en el catálogo de Conversiones de UM.';
}
if (!formData.fraction?.trim()) { if (!formData.fraction?.trim()) {
errors.fraction = 'La fracción arancelaria es obligatoria'; errors.fraction = 'La fracción arancelaria es obligatoria';
} }
@@ -611,6 +741,14 @@ const isInventoryMode = $derived(mode === 'inventory');
} else { } else {
delete errors.unit_of_measure; delete errors.unit_of_measure;
} }
if (isInventoryMode) {
validateStockUnitOfMeasureField(errors);
}
break;
case 'stock_unit_of_measure':
if (isInventoryMode) {
validateStockUnitOfMeasureField(errors);
}
break; break;
case 'fraction': case 'fraction':
if (!formData.fraction?.trim()) { if (!formData.fraction?.trim()) {
@@ -625,6 +763,10 @@ const isInventoryMode = $derived(mode === 'inventory');
} }
async function handleSave() { async function handleSave() {
if (isInventoryMode && stockUmcDiffer) {
await checkUnitConversionPair();
}
// Validar formulario // Validar formulario
if (!validateForm()) { if (!validateForm()) {
showErrors = true; showErrors = true;
@@ -789,6 +931,48 @@ const isInventoryMode = $derived(mode === 'inventory');
{/if} {/if}
</div> </div>
{#if isInventoryMode}
<div class="space-y-2 md:col-span-2">
<Label for="stock_unit_of_measure" class="font-bold">
U.M. Existencia: <span class="text-red-500">*</span>
</Label>
<div class="flex flex-wrap items-center gap-2">
<Input
id="stock_unit_of_measure"
bind:value={formData.stock_unit_of_measure}
placeholder="UME"
class="min-w-[5rem] flex-1 uppercase {validationErrors.stock_unit_of_measure
? 'border-red-500 focus-visible:ring-red-500'
: ''}"
maxlength={5}
onblur={handleStockUomBlur}
/>
<Button type="button" variant="outline" size="icon" onclick={openStockUomSearch}>
<Folder class="h-4 w-4" />
</Button>
<span class="text-sm text-blue-600">
{formData.stock_unit_of_measure_description || ''}
</span>
</div>
{#if validationErrors.stock_unit_of_measure}
<p class="mt-1 text-sm text-red-500">{validationErrors.stock_unit_of_measure}</p>
{/if}
{#if stockUmcDiffer}
{#if conversionCheckLoading}
<p class="text-sm text-muted-foreground">Verificando factor de conversión…</p>
{:else if conversionExists === false}
<p class="text-sm font-semibold text-red-600">
UM Existencia ≠ UM Comercial y no existe factor de conversión para este par.
Configúrelo primero en el catálogo de Conversiones de UM.
</p>
{:else if conversionExists === true}
<p class="text-sm text-green-600">Factor de conversión encontrado para este par.</p>
{/if}
{/if}
</div>
{/if}
<div class="space-y-2"> <div class="space-y-2">
<Label for="fraction" class="font-bold"> <Label for="fraction" class="font-bold">
Fracción Arancelaria: <span class="text-red-500">*</span> Fracción Arancelaria: <span class="text-red-500">*</span>

View File

@@ -13,7 +13,7 @@
import { companyStore } from '$lib/stores/company.svelte'; import { companyStore } from '$lib/stores/company.svelte';
import { systemStore } from '$lib/stores/system.svelte'; import { systemStore } from '$lib/stores/system.svelte';
import DataTable from '$lib/components/dashboard/goods/classes/data-table.svelte'; import DataTable from '$lib/components/dashboard/goods/classes/data-table.svelte';
import { columns } from '$lib/components/dashboard/goods/classes/columns'; import { createColumns } from '$lib/components/dashboard/goods/classes/columns';
import { currentUser } from '$lib/auth'; import { currentUser } from '$lib/auth';
import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
import { import {
@@ -72,6 +72,7 @@
const activeSystem = $derived(systemStore.activeSystem); const activeSystem = $derived(systemStore.activeSystem);
const isInventoryMode = $derived(activeSystem === 'inventory'); const isInventoryMode = $derived(activeSystem === 'inventory');
const isFixedAssetMode = $derived(activeSystem !== 'inventory'); const isFixedAssetMode = $derived(activeSystem !== 'inventory');
const tableColumns = $derived(createColumns({ includeStockUom: isInventoryMode }));
// Permisos // Permisos
const canView = $derived(canViewGoodsClasses($currentUser)); const canView = $derived(canViewGoodsClasses($currentUser));
@@ -494,7 +495,7 @@
<div class="min-h-0 flex-1 overflow-hidden"> <div class="min-h-0 flex-1 overflow-hidden">
<DataTable <DataTable
data={classes} data={classes}
{columns} columns={tableColumns}
loading={isLoading} loading={isLoading}
selectedIds={selectedClassIds} selectedIds={selectedClassIds}
onSelectedIdsChange={handleSelectedIdsChange} onSelectedIdsChange={handleSelectedIdsChange}
@@ -756,6 +757,12 @@
description_en: cleanData.description_en?.trim() || '', description_en: cleanData.description_en?.trim() || '',
material_key: cleanData.material_key?.trim() || '', material_key: cleanData.material_key?.trim() || '',
unit_of_measure: cleanData.unit_of_measure?.trim() || '', unit_of_measure: cleanData.unit_of_measure?.trim() || '',
...(isInventoryMode
? {
stock_unit_of_measure:
cleanData.stock_unit_of_measure?.trim() || null
}
: {}),
fraction: mxFraccionNorm, fraction: mxFraccionNorm,
us_fraction: cleanData.us_fraction || '', us_fraction: cleanData.us_fraction || '',
physical_review: cleanData.physical_review ? 1 : 0, physical_review: cleanData.physical_review ? 1 : 0,
@@ -825,6 +832,12 @@
description_en: cleanData.description_en?.trim() || '', description_en: cleanData.description_en?.trim() || '',
material_key: cleanData.material_key?.trim() || '', material_key: cleanData.material_key?.trim() || '',
unit_of_measure: cleanData.unit_of_measure?.trim() || '', unit_of_measure: cleanData.unit_of_measure?.trim() || '',
...(isInventoryMode
? {
stock_unit_of_measure:
cleanData.stock_unit_of_measure?.trim() || null
}
: {}),
fraction: mxFraccionNorm, fraction: mxFraccionNorm,
us_fraction: cleanData.us_fraction?.trim() || '', us_fraction: cleanData.us_fraction?.trim() || '',
sub_key: cleanData.sub_key || '', sub_key: cleanData.sub_key || '',