Hello from MCP server
import { getDb } from "@/dataAccess/getDb";
import { convertToPrefs, type ConvertedValue } from "@/framework/currencies";
// ============================================================================
// Tech Handbook Types and Functions (moved from composables/useTechHandbook.ts)
// ============================================================================
export interface TechHandbookItem {
content: string;
}
export interface TierTechHandbook {
tierName: string;
offerId: string;
items: string[];
}
/**
* Load tech handbook content for a single offer by ID
* Returns an array of content strings
*/
export async function loadTechHandbookForOffer(offerId: string): Promise<string[]> {
const db = await getDb();
if (!db || !offerId) return [];
const fullOffer = await db.offers.byOfferId(offerId);
if (!fullOffer?.techHandbookExpanded || fullOffer.techHandbookExpanded.length === 0) {
return [];
}
// Extract content from each contentItem and reverse to get correct order
return fullOffer.techHandbookExpanded
.map((item: TechHandbookItem) => item.content)
.filter((content: string) => content)
.reverse();
}
/**
* Load tech handbook content for multiple tiers
* Returns an array of tier names with their handbook items
*/
export async function loadTechHandbookForTiers(tiers: any[]): Promise<TierTechHandbook[]> {
const result: TierTechHandbook[] = [];
for (const tier of tiers || []) {
const tierName = tier.name || tier.title || 'Unknown Tier';
const offerId = tier.offer?.id;
if (!offerId) continue;
const items = await loadTechHandbookForOffer(offerId);
if (items.length > 0) {
result.push({ tierName, offerId, items });
}
}
return result;
}
// ============================================================================
// Job Content Hierarchy Types
// ============================================================================
export interface ContentItem {
id: string;
name: string;
refId: string;
content: string;
}
export interface CostTime {
id: string;
name: string;
refId: string;
hours: number;
}
export interface CostMaterial {
id: string;
name: string;
refId: string;
quantity: number;
quantityConverted: ConvertedValue | null;
}
export interface Offer {
id: string;
name: string;
refId: string;
costsTime: CostTime[];
costsMaterial: CostMaterial[];
techHandbook: ContentItem[];
}
export interface Tier {
id: string;
name: string;
refId: string;
rank: number;
warrantyCopy: string | null;
}
export interface MenuCopy {
id: string;
name: string;
refId: string;
contentItems: ContentItem[];
}
export interface MenuTier {
id: string;
refId: string;
tier: Tier;
offer: Offer;
menuCopy: MenuCopy[];
contentItems: ContentItem[];
}
export interface Menu {
id: string;
name: string;
refId: string;
tiers: MenuTier[];
}
export interface Problem {
id: string;
name: string;
description: string;
refId: string;
}
export interface JobContentHierarchy {
problem: Problem;
menus: Menu[];
}
// ============================================================================
// Job Content Loading Function
// ============================================================================
/**
* Given a problem ID, returns a hierarchical object with:
* - problem (with name, description, refId)
* - menus[] (each with tiers)
* - tiers[] (each with tier info, offer, menuCopy, contentItems)
* - offer (with costsTime, costsMaterial, techHandbook)
*/
export async function loadJobContentByProblemId(problemId: string): Promise<JobContentHierarchy | null> {
const db = await getDb();
if (!db || !problemId) return null;
// Get the problem
const problem = await db.problems.byId(problemId);
if (!problem) return null;
const result: JobContentHierarchy = {
problem: {
id: problem.id,
name: problem.name,
description: problem.description,
refId: problem.refId,
},
menus: [],
};
// Get menus for this problem
const menuIds = problem.menus || [];
for (const menuId of menuIds) {
// Use the existing byMenuId which already builds the hierarchy
const menuData = await db.menus.byMenuId(menuId);
if (!menuData) continue;
const menu: Menu = {
id: menuData.id,
name: menuData.name,
refId: menuData.refId,
tiers: [],
};
// Transform tiers to our structure
for (const tierData of menuData.tiers || []) {
const menuTier: MenuTier = {
id: tierData.id,
refId: tierData.refId,
tier: {
id: tierData.id,
name: tierData.name,
refId: tierData.refId,
rank: tierData.rank,
warrantyCopy: tierData.warrantyCopy || null,
},
offer: {
id: tierData.offer?.id || '',
name: tierData.offer?.name || '',
refId: tierData.offer?.refId || '',
costsTime: (tierData.offer?.costsTime || []).map((c: any) => ({
id: c.id,
name: c.name,
refId: c.refId,
hours: c.hours || 0,
})),
costsMaterial: await Promise.all((tierData.offer?.costsMaterial || []).map(async (c: any) => ({
id: c.id,
name: c.name,
refId: c.refId,
quantity: c.quantity || 0,
quantityConverted: await convertToPrefs(c.quantity || 0),
}))),
techHandbook: (tierData.offer?.techHandbookExpanded || []).map((c: any) => ({
id: c.id,
name: c.name,
refId: c.refId,
content: c.content || '',
})),
},
menuCopy: (tierData.menuCopy || []).map((mc: any) => ({
id: mc.id,
name: mc.name,
refId: mc.refId,
contentItems: (mc.contentItems || []).map((c: any) => ({
id: c.id,
name: c.name,
refId: c.refId,
content: c.content || '',
})),
})),
contentItems: (tierData.contentItems || []).map((c: any) => ({
id: c.id,
name: c.name,
refId: c.refId,
content: c.content || '',
})),
};
menu.tiers.push(menuTier);
}
result.menus.push(menu);
}
return result;
}
/**
* Given a problem refId (e.g., "PA1_problem"), returns a hierarchical object
* with problem, menus, tiers, and offers.
*/
export async function loadJobContentByProblemRefId(refId: string): Promise<JobContentHierarchy | null> {
const db = await getDb();
if (!db || !refId) return null;
// Get the problem by refId
const problem = await db.problems.byRefId(refId);
if (!problem) return null;
// Use the existing function to load the full hierarchy
return loadJobContentByProblemId(problem.id);
}