[[package]]
name = "strum"
-version = "0.26.3"
+version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
+checksum = "ce1475c515a4f03a8a7129bb5228b81a781a86cb0b3fbbc19e1c556d491a401f"
[[package]]
name = "strum_macros"
-version = "0.26.4"
+version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
+checksum = "9688894b43459159c82bfa5a5fa0435c19cbe3c9b427fa1dd7b1ce0c279b18a7"
dependencies = [
"heck",
"proc-macro2",
argon2 = { version = "0.5", features = ["default", "std"] }
rand_core = { version = "0.9", features = ["std"] }
rand = "0.9"
-strum = "0.26"
-strum_macros = "0.26"
+strum = "0.27"
+strum_macros = "0.27"
lettre = { version = "0.11", default-features = false, features = [
"smtp-transport",
-- In both cases [name], [quantity_value] and [quantity_unit] are used to display
-- the entry instead of [Ingredient] data.
[ingredient_id] INTEGER,
+ [recipe_scheduled_id] INTEGER, -- Can be null when manually added.
+
[is_checked] INTEGER NOT NULL DEFAULT FALSE,
[name] TEXT NOT NULL DEFAULT '',
[servings] INTEGER,
FOREIGN KEY([user_id]) REFERENCES [User]([id]) ON DELETE CASCADE,
- FOREIGN KEY([ingredient_id]) REFERENCES [Ingredient]([id]) ON DELETE SET NULL
+ FOREIGN KEY([ingredient_id]) REFERENCES [Ingredient]([id]) ON DELETE SET NULL,
+ FOREIGN KEY([recipe_scheduled_id]) REFERENCES [RecipeScheduled]([id]) ON DELETE SET NULL
);
-- When an ingredient is deleted, its values are copied to any shopping entry
recipe_id: i64,
date: NaiveDate,
servings: u32,
+ add_ingredients_element: bool,
) -> Result<AddScheduledRecipeResult> {
+ let mut tx = self.tx().await?;
+
match sqlx::query(
r#"
INSERT INTO [RecipeScheduled] (user_id, recipe_id, date, servings)
.bind(recipe_id)
.bind(date)
.bind(servings)
- .execute(&self.pool)
+ .execute(&mut *tx)
.await
{
Err(Error::Database(error))
{
Ok(AddScheduledRecipeResult::RecipeAlreadyScheduledAtThisDate)
}
- _ => Ok(AddScheduledRecipeResult::Ok),
+ Err(error) => {
+ Err(DBError::from(error))
+ }
+ Ok(insert_result) => {
+
+ if add_ingredients_element {
+ sqlx::query(
+ r#"
+INSERT INTO [ShoppingEntry] ([ingredient_id], [user_id], [recipe_scheduled_id], [servings])
+ SELECT [Ingredient].[id], $2, $3, $4 FROM [Ingredient]
+ INNER JOIN [Step] ON [Step].[id] = [Ingredient].[step_id]
+ INNER JOIN [Group] ON [Group].[id] = [Step].[group_id]
+ INNER JOIN [Recipe] ON [Recipe].[id] = [Group].[recipe_id]
+ WHERE [Recipe].[id] = $1
+ "#)
+ .bind(recipe_id)
+ .bind(user_id)
+ .bind(insert_result.last_insert_rowid())
+ .bind(servings)
+ .execute(&mut *tx)
+ .await?;
+ }
+
+ tx.commit().await?;
+
+ Ok(AddScheduledRecipeResult::Ok)
+ }
}
}
let tomorrow = today + Days::new(1);
connection
- .add_scheduled_recipe(user_id, recipe_id_1, today, 4)
+ .add_scheduled_recipe(user_id, recipe_id_1, today, 4, false)
.await?;
connection
- .add_scheduled_recipe(user_id, recipe_id_2, yesterday, 4)
+ .add_scheduled_recipe(user_id, recipe_id_2, yesterday, 4, false)
.await?;
connection
- .add_scheduled_recipe(user_id, recipe_id_1, tomorrow, 4)
+ .add_scheduled_recipe(user_id, recipe_id_1, tomorrow, 4, false)
.await?;
assert_eq!(
// Recipe scheduled at the same date is forbidden.
let Ok(AddScheduledRecipeResult::RecipeAlreadyScheduledAtThisDate) = connection
- .add_scheduled_recipe(user_id, recipe_id_1, today, 4)
+ .add_scheduled_recipe(user_id, recipe_id_1, today, 4, false)
.await
else {
panic!("DBError::RecipeAlreadyScheduledAtThisDate must be returned");
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
if let Some(user) = user {
connection
- .add_scheduled_recipe(user.id, ron.recipe_id, ron.date, ron.servings)
+ .add_scheduled_recipe(
+ user.id,
+ ron.recipe_id,
+ ron.date,
+ ron.servings,
+ ron.add_ingredients_to_shopping_list,
+ )
.await
.map(|res| {
ron_response_ok(common::ron_api::ScheduleRecipeResult::from(res)).into_response()
CalendarAddToPlannerSuccess,
CalendarAddToPlannerAlreadyExists,
CalendarDateFormat, // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
+ CalendarAddIngredientsToShoppingList,
}
pub const DEFAULT_LANGUAGE_CODE: &str = "en";
{# To create a modal dialog to choose a date and and servings #}
<div class="date-and-servings" >
{% include "calendar.html" %}
+
<label for="input-servings">{{ tr.t(Sentence::RecipeServings) }}</label>
<input
id="input-servings"
4
{% endif %}
"/>
+
+ <input
+ id="input-add-ingredients-to-shopping-list"
+ type="checkbox"
+ checked
+ >
+ <label for="input-add-ingredients-to-shopping-list">
+ {{ tr.t(Sentence::CalendarAddIngredientsToShoppingList) }}
+ </label>
</div>
<span class="calendar-add-to-planner-success">{{ tr.t(Sentence::CalendarAddToPlannerSuccess) }}</span>
(CalendarAddToPlannerSuccess, "Recipe {title} has been scheduled for {date}"),
(CalendarAddToPlannerAlreadyExists, "Recipe {title} has already been scheduled for {date}"),
(CalendarDateFormat, "%A, %-d %B, %C%y"), // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
+ (CalendarAddIngredientsToShoppingList, "Add ingredients to shopping list"),
]
),
(
(CalendarAddToPlannerSuccess, "La recette {title} a été agendée pour le {date}"),
(CalendarAddToPlannerAlreadyExists, "La recette {title} a été déjà été agendée pour le {date}"),
(CalendarDateFormat, "%A %-d %B %C%y"), // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
+ (CalendarAddIngredientsToShoppingList, "Ajouter les ingrédients à la liste de course"),
]
)
]
\ No newline at end of file
pub recipe_id: i64,
pub date: NaiveDate,
pub servings: u32,
+ pub add_ingredients_to_shopping_list: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
"NodeList",
"Window",
"Location",
- "Storage",
"EventTarget",
"DragEvent",
"DataTransfer",
recipe_id: i64,
date: NaiveDate,
servings: u32,
+ add_ingredients_to_shopping_list: bool,
) -> Result<ScheduleRecipeResult> {
if self.is_local {
- // storage.get(format("scheduled_recipes-{}-{}", )
- // storage.set("asd", "hello").unwrap();
+ // TODO: use 'add_ingredients_to_shopping_list'.
let mut recipe_ids_and_dates = load_scheduled_recipes(date.year(), date.month0());
for recipe in recipe_ids_and_dates.iter() {
if recipe.recipe_id == recipe_id && recipe.date == date {
recipe_id,
date,
servings,
+ add_ingredients_to_shopping_list,
},
)
.await
EventListener::new(&add_to_planner, "click", move |_event| {
spawn_local(async move {
- if let Some((date, servings)) = modal_dialog::show_and_initialize_with_ok(
- "#hidden-templates .date-and-servings",
- async |element| {
- calendar::setup(
- element.selector(".calendar"),
- calendar::CalendarOptions {
- can_select_date: true,
- with_link_and_remove: false,
- },
- recipe_scheduler,
- )
- },
- |element, calendar_state| {
- let servings_element: HtmlInputElement = element.selector("#input-servings");
- (
- calendar_state.get_selected_date(),
- servings_element.value_as_number() as u32,
- )
- },
- )
- .await
+ if let Some((date, servings, add_ingredients_to_shopping_list)) =
+ modal_dialog::show_and_initialize_with_ok(
+ "#hidden-templates .date-and-servings",
+ async |element| {
+ calendar::setup(
+ element.selector(".calendar"),
+ calendar::CalendarOptions {
+ can_select_date: true,
+ with_link_and_remove: false,
+ },
+ recipe_scheduler,
+ )
+ },
+ |element, calendar_state| {
+ let servings_element: HtmlInputElement =
+ element.selector("#input-servings");
+
+ let add_ingredients_element: HtmlInputElement =
+ element.selector("#input-add-ingredients-to-shopping-list");
+
+ (
+ calendar_state.get_selected_date(),
+ servings_element.value_as_number() as u32,
+ add_ingredients_element.checked(),
+ )
+ },
+ )
+ .await
{
if let Ok(result) = recipe_scheduler
- .shedule_recipe(recipe_id, date, servings)
+ .shedule_recipe(recipe_id, date, servings, add_ingredients_to_shopping_list)
.await
{
toast::show_element_and_initialize(