Hello from MCP server
import type { OffersRecord, CostsTimeRecord, CostsMaterialRecord, MenusRecord, MenuTiersRecord, TiersRecord } from "@/pocketbase-types";
import { getDb } from "@/dataAccess/getDb";
import calculatePrice, { type FormulaStep } from "@/framework/calculatePrice";
import { convertToPrefs, type ConvertedValue } from "@/framework/currencies";
export interface EnrichedCostsMaterial extends CostsMaterialRecord {
quantityConverted: ConvertedValue | null;
}
export interface EnrichedOffer extends Omit<OffersRecord, "costsTime" | "costsMaterial"> {
costsTime: CostsTimeRecord[];
costsMaterial: EnrichedCostsMaterial[];
menuName: string | null;
menuTier: string | null;
finalPrice: number | null;
finalPriceConverted: ConvertedValue | null;
formulaSteps: FormulaStep[] | null;
derivedVars: Record<string, number> | null;
derivedVarsConverted: Record<string, ConvertedValue> | null;
}
export interface PriceListData {
offers: EnrichedOffer[];
total: number;
}
export interface LoadPriceListOptions {
formulaFactory?: () => any;
limit?: number;
}
export async function loadPriceList(options: LoadPriceListOptions = {}): Promise<PriceListData> {
const { formulaFactory, limit = 25 } = options;
const db = await getDb();
// Load all data in parallel
const [offers, costsTime, costsMaterial, menus, menuTiers, tiers] = await Promise.all([
db.selectAll("offers"),
db.selectAll("costsTime"),
db.selectAll("costsMaterial"),
db.selectAll("menus"),
db.selectAll("menuTiers"),
db.selectAll("tiers"),
]);
// Build lookup maps for O(1) access
const timeMap = new Map<string, CostsTimeRecord>();
for (const cost of costsTime || []) {
timeMap.set(cost.id, cost);
}
const materialMap = new Map<string, CostsMaterialRecord>();
for (const cost of costsMaterial || []) {
materialMap.set(cost.id, cost);
}
const menusMap = new Map<string, MenusRecord>();
for (const menu of menus || []) {
menusMap.set(menu.id, menu);
}
const tiersMap = new Map<string, TiersRecord>();
for (const tier of tiers || []) {
tiersMap.set(tier.id, tier);
}
// Parse JSON string or return array as-is (defined early for use in lookups)
function parseIds(value: unknown): string[] {
if (!value) return [];
if (Array.isArray(value)) return value;
if (typeof value === "string") {
try {
return JSON.parse(value);
} catch {
return [];
}
}
return [];
}
// Build reverse lookup: offer ID -> menuTier
// offers field may be an array of offer IDs
const offerToMenuTier = new Map<string, MenuTiersRecord>();
for (const mt of menuTiers || []) {
const offerIds = parseIds(mt.offers);
for (const offerId of offerIds) {
offerToMenuTier.set(offerId, mt);
}
}
// Get total count and apply limit
const allOffers = offers || [];
const total = allOffers.length;
const limitedOffers = allOffers.slice(0, limit);
// Enrich offers with resolved cost objects and menu info
const enrichedOffers: EnrichedOffer[] = await Promise.all(
limitedOffers.map(async (offer: OffersRecord) => {
const timeIds = parseIds(offer.costsTime);
const materialIds = parseIds(offer.costsMaterial);
// Find menu and tier info via menuTiers
const menuTierRecord = offerToMenuTier.get(offer.id);
// menus is an array - take the first one
const menuIds = menuTierRecord ? parseIds(menuTierRecord.menus) : [];
const menu = menuIds.length > 0 ? menusMap.get(menuIds[0]) : null;
// tiers may be a single ID or need parsing
const tierIds = menuTierRecord ? parseIds(menuTierRecord.tiers) : [];
const tier = tierIds.length > 0 ? tiersMap.get(tierIds[0]) : null;
// Resolve cost arrays
const resolvedCostsTime = timeIds
.map((id) => timeMap.get(id))
.filter((c): c is CostsTimeRecord => c !== undefined);
const resolvedCostsMaterialRaw = materialIds
.map((id) => materialMap.get(id))
.filter((c): c is CostsMaterialRecord => c !== undefined);
// Convert costsMaterial quantities to user's preferred currency
const resolvedCostsMaterial: EnrichedCostsMaterial[] = await Promise.all(
resolvedCostsMaterialRaw.map(async (cost) => ({
...cost,
quantityConverted: cost.quantity ? await convertToPrefs(Number(cost.quantity)) : null,
}))
);
// Calculate price if formula provided
let finalPrice: number | null = null;
let finalPriceConverted: ConvertedValue | null = null;
let formulaSteps: FormulaStep[] | null = null;
let derivedVars: Record<string, number> | null = null;
let derivedVarsConverted: Record<string, ConvertedValue> | null = null;
if (formulaFactory) {
const formula = formulaFactory();
const result = await calculatePrice(resolvedCostsMaterial, resolvedCostsTime, formula);
finalPrice = result.finalPrice;
finalPriceConverted = result.finalPriceConverted;
formulaSteps = result.formulaSteps;
derivedVars = result.derivedVars;
derivedVarsConverted = result.derivedVarsConverted;
}
return {
...offer,
costsTime: resolvedCostsTime,
costsMaterial: resolvedCostsMaterial,
menuName: menu?.refId || null,
menuTier: tier?.refId || null,
finalPrice,
finalPriceConverted,
formulaSteps,
derivedVars,
derivedVarsConverted,
};
})
);
return {
offers: enrichedOffers,
total,
};
}