1use crate::ctx::Ctx;
2use crate::model::ModelManager;
3use crate::model::Result;
4use crate::model::base::{self, DbBmc};
5use modql::field::Fields;
7use modql::filter::{FilterNodes, ListOptions, OpValsInt64, OpValsString};
8use serde::{Deserialize, Serialize};
9use sqlx::FromRow;
10
11impl ValidationBmc {
12 #[must_use]
13 pub fn get_create_sql(drop_table: bool) -> String {
14 let table = Self::TABLE;
15 format!(
16 r##"{}
17create table if not exists "{table}" (
18 id serial primary key,
19 group_id integer NOT NULL,
20 created_by integer NOT NULL,
21 clone_id integer NOT NULL,
22 lot_id integer,
23 conjugate_id integer,
24 species_id integer,
25 application integer NOT NULL,
26 positive_control character varying,
27 negative_control character varying,
28 incubation_conditions character varying,
29 concentration character varying,
30 concentration_unit character varying,
31 tissue character varying,
32 fixation integer,
33 fixation_notes character varying,
34 notes character varying,
35 status integer DEFAULT 3 NOT NULL,
36 antigen_retrieval_type character varying,
37 antigen_retrieval_time character varying,
38 antigen_retrieval_temperature character varying,
39 saponin boolean,
40 saponin_concentration character varying,
41 methanol_treatment boolean,
42 methanol_treatment_concentration character varying,
43 surface_staining boolean,
44 surface_staining_concentration character varying,
45 meta jsonb,
46 file_id integer,
47 is_archived boolean DEFAULT false NOT NULL,
48 created_at timestamp with time zone DEFAULT now() NOT NULL,
49 updated_at timestamp with time zone DEFAULT now() NOT NULL
50);
51CREATE INDEX "IDX_validation_application" ON validation USING btree (application);
52CREATE INDEX "IDX_validation_clone_id" ON validation USING btree (clone_id);
53CREATE INDEX "IDX_validation_conjugate_id" ON validation USING btree (conjugate_id);
54CREATE INDEX "IDX_validation_created_by" ON validation USING btree (created_by);
55CREATE INDEX "IDX_validation_group_id" ON validation USING btree (group_id);
56CREATE INDEX "IDX_validation_lot_id" ON validation USING btree (lot_id);
57CREATE INDEX "IDX_validation_species_id" ON validation USING btree (species_id);
58CREATE INDEX "IDX_validation_status" ON validation USING btree (status);
59 "##,
60 if drop_table {
61 format!("drop table if exists {table};")
62 } else {
63 String::new()
64 }
65 )
66 }
67}
68
69#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
70pub struct MinValidation {
71 pub id: i32,
72 #[serde(rename = "cloneId")]
73 pub clone_id: i32,
74 pub application: i32,
75 pub status: i32,
76}
77
78#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
79pub struct Validation {
80 pub id: i32,
81 #[serde(rename = "groupId")]
82 pub group_id: i32,
83
84 #[serde(rename = "createdBy")]
85 pub created_by: i32,
86 #[serde(rename = "cloneId")]
87 pub clone_id: i32,
88 #[serde(rename = "lotId")]
89 pub lot_id: Option<i32>,
90 #[serde(rename = "conjugateId")]
91 pub conjugate_id: Option<i32>,
92 #[serde(rename = "speciesId")]
93 pub species_id: Option<i32>,
94 pub application: i32,
95 #[serde(rename = "positiveControl")]
96 pub positive_control: Option<String>,
97 #[serde(rename = "negativeControl")]
98 pub negative_control: Option<String>,
99 #[serde(rename = "incubationConditions")]
100 pub incubation_conditions: Option<String>,
101 pub concentration: Option<String>,
102 #[serde(rename = "concentrationUnit")]
103 pub concentration_unit: Option<String>,
104 pub tissue: Option<String>,
105 pub fixation: Option<i32>,
106 #[serde(rename = "fixationNotes")]
107 pub fixation_notes: Option<String>,
108 pub notes: Option<String>,
109 pub status: i32,
110 #[serde(rename = "antigenRetrievalType")]
111 pub antigen_retrieval_type: Option<String>,
112 #[serde(rename = "antigenRetrievalTime")]
113 pub antigen_retrieval_time: Option<String>,
114 #[serde(rename = "antigenRetrievalTemperature")]
115 pub antigen_retrieval_temperature: Option<String>,
116 pub saponin: Option<bool>,
117 #[serde(rename = "saponinConcentration")]
118 pub saponin_concentration: Option<String>,
119 #[serde(rename = "saponinTreatment")]
120 pub methanol_treatment: Option<bool>,
121 #[serde(rename = "methanolTreatmentConcentration")]
122 pub methanol_treatment_concentration: Option<String>,
123 #[serde(rename = "methanolStaining")]
124 pub surface_staining: Option<bool>,
125 #[serde(rename = "surfaceStainingConcentration")]
126 pub surface_staining_concentration: Option<String>,
127 pub meta: Option<serde_json::Value>,
128 #[serde(rename = "fileId")]
129 pub file_id: Option<i32>,
130 #[serde(rename = "isArchived")]
131 pub is_archived: bool,
132 #[serde(rename = "createdAt")]
133 pub created_at: chrono::DateTime<chrono::Utc>,
134 #[serde(rename = "updatedAt")]
135 pub updated_at: chrono::DateTime<chrono::Utc>,
136}
137
138#[derive(Fields, Deserialize, Clone, Debug)]
139pub struct ValidationForCreate {
140 #[serde(rename = "groupId")]
141 pub group_id: i32,
142 #[serde(rename = "createdBy")]
143 pub created_by: Option<i32>,
144 #[serde(rename = "cloneId")]
145 pub clone_id: i32,
146 #[serde(rename = "lotId")]
147 pub lot_id: Option<i32>,
148 #[serde(rename = "conjugateId")]
149 pub conjugate_id: Option<i32>,
150 #[serde(rename = "speciesId")]
151 pub species_id: Option<i32>,
152 pub application: Option<i32>,
153 #[serde(rename = "positiveControl")]
154 pub positive_control: Option<String>,
155 #[serde(rename = "negativeControl")]
156 pub negative_control: Option<String>,
157 #[serde(rename = "incubationConditions")]
158 pub incubation_conditions: Option<String>,
159 pub concentration: Option<String>,
160 #[serde(rename = "concentrationUnit")]
161 pub concentration_unit: Option<String>,
162 pub tissue: Option<String>,
163 pub fixation: Option<i32>,
164 #[serde(rename = "fixationNotes")]
165 pub fixation_notes: Option<String>,
166 pub notes: Option<String>,
167 pub status: Option<i32>,
168 #[serde(rename = "antigenRetrievalType")]
169 pub antigen_retrieval_type: Option<String>,
170 #[serde(rename = "antigenRetrievalTime")]
171 pub antigen_retrieval_time: Option<String>,
172 #[serde(rename = "antigenRetrievalTemperature")]
173 pub antigen_retrieval_temperature: Option<String>,
174 pub saponin: Option<bool>,
175 #[serde(rename = "saponinConcentration")]
176 pub saponin_concentration: Option<String>,
177 #[serde(rename = "saponinTreatment")]
178 pub methanol_treatment: Option<bool>,
179 #[serde(rename = "methanolTreatmentConcentration")]
180 pub methanol_treatment_concentration: Option<String>,
181 #[serde(rename = "methanolStaining")]
182 pub surface_staining: Option<bool>,
183 #[serde(rename = "surfaceStainingConcentration")]
184 pub surface_staining_concentration: Option<String>,
185 #[serde(rename = "fileId")]
187 pub file_id: Option<i32>,
188 #[serde(rename = "isArchived")]
189 pub is_archived: Option<bool>,
190 #[serde(rename = "createdAt")]
191 pub created_at: Option<chrono::DateTime<chrono::Utc>>,
192 #[serde(rename = "updatedAt")]
193 pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
194}
195
196#[derive(Fields, Default, Deserialize, Debug)]
197pub struct ValidationForUpdate {
198 pub application: i32,
199 #[serde(rename = "positiveControl")]
200 pub positive_control: Option<String>,
201 #[serde(rename = "negativeControl")]
202 pub negative_control: Option<String>,
203 #[serde(rename = "incubationConditions")]
204 pub incubation_conditions: Option<String>,
205 pub concentration: Option<String>,
206 #[serde(rename = "concentrationUnit")]
207 pub concentration_unit: Option<String>,
208 pub tissue: Option<String>,
209 pub fixation: Option<i32>,
210 #[serde(rename = "fixationNotes")]
211 pub fixation_notes: Option<String>,
212 pub notes: Option<String>,
213 pub status: i32,
214 #[serde(rename = "antigenRetrievalType")]
215 pub antigen_retrieval_type: Option<String>,
216 #[serde(rename = "antigenRetrievalTime")]
217 pub antigen_retrieval_time: Option<String>,
218 #[serde(rename = "antigenRetrievalTemperature")]
219 pub antigen_retrieval_temperature: Option<String>,
220 pub saponin: Option<bool>,
221 #[serde(rename = "saponinConcentration")]
222 pub saponin_concentration: Option<String>,
223 #[serde(rename = "saponinTreatment")]
224 pub methanol_treatment: Option<bool>,
225 #[serde(rename = "methanolTreatmentConcentration")]
226 pub methanol_treatment_concentration: Option<String>,
227 #[serde(rename = "methanolStaining")]
228 pub surface_staining: Option<bool>,
229 #[serde(rename = "surfaceStainingConcentration")]
230 pub surface_staining_concentration: Option<String>,
231 #[serde(rename = "fileId")]
233 pub file_id: Option<i32>,
234 #[serde(rename = "isArchived")]
235 pub is_archived: Option<bool>,
236}
237
238#[derive(FilterNodes, Deserialize, Default, Debug)]
239pub struct ValidationFilter {
240 id: Option<OpValsInt64>,
241 group_id: Option<OpValsInt64>,
242
243 tissue: Option<OpValsString>,
244}
245
246pub struct ValidationBmc;
247
248impl DbBmc for ValidationBmc {
249 const TABLE: &'static str = "validation";
250}
251
252impl ValidationBmc {
253 pub async fn create(
254 ctx: &Ctx,
255 mm: &ModelManager,
256 validation_c: ValidationForCreate,
257 ) -> Result<i32> {
258 base::create::<Self, _>(ctx, mm, validation_c).await
259 }
260 pub async fn create_full(
261 ctx: &Ctx,
262 mm: &ModelManager,
263 validation_c: Validation,
264 ) -> Result<i32> {
265 base::create::<Self, _>(ctx, mm, validation_c).await
266 }
267
268 pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<Validation> {
269 base::get::<Self, _>(ctx, mm, id).await
270 }
271
272 pub async fn list(
273 ctx: &Ctx,
274 mm: &ModelManager,
275 filters: Option<Vec<ValidationFilter>>,
276 list_options: Option<ListOptions>,
277 ) -> Result<Vec<Validation>> {
278 base::list::<Self, _, _>(ctx, mm, filters, list_options).await
279 }
280
281 pub async fn minlist(
282 ctx: &Ctx,
283 mm: &ModelManager,
284 filters: Option<Vec<ValidationFilter>>,
285 list_options: Option<ListOptions>,
286 ) -> Result<Vec<MinValidation>> {
287 base::list::<Self, _, _>(ctx, mm, filters, list_options).await
288 }
289
290 pub async fn update(
291 ctx: &Ctx,
292 mm: &ModelManager,
293 id: i32,
294 validation_u: ValidationForUpdate,
295 ) -> Result<()> {
296 base::update::<Self, _>(ctx, mm, id, validation_u).await
297 }
298
299 pub async fn delete(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<()> {
300 base::delete::<Self>(ctx, mm, id).await
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::_dev_utils;
308 use crate::model::Error;
309 use anyhow::Result;
310 use serde_json::json;
311
312 #[ignore]
313 #[tokio::test]
314 async fn test_validation_create_ok() -> Result<()> {
315 let mm = ModelManager::new().await?;
316 let ctx = Ctx::root_ctx();
317 let validation_c = ValidationForCreate {
318 group_id: 1,
319 created_by: Some(261),
320 clone_id: 3124,
321 lot_id: Some(5495),
322 conjugate_id: Some(4291),
323 species_id: Some(44),
324 application: Some(1),
325 positive_control: None,
326 negative_control: None,
327 incubation_conditions: None,
328 concentration: None,
329 concentration_unit: None,
330 tissue: None,
331 fixation: None,
332 fixation_notes: None,
333 notes: None,
334 antigen_retrieval_type: None,
335 antigen_retrieval_time: None,
336 antigen_retrieval_temperature: None,
337 status: Some(1),
338 saponin: Some(false),
339 saponin_concentration: None,
340 methanol_treatment: Some(false),
341 methanol_treatment_concentration: None,
342 surface_staining: Some(false),
343 surface_staining_concentration: None,
344 file_id: Some(2909),
345 is_archived: Some(false),
346 created_at: Some(chrono::offset::Utc::now()),
347 updated_at: Some(chrono::offset::Utc::now()),
348 };
349 let id = ValidationBmc::create(&ctx, &mm, validation_c).await?;
350
351 let validation = ValidationBmc::get(&ctx, &mm, id).await?;
352 assert_eq!(validation.id, 3);
353
354 ValidationBmc::delete(&ctx, &mm, id).await?;
355
356 Ok(())
357 }
358
359 #[ignore]
360 #[tokio::test]
361 async fn test_validation_get_err_not_found() -> Result<()> {
362 let mm = ModelManager::new().await?;
363 let ctx = Ctx::root_ctx();
364 let fx_id = 100;
365
366 let res = ValidationBmc::get(&ctx, &mm, fx_id).await;
367
368 assert!(
369 matches!(
370 res,
371 Err(Error::EntityNotFound {
372 entity: "validation",
373 id: 100
374 })
375 ),
376 "EntityNotFound not matching"
377 );
378
379 Ok(())
380 }
381
382 #[ignore]
383 #[tokio::test]
384 async fn test_validation_list_all_ok() -> Result<()> {
385 let mm = ModelManager::new().await?;
386 let ctx = Ctx::root_ctx();
387 let tname = "test_validation_list_all_ok";
388 let seeds = _dev_utils::get_validation_seed(tname);
389 _dev_utils::seed_validations(&ctx, &mm, &seeds).await?;
390
391 let validations = ValidationBmc::list(&ctx, &mm, None, None).await?;
392
393 let validations: Vec<Validation> = validations.into_iter().filter(|t| t.id == 1).collect();
394 assert_eq!(validations.len(), 4, "number of seeded validations.");
395
396 for validation in validations.iter() {
397 ValidationBmc::delete(&ctx, &mm, validation.id).await?;
398 }
399
400 Ok(())
401 }
402
403 #[ignore]
404 #[tokio::test]
405 async fn test_validation_list_by_filter_ok() -> Result<()> {
406 let mm = ModelManager::new().await?;
407 let ctx = Ctx::root_ctx();
408 let tname = "test_validation_list_by_filter_ok";
409 let seeds = _dev_utils::get_validation_seed(tname);
410 _dev_utils::seed_validations(&ctx, &mm, &seeds).await?;
411
412 let filters: Vec<ValidationFilter> = serde_json::from_value(json!([
413 {
414 "name": {
415 "$endsWith": ".a",
416 "$containsAny": ["01", "02"]
417 }
418 },
419 {
420 "name": {"$contains": "03"}
421 }
422 ]))?;
423 let list_options = serde_json::from_value(json!({
424 "order_bys": "!id"
425 }))?;
426 let validations = ValidationBmc::list(&ctx, &mm, Some(filters), Some(list_options)).await?;
427
428 assert_eq!(validations.len(), 3);
429 assert!(validations[0].id == 1);
430
431 let validations = ValidationBmc::list(
432 &ctx,
433 &mm,
434 Some(serde_json::from_value(json!([{
435 "name": {"$startsWith": "test_list_by_filter_ok"}
436 }]))?),
437 None,
438 )
439 .await?;
440 assert_eq!(validations.len(), 5);
441 for validation in validations.iter() {
442 ValidationBmc::delete(&ctx, &mm, validation.id).await?;
443 }
444 Ok(())
445 }
446
447 #[ignore]
448 #[tokio::test]
449 async fn test_validation_update_ok() -> Result<()> {
450 let mm = ModelManager::new().await?;
451 let ctx = Ctx::root_ctx();
452 let tname = "test_validation_update_ok";
453 let seeds = _dev_utils::get_validation_seed(tname);
454 let fx_validation = _dev_utils::seed_validations(&ctx, &mm, &seeds)
455 .await?
456 .remove(0);
457
458 ValidationBmc::update(
459 &ctx,
460 &mm,
461 fx_validation.id,
462 ValidationForUpdate {
463 ..Default::default()
464 },
465 )
466 .await?;
467
468 let validation = ValidationBmc::get(&ctx, &mm, fx_validation.id).await?;
469 assert_eq!(validation.id, 1);
470
471 Ok(())
472 }
473
474 #[ignore]
475 #[tokio::test]
476 async fn test_validation_delete_err_not_found() -> Result<()> {
477 let mm = ModelManager::new().await?;
478 let ctx = Ctx::root_ctx();
479 let fx_id = 100;
480
481 let res = ValidationBmc::delete(&ctx, &mm, fx_id).await;
482
483 assert!(
484 matches!(
485 res,
486 Err(Error::EntityNotFound {
487 entity: "validation",
488 id: 100
489 })
490 ),
491 "EntityNotFound not matching"
492 );
493
494 Ok(())
495 }
496}