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 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}