269 lines
8.1 KiB
Python
269 lines
8.1 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
from typing import Dict, Any, List, Optional
|
|
from pydantic import BaseModel
|
|
from datetime import datetime
|
|
import time
|
|
|
|
from adapters.sqlite.database import get_db
|
|
from adapters.sqlite.models import ExportConfiguration, ExportEntityMapping, ExportJob
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class FieldMapping(BaseModel):
|
|
field_name: str
|
|
column: str
|
|
order: int
|
|
|
|
|
|
class EntityMappingConfig(BaseModel):
|
|
sheet_name: str
|
|
is_enabled: bool = True
|
|
field_mapping: List[FieldMapping]
|
|
|
|
|
|
class ExportConfigurationRequest(BaseModel):
|
|
name: str
|
|
sheet_id: str
|
|
date_range_start: Optional[str] = None
|
|
date_range_end: Optional[str] = None
|
|
entity_mappings: Dict[str, EntityMappingConfig]
|
|
|
|
|
|
class ExportJobStart(BaseModel):
|
|
configuration_id: int
|
|
|
|
|
|
@router.post("/configure")
|
|
async def create_export_configuration(
|
|
config: ExportConfigurationRequest,
|
|
db: Session = Depends(get_db)
|
|
) -> Dict[str, Any]:
|
|
"""Create or update unified export configuration for all entities"""
|
|
|
|
# Convert date strings to timestamps
|
|
date_start = None
|
|
date_end = None
|
|
|
|
if config.date_range_start:
|
|
try:
|
|
date_start = int(datetime.fromisoformat(config.date_range_start.replace('Z', '+00:00')).timestamp())
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid date_range_start format")
|
|
|
|
if config.date_range_end:
|
|
try:
|
|
date_end = int(datetime.fromisoformat(config.date_range_end.replace('Z', '+00:00')).timestamp())
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid date_range_end format")
|
|
|
|
# Create configuration
|
|
db_config = ExportConfiguration(
|
|
name=config.name,
|
|
sheet_id=config.sheet_id,
|
|
date_range_start=date_start,
|
|
date_range_end=date_end,
|
|
is_active=True,
|
|
created_at=int(time.time()),
|
|
updated_at=int(time.time())
|
|
)
|
|
|
|
db.add(db_config)
|
|
db.flush() # Get the ID
|
|
|
|
# Create entity mappings
|
|
valid_entities = ["deals", "contacts", "companies", "pipelines", "users", "events"]
|
|
|
|
for entity_type, mapping_config in config.entity_mappings.items():
|
|
if entity_type not in valid_entities:
|
|
raise HTTPException(status_code=400, detail=f"Invalid entity type: {entity_type}")
|
|
|
|
# Convert field mapping to JSON
|
|
field_mapping_json = [
|
|
{
|
|
"field_name": fm.field_name,
|
|
"column": fm.column,
|
|
"order": fm.order
|
|
}
|
|
for fm in mapping_config.field_mapping
|
|
]
|
|
|
|
db_mapping = ExportEntityMapping(
|
|
configuration_id=db_config.id,
|
|
entity_type=entity_type,
|
|
sheet_name=mapping_config.sheet_name,
|
|
field_mapping=field_mapping_json,
|
|
is_enabled=mapping_config.is_enabled,
|
|
created_at=int(time.time()),
|
|
updated_at=int(time.time())
|
|
)
|
|
|
|
db.add(db_mapping)
|
|
|
|
db.commit()
|
|
|
|
return {
|
|
"configuration_id": db_config.id,
|
|
"message": "Export configuration created successfully"
|
|
}
|
|
|
|
|
|
@router.get("/configurations")
|
|
async def list_export_configurations(db: Session = Depends(get_db)) -> Dict[str, Any]:
|
|
"""List all export configurations"""
|
|
|
|
configs = db.query(ExportConfiguration).filter(
|
|
ExportConfiguration.is_active == True
|
|
).all()
|
|
|
|
result = []
|
|
for config in configs:
|
|
entity_mappings = {}
|
|
for mapping in config.entity_mappings:
|
|
entity_mappings[mapping.entity_type] = {
|
|
"sheet_name": mapping.sheet_name,
|
|
"is_enabled": mapping.is_enabled,
|
|
"field_mapping": mapping.field_mapping
|
|
}
|
|
|
|
result.append({
|
|
"id": config.id,
|
|
"name": config.name,
|
|
"sheet_id": config.sheet_id,
|
|
"date_range_start": datetime.fromtimestamp(config.date_range_start).isoformat() + "Z" if config.date_range_start else None,
|
|
"date_range_end": datetime.fromtimestamp(config.date_range_end).isoformat() + "Z" if config.date_range_end else None,
|
|
"entity_mappings": entity_mappings,
|
|
"created_at": datetime.fromtimestamp(config.created_at).isoformat() + "Z" if config.created_at else None
|
|
})
|
|
|
|
return {"configurations": result}
|
|
|
|
|
|
@router.post("/start")
|
|
async def start_export_job(
|
|
job_request: ExportJobStart,
|
|
db: Session = Depends(get_db)
|
|
) -> Dict[str, Any]:
|
|
"""Start async export job"""
|
|
|
|
# Check if configuration exists
|
|
config = db.query(ExportConfiguration).filter(
|
|
ExportConfiguration.id == job_request.configuration_id,
|
|
ExportConfiguration.is_active == True
|
|
).first()
|
|
|
|
if not config:
|
|
raise HTTPException(status_code=404, detail="Export configuration not found")
|
|
|
|
# Create export job
|
|
job = ExportJob(
|
|
configuration_id=config.id,
|
|
status="pending",
|
|
records_processed=0,
|
|
total_records=0,
|
|
created_at=int(time.time())
|
|
)
|
|
|
|
db.add(job)
|
|
db.commit()
|
|
|
|
# TODO: Queue job for background processing with Celery
|
|
# celery_app.send_task("export_to_sheets", args=[job.id])
|
|
|
|
return {
|
|
"job_id": job.id,
|
|
"status": "pending",
|
|
"message": "Export job queued successfully"
|
|
}
|
|
|
|
|
|
@router.get("/status/{job_id}")
|
|
async def get_export_status(job_id: int, db: Session = Depends(get_db)) -> Dict[str, Any]:
|
|
"""Get export job status"""
|
|
|
|
job = db.query(ExportJob).filter(ExportJob.id == job_id).first()
|
|
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="Export job not found")
|
|
|
|
result = {
|
|
"job_id": job.id,
|
|
"status": job.status,
|
|
"progress": {
|
|
"records_processed": job.records_processed,
|
|
"total_records": job.total_records,
|
|
"percentage": round((job.records_processed / job.total_records * 100), 2) if job.total_records > 0 else 0
|
|
}
|
|
}
|
|
|
|
if job.started_at:
|
|
result["started_at"] = datetime.fromtimestamp(job.started_at).isoformat() + "Z"
|
|
|
|
if job.completed_at:
|
|
result["completed_at"] = datetime.fromtimestamp(job.completed_at).isoformat() + "Z"
|
|
|
|
if job.error_message:
|
|
result["error_message"] = job.error_message
|
|
|
|
# TODO: Add detailed entity progress when implementing worker
|
|
if job.status == "running":
|
|
result["progress"]["entities"] = {
|
|
"deals": {"processed": 1500, "total": 1500, "status": "completed"},
|
|
"contacts": {"processed": 600, "total": 2200, "status": "running"},
|
|
"companies": {"processed": 0, "total": 300, "status": "pending"},
|
|
"events": {"processed": 0, "total": 200, "status": "pending"}
|
|
}
|
|
result["estimated_completion"] = "2024-01-15T10:35:00Z" # TODO: Calculate based on progress
|
|
|
|
return result
|
|
|
|
|
|
@router.get("/jobs")
|
|
async def list_export_jobs(
|
|
page: int = 1,
|
|
per_page: int = 20,
|
|
db: Session = Depends(get_db)
|
|
) -> Dict[str, Any]:
|
|
"""List all export jobs"""
|
|
|
|
offset = (page - 1) * per_page
|
|
|
|
jobs_query = db.query(ExportJob).order_by(ExportJob.created_at.desc())
|
|
total = jobs_query.count()
|
|
jobs = jobs_query.offset(offset).limit(per_page).all()
|
|
|
|
result_jobs = []
|
|
for job in jobs:
|
|
job_data = {
|
|
"job_id": job.id,
|
|
"configuration_name": job.configuration.name,
|
|
"status": job.status,
|
|
"records_processed": job.records_processed,
|
|
"total_records": job.total_records
|
|
}
|
|
|
|
if job.started_at:
|
|
job_data["started_at"] = datetime.fromtimestamp(job.started_at).isoformat() + "Z"
|
|
|
|
if job.completed_at:
|
|
job_data["completed_at"] = datetime.fromtimestamp(job.completed_at).isoformat() + "Z"
|
|
|
|
# TODO: Add entities_processed breakdown when implementing worker
|
|
if job.status == "completed":
|
|
job_data["entities_processed"] = {
|
|
"deals": 1500,
|
|
"contacts": 2200,
|
|
"companies": 300,
|
|
"events": 200
|
|
}
|
|
|
|
result_jobs.append(job_data)
|
|
|
|
return {
|
|
"jobs": result_jobs,
|
|
"total": total,
|
|
"page": page,
|
|
"per_page": per_page
|
|
}
|