use anyhow::Result;
use chrono::NaiveDate;
use rust_decimal::Decimal;
use sqlx::{PgPool, Row};
use uuid::Uuid;
use backbone_orm::{company_scope, org_scope};
use crate::domain::entity::MaintenanceVisit;
pub const TABLE_NAME: &str = "maintenance.maintenance_visits";
pub struct MaintenanceVisitRepository(
backbone_orm::GenericCrudRepository<MaintenanceVisit, backbone_orm::SoftDelete>,
);
impl std::ops::Deref for MaintenanceVisitRepository {
type Target = backbone_orm::GenericCrudRepository<MaintenanceVisit, backbone_orm::SoftDelete>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl MaintenanceVisitRepository {
pub fn new(pool: PgPool) -> Self {
Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
}
}
pub struct NewVisitRow<'a> {
pub id: Uuid,
pub asset_id: Uuid,
pub schedule_id: Option<Uuid>,
pub maintenance_type: &'a str,
pub warehouse_id: Option<Uuid>,
pub warranty_claim_id: Option<Uuid>,
pub scheduled_date: NaiveDate,
pub labor_cost: Decimal,
pub maintenance_expense_account_id: Uuid,
pub parts_inventory_account_id: Uuid,
pub labor_payable_account_id: Uuid,
}
pub struct VisitCompletionRow {
pub asset_id: Uuid,
pub status: String,
pub warehouse_id: Option<Uuid>,
pub labor_cost: Decimal,
pub maintenance_expense_account_id: Option<Uuid>,
pub parts_inventory_account_id: Option<Uuid>,
pub labor_payable_account_id: Option<Uuid>,
pub journal_id: Option<Uuid>,
pub total_cost: Decimal,
pub schedule_id: Option<Uuid>,
pub maintenance_type: String,
}
pub struct VisitCompletion {
pub performed_date: NaiveDate,
pub parts_cost: Decimal,
pub total_cost: Decimal,
pub journal_id: Option<Uuid>,
pub accounting_post_id: Option<Uuid>,
}
impl MaintenanceVisitRepository {
pub async fn insert_visit(&self, pool: &PgPool, v: &NewVisitRow<'_>) -> Result<(), sqlx::Error> {
org_scope::execute_scoped(
pool,
sqlx::query(
r#"INSERT INTO maintenance.maintenance_visits
(id, asset_id, schedule_id, maintenance_type, status, warehouse_id,
warranty_claim_id, scheduled_date, labor_cost, parts_cost, total_cost,
maintenance_expense_account_id, parts_inventory_account_id, labor_payable_account_id)
VALUES ($1,$2,$3,$4::maintenance_type,'planned'::visit_status,$5,$6,$7,$8,0,0,$9,$10,$11)"#,
)
.bind(v.id).bind(v.asset_id).bind(v.schedule_id).bind(v.maintenance_type)
.bind(v.warehouse_id).bind(v.warranty_claim_id).bind(v.scheduled_date).bind(v.labor_cost)
.bind(v.maintenance_expense_account_id).bind(v.parts_inventory_account_id)
.bind(v.labor_payable_account_id),
)
.await?;
Ok(())
}
pub async fn fetch_status(
&self,
pool: &PgPool,
visit_id: Uuid,
) -> Result<Option<String>, sqlx::Error> {
company_scope::fetch_optional_scalar_scoped(
pool,
sqlx::query_scalar(
"SELECT status::text FROM maintenance.maintenance_visits WHERE id=$1 AND (metadata->>'deleted_at') IS NULL",
)
.bind(visit_id),
)
.await
}
pub async fn fetch_for_completion(
&self,
pool: &PgPool,
visit_id: Uuid,
) -> Result<Option<VisitCompletionRow>, sqlx::Error> {
let row = company_scope::fetch_optional_row_scoped(
pool,
sqlx::query(
r#"SELECT asset_id, status::text AS status, warehouse_id, labor_cost,
maintenance_expense_account_id, parts_inventory_account_id, labor_payable_account_id,
journal_id, total_cost, schedule_id, maintenance_type::text AS maintenance_type
FROM maintenance.maintenance_visits WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
)
.bind(visit_id),
)
.await?;
Ok(row.map(|r| VisitCompletionRow {
asset_id: r.get("asset_id"),
status: r.get("status"),
warehouse_id: r.get("warehouse_id"),
labor_cost: r.get("labor_cost"),
maintenance_expense_account_id: r.get("maintenance_expense_account_id"),
parts_inventory_account_id: r.get("parts_inventory_account_id"),
labor_payable_account_id: r.get("labor_payable_account_id"),
journal_id: r.get("journal_id"),
total_cost: r.get("total_cost"),
schedule_id: r.get("schedule_id"),
maintenance_type: r.get("maintenance_type"),
}))
}
pub async fn claim_in_progress(
&self,
pool: &PgPool,
visit_id: Uuid,
) -> Result<u64, sqlx::Error> {
let r = org_scope::execute_scoped(
pool,
sqlx::query(
r#"UPDATE maintenance.maintenance_visits SET status='in_progress'::visit_status
WHERE id=$1 AND status='planned'::visit_status"#,
)
.bind(visit_id),
)
.await?;
Ok(r.rows_affected())
}
pub async fn complete(
&self,
conn: &mut sqlx::PgConnection,
visit_id: Uuid,
c: &VisitCompletion,
) -> Result<u64, sqlx::Error> {
let r = sqlx::query(
r#"UPDATE maintenance.maintenance_visits
SET status='completed'::visit_status, performed_date=$2, parts_cost=$3, total_cost=$4,
journal_id=$5, accounting_post_id=$6
WHERE id=$1 AND status IN ('planned'::visit_status,'in_progress'::visit_status)"#,
)
.bind(visit_id).bind(c.performed_date).bind(c.parts_cost).bind(c.total_cost)
.bind(c.journal_id).bind(c.accounting_post_id)
.execute(conn)
.await?;
Ok(r.rows_affected())
}
pub async fn fetch_journal_id(
&self,
pool: &PgPool,
visit_id: Uuid,
) -> Result<Option<Uuid>, sqlx::Error> {
company_scope::fetch_one_scalar_scoped(
pool,
sqlx::query_scalar("SELECT journal_id FROM maintenance.maintenance_visits WHERE id=$1")
.bind(visit_id),
)
.await
}
pub async fn cancel(&self, pool: &PgPool, visit_id: Uuid) -> Result<u64, sqlx::Error> {
let r = org_scope::execute_scoped(
pool,
sqlx::query(
r#"UPDATE maintenance.maintenance_visits SET status='cancelled'::visit_status
WHERE id=$1 AND status IN ('planned'::visit_status,'in_progress'::visit_status)"#,
)
.bind(visit_id),
)
.await?;
Ok(r.rows_affected())
}
}
backbone_core::impl_crud_repository!(MaintenanceVisitRepository, MaintenanceVisit, soft_delete);