plc_control/crates/app_feeder_distributor/src/handler/equipment.rs

336 lines
9.5 KiB
Rust

use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use validator::Validate;
use plc_platform_core::util::{
pagination::{PaginatedResponse, PaginationParams},
response::ApiErr,
};
use crate::AppState;
async fn notify_units(
state: &AppState,
unit_ids: impl IntoIterator<Item = Uuid>,
) {
let mut seen = std::collections::HashSet::new();
for unit_id in unit_ids {
if seen.insert(unit_id) {
state.control_runtime.notify_unit(unit_id).await;
}
}
}
#[derive(Deserialize, Validate)]
pub struct GetEquipmentListQuery {
#[validate(length(min = 1, max = 100))]
pub keyword: Option<String>,
#[serde(flatten)]
pub pagination: PaginationParams,
}
#[derive(Serialize)]
pub struct SignalRolePoint {
pub point_id: uuid::Uuid,
pub signal_role: String,
pub point_monitor: Option<crate::telemetry::PointMonitorInfo>,
}
#[derive(Serialize)]
pub struct EquipmentListItem {
#[serde(flatten)]
pub equipment: plc_platform_core::model::Equipment,
pub point_count: i64,
pub role_points: Vec<SignalRolePoint>,
}
pub async fn get_equipment_list(
State(state): State<AppState>,
Query(query): Query<GetEquipmentListQuery>,
) -> Result<impl IntoResponse, ApiErr> {
query.validate()?;
let total = crate::service::get_equipment_count(&state.pool, query.keyword.as_deref()).await?;
let items = crate::service::get_equipment_paginated(
&state.pool,
query.keyword.as_deref(),
query.pagination.page_size,
query.pagination.offset(),
)
.await?;
let equipment_ids: Vec<uuid::Uuid> = items.iter().map(|item| item.equipment.id).collect();
let role_point_rows =
crate::service::get_signal_role_points_batch(&state.pool, &equipment_ids).await?;
let monitor_guard = state
.connection_manager
.get_point_monitor_data_read_guard()
.await;
let mut role_points_map: std::collections::HashMap<uuid::Uuid, Vec<SignalRolePoint>> =
std::collections::HashMap::new();
for rp in role_point_rows {
role_points_map
.entry(rp.equipment_id)
.or_default()
.push(SignalRolePoint {
point_id: rp.point_id,
signal_role: rp.signal_role,
point_monitor: monitor_guard.get(&rp.point_id).cloned(),
});
}
let data = items
.into_iter()
.map(|item| {
let equipment_id = item.equipment.id;
EquipmentListItem {
equipment: item.equipment,
point_count: item.point_count,
role_points: role_points_map.remove(&equipment_id).unwrap_or_default(),
}
})
.collect::<Vec<_>>();
Ok(Json(PaginatedResponse::new(
data,
total,
query.pagination.page,
query.pagination.page_size,
)))
}
pub async fn get_equipment(
State(state): State<AppState>,
Path(equipment_id): Path<Uuid>,
) -> Result<impl IntoResponse, ApiErr> {
let equipment = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?;
match equipment {
Some(item) => Ok(Json(item)),
None => Err(ApiErr::NotFound("Equipment not found".to_string(), None)),
}
}
pub async fn get_equipment_points(
State(state): State<AppState>,
Path(equipment_id): Path<Uuid>,
) -> Result<impl IntoResponse, ApiErr> {
let exists = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?;
if exists.is_none() {
return Err(ApiErr::NotFound("Equipment not found".to_string(), None));
}
let points = crate::service::get_points_by_equipment_id(&state.pool, equipment_id).await?;
Ok(Json(points))
}
#[derive(Debug, Deserialize, Validate)]
pub struct CreateEquipmentReq {
pub unit_id: Option<Uuid>,
#[validate(length(min = 1, max = 100))]
pub code: String,
#[validate(length(min = 1, max = 100))]
pub name: String,
pub kind: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, Validate)]
pub struct UpdateEquipmentReq {
pub unit_id: Option<Option<Uuid>>,
#[validate(length(min = 1, max = 100))]
pub code: Option<String>,
#[validate(length(min = 1, max = 100))]
pub name: Option<String>,
pub kind: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, Validate)]
pub struct BatchSetEquipmentUnitReq {
pub equipment_ids: Vec<Uuid>,
pub unit_id: Option<Uuid>,
}
pub async fn create_equipment(
State(state): State<AppState>,
Json(payload): Json<CreateEquipmentReq>,
) -> Result<impl IntoResponse, ApiErr> {
payload.validate()?;
let exists = crate::service::get_equipment_by_code(&state.pool, &payload.code).await?;
if exists.is_some() {
return Err(ApiErr::BadRequest(
"Equipment code already exists".to_string(),
None,
));
}
if let Some(unit_id) = payload.unit_id {
let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?;
if unit_exists.is_none() {
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
}
}
let equipment_id = crate::service::create_equipment(
&state.pool,
payload.unit_id,
&payload.code,
&payload.name,
payload.kind.as_deref(),
payload.description.as_deref(),
)
.await?;
if let Some(unit_id) = payload.unit_id {
notify_units(&state, [unit_id]).await;
}
Ok((
StatusCode::CREATED,
Json(serde_json::json!({
"id": equipment_id,
"ok_msg": "Equipment created successfully"
})),
))
}
pub async fn update_equipment(
State(state): State<AppState>,
Path(equipment_id): Path<Uuid>,
Json(payload): Json<UpdateEquipmentReq>,
) -> Result<impl IntoResponse, ApiErr> {
payload.validate()?;
if payload.unit_id.is_none()
&& payload.code.is_none()
&& payload.name.is_none()
&& payload.kind.is_none()
&& payload.description.is_none()
{
return Ok(Json(serde_json::json!({"ok_msg": "No fields to update"})));
}
let exists = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?;
let existing_equipment = if let Some(equipment) = exists {
equipment
} else {
return Err(ApiErr::NotFound("Equipment not found".to_string(), None));
};
if let Some(Some(unit_id)) = payload.unit_id {
let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?;
if unit_exists.is_none() {
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
}
}
if let Some(code) = payload.code.as_deref() {
let duplicate = crate::service::get_equipment_by_code(&state.pool, code).await?;
if duplicate
.as_ref()
.is_some_and(|item| item.id != equipment_id)
{
return Err(ApiErr::BadRequest(
"Equipment code already exists".to_string(),
None,
));
}
}
crate::service::update_equipment(
&state.pool,
equipment_id,
payload.unit_id,
payload.code.as_deref(),
payload.name.as_deref(),
payload.kind.as_deref(),
payload.description.as_deref(),
)
.await?;
let mut unit_ids = Vec::new();
if let Some(unit_id) = existing_equipment.unit_id {
unit_ids.push(unit_id);
}
let next_unit_id = match payload.unit_id {
Some(next) => next,
None => existing_equipment.unit_id,
};
if let Some(unit_id) = next_unit_id {
unit_ids.push(unit_id);
}
notify_units(&state, unit_ids).await;
Ok(Json(serde_json::json!({
"ok_msg": "Equipment updated successfully"
})))
}
pub async fn batch_set_equipment_unit(
State(state): State<AppState>,
Json(payload): Json<BatchSetEquipmentUnitReq>,
) -> Result<impl IntoResponse, ApiErr> {
payload.validate()?;
if payload.equipment_ids.is_empty() {
return Err(ApiErr::BadRequest(
"equipment_ids cannot be empty".to_string(),
None,
));
}
if let Some(unit_id) = payload.unit_id {
let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?;
if unit_exists.is_none() {
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
}
}
let before_unit_ids =
crate::service::get_unit_ids_by_equipment_ids(&state.pool, &payload.equipment_ids).await?;
let updated_count = crate::service::batch_set_equipment_unit(
&state.pool,
&payload.equipment_ids,
payload.unit_id,
)
.await?;
let mut unit_ids = before_unit_ids;
if let Some(unit_id) = payload.unit_id {
unit_ids.push(unit_id);
}
notify_units(&state, unit_ids).await;
Ok(Json(serde_json::json!({
"ok_msg": "Equipment unit updated successfully",
"updated_count": updated_count
})))
}
pub async fn delete_equipment(
State(state): State<AppState>,
Path(equipment_id): Path<Uuid>,
) -> Result<impl IntoResponse, ApiErr> {
let unit_ids = crate::service::get_unit_ids_by_equipment_ids(&state.pool, &[equipment_id]).await?;
let deleted = crate::service::delete_equipment(&state.pool, equipment_id).await?;
if !deleted {
return Err(ApiErr::NotFound("Equipment not found".to_string(), None));
}
notify_units(&state, unit_ids).await;
Ok(StatusCode::NO_CONTENT)
}