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