airlab-lib 0.1.3

airlab backend
Documentation
use crate::ctx::Ctx;
use crate::model::ModelManager;
use crate::model::Result;
use crate::model::clone::CloneFilter;
use crate::model::lot::{Lot, LotBmc, LotFilter};
use crate::model::provider::{Provider, ProviderBmc, ProviderFilter};
use crate::model::validation::Validation;
use crate::model::validation::{ValidationBmc, ValidationFilter};
use crate::model::view_clone::{ViewClone, ViewCloneBmc};
use modql::field::Fields;
use modql::filter::ListOptions;
use serde::{Deserialize, Serialize};
use serde_json::json;
use sqlx::FromRow;
use std::collections::{HashMap, hash_map::Entry};

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ViewLot {
    pub id: i32,
    #[serde(rename = "createdBy")]
    pub created_by: Option<i32>,
    pub storage: String,
    #[serde(rename = "cloneId")]
    pub clone_id: i32,
    #[serde(rename = "providerId")]
    pub provider_id: Option<i32>,
    pub name: String,
    pub reference: Option<String>,
    #[serde(rename = "requestedBy")]
    pub requested_by: Option<i32>,
    #[serde(rename = "approvedBy")]
    pub approved_by: Option<i32>,
    #[serde(rename = "orderedBy")]
    pub ordered_by: Option<i32>,
    #[serde(rename = "receivedBy")]
    pub received_by: Option<i32>,
    #[serde(rename = "finishedBy")]
    pub finished_by: Option<i32>,
    pub status: Option<i16>,
    pub purpose: Option<String>,
    pub number: Option<String>,
    pub url: Option<String>,
    pub price: Option<String>,
    pub note: Option<String>,
    #[serde(rename = "requestedAt")]
    pub requested_at: Option<chrono::DateTime<chrono::Utc>>,
    #[serde(rename = "orderedAt")]
    pub ordered_at: Option<chrono::DateTime<chrono::Utc>>,
    #[serde(rename = "receivedAt")]
    pub received_at: Option<chrono::DateTime<chrono::Utc>>,
    #[serde(rename = "finishedAt")]
    pub finished_at: Option<chrono::DateTime<chrono::Utc>>,
    pub clone: ViewClone,
    pub validations: Vec<Validation>,
    pub provider: Option<Provider>,
}

#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
pub struct ViewLotDetails {
    pub id: i32,
    #[serde(rename = "groupId")]
    pub group_id: i32,
    #[serde(rename = "createdBy")]
    pub created_by: i32,
    #[serde(rename = "cloneId")]
    pub clone_id: i32,
    #[serde(rename = "providerId")]
    pub provider_id: Option<i32>,
    pub name: Option<String>,
    pub reference: Option<String>,
    #[serde(rename = "requestedBy")]
    pub requested_by: Option<i32>,
    #[serde(rename = "approvedBy")]
    pub approved_by: Option<i32>,
    #[serde(rename = "orderedBy")]
    pub ordered_by: Option<i32>,
    #[serde(rename = "receivedBy")]
    pub received_by: Option<i32>,
    #[serde(rename = "finishedBy")]
    pub finished_by: Option<i32>,
    pub number: Option<String>,
    pub status: Option<i16>,
    pub purpose: Option<String>,
    pub url: Option<String>,
    pub price: Option<String>,
    pub note: Option<String>,
    #[serde(rename = "requestedAt")]
    pub requested_at: Option<chrono::DateTime<chrono::Utc>>,
    #[serde(rename = "orderedAt")]
    pub ordered_at: Option<chrono::DateTime<chrono::Utc>>,
    #[serde(rename = "receivedAt")]
    pub received_at: Option<chrono::DateTime<chrono::Utc>>,
    #[serde(rename = "finishedAt")]
    pub finished_at: Option<chrono::DateTime<chrono::Utc>>,
    pub meta: Option<serde_json::Value>,
    pub clone: Option<serde_json::Value>,
    pub provider: Option<serde_json::Value>,
    #[serde(rename = "requestedByUser")]
    pub requested_by_user: Option<serde_json::Value>,
    #[serde(rename = "approvedByUser")]
    pub approved_by_user: Option<serde_json::Value>,
    #[serde(rename = "orderedByUser")]
    pub ordered_by_user: Option<serde_json::Value>,
    #[serde(rename = "receivedByUser")]
    pub received_by_user: Option<serde_json::Value>,
    #[serde(rename = "finishedByUser")]
    pub finished_by_user: Option<serde_json::Value>,
}

pub struct ViewLotBmc;

impl ViewLotBmc {
    pub async fn get_details(_ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<ViewLotDetails> {
        let stmt = r#"SELECT
  lot.id,
  lot.group_id,
  lot.created_by,
  lot.clone_id,
  lot.provider_id,
  lot.name,
  lot.reference,
  lot.requested_by,
  lot.approved_by,
  lot.ordered_by,
  lot.received_by,
  lot.finished_by,
  lot.number,
  lot.status,
  lot.purpose,
  lot.url,
  lot.price,
  lot.note,
  lot.requested_at,
  lot.ordered_at,
  lot.received_at,
  lot.finished_at,
  lot.meta,
  jsonb_build_object(
    'id', reu.id,
    'name', reu.name,
    'isAdmin', reu.is_admin
  ) AS requested_by_user,
  jsonb_build_object(
    'id', apu.id,
    'name', apu.name,
    'isAdmin', apu.is_admin
  ) AS approved_by_user,
  jsonb_build_object(
    'id', oru.id,
    'name', oru.name,
    'isAdmin', oru.is_admin
  ) AS ordered_by_user,
  jsonb_build_object(
    'id', rcu.id,
    'name', rcu.name,
    'isAdmin', rcu.is_admin
  ) AS received_by_user,
  jsonb_build_object(
    'id', fiu.id,
    'name', fiu.name,
    'isAdmin', fiu.is_admin
  ) AS finished_by_user,
  jsonb_build_object(
    'id', cl.id,
    'name', cl.name
  ) AS clone,
  jsonb_build_object(
    'id', pv.id,
    'name', pv.name
  ) AS provider
FROM
  lot
LEFT JOIN 
    member rem ON lot.requested_by = rem.id
LEFT JOIN 
    "user" reu ON rem.user_id = reu.id
LEFT JOIN 
    member apm ON lot.approved_by = apm.id
LEFT JOIN 
    "user" apu ON apm.user_id = apu.id
LEFT JOIN 
    member orm ON lot.ordered_by = orm.id
LEFT JOIN 
    "user" oru ON orm.user_id = oru.id
LEFT JOIN 
    member rcm ON lot.received_by = rcm.id
LEFT JOIN 
    "user" rcu ON rcm.user_id = rcu.id
LEFT JOIN 
    member fim ON lot.finished_by = fim.id
LEFT JOIN 
    "user" fiu ON fim.user_id = fiu.id
LEFT JOIN 
    clone cl ON lot.clone_id = cl.id
LEFT JOIN 
    provider pv ON lot.provider_id = pv.id
WHERE
  lot.id = $1"#;
        let item: ViewLotDetails = sqlx::query_as::<_, ViewLotDetails>(stmt)
            .bind(id)
            .fetch_one(&mm.db)
            .await?;
        Ok(item)
    }
    pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<ViewLot> {
        let item = LotBmc::get(ctx, mm, id).await?;
        let clone = ViewCloneBmc::get(ctx, mm, item.clone_id).await?;
        let provider = match item.provider_id {
            Some(pid) => Some(ProviderBmc::get(ctx, mm, pid).await?),
            None => None,
        };
        let ret = ViewLot {
            id: item.id,
            created_by: item.created_by,
            storage: "storage_const".into(),
            clone_id: item.clone_id,
            provider_id: item.provider_id,
            name: item.name,
            reference: item.reference,
            requested_by: item.requested_by,
            approved_by: item.approved_by,
            ordered_by: item.ordered_by,
            received_by: item.received_by,
            finished_by: item.finished_by,
            status: item.status,
            purpose: item.purpose,
            number: item.number,
            url: item.url,
            price: item.price,
            note: item.note,
            requested_at: item.requested_at,
            ordered_at: item.ordered_at,
            received_at: item.received_at,
            finished_at: item.finished_at,
            clone,
            validations: vec![],
            provider,
        };
        Ok(ret)
    }
    pub async fn list(
        ctx: &Ctx,
        mm: &ModelManager,
        group_id: Option<i32>,
        filters: Option<Vec<LotFilter>>,
        list_options: Option<ListOptions>,
    ) -> Result<Vec<ViewLot>> {
        let mut clone_map = HashMap::new();
        let mut provider_map = HashMap::new();
        let mut validation_map = HashMap::new();
        if let Some(group_id) = group_id {
            let clone_filters: Option<Vec<CloneFilter>> = match serde_json::from_value(json!([
                {
                    "group_id": {"$eq":group_id}
                }
            ])) {
                Ok(ok) => Some(ok),
                Err(_) => None,
            };
            let op = ListOptions {
                limit: Some(10_000),
                ..Default::default()
            };
            let clones: Vec<ViewClone> =
                ViewCloneBmc::list(ctx, mm, Some(group_id), clone_filters, Some(op)).await?;
            for clone in clones {
                clone_map.insert(clone.id, clone);
            }

            let provider_filters: Option<Vec<ProviderFilter>> =
                match serde_json::from_value(json!([
                    {
                        "group_id": {"$eq":group_id}
                    }
                ])) {
                    Ok(ok) => Some(ok),
                    Err(_) => None,
                };
            let providers: Vec<Provider> =
                ProviderBmc::list(ctx, mm, provider_filters, None).await?;
            for provider in providers {
                provider_map.insert(provider.id, provider);
            }
            let validation_op = ListOptions {
                limit: Some(100_000),
                ..Default::default()
            };

            let validation_filters: Option<Vec<ValidationFilter>> =
                match serde_json::from_value(json!([
                    {
                        "group_id": {"$eq":group_id}
                    }
                ])) {
                    Ok(ok) => Some(ok),
                    Err(_) => None,
                };
            let validations: Vec<Validation> =
                ValidationBmc::list(ctx, mm, validation_filters, Some(validation_op)).await?;
            for validation in validations {
                let h = match validation_map.entry(validation.lot_id.unwrap_or(0)) {
                    Entry::Occupied(o) => o.into_mut(),
                    Entry::Vacant(v) => v.insert(vec![]),
                };
                h.push(validation);
            }
        }

        let lots: Vec<Lot> = LotBmc::list(ctx, mm, filters, list_options).await?;
        let mut returns = vec![];
        for item in lots {
            let clone = match clone_map.get(&{ item.clone_id }) {
                Some(v) => v.clone(),
                None => ViewClone::default(),
            };
            let mut provider = None;

            if let Some(provider_id) = &item.provider_id {
                provider = provider_map.get(provider_id).cloned();
            }

            returns.push(ViewLot {
                id: item.id,
                storage: "storage_const".into(),
                created_by: item.created_by,
                clone_id: item.clone_id,
                provider_id: item.provider_id,
                name: item.name,
                reference: item.reference,
                requested_by: item.requested_by,
                approved_by: item.approved_by,
                ordered_by: item.ordered_by,
                received_by: item.received_by,
                finished_by: item.finished_by,
                status: item.status,
                purpose: item.purpose,
                number: item.number,
                url: item.url,
                price: item.price,
                note: item.note,
                requested_at: item.requested_at,
                ordered_at: item.ordered_at,
                received_at: item.received_at,
                finished_at: item.finished_at,
                clone,
                validations: validation_map.get(&item.id).unwrap_or(&vec![]).clone(),
                provider,
            });
        }

        Ok(returns)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use anyhow::Result;

    #[ignore]
    #[tokio::test]
    async fn test_view_lot_list_all_ok() -> Result<()> {
        let mm = ModelManager::new().await?;
        let ctx = Ctx::root_ctx();

        let lots = ViewLotBmc::list(&ctx, &mm, Some(1000), None, None).await?;

        let _lots: Vec<ViewLot> = lots
            .into_iter()
            .filter(|t| t.name.starts_with("test_list_all_ok-lot"))
            .collect();

        Ok(())
    }
}