feat(backend): move product pricing to async persisted jobs

This commit is contained in:
Piotr Oleszczyk 2026-03-04 22:46:16 +01:00
parent c869f88db2
commit 0e439b4ca7
18 changed files with 468 additions and 67 deletions

View file

@ -9,6 +9,7 @@ import type {
MedicationUsage,
PartOfDay,
Product,
ProductSummary,
ProductContext,
ProductEffectProfile,
ProductInventory,
@ -70,6 +71,20 @@ export function getProducts(
return api.get(`/products${qs ? `?${qs}` : ""}`);
}
export function getProductSummaries(
params: ProductListParams = {},
): Promise<ProductSummary[]> {
const q = new URLSearchParams();
if (params.category) q.set("category", params.category);
if (params.brand) q.set("brand", params.brand);
if (params.targets) params.targets.forEach((t) => q.append("targets", t));
if (params.is_medication != null)
q.set("is_medication", String(params.is_medication));
if (params.is_tool != null) q.set("is_tool", String(params.is_tool));
const qs = q.toString();
return api.get(`/products/summary${qs ? `?${qs}` : ""}`);
}
export const getProduct = (id: string): Promise<Product> =>
api.get(`/products/${id}`);
export const createProduct = (

View file

@ -183,6 +183,19 @@ export interface Product {
inventory: ProductInventory[];
}
export interface ProductSummary {
id: string;
name: string;
brand: string;
category: ProductCategory;
recommended_time: DayTime;
targets: SkinConcern[];
is_owned: boolean;
price_tier?: PriceTier;
price_per_use_pln?: number;
price_tier_source?: PriceTierSource;
}
// ─── Routine types ───────────────────────────────────────────────────────────
export interface RoutineStep {

View file

@ -1,7 +1,7 @@
import { getProducts } from '$lib/api';
import { getProductSummaries } from '$lib/api';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
const products = await getProducts();
const products = await getProductSummaries();
return { products };
};

View file

@ -1,6 +1,6 @@
<script lang="ts">
import type { PageData } from './$types';
import type { Product } from '$lib/types';
import type { ProductSummary } from '$lib/types';
import { resolve } from '$app/paths';
import { SvelteMap } from 'svelte/reactivity';
import { m } from '$lib/paraglide/messages.js';
@ -31,8 +31,8 @@
'spf', 'mask', 'exfoliant', 'hair_treatment', 'tool', 'spot_treatment', 'oil'
];
function isOwned(p: Product): boolean {
return p.inventory?.some(inv => !inv.finished_at) ?? false;
function isOwned(p: ProductSummary): boolean {
return p.is_owned;
}
function setSort(nextKey: SortKey): void {
@ -90,7 +90,7 @@
return sortDirection === 'asc' ? cmp : -cmp;
});
const map = new SvelteMap<string, Product[]>();
const map = new SvelteMap<string, ProductSummary[]>();
for (const p of items) {
if (!map.has(p.category)) map.set(p.category, []);
map.get(p.category)!.push(p);
@ -121,8 +121,8 @@
return value;
}
function getPricePerUse(product: Product): number | undefined {
return (product as Product & { price_per_use_pln?: number }).price_per_use_pln;
function getPricePerUse(product: ProductSummary): number | undefined {
return product.price_per_use_pln;
}
function formatCategory(value: string): string {

View file

@ -1,10 +1,10 @@
import { addRoutineStep, deleteRoutine, deleteRoutineStep, getProducts, getRoutine } from '$lib/api';
import { addRoutineStep, deleteRoutine, deleteRoutineStep, getProductSummaries, getRoutine } from '$lib/api';
import { error, fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
try {
const [routine, products] = await Promise.all([getRoutine(params.id), getProducts()]);
const [routine, products] = await Promise.all([getRoutine(params.id), getProductSummaries()]);
return { routine, products };
} catch {
error(404, 'Routine not found');

View file

@ -1,9 +1,9 @@
import { addRoutineStep, createRoutine, getProducts, suggestBatch, suggestRoutine } from '$lib/api';
import { addRoutineStep, createRoutine, getProductSummaries, suggestBatch, suggestRoutine } from '$lib/api';
import { fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
const products = await getProducts();
const products = await getProductSummaries();
const today = new Date().toISOString().slice(0, 10);
return { products, today };
};