use chrono::{DateTime, NaiveDate, Utc};
use rust_decimal::Decimal;
use sqlx::{PgPool, Row};
use uuid::Uuid;
use backbone_orm::{company_scope, org_scope};
use crate::domain::entity::MaintenanceRequest;
pub const TABLE_NAME: &str = "maintenance.maintenance_requests";
pub struct MaintenanceRequestRepository(
backbone_orm::GenericCrudRepository<MaintenanceRequest, backbone_orm::SoftDelete>,
);
impl std::ops::Deref for MaintenanceRequestRepository {
type Target = backbone_orm::GenericCrudRepository<MaintenanceRequest, backbone_orm::SoftDelete>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl MaintenanceRequestRepository {
pub fn new(pool: PgPool) -> Self {
Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
}
}
pub struct NewRequestRow<'a> {
pub id: Uuid,
pub name: &'a str,
pub description: Option<&'a str>,
pub schedule_date: Option<DateTime<Utc>>,
pub schedule_end: Option<DateTime<Utc>>,
pub duration: Decimal,
pub owner_user_id: Option<Uuid>,
pub user_id: Option<Uuid>,
pub asset_id: Option<Uuid>,
pub stage_id: Uuid,
pub kanban_state: &'a str,
pub priority: &'a str,
pub maintenance_type: &'a str,
pub recurring: bool,
pub repeat_interval: i32,
pub repeat_unit: &'a str,
pub repeat_type: &'a str,
pub repeat_until: Option<NaiveDate>,
pub successor_of_request_id: Option<Uuid>,
}
pub struct RequestTransitionRow {
pub name: String,
pub description: Option<String>,
pub schedule_date: Option<DateTime<Utc>>,
pub schedule_end: Option<DateTime<Utc>>,
pub close_date: Option<NaiveDate>,
pub duration: Decimal,
pub owner_user_id: Option<Uuid>,
pub user_id: Option<Uuid>,
pub asset_id: Option<Uuid>,
pub stage_id: Uuid,
pub kanban_state: String,
pub priority: String,
pub maintenance_type: String,
pub recurring: bool,
pub repeat_interval: i32,
pub repeat_unit: String,
pub repeat_type: String,
pub repeat_until: Option<NaiveDate>,
pub successor_request_id: Option<Uuid>,
pub stage_done: bool,
}
pub struct StageRefRow {
pub id: Uuid,
pub name: String,
pub sequence: i32,
pub done: bool,
}
pub struct RequestFieldUpdates<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub schedule_date: Option<DateTime<Utc>>,
pub schedule_end: Option<DateTime<Utc>>,
pub duration: Option<Decimal>,
pub owner_user_id: Option<Uuid>,
pub user_id: Option<Uuid>,
pub asset_id: Option<Uuid>,
pub priority: Option<&'a str>,
pub maintenance_type: Option<&'a str>,
pub recurring: Option<bool>,
pub repeat_interval: Option<i32>,
pub repeat_unit: Option<&'a str>,
pub repeat_type: Option<&'a str>,
pub repeat_until: Option<NaiveDate>,
}
impl<'a> RequestFieldUpdates<'a> {
pub fn is_empty(&self) -> bool {
self.name.is_none()
&& self.description.is_none()
&& self.schedule_date.is_none()
&& self.schedule_end.is_none()
&& self.duration.is_none()
&& self.owner_user_id.is_none()
&& self.user_id.is_none()
&& self.asset_id.is_none()
&& self.priority.is_none()
&& self.maintenance_type.is_none()
&& self.recurring.is_none()
&& self.repeat_interval.is_none()
&& self.repeat_unit.is_none()
&& self.repeat_type.is_none()
&& self.repeat_until.is_none()
}
}
impl MaintenanceRequestRepository {
pub async fn insert_request_on(
&self,
conn: &mut sqlx::PgConnection,
r: &NewRequestRow<'_>,
) -> Result<(), sqlx::Error> {
sqlx::query(
r#"INSERT INTO maintenance.maintenance_requests
(id, name, description, schedule_date, schedule_end, duration,
owner_user_id, user_id, asset_id, stage_id, kanban_state, priority,
maintenance_type, recurring, repeat_interval, repeat_unit, repeat_type,
repeat_until, successor_of_request_id)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::request_kanban_state,
$12::request_priority,$13::maintenance_type,$14,$15,$16::repeat_unit,
$17::repeat_type,$18,$19)"#,
)
.bind(r.id).bind(r.name).bind(r.description)
.bind(r.schedule_date).bind(r.schedule_end).bind(r.duration)
.bind(r.owner_user_id).bind(r.user_id).bind(r.asset_id).bind(r.stage_id)
.bind(r.kanban_state).bind(r.priority).bind(r.maintenance_type).bind(r.recurring)
.bind(r.repeat_interval).bind(r.repeat_unit).bind(r.repeat_type).bind(r.repeat_until)
.bind(r.successor_of_request_id)
.execute(conn)
.await?;
Ok(())
}
pub async fn insert_request(
&self,
pool: &PgPool,
r: &NewRequestRow<'_>,
) -> Result<(), sqlx::Error> {
org_scope::execute_scoped(
pool,
sqlx::query(
r#"INSERT INTO maintenance.maintenance_requests
(id, name, description, schedule_date, schedule_end, duration,
owner_user_id, user_id, asset_id, stage_id, kanban_state, priority,
maintenance_type, recurring, repeat_interval, repeat_unit, repeat_type,
repeat_until, successor_of_request_id)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::request_kanban_state,
$12::request_priority,$13::maintenance_type,$14,$15,$16::repeat_unit,
$17::repeat_type,$18,$19)"#,
)
.bind(r.id).bind(r.name).bind(r.description)
.bind(r.schedule_date).bind(r.schedule_end).bind(r.duration)
.bind(r.owner_user_id).bind(r.user_id).bind(r.asset_id).bind(r.stage_id)
.bind(r.kanban_state).bind(r.priority).bind(r.maintenance_type).bind(r.recurring)
.bind(r.repeat_interval).bind(r.repeat_unit).bind(r.repeat_type).bind(r.repeat_until)
.bind(r.successor_of_request_id),
)
.await?;
Ok(())
}
pub async fn fetch_for_transition(
&self,
pool: &PgPool,
request_id: Uuid,
) -> Result<Option<RequestTransitionRow>, sqlx::Error> {
let row = company_scope::fetch_optional_row_scoped(
pool,
sqlx::query(
r#"SELECT r.name, r.description, r.schedule_date, r.schedule_end,
r.close_date, r.duration, r.owner_user_id, r.user_id, r.asset_id,
r.stage_id, r.kanban_state::text AS kanban_state,
r.priority::text AS priority, r.maintenance_type::text AS maintenance_type,
r.recurring, r.repeat_interval, r.repeat_unit::text AS repeat_unit,
r.repeat_type::text AS repeat_type, r.repeat_until,
r.successor_request_id, COALESCE(s.done, FALSE) AS stage_done
FROM maintenance.maintenance_requests r
LEFT JOIN maintenance.maintenance_stages s ON s.id = r.stage_id
WHERE r.id = $1 AND (r.metadata->>'deleted_at') IS NULL"#,
)
.bind(request_id),
)
.await?;
Ok(row.map(|r| RequestTransitionRow {
name: r.get("name"),
description: r.get("description"),
schedule_date: r.get("schedule_date"),
schedule_end: r.get("schedule_end"),
close_date: r.get("close_date"),
duration: r.get("duration"),
owner_user_id: r.get("owner_user_id"),
user_id: r.get("user_id"),
asset_id: r.get("asset_id"),
stage_id: r.get("stage_id"),
kanban_state: r.get("kanban_state"),
priority: r.get("priority"),
maintenance_type: r.get("maintenance_type"),
recurring: r.get("recurring"),
repeat_interval: r.get("repeat_interval"),
repeat_unit: r.get("repeat_unit"),
repeat_type: r.get("repeat_type"),
repeat_until: r.get("repeat_until"),
successor_request_id: r.get("successor_request_id"),
stage_done: r.get("stage_done"),
}))
}
pub async fn fetch_stage(
&self,
pool: &PgPool,
stage_id: Uuid,
) -> Result<Option<StageRefRow>, sqlx::Error> {
let row = company_scope::fetch_optional_row_scoped(
pool,
sqlx::query(
r#"SELECT id, name, sequence, done
FROM maintenance.maintenance_stages
WHERE id = $1 AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(stage_id),
)
.await?;
Ok(row.map(|r| StageRefRow {
id: r.get("id"),
name: r.get("name"),
sequence: r.get("sequence"),
done: r.get("done"),
}))
}
pub async fn fetch_first_stage_id(&self, pool: &PgPool) -> Result<Option<Uuid>, sqlx::Error> {
company_scope::fetch_optional_scalar_scoped(
pool,
sqlx::query_scalar(
r#"SELECT id FROM maintenance.maintenance_stages
WHERE (metadata->>'deleted_at') IS NULL
ORDER BY sequence, id LIMIT 1"#,
),
)
.await
}
pub async fn fetch_first_stage_id_on(
&self,
conn: &mut sqlx::PgConnection,
) -> Result<Option<Uuid>, sqlx::Error> {
let row = sqlx::query_scalar::<_, Uuid>(
r#"SELECT id FROM maintenance.maintenance_stages
WHERE (metadata->>'deleted_at') IS NULL
ORDER BY sequence, id LIMIT 1"#,
)
.fetch_optional(conn)
.await?;
Ok(row)
}
pub async fn transition_cas(
&self,
conn: &mut sqlx::PgConnection,
request_id: Uuid,
target_stage_id: Uuid,
kanban_override: Option<&str>,
) -> Result<u64, sqlx::Error> {
let r = match kanban_override {
Some(k) => sqlx::query(
r#"UPDATE maintenance.maintenance_requests
SET stage_id = $2, kanban_state = $3::request_kanban_state
WHERE id = $1 AND stage_id IS DISTINCT FROM $2
AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(request_id)
.bind(target_stage_id)
.bind(k)
.execute(conn)
.await?,
None => sqlx::query(
r#"UPDATE maintenance.maintenance_requests
SET stage_id = $2
WHERE id = $1 AND stage_id IS DISTINCT FROM $2
AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(request_id)
.bind(target_stage_id)
.execute(conn)
.await?,
};
Ok(r.rows_affected())
}
pub async fn claim_successor_slot(
&self,
conn: &mut sqlx::PgConnection,
request_id: Uuid,
successor_id: Uuid,
) -> Result<u64, sqlx::Error> {
let r = sqlx::query(
r#"UPDATE maintenance.maintenance_requests SET successor_request_id = $2
WHERE id = $1 AND successor_request_id IS NULL
AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(request_id)
.bind(successor_id)
.execute(conn)
.await?;
Ok(r.rows_affected())
}
pub async fn update_request_fields(
&self,
pool: &PgPool,
request_id: Uuid,
u: &RequestFieldUpdates<'_>,
) -> Result<u64, sqlx::Error> {
let r = org_scope::execute_scoped(
pool,
sqlx::query(
r#"UPDATE maintenance.maintenance_requests SET
name = COALESCE($2, name),
description = COALESCE($3, description),
schedule_date = COALESCE($4::timestamptz, schedule_date),
schedule_end = COALESCE($5::timestamptz, schedule_end),
duration = COALESCE($6, duration),
owner_user_id = COALESCE($7, owner_user_id),
user_id = COALESCE($8, user_id),
asset_id = COALESCE($9, asset_id),
priority = COALESCE($10::request_priority, priority),
maintenance_type = COALESCE($11::maintenance_type, maintenance_type),
recurring = COALESCE($12, recurring),
repeat_interval = COALESCE($13, repeat_interval),
repeat_unit = COALESCE($14::repeat_unit, repeat_unit),
repeat_type = COALESCE($15::repeat_type, repeat_type),
repeat_until = COALESCE($16, repeat_until)
WHERE id = $1 AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(request_id)
.bind(u.name)
.bind(u.description)
.bind(u.schedule_date)
.bind(u.schedule_end)
.bind(u.duration)
.bind(u.owner_user_id)
.bind(u.user_id)
.bind(u.asset_id)
.bind(u.priority)
.bind(u.maintenance_type)
.bind(u.recurring)
.bind(u.repeat_interval)
.bind(u.repeat_unit)
.bind(u.repeat_type)
.bind(u.repeat_until),
)
.await?;
Ok(r.rows_affected())
}
}
backbone_core::impl_crud_repository!(MaintenanceRequestRepository, MaintenanceRequest, soft_delete);