Partidas por tipo de factura en importacion
This commit is contained in:
@@ -32,6 +32,7 @@ class IdentifierDetailBase(BaseModel):
|
|||||||
part_line: Optional[int] = Field(None, description="Part Line")
|
part_line: Optional[int] = Field(None, description="Part Line")
|
||||||
identifier_code: Optional[str] = Field(
|
identifier_code: Optional[str] = Field(
|
||||||
None, max_length=2, description="Identifier Code")
|
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")
|
module: Optional[str] = Field(None, max_length=20, description="Module")
|
||||||
complement1: Optional[str] = Field(
|
complement1: Optional[str] = Field(
|
||||||
None, max_length=50, description="Complement 1")
|
None, max_length=50, description="Complement 1")
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
from typing import Optional
|
from typing import Optional, TYPE_CHECKING
|
||||||
from sqlalchemy import Integer, String, UniqueConstraint, ForeignKey
|
from sqlalchemy import Integer, String, UniqueConstraint, ForeignKey
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||||
from core.database import Base
|
from core.database import Base
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from api.v1.modules.a76.items.models import LineItem
|
||||||
|
|
||||||
class Identifier(Base, TenantScopedMixin, TimestampMixin):
|
class Identifier(Base, TenantScopedMixin, TimestampMixin):
|
||||||
__tablename__ = "identifiers"
|
__tablename__ = "identifiers"
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
@@ -44,6 +47,8 @@ class IdentifierDetail(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
Integer, nullable=True) # LINEAPARTIDA
|
Integer, nullable=True) # LINEAPARTIDA
|
||||||
identifier_code: Mapped[Optional[str]] = mapped_column(
|
identifier_code: Mapped[Optional[str]] = mapped_column(
|
||||||
String(2), ForeignKey("a76.identifiers.code"), nullable=True) # ID
|
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(
|
module: Mapped[Optional[str]] = mapped_column(
|
||||||
String(20), nullable=True) # MODULO
|
String(20), nullable=True) # MODULO
|
||||||
complement1: Mapped[Optional[str]] = mapped_column(
|
complement1: Mapped[Optional[str]] = mapped_column(
|
||||||
@@ -54,3 +59,4 @@ class IdentifierDetail(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
String(50), nullable=True) # COMPLEMENTO3
|
String(50), nullable=True) # COMPLEMENTO3
|
||||||
|
|
||||||
identifier: Mapped["Identifier"] = relationship(back_populates="details")
|
identifier: Mapped["Identifier"] = relationship(back_populates="details")
|
||||||
|
line: Mapped[Optional["LineItem"]] = relationship(back_populates="identifiers")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ SQLAlchemy v2 - Annex 24 Compliance
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, List, TYPE_CHECKING
|
||||||
from core.database import Base
|
from core.database import Base
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from sqlalchemy import Boolean, Date, String, Integer, Numeric, SmallInteger, ForeignKey
|
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.a24.fa.fa_item_lines.models import FaLineItem
|
||||||
from api.v1.modules.a76.parts.models import Part
|
from api.v1.modules.a76.parts.models import Part
|
||||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||||
|
from api.v1.modules.a76.general_catalogs.identifiers.models import IdentifierDetail
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# CORE ENTITIES
|
# CORE ENTITIES
|
||||||
@@ -216,10 +217,14 @@ class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
)
|
)
|
||||||
fa_data: Mapped[Optional["FaLineItem"]] = relationship(
|
fa_data: Mapped[Optional["FaLineItem"]] = relationship(
|
||||||
"FaLineItem",
|
"FaLineItem",
|
||||||
back_populates="master_info",
|
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
uselist=False,
|
uselist=False,
|
||||||
)
|
)
|
||||||
|
identifiers: Mapped[List["IdentifierDetail"]] = relationship(
|
||||||
|
"IdentifierDetail",
|
||||||
|
back_populates="line",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
)
|
||||||
part_info: Mapped[Optional["Part"]] = relationship(
|
part_info: Mapped[Optional["Part"]] = relationship(
|
||||||
"Part",
|
"Part",
|
||||||
foreign_keys=[part_number_id],
|
foreign_keys=[part_number_id],
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ from .line_references.schemas import (
|
|||||||
LineReferenceResponse,
|
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 (
|
from api.v1.modules.a24.fa.fa_item_lines.dto import (
|
||||||
FaLineItemCreateDTO,
|
FaLineItemCreateDTO,
|
||||||
FaLineItemUpdateDTO,
|
FaLineItemUpdateDTO,
|
||||||
@@ -238,6 +244,9 @@ class LineItemCreate(LineItemBase):
|
|||||||
series: Optional[list[SerieCreate]] = Field(
|
series: Optional[list[SerieCreate]] = Field(
|
||||||
None, description="Series data for this line (multiple per line)"
|
None, description="Series data for this line (multiple per line)"
|
||||||
)
|
)
|
||||||
|
identifiers: Optional[list[IdentifierDetailCreate]] = Field(
|
||||||
|
None, description="Identifiers for this line"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LineItemUpdate(LineItemBase):
|
class LineItemUpdate(LineItemBase):
|
||||||
@@ -267,6 +276,9 @@ class LineItemUpdate(LineItemBase):
|
|||||||
series: Optional[list[SerieUpdate]] = Field(
|
series: Optional[list[SerieUpdate]] = Field(
|
||||||
None, description="Series data for this line (replace all)"
|
None, description="Series data for this line (replace all)"
|
||||||
)
|
)
|
||||||
|
identifiers: Optional[list[IdentifierDetailUpdate]] = Field(
|
||||||
|
None, description="Identifiers for this line"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LineItemResponse(LineItemBase):
|
class LineItemResponse(LineItemBase):
|
||||||
@@ -295,6 +307,7 @@ class LineItemResponse(LineItemBase):
|
|||||||
reference: Optional[LineReferenceResponse] = None
|
reference: Optional[LineReferenceResponse] = None
|
||||||
fa_data: Optional[FaLineItemResponseDTO] = None
|
fa_data: Optional[FaLineItemResponseDTO] = None
|
||||||
series: Optional[list[SerieResponse]] = None
|
series: Optional[list[SerieResponse]] = None
|
||||||
|
identifiers: Optional[list[IdentifierDetailResponse]] = None
|
||||||
|
|
||||||
# Fields populated from relationships
|
# Fields populated from relationships
|
||||||
class_code: Optional[str] = None
|
class_code: Optional[str] = None
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ class Serie(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA
|
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA
|
||||||
expo_brad: Mapped[Optional[str]] = mapped_column(String(50)) # MARCAEXPO
|
expo_brad: Mapped[Optional[str]] = mapped_column(String(50)) # MARCAEXPO
|
||||||
number_id: Mapped[Optional[str]] = mapped_column(String(25)) # NUMIDEXPO
|
number_id: Mapped[Optional[str]] = mapped_column(String(25)) # NUMIDEXPO
|
||||||
|
import_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURAIMPO
|
||||||
|
import_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEAIMPO
|
||||||
|
image_path: Mapped[Optional[str]] = mapped_column(String(255)) # PATH DE IMAGEN (MEX)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -10,6 +10,9 @@ class SerieBase(BaseModel):
|
|||||||
brand: Optional[str] = Field(None, max_length=50, description="Brand (MARCA)")
|
brand: Optional[str] = Field(None, max_length=50, description="Brand (MARCA)")
|
||||||
expo_brad: Optional[str] = Field(None, max_length=50, description="Expo brand (MARCAEXPO)")
|
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)")
|
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):
|
class SerieCreate(SerieBase):
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ from .models import LineItem
|
|||||||
from .series.models import Serie
|
from .series.models import Serie
|
||||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||||
from api.v1.modules.a76.parts.models import Part
|
from api.v1.modules.a76.parts.models import Part
|
||||||
|
from api.v1.modules.a76.general_catalogs.identifiers.models import IdentifierDetail
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -195,6 +196,26 @@ class ItemService:
|
|||||||
serie_dict["row"] = 1
|
serie_dict["row"] = 1
|
||||||
db.add(Serie(**serie_dict))
|
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
|
@staticmethod
|
||||||
def _attach_series(db: Session, item: LineItem) -> None:
|
def _attach_series(db: Session, item: LineItem) -> None:
|
||||||
"""Query and attach all Serie rows for this item as a list."""
|
"""Query and attach all Serie rows for this item as a list."""
|
||||||
@@ -206,6 +227,16 @@ class ItemService:
|
|||||||
)
|
)
|
||||||
item.series = list(series)
|
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
|
@staticmethod
|
||||||
def get_by_id(
|
def get_by_id(
|
||||||
db: Session, item_id: int, tenant_id: int, company_id: int
|
db: Session, item_id: int, tenant_id: int, company_id: int
|
||||||
@@ -232,6 +263,7 @@ class ItemService:
|
|||||||
)
|
)
|
||||||
if result:
|
if result:
|
||||||
ItemService._attach_series(db, result)
|
ItemService._attach_series(db, result)
|
||||||
|
ItemService._attach_identifiers(db, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -285,6 +317,7 @@ class ItemService:
|
|||||||
items = query.offset(skip).limit(limit).all()
|
items = query.offset(skip).limit(limit).all()
|
||||||
for item in items:
|
for item in items:
|
||||||
ItemService._attach_series(db, item)
|
ItemService._attach_series(db, item)
|
||||||
|
ItemService._attach_identifiers(db, item)
|
||||||
return items, total
|
return items, total
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -318,6 +351,7 @@ class ItemService:
|
|||||||
items = query.offset(skip).limit(limit).all()
|
items = query.offset(skip).limit(limit).all()
|
||||||
for item in items:
|
for item in items:
|
||||||
ItemService._attach_series(db, item)
|
ItemService._attach_series(db, item)
|
||||||
|
ItemService._attach_identifiers(db, item)
|
||||||
return items, total
|
return items, total
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -443,6 +477,7 @@ class ItemService:
|
|||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_item)
|
db.refresh(db_item)
|
||||||
ItemService._attach_series(db, db_item)
|
ItemService._attach_series(db, db_item)
|
||||||
|
ItemService._attach_identifiers(db, db_item)
|
||||||
return db_item
|
return db_item
|
||||||
|
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
@@ -591,6 +626,7 @@ class ItemService:
|
|||||||
).delete()
|
).delete()
|
||||||
db.query(FaLineItem).filter(FaLineItem.id == db_item.id).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(Serie).filter(Serie.line_item_id == db_item.id).delete()
|
||||||
|
db.query(IdentifierDetail).filter(IdentifierDetail.item_line_id == db_item.id).delete()
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
# Create new nested data
|
# Create new nested data
|
||||||
@@ -604,6 +640,7 @@ class ItemService:
|
|||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_item)
|
db.refresh(db_item)
|
||||||
ItemService._attach_series(db, db_item)
|
ItemService._attach_series(db, db_item)
|
||||||
|
ItemService._attach_identifiers(db, db_item)
|
||||||
return db_item
|
return db_item
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
|
|||||||
@@ -35,6 +35,33 @@ export interface IdentifierListResponse {
|
|||||||
pages: number;
|
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(
|
export async function getIdentifiers(
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 50,
|
pageSize = 50,
|
||||||
@@ -71,4 +98,29 @@ export async function deleteIdentifier(
|
|||||||
companyId: number
|
companyId: number
|
||||||
): Promise<ApiResponse<void>> {
|
): Promise<ApiResponse<void>> {
|
||||||
return await api.delete(`/v1/a76/identifiers/${id}/?company_id=${companyId}`);
|
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}`);
|
||||||
}
|
}
|
||||||
@@ -88,6 +88,10 @@ export interface LineReferences {
|
|||||||
serie_id?: number;
|
serie_id?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import type {
|
||||||
|
IdentifierDetail
|
||||||
|
} from './general_catalogs/identifiers';
|
||||||
|
|
||||||
export interface Serie {
|
export interface Serie {
|
||||||
id?: number;
|
id?: number;
|
||||||
line_item_id?: number;
|
line_item_id?: number;
|
||||||
@@ -98,6 +102,9 @@ export interface Serie {
|
|||||||
brand?: string;
|
brand?: string;
|
||||||
expo_brad?: string;
|
expo_brad?: string;
|
||||||
number_id?: string;
|
number_id?: string;
|
||||||
|
import_invoice?: string;
|
||||||
|
import_line?: number;
|
||||||
|
image_path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FaLineItem {
|
export interface FaLineItem {
|
||||||
@@ -207,6 +214,7 @@ export interface Item {
|
|||||||
reference?: LineReferences;
|
reference?: LineReferences;
|
||||||
fa_data?: FaLineItem; // Fixed Asset specific data
|
fa_data?: FaLineItem; // Fixed Asset specific data
|
||||||
series?: Serie[]; // Series data (multiple per line)
|
series?: Serie[]; // Series data (multiple per line)
|
||||||
|
identifiers?: IdentifierDetail[]; // Identifiers for this line
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemListResponse {
|
export interface ItemListResponse {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -216,7 +216,7 @@
|
|||||||
{ value: 'generales', label: 'General', visible: true },
|
{ value: 'generales', label: 'General', visible: true },
|
||||||
{ value: 'continuacion', label: 'Continuación', visible: true },
|
{ value: 'continuacion', label: 'Continuación', visible: true },
|
||||||
{ value: 'series', label: 'Series', 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 }
|
{ value: 'identificadores', label: 'IDs', visible: visibility.showIdentifiersTab }
|
||||||
].filter((tab) => tab.visible));
|
].filter((tab) => tab.visible));
|
||||||
const tabListStyle = $derived(`grid-template-columns: repeat(${visibleTabs.length || 1}, minmax(0, 1fr));`);
|
const tabListStyle = $derived(`grid-template-columns: repeat(${visibleTabs.length || 1}, minmax(0, 1fr));`);
|
||||||
@@ -544,13 +544,20 @@
|
|||||||
/>
|
/>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
|
|
||||||
<Tabs.Content value="etiquetado" class="m-0 focus-visible:outline-none">
|
{#if visibility.showLabelingTab}
|
||||||
<TabLabeling bind:descriptions={editingItem.description!} />
|
<Tabs.Content value="etiquetado" class="m-0 focus-visible:outline-none">
|
||||||
</Tabs.Content>
|
<TabLabeling bind:lineItem={editingItem} bind:descriptions={editingItem.description!} {visibility} />
|
||||||
|
</Tabs.Content>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if visibility.showIdentifiersTab}
|
{#if visibility.showIdentifiersTab}
|
||||||
<Tabs.Content value="identificadores" class="m-0 focus-visible:outline-none">
|
<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>
|
</Tabs.Content>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,376 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Input } from '$lib/components/ui/input';
|
import { Input } from '$lib/components/ui/input';
|
||||||
import { Label } from '$lib/components/ui/label';
|
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>
|
</script>
|
||||||
|
|
||||||
<fieldset class="border rounded-md p-3 space-y-3">
|
<div class="space-y-4">
|
||||||
<legend class="text-xs font-semibold px-2 uppercase">Identifiers</legend>
|
{#if visibility.showMexicanIdEnhanced}
|
||||||
|
<!-- MEX Specific Layout -->
|
||||||
<div class="space-y-2">
|
<fieldset class="border rounded-md p-3">
|
||||||
<Label for="identificador1" class="text-xs">Main Identifier:</Label>
|
<legend class="text-xs font-semibold px-2 uppercase flex items-center gap-2">
|
||||||
<Input id="identificador1" bind:value={lineItem.identifier} class="h-8 text-sm" />
|
Activos / Num. Etiquetado (MEX)
|
||||||
</div>
|
</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">
|
<div class="border rounded-md overflow-hidden bg-white dark:bg-zinc-900">
|
||||||
<Label for="notas_identificadores" class="text-xs">Additional Identifiers / Notes:</Label>
|
<Table.Root>
|
||||||
<textarea
|
<Table.Header class="bg-gray-50 dark:bg-zinc-800">
|
||||||
id="notas_identificadores"
|
<Table.Row class="h-8">
|
||||||
bind:value={lineItem.wildcard_field}
|
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Asset Number</Table.Head>
|
||||||
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"
|
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Num. Factura</Table.Head>
|
||||||
placeholder="Additional identifiers or notes..."
|
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8">Línea</Table.Head>
|
||||||
></textarea>
|
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8 text-center">Imagen</Table.Head>
|
||||||
</div>
|
<Table.Head class="text-[10px] uppercase font-bold text-gray-600 dark:text-gray-300 h-8 w-20 text-center">Acciones</Table.Head>
|
||||||
</fieldset>
|
</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}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -1,32 +1,256 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Input } from '$lib/components/ui/input';
|
import { Input } from '$lib/components/ui/input';
|
||||||
import { Label } from '$lib/components/ui/label';
|
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>
|
</script>
|
||||||
|
|
||||||
<fieldset class="border rounded-md p-3 space-y-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
<legend class="text-xs font-semibold px-2 uppercase">Labeling</legend>
|
<!-- Left Side: Labeling & Valuation -->
|
||||||
|
<fieldset class="border rounded-md p-2 space-y-2">
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<legend class="text-xs font-semibold px-2 bg-gray-200 dark:bg-gray-700 uppercase">Labeling & Valuation</legend>
|
||||||
<div class="space-y-2">
|
|
||||||
<Label for="numero_etiqueta" class="text-xs">Label Number:</Label>
|
{#if visibility.showLabelingStandard}
|
||||||
<Input id="numero_etiqueta" bind:value={descriptions.lot} class="h-8 text-sm" />
|
<div class="grid grid-cols-2 gap-2">
|
||||||
</div>
|
<div class="space-y-1">
|
||||||
<div class="space-y-2">
|
<Label for="numero_etiqueta" class="text-xs">Label Number:</Label>
|
||||||
<Label for="tipo_etiqueta" class="text-xs">Label Type:</Label>
|
<Input id="numero_etiqueta" bind:value={descriptions.lot} class="h-7 text-xs" />
|
||||||
<Input id="tipo_etiqueta" bind:value={descriptions.entry_number} class="h-8 text-sm" />
|
</div>
|
||||||
</div>
|
<div class="space-y-1">
|
||||||
</div>
|
<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">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<Label for="observaciones_etiqueta" class="text-xs">Observations:</Label>
|
{#if visibility.showLabelingEnhanced}
|
||||||
<textarea
|
{#if visibility.showLabelingQuantity}
|
||||||
id="observaciones_etiqueta"
|
<div class="space-y-1">
|
||||||
bind:value={descriptions.additional_info_spanish}
|
<Label for="cantidad_importar" class="text-xs">Cantidad a importar:</Label>
|
||||||
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"
|
<Input
|
||||||
placeholder="Labeling observations..."
|
id="cantidad_importar"
|
||||||
></textarea>
|
type="number"
|
||||||
</div>
|
bind:value={lineItem.quantity!.quantity}
|
||||||
</fieldset>
|
class="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<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}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if visibility.showLabelingEnhanced}
|
||||||
|
<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 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.showLabelingStandard}
|
||||||
|
<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 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}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -31,6 +31,18 @@ export interface InvoiceItemVisibility {
|
|||||||
showContinuationConsiderA31: boolean;
|
showContinuationConsiderA31: boolean;
|
||||||
/** Continuación tab: Extra Description in Spanish. */
|
/** Continuación tab: Extra Description in Spanish. */
|
||||||
showContinuationExtraDescription: boolean;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultVisibility: InvoiceItemVisibility = {
|
const defaultVisibility: InvoiceItemVisibility = {
|
||||||
@@ -50,7 +62,13 @@ const defaultVisibility: InvoiceItemVisibility = {
|
|||||||
showContinuationOwnOmitAnnex: true,
|
showContinuationOwnOmitAnnex: true,
|
||||||
showContinuationLotEntry: true,
|
showContinuationLotEntry: true,
|
||||||
showContinuationConsiderA31: true,
|
showContinuationConsiderA31: true,
|
||||||
showContinuationExtraDescription: true
|
showContinuationExtraDescription: true,
|
||||||
|
showLabelingEnhanced: false,
|
||||||
|
showMexicanIdEnhanced: false,
|
||||||
|
showLabelingTab: true,
|
||||||
|
showUsageReason: false,
|
||||||
|
showLabelingStandard: true,
|
||||||
|
showLabelingQuantity: false
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizeInvoiceType(invoiceType?: string | null): string {
|
function normalizeInvoiceType(invoiceType?: string | null): string {
|
||||||
@@ -126,14 +144,21 @@ export function getVisibility(
|
|||||||
return {
|
return {
|
||||||
...defaultVisibility,
|
...defaultVisibility,
|
||||||
showCrTrackingHeader: false,
|
showCrTrackingHeader: false,
|
||||||
showFdaFcc: false
|
showFdaFcc: false,
|
||||||
|
showLabelingEnhanced: true,
|
||||||
|
showLabelingQuantity: true
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'CR':
|
case 'CR':
|
||||||
return {
|
return {
|
||||||
...defaultVisibility,
|
...defaultVisibility,
|
||||||
showEighthRule: false,
|
showEighthRule: false,
|
||||||
showValuationFields: true
|
showIdentifiersTab: true,
|
||||||
|
showValuationFields: true,
|
||||||
|
showUsageReason: true,
|
||||||
|
showLabelingEnhanced: true,
|
||||||
|
showLabelingQuantity: false,
|
||||||
|
showLabelingStandard: false
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'REP':
|
case 'REP':
|
||||||
@@ -165,7 +190,9 @@ export function getVisibility(
|
|||||||
showEighthRule: false,
|
showEighthRule: false,
|
||||||
showFdaFcc: false,
|
showFdaFcc: false,
|
||||||
showCertificateOfOrigin: false,
|
showCertificateOfOrigin: false,
|
||||||
showIdentifiersTab: false,
|
showIdentifiersTab: true,
|
||||||
|
showMexicanIdEnhanced: true,
|
||||||
|
showLabelingTab: false,
|
||||||
showContinuationIgi: false,
|
showContinuationIgi: false,
|
||||||
showContinuationLocation: true,
|
showContinuationLocation: true,
|
||||||
showContinuationMilitary: false,
|
showContinuationMilitary: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user