use chrono::{Duration, NaiveDate, Utc};
use diesel::pg::Pg;
use diesel::{
debug_query, BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods, QueryDsl,
QueryResult,
};
use diesel_async::scoped_futures::ScopedFutureExt;
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
use log::debug;
use std::future::Future;
use uuid::Uuid;
use crate::model::dto::plantings::{DeletePlantingDto, PlantingDto, UpdatePlantingDto};
use crate::model::entity::plantings::{NewPlanting, Planting, UpdatePlanting};
use crate::model::entity::Map;
use crate::model::r#enum::life_cycle::LifeCycle;
use crate::schema::plantings::{self, layer_id, plant_id};
use crate::schema::plants;
use crate::schema::seeds;
pub struct FindPlantingsParameters {
pub plant_id: Option<i32>,
pub layer_id: Option<Uuid>,
pub from: NaiveDate,
pub to: NaiveDate,
}
impl Planting {
pub async fn find(
search_parameters: FindPlantingsParameters,
conn: &mut AsyncPgConnection,
) -> QueryResult<Vec<PlantingDto>> {
let mut query = plantings::table
.left_join(seeds::table)
.select((plantings::all_columns, seeds::name.nullable()))
.into_boxed();
if let Some(id) = search_parameters.plant_id {
query = query.filter(plant_id.eq(id));
}
if let Some(id) = search_parameters.layer_id {
query = query.filter(layer_id.eq(id));
}
let from = search_parameters.from;
let to = search_parameters.to;
let plantings_added_before_date =
plantings::add_date.is_null().or(plantings::add_date.lt(to));
let plantings_removed_after_date = plantings::remove_date
.is_null()
.or(plantings::remove_date.gt(from));
query = query.filter(plantings_added_before_date.and(plantings_removed_after_date));
debug!("{}", debug_query::<Pg, _>(&query));
Ok(query
.load::<(Self, Option<String>)>(conn)
.await?
.into_iter()
.map(Into::into)
.collect())
}
pub async fn find_by_seed_id(
seed_id: i32,
conn: &mut AsyncPgConnection,
) -> QueryResult<Vec<PlantingDto>> {
let query = plantings::table
.select(plantings::all_columns)
.filter(plantings::seed_id.eq(seed_id));
Ok(query
.load::<Self>(conn)
.await?
.into_iter()
.map(Into::into)
.collect())
}
async fn set_end_date_according_to_cycle_types(
conn: &mut AsyncPgConnection,
plantings: &mut Vec<NewPlanting>,
) -> QueryResult<()> {
type LifeCycleType = Option<Vec<Option<LifeCycle>>>;
let plant_ids: Vec<i32> = plantings.iter().map(|p| p.plant_id).collect();
let life_cycles_query = plants::table
.filter(plants::id.eq_any(&plant_ids))
.select((plants::id, plants::life_cycle.nullable()));
let life_cycle_lookup = life_cycles_query.load::<(i32, LifeCycleType)>(conn).await?;
for planting in plantings {
if let Some(add_date) = planting.add_date {
let current_plant_id = planting.plant_id;
let life_cycle_info_opt: Option<(i32, LifeCycleType)> = life_cycle_lookup
.iter()
.find_map(|x| (x.0 == current_plant_id).then(|| x.clone()));
if let Some((_, Some(life_cycles))) = life_cycle_info_opt {
if life_cycles.contains(&Some(LifeCycle::Perennial)) {
continue;
} else if life_cycles.contains(&Some(LifeCycle::Biennial)) {
planting.remove_date = Some(add_date + Duration::days(2 * 365));
} else if life_cycles.contains(&Some(LifeCycle::Annual)) {
planting.remove_date = Some(add_date + Duration::days(365));
}
};
};
}
Ok(())
}
pub async fn create(
dto_vec: Vec<PlantingDto>,
map_id: i32,
user_id: Uuid,
conn: &mut AsyncPgConnection,
) -> QueryResult<Vec<PlantingDto>> {
let mut planting_creations: Vec<NewPlanting> = dto_vec
.into_iter()
.map(|dto| NewPlanting::from((dto, user_id)))
.collect();
Self::set_end_date_according_to_cycle_types(conn, &mut planting_creations).await?;
let query = diesel::insert_into(plantings::table).values(&planting_creations);
debug!("{}", debug_query::<Pg, _>(&query));
let query_result: Vec<Self> = query.get_results::<Self>(conn).await?;
if let Some(first) = query_result.get(0) {
Map::update_modified_metadata(map_id, user_id, first.created_at, conn).await?;
}
let seed_ids = query_result
.iter()
.map(|planting| planting.seed_id)
.collect::<Vec<_>>();
let additional_names_query = seeds::table
.filter(seeds::id.nullable().eq_any(&seed_ids))
.select((seeds::id, seeds::name));
debug!("{}", debug_query::<Pg, _>(&additional_names_query));
let seed_ids_names: Vec<(i32, String)> = additional_names_query.get_results(conn).await?;
let seed_ids_to_names = seed_ids_names
.into_iter()
.collect::<std::collections::HashMap<_, _>>();
let result_vec = query_result
.into_iter()
.map(PlantingDto::from)
.map(|mut dto| {
if let Some(seed_id) = dto.seed_id {
dto.additional_name = seed_ids_to_names.get(&seed_id).cloned();
}
dto
})
.collect::<Vec<_>>();
Ok(result_vec)
}
pub async fn update(
dto: UpdatePlantingDto,
map_id: i32,
user_id: Uuid,
conn: &mut AsyncPgConnection,
) -> QueryResult<Vec<PlantingDto>> {
let planting_updates = Vec::from(dto);
let result = conn
.transaction(|transaction| {
async move {
let futures = Self::do_update(planting_updates, user_id, transaction);
let results = futures_util::future::try_join_all(futures).await?;
if let Some(first) = results.get(0) {
Map::update_modified_metadata(
map_id,
user_id,
first.modified_at,
transaction,
)
.await?;
}
Ok(results) as QueryResult<Vec<Self>>
}
.scope_boxed()
})
.await?;
Ok(result.into_iter().map(Into::into).collect())
}
fn do_update(
updates: Vec<UpdatePlanting>,
user_id: Uuid,
conn: &mut AsyncPgConnection,
) -> Vec<impl Future<Output = QueryResult<Self>>> {
let now = Utc::now().naive_utc();
updates
.into_iter()
.map(|update| {
diesel::update(plantings::table.find(update.id))
.set((
update,
plantings::modified_at.eq(now),
plantings::modified_by.eq(user_id),
))
.get_result::<Self>(conn)
})
.collect::<Vec<_>>()
}
pub async fn delete_by_ids(
dto: Vec<DeletePlantingDto>,
map_id: i32,
user_id: Uuid,
conn: &mut AsyncPgConnection,
) -> QueryResult<usize> {
let ids: Vec<Uuid> = dto.iter().map(|&DeletePlantingDto { id }| id).collect();
conn.transaction(|transaction| {
Box::pin(async {
let query = diesel::delete(plantings::table.filter(plantings::id.eq_any(ids)));
debug!("{}", debug_query::<Pg, _>(&query));
let deleted_plantings = query.execute(transaction).await?;
Map::update_modified_metadata(map_id, user_id, Utc::now().naive_utc(), transaction)
.await?;
Ok(deleted_plantings)
})
})
.await
}
}