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<i64>,
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: i64,
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<i64> = 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::<(i64, 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<(i64, 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: i64,
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<(i64, 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 let seed_id_i64 = i64::from(seed_id);
184 dto.additional_name = seed_ids_to_names.get(&seed_id_i64).cloned();
185 }
186 dto
187 })
188 .collect::<Vec<_>>();
189
190 Ok(result_vec)
191 }
192
193 pub async fn update(
198 dto: UpdatePlantingDto,
199 map_id: i64,
200 user_id: Uuid,
201 conn: &mut AsyncPgConnection,
202 ) -> QueryResult<Vec<PlantingDto>> {
203 let planting_updates = Vec::from(dto);
204
205 let result = conn
206 .transaction(|transaction| {
207 async move {
208 let futures = Self::do_update(planting_updates, user_id, transaction);
209
210 let results = futures_util::future::try_join_all(futures).await?;
211
212 if let Some(first) = results.get(0) {
213 Map::update_modified_metadata(
214 map_id,
215 user_id,
216 first.modified_at,
217 transaction,
218 )
219 .await?;
220 }
221
222 Ok(results) as QueryResult<Vec<Self>>
223 }
224 .scope_boxed()
225 })
226 .await?;
227
228 Ok(result.into_iter().map(Into::into).collect())
229 }
230
231 fn do_update(
236 updates: Vec<UpdatePlanting>,
237 user_id: Uuid,
238 conn: &mut AsyncPgConnection,
239 ) -> Vec<impl Future<Output = QueryResult<Self>>> {
240 let now = Utc::now().naive_utc();
241 updates
243 .into_iter()
244 .map(|update| {
245 diesel::update(plantings::table.find(update.id))
246 .set((
247 update,
248 plantings::modified_at.eq(now),
249 plantings::modified_by.eq(user_id),
250 ))
251 .get_result::<Self>(conn)
252 })
253 .collect::<Vec<_>>()
254 }
255
256 pub async fn delete_by_ids(
261 dto: Vec<DeletePlantingDto>,
262 map_id: i64,
263 user_id: Uuid,
264 conn: &mut AsyncPgConnection,
265 ) -> QueryResult<usize> {
266 let ids: Vec<Uuid> = dto.iter().map(|&DeletePlantingDto { id }| id).collect();
267
268 conn.transaction(|transaction| {
269 Box::pin(async {
270 let query = diesel::delete(plantings::table.filter(plantings::id.eq_any(ids)));
271 debug!("{}", debug_query::<Pg, _>(&query));
272 let deleted_plantings = query.execute(transaction).await?;
273
274 Map::update_modified_metadata(map_id, user_id, Utc::now().naive_utc(), transaction)
275 .await?;
276 Ok(deleted_plantings)
277 })
278 })
279 .await
280 }
281}