backend/model/entity/
plantings_impl.rs1use chrono::{Duration, NaiveDate, Utc};
4use diesel::pg::Pg;
5use diesel::{
6 debug_query, BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods, QueryDsl,
7 QueryResult,
8};
9use diesel_async::scoped_futures::ScopedFutureExt;
10use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
11use log::debug;
12use std::future::Future;
13use uuid::Uuid;
14
15use crate::model::dto::plantings::{DeletePlantingDto, PlantingDto, UpdatePlantingDto};
16use crate::model::entity::plantings::{NewPlanting, Planting, UpdatePlanting};
17use crate::model::entity::Map;
18use crate::model::r#enum::life_cycle::LifeCycle;
19use crate::schema::plantings::{self, layer_id, plant_id};
20use crate::schema::plants;
21use crate::schema::seeds;
22
23pub struct FindPlantingsParameters {
25 pub plant_id: Option<i32>,
27 pub layer_id: Option<Uuid>,
29 pub from: NaiveDate,
31 pub to: NaiveDate,
33}
34
35impl Planting {
36 pub async fn find(
41 search_parameters: FindPlantingsParameters,
42 conn: &mut AsyncPgConnection,
43 ) -> QueryResult<Vec<PlantingDto>> {
44 let mut query = plantings::table
45 .left_join(seeds::table)
46 .select((plantings::all_columns, seeds::name.nullable()))
47 .into_boxed();
48
49 if let Some(id) = search_parameters.plant_id {
50 query = query.filter(plant_id.eq(id));
51 }
52 if let Some(id) = search_parameters.layer_id {
53 query = query.filter(layer_id.eq(id));
54 }
55
56 let from = search_parameters.from;
57 let to = search_parameters.to;
58
59 let plantings_added_before_date =
60 plantings::add_date.is_null().or(plantings::add_date.lt(to));
61 let plantings_removed_after_date = plantings::remove_date
62 .is_null()
63 .or(plantings::remove_date.gt(from));
64
65 query = query.filter(plantings_added_before_date.and(plantings_removed_after_date));
66
67 debug!("{}", debug_query::<Pg, _>(&query));
68
69 Ok(query
70 .load::<(Self, Option<String>)>(conn)
71 .await?
72 .into_iter()
73 .map(Into::into)
74 .collect())
75 }
76
77 pub async fn find_by_seed_id(
82 seed_id: i32,
83 conn: &mut AsyncPgConnection,
84 ) -> QueryResult<Vec<PlantingDto>> {
85 let query = plantings::table
86 .select(plantings::all_columns)
87 .filter(plantings::seed_id.eq(seed_id));
88
89 Ok(query
90 .load::<Self>(conn)
91 .await?
92 .into_iter()
93 .map(Into::into)
94 .collect())
95 }
96
97 async fn set_end_date_according_to_cycle_types(
99 conn: &mut AsyncPgConnection,
100 plantings: &mut Vec<NewPlanting>,
101 ) -> QueryResult<()> {
102 type LifeCycleType = Option<Vec<Option<LifeCycle>>>;
104
105 let plant_ids: Vec<i32> = plantings.iter().map(|p| p.plant_id).collect();
106 let life_cycles_query = plants::table
107 .filter(plants::id.eq_any(&plant_ids))
108 .select((plants::id, plants::life_cycle.nullable()));
109
110 let life_cycle_lookup = life_cycles_query.load::<(i32, LifeCycleType)>(conn).await?;
111
112 for planting in plantings {
113 if let Some(add_date) = planting.add_date {
114 let current_plant_id = planting.plant_id;
115 let life_cycle_info_opt: Option<(i32, LifeCycleType)> = life_cycle_lookup
116 .iter()
117 .find_map(|x| (x.0 == current_plant_id).then(|| x.clone()));
118 if let Some((_, Some(life_cycles))) = life_cycle_info_opt {
119 if life_cycles.contains(&Some(LifeCycle::Perennial)) {
120 } else if life_cycles.contains(&Some(LifeCycle::Biennial)) {
121 planting.remove_date = Some(add_date + Duration::days(2 * 365));
122 } else if life_cycles.contains(&Some(LifeCycle::Annual)) {
123 planting.remove_date = Some(add_date + Duration::days(365));
124 }
125 }
126 }
127 }
128 Ok(())
129 }
130
131 pub async fn create(
137 dto_vec: Vec<PlantingDto>,
138 map_id: i32,
139 user_id: Uuid,
140 conn: &mut AsyncPgConnection,
141 ) -> QueryResult<Vec<PlantingDto>> {
142 let mut planting_creations: Vec<NewPlanting> = dto_vec
143 .into_iter()
144 .map(|dto| NewPlanting::from((dto, user_id)))
145 .collect();
146
147 Self::set_end_date_according_to_cycle_types(conn, &mut planting_creations).await?;
148
149 let query = diesel::insert_into(plantings::table).values(&planting_creations);
150
151 debug!("{}", debug_query::<Pg, _>(&query));
152
153 let query_result: Vec<Self> = query.get_results::<Self>(conn).await?;
154
155 if let Some(first) = query_result.get(0) {
156 Map::update_modified_metadata(map_id, user_id, first.created_at, conn).await?;
157 }
158
159 let seed_ids = query_result
160 .iter()
161 .map(|planting| planting.seed_id)
162 .collect::<Vec<_>>();
163
164 let additional_names_query = seeds::table
167 .filter(seeds::id.nullable().eq_any(&seed_ids))
168 .select((seeds::id, seeds::name));
169
170 debug!("{}", debug_query::<Pg, _>(&additional_names_query));
171
172 let seed_ids_names: Vec<(i32, String)> = additional_names_query.get_results(conn).await?;
173
174 let seed_ids_to_names = seed_ids_names
175 .into_iter()
176 .collect::<std::collections::HashMap<_, _>>();
177
178 let result_vec = query_result
179 .into_iter()
180 .map(PlantingDto::from)
181 .map(|mut dto| {
182 if let Some(seed_id) = dto.seed_id {
183 dto.additional_name = seed_ids_to_names.get(&seed_id).cloned();
184 }
185 dto
186 })
187 .collect::<Vec<_>>();
188
189 Ok(result_vec)
190 }
191
192 pub async fn update(
197 dto: UpdatePlantingDto,
198 map_id: i32,
199 user_id: Uuid,
200 conn: &mut AsyncPgConnection,
201 ) -> QueryResult<Vec<PlantingDto>> {
202 let planting_updates = Vec::from(dto);
203
204 let result = conn
205 .transaction(|transaction| {
206 async move {
207 let futures = Self::do_update(planting_updates, user_id, transaction);
208
209 let results = futures_util::future::try_join_all(futures).await?;
210
211 if let Some(first) = results.get(0) {
212 Map::update_modified_metadata(
213 map_id,
214 user_id,
215 first.modified_at,
216 transaction,
217 )
218 .await?;
219 }
220
221 Ok(results) as QueryResult<Vec<Self>>
222 }
223 .scope_boxed()
224 })
225 .await?;
226
227 Ok(result.into_iter().map(Into::into).collect())
228 }
229
230 fn do_update(
235 updates: Vec<UpdatePlanting>,
236 user_id: Uuid,
237 conn: &mut AsyncPgConnection,
238 ) -> Vec<impl Future<Output = QueryResult<Self>>> {
239 let now = Utc::now().naive_utc();
240 updates
242 .into_iter()
243 .map(|update| {
244 diesel::update(plantings::table.find(update.id))
245 .set((
246 update,
247 plantings::modified_at.eq(now),
248 plantings::modified_by.eq(user_id),
249 ))
250 .get_result::<Self>(conn)
251 })
252 .collect::<Vec<_>>()
253 }
254
255 pub async fn delete_by_ids(
260 dto: Vec<DeletePlantingDto>,
261 map_id: i32,
262 user_id: Uuid,
263 conn: &mut AsyncPgConnection,
264 ) -> QueryResult<usize> {
265 let ids: Vec<Uuid> = dto.iter().map(|&DeletePlantingDto { id }| id).collect();
266
267 conn.transaction(|transaction| {
268 Box::pin(async {
269 let query = diesel::delete(plantings::table.filter(plantings::id.eq_any(ids)));
270 debug!("{}", debug_query::<Pg, _>(&query));
271 let deleted_plantings = query.execute(transaction).await?;
272
273 Map::update_modified_metadata(map_id, user_id, Utc::now().naive_utc(), transaction)
274 .await?;
275 Ok(deleted_plantings)
276 })
277 })
278 .await
279 }
280}