Merge pull request 'feature/item_por_tipo_factura' (#217) from feature/item_por_tipo_factura into development

Reviewed-on: ADUANASOFT/anexo76#217
This commit is contained in:
2026-03-18 03:51:07 +00:00
16 changed files with 1062 additions and 58 deletions

0
archivo.txt Normal file
View File

View File

@@ -32,6 +32,7 @@ class IdentifierDetailBase(BaseModel):
part_line: Optional[int] = Field(None, description="Part Line")
identifier_code: Optional[str] = Field(
None, max_length=2, description="Identifier Code")
item_line_id: Optional[int] = Field(None, description="Item Line ID")
module: Optional[str] = Field(None, max_length=20, description="Module")
complement1: Optional[str] = Field(
None, max_length=50, description="Complement 1")

View File

@@ -1,10 +1,13 @@
from typing import Optional
from typing import Optional, TYPE_CHECKING
from sqlalchemy import Integer, String, UniqueConstraint, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.items.models import LineItem
class Identifier(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "identifiers"
__table_args__ = (
@@ -44,6 +47,8 @@ class IdentifierDetail(Base, TenantScopedMixin, TimestampMixin):
Integer, nullable=True) # LINEAPARTIDA
identifier_code: Mapped[Optional[str]] = mapped_column(
String(2), ForeignKey("a76.identifiers.code"), nullable=True) # ID
item_line_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("a76.item_lines.id"), nullable=True)
module: Mapped[Optional[str]] = mapped_column(
String(20), nullable=True) # MODULO
complement1: Mapped[Optional[str]] = mapped_column(
@@ -54,3 +59,4 @@ class IdentifierDetail(Base, TenantScopedMixin, TimestampMixin):
String(50), nullable=True) # COMPLEMENTO3
identifier: Mapped["Identifier"] = relationship(back_populates="details")
line: Mapped[Optional["LineItem"]] = relationship(back_populates="identifiers")

View File

@@ -4,7 +4,7 @@ SQLAlchemy v2 - Annex 24 Compliance
"""
from datetime import datetime
from typing import Optional, TYPE_CHECKING
from typing import Optional, List, TYPE_CHECKING
from core.database import Base
from decimal import Decimal
from sqlalchemy import Boolean, Date, String, Integer, Numeric, SmallInteger, ForeignKey
@@ -24,6 +24,7 @@ if TYPE_CHECKING:
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
from api.v1.modules.a76.parts.models import Part
from api.v1.modules.a76.invoices.models import InvoiceHeader
from api.v1.modules.a76.general_catalogs.identifiers.models import IdentifierDetail
# ============================================================================
# CORE ENTITIES
@@ -216,10 +217,14 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
)
fa_data: Mapped[Optional["FaLineItem"]] = relationship(
"FaLineItem",
back_populates="master_info",
cascade="all, delete-orphan",
uselist=False,
)
identifiers: Mapped[List["IdentifierDetail"]] = relationship(
"IdentifierDetail",
back_populates="line",
cascade="all, delete-orphan",
)
part_info: Mapped[Optional["Part"]] = relationship(
"Part",
foreign_keys=[part_number_id],

View File

@@ -37,6 +37,12 @@ from .line_references.schemas import (
LineReferenceResponse,
)
from api.v1.modules.a76.general_catalogs.identifiers.dto import (
IdentifierDetailCreate,
IdentifierDetailUpdate,
IdentifierDetailResponse,
)
from api.v1.modules.a76.classes.models import Class
from api.v1.modules.a24.fa.fa_item_lines.dto import (
FaLineItemCreateDTO,
FaLineItemUpdateDTO,
@@ -238,6 +244,9 @@ class LineItemCreate(LineItemBase):
series: Optional[list[SerieCreate]] = Field(
None, description="Series data for this line (multiple per line)"
)
identifiers: Optional[list[IdentifierDetailCreate]] = Field(
None, description="Identifiers for this line"
)
class LineItemUpdate(LineItemBase):
@@ -267,6 +276,9 @@ class LineItemUpdate(LineItemBase):
series: Optional[list[SerieUpdate]] = Field(
None, description="Series data for this line (replace all)"
)
identifiers: Optional[list[IdentifierDetailUpdate]] = Field(
None, description="Identifiers for this line"
)
class LineItemResponse(LineItemBase):
@@ -295,6 +307,7 @@ class LineItemResponse(LineItemBase):
reference: Optional[LineReferenceResponse] = None
fa_data: Optional[FaLineItemResponseDTO] = None
series: Optional[list[SerieResponse]] = None
identifiers: Optional[list[IdentifierDetailResponse]] = None
# Fields populated from relationships
class_code: Optional[str] = None

View File

@@ -20,5 +20,7 @@ class Serie(Base, TenantScopedMixin, TimestampMixin):
number_id: Mapped[Optional[str]] = mapped_column(String(25)) # NUMIDEXPO
discharge: Mapped[Optional[bool]] = mapped_column(Boolean) # MARCA
serie_row: Mapped[Optional[int]] = mapped_column(Integer) # LINEASERIEIMPO <-- IN CASE OF EXPO
image_path: Mapped[Optional[str]] = mapped_column(String(255)) # PATH DE IMAGEN (MEX)

View File

@@ -10,6 +10,9 @@ class SerieBase(BaseModel):
brand: Optional[str] = Field(None, max_length=50, description="Brand (MARCA)")
expo_brad: Optional[str] = Field(None, max_length=50, description="Expo brand (MARCAEXPO)")
number_id: Optional[str] = Field(None, max_length=25, description="Number ID (NUMIDEXPO)")
import_invoice: Optional[str] = Field(None, max_length=15, description="Import invoice (FACTURAIMPO)")
import_line: Optional[int] = Field(None, description="Import line (LINEAIMPO)")
image_path: Optional[str] = Field(None, max_length=255, description="Image path (IMAGEPATHMEX)")
class SerieCreate(SerieBase):

View File

@@ -40,6 +40,7 @@ from .models import LineItem
from .series.models import Serie
from api.v1.modules.a76.invoices.models import InvoiceHeader
from api.v1.modules.a76.parts.models import Part
from api.v1.modules.a76.general_catalogs.identifiers.models import IdentifierDetail
logger = logging.getLogger(__name__)
@@ -195,6 +196,26 @@ class ItemService:
serie_dict["row"] = 1
db.add(Serie(**serie_dict))
# Identifier Detail data
if hasattr(line_data, "identifiers") and line_data.identifiers:
id_list = (
line_data.identifiers
if isinstance(line_data.identifiers, list)
else [line_data.identifiers]
)
for d in id_list:
id_dict = (
d.model_dump(exclude_unset=True)
if hasattr(d, "model_dump")
else (dict(d) if isinstance(d, dict) else {})
)
if not id_dict:
continue
id_dict["item_line_id"] = line.id
id_dict["tenant_id"] = tenant_id
id_dict["company_id"] = company_id
db.add(IdentifierDetail(**id_dict))
@staticmethod
def _attach_series(db: Session, item: LineItem) -> None:
"""Query and attach all Serie rows for this item as a list."""
@@ -206,6 +227,16 @@ class ItemService:
)
item.series = list(series)
@staticmethod
def _attach_identifiers(db: Session, item: LineItem) -> None:
"""Query and attach all IdentifierDetail rows for this item."""
identifiers = (
db.query(IdentifierDetail)
.filter(IdentifierDetail.item_line_id == item.id)
.all()
)
item.identifiers = list(identifiers)
@staticmethod
def get_by_id(
db: Session, item_id: int, tenant_id: int, company_id: int
@@ -232,6 +263,7 @@ class ItemService:
)
if result:
ItemService._attach_series(db, result)
ItemService._attach_identifiers(db, result)
return result
@staticmethod
@@ -285,6 +317,7 @@ class ItemService:
items = query.offset(skip).limit(limit).all()
for item in items:
ItemService._attach_series(db, item)
ItemService._attach_identifiers(db, item)
return items, total
@staticmethod
@@ -318,6 +351,7 @@ class ItemService:
items = query.offset(skip).limit(limit).all()
for item in items:
ItemService._attach_series(db, item)
ItemService._attach_identifiers(db, item)
return items, total
@staticmethod
@@ -443,6 +477,7 @@ class ItemService:
db.commit()
db.refresh(db_item)
ItemService._attach_series(db, db_item)
ItemService._attach_identifiers(db, db_item)
return db_item
except IntegrityError as e:
@@ -591,6 +626,7 @@ class ItemService:
).delete()
db.query(FaLineItem).filter(FaLineItem.id == db_item.id).delete()
db.query(Serie).filter(Serie.line_item_id == db_item.id).delete()
db.query(IdentifierDetail).filter(IdentifierDetail.item_line_id == db_item.id).delete()
db.flush()
# Create new nested data
@@ -604,6 +640,7 @@ class ItemService:
db.commit()
db.refresh(db_item)
ItemService._attach_series(db, db_item)
ItemService._attach_identifiers(db, db_item)
return db_item
except HTTPException:

View File

@@ -35,6 +35,33 @@ export interface IdentifierListResponse {
pages: number;
}
export interface IdentifierDetail {
id: number;
invoice_consecutive: number | null;
part_line: number | null;
identifier_code: string | null;
module: string | null;
complement1: string | null;
complement2: string | null;
complement3: string | null;
item_line_id: number | null;
company_id: number;
tenant_id: number;
}
export interface IdentifierDetailCreate {
invoice_consecutive?: number | null;
part_line?: number | null;
identifier_code?: string | null;
module?: string | null;
complement1?: string | null;
complement2?: string | null;
complement3?: string | null;
item_line_id?: number | null;
}
export interface IdentifierDetailUpdate extends Partial<IdentifierDetailCreate> { }
export async function getIdentifiers(
page = 1,
pageSize = 50,
@@ -71,4 +98,29 @@ export async function deleteIdentifier(
companyId: number
): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/identifiers/${id}/?company_id=${companyId}`);
}
/**
* API for Identifier Details
*/
export async function createIdentifierDetail(
data: IdentifierDetailCreate,
companyId: number
): Promise<ApiResponse<IdentifierDetail>> {
return await api.post(`/v1/a76/identifiers/details/?company_id=${companyId}`, data);
}
export async function updateIdentifierDetail(
id: number,
data: IdentifierDetailUpdate,
companyId: number
): Promise<ApiResponse<IdentifierDetail>> {
return await api.put(`/v1/a76/identifiers/details/${id}/?company_id=${companyId}`, data);
}
export async function deleteIdentifierDetail(
id: number,
companyId: number
): Promise<ApiResponse<void>> {
return await api.delete(`/v1/a76/identifiers/details/${id}/?company_id=${companyId}`);
}

View File

@@ -88,6 +88,10 @@ export interface LineReferences {
serie_id?: number;
}
import type {
IdentifierDetail
} from './general_catalogs/identifiers';
export interface Serie {
id?: number;
line_item_id?: number;
@@ -98,6 +102,9 @@ export interface Serie {
brand?: string;
expo_brad?: string;
number_id?: string;
import_invoice?: string;
import_line?: number;
image_path?: string;
}
export interface FaLineItem {
@@ -207,6 +214,7 @@ export interface Item {
reference?: LineReferences;
fa_data?: FaLineItem; // Fixed Asset specific data
series?: Serie[]; // Series data (multiple per line)
identifiers?: IdentifierDetail[]; // Identifiers for this line
}
export interface ItemListResponse {

View File

@@ -0,0 +1,115 @@
<script lang="ts">
import * as Dialog from "$lib/components/ui/dialog";
import * as Table from "$lib/components/ui/table";
import { Button } from "$lib/components/ui/button";
import { Input } from "$lib/components/ui/input";
import { Search, Loader2 } from "lucide-svelte";
import { getIdentifiers, type Identifier } from "$lib/api/dashboard/a76/general_catalogs/identifiers";
import { onMount } from "svelte";
import { companyStore } from '$lib/stores/company.svelte';
let {
open = $bindable(false),
onSelect
}: {
open: boolean;
onSelect: (identifier: Identifier) => void;
} = $props();
let identifiers = $state<Identifier[]>([]);
let loading = $state(false);
let searchTerm = $state("");
async function loadIdentifiers() {
const companyId = companyStore?.activeCompany?.id;
if (!companyId) return;
loading = true;
try {
// Using a large limit for now to avoid complex pagination in the selector
const res = await getIdentifiers(1, 1000, companyId);
if (res.data) {
identifiers = res.data.items || [];
}
} catch (error) {
console.error("Error loading identifiers:", error);
} finally {
loading = false;
}
}
onMount(() => {
loadIdentifiers();
});
const filteredIdentifiers = $derived(
identifiers.filter(i =>
i.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
(i.description?.toLowerCase().includes(searchTerm.toLowerCase()) ?? false)
)
);
function handleSelect(identifier: Identifier) {
onSelect(identifier);
open = false;
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[600px] h-[600px] flex flex-col p-0 text-xs">
<Dialog.Header class="px-6 pt-6 pb-4 shrink-0">
<Dialog.Title>Seleccionar Identificador</Dialog.Title>
<Dialog.Description>
Busca y selecciona un identificador del catálogo (Apéndice 8).
</Dialog.Description>
</Dialog.Header>
<div class="px-6 pb-4 shrink-0">
<div class="relative">
<Search class="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Buscar por clave o descripción..."
class="pl-9 h-9 text-xs"
bind:value={searchTerm}
/>
</div>
</div>
<div class="flex-1 overflow-y-auto px-6 pb-6">
{#if loading}
<div class="flex h-full items-center justify-center">
<Loader2 class="h-6 w-6 animate-spin text-muted-foreground" />
</div>
{:else}
<Table.Root>
<Table.Header class="sticky top-0 bg-background z-10">
<Table.Row>
<Table.Head class="w-16">Clave</Table.Head>
<Table.Head>Descripción</Table.Head>
<Table.Head class="w-16">Nivel</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each filteredIdentifiers as item}
<Table.Row
class="cursor-pointer hover:bg-muted"
onclick={() => handleSelect(item)}
>
<Table.Cell class="font-bold">{item.code}</Table.Cell>
<Table.Cell>{item.description || '-'}</Table.Cell>
<Table.Cell>{item.level || '-'}</Table.Cell>
</Table.Row>
{:else}
<Table.Row>
<Table.Cell colspan={3} class="h-24 text-center text-muted-foreground">
No se encontraron identificadores.
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
{/if}
</div>
</Dialog.Content>
</Dialog.Root>

View File

@@ -216,7 +216,7 @@
{ value: 'generales', label: 'General', visible: true },
{ value: 'continuacion', label: 'Continuación', visible: true },
{ value: 'series', label: 'Series', visible: true },
{ value: 'etiquetado', label: 'Etiquetado', visible: true },
{ value: 'etiquetado', label: 'Etiquetado', visible: visibility.showLabelingTab },
{ value: 'identificadores', label: 'IDs', visible: visibility.showIdentifiersTab }
].filter((tab) => tab.visible));
const tabListStyle = $derived(`grid-template-columns: repeat(${visibleTabs.length || 1}, minmax(0, 1fr));`);
@@ -544,13 +544,20 @@
/>
</Tabs.Content>
<Tabs.Content value="etiquetado" class="m-0 focus-visible:outline-none">
<TabLabeling bind:descriptions={editingItem.description!} />
</Tabs.Content>
{#if visibility.showLabelingTab}
<Tabs.Content value="etiquetado" class="m-0 focus-visible:outline-none">
<TabLabeling bind:lineItem={editingItem} bind:descriptions={editingItem.description!} {visibility} />
</Tabs.Content>
{/if}
{#if visibility.showIdentifiersTab}
<Tabs.Content value="identificadores" class="m-0 focus-visible:outline-none">
<TabIdentifiers bind:lineItem={editingItem} />
<TabIdentifiers
bind:lineItem={editingItem}
invoiceConsecutive={invoice?.id}
invoiceNumber={invoice?.invoice_number ?? ''}
{visibility}
/>
</Tabs.Content>
{/if}
</div>

View File

@@ -1,26 +1,376 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import type { Item } from '$lib/api/dashboard/a76/items';
import { Button } from '$lib/components/ui/button';
import * as Table from '$lib/components/ui/table';
import * as Dialog from '$lib/components/ui/dialog';
import { Plus, Pencil, Trash2, Search, Image as ImageIcon, Upload } from 'lucide-svelte';
import type { Item, Serie } from '$lib/api/dashboard/a76/items';
import type { IdentifierDetail } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
import IdentifierCatalogSelector from './identifier-catalog-selector.svelte';
import { companyStore } from '$lib/stores/company.svelte';
let { lineItem = $bindable() }: { lineItem: Partial<Item> } = $props();
let {
lineItem = $bindable(),
invoiceConsecutive = undefined,
invoiceNumber = '',
visibility = { showMexicanIdEnhanced: false }
}: {
lineItem: Partial<Item>,
invoiceConsecutive?: number,
invoiceNumber?: string,
visibility?: any
} = $props();
// Initialize identifiers if not present
if (!lineItem.identifiers) {
lineItem.identifiers = [];
}
// Initialize series if not present
if (!lineItem.series) {
lineItem.series = [];
}
let showModal = $state(false);
let showMexModal = $state(false);
let showCatalogSelector = $state(false);
let isEditing = $state(false);
let editingIndex = $state(-1);
// State for standard Identifiers
let currentDetail = $state<Partial<IdentifierDetail>>({
invoice_consecutive: invoiceConsecutive || null,
part_line: lineItem.line_number || null,
module: 'SCAF-IT',
identifier_code: '',
complement1: '',
complement2: '',
complement3: ''
});
// State for MEX Assets
let currentMexAsset = $state<Partial<Serie>>({
number_id: '',
import_invoice: invoiceNumber || '',
import_line: lineItem.line_number ?? undefined,
image_path: ''
});
// Standard ID Functions
function openInsert() {
isEditing = false;
currentDetail = {
invoice_consecutive: invoiceConsecutive ?? null,
part_line: lineItem.line_number ?? null,
module: 'SCAF-IT',
identifier_code: '',
complement1: '',
complement2: '',
complement3: ''
};
showModal = true;
}
function openEdit(index: number) {
isEditing = true;
editingIndex = index;
currentDetail = { ...lineItem.identifiers![index] };
showModal = true;
}
function deleteDetail(index: number) {
lineItem.identifiers = lineItem.identifiers!.filter((_, i) => i !== index);
}
function saveDetail() {
if (!lineItem.identifiers) lineItem.identifiers = [];
if (isEditing) {
lineItem.identifiers[editingIndex] = currentDetail as IdentifierDetail;
} else {
lineItem.identifiers = [...lineItem.identifiers, currentDetail as IdentifierDetail];
}
showModal = false;
}
// MEX Asset Functions
function openMexInsert() {
isEditing = false;
currentMexAsset = {
number_id: '',
import_invoice: invoiceNumber || '',
import_line: lineItem.line_number ?? undefined,
image_path: ''
};
showMexModal = true;
}
function openMexEdit(index: number) {
isEditing = true;
editingIndex = index;
currentMexAsset = { ...lineItem.series![index] };
showMexModal = true;
}
function deleteMexAsset(index: number) {
lineItem.series = lineItem.series!.filter((_, i) => i !== index);
}
function saveMexAsset() {
if (!lineItem.series) lineItem.series = [];
if (isEditing) {
lineItem.series[editingIndex] = currentMexAsset as Serie;
} else {
lineItem.series = [...lineItem.series, currentMexAsset as Serie];
}
showMexModal = false;
}
function handleCatalogSelect(identifier: any) {
currentDetail.identifier_code = identifier.code;
showCatalogSelector = false;
}
// File selection simulation for image_path
function triggerFileSelect() {
// In a real scenario, this would trigger an input type="file"
// For now we'll just simulate setting a path/name
const simulatedPath = `assets/img_${Date.now()}.jpg`;
currentMexAsset.image_path = simulatedPath;
}
</script>
<fieldset class="border rounded-md p-3 space-y-3">
<legend class="text-xs font-semibold px-2 uppercase">Identifiers</legend>
<div class="space-y-2">
<Label for="identificador1" class="text-xs">Main Identifier:</Label>
<Input id="identificador1" bind:value={lineItem.identifier} class="h-8 text-sm" />
</div>
<div class="space-y-4">
{#if visibility.showMexicanIdEnhanced}
<!-- MEX Specific Layout -->
<fieldset class="border rounded-md p-3">
<legend class="text-xs font-semibold px-2 uppercase flex items-center gap-2">
Activos / Num. Etiquetado (MEX)
</legend>
<div class="flex justify-end mb-2">
<Button size="sm" variant="outline" class="h-8 text-xs gap-1" onclick={openMexInsert}>
<Plus class="h-3 w-3" />
Insertar Activo
</Button>
</div>
<div class="space-y-2">
<Label for="notas_identificadores" class="text-xs">Additional Identifiers / Notes:</Label>
<textarea
id="notas_identificadores"
bind:value={lineItem.wildcard_field}
class="flex min-h-[120px] w-full rounded-md border-2 border-input dark:border-zinc-600 bg-background dark:text-white px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 dark:focus-visible:border-zinc-400 dark:focus-visible:ring-zinc-400/50"
placeholder="Additional identifiers or notes..."
></textarea>
</div>
</fieldset>
<div class="border rounded-md overflow-hidden bg-white dark:bg-zinc-900">
<Table.Root>
<Table.Header class="bg-gray-50 dark:bg-zinc-800">
<Table.Row class="h-8">
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Asset Number</Table.Head>
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Num. Factura</Table.Head>
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Línea</Table.Head>
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8 text-center">Imagen</Table.Head>
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8 w-20 text-center">Acciones</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#if lineItem.series && lineItem.series.length > 0}
{#each lineItem.series as asset, index}
<Table.Row class="h-8 hover:bg-gray-50 dark:hover:bg-zinc-800/50 transition-colors">
<Table.Cell class="py-1 text-xs font-semibold">{asset.number_id}</Table.Cell>
<Table.Cell class="py-1 text-xs">{asset.import_invoice || '-'}</Table.Cell>
<Table.Cell class="py-1 text-xs">{asset.import_line || '-'}</Table.Cell>
<Table.Cell class="py-1 text-xs text-center">
{#if asset.image_path}
<div class="flex justify-center">
<ImageIcon class="h-4 w-4 text-blue-500" />
</div>
{:else}
<span class="text-gray-400">-</span>
{/if}
</Table.Cell>
<Table.Cell class="py-1 text-xs">
<div class="flex items-center justify-center gap-1">
<Button size="icon" variant="ghost" class="h-6 w-6 text-blue-500 hover:text-blue-600 hover:bg-blue-50" onclick={() => openMexEdit(index)}>
<Pencil class="h-3 w-3" />
</Button>
<Button size="icon" variant="ghost" class="h-6 w-6 text-red-500 hover:text-red-600 hover:bg-red-50" onclick={() => deleteMexAsset(index)}>
<Trash2 class="h-3 w-3" />
</Button>
</div>
</Table.Cell>
</Table.Row>
{/each}
{:else}
<Table.Row>
<Table.Cell colspan={5} class="h-20 text-center text-gray-400 text-xs italic">
No hay activos registrados.
</Table.Cell>
</Table.Row>
{/if}
</Table.Body>
</Table.Root>
</div>
</fieldset>
{:else}
<!-- Standard ID Layout -->
<fieldset class="border rounded-md p-3">
<legend class="text-xs font-semibold px-2 uppercase flex items-center gap-2">
Tabla de Identificadores
</legend>
<div class="flex justify-end mb-2">
<Button size="sm" variant="outline" class="h-8 text-xs gap-1" onclick={openInsert}>
<Plus class="h-3 w-3" />
Insertar
</Button>
</div>
<div class="border rounded-md overflow-hidden bg-white dark:bg-zinc-900">
<Table.Root>
<Table.Header class="bg-gray-100 dark:bg-zinc-800">
<Table.Row class="h-8">
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Clave</Table.Head>
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Compl. 1</Table.Head>
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Compl. 2</Table.Head>
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Compl. 3</Table.Head>
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8 w-20 text-center">Acciones</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#if lineItem.identifiers && lineItem.identifiers.length > 0}
{#each lineItem.identifiers as idDetail, index}
<Table.Row class="h-8 hover:bg-gray-50 dark:hover:bg-zinc-800/50 transition-colors">
<Table.Cell class="py-1 text-xs font-semibold">{idDetail.identifier_code}</Table.Cell>
<Table.Cell class="py-1 text-xs">{idDetail.complement1 || '-'}</Table.Cell>
<Table.Cell class="py-1 text-xs">{idDetail.complement2 || '-'}</Table.Cell>
<Table.Cell class="py-1 text-xs">{idDetail.complement3 || '-'}</Table.Cell>
<Table.Cell class="py-1 text-xs">
<div class="flex items-center justify-center gap-1">
<Button size="icon" variant="ghost" class="h-6 w-6 text-blue-500 hover:text-blue-600 hover:bg-blue-50" onclick={() => openEdit(index)}>
<Pencil class="h-3 w-3" />
</Button>
<Button size="icon" variant="ghost" class="h-6 w-6 text-red-500 hover:text-red-600 hover:bg-red-50" onclick={() => deleteDetail(index)}>
<Trash2 class="h-3 w-3" />
</Button>
</div>
</Table.Cell>
</Table.Row>
{/each}
{:else}
<Table.Row>
<Table.Cell colspan={5} class="h-20 text-center text-gray-400 text-xs italic">
No hay identificadores registrados.
</Table.Cell>
</Table.Row>
{/if}
</Table.Body>
</Table.Root>
</div>
</fieldset>
{/if}
</div>
<!-- Modal Standard ID -->
<Dialog.Root bind:open={showModal}>
<Dialog.Content class="sm:max-w-[500px]">
<Dialog.Header>
<Dialog.Title>{isEditing ? 'Editar' : 'Insertar'} Identificador</Dialog.Title>
<Dialog.Description>
Ingrese los detalles del identificador para esta partida.
</Dialog.Description>
</Dialog.Header>
<div class="grid grid-cols-2 gap-4 py-4">
<div class="space-y-2">
<Label class="text-xs">Consecutivo Factura</Label>
<Input bind:value={currentDetail.invoice_consecutive} readonly class="h-8 text-xs bg-gray-50" />
</div>
<div class="space-y-2">
<Label class="text-xs">Partida (Número)</Label>
<Input bind:value={currentDetail.part_line} readonly class="h-8 text-xs bg-gray-50" />
</div>
<div class="space-y-2">
<Label class="text-xs">Módulo</Label>
<Input bind:value={currentDetail.module} class="h-8 text-xs" />
</div>
<div class="space-y-2">
<Label class="text-xs">Clave</Label>
<div class="flex gap-1">
<Input bind:value={currentDetail.identifier_code} class="h-8 text-xs font-bold" placeholder="Clave" />
<Button size="icon" variant="outline" class="h-8 w-8 shrink-0" onclick={() => showCatalogSelector = true}>
<Search class="h-3 w-3" />
</Button>
</div>
</div>
<div class="col-span-2 grid grid-cols-3 gap-2">
<div class="space-y-2">
<Label class="text-xs">Complemento 1</Label>
<Input bind:value={currentDetail.complement1} class="h-8 text-xs" />
</div>
<div class="space-y-2">
<Label class="text-xs">Complemento 2</Label>
<Input bind:value={currentDetail.complement2} class="h-8 text-xs" />
</div>
<div class="space-y-2">
<Label class="text-xs">Complemento 3</Label>
<Input bind:value={currentDetail.complement3} class="h-8 text-xs" />
</div>
</div>
</div>
<Dialog.Footer>
<Button variant="outline" onclick={() => showModal = false} class="h-8 text-xs">Cancelar</Button>
<Button onclick={saveDetail} class="h-8 text-xs">Guardar</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
<!-- Modal MEX Assets -->
<Dialog.Root bind:open={showMexModal}>
<Dialog.Content class="sm:max-w-[450px]">
<Dialog.Header>
<Dialog.Title>{isEditing ? 'Editar' : 'Insertar'} Asset Tag (Etiquetado)</Dialog.Title>
<Dialog.Description>
Detalles del etiquetado de activos para Compras Mexicanas.
</Dialog.Description>
</Dialog.Header>
<div class="space-y-4 py-4">
<!-- Info Section -->
<div class="bg-gray-50 dark:bg-zinc-800 p-3 rounded-md grid grid-cols-2 gap-3 border">
<div class="space-y-1">
<Label class="text-[10px] uppercase text-gray-500">Número de Factura</Label>
<p class="text-xs font-bold">{currentMexAsset.import_invoice || '-'}</p>
</div>
<div class="space-y-1">
<Label class="text-[10px] uppercase text-gray-500">Línea de Partida</Label>
<p class="text-xs font-bold">{currentMexAsset.import_line || '-'}</p>
</div>
</div>
<!-- Input Section -->
<div class="space-y-3">
<div class="space-y-2">
<Label class="text-xs">Asset Number</Label>
<Input bind:value={currentMexAsset.number_id} placeholder="Ingrese número de activo..." class="h-9 text-xs" />
</div>
<div class="space-y-2">
<Label class="text-xs">Imagen del Archivo</Label>
<div class="flex items-center gap-2">
<div class="flex-1 h-9 px-3 py-2 border rounded-md bg-white dark:bg-zinc-900 text-xs truncate italic text-gray-500">
{currentMexAsset.image_path || 'Ningún archivo seleccionado'}
</div>
<Button size="sm" variant="secondary" class="h-9 gap-1 text-xs" onclick={triggerFileSelect}>
<Upload class="h-3 w-3" />
Subir
</Button>
</div>
</div>
</div>
</div>
<Dialog.Footer>
<Button variant="outline" onclick={() => showMexModal = false} class="h-8 text-xs">Cancelar</Button>
<Button onclick={saveMexAsset} class="h-8 text-xs">Guardar</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
<IdentifierCatalogSelector
bind:open={showCatalogSelector}
onSelect={handleCatalogSelect}
/>

View File

@@ -1,32 +1,262 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import type { LineDescriptions } from '$lib/api/dashboard/a76/items';
import { Button } from '$lib/components/ui/button';
import * as Table from '$lib/components/ui/table';
import { Plus, Pencil, Trash2, Folder } from 'lucide-svelte';
import type { Item, LineDescriptions, Serie } from '$lib/api/dashboard/a76/items';
import ValuationMethodSelector from './valuation-method-selector.svelte';
let { descriptions = $bindable() }: { descriptions: LineDescriptions } = $props();
let {
lineItem = $bindable(),
descriptions = $bindable(),
visibility
}: {
lineItem: Partial<Item>,
descriptions: LineDescriptions,
visibility: any
} = $props();
// Ensure series is an array
if (!lineItem.series) {
lineItem.series = [];
}
let valuationSelectorOpen = $state(false);
// Asset management state
let selectedAssetIndex = $state<number | null>(null);
let editingAsset = $state<Serie>({
row: 0,
number_id: '',
import_invoice: '',
import_line: undefined
});
function handleValuationMethodSelect(method: { key: string }) {
lineItem.valuation_method = method.key;
}
function addAsset() {
const newAsset: Serie = {
row: (lineItem.series?.length || 0) + 1,
number_id: '',
import_invoice: '',
import_line: undefined
};
lineItem.series = [...(lineItem.series || []), newAsset];
editAsset((lineItem.series || []).length - 1);
}
function editAsset(index: number) {
selectedAssetIndex = index;
editingAsset = { ...(lineItem.series?.[index] as Serie) };
}
function saveAsset() {
if (selectedAssetIndex !== null && lineItem.series) {
const updatedSeries = [...lineItem.series];
updatedSeries[selectedAssetIndex] = { ...editingAsset };
lineItem.series = updatedSeries;
selectedAssetIndex = null;
}
}
function deleteAsset(index: number) {
if (lineItem.series) {
lineItem.series = lineItem.series.filter((_, i) => i !== index);
// Re-index rows
lineItem.series = lineItem.series.map((s, i) => ({ ...s, row: i + 1 }));
if (selectedAssetIndex === index) {
selectedAssetIndex = null;
} else if (selectedAssetIndex !== null && selectedAssetIndex > index) {
selectedAssetIndex--;
}
}
}
function cancelAssetEdit() {
selectedAssetIndex = null;
}
</script>
<fieldset class="border rounded-md p-3 space-y-3">
<legend class="text-xs font-semibold px-2 uppercase">Labeling</legend>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="numero_etiqueta" class="text-xs">Label Number:</Label>
<Input id="numero_etiqueta" bind:value={descriptions.lot} class="h-8 text-sm" />
</div>
<div class="space-y-2">
<Label for="tipo_etiqueta" class="text-xs">Label Type:</Label>
<Input id="tipo_etiqueta" bind:value={descriptions.entry_number} class="h-8 text-sm" />
</div>
</div>
<div class="grid grid-cols-1 {visibility.showLabelingLeftSection ? 'md:grid-cols-2' : ''} gap-2">
{#if visibility.showLabelingLeftSection}
<!-- Left Side: Labeling & Valuation -->
<fieldset class="border rounded-md p-2 space-y-2">
<legend class="text-xs font-semibold px-2 bg-gray-200 dark:bg-gray-700 uppercase">Labeling & Valuation</legend>
{#if visibility.showLabelingStandard}
<div class="grid grid-cols-2 gap-2">
<div class="space-y-1">
<Label for="numero_etiqueta" class="text-xs">Label Number:</Label>
<Input id="numero_etiqueta" bind:value={descriptions.lot} class="h-7 text-xs" />
</div>
<div class="space-y-1">
<Label for="tipo_etiqueta" class="text-xs">Label Type:</Label>
<Input id="tipo_etiqueta" bind:value={descriptions.entry_number} class="h-7 text-xs" />
</div>
</div>
{/if}
<div class="space-y-2">
<Label for="observaciones_etiqueta" class="text-xs">Observations:</Label>
<textarea
id="observaciones_etiqueta"
bind:value={descriptions.additional_info_spanish}
class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
placeholder="Labeling observations..."
></textarea>
</div>
</fieldset>
<div class="grid grid-cols-2 gap-2">
{#if visibility.showLabelingEnhanced}
{#if visibility.showLabelingQuantity}
<div class="space-y-1">
<Label for="cantidad_importar" class="text-xs">Cantidad a importar:</Label>
<Input
id="cantidad_importar"
type="number"
bind:value={lineItem.quantity!.quantity}
class="h-7 text-xs"
/>
</div>
{/if}
{#if visibility.showLabelingValuationValue}
<div class="space-y-1">
<Label for="valor_det" class="text-xs">Valor Det:</Label>
<Input
id="valor_det"
type="number"
step="0.00000001"
bind:value={lineItem.valuation_determined_value}
class="h-7 text-xs"
/>
</div>
{/if}
{/if}
</div>
{#if visibility.showLabelingEnhanced}
{#if visibility.showLabelingValuationMethod}
<div class="space-y-1">
<Label for="metodos_valoracion" class="text-xs">Métodos de valoración:</Label>
<div class="flex gap-1">
<Input
id="metodos_valoracion"
bind:value={lineItem.valuation_method}
class="h-7 text-xs flex-1"
placeholder="Seleccione..."
/>
<Button
size="icon"
variant="outline"
class="h-7 w-7"
onclick={() => valuationSelectorOpen = true}
>
<Folder class="h-3 w-3" />
</Button>
</div>
</div>
{/if}
{#if visibility.showUsageReason}
<div class="space-y-1">
<Label for="motivo_uso" class="text-xs">Motivo de uso:</Label>
<Input
id="motivo_uso"
bind:value={lineItem.valuation_reason}
class="h-7 text-xs"
placeholder="Especifique motivo..."
/>
</div>
{/if}
{/if}
{#if visibility.showLabelingObservations}
<div class="space-y-1">
<Label for="observaciones_etiqueta" class="text-xs">Observations:</Label>
<textarea
id="observaciones_etiqueta"
bind:value={descriptions.additional_info_spanish}
class="flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-1.5 text-xs ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
placeholder="Labeling observations..."
></textarea>
</div>
{/if}
</fieldset>
{/if}
{#if visibility.showLabelingEnhanced}
<!-- Right Side: Assets Table -->
<fieldset class="border rounded-md p-2 space-y-2">
<legend class="text-xs font-semibold px-2 bg-gray-200 dark:bg-gray-700 uppercase">Assets / Series</legend>
<div class="flex justify-end">
<Button size="sm" variant="outline" class="h-7 text-xs px-2" onclick={addAsset}>
<Plus class="h-3 h-3 mr-1" /> Insertar Activo
</Button>
</div>
<div class="rounded-md border overflow-hidden max-h-[180px] overflow-y-auto">
<Table.Root>
<Table.Header>
<Table.Row class="bg-muted/50 h-7">
<Table.Head class="w-8 text-center text-[10px] p-0 px-1">#</Table.Head>
<Table.Head class="text-[10px] p-0 px-1">Asset Num</Table.Head>
<Table.Head class="text-[10px] p-0 px-1">Factura</Table.Head>
<Table.Head class="text-[10px] p-0 px-1">Línea</Table.Head>
<Table.Head class="w-14 text-right text-[10px] p-0 px-2">Acc</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each lineItem.series || [] as asset, i}
<Table.Row class="hover:bg-muted/50 h-7">
<Table.Cell class="text-center text-[10px] p-0 px-1 font-medium">{asset.row || i+1}</Table.Cell>
<Table.Cell class="text-[10px] p-0 px-1 truncate max-w-[60px]">{asset.number_id || '-'}</Table.Cell>
<Table.Cell class="text-[10px] p-0 px-1 truncate max-w-[60px]">{asset.import_invoice || '-'}</Table.Cell>
<Table.Cell class="text-[10px] p-0 px-1">{asset.import_line || '-'}</Table.Cell>
<Table.Cell class="text-right p-0 px-2">
<div class="flex justify-end gap-0.5">
<Button variant="ghost" size="icon" class="h-5 w-5" onclick={() => editAsset(i)}>
<Pencil class="h-2.5 w-2.5" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5 text-destructive" onclick={() => deleteAsset(i)}>
<Trash2 class="h-2.5 w-2.5" />
</Button>
</div>
</Table.Cell>
</Table.Row>
{:else}
<Table.Row>
<Table.Cell colspan={5} class="h-12 text-center text-muted-foreground text-[10px]">
No hay activos.
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
{#if selectedAssetIndex !== null}
<div class="p-2 border rounded bg-muted/20 space-y-2">
<div class="text-[10px] font-semibold uppercase">Editar #{editingAsset.row}</div>
<div class="grid grid-cols-2 gap-2">
<div class="space-y-0.5">
<Label class="text-[10px]">Asset Number</Label>
<Input bind:value={editingAsset.number_id} class="h-6 text-[10px]" />
</div>
<div class="space-y-0.5">
<Label class="text-[10px]">Factura Impo</Label>
<Input bind:value={editingAsset.import_invoice} class="h-6 text-[10px]" />
</div>
<div class="space-y-0.5">
<Label class="text-[10px]">Linea Impo</Label>
<Input type="number" bind:value={editingAsset.import_line} class="h-6 text-[10px]" />
</div>
</div>
<div class="flex justify-end gap-1.5 pt-1">
<Button size="sm" variant="ghost" class="h-6 text-[10px] px-2" onclick={cancelAssetEdit}>Can</Button>
<Button size="sm" class="h-6 text-[10px] px-2" onclick={saveAsset}>Guar</Button>
</div>
</div>
{/if}
</fieldset>
{/if}
</div>
<ValuationMethodSelector
bind:open={valuationSelectorOpen}
onSelect={handleValuationMethodSelect}
/>

View File

@@ -0,0 +1,108 @@
<script lang="ts">
import * as Dialog from "$lib/components/ui/dialog";
import * as Table from "$lib/components/ui/table";
import { Button } from "$lib/components/ui/button";
import { Input } from "$lib/components/ui/input";
import { Search, Loader2 } from "lucide-svelte";
import { valuationMethodsApi, type ValuationMethod } from "$lib/api/dashboard/reference_data/valuation_methods";
import { onMount } from "svelte";
let {
open = $bindable(false),
onSelect
}: {
open: boolean;
onSelect: (method: ValuationMethod) => void;
} = $props();
let methods = $state<ValuationMethod[]>([]);
let loading = $state(false);
let searchTerm = $state("");
async function loadMethods() {
loading = true;
try {
const res = await valuationMethodsApi.list(1, 100);
if (res.data) {
methods = res.data.items || [];
}
} catch (error) {
console.error("Error loading valuation methods:", error);
} finally {
loading = false;
}
}
onMount(() => {
loadMethods();
});
const filteredMethods = $derived(
methods.filter(m =>
m.key.toLowerCase().includes(searchTerm.toLowerCase()) ||
m.description.toLowerCase().includes(searchTerm.toLowerCase())
)
);
function handleSelect(method: ValuationMethod) {
onSelect(method);
open = false;
}
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[500px] h-[600px] flex flex-col p-0">
<Dialog.Header class="px-6 pt-6 pb-4 shrink-0">
<Dialog.Title>Seleccionar Método de Valoración</Dialog.Title>
<Dialog.Description>
Busca y selecciona un método de valoración de la lista.
</Dialog.Description>
</Dialog.Header>
<div class="px-6 pb-4 shrink-0">
<div class="relative">
<Search class="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Buscar por clave o descripción..."
class="pl-9 h-9"
bind:value={searchTerm}
/>
</div>
</div>
<div class="flex-1 overflow-y-auto px-6 pb-6">
{#if loading}
<div class="flex h-full items-center justify-center">
<Loader2 class="h-6 w-6 animate-spin text-muted-foreground" />
</div>
{:else}
<Table.Root>
<Table.Header class="sticky top-0 bg-background z-10">
<Table.Row>
<Table.Head class="w-20">Clave</Table.Head>
<Table.Head>Descripción</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each filteredMethods as method}
<Table.Row
class="cursor-pointer hover:bg-muted"
onclick={() => handleSelect(method)}
>
<Table.Cell class="font-medium">{method.key}</Table.Cell>
<Table.Cell>{method.description}</Table.Cell>
</Table.Row>
{:else}
<Table.Row>
<Table.Cell colspan={2} class="h-24 text-center text-muted-foreground">
No se encontraron métodos de valoración.
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
{/if}
</div>
</Dialog.Content>
</Dialog.Root>

View File

@@ -31,6 +31,26 @@ export interface InvoiceItemVisibility {
showContinuationConsiderA31: boolean;
/** Continuación tab: Extra Description in Spanish. */
showContinuationExtraDescription: boolean;
/** Etiquetado tab: Quantity, Valuation, Assets Table. */
showLabelingEnhanced: boolean;
/** Identificadores tab: Special Assets table for MEX. */
showMexicanIdEnhanced: boolean;
/** Etiquetado tab visibility. */
showLabelingTab: boolean;
/** Etiquetado tab: Usage Reason field. */
showUsageReason: boolean;
/** Etiquetado tab: Standard fields (Label No, Type, Observations). */
showLabelingStandard: boolean;
/** Etiquetado tab: Quantity field. */
showLabelingQuantity: boolean;
/** Etiquetado tab: Observations field. */
showLabelingObservations: boolean;
/** Etiquetado tab: Valuation field (Valor Det). */
showLabelingValuationValue: boolean;
/** Etiquetado tab: Valuation Method selector. */
showLabelingValuationMethod: boolean;
/** Etiquetado tab: Whole left section (Legend + all fields). */
showLabelingLeftSection: boolean;
}
const defaultVisibility: InvoiceItemVisibility = {
@@ -50,7 +70,17 @@ const defaultVisibility: InvoiceItemVisibility = {
showContinuationOwnOmitAnnex: true,
showContinuationLotEntry: true,
showContinuationConsiderA31: true,
showContinuationExtraDescription: true
showContinuationExtraDescription: true,
showLabelingEnhanced: false,
showMexicanIdEnhanced: false,
showLabelingTab: true,
showUsageReason: false,
showLabelingStandard: true,
showLabelingQuantity: false,
showLabelingObservations: true,
showLabelingValuationValue: false,
showLabelingValuationMethod: true,
showLabelingLeftSection: true
};
function normalizeInvoiceType(invoiceType?: string | null): string {
@@ -106,8 +136,27 @@ export function getVisibility(
const expVisibility: InvoiceItemVisibility = {
...defaultVisibility,
showExportLinkToImportBlock: true,
showExportValuationFields: true
showExportValuationFields: true,
showLabelingEnhanced: true,
showUsageReason: true,
showLabelingValuationValue: true,
showLabelingStandard: false,
showLabelingObservations: false,
showLabelingQuantity: false
};
// CHECK FOR EXPORT REPAIR (REP / REPAR)
const upInvoiceType = normalizeInvoiceType(invoiceType);
if (upInvoiceType === 'REP' || upInvoiceType === 'REPAR') {
return {
...expVisibility,
showLabelingValuationMethod: false,
showLabelingValuationValue: false,
showUsageReason: false,
showLabelingLeftSection: false
};
}
// Per-type overrides can be added here (e.g. hide valuation for SCRAP).
switch (exportType) {
case 'NODES':
@@ -126,14 +175,24 @@ export function getVisibility(
return {
...defaultVisibility,
showCrTrackingHeader: false,
showFdaFcc: false
showFdaFcc: false,
showLabelingEnhanced: true,
showLabelingQuantity: true,
showLabelingObservations: true,
showLabelingValuationValue: true
};
case 'CR':
return {
...defaultVisibility,
showEighthRule: false,
showValuationFields: true
showIdentifiersTab: true,
showValuationFields: true,
showUsageReason: true,
showLabelingEnhanced: true,
showLabelingQuantity: false,
showLabelingStandard: false,
showLabelingObservations: false
};
case 'REP':
@@ -154,7 +213,13 @@ export function getVisibility(
showContinuationOwnOmitAnnex: false,
showContinuationLotEntry: false,
showContinuationConsiderA31: false,
showContinuationExtraDescription: true
showContinuationExtraDescription: true,
showLabelingEnhanced: true,
showLabelingQuantity: true,
showLabelingStandard: true,
showLabelingObservations: false,
showLabelingValuationValue: false,
showLabelingValuationMethod: true
};
case 'MEX':
@@ -165,7 +230,9 @@ export function getVisibility(
showEighthRule: false,
showFdaFcc: false,
showCertificateOfOrigin: false,
showIdentifiersTab: false,
showIdentifiersTab: true,
showMexicanIdEnhanced: true,
showLabelingTab: false,
showContinuationIgi: false,
showContinuationLocation: true,
showContinuationMilitary: false,