backbone_accounting/presentation/http/
reporting_handler.rs1use std::sync::Arc;
17
18use axum::{
19 extract::{Query, State},
20 http::StatusCode,
21 response::IntoResponse,
22 routing::get,
23 Json, Router,
24};
25use chrono::NaiveDate;
26use serde::Deserialize;
27use uuid::Uuid;
28
29use crate::application::service::reporting_service::ReportingService;
30
31#[derive(Debug, Deserialize)]
32pub struct AsOfQuery {
33 pub as_of: NaiveDate,
34}
35
36#[derive(Debug, Deserialize)]
37pub struct PeriodQuery {
38 pub period_start: NaiveDate,
39 pub period_end: NaiveDate,
40}
41
42#[derive(Debug, Deserialize)]
43pub struct GeneralLedgerQuery {
44 pub to_date: NaiveDate,
45 pub from_date: Option<NaiveDate>,
46 pub account_id: Option<Uuid>,
47 pub limit: Option<i64>,
48 pub offset: Option<i64>,
49}
50
51#[derive(Debug, Deserialize)]
52pub struct PartnerLedgerQuery {
53 pub party_type: String,
54 pub party_id: Uuid,
55 pub as_of: NaiveDate,
56}
57
58fn err(e: anyhow::Error) -> (StatusCode, Json<serde_json::Value>) {
59 (
60 StatusCode::INTERNAL_SERVER_ERROR,
61 Json(serde_json::json!({ "success": false, "error": e.to_string() })),
62 )
63}
64
65async fn trial_balance(
66 State(svc): State<Arc<ReportingService>>,
67 Query(q): Query<AsOfQuery>,
68) -> impl IntoResponse {
69 match svc.trial_balance(q.as_of).await {
70 Ok(r) => (StatusCode::OK, Json(serde_json::to_value(r).unwrap())),
71 Err(e) => err(e),
72 }
73}
74
75async fn balance_sheet(
76 State(svc): State<Arc<ReportingService>>,
77 Query(q): Query<AsOfQuery>,
78) -> impl IntoResponse {
79 match svc.balance_sheet(q.as_of).await {
80 Ok(r) => (StatusCode::OK, Json(serde_json::to_value(r).unwrap())),
81 Err(e) => err(e),
82 }
83}
84
85async fn income_statement(
86 State(svc): State<Arc<ReportingService>>,
87 Query(q): Query<PeriodQuery>,
88) -> impl IntoResponse {
89 match svc
90 .income_statement(q.period_start, q.period_end)
91 .await
92 {
93 Ok(r) => (StatusCode::OK, Json(serde_json::to_value(r).unwrap())),
94 Err(e) => err(e),
95 }
96}
97
98async fn general_ledger(
99 State(svc): State<Arc<ReportingService>>,
100 Query(q): Query<GeneralLedgerQuery>,
101) -> impl IntoResponse {
102 match svc
103 .general_ledger(
104 q.account_id,
105 q.from_date,
106 q.to_date,
107 q.limit,
108 q.offset.unwrap_or(0),
109 )
110 .await
111 {
112 Ok(r) => (StatusCode::OK, Json(serde_json::to_value(r).unwrap())),
113 Err(e) => err(e),
114 }
115}
116
117async fn partner_ledger(
118 State(svc): State<Arc<ReportingService>>,
119 Query(q): Query<PartnerLedgerQuery>,
120) -> impl IntoResponse {
121 if !matches!(q.party_type.as_str(), "customer" | "supplier") {
124 return (
125 StatusCode::BAD_REQUEST,
126 Json(serde_json::json!({
127 "success": false,
128 "error": format!(
129 "party_type must be 'customer' or 'supplier', got {:?}",
130 q.party_type
131 ),
132 })),
133 );
134 }
135 match svc
136 .partner_ledger(&q.party_type, q.party_id, q.as_of)
137 .await
138 {
139 Ok(r) => (StatusCode::OK, Json(serde_json::to_value(r).unwrap())),
140 Err(e) => err(e),
141 }
142}
143
144async fn aged_receivables(
145 State(svc): State<Arc<ReportingService>>,
146 Query(q): Query<AsOfQuery>,
147) -> impl IntoResponse {
148 match svc.aged_receivables(q.as_of).await {
149 Ok(r) => (StatusCode::OK, Json(serde_json::to_value(r).unwrap())),
150 Err(e) => err(e),
151 }
152}
153
154async fn aged_payables(
155 State(svc): State<Arc<ReportingService>>,
156 Query(q): Query<AsOfQuery>,
157) -> impl IntoResponse {
158 match svc.aged_payables(q.as_of).await {
159 Ok(r) => (StatusCode::OK, Json(serde_json::to_value(r).unwrap())),
160 Err(e) => err(e),
161 }
162}
163
164pub fn create_reporting_routes(service: Arc<ReportingService>) -> Router {
166 Router::new()
167 .route("/accounting/reports/trial-balance", get(trial_balance))
168 .route("/accounting/reports/balance-sheet", get(balance_sheet))
169 .route(
170 "/accounting/reports/income-statement",
171 get(income_statement),
172 )
173 .route("/accounting/reports/general-ledger", get(general_ledger))
174 .route("/accounting/reports/partner-ledger", get(partner_ledger))
175 .route(
176 "/accounting/reports/aged-receivables",
177 get(aged_receivables),
178 )
179 .route("/accounting/reports/aged-payables", get(aged_payables))
180 .with_state(service)
181}