Refactor Part model and related components
- Updated the Part model in models.py to improve readability and maintainability by organizing imports and formatting. - Enhanced relationships in the Part model for better clarity. - Modified main.py to streamline imports for parts and related models. - Adjusted partForm.svelte to correctly reference properties from inv_data and fa_data. - Updated +page.svelte to fix the import path for DataTable and refine data handling. - Changed edit/[[id]]/+page.svelte to enforce type safety for the 'type' variable.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Modelos ORM para gestión de partes/componentes - Anexo 76 (Master Data)
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
@@ -8,19 +9,29 @@ from typing import TYPE_CHECKING, Optional
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
from sqlalchemy import (
|
||||
ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint,
|
||||
String, UniqueConstraint, Boolean, DateTime
|
||||
ForeignKeyConstraint,
|
||||
Integer,
|
||||
Numeric,
|
||||
PrimaryKeyConstraint,
|
||||
String,
|
||||
UniqueConstraint,
|
||||
Boolean,
|
||||
DateTime,
|
||||
)
|
||||
|
||||
# Importante usar relationship y Mapped
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
from api.v1.modules.public.reference_data.currency_types.models import CurrencyType
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import (
|
||||
UnitOfMeasure,
|
||||
)
|
||||
from api.v1.modules.a24.fa.fa_parts.models import FaPart
|
||||
from api.v1.modules.a24.inv.inv_parts.models import InvPart
|
||||
|
||||
|
||||
class Part(Base, TenantScopedMixin, TimestampMixin):
|
||||
__tablename__ = "parts"
|
||||
__table_args__ = (
|
||||
@@ -30,14 +41,21 @@ class Part(Base, TenantScopedMixin, TimestampMixin):
|
||||
),
|
||||
ForeignKeyConstraint(
|
||||
["unit_of_measure", "tenant_id", "company_id"],
|
||||
["a76.units_of_measure.code", "a76.units_of_measure.tenant_id",
|
||||
"a76.units_of_measure.company_id"],
|
||||
[
|
||||
"a76.units_of_measure.code",
|
||||
"a76.units_of_measure.tenant_id",
|
||||
"a76.units_of_measure.company_id",
|
||||
],
|
||||
),
|
||||
# Puente hacia la tabla de clases
|
||||
ForeignKeyConstraint(
|
||||
["part_class", "tenant_id", "company_id"],
|
||||
["a76.classes.class_code", "a76.classes.tenant_id", "a76.classes.company_id"],
|
||||
name="fk_parts_class"
|
||||
[
|
||||
"a76.classes.class_code",
|
||||
"a76.classes.tenant_id",
|
||||
"a76.classes.company_id",
|
||||
],
|
||||
name="fk_parts_class",
|
||||
),
|
||||
UniqueConstraint(
|
||||
"tenant_id", "company_id", "part_number", name="client_part_ukey"
|
||||
@@ -54,7 +72,7 @@ class Part(Base, TenantScopedMixin, TimestampMixin):
|
||||
description_english: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
part_class: Mapped[Optional[str]] = mapped_column(String(8))
|
||||
unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5))
|
||||
|
||||
|
||||
unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8))
|
||||
currency_type: Mapped[Optional[str]] = mapped_column(String(2))
|
||||
currency_key: Mapped[Optional[str]] = mapped_column(String(3))
|
||||
@@ -73,31 +91,33 @@ class Part(Base, TenantScopedMixin, TimestampMixin):
|
||||
|
||||
is_active: Mapped[Optional[bool]] = mapped_column(Boolean, default=True)
|
||||
part_photo: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
|
||||
|
||||
creation_date: Mapped[Optional[int]] = mapped_column()
|
||||
modification_date: Mapped[Optional[int]] = mapped_column()
|
||||
modification_date_iso: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
|
||||
# --- RELACIONES CORREGIDAS ---
|
||||
currency: Mapped[Optional["CurrencyType"]] = relationship(
|
||||
foreign_keys="[Part.currency_key]"
|
||||
)
|
||||
# --- RELACIONES ---
|
||||
currency: Mapped[Optional["CurrencyType"]] = relationship("CurrencyType")
|
||||
unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship(
|
||||
foreign_keys="[Part.unit_of_measure, Part.tenant_id, Part.company_id]"
|
||||
"UnitOfMeasure"
|
||||
)
|
||||
part_class_info: Mapped[Optional["Class"]] = relationship(
|
||||
"Class",
|
||||
back_populates="parts",
|
||||
foreign_keys="[Part.part_class, Part.tenant_id, Part.company_id]"
|
||||
"Class", back_populates="parts", overlaps="unit_of_measure_info"
|
||||
)
|
||||
|
||||
# Extensiones Anexo 24
|
||||
fa_data: Mapped[Optional["FaPart"]] = relationship(
|
||||
"FaPart", back_populates="master_info", uselist=False, cascade="all, delete-orphan"
|
||||
"FaPart",
|
||||
back_populates="master_info",
|
||||
uselist=False,
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
inv_data: Mapped[Optional["InvPart"]] = relationship(
|
||||
"InvPart", back_populates="master_info", uselist=False, cascade="all, delete-orphan"
|
||||
"InvPart",
|
||||
back_populates="master_info",
|
||||
uselist=False,
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Part(id={self.id}, part_number='{self.part_number}')>"
|
||||
return f"<Part(id={self.id}, part_number='{self.part_number}')>"
|
||||
|
||||
@@ -19,12 +19,12 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from api.v1.modules.a76.items.models import (
|
||||
Item,
|
||||
) # Importar rutas para registrar con el router
|
||||
from api.v1.modules.a76.items.series.models import (
|
||||
Serie,
|
||||
) # Importar modelos para registrar con SQLAlchemy
|
||||
# Importar modelos para registrar con SQLAlchemy
|
||||
from api.v1.modules.a76.items.models import Item
|
||||
from api.v1.modules.a76.items.series.models import Serie
|
||||
from api.v1.modules.a76.parts.models import Part
|
||||
from api.v1.modules.a24.fa.fa_parts.models import FaPart
|
||||
from api.v1.modules.a24.inv.inv_parts.models import InvPart
|
||||
|
||||
# Configurar logging
|
||||
logging.basicConfig(
|
||||
@@ -51,7 +51,9 @@ register_exception_handlers(app)
|
||||
# Add validation error handler
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
logger.error(f"Validation error for {request.method} {request.url.path}: {exc.errors()}")
|
||||
logger.error(
|
||||
f"Validation error for {request.method} {request.url.path}: {exc.errors()}"
|
||||
)
|
||||
logger.error(f"Request body: {await request.body()}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -62,7 +64,9 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
|
||||
# Add HTTP exception handler
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_exception_handler(request: Request, exc: HTTPException):
|
||||
logger.error(f"HTTP {exc.status_code} for {request.method} {request.url.path}: {exc.detail}")
|
||||
logger.error(
|
||||
f"HTTP {exc.status_code} for {request.method} {request.url.path}: {exc.detail}"
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={"detail": exc.detail},
|
||||
|
||||
@@ -107,14 +107,14 @@
|
||||
description_spanish: d.description_spanish || '',
|
||||
description_english: d.description_english || '',
|
||||
part_class: d.part_class || '',
|
||||
material_type: (d as any).material_type || '',
|
||||
origin_country: d.origin_country || 'MEX',
|
||||
material_type: d.inv_data?.material_type || '',
|
||||
origin_country: d.fa_data?.origin_country || 'MEX',
|
||||
unit_of_measure: d.unit_of_measure || 'PZ',
|
||||
fraction: d.fraction || '',
|
||||
us_fraction: d.us_fraction || '',
|
||||
unit_weight: Number(d.unit_weight) || 0,
|
||||
weight_type: d.weight_type || 'KG',
|
||||
supplier: d.supplier || '',
|
||||
supplier: d.inv_data?.supplier_code || '',
|
||||
fda_key: d.fda_key || '',
|
||||
fcc_key: d.fcc_key || '',
|
||||
eccn: d.eccn || '',
|
||||
@@ -123,10 +123,10 @@
|
||||
exclusion_symbol: d.exclusion_symbol || '',
|
||||
unit_cost: Number(d.unit_cost) || 0,
|
||||
currency_key: d.currency_key || 'USD',
|
||||
added_value: Number(d.added_value) || 0,
|
||||
added_value: Number(d.inv_data?.added_value) || 0,
|
||||
value_added_type: 'USD',
|
||||
commercial_part_number: d.commercial_part_number || '',
|
||||
alternate_unit_measure: d.alternate_unit_measure || '',
|
||||
alternate_unit_measure: d.inv_data?.alternate_uom || '',
|
||||
part_photo: d.part_photo || '',
|
||||
is_active: d.is_active ?? true,
|
||||
// Cargar datos FA si existen
|
||||
@@ -135,7 +135,7 @@
|
||||
};
|
||||
if (d.client_id) await fetchClientName(d.client_id, companyId);
|
||||
if (d.part_class) await fetchClassDesc(d.part_class, companyId);
|
||||
if ((d as any).material_type) await fetchMaterialName((d as any).material_type);
|
||||
if (d.inv_data?.material_type) await fetchMaterialName(d.inv_data.material_type);
|
||||
}
|
||||
} catch (e) { console.error(e); } finally { loading = false; }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { partsApi, type Part } from '$lib/api/dashboard/a76/parts';
|
||||
import DataTable from '$lib/components/dashboard/goods/classes/data-table.svelte';
|
||||
import DataTable from '$lib/components/dashboard/goods/parts/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/goods/parts/columns.js';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -38,7 +38,7 @@
|
||||
});
|
||||
|
||||
if (response.data) {
|
||||
partsList = response.data.items || response.data.parts || [];
|
||||
partsList = response.data.items || [];
|
||||
} else if (response.error) {
|
||||
listError = response.error;
|
||||
}
|
||||
@@ -72,7 +72,7 @@
|
||||
q: searchCode.trim()
|
||||
});
|
||||
|
||||
const results = response.data.items || response.data.parts || [];
|
||||
const results = response.data?.items || [];
|
||||
|
||||
if (results.length > 0) {
|
||||
searchResults = results; // Guardamos TODOS los resultados
|
||||
@@ -172,7 +172,13 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<DataTable data={tableData} {columns} />
|
||||
<DataTable
|
||||
data={tableData}
|
||||
{columns}
|
||||
loading={listLoading || searchLoading}
|
||||
hasMore={false}
|
||||
loadMore={() => {}}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
@@ -3,7 +3,7 @@
|
||||
import PartForm from '$lib/components/dashboard/goods/parts/partForm.svelte';
|
||||
|
||||
let id = $derived($page.params.id === 'new' ? null : Number($page.params.id));
|
||||
let type = 'fa';
|
||||
let type: 'inv' | 'fa' = 'fa';
|
||||
</script>
|
||||
|
||||
<PartForm partId={id} formType={type} />
|
||||
Reference in New Issue
Block a user