Fas 3 steg 3b: minimala efterfrågor + hushållsantagandeprofiler per ingrediens
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
} from "@app/analytics";
|
||||
import { trackProductAnalytics } from "../lib/helpers.js";
|
||||
import { completeCookingSessionCore } from "../lib/cooking.js";
|
||||
import { updateCookingAssumptionProfile } from "@app/inventory-engine";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
@@ -137,6 +138,7 @@ export async function cookingSessionRoutes(app: FastifyInstance) {
|
||||
/**
|
||||
* Complete en session.
|
||||
* I steg 3a: anropar samma logik som gamla /cook, men länkar allt till cookingSessionId.
|
||||
* I steg 3b: sparar svar på max 2–3 frågor och uppdaterar hushållsantagandeprofiler.
|
||||
*/
|
||||
app.post("/v1/cooking-sessions/:id/complete", auth, async (req) => {
|
||||
const { id } = parse(idParamSchema, req.params);
|
||||
@@ -146,7 +148,83 @@ export async function cookingSessionRoutes(app: FastifyInstance) {
|
||||
throw errors.conflict("Sessionen måste vara startad för att avslutas.");
|
||||
}
|
||||
|
||||
const result = await completeCookingSessionCore(app, session, req.userId, input, req.correlationId);
|
||||
const mealBoxPortions = input.mealBoxPortions ?? 0;
|
||||
const plannedPortions = input.portionsCooked ?? session.plannedPortions;
|
||||
const actualPortionsEaten = input.actualPortionsEaten ?? Math.max(0, plannedPortions - mealBoxPortions);
|
||||
const leftoverEstimatePortions = input.leftoverEstimatePortions ?? mealBoxPortions;
|
||||
|
||||
if (actualPortionsEaten + leftoverEstimatePortions > plannedPortions) {
|
||||
throw errors.badRequest("Åtna portioner + rester får inte överstiga totalt antal portioner.");
|
||||
}
|
||||
|
||||
const result = await completeCookingSessionCore(
|
||||
app,
|
||||
session,
|
||||
req.userId,
|
||||
{ ...input, portionsCooked: plannedPortions, mealBoxPortions },
|
||||
req.correlationId,
|
||||
);
|
||||
|
||||
await app.db
|
||||
.update(schema.cookingSessions)
|
||||
.set({
|
||||
actualPortionsEaten,
|
||||
leftoverEstimatePortions,
|
||||
leftoverNote: input.leftoverNote ?? null,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.cookingSessions.id, id));
|
||||
|
||||
// Uppdatera antagandeprofiler per ingrediens (hushållsnivå).
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
for (const ing of result.recipeIngredients) {
|
||||
if (ing.optional) continue;
|
||||
const [existing] = await app.db
|
||||
.select()
|
||||
.from(schema.cookingAssumptionProfiles)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.cookingAssumptionProfiles.householdId, session.householdId),
|
||||
eq(schema.cookingAssumptionProfiles.canonicalIngredientId, ing.canonicalIngredientId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
const updatedProfile = updateCookingAssumptionProfile(
|
||||
{
|
||||
householdId: session.householdId,
|
||||
canonicalIngredientId: ing.canonicalIngredientId,
|
||||
plannedPortions,
|
||||
actualPortionsEaten,
|
||||
leftoverEstimatePortions,
|
||||
sessionId: id,
|
||||
date,
|
||||
},
|
||||
existing ?? { averageEatenPortions: null, averageLeftoverPortions: null, observationCount: 0 },
|
||||
);
|
||||
|
||||
await app.db
|
||||
.insert(schema.cookingAssumptionProfiles)
|
||||
.values({
|
||||
householdId: session.householdId,
|
||||
canonicalIngredientId: ing.canonicalIngredientId,
|
||||
...updatedProfile,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: [
|
||||
schema.cookingAssumptionProfiles.householdId,
|
||||
schema.cookingAssumptionProfiles.canonicalIngredientId,
|
||||
],
|
||||
set: {
|
||||
averageEatenPortions: updatedProfile.averageEatenPortions,
|
||||
averageLeftoverPortions: updatedProfile.averageLeftoverPortions,
|
||||
observationCount: updatedProfile.observationCount,
|
||||
lastSessionAnswers: updatedProfile.lastSessionAnswers,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const [updated] = await app.db
|
||||
.select()
|
||||
@@ -162,8 +240,10 @@ export async function cookingSessionRoutes(app: FastifyInstance) {
|
||||
properties: {
|
||||
cookingSessionId: id,
|
||||
recipeId: session.recipeId,
|
||||
portionsCooked: session.plannedPortions,
|
||||
mealBoxPortions: input.mealBoxPortions ?? 0,
|
||||
portionsCooked: plannedPortions,
|
||||
actualPortionsEaten,
|
||||
leftoverEstimatePortions,
|
||||
mealBoxPortions,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -171,6 +251,39 @@ export async function cookingSessionRoutes(app: FastifyInstance) {
|
||||
return { ...result, session: updated };
|
||||
});
|
||||
|
||||
/** Hämta antagandeprofil för ett recept (per hushåll + ingrediens). */
|
||||
app.get("/v1/recipes/:id/cooking-assumptions", auth, async (req) => {
|
||||
const { id } = parse(idParamSchema, req.params);
|
||||
const householdId = await requireActiveHousehold(app.db, req.userId);
|
||||
await requireMembership(app.db, householdId, req.userId);
|
||||
|
||||
const ings = await app.db
|
||||
.select({ canonicalIngredientId: schema.recipeIngredients.canonicalIngredientId })
|
||||
.from(schema.recipeIngredients)
|
||||
.where(eq(schema.recipeIngredients.recipeId, id));
|
||||
if (ings.length === 0) throw errors.notFound("Receptet finns inte.");
|
||||
|
||||
const profiles = await app.db
|
||||
.select()
|
||||
.from(schema.cookingAssumptionProfiles)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.cookingAssumptionProfiles.householdId, householdId),
|
||||
eq(schema.cookingAssumptionProfiles.canonicalIngredientId, ings[0]!.canonicalIngredientId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
const p = profiles[0];
|
||||
return {
|
||||
householdId,
|
||||
recipeId: id,
|
||||
defaultActualPortionsEaten: p?.averageEatenPortions ?? null,
|
||||
defaultLeftoverEstimatePortions: p?.averageLeftoverPortions ?? null,
|
||||
observationCount: p?.observationCount ?? 0,
|
||||
};
|
||||
});
|
||||
|
||||
/** Lista hushållets aktiva sessioner. */
|
||||
app.get("/v1/cooking-sessions", auth, async (req) => {
|
||||
const householdId = await requireActiveHousehold(app.db, req.userId);
|
||||
|
||||
Reference in New Issue
Block a user