use std::sync::Arc;
use axum::{
extract::{Path, Query, State},
http::StatusCode,
middleware::from_fn_with_state,
response::IntoResponse,
routing::{get, post},
Json, Router,
};
use backbone_auth::org::{org_auth, OrgVerifier};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use uuid::Uuid;
use crate::application::service::banking_write_service::{
BankingError, BankingWriteService, NewStatementImport, NewStatementLine,
};
use crate::BankingModule;
use super::{
create_bank_account_read_routes, create_bank_read_routes,
create_bank_reconciliation_read_routes, create_bank_statement_import_read_routes,
create_bank_transaction_read_routes, create_reconcile_preset_read_routes,
};
#[derive(Debug, Serialize)]
struct ErrorBody {
error: String,
message: String,
}
#[derive(Debug, Serialize)]
struct IdResponse {
id: Uuid,
}
fn err(e: BankingError) -> axum::response::Response {
let s = StatusCode::from_u16(e.http_status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
(
s,
Json(ErrorBody {
error: e.code(),
message: e.to_string(),
}),
)
.into_response()
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LineBody {
txn_date: chrono::NaiveDate,
#[serde(default)]
description: Option<String>,
#[serde(default)]
reference_no: Option<String>,
#[serde(default)]
deposit: Decimal,
#[serde(default)]
withdrawal: Decimal,
}
impl From<LineBody> for NewStatementLine {
fn from(b: LineBody) -> Self {
NewStatementLine {
txn_date: b.txn_date,
description: b.description,
reference_no: b.reference_no,
deposit: b.deposit,
withdrawal: b.withdrawal,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ImportBody {
bank_account_id: Uuid,
#[serde(default)]
source_format: Option<String>,
period_start: chrono::NaiveDate,
period_end: chrono::NaiveDate,
#[serde(default)]
opening_balance: Decimal,
#[serde(default)]
closing_balance: Decimal,
#[serde(default)]
file_ref: Option<String>,
lines: Vec<LineBody>,
}
async fn import_statement(
State(svc): State<Arc<BankingWriteService>>,
Json(b): Json<ImportBody>,
) -> axum::response::Response {
let imp = NewStatementImport {
bank_account_id: b.bank_account_id,
source_format: b.source_format,
period_start: b.period_start,
period_end: b.period_end,
opening_balance: b.opening_balance,
closing_balance: b.closing_balance,
file_ref: b.file_ref,
lines: b.lines.into_iter().map(Into::into).collect(),
};
match svc.import_statement(imp).await {
Ok(id) => (StatusCode::CREATED, Json(IdResponse { id })).into_response(),
Err(e) => err(e),
}
}
#[derive(Debug, Deserialize)]
struct CandidatesQuery {
txn_id: Uuid,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct CandidateBody {
payment_id: Uuid,
payment_number: String,
open_amount: Decimal,
posting_date: chrono::NaiveDate,
score: i64,
reason: String,
}
async fn preset_candidates(
State(svc): State<Arc<BankingWriteService>>,
Path(preset_id): Path<Uuid>,
Query(q): Query<CandidatesQuery>,
) -> axum::response::Response {
match svc.order_candidates(preset_id, q.txn_id).await {
Ok(list) => (
StatusCode::OK,
Json(
list.into_iter()
.map(|c| CandidateBody {
payment_id: c.payment_id,
payment_number: c.payment_number,
open_amount: c.open_amount,
posting_date: c.posting_date,
score: c.score,
reason: c.reason,
})
.collect::<Vec<_>>(),
),
)
.into_response(),
Err(e) => err(e),
}
}
fn write_routes(svc: Arc<BankingWriteService>, verifier: OrgVerifier) -> Router {
Router::new()
.route("/bank-statements/import", post(import_statement))
.route("/reconcile-presets/:id/candidates", get(preset_candidates))
.route_layer(from_fn_with_state(verifier, org_auth))
.with_state(svc)
}
pub fn create_guarded_banking_routes(
m: &BankingModule,
pool: PgPool,
verifier: OrgVerifier,
) -> Router {
let write = Arc::new(BankingWriteService::new(pool));
Router::new()
.merge(create_bank_read_routes(m.bank_service.clone()))
.merge(create_bank_account_read_routes(
m.bank_account_service.clone(),
))
.merge(create_bank_statement_import_read_routes(
m.bank_statement_import_service.clone(),
))
.merge(create_bank_transaction_read_routes(
m.bank_transaction_service.clone(),
))
.merge(create_bank_reconciliation_read_routes(
m.bank_reconciliation_service.clone(),
))
.merge(create_reconcile_preset_read_routes(
m.reconcile_preset_service.clone(),
))
.merge(write_routes(write, verifier))
}