Files
boc/rust-service/src/handlers.rs
T

340 lines
11 KiB
Rust
Raw Normal View History

use axum::{extract::State, Json};
use chrono::Utc;
use std::collections::HashMap;
use tracing::{info, warn};
use uuid::Uuid;
use crate::{AppState, models::*};
pub async fn generate_report(
State(state): State<AppState>,
Json(req): Json<ReportRequest>,
) -> Json<ReportResult> {
let cache_key = format!("{}:{:?}", req.tenant_id, req.report_type);
if let Some(cached) = state.report_cache.get(&cache_key) {
info!("Cache hit for report: {}", cache_key);
return Json(cached.clone());
}
info!("Generating report: {:?} for tenant: {}", req.report_type, req.tenant_id);
let result = match req.report_type {
ReportType::FinancialSummary => generate_financial_summary(&req).await,
ReportType::SalesPipeline => generate_sales_pipeline(&req).await,
ReportType::CustomerAnalytics => generate_customer_analytics(&req).await,
ReportType::RevenueForecast => generate_revenue_forecast(&req).await,
ReportType::ExpenseBreakdown => generate_expense_breakdown(&req).await,
ReportType::CashflowProjection => generate_cashflow_projection(&req).await,
ReportType::MrrAnalysis => generate_mrr_analysis(&req).await,
ReportType::ChurnAnalysis => generate_churn_analysis(&req).await,
ReportType::Custom(ref name) => generate_custom_report(&req, name).await,
};
state.report_cache.insert(cache_key, result.clone());
Json(result)
}
pub async fn analytics_query(
State(_state): State<AppState>,
Json(req): Json<AnalyticsQuery>,
) -> Json<AnalyticsResult> {
let start = std::time::Instant::now();
info!("Analytics query: {} for tenant: {}", req.query_type, req.tenant_id);
let rows: Vec<HashMap<String, serde_json::Value>> = match req.query_type.as_str() {
"revenue_by_month" => vec![
{
let mut row = HashMap::new();
row.insert("month".to_string(), serde_json::json!("2026-07"));
row.insert("revenue".to_string(), serde_json::json!(12450.0));
row.insert("expenses".to_string(), serde_json::json!(8230.0));
row
},
{
let mut row = HashMap::new();
row.insert("month".to_string(), serde_json::json!("2026-06"));
row.insert("revenue".to_string(), serde_json::json!(11520.0));
row.insert("expenses".to_string(), serde_json::json!(8480.0));
row
},
],
"sales_pipeline" => vec![
{
let mut row = HashMap::new();
row.insert("stage".to_string(), serde_json::json!("qualified"));
row.insert("count".to_string(), serde_json::json!(12));
row.insert("value".to_string(), serde_json::json!(145000.0));
row
},
{
let mut row = HashMap::new();
row.insert("stage".to_string(), serde_json::json!("proposal"));
row.insert("count".to_string(), serde_json::json!(5));
row.insert("value".to_string(), serde_json::json!(89000.0));
row
},
],
_ => vec![],
};
let mut totals = HashMap::new();
totals.insert("total_revenue".to_string(), 23970.0);
totals.insert("total_expenses".to_string(), 16710.0);
Json(AnalyticsResult {
query_id: Uuid::new_v4().to_string(),
rows,
totals,
execution_time_ms: start.elapsed().as_millis() as u64,
})
}
pub async fn batch_analytics(
State(state): State<AppState>,
Json(req): Json<BatchRequest>,
) -> Json<Vec<AnalyticsResult>> {
info!("Batch analytics: {} queries for tenant: {}", req.queries.len(), req.tenant_id);
let results: Vec<AnalyticsResult> = req.queries
.into_iter()
.map(|q| {
let start = std::time::Instant::now();
AnalyticsResult {
query_id: Uuid::new_v4().to_string(),
rows: vec![],
totals: HashMap::new(),
execution_time_ms: start.elapsed().as_millis() as u64,
}
})
.collect();
Json(results)
}
pub async fn dashboard_summary(
State(_state): State<AppState>,
) -> Json<DashboardSummary> {
Json(DashboardSummary {
tenant_id: "default".to_string(),
generated_at: Utc::now(),
finance: FinanceKPIs {
revenue_mtd: 12450.0,
expenses_mtd: 8230.0,
cash_balance: 45230.0,
runway_months: 14.5,
outstanding_invoices: 12340.0,
tax_due_days: Some(7),
},
sales: SalesKPIs {
mrr: 8450.0,
arr: 101400.0,
pipeline_value: 340000.0,
win_rate: 0.34,
avg_deal_size: 15500.0,
sales_cycle_days: 45.0,
},
marketing: MarketingKPIs {
active_campaigns: 3,
impressions_30d: 45230,
ctr: 0.058,
brand_mentions: 23,
ai_spend: 1247.0,
},
hr: HRKPIs {
headcount: 12,
open_positions: 3,
avg_time_to_hire_days: 28.0,
retention_rate: 0.92,
},
})
}
async fn generate_financial_summary(req: &ReportRequest) -> ReportResult {
let mut key_metrics = HashMap::new();
key_metrics.insert("total_revenue".to_string(), 12450.0);
key_metrics.insert("total_expenses".to_string(), 8230.0);
key_metrics.insert("net_profit".to_string(), 4220.0);
key_metrics.insert("profit_margin".to_string(), 0.34);
ReportResult {
report_type: "financial_summary".to_string(),
tenant_id: req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({
"revenue_by_category": {
"subscriptions": 8450.0,
"services": 2800.0,
"other": 1200.0,
},
"expenses_by_category": {
"infrastructure": 3200.0,
"personnel": 3800.0,
"marketing": 1230.0,
},
}),
summary: ReportSummary {
total_records: 156,
period_days: 30,
key_metrics,
},
}
}
async fn generate_sales_pipeline(req: &ReportRequest) -> ReportResult {
let mut key_metrics = HashMap::new();
key_metrics.insert("pipeline_value".to_string(), 340000.0);
key_metrics.insert("win_rate".to_string(), 0.34);
key_metrics.insert("avg_deal_size".to_string(), 15500.0);
ReportResult {
report_type: "sales_pipeline".to_string(),
tenant_id: req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({
"stages": [
{"name": "lead", "count": 45, "value": 0.0},
{"name": "qualified", "count": 12, "value": 145000.0},
{"name": "proposal", "count": 5, "value": 89000.0},
{"name": "negotiation", "count": 3, "value": 67000.0},
{"name": "closed_won", "count": 8, "value": 124000.0},
],
}),
summary: ReportSummary {
total_records: 73,
period_days: 30,
key_metrics,
},
}
}
async fn generate_customer_analytics(_req: &ReportRequest) -> ReportResult {
ReportResult {
report_type: "customer_analytics".to_string(),
tenant_id: _req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({}),
summary: ReportSummary {
total_records: 0,
period_days: 30,
key_metrics: HashMap::new(),
},
}
}
async fn generate_revenue_forecast(_req: &ReportRequest) -> ReportResult {
ReportResult {
report_type: "revenue_forecast".to_string(),
tenant_id: _req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({
"forecast_3m": 38500.0,
"forecast_6m": 82000.0,
"forecast_12m": 185000.0,
}),
summary: ReportSummary {
total_records: 12,
period_days: 365,
key_metrics: HashMap::new(),
},
}
}
async fn generate_expense_breakdown(_req: &ReportRequest) -> ReportResult {
ReportResult {
report_type: "expense_breakdown".to_string(),
tenant_id: _req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({}),
summary: ReportSummary {
total_records: 0,
period_days: 30,
key_metrics: HashMap::new(),
},
}
}
async fn generate_cashflow_projection(_req: &ReportRequest) -> ReportResult {
ReportResult {
report_type: "cashflow_projection".to_string(),
tenant_id: _req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({
"projected_balance_3m": 52000.0,
"projected_balance_6m": 61000.0,
"burn_rate_monthly": 8230.0,
}),
summary: ReportSummary {
total_records: 6,
period_days: 180,
key_metrics: HashMap::new(),
},
}
}
async fn generate_mrr_analysis(_req: &ReportRequest) -> ReportResult {
let mut key_metrics = HashMap::new();
key_metrics.insert("mrr".to_string(), 8450.0);
key_metrics.insert("arr".to_string(), 101400.0);
key_metrics.insert("mrr_growth_rate".to_string(), 0.08);
ReportResult {
report_type: "mrr_analysis".to_string(),
tenant_id: _req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({
"mrr_by_plan": {
"starter": 1200.0,
"professional": 4800.0,
"enterprise": 2450.0,
},
}),
summary: ReportSummary {
total_records: 48,
period_days: 30,
key_metrics,
},
}
}
async fn generate_churn_analysis(_req: &ReportRequest) -> ReportResult {
let mut key_metrics = HashMap::new();
key_metrics.insert("churn_rate".to_string(), 0.05);
key_metrics.insert("retention_rate".to_string(), 0.95);
ReportResult {
report_type: "churn_analysis".to_string(),
tenant_id: _req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({
"churn_by_reason": {
"price": 2,
"features": 1,
"competitor": 0,
"other": 1,
},
}),
summary: ReportSummary {
total_records: 48,
period_days: 90,
key_metrics,
},
}
}
async fn generate_custom_report(req: &ReportRequest, name: &str) -> ReportResult {
warn!("Custom report '{}' not fully implemented", name);
ReportResult {
report_type: format!("custom:{}", name),
tenant_id: req.tenant_id.clone(),
generated_at: Utc::now(),
data: serde_json::json!({"message": "Custom report template"}),
summary: ReportSummary {
total_records: 0,
period_days: 30,
key_metrics: HashMap::new(),
},
}
}