Ingredients can now be added to shopping list when a recipe is scheduled

This commit is contained in:
Greg Burri 2025-02-10 15:02:20 +01:00
parent 15173c4842
commit ce3821b94e
12 changed files with 98 additions and 39 deletions

View file

@ -32,8 +32,8 @@ rinja = { version = "0.3" }
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",

View file

@ -186,6 +186,8 @@ CREATE TABLE [ShoppingEntry] (
-- 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 '',
@ -194,7 +196,8 @@ CREATE TABLE [ShoppingEntry] (
[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

View file

@ -795,7 +795,10 @@ VALUES ($1, $2)
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)
@ -806,7 +809,7 @@ VALUES ($1, $2, $3, $4)
.bind(recipe_id)
.bind(date)
.bind(servings)
.execute(&self.pool)
.execute(&mut *tx)
.await
{
Err(Error::Database(error))
@ -815,7 +818,33 @@ VALUES ($1, $2, $3, $4)
{
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)
}
}
}
@ -1009,13 +1038,13 @@ VALUES
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!(
@ -1054,7 +1083,7 @@ VALUES
// 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");

View file

@ -660,7 +660,13 @@ pub async fn schedule_recipe(
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()

View file

@ -145,6 +145,7 @@ pub enum Sentence {
CalendarAddToPlannerSuccess,
CalendarAddToPlannerAlreadyExists,
CalendarDateFormat, // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
CalendarAddIngredientsToShoppingList,
}
pub const DEFAULT_LANGUAGE_CODE: &str = "en";

View file

@ -84,6 +84,7 @@
{# 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"
@ -96,6 +97,15 @@
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>

View file

@ -129,6 +129,7 @@
(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"),
]
),
(
@ -261,6 +262,7 @@
(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"),
]
)
]