Files
plantillas-proyectos/backend/api/v1/modules/a76/company/service.py
Kevin Rosales 38d86531e8 feat: Add comprehensive A76 modules with database relationships
 New Features:
- Company module: Single company management with comprehensive business info
- Client & Provider module: Manages clients/providers with address/program relationships
- GParts module: Parts/components management for SCAII, SCAF, and WINSAAI systems
- GClass module: Class classifications for SCAII and SCAF with tariff information

🔗 Database Relationships:
- GPart ↔ GClass: Composite key relationship (client_key, part_class ↔ class_code)
- GPart → Country: Foreign key to public.countries (country_of_origin)
- GPart → CurrencyType: Foreign key to public.currency_types (currency_key)
- GClass → MaterialType: Foreign key to public.material_types (material_key)

📊 API Endpoints Added:

Company Module (/company):
- POST / - Create company
- GET / - Get single company

Client & Provider Module (/clients-providers):
- POST / - Create client/provider
- GET / - List all with pagination
- GET /clients - List only clients
- GET /providers - List only providers
- GET /search/rfc/{rfc} - Search by RFC
- GET /{client_id} - Get by ID
- PUT /{client_id} - Update client/provider
- DELETE /{client_id} - Delete client/provider
- PATCH /{client_id}/toggle-status - Toggle status
- GET /{client_id}/address - Get address info
- GET /{client_id}/programs - Get programs info
- GET /{client_id}/basic - Get basic info

GParts Module (/parts):
- POST / - Create part
- GET / - List all with pagination and filters
- GET /client/{client_key} - Get parts by client
- GET /search/fraction/{fraction} - Search by tariff fraction
- GET /search/supplier/{supplier} - Search by supplier
- GET /search/country/{country_code} - Search by country
- GET /statistics - Get parts statistics
- GET /{client_key}/{part_number} - Get specific part
- PUT /{client_key}/{part_number} - Update part
- DELETE /{client_key}/{part_number} - Delete part
- PATCH /{client_key}/{part_number}/toggle-status - Toggle status
- GET /{client_key}/{part_number}/basic - Get basic info
- GET /{client_key}/{part_number}/regulatory - Get regulatory info

GClass Module (/classes):
- POST / - Create class
- GET / - List all with pagination and filters
- GET /client/{client_key} - Get classes by client
- GET /search/fraction/{fraction} - Search by tariff fraction
- GET /search/material/{material_key} - Search by material
- GET /search/unit-measure/{unit_of_measure} - Search by unit of measure
- GET /search/physical-review/{physical_review} - Search by physical review status
- GET /statistics - Get class statistics
- GET /{client_key}/{class_code} - Get specific class
- PUT /{client_key}/{class_code} - Update class
- DELETE /{client_key}/{class_code} - Delete class
- GET /{client_key}/{class_code}/basic - Get basic info
- GET /{client_key}/{class_code}/tariff - Get tariff information

🏗️ Architecture:
- Modular design with models, DTOs, services, and routes for each entity
- English field names with composite primary keys where applicable
- Comprehensive CRUD operations with specialized search endpoints
- SQLAlchemy relationships with proper foreign key constraints
- Type-safe DTOs with Pydantic validation

📝 Documentation:
- RELATIONSHIPS.md: Complete documentation of database relationships
- Detailed type hints and comprehensive service methods
- Consistent patterns across all modules for maintainability
2025-11-04 21:48:05 -06:00

185 lines
6.9 KiB
Python

"""
Capa de servicio para lógica de negocio de empresa
"""
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException
from typing import List, Optional
import logging
from .models import GCompany
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
logger = logging.getLogger(__name__)
class CompanyService:
"""Servicio para gestión de empresa"""
def __init__(self, db: Session):
self.db = db
def create_company(self, company_data: CompanyCreateDTO) -> CompanyResponseDTO:
"""
Crea una nueva empresa en el sistema
Args:
company_data: Datos de la empresa a crear
Returns:
CompanyResponseDTO con información de la empresa creada
Raises:
HTTPException: Si ya existe una empresa o error en la creación
"""
try:
# Verificar que no exista ya una empresa (solo puede haber una por el consecutivo único)
existing = self.db.query(GCompany).filter(GCompany.consecutive == True).first()
if existing:
raise HTTPException(status_code=400, detail="A company is already registered in the system")
# Crear empresa
db_company = GCompany(
id=company_data.id,
consecutive=company_data.consecutive,
name=company_data.name,
rfc=company_data.rfc,
main_activity=company_data.main_activity,
program=company_data.program,
program_number=company_data.program_number,
prosec=company_data.prosec,
prosec_authorization=company_data.prosec_authorization,
manufacturer_id=company_data.manufacturer_id,
broker_company=company_data.broker_company,
responsible=company_data.responsible,
responsible_name=company_data.responsible_name,
responsible_last_name=company_data.responsible_last_name,
responsible_mother_last_name=company_data.responsible_mother_last_name,
responsible_rfc=company_data.responsible_rfc,
position=company_data.position,
logo=company_data.logo,
has_express_line=company_data.has_express_line,
order_format_type=company_data.order_format_type,
previous_code=company_data.previous_code,
is_service_company=company_data.is_service_company,
client_name=company_data.client_name,
subassembly_mode=company_data.subassembly_mode,
curp=company_data.curp,
inter_db_name=company_data.inter_db_name,
ctpat_svi=company_data.ctpat_svi,
trusted_exporter_number=company_data.trusted_exporter_number,
prevalidator_key=company_data.prevalidator_key,
seventh_amendment=company_data.seventh_amendment
)
self.db.add(db_company)
self.db.commit()
self.db.refresh(db_company)
logger.info(f"Company created: {db_company.id} - {db_company.name}")
return CompanyResponseDTO.model_validate(db_company)
except IntegrityError as e:
self.db.rollback()
logger.error(f"IntegrityError creating company: {str(e)}")
raise HTTPException(status_code=400, detail="Integrity error: A company already exists in the system")
except HTTPException:
raise
except Exception as e:
self.db.rollback()
logger.error(f"Error creating company: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating company")
def get_company(self) -> Optional[CompanyResponseDTO]:
"""
Obtiene la empresa (solo puede haber una)
Returns:
CompanyResponseDTO o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.consecutive == True).first()
if not company:
return None
return CompanyResponseDTO.model_validate(company)
def get_company_by_id(self, company_id: str) -> Optional[CompanyResponseDTO]:
"""
Obtiene una empresa por ID
Args:
company_id: ID de la empresa
Returns:
CompanyResponseDTO o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
if not company:
return None
return CompanyResponseDTO.model_validate(company)
def update_company(self, company_id: str, company_data: CompanyUpdateDTO) -> Optional[CompanyResponseDTO]:
"""
Actualiza una empresa
Args:
company_id: ID de la empresa a actualizar
company_data: Datos a actualizar
Returns:
CompanyResponseDTO actualizada o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
if not company:
return None
# Actualizar solo campos proporcionados
update_data = company_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(company, field, value)
try:
self.db.commit()
self.db.refresh(company)
logger.info(f"Company updated: {company_id}")
return CompanyResponseDTO.model_validate(company)
except Exception as e:
self.db.rollback()
logger.error(f"Error updating company {company_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating company")
def delete_company(self, company_id: str) -> bool:
"""
Elimina una empresa
Args:
company_id: ID de la empresa a eliminar
Returns:
True si se eliminó, False si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
if not company:
return False
try:
self.db.delete(company)
self.db.commit()
logger.info(f"Company deleted: {company_id}")
return True
except Exception as e:
self.db.rollback()
logger.error(f"Error deleting company {company_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting company")
def exists_company(self) -> bool:
"""
Verifica si existe una empresa registrada
Returns:
True si existe una empresa, False en caso contrario
"""
return self.db.query(GCompany).filter(GCompany.consecutive == True).first() is not None