All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m27s
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
import type Database from 'better-sqlite3';
|
|
|
|
// Fallback when a recipe has no servings_default set — matches the default
|
|
// used by RecipeEditor's "new recipe" template.
|
|
const DEFAULT_SERVINGS = 4;
|
|
|
|
export type ShoppingCartRecipe = {
|
|
recipe_id: number;
|
|
title: string;
|
|
image_path: string | null;
|
|
servings: number;
|
|
servings_default: number;
|
|
};
|
|
|
|
export type ShoppingListRow = {
|
|
name_key: string;
|
|
unit_key: string;
|
|
display_name: string;
|
|
display_unit: string | null;
|
|
total_quantity: number | null;
|
|
from_recipes: string;
|
|
checked: 0 | 1;
|
|
};
|
|
|
|
export type ShoppingListSnapshot = {
|
|
recipes: ShoppingCartRecipe[];
|
|
rows: ShoppingListRow[];
|
|
uncheckedCount: number;
|
|
};
|
|
|
|
export function addRecipeToCart(
|
|
db: Database.Database,
|
|
recipeId: number,
|
|
profileId: number | null,
|
|
servings?: number
|
|
): void {
|
|
const row = db
|
|
.prepare('SELECT servings_default FROM recipe WHERE id = ?')
|
|
.get(recipeId) as { servings_default: number | null } | undefined;
|
|
const resolved = servings ?? row?.servings_default ?? DEFAULT_SERVINGS;
|
|
// ON CONFLICT updates only servings — added_by_profile_id stays with the
|
|
// first profile that added the recipe (household cart, audit trail).
|
|
db.prepare(
|
|
`INSERT INTO shopping_cart_recipe (recipe_id, servings, added_by_profile_id)
|
|
VALUES (?, ?, ?)
|
|
ON CONFLICT(recipe_id) DO UPDATE SET servings = excluded.servings`
|
|
).run(recipeId, resolved, profileId);
|
|
}
|
|
|
|
export function removeRecipeFromCart(
|
|
db: Database.Database,
|
|
recipeId: number
|
|
): void {
|
|
db.prepare('DELETE FROM shopping_cart_recipe WHERE recipe_id = ?').run(recipeId);
|
|
}
|
|
|
|
export function setCartServings(
|
|
_db: Database.Database,
|
|
_recipeId: number,
|
|
_servings: number
|
|
): void {
|
|
throw new Error('not implemented');
|
|
}
|
|
|
|
export function listShoppingList(db: Database.Database): ShoppingListSnapshot {
|
|
const recipes = db
|
|
.prepare(
|
|
`SELECT cr.recipe_id, r.title, r.image_path, cr.servings,
|
|
COALESCE(r.servings_default, cr.servings) AS servings_default
|
|
FROM shopping_cart_recipe cr
|
|
JOIN recipe r ON r.id = cr.recipe_id
|
|
ORDER BY cr.added_at ASC`
|
|
)
|
|
.all() as ShoppingCartRecipe[];
|
|
// TODO(Task 6): rows + uncheckedCount are populated by the aggregation query.
|
|
// Until then, callers must not rely on these fields.
|
|
return { recipes, rows: [], uncheckedCount: 0 };
|
|
}
|
|
|
|
export function toggleCheck(
|
|
_db: Database.Database,
|
|
_nameKey: string,
|
|
_unitKey: string,
|
|
_checked: boolean
|
|
): void {
|
|
throw new Error('not implemented');
|
|
}
|
|
|
|
export function clearCheckedItems(_db: Database.Database): void {
|
|
throw new Error('not implemented');
|
|
}
|
|
|
|
export function clearCart(_db: Database.Database): void {
|
|
throw new Error('not implemented');
|
|
}
|