use std::sync::Arc;
use axum::{
extract::{Path, State},
http::StatusCode,
response::IntoResponse,
routing::post,
Json, Router,
};
use chrono::{DateTime, NaiveDate, Utc};
use rust_decimal::Decimal;
use serde::Deserialize;
use serde_json::{json, Value};
use uuid::Uuid;
use crate::application::service::{
MaintenanceEventSink, MaintenanceRequestUpdate, MaintenanceRequestWriteService,
NewMaintenanceRequest, RequestWriteError as WriteError,
};
use crate::domain::entity::{MaintenanceType, RequestKanbanState, RequestPriority, RepeatType, RepeatUnit};
#[derive(Clone)]
pub struct MaintenanceRequestLifecycleState {
pub write_svc: Arc<MaintenanceRequestWriteService>,
pub event_sink: Arc<dyn MaintenanceEventSink>,
}
pub struct MaintenanceRequestApiError(pub WriteError);
impl IntoResponse for MaintenanceRequestApiError {
fn into_response(self) -> axum::response::Response {
let msg = self.0.to_string();
let (status, code) = match self.0 {
WriteError::NotFound(_) => (StatusCode::NOT_FOUND, "MAINTENANCE_REQUEST_NOT_FOUND"),
WriteError::ScheduleEndBeforeStart => (
StatusCode::UNPROCESSABLE_ENTITY,
"MAINTENANCE_REQUEST_SCHEDULE_END_BEFORE_START",
),
WriteError::RepeatIntervalBelowOne => (
StatusCode::UNPROCESSABLE_ENTITY,
"MAINTENANCE_REQUEST_REPEAT_INTERVAL_BELOW_ONE",
),
WriteError::RepeatUntilMissing => (
StatusCode::UNPROCESSABLE_ENTITY,
"MAINTENANCE_REQUEST_REPEAT_UNTIL_MISSING",
),
WriteError::CorrectiveCannotRecur => (
StatusCode::UNPROCESSABLE_ENTITY,
"MAINTENANCE_REQUEST_CORRECTIVE_CANNOT_RECUR",
),
WriteError::InvalidState(_) => (StatusCode::CONFLICT, "MAINTENANCE_REQUEST_INVALID_STATE"),
WriteError::Invalid(_) => (StatusCode::BAD_REQUEST, "MAINTENANCE_REQUEST_INVALID_INPUT"),
WriteError::Db(_) => (StatusCode::INTERNAL_SERVER_ERROR, "MAINTENANCE_REQUEST_DATABASE_ERROR"),
};
(status, Json(json!({ "success": false, "error": code, "message": msg }))).into_response()
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateMaintenanceRequestBody {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub schedule_date: Option<DateTime<Utc>>,
#[serde(default)]
pub schedule_end: Option<DateTime<Utc>>,
#[serde(default)]
pub duration: Decimal,
#[serde(default)]
pub owner_user_id: Option<Uuid>,
#[serde(default)]
pub user_id: Option<Uuid>,
#[serde(default)]
pub asset_id: Option<Uuid>,
#[serde(default)]
pub stage_id: Option<Uuid>,
#[serde(default)]
pub kanban_state: RequestKanbanState,
#[serde(default)]
pub priority: RequestPriority,
#[serde(default = "default_maintenance_type")]
pub maintenance_type: MaintenanceType,
#[serde(default)]
pub recurring: bool,
#[serde(default = "default_repeat_interval")]
pub repeat_interval: i32,
#[serde(default)]
pub repeat_unit: RepeatUnit,
#[serde(default)]
pub repeat_type: RepeatType,
#[serde(default)]
pub repeat_until: Option<NaiveDate>,
}
fn default_maintenance_type() -> MaintenanceType {
MaintenanceType::Corrective
}
fn default_repeat_interval() -> i32 {
1
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransitionRequestBody {
pub target_stage_id: Uuid,
#[serde(default)]
pub kanban_state: Option<RequestKanbanState>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateMaintenanceRequestBody {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub schedule_date: Option<DateTime<Utc>>,
#[serde(default)]
pub schedule_end: Option<DateTime<Utc>>,
#[serde(default)]
pub duration: Option<Decimal>,
#[serde(default)]
pub owner_user_id: Option<Uuid>,
#[serde(default)]
pub user_id: Option<Uuid>,
#[serde(default)]
pub asset_id: Option<Uuid>,
#[serde(default)]
pub priority: Option<RequestPriority>,
#[serde(default)]
pub maintenance_type: Option<MaintenanceType>,
#[serde(default)]
pub recurring: Option<bool>,
#[serde(default)]
pub repeat_interval: Option<i32>,
#[serde(default)]
pub repeat_unit: Option<RepeatUnit>,
#[serde(default)]
pub repeat_type: Option<RepeatType>,
#[serde(default)]
pub repeat_until: Option<NaiveDate>,
}
pub async fn create_request(
State(st): State<MaintenanceRequestLifecycleState>,
Json(req): Json<CreateMaintenanceRequestBody>,
) -> Result<(StatusCode, Json<Value>), MaintenanceRequestApiError> {
let id = st
.write_svc
.create_request(NewMaintenanceRequest {
name: req.name,
description: req.description,
schedule_date: req.schedule_date,
schedule_end: req.schedule_end,
duration: req.duration,
owner_user_id: req.owner_user_id,
user_id: req.user_id,
asset_id: req.asset_id,
stage_id: req.stage_id,
kanban_state: req.kanban_state,
priority: req.priority,
maintenance_type: req.maintenance_type,
recurring: req.recurring,
repeat_interval: req.repeat_interval,
repeat_unit: req.repeat_unit,
repeat_type: req.repeat_type,
repeat_until: req.repeat_until,
})
.await
.map_err(MaintenanceRequestApiError)?;
Ok((
StatusCode::CREATED,
Json(json!({ "success": true, "data": { "id": id } })),
))
}
pub async fn transition_request(
State(st): State<MaintenanceRequestLifecycleState>,
Path(id): Path<Uuid>,
Json(req): Json<TransitionRequestBody>,
) -> Result<(StatusCode, Json<Value>), MaintenanceRequestApiError> {
let outcome = st
.write_svc
.transition_request(id, req.target_stage_id, req.kanban_state, st.event_sink.as_ref())
.await
.map_err(MaintenanceRequestApiError)?;
Ok((
StatusCode::OK,
Json(json!({
"success": true,
"data": {
"request_id": outcome.request_id,
"from_stage_id": outcome.from_stage_id,
"to_stage_id": outcome.to_stage_id,
"close_date": outcome.close_date,
"spawned_successor_id": outcome.spawned_successor_id,
"already": outcome.already,
}
})),
))
}
pub async fn update_request(
State(st): State<MaintenanceRequestLifecycleState>,
Path(id): Path<Uuid>,
Json(req): Json<UpdateMaintenanceRequestBody>,
) -> Result<(StatusCode, Json<Value>), MaintenanceRequestApiError> {
st.write_svc
.update_request(
id,
MaintenanceRequestUpdate {
name: req.name,
description: req.description,
schedule_date: req.schedule_date,
schedule_end: req.schedule_end,
duration: req.duration,
owner_user_id: req.owner_user_id,
user_id: req.user_id,
asset_id: req.asset_id,
priority: req.priority,
maintenance_type: req.maintenance_type,
recurring: req.recurring,
repeat_interval: req.repeat_interval,
repeat_unit: req.repeat_unit,
repeat_type: req.repeat_type,
repeat_until: req.repeat_until,
},
)
.await
.map_err(MaintenanceRequestApiError)?;
Ok((StatusCode::OK, Json(json!({ "success": true }))))
}
pub fn create_maintenance_request_lifecycle_routes(state: MaintenanceRequestLifecycleState) -> Router {
Router::<MaintenanceRequestLifecycleState>::new()
.route("/maintenance_requests", post(create_request))
.route("/maintenance_requests/:id/transition", post(transition_request))
.route("/maintenance_requests/:id", post(update_request))
.with_state(state)
}