1use crate::ctx::Ctx;
2use crate::model::ModelManager;
3use crate::model::Result;
4use crate::model::clone::CloneFilter;
5use crate::model::lot::{Lot, LotBmc, LotFilter};
6use crate::model::provider::{Provider, ProviderBmc, ProviderFilter};
7use crate::model::validation::Validation;
8use crate::model::validation::{ValidationBmc, ValidationFilter};
9use crate::model::view_clone::{ViewClone, ViewCloneBmc};
10use modql::field::Fields;
11use modql::filter::ListOptions;
12use serde::{Deserialize, Serialize};
13use serde_json::json;
14use sqlx::FromRow;
15use std::collections::{HashMap, hash_map::Entry};
16
17#[derive(Serialize, Deserialize, Debug, Clone, Default)]
18pub struct ViewLot {
19 pub id: i32,
20 #[serde(rename = "createdBy")]
21 pub created_by: Option<i32>,
22 pub storage: String,
23 #[serde(rename = "cloneId")]
24 pub clone_id: i32,
25 #[serde(rename = "providerId")]
26 pub provider_id: Option<i32>,
27 pub name: String,
28 pub reference: Option<String>,
29 #[serde(rename = "requestedBy")]
30 pub requested_by: Option<i32>,
31 #[serde(rename = "approvedBy")]
32 pub approved_by: Option<i32>,
33 #[serde(rename = "orderedBy")]
34 pub ordered_by: Option<i32>,
35 #[serde(rename = "receivedBy")]
36 pub received_by: Option<i32>,
37 #[serde(rename = "finishedBy")]
38 pub finished_by: Option<i32>,
39 pub status: Option<i16>,
40 pub purpose: Option<String>,
41 pub number: Option<String>,
42 pub url: Option<String>,
43 pub price: Option<String>,
44 pub note: Option<String>,
45 #[serde(rename = "requestedAt")]
46 pub requested_at: Option<chrono::DateTime<chrono::Utc>>,
47 #[serde(rename = "orderedAt")]
48 pub ordered_at: Option<chrono::DateTime<chrono::Utc>>,
49 #[serde(rename = "receivedAt")]
50 pub received_at: Option<chrono::DateTime<chrono::Utc>>,
51 #[serde(rename = "finishedAt")]
52 pub finished_at: Option<chrono::DateTime<chrono::Utc>>,
53 pub clone: ViewClone,
54 pub validations: Vec<Validation>,
55 pub provider: Option<Provider>,
56}
57
58#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
59pub struct ViewLotDetails {
60 pub id: i32,
61 #[serde(rename = "groupId")]
62 pub group_id: i32,
63 #[serde(rename = "createdBy")]
64 pub created_by: i32,
65 #[serde(rename = "cloneId")]
66 pub clone_id: i32,
67 #[serde(rename = "providerId")]
68 pub provider_id: Option<i32>,
69 pub name: Option<String>,
70 pub reference: Option<String>,
71 #[serde(rename = "requestedBy")]
72 pub requested_by: Option<i32>,
73 #[serde(rename = "approvedBy")]
74 pub approved_by: Option<i32>,
75 #[serde(rename = "orderedBy")]
76 pub ordered_by: Option<i32>,
77 #[serde(rename = "receivedBy")]
78 pub received_by: Option<i32>,
79 #[serde(rename = "finishedBy")]
80 pub finished_by: Option<i32>,
81 pub number: Option<String>,
82 pub status: Option<i16>,
83 pub purpose: Option<String>,
84 pub url: Option<String>,
85 pub price: Option<String>,
86 pub note: Option<String>,
87 #[serde(rename = "requestedAt")]
88 pub requested_at: Option<chrono::DateTime<chrono::Utc>>,
89 #[serde(rename = "orderedAt")]
90 pub ordered_at: Option<chrono::DateTime<chrono::Utc>>,
91 #[serde(rename = "receivedAt")]
92 pub received_at: Option<chrono::DateTime<chrono::Utc>>,
93 #[serde(rename = "finishedAt")]
94 pub finished_at: Option<chrono::DateTime<chrono::Utc>>,
95 pub meta: Option<serde_json::Value>,
96 pub clone: Option<serde_json::Value>,
97 pub provider: Option<serde_json::Value>,
98 #[serde(rename = "requestedByUser")]
99 pub requested_by_user: Option<serde_json::Value>,
100 #[serde(rename = "approvedByUser")]
101 pub approved_by_user: Option<serde_json::Value>,
102 #[serde(rename = "orderedByUser")]
103 pub ordered_by_user: Option<serde_json::Value>,
104 #[serde(rename = "receivedByUser")]
105 pub received_by_user: Option<serde_json::Value>,
106 #[serde(rename = "finishedByUser")]
107 pub finished_by_user: Option<serde_json::Value>,
108}
109
110pub struct ViewLotBmc;
111
112impl ViewLotBmc {
113 pub async fn get_details(_ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<ViewLotDetails> {
114 let stmt = r#"SELECT
115 lot.id,
116 lot.group_id,
117 lot.created_by,
118 lot.clone_id,
119 lot.provider_id,
120 lot.name,
121 lot.reference,
122 lot.requested_by,
123 lot.approved_by,
124 lot.ordered_by,
125 lot.received_by,
126 lot.finished_by,
127 lot.number,
128 lot.status,
129 lot.purpose,
130 lot.url,
131 lot.price,
132 lot.note,
133 lot.requested_at,
134 lot.ordered_at,
135 lot.received_at,
136 lot.finished_at,
137 lot.meta,
138 jsonb_build_object(
139 'id', reu.id,
140 'name', reu.name,
141 'isAdmin', reu.is_admin
142 ) AS requested_by_user,
143 jsonb_build_object(
144 'id', apu.id,
145 'name', apu.name,
146 'isAdmin', apu.is_admin
147 ) AS approved_by_user,
148 jsonb_build_object(
149 'id', oru.id,
150 'name', oru.name,
151 'isAdmin', oru.is_admin
152 ) AS ordered_by_user,
153 jsonb_build_object(
154 'id', rcu.id,
155 'name', rcu.name,
156 'isAdmin', rcu.is_admin
157 ) AS received_by_user,
158 jsonb_build_object(
159 'id', fiu.id,
160 'name', fiu.name,
161 'isAdmin', fiu.is_admin
162 ) AS finished_by_user,
163 jsonb_build_object(
164 'id', cl.id,
165 'name', cl.name
166 ) AS clone,
167 jsonb_build_object(
168 'id', pv.id,
169 'name', pv.name
170 ) AS provider
171FROM
172 lot
173LEFT JOIN
174 member rem ON lot.requested_by = rem.id
175LEFT JOIN
176 "user" reu ON rem.user_id = reu.id
177LEFT JOIN
178 member apm ON lot.approved_by = apm.id
179LEFT JOIN
180 "user" apu ON apm.user_id = apu.id
181LEFT JOIN
182 member orm ON lot.ordered_by = orm.id
183LEFT JOIN
184 "user" oru ON orm.user_id = oru.id
185LEFT JOIN
186 member rcm ON lot.received_by = rcm.id
187LEFT JOIN
188 "user" rcu ON rcm.user_id = rcu.id
189LEFT JOIN
190 member fim ON lot.finished_by = fim.id
191LEFT JOIN
192 "user" fiu ON fim.user_id = fiu.id
193LEFT JOIN
194 clone cl ON lot.clone_id = cl.id
195LEFT JOIN
196 provider pv ON lot.provider_id = pv.id
197WHERE
198 lot.id = $1"#;
199 let item: ViewLotDetails = sqlx::query_as::<_, ViewLotDetails>(stmt)
200 .bind(id)
201 .fetch_one(&mm.db)
202 .await?;
203 Ok(item)
204 }
205 pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<ViewLot> {
206 let item = LotBmc::get(ctx, mm, id).await?;
207 let clone = ViewCloneBmc::get(ctx, mm, item.clone_id).await?;
208 let provider = match item.provider_id {
209 Some(pid) => Some(ProviderBmc::get(ctx, mm, pid).await?),
210 None => None,
211 };
212 let ret = ViewLot {
213 id: item.id,
214 created_by: item.created_by,
215 storage: "storage_const".into(),
216 clone_id: item.clone_id,
217 provider_id: item.provider_id,
218 name: item.name,
219 reference: item.reference,
220 requested_by: item.requested_by,
221 approved_by: item.approved_by,
222 ordered_by: item.ordered_by,
223 received_by: item.received_by,
224 finished_by: item.finished_by,
225 status: item.status,
226 purpose: item.purpose,
227 number: item.number,
228 url: item.url,
229 price: item.price,
230 note: item.note,
231 requested_at: item.requested_at,
232 ordered_at: item.ordered_at,
233 received_at: item.received_at,
234 finished_at: item.finished_at,
235 clone,
236 validations: vec![],
237 provider,
238 };
239 Ok(ret)
240 }
241 pub async fn list(
242 ctx: &Ctx,
243 mm: &ModelManager,
244 group_id: Option<i32>,
245 filters: Option<Vec<LotFilter>>,
246 list_options: Option<ListOptions>,
247 ) -> Result<Vec<ViewLot>> {
248 let mut clone_map = HashMap::new();
249 let mut provider_map = HashMap::new();
250 let mut validation_map = HashMap::new();
251 if let Some(group_id) = group_id {
252 let clone_filters: Option<Vec<CloneFilter>> = match serde_json::from_value(json!([
253 {
254 "group_id": {"$eq":group_id}
255 }
256 ])) {
257 Ok(ok) => Some(ok),
258 Err(_) => None,
259 };
260 let op = ListOptions {
261 limit: Some(10_000),
262 ..Default::default()
263 };
264 let clones: Vec<ViewClone> =
265 ViewCloneBmc::list(ctx, mm, Some(group_id), clone_filters, Some(op)).await?;
266 for clone in clones {
267 clone_map.insert(clone.id, clone);
268 }
269
270 let provider_filters: Option<Vec<ProviderFilter>> =
271 match serde_json::from_value(json!([
272 {
273 "group_id": {"$eq":group_id}
274 }
275 ])) {
276 Ok(ok) => Some(ok),
277 Err(_) => None,
278 };
279 let providers: Vec<Provider> =
280 ProviderBmc::list(ctx, mm, provider_filters, None).await?;
281 for provider in providers {
282 provider_map.insert(provider.id, provider);
283 }
284 let validation_op = ListOptions {
285 limit: Some(100_000),
286 ..Default::default()
287 };
288
289 let validation_filters: Option<Vec<ValidationFilter>> =
290 match serde_json::from_value(json!([
291 {
292 "group_id": {"$eq":group_id}
293 }
294 ])) {
295 Ok(ok) => Some(ok),
296 Err(_) => None,
297 };
298 let validations: Vec<Validation> =
299 ValidationBmc::list(ctx, mm, validation_filters, Some(validation_op)).await?;
300 for validation in validations {
301 let h = match validation_map.entry(validation.lot_id.unwrap_or(0)) {
302 Entry::Occupied(o) => o.into_mut(),
303 Entry::Vacant(v) => v.insert(vec![]),
304 };
305 h.push(validation);
306 }
307 }
308
309 let lots: Vec<Lot> = LotBmc::list(ctx, mm, filters, list_options).await?;
310 let mut returns = vec![];
311 for item in lots {
312 let clone = match clone_map.get(&{ item.clone_id }) {
313 Some(v) => v.clone(),
314 None => ViewClone::default(),
315 };
316 let mut provider = None;
317
318 if let Some(provider_id) = &item.provider_id {
319 provider = provider_map.get(provider_id).cloned();
320 }
321
322 returns.push(ViewLot {
323 id: item.id,
324 storage: "storage_const".into(),
325 created_by: item.created_by,
326 clone_id: item.clone_id,
327 provider_id: item.provider_id,
328 name: item.name,
329 reference: item.reference,
330 requested_by: item.requested_by,
331 approved_by: item.approved_by,
332 ordered_by: item.ordered_by,
333 received_by: item.received_by,
334 finished_by: item.finished_by,
335 status: item.status,
336 purpose: item.purpose,
337 number: item.number,
338 url: item.url,
339 price: item.price,
340 note: item.note,
341 requested_at: item.requested_at,
342 ordered_at: item.ordered_at,
343 received_at: item.received_at,
344 finished_at: item.finished_at,
345 clone,
346 validations: validation_map.get(&item.id).unwrap_or(&vec![]).clone(),
347 provider,
348 });
349 }
350
351 Ok(returns)
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358 use anyhow::Result;
359
360 #[ignore]
361 #[tokio::test]
362 async fn test_view_lot_list_all_ok() -> Result<()> {
363 let mm = ModelManager::new().await?;
364 let ctx = Ctx::root_ctx();
365
366 let lots = ViewLotBmc::list(&ctx, &mm, Some(1000), None, None).await?;
367
368 let _lots: Vec<ViewLot> = lots
369 .into_iter()
370 .filter(|t| t.name.starts_with("test_list_all_ok-lot"))
371 .collect();
372
373 Ok(())
374 }
375}