koprogo_api/infrastructure/web/handlers/
financial_report_handlers_building.rs1use super::financial_report_handlers::IncomeStatementQuery;
3use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
4use crate::infrastructure::web::{AppState, AuthenticatedUser};
5use actix_web::{get, web, HttpResponse, Responder};
6
7#[get("/buildings/{building_id}/reports/balance-sheet")]
9pub async fn generate_balance_sheet_for_building(
10 state: web::Data<AppState>,
11 user: AuthenticatedUser,
12 building_id: web::Path<uuid::Uuid>,
13) -> impl Responder {
14 if !matches!(user.role.as_str(), "accountant" | "superadmin" | "syndic") {
15 return HttpResponse::Forbidden().json(serde_json::json!({
16 "error": "Only accountants, syndics and superadmins can generate financial reports"
17 }));
18 }
19
20 let organization_id = match user.require_organization() {
21 Ok(org_id) => org_id,
22 Err(e) => {
23 return HttpResponse::Unauthorized().json(serde_json::json!({
24 "error": e.to_string()
25 }))
26 }
27 };
28
29 match state
30 .financial_report_use_cases
31 .generate_balance_sheet_for_building(organization_id, *building_id)
32 .await
33 {
34 Ok(report) => {
35 AuditLogEntry::new(
36 AuditEventType::ReportGenerated,
37 Some(user.user_id),
38 Some(organization_id),
39 )
40 .with_metadata(serde_json::json!({
41 "report_type": "balance_sheet",
42 "building_id": building_id.to_string()
43 }))
44 .log();
45
46 HttpResponse::Ok().json(report)
47 }
48 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
49 "error": err
50 })),
51 }
52}
53
54#[get("/buildings/{building_id}/reports/income-statement")]
56pub async fn generate_income_statement_for_building(
57 state: web::Data<AppState>,
58 user: AuthenticatedUser,
59 building_id: web::Path<uuid::Uuid>,
60 query: web::Query<IncomeStatementQuery>,
61) -> impl Responder {
62 if !matches!(user.role.as_str(), "accountant" | "superadmin" | "syndic") {
63 return HttpResponse::Forbidden().json(serde_json::json!({
64 "error": "Only accountants, syndics and superadmins can generate financial reports"
65 }));
66 }
67
68 let organization_id = match user.require_organization() {
69 Ok(org_id) => org_id,
70 Err(e) => {
71 return HttpResponse::Unauthorized().json(serde_json::json!({
72 "error": e.to_string()
73 }))
74 }
75 };
76
77 let period_start = match chrono::DateTime::parse_from_rfc3339(&query.period_start) {
78 Ok(dt) => dt.with_timezone(&chrono::Utc),
79 Err(_) => {
80 return HttpResponse::BadRequest().json(serde_json::json!({
81 "error": "Invalid period_start format"
82 }))
83 }
84 };
85
86 let period_end = match chrono::DateTime::parse_from_rfc3339(&query.period_end) {
87 Ok(dt) => dt.with_timezone(&chrono::Utc),
88 Err(_) => {
89 return HttpResponse::BadRequest().json(serde_json::json!({
90 "error": "Invalid period_end format"
91 }))
92 }
93 };
94
95 if period_start >= period_end {
96 return HttpResponse::BadRequest().json(serde_json::json!({
97 "error": "period_start must be before period_end"
98 }));
99 }
100
101 match state
102 .financial_report_use_cases
103 .generate_income_statement_for_building(
104 organization_id,
105 *building_id,
106 period_start,
107 period_end,
108 )
109 .await
110 {
111 Ok(report) => {
112 AuditLogEntry::new(
113 AuditEventType::ReportGenerated,
114 Some(user.user_id),
115 Some(organization_id),
116 )
117 .with_metadata(serde_json::json!({
118 "report_type": "income_statement",
119 "building_id": building_id.to_string()
120 }))
121 .log();
122
123 HttpResponse::Ok().json(report)
124 }
125 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
126 "error": err
127 })),
128 }
129}