koprogo_api/infrastructure/web/handlers/
owner_contribution_handlers.rs

1use crate::application::dto::{
2    CreateOwnerContributionRequest, OwnerContributionResponse, RecordPaymentRequest,
3};
4use crate::infrastructure::web::{AppState, AuthenticatedUser};
5use actix_web::{get, post, put, web, HttpResponse};
6use uuid::Uuid;
7
8/// POST /api/v1/owner-contributions
9/// Create a new owner contribution
10#[post("/owner-contributions")]
11pub async fn create_contribution(
12    state: web::Data<AppState>,
13    user: AuthenticatedUser,
14    req: web::Json<CreateOwnerContributionRequest>,
15) -> HttpResponse {
16    // Get organization_id from user (required for creating contributions)
17    let organization_id = match user.organization_id {
18        Some(org_id) => org_id,
19        None => return HttpResponse::BadRequest().body("Organization ID required"),
20    };
21
22    match state
23        .owner_contribution_use_cases
24        .create_contribution(
25            organization_id,
26            req.owner_id,
27            req.unit_id,
28            req.description.clone(),
29            req.amount,
30            req.contribution_type.clone(),
31            req.contribution_date,
32            req.account_code.clone(),
33        )
34        .await
35    {
36        Ok(contribution) => {
37            let response = OwnerContributionResponse::from(contribution);
38            HttpResponse::Created().json(response)
39        }
40        Err(e) => HttpResponse::BadRequest().body(e),
41    }
42}
43
44/// GET /api/v1/owner-contributions/{id}
45/// Get contribution by ID
46#[get("/owner-contributions/{id}")]
47pub async fn get_contribution(
48    state: web::Data<AppState>,
49    _user: AuthenticatedUser,
50    id: web::Path<Uuid>,
51) -> HttpResponse {
52    match state
53        .owner_contribution_use_cases
54        .get_contribution(*id)
55        .await
56    {
57        Ok(Some(contribution)) => {
58            let response = OwnerContributionResponse::from(contribution);
59            HttpResponse::Ok().json(response)
60        }
61        Ok(None) => HttpResponse::NotFound().body("Contribution not found"),
62        Err(e) => HttpResponse::InternalServerError().body(e),
63    }
64}
65
66/// GET /api/v1/owner-contributions?owner_id={uuid}
67/// Get contributions by owner, or all contributions for organization if owner_id not provided
68#[get("/owner-contributions")]
69pub async fn get_contributions_by_owner(
70    state: web::Data<AppState>,
71    user: AuthenticatedUser,
72    query: web::Query<std::collections::HashMap<String, String>>,
73) -> HttpResponse {
74    // If owner_id provided, filter by owner
75    if let Some(id_str) = query.get("owner_id") {
76        let owner_id = match Uuid::parse_str(id_str) {
77            Ok(id) => id,
78            Err(_) => return HttpResponse::BadRequest().body("Invalid owner_id format"),
79        };
80
81        match state
82            .owner_contribution_use_cases
83            .get_contributions_by_owner(owner_id)
84            .await
85        {
86            Ok(contributions) => {
87                let responses: Vec<OwnerContributionResponse> =
88                    contributions.into_iter().map(Into::into).collect();
89                return HttpResponse::Ok().json(responses);
90            }
91            Err(e) => return HttpResponse::InternalServerError().body(e),
92        }
93    }
94
95    // Otherwise, return all contributions for user's organization
96    let organization_id = match user.organization_id {
97        Some(org_id) => org_id,
98        None => return HttpResponse::BadRequest().body("Organization ID required"),
99    };
100
101    match state
102        .owner_contribution_use_cases
103        .get_contributions_by_organization(organization_id)
104        .await
105    {
106        Ok(contributions) => {
107            let responses: Vec<OwnerContributionResponse> =
108                contributions.into_iter().map(Into::into).collect();
109            HttpResponse::Ok().json(responses)
110        }
111        Err(e) => HttpResponse::InternalServerError().body(e),
112    }
113}
114
115/// GET /api/v1/owner-contributions/outstanding?owner_id={uuid}
116/// Get outstanding (unpaid) contributions for an owner
117#[get("/owner-contributions/outstanding")]
118pub async fn get_outstanding_contributions(
119    state: web::Data<AppState>,
120    _user: AuthenticatedUser,
121    query: web::Query<std::collections::HashMap<String, String>>,
122) -> HttpResponse {
123    let owner_id = match query.get("owner_id") {
124        Some(id_str) => match Uuid::parse_str(id_str) {
125            Ok(id) => id,
126            Err(_) => return HttpResponse::BadRequest().body("Invalid owner_id format"),
127        },
128        None => return HttpResponse::BadRequest().body("owner_id is required"),
129    };
130
131    match state
132        .owner_contribution_use_cases
133        .get_outstanding_contributions(owner_id)
134        .await
135    {
136        Ok(contributions) => {
137            let responses: Vec<OwnerContributionResponse> =
138                contributions.into_iter().map(Into::into).collect();
139            HttpResponse::Ok().json(responses)
140        }
141        Err(e) => HttpResponse::InternalServerError().body(e),
142    }
143}
144
145/// PUT /api/v1/owner-contributions/{id}/mark-paid
146/// Record payment for a contribution
147#[put("/owner-contributions/{id}/mark-paid")]
148pub async fn record_payment(
149    state: web::Data<AppState>,
150    _user: AuthenticatedUser,
151    id: web::Path<Uuid>,
152    req: web::Json<RecordPaymentRequest>,
153) -> HttpResponse {
154    match state
155        .owner_contribution_use_cases
156        .record_payment(
157            *id,
158            req.payment_date,
159            req.payment_method.clone(),
160            req.payment_reference.clone(),
161        )
162        .await
163    {
164        Ok(contribution) => {
165            let response = OwnerContributionResponse::from(contribution);
166            HttpResponse::Ok().json(response)
167        }
168        Err(e) => HttpResponse::BadRequest().body(e),
169    }
170}