feat: Implement Fixed Asset (FA) line item management
- Added new schemas for FA line items in the backend, including creation, update, and response DTOs. - Updated existing line item schemas to include FA data. - Modified database models to reflect new table names for line quantities and references. - Enhanced ItemService to handle FA data during item creation and updates. - Introduced new routes and service layer for FA line items, including CRUD operations. - Updated frontend components to support FA line item data, including new fields and UI adjustments. - Implemented data flattening for improved item display in the dashboard.
This commit is contained in:
@@ -76,15 +76,52 @@ export interface LineReferences {
|
||||
serie_id?: number;
|
||||
}
|
||||
|
||||
export interface FaLineItem {
|
||||
id?: number;
|
||||
tenant_id?: number;
|
||||
company_id?: number;
|
||||
|
||||
// Asset information (SCAF specific)
|
||||
asset_number?: string;
|
||||
asset_photo?: string;
|
||||
equipment_message?: string;
|
||||
invoice_type_asset?: string;
|
||||
return_import_invoice?: string;
|
||||
return_import_date?: number;
|
||||
movement_type_import?: string;
|
||||
|
||||
// Cross-references for import repair
|
||||
search_invoice?: string;
|
||||
search_line?: number;
|
||||
|
||||
// Search type
|
||||
search_type?: string;
|
||||
|
||||
// Subitems
|
||||
is_subitem?: boolean;
|
||||
contains_subitems?: boolean;
|
||||
includes_subitems?: boolean;
|
||||
subitem_number?: number;
|
||||
|
||||
// Special flags
|
||||
download?: boolean;
|
||||
own_equipment?: boolean;
|
||||
omit_annex31?: boolean;
|
||||
|
||||
// Timestamps
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface LineItem {
|
||||
id?: number;
|
||||
item_id?: number;
|
||||
line_number: number;
|
||||
|
||||
// Identification
|
||||
part_number?: string;
|
||||
component_part_number?: string;
|
||||
class_code?: string;
|
||||
part_number_id?: string;
|
||||
component_part_number_id?: string;
|
||||
class_id?: number;
|
||||
identifier?: string;
|
||||
|
||||
// Unit of Measure
|
||||
@@ -106,6 +143,12 @@ export interface LineItem {
|
||||
payment_method?: string;
|
||||
igi_amount?: number;
|
||||
|
||||
// Computed fields from class_info relation
|
||||
class_code?: string;
|
||||
class_description?: string;
|
||||
|
||||
// Computed field from unit_of_measure_info relation
|
||||
unit_of_measure_code?: string;
|
||||
|
||||
// Nested relations (Singular names to match backend Pydantic models)
|
||||
customs?: LineCustoms;
|
||||
@@ -113,6 +156,7 @@ export interface LineItem {
|
||||
quantity?: LineQuantities;
|
||||
description?: LineDescriptions;
|
||||
reference?: LineReferences;
|
||||
fa_data?: FaLineItem; // Fixed Asset specific data
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
|
||||
@@ -12,20 +12,27 @@
|
||||
descriptions: LineDescriptions;
|
||||
} = $props();
|
||||
|
||||
// Initialize fa_data for fixed asset system
|
||||
if (!lineItem.fa_data) {
|
||||
lineItem.fa_data = {};
|
||||
}
|
||||
|
||||
// Helper to map boolean to string for RadioGroup
|
||||
let isSubPartidaValue = $derived(lineItem.is_subitem ? 'subpartida' : 'partida');
|
||||
let isSubPartidaValue = $derived(lineItem.fa_data?.is_subitem ? 'subpartida' : 'partida');
|
||||
function setIsSubPartida(val: string) {
|
||||
lineItem.is_subitem = val === 'subpartida';
|
||||
if (!lineItem.fa_data) lineItem.fa_data = {};
|
||||
lineItem.fa_data.is_subitem = val === 'subpartida';
|
||||
}
|
||||
|
||||
let continueSubPartidasValue = $derived(lineItem.includes_subitems ? 'si' : 'no');
|
||||
let containsSubPartidasValue = $derived(lineItem.fa_data?.contains_subitems ? 'si' : 'no');
|
||||
function setContinueSubPartidas(val: string) {
|
||||
lineItem.includes_subitems = val === 'si';
|
||||
if (!lineItem.fa_data) lineItem.fa_data = {};
|
||||
lineItem.fa_data.contains_subitems = val === 'si';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="lg:col-span-5 space-y-3">
|
||||
<!-- Is Item/Subitem and Continue Sub-Items -->
|
||||
<!-- Is Item/Subitem and Contains Sub-Items -->
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<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">Is</legend>
|
||||
@@ -45,9 +52,9 @@
|
||||
</fieldset>
|
||||
|
||||
<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">Continue Sub-Items</legend>
|
||||
<legend class="text-xs font-semibold px-2 bg-gray-200 dark:bg-gray-700">Contains Sub-Items</legend>
|
||||
<RadioGroup.Root
|
||||
value={continueSubPartidasValue}
|
||||
value={containsSubPartidasValue}
|
||||
onValueChange={setContinueSubPartidas}
|
||||
class="flex gap-3">
|
||||
<div class="flex items-center space-x-2">
|
||||
@@ -67,7 +74,7 @@
|
||||
<div class="space-y-1">
|
||||
<Label for="num_parte" class="text-xs">Part Number:</Label>
|
||||
<div class="flex gap-1">
|
||||
<Input id="num_parte" bind:value={lineItem.part_number} class="h-7 text-xs" />
|
||||
<Input id="num_parte" bind:value={lineItem.part_number_id} class="h-7 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="col-span-4 space-y-1">
|
||||
<Label for="clase" class="text-xs font-medium">* Class:</Label>
|
||||
<div class="flex gap-1">
|
||||
<Input id="clase" bind:value={lineItem.class_code} class="h-8 text-xs" />
|
||||
<Input id="clase" bind:value={lineItem.class_id} class="h-8 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,9 +25,24 @@
|
||||
let gross_weight = 0;
|
||||
|
||||
let items = $state<Item[]>([]);
|
||||
let displayedItems = $state<Item[]>([]);
|
||||
let displayedItems = $state<any[]>([]);
|
||||
let itemsPerPage = 20;
|
||||
let currentPage = $state(1);
|
||||
|
||||
// Aplanar items en líneas para la tabla
|
||||
const flattenedLines = $derived(
|
||||
items.flatMap(item =>
|
||||
(item.lines || []).map(line => ({
|
||||
...line,
|
||||
item_id: item.id,
|
||||
reference_number: item.reference_number,
|
||||
order: item.order,
|
||||
warehouse: item.warehouse,
|
||||
location: item.location,
|
||||
full_item: item
|
||||
}))
|
||||
)
|
||||
);
|
||||
let tableContainer: HTMLDivElement | undefined = $state();
|
||||
let isLoadingMore = $state(false);
|
||||
let isLoadingItems = $state(false);
|
||||
@@ -84,7 +99,7 @@
|
||||
function loadMoreItems() {
|
||||
const start = 0;
|
||||
const end = currentPage * itemsPerPage;
|
||||
displayedItems = items.slice(start, end);
|
||||
displayedItems = flattenedLines.slice(start, end);
|
||||
isLoadingMore = false;
|
||||
}
|
||||
|
||||
@@ -93,7 +108,7 @@
|
||||
const threshold = 100;
|
||||
const scrolledToBottom = target.scrollHeight - target.scrollTop - target.clientHeight < threshold;
|
||||
|
||||
if (scrolledToBottom && !isLoadingMore && displayedItems.length < items.length) {
|
||||
if (scrolledToBottom && !isLoadingMore && displayedItems.length < flattenedLines.length) {
|
||||
isLoadingMore = true;
|
||||
currentPage++;
|
||||
loadMoreItems();
|
||||
@@ -122,18 +137,16 @@
|
||||
lines: [{
|
||||
line_number: 1,
|
||||
// LineItem fields
|
||||
part_number: undefined,
|
||||
component_part_number: undefined,
|
||||
class_code: undefined,
|
||||
part_number_id: undefined,
|
||||
component_part_number_id: undefined,
|
||||
class_id: undefined,
|
||||
identifier: undefined,
|
||||
unit_of_measure: undefined,
|
||||
alternate_unit: undefined,
|
||||
permit_number: undefined,
|
||||
page_line: undefined,
|
||||
has_certificate: false,
|
||||
certificate_number: undefined,
|
||||
is_subitem: false,
|
||||
includes_subitems: false,
|
||||
certificate_number: undefined,
|
||||
tax_payment: false,
|
||||
payment_method: undefined,
|
||||
igi_amount: undefined,
|
||||
@@ -186,11 +199,11 @@
|
||||
};
|
||||
}
|
||||
|
||||
function handleEdit(item: Item) {
|
||||
function handleEdit(lineData: any) {
|
||||
isEditMode = true;
|
||||
selectedItem = item;
|
||||
selectedItem = lineData.full_item;
|
||||
// Deep clone and normalize numeric values
|
||||
editingItem = normalizeItemData({ ...item });
|
||||
editingItem = normalizeItemData({ ...lineData.full_item });
|
||||
showItemSheet = true;
|
||||
}
|
||||
|
||||
@@ -245,8 +258,8 @@
|
||||
return item;
|
||||
}
|
||||
|
||||
function handleDelete(item: Item) {
|
||||
selectedItem = item;
|
||||
function handleDelete(lineData: any) {
|
||||
selectedItem = lineData.full_item;
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
|
||||
@@ -364,11 +377,16 @@
|
||||
>
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 bg-background z-10">
|
||||
<Table.Row>
|
||||
<Table.Head>Referencia</Table.Head>
|
||||
<Table.Head>Orden</Table.Head>
|
||||
<Table.Head>Almacén</Table.Head>
|
||||
<Table.Head>Ubicación</Table.Head>
|
||||
<Table.Row>
|
||||
<Table.Head>Línea</Table.Head>
|
||||
<Table.Head>P/S</Table.Head>
|
||||
<Table.Head>Clase</Table.Head>
|
||||
<Table.Head>Descripcion Clase</Table.Head>
|
||||
<Table.Head>Cant. Importada</Table.Head>
|
||||
<Table.Head>U.M.</Table.Head>
|
||||
<Table.Head>Preferencia</Table.Head>
|
||||
<Table.Head>Contiene Subpartida</Table.Head>
|
||||
<Table.Head>Partida Principal</Table.Head>
|
||||
<Table.Head class="text-right w-[120px]">Acciones</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
@@ -382,10 +400,15 @@
|
||||
{:else}
|
||||
{#each displayedItems as item (item.id)}
|
||||
<Table.Row>
|
||||
<Table.Cell>{item.line_number}</Table.Cell>
|
||||
<Table.Cell>{item.is_subitem ? 'S' : 'P'}</Table.Cell>
|
||||
<Table.Cell>{item.class_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.class_description || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.quantity?.quantity || '0'}</Table.Cell>
|
||||
<Table.Cell>{item.unit_of_measure_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.reference_number || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.order || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.warehouse || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.location || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.contains_subitems || '-'}</Table.Cell>
|
||||
<Table.Cell>{item.warehouse || '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button size="icon" variant="ghost" onclick={() => handleEdit(item)}>
|
||||
@@ -410,9 +433,9 @@
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
{#if items.length > 0}
|
||||
{#if flattenedLines.length > 0}
|
||||
<div class="text-xs text-muted-foreground text-right">
|
||||
Mostrando {displayedItems.length} de {items.length} items
|
||||
Mostrando {displayedItems.length} de {flattenedLines.length} líneas
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user