airlab_lib/model/
conjugate.rs

1use crate::ctx::Ctx;
2use crate::model::ModelManager;
3use crate::model::Result;
4use crate::model::base::{self, DbBmc};
5use modql::field::Fields;
6use modql::filter::{FilterNodes, ListOptions, OpValsInt64, OpValsString};
7use serde::{Deserialize, Serialize};
8use sqlx::FromRow;
9
10impl ConjugateBmc {
11    #[must_use]
12    pub fn get_create_sql(drop_table: bool) -> String {
13        let table = Self::TABLE;
14        format!(
15            r##"{}
16create table if not exists "{table}" (
17  id serial primary key,
18  group_id integer NOT NULL,
19  created_by integer NOT NULL,
20  labeled_by integer,
21  finished_by integer,
22  lot_id integer NOT NULL,
23  tag_id integer NOT NULL,
24  status smallint DEFAULT 0 NOT NULL,
25  tube_number integer NOT NULL,
26  concentration real,
27  description character varying,
28  finished_at timestamp with time zone,
29  is_archived boolean DEFAULT false NOT NULL,
30  custom_id character varying,
31  meta jsonb,
32  created_at timestamp with time zone DEFAULT now() NOT NULL,
33  updated_at timestamp with time zone DEFAULT now() NOT NULL
34);
35CREATE INDEX "IDX_conjugate_created_by" ON conjugate USING btree (created_by);
36CREATE INDEX "IDX_conjugate_group_id" ON conjugate USING btree (group_id);
37CREATE INDEX "IDX_conjugate_lot_id" ON conjugate USING btree (lot_id);
38CREATE INDEX "IDX_conjugate_status" ON conjugate USING btree (status);
39CREATE INDEX "IDX_conjugate_tag_id" ON conjugate USING btree (tag_id);
40CREATE INDEX "IDX_conjugate_tube_number" ON conjugate USING btree (tube_number);
41        "##,
42            if drop_table {
43                format!("drop table if exists {table};")
44            } else {
45                String::new()
46            }
47        )
48    }
49}
50
51#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
52pub struct Conjugate {
53    pub id: i32,
54    #[serde(rename = "groupId")]
55    pub group_id: i32,
56
57    #[serde(rename = "createdBy")]
58    pub created_by: i32,
59    #[serde(rename = "labeledBy")]
60    pub labeled_by: Option<i32>,
61    #[serde(rename = "finishedBy")]
62    pub finished_by: Option<i32>,
63    #[serde(rename = "lotId")]
64    pub lot_id: i32,
65    #[serde(rename = "tagId")]
66    pub tag_id: i32,
67    pub status: i16,
68    #[serde(rename = "tubeNumber")]
69    pub tube_number: i32,
70    pub concentration: Option<f32>,
71    pub description: Option<String>,
72    #[serde(rename = "finishedAt")]
73    pub finished_at: Option<chrono::DateTime<chrono::Utc>>,
74    #[serde(rename = "isArchived")]
75    pub is_archived: Option<bool>,
76    #[serde(rename = "customId")]
77    pub custom_id: Option<String>,
78    pub meta: Option<serde_json::Value>,
79    #[serde(rename = "createdAt")]
80    pub created_at: chrono::DateTime<chrono::Utc>,
81    #[serde(rename = "updatedAt")]
82    pub updated_at: chrono::DateTime<chrono::Utc>,
83}
84
85#[derive(Fields, Deserialize, Clone, Debug)]
86pub struct ConjugateForCreate {
87    #[serde(rename = "groupId")]
88    pub group_id: i32,
89    #[serde(rename = "createdBy")]
90    pub created_by: Option<i32>,
91    #[serde(rename = "labeledBy")]
92    pub labeled_by: Option<i32>,
93    #[serde(rename = "finishedBy")]
94    pub finished_by: Option<i32>,
95    #[serde(rename = "lotId")]
96    pub lot_id: i32,
97    #[serde(rename = "tagId")]
98    pub tag_id: i32,
99    pub status: Option<i16>,
100    pub concentration: Option<f32>,
101    pub description: Option<String>,
102    #[serde(rename = "finishedAt")]
103    pub finished_at: Option<chrono::DateTime<chrono::Utc>>,
104    #[serde(rename = "isArchived")]
105    pub is_archived: Option<bool>,
106    #[serde(rename = "customId")]
107    pub custom_id: Option<String>,
108}
109
110#[derive(Fields, Default, Deserialize, Debug)]
111pub struct ConjugateForUpdate {
112    #[serde(rename = "labeledBy")]
113    pub labeled_by: Option<i32>,
114    #[serde(rename = "finishedBy")]
115    pub finished_by: Option<i32>,
116    #[serde(rename = "lotId")]
117    pub lot_id: Option<i32>,
118    #[serde(rename = "tagId")]
119    pub tag_id: Option<i32>,
120    pub status: Option<i16>,
121    #[serde(rename = "tubeNumber")]
122    pub tube_number: Option<i32>,
123    pub concentration: Option<f32>,
124    pub description: Option<String>,
125    #[serde(rename = "finishedAt")]
126    pub finished_at: Option<chrono::DateTime<chrono::Utc>>,
127    #[serde(rename = "isArchived")]
128    pub is_archived: Option<bool>,
129    #[serde(rename = "customId")]
130    pub custom_id: Option<String>,
131}
132
133#[derive(FilterNodes, Deserialize, Default, Debug)]
134pub struct ConjugateFilter {
135    id: Option<OpValsInt64>,
136    lot_id: Option<OpValsInt64>,
137    group_id: Option<OpValsInt64>,
138
139    description: Option<OpValsString>,
140}
141
142pub struct ConjugateBmc;
143
144impl DbBmc for ConjugateBmc {
145    const TABLE: &'static str = "conjugate";
146}
147
148impl ConjugateBmc {
149    pub async fn create(
150        ctx: &Ctx,
151        mm: &ModelManager,
152        conjugate_c: ConjugateForCreate,
153    ) -> Result<i32> {
154        base::create::<Self, _>(ctx, mm, conjugate_c).await
155    }
156    pub async fn create_full(ctx: &Ctx, mm: &ModelManager, conjugate_c: Conjugate) -> Result<i32> {
157        base::create::<Self, _>(ctx, mm, conjugate_c).await
158    }
159
160    pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<Conjugate> {
161        base::get::<Self, _>(ctx, mm, id).await
162    }
163
164    pub async fn list(
165        ctx: &Ctx,
166        mm: &ModelManager,
167        filters: Option<Vec<ConjugateFilter>>,
168        list_options: Option<ListOptions>,
169    ) -> Result<Vec<Conjugate>> {
170        base::list::<Self, _, _>(ctx, mm, filters, list_options).await
171    }
172
173    pub async fn update(
174        ctx: &Ctx,
175        mm: &ModelManager,
176        id: i32,
177        conjugate_u: ConjugateForUpdate,
178    ) -> Result<()> {
179        base::update::<Self, _>(ctx, mm, id, conjugate_u).await
180    }
181
182    pub async fn delete(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<()> {
183        base::delete::<Self>(ctx, mm, id).await
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::_dev_utils;
191    use crate::model::Error;
192    use anyhow::Result;
193    use serde_json::json;
194
195    #[ignore]
196    #[tokio::test]
197    async fn test_conjugate_create_ok() -> Result<()> {
198        let mm = ModelManager::new().await?;
199        let ctx = Ctx::root_ctx();
200        let fx_description = "test_create_ok description";
201
202        let conjugate_c = ConjugateForCreate {
203            description: Some(fx_description.to_string()),
204            group_id: 1,
205            created_by: Some(261),
206            labeled_by: None,
207            finished_by: None,
208            lot_id: 5495,
209            status: Some(1),
210            tag_id: 211,
211            concentration: None,
212            finished_at: None,
213            is_archived: None,
214            custom_id: None,
215        };
216        let id = ConjugateBmc::create(&ctx, &mm, conjugate_c).await?;
217
218        let conjugate = ConjugateBmc::get(&ctx, &mm, id).await?;
219        assert_eq!(conjugate.description.unwrap_or("".into()), fx_description);
220
221        ConjugateBmc::delete(&ctx, &mm, id).await?;
222
223        Ok(())
224    }
225
226    #[ignore]
227    #[tokio::test]
228    async fn test_conjugate_get_err_not_found() -> Result<()> {
229        let mm = ModelManager::new().await?;
230        let ctx = Ctx::root_ctx();
231        let fx_id = 100;
232
233        let res = ConjugateBmc::get(&ctx, &mm, fx_id).await;
234
235        assert!(
236            matches!(
237                res,
238                Err(Error::EntityNotFound {
239                    entity: "conjugate",
240                    id: 100
241                })
242            ),
243            "EntityNotFound not matching"
244        );
245
246        Ok(())
247    }
248
249    #[ignore]
250    #[tokio::test]
251    async fn test_conjugate_list_all_ok() -> Result<()> {
252        let mm = ModelManager::new().await?;
253        let ctx = Ctx::root_ctx();
254        let tname = "test_conjugate_list_all_ok";
255        let seeds = _dev_utils::get_conjugate_seed(tname);
256        _dev_utils::seed_conjugates(&ctx, &mm, &seeds).await?;
257
258        let conjugates = ConjugateBmc::list(&ctx, &mm, None, None).await?;
259
260        let conjugates: Vec<Conjugate> = conjugates.into_iter().collect();
261        assert_eq!(conjugates.len(), 4, "number of seeded conjugates.");
262
263        for conjugate in conjugates.iter() {
264            ConjugateBmc::delete(&ctx, &mm, conjugate.id).await?;
265        }
266
267        Ok(())
268    }
269
270    #[ignore]
271    #[tokio::test]
272    async fn test_conjugate_list_by_filter_ok() -> Result<()> {
273        let mm = _dev_utils::init_test().await;
274        let ctx = Ctx::root_ctx();
275        let tname = "test_conjugate_list_by_filter_ok";
276        let seeds = _dev_utils::get_conjugate_seed(tname);
277        _dev_utils::seed_conjugates(&ctx, &mm, &seeds).await?;
278
279        let filters: Vec<ConjugateFilter> = serde_json::from_value(json!([
280            {
281                "description": {
282                    "$endsWith": ".a",
283                    "$containsAny": ["01", "02"]
284                }
285            },
286            {
287                "description": {"$contains": "03"}
288            }
289        ]))?;
290        let list_options = serde_json::from_value(json!({
291            "order_bys": "!id"
292        }))?;
293        let conjugates = ConjugateBmc::list(&ctx, &mm, Some(filters), Some(list_options)).await?;
294
295        assert_eq!(conjugates.len(), 3);
296        //assert!(conjugates[0].description.unwrap().ends_with("03"));
297
298        let conjugates = ConjugateBmc::list(
299            &ctx,
300            &mm,
301            Some(serde_json::from_value(json!([{
302                "description": {"$startsWith": "test_list_by_filter_ok"}
303            }]))?),
304            None,
305        )
306        .await?;
307        assert_eq!(conjugates.len(), 5);
308        for conjugate in conjugates.iter() {
309            ConjugateBmc::delete(&ctx, &mm, conjugate.id).await?;
310        }
311
312        Ok(())
313    }
314
315    #[ignore]
316    #[tokio::test]
317    async fn test_conjugate_update_ok() -> Result<()> {
318        let mm = _dev_utils::init_test().await;
319        let ctx = Ctx::root_ctx();
320        let tname = "test_conjugate_update_ok";
321        let seeds = _dev_utils::get_conjugate_seed(tname);
322        let fx_conjugate = _dev_utils::seed_conjugates(&ctx, &mm, &seeds)
323            .await?
324            .remove(0);
325
326        ConjugateBmc::update(
327            &ctx,
328            &mm,
329            fx_conjugate.id,
330            ConjugateForUpdate {
331                description: Some(tname.to_string()),
332                ..Default::default()
333            },
334        )
335        .await?;
336
337        let conjugate = ConjugateBmc::get(&ctx, &mm, fx_conjugate.id).await?;
338        assert_eq!(conjugate.description, Some(tname.into()));
339
340        Ok(())
341    }
342
343    #[ignore]
344    #[tokio::test]
345    async fn test_conjugate_delete_err_not_found() -> Result<()> {
346        let mm = _dev_utils::init_test().await;
347        let ctx = Ctx::root_ctx();
348        let fx_id = 100;
349
350        let res = ConjugateBmc::delete(&ctx, &mm, fx_id).await;
351
352        assert!(
353            matches!(
354                res,
355                Err(Error::EntityNotFound {
356                    entity: "conjugate",
357                    id: 100
358                })
359            ),
360            "EntityNotFound not matching"
361        );
362
363        Ok(())
364    }
365}