feat: add Fixed Asset Classes management page with CRUD functionality

This commit is contained in:
AlexeerCT
2026-01-08 21:14:08 -06:00
parent 08416d6cad
commit 8dca19413b
8 changed files with 2078 additions and 183 deletions

View File

@@ -46,8 +46,6 @@ class ClassService:
)
if filters:
if filters.get("client_id"):
query = query.filter(Class.client_id == filters["client_id"])
if filters.get("class_code"):
query = query.filter(
Class.class_code.ilike(f"%{filters['class_code']}%")
@@ -106,7 +104,6 @@ class ClassService:
existing = db.query(Class).filter(
Class.tenant_id == tenant_id,
Class.company_id == company_id,
Class.client_id == data_dict["client_id"],
Class.class_code == data_dict["class_code"]
).first()
@@ -177,16 +174,15 @@ class ClassService:
if "class_code" in update_data and update_data["class_code"]:
new_code = update_data["class_code"]
# Check if another class with this code exists (excluding current class)
# The unique constraint is on (tenant_id, company_id, client_id, class_code)
# The unique constraint is on (tenant_id, company_id, class_code)
existing_class = db.query(Class).filter(
Class.class_code == new_code,
Class.tenant_id == tenant_id,
Class.company_id == company_id,
Class.client_id == class_obj.client_id, # Same client
Class.id != class_id # Exclude current class
).first()
logger.info(f"Checking for duplicate class_code '{new_code}' for client {class_obj.client_id}")
logger.info(f"Checking for duplicate class_code '{new_code}'")
if existing_class:
logger.warning(f"Duplicate class_code found: {existing_class.id}")
raise HTTPException(
@@ -265,7 +261,7 @@ class ClassService:
# Extract base class fields
base_fields = {
"client_id", "class_code", "description_es", "description_en",
"class_code", "description_es", "description_en",
"material_key", "unit_of_measure", "fraction", "us_fraction",
"sub_key", "physical_review", "iva_exempt_fraction"
}
@@ -300,7 +296,6 @@ class ClassService:
"id": base_class.id,
"tenant_id": base_class.tenant_id,
"company_id": base_class.company_id,
"client_id": base_class.client_id,
"class_code": base_class.class_code,
"description_es": base_class.description_es,
"description_en": base_class.description_en,
@@ -352,62 +347,6 @@ class ClassService:
detail=f"Error al crear clase de activo fijo: {error_msg}"
)
@staticmethod
def seed_initial_data(
db: Session, tenant_id: int, company_id: int, client_id: int
) -> int:
"""
Seed initial fixed asset class data
Returns: number of records created
"""
from .seed import seed
created_count = 0
for record in seed:
(
class_code,
description_es,
description_en,
material_key,
unit_of_measure,
fraction,
us_fraction,
bom,
) = record
# Check if already exists
existing = (
db.query(Class)
.filter(
Class.tenant_id == tenant_id,
Class.company_id == company_id,
Class.client_id == client_id,
Class.class_code == class_code,
)
.first()
)
if not existing:
class_obj = Class(
tenant_id=tenant_id,
company_id=company_id,
client_id=client_id,
class_code=class_code,
description_es=description_es,
description_en=description_en,
material_key=material_key if material_key else None,
unit_of_measure=unit_of_measure if unit_of_measure else None,
fraction=fraction if fraction else None,
us_fraction=us_fraction if us_fraction else None,
)
db.add(class_obj)
created_count += 1
if created_count > 0:
db.commit()
return created_count
def __init__(self, db: Session):
self.db = db
@@ -430,7 +369,6 @@ class ClassService:
self.db.query(Class)
.filter(
and_(
Class.client_id == class_data.client_id,
Class.class_code == class_data.class_code,
)
)
@@ -440,12 +378,11 @@ class ClassService:
if existing:
raise HTTPException(
status_code=400,
detail=f"Class with client_id '{class_data.client_id}' and class_code '{class_data.class_code}' already exists",
detail=f"Class with class_code '{class_data.class_code}' already exists",
)
# Crear clase
db_class = Class(
client_id=class_data.client_id,
class_code=class_data.class_code,
description_spanish=class_data.description_spanish,
description_english=class_data.description_english,
@@ -469,7 +406,7 @@ class ClassService:
logger.error(f"IntegrityError creating class: {str(e)}")
raise HTTPException(
status_code=400,
detail="Class with this client_id and class_code already exists",
detail="Class with this class_code already exists",
)
except HTTPException:
raise
@@ -478,12 +415,11 @@ class ClassService:
logger.error(f"Error creating class: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating class")
def get_class(self, client_id: int, class_code: str) -> Optional[ClassResponseDTO]:
def get_class(self, class_code: str) -> Optional[ClassResponseDTO]:
"""
Obtiene una clase por clave compuesta
Args:
client_id: Clave del cliente
class_code: Código de clase
Returns:
@@ -491,7 +427,7 @@ class ClassService:
"""
class_obj = (
self.db.query(Class)
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
.filter(and_(Class.class_code == class_code))
.first()
)
@@ -520,9 +456,6 @@ class ClassService:
# Aplicar filtros si se proporcionan
if search_params:
if search_params.client_id:
query = query.filter(Class.client_id == search_params.client_id)
if search_params.class_code:
query = query.filter(
Class.class_code.ilike(f"%{search_params.class_code}%")
@@ -569,13 +502,12 @@ class ClassService:
)
def update_class(
self, client_id: int, class_code: str, class_data: ClassUpdateDTO
self, class_code: str, class_data: ClassUpdateDTO
) -> Optional[ClassResponseDTO]:
"""
Actualiza una clase
Args:
client_id: Clave del cliente
class_code: Código de clase
class_data: Datos a actualizar
@@ -584,7 +516,7 @@ class ClassService:
"""
class_obj = (
self.db.query(Class)
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
.filter(and_(Class.class_code == class_code))
.first()
)
@@ -604,15 +536,14 @@ class ClassService:
except Exception as e:
self.db.rollback()
logger.error(f"Error updating class {client_id}-{class_code}: {str(e)}")
logger.error(f"Error updating class {class_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating class")
def delete_class(self, client_id: int, class_code: str) -> bool:
def delete_class(self, class_code: str) -> bool:
"""
Elimina una clase
Args:
client_id: Clave del cliente
class_code: Código de clase
Returns:
@@ -620,7 +551,7 @@ class ClassService:
"""
class_obj = (
self.db.query(Class)
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
.filter(and_(Class.class_code == class_code))
.first()
)
@@ -633,7 +564,7 @@ class ClassService:
return True
except Exception as e:
self.db.rollback()
logger.error(f"Error deleting class {client_id}-{class_code}: {str(e)}")
logger.error(f"Error deleting class {class_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting class")
def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]:
@@ -643,19 +574,6 @@ class ClassService:
)
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def search_by_client(
self, client_id: int, skip: int = 0, limit: int = 100
) -> List[ClassBasicDTO]:
"""Obtiene todas las clases de un cliente específico"""
classes = (
self.db.query(Class)
.filter(Class.client_id == client_id)
.offset(skip)
.limit(limit)
.all()
)
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def search_by_material(self, material_key: str) -> List[ClassBasicDTO]:
"""Busca clases por clave de material"""
classes = (
@@ -678,9 +596,6 @@ class ClassService:
"""Obtiene estadísticas básicas de clases"""
total_classes = self.db.query(Class).count()
# Contar por clientes
clients_count = self.db.query(Class.client_id).distinct().count()
# Contar por revisión física
physical_review_stats = {}
for i in range(3): # Asumiendo valores 0, 1, 2
@@ -695,7 +610,6 @@ class ClassService:
return {
"total_classes": total_classes,
"clients_with_classes": clients_count,
"classes_with_fraction": with_fraction,
"classes_with_us_fraction": with_us_fraction,
**physical_review_stats,