Arreglo de equivalencias
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
"""isolate equivalency items
|
||||||
|
|
||||||
|
Revision ID: f1a2b3c4d5e6
|
||||||
|
Revises: e4f5a6b7c8d9
|
||||||
|
Create Date: 2026-05-12 17:18:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "f1a2b3c4d5e6"
|
||||||
|
down_revision: Union[str, None] = "e4f5a6b7c8d9"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# 1. Add equivalency_id column
|
||||||
|
op.add_column(
|
||||||
|
'equivalency_items',
|
||||||
|
sa.Column(
|
||||||
|
'equivalency_id',
|
||||||
|
sa.Integer(),
|
||||||
|
sa.ForeignKey('a76.equivalencies.id', ondelete='CASCADE'),
|
||||||
|
nullable=True
|
||||||
|
),
|
||||||
|
schema='a76'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Drop old unique constraint
|
||||||
|
# Note: In some DBs we might need to specify the type, but for Postgres 'unique' or 'foreignkey' works.
|
||||||
|
op.drop_constraint('uq_equivalency_item_fields', 'equivalency_items', schema='a76', type_='unique')
|
||||||
|
|
||||||
|
# 3. Create new unique constraint including equivalency_id
|
||||||
|
op.create_unique_constraint(
|
||||||
|
'uq_equivalency_item_fields',
|
||||||
|
'equivalency_items',
|
||||||
|
['equivalency_id', 'original_field', 'external_field', 'tenant_id', 'company_id'],
|
||||||
|
schema='a76'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Increase identifier length in equivalencies table
|
||||||
|
op.alter_column(
|
||||||
|
'equivalencies',
|
||||||
|
'identifier',
|
||||||
|
type_=sa.String(50),
|
||||||
|
existing_type=sa.String(10),
|
||||||
|
schema='a76'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# 1. Drop new unique constraint
|
||||||
|
op.drop_constraint('uq_equivalency_item_fields', 'equivalency_items', schema='a76', type_='unique')
|
||||||
|
|
||||||
|
# 2. Re-create old unique constraint
|
||||||
|
op.create_unique_constraint(
|
||||||
|
'uq_equivalency_item_fields',
|
||||||
|
'equivalency_items',
|
||||||
|
['original_field', 'external_field', 'tenant_id', 'company_id'],
|
||||||
|
schema='a76'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Revert identifier length in equivalencies table
|
||||||
|
op.alter_column(
|
||||||
|
'equivalencies',
|
||||||
|
'identifier',
|
||||||
|
type_=sa.String(10),
|
||||||
|
existing_type=sa.String(50),
|
||||||
|
schema='a76'
|
||||||
|
)
|
||||||
|
# 5. Drop equivalency_id column
|
||||||
|
op.drop_column('equivalency_items', 'equivalency_id', schema='a76')
|
||||||
@@ -10,6 +10,7 @@ class EquivalencyItemBase(BaseModel):
|
|||||||
original_field: str = Field(..., max_length=100, description="Campo original (from)")
|
original_field: str = Field(..., max_length=100, description="Campo original (from)")
|
||||||
external_field: str = Field(..., max_length=100, description="Campo externo (to)")
|
external_field: str = Field(..., max_length=100, description="Campo externo (to)")
|
||||||
conversion_factor: Optional[Decimal] = Field(None, description="Factor de conversión")
|
conversion_factor: Optional[Decimal] = Field(None, description="Factor de conversión")
|
||||||
|
equivalency_id: Optional[int] = Field(None, description="ID de la equivalencia padre")
|
||||||
|
|
||||||
|
|
||||||
class EquivalencyItemCreate(EquivalencyItemBase):
|
class EquivalencyItemCreate(EquivalencyItemBase):
|
||||||
@@ -20,6 +21,7 @@ class EquivalencyItemUpdate(BaseModel):
|
|||||||
original_field: Optional[str] = Field(None, max_length=100)
|
original_field: Optional[str] = Field(None, max_length=100)
|
||||||
external_field: Optional[str] = Field(None, max_length=100)
|
external_field: Optional[str] = Field(None, max_length=100)
|
||||||
conversion_factor: Optional[Decimal] = None
|
conversion_factor: Optional[Decimal] = None
|
||||||
|
equivalency_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class EquivalencyItemResponse(EquivalencyItemBase):
|
class EquivalencyItemResponse(EquivalencyItemBase):
|
||||||
@@ -34,7 +36,7 @@ class EquivalencyItemResponse(EquivalencyItemBase):
|
|||||||
|
|
||||||
|
|
||||||
class EquivalencyBase(BaseModel):
|
class EquivalencyBase(BaseModel):
|
||||||
identifier: str = Field(..., max_length=10, description="Identificador único")
|
identifier: str = Field(..., max_length=50, description="Identificador único")
|
||||||
description: Optional[str] = Field(None, max_length=200, description="Descripción (opcional)")
|
description: Optional[str] = Field(None, max_length=200, description="Descripción (opcional)")
|
||||||
item_id: Optional[int] = Field(None, description="ID del EquivalencyItem seleccionado")
|
item_id: Optional[int] = Field(None, description="ID del EquivalencyItem seleccionado")
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@ class EquivalencyCreate(EquivalencyBase):
|
|||||||
|
|
||||||
|
|
||||||
class EquivalencyUpdate(BaseModel):
|
class EquivalencyUpdate(BaseModel):
|
||||||
identifier: Optional[str] = Field(None, max_length=10)
|
identifier: Optional[str] = Field(None, max_length=50)
|
||||||
description: Optional[str] = Field(None, max_length=200)
|
description: Optional[str] = Field(None, max_length=200)
|
||||||
item_id: Optional[int] = Field(None, description="Re-asociar a otro EquivalencyItem")
|
item_id: Optional[int] = Field(None, description="Re-asociar a otro EquivalencyItem")
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class EquivalencyItem(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
__tablename__ = "equivalency_items"
|
__tablename__ = "equivalency_items"
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint(
|
UniqueConstraint(
|
||||||
"original_field", "external_field", "tenant_id", "company_id",
|
"equivalency_id", "original_field", "external_field", "tenant_id", "company_id",
|
||||||
name="uq_equivalency_item_fields"
|
name="uq_equivalency_item_fields"
|
||||||
),
|
),
|
||||||
{"schema": "a76"}
|
{"schema": "a76"}
|
||||||
@@ -27,6 +27,14 @@ class EquivalencyItem(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
conversion_factor: Mapped[Optional[Decimal]] = mapped_column(
|
conversion_factor: Mapped[Optional[Decimal]] = mapped_column(
|
||||||
Numeric(13, 6), nullable=True)
|
Numeric(13, 6), nullable=True)
|
||||||
|
|
||||||
|
equivalency_id: Mapped[Optional[int]] = mapped_column(
|
||||||
|
Integer,
|
||||||
|
ForeignKey("a76.equivalencies.id", ondelete="CASCADE"),
|
||||||
|
nullable=True)
|
||||||
|
|
||||||
|
equivalency: Mapped[Optional["Equivalency"]] = relationship(
|
||||||
|
back_populates="items", foreign_keys=[equivalency_id])
|
||||||
|
|
||||||
equivalencies: Mapped[List["Equivalency"]] = relationship(
|
equivalencies: Mapped[List["Equivalency"]] = relationship(
|
||||||
back_populates="item", foreign_keys="[Equivalency.item_id]")
|
back_populates="item", foreign_keys="[Equivalency.item_id]")
|
||||||
|
|
||||||
@@ -44,7 +52,7 @@ class Equivalency(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
id: Mapped[int] = mapped_column(
|
id: Mapped[int] = mapped_column(
|
||||||
Integer, primary_key=True, autoincrement=True)
|
Integer, primary_key=True, autoincrement=True)
|
||||||
|
|
||||||
identifier: Mapped[str] = mapped_column(String(10), nullable=False)
|
identifier: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||||
description: Mapped[Optional[str]] = mapped_column(
|
description: Mapped[Optional[str]] = mapped_column(
|
||||||
String(200), nullable=True)
|
String(200), nullable=True)
|
||||||
|
|
||||||
@@ -55,3 +63,6 @@ class Equivalency(Base, TenantScopedMixin, TimestampMixin):
|
|||||||
|
|
||||||
item: Mapped[Optional["EquivalencyItem"]] = relationship(
|
item: Mapped[Optional["EquivalencyItem"]] = relationship(
|
||||||
back_populates="equivalencies", foreign_keys=[item_id])
|
back_populates="equivalencies", foreign_keys=[item_id])
|
||||||
|
|
||||||
|
items: Mapped[List["EquivalencyItem"]] = relationship(
|
||||||
|
back_populates="equivalency", foreign_keys="[EquivalencyItem.equivalency_id]")
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ class EquivalencyItemService:
|
|||||||
query = query.filter(
|
query = query.filter(
|
||||||
EquivalencyItem.external_field.ilike(f"%{filters['external_field']}%")
|
EquivalencyItem.external_field.ilike(f"%{filters['external_field']}%")
|
||||||
)
|
)
|
||||||
|
if "equivalency_id" in filters:
|
||||||
|
query = query.filter(
|
||||||
|
EquivalencyItem.equivalency_id == filters["equivalency_id"]
|
||||||
|
)
|
||||||
|
|
||||||
total = query.count()
|
total = query.count()
|
||||||
items = query.offset(skip).limit(limit).all()
|
items = query.offset(skip).limit(limit).all()
|
||||||
@@ -63,6 +67,7 @@ class EquivalencyItemService:
|
|||||||
original_field=data.original_field,
|
original_field=data.original_field,
|
||||||
external_field=data.external_field,
|
external_field=data.external_field,
|
||||||
conversion_factor=data.conversion_factor,
|
conversion_factor=data.conversion_factor,
|
||||||
|
equivalency_id=data.equivalency_id,
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
company_id=company_id,
|
company_id=company_id,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface EquivalencyItem {
|
|||||||
original_field: string;
|
original_field: string;
|
||||||
external_field: string;
|
external_field: string;
|
||||||
conversion_factor: number | null;
|
conversion_factor: number | null;
|
||||||
|
equivalency_id: number | null;
|
||||||
tenant_id: number;
|
tenant_id: number;
|
||||||
company_id: number;
|
company_id: number;
|
||||||
}
|
}
|
||||||
@@ -18,12 +19,14 @@ export interface EquivalencyItemCreate {
|
|||||||
original_field: string;
|
original_field: string;
|
||||||
external_field: string;
|
external_field: string;
|
||||||
conversion_factor?: number | null;
|
conversion_factor?: number | null;
|
||||||
|
equivalency_id?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EquivalencyItemUpdate {
|
export interface EquivalencyItemUpdate {
|
||||||
original_field?: string;
|
original_field?: string;
|
||||||
external_field?: string;
|
external_field?: string;
|
||||||
conversion_factor?: number | null;
|
conversion_factor?: number | null;
|
||||||
|
equivalency_id?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EquivalencyItemListResponse {
|
export interface EquivalencyItemListResponse {
|
||||||
|
|||||||
@@ -16,11 +16,13 @@
|
|||||||
let {
|
let {
|
||||||
open = $bindable(false),
|
open = $bindable(false),
|
||||||
item = null,
|
item = null,
|
||||||
|
equivalency_id = null,
|
||||||
defaultOriginalField = null,
|
defaultOriginalField = null,
|
||||||
onSuccess
|
onSuccess
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
item?: EquivalencyItem | null;
|
item?: EquivalencyItem | null;
|
||||||
|
equivalency_id?: number | null;
|
||||||
defaultOriginalField?: string | null;
|
defaultOriginalField?: string | null;
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -82,7 +84,8 @@
|
|||||||
|
|
||||||
const dataToSend = {
|
const dataToSend = {
|
||||||
original_field: formData.original_field.trim(),
|
original_field: formData.original_field.trim(),
|
||||||
external_field: formData.external_field.trim()
|
external_field: formData.external_field.trim(),
|
||||||
|
equivalency_id: equivalency_id
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isEdit && item) {
|
if (isEdit && item) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import * as Table from '$lib/components/ui/table';
|
import * as Table from '$lib/components/ui/table';
|
||||||
import { companyStore } from '$lib/stores/company.svelte';
|
import { companyStore } from '$lib/stores/company.svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
import { friendlyApiErrorParts } from '$lib/api';
|
||||||
import { Check } from 'lucide-svelte';
|
import { Check } from 'lucide-svelte';
|
||||||
import type {
|
import type {
|
||||||
Equivalency,
|
Equivalency,
|
||||||
@@ -68,7 +69,17 @@
|
|||||||
loading = true;
|
loading = true;
|
||||||
error = null;
|
error = null;
|
||||||
try {
|
try {
|
||||||
const resp = await getEquivalencyItems(1, 2000, companyId);
|
const eq = getEffectiveEquivalency();
|
||||||
|
if (!eq && mode === 'create') {
|
||||||
|
items = [];
|
||||||
|
loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filters: Record<string, any> = {};
|
||||||
|
if (eq) filters.equivalency_id = eq.id;
|
||||||
|
|
||||||
|
const resp = await getEquivalencyItems(1, 2000, companyId, filters);
|
||||||
if (resp.error) throw new Error(resp.error);
|
if (resp.error) throw new Error(resp.error);
|
||||||
items = resp.data.items || [];
|
items = resp.data.items || [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -144,7 +155,8 @@
|
|||||||
companyId
|
companyId
|
||||||
);
|
);
|
||||||
if (created.error || !created.data) {
|
if (created.error || !created.data) {
|
||||||
toast.error(created.error || 'Error al guardar');
|
const { title, description } = friendlyApiErrorParts(created);
|
||||||
|
toast.error(title, { description });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
savedEquivalency = created.data;
|
savedEquivalency = created.data;
|
||||||
@@ -248,7 +260,6 @@
|
|||||||
function handleInputsSuccess() {
|
function handleInputsSuccess() {
|
||||||
inputsOpen = false;
|
inputsOpen = false;
|
||||||
reloadItems();
|
reloadItems();
|
||||||
onSuccess?.();
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -378,6 +389,7 @@
|
|||||||
<EquivalencyItemCreateEditDialog
|
<EquivalencyItemCreateEditDialog
|
||||||
bind:open={inputsOpen}
|
bind:open={inputsOpen}
|
||||||
item={inputsItem}
|
item={inputsItem}
|
||||||
|
equivalency_id={getEffectiveEquivalency()?.id ?? null}
|
||||||
defaultOriginalField={inputsItem ? null : null}
|
defaultOriginalField={inputsItem ? null : null}
|
||||||
onSuccess={handleInputsSuccess}
|
onSuccess={handleInputsSuccess}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user