|
@@ -202,31 +202,84 @@ function formatCustomerForLlm(customer: any): LlmCustomer {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Convert ShopRenter order to LLM-friendly format
|
|
* Convert ShopRenter order to LLM-friendly format
|
|
|
|
|
+ *
|
|
|
|
|
+ * ShopRenter API returns orders from /orderExtend endpoint with this structure:
|
|
|
|
|
+ * - firstname, lastname, email, phone (top-level fields)
|
|
|
|
|
+ * - orderProducts[] array with product details
|
|
|
|
|
+ * - orderTotals[] array with pricing breakdown
|
|
|
|
|
+ * - shippingAddress1, shippingCity, etc.
|
|
|
|
|
+ * - paymentAddress1, paymentCity, etc.
|
|
|
|
|
+ *
|
|
|
|
|
+ * We preserve the raw data and apply cleanResponseData to remove URLs/empty values
|
|
|
*/
|
|
*/
|
|
|
-function formatOrderForLlm(order: any): LlmOrder {
|
|
|
|
|
- const customerName = order.customer_name ||
|
|
|
|
|
- (order.customer ? `${order.customer.firstname || ''} ${order.customer.lastname || ''}`.trim() : 'Unknown');
|
|
|
|
|
- const customerEmail = order.customer_email || order.customer?.email || '';
|
|
|
|
|
- const customerPhone = order.customer_phone || order.customer?.phone || undefined;
|
|
|
|
|
|
|
+function formatOrderForLlm(order: any): any {
|
|
|
|
|
+ // ShopRenter orderExtend API returns the order data in a specific structure
|
|
|
|
|
+ // We'll preserve most of the original structure but ensure key fields are present
|
|
|
|
|
|
|
|
- return {
|
|
|
|
|
|
|
+ const formattedOrder: any = {
|
|
|
id: order.id,
|
|
id: order.id,
|
|
|
- orderNumber: order.innerId || order.order_number || order.number || order.id,
|
|
|
|
|
- customer: {
|
|
|
|
|
- name: customerName,
|
|
|
|
|
- email: customerEmail,
|
|
|
|
|
- phone: customerPhone
|
|
|
|
|
- },
|
|
|
|
|
- status: order.status,
|
|
|
|
|
|
|
+ orderNumber: order.innerId || order.orderNumber || order.id,
|
|
|
|
|
+
|
|
|
|
|
+ // Customer details (top-level fields in ShopRenter API)
|
|
|
|
|
+ firstname: order.firstname || '',
|
|
|
|
|
+ lastname: order.lastname || '',
|
|
|
|
|
+ email: order.email || '',
|
|
|
|
|
+ phone: order.phone || '',
|
|
|
|
|
+
|
|
|
|
|
+ // Order status and totals
|
|
|
|
|
+ status: order.status || order.orderStatus,
|
|
|
total: order.total || '0',
|
|
total: order.total || '0',
|
|
|
- currency: order.currency || 'HUF',
|
|
|
|
|
- items: (order.items || order.line_items || []).map((item: any) => ({
|
|
|
|
|
- name: item.name || item.product_name || 'Unknown',
|
|
|
|
|
- quantity: item.quantity || 1,
|
|
|
|
|
- price: item.price || item.total || '0'
|
|
|
|
|
- })),
|
|
|
|
|
- createdAt: order.created_at || order.date_created || new Date().toISOString()
|
|
|
|
|
|
|
+ currency: order.currency || {},
|
|
|
|
|
+
|
|
|
|
|
+ // Shipping details
|
|
|
|
|
+ shippingFirstname: order.shippingFirstname,
|
|
|
|
|
+ shippingLastname: order.shippingLastname,
|
|
|
|
|
+ shippingAddress1: order.shippingAddress1,
|
|
|
|
|
+ shippingAddress2: order.shippingAddress2,
|
|
|
|
|
+ shippingCity: order.shippingCity,
|
|
|
|
|
+ shippingPostcode: order.shippingPostcode,
|
|
|
|
|
+ shippingCountryName: order.shippingCountryName,
|
|
|
|
|
+ shippingMethodName: order.shippingMethodName,
|
|
|
|
|
+
|
|
|
|
|
+ // Payment details
|
|
|
|
|
+ paymentFirstname: order.paymentFirstname,
|
|
|
|
|
+ paymentLastname: order.paymentLastname,
|
|
|
|
|
+ paymentAddress1: order.paymentAddress1,
|
|
|
|
|
+ paymentAddress2: order.paymentAddress2,
|
|
|
|
|
+ paymentCity: order.paymentCity,
|
|
|
|
|
+ paymentPostcode: order.paymentPostcode,
|
|
|
|
|
+ paymentCountryName: order.paymentCountryName,
|
|
|
|
|
+ paymentMethodName: order.paymentMethodName,
|
|
|
|
|
+
|
|
|
|
|
+ // Order items (orderProducts array)
|
|
|
|
|
+ orderProducts: order.orderProducts || [],
|
|
|
|
|
+
|
|
|
|
|
+ // Order totals breakdown
|
|
|
|
|
+ orderTotals: order.orderTotals || [],
|
|
|
|
|
+
|
|
|
|
|
+ // Additional fields
|
|
|
|
|
+ comment: order.comment,
|
|
|
|
|
+ dateCreated: order.dateCreated || order.created_at,
|
|
|
|
|
+ dateUpdated: order.dateUpdated || order.updated_at,
|
|
|
|
|
+
|
|
|
|
|
+ // Language and other metadata
|
|
|
|
|
+ language: order.language,
|
|
|
|
|
+
|
|
|
|
|
+ // Coupon info
|
|
|
|
|
+ coupon: order.coupon,
|
|
|
|
|
+
|
|
|
|
|
+ // Shipping mode
|
|
|
|
|
+ shippingMode: order.shippingMode
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
|
|
+ // Remove undefined values (cleanResponseData will handle empty strings, empty arrays, etc.)
|
|
|
|
|
+ Object.keys(formattedOrder).forEach(key => {
|
|
|
|
|
+ if (formattedOrder[key] === undefined) {
|
|
|
|
|
+ delete formattedOrder[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return formattedOrder;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -590,6 +643,8 @@ async function handleListOrders(args: Record<string, any>): Promise<ToolCallResu
|
|
|
limit = 5
|
|
limit = 5
|
|
|
} = args;
|
|
} = args;
|
|
|
|
|
|
|
|
|
|
+ console.log('[MCP ShopRenter] handleListOrders called with:', { shop_id, email, status, limit });
|
|
|
|
|
+
|
|
|
// Validate at least one filter is provided
|
|
// Validate at least one filter is provided
|
|
|
const hasFilter = createdAtMin || createdAtMax || updatedAtMin || updatedAtMax || email;
|
|
const hasFilter = createdAtMin || createdAtMax || updatedAtMin || updatedAtMax || email;
|
|
|
if (!hasFilter) {
|
|
if (!hasFilter) {
|
|
@@ -610,18 +665,21 @@ async function handleListOrders(args: Record<string, any>): Promise<ToolCallResu
|
|
|
// Validate shop exists and is ShopRenter
|
|
// Validate shop exists and is ShopRenter
|
|
|
const { data: store, error: storeError } = await supabase
|
|
const { data: store, error: storeError } = await supabase
|
|
|
.from('stores')
|
|
.from('stores')
|
|
|
- .select('id, platform_name, data_access_permissions')
|
|
|
|
|
|
|
+ .select('id, platform_name, store_name, data_access_permissions')
|
|
|
.eq('id', shop_id)
|
|
.eq('id', shop_id)
|
|
|
.eq('platform_name', 'shoprenter')
|
|
.eq('platform_name', 'shoprenter')
|
|
|
.single();
|
|
.single();
|
|
|
|
|
|
|
|
if (storeError || !store) {
|
|
if (storeError || !store) {
|
|
|
|
|
+ console.error('[MCP ShopRenter] Store not found:', { shop_id, error: storeError });
|
|
|
return {
|
|
return {
|
|
|
content: [{ type: 'text', text: JSON.stringify({ error: 'ShopRenter store not found' }) }],
|
|
content: [{ type: 'text', text: JSON.stringify({ error: 'ShopRenter store not found' }) }],
|
|
|
isError: true
|
|
isError: true
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ console.log('[MCP ShopRenter] Store found:', { id: store.id, store_name: store.store_name });
|
|
|
|
|
+
|
|
|
// Check permissions
|
|
// Check permissions
|
|
|
const permissions = store.data_access_permissions as any;
|
|
const permissions = store.data_access_permissions as any;
|
|
|
if (permissions && !permissions.allow_order_access) {
|
|
if (permissions && !permissions.allow_order_access) {
|
|
@@ -632,47 +690,127 @@ async function handleListOrders(args: Record<string, any>): Promise<ToolCallResu
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // Build filters using correct parameter names
|
|
|
|
|
- const filters: ShopRenterOrderFilters = {};
|
|
|
|
|
- if (status) filters.status = status;
|
|
|
|
|
- if (email) filters.email = email;
|
|
|
|
|
- if (createdAtMin) filters.createdAtMin = createdAtMin;
|
|
|
|
|
- if (createdAtMax) filters.createdAtMax = createdAtMax;
|
|
|
|
|
- if (updatedAtMin) filters.updatedAtMin = updatedAtMin;
|
|
|
|
|
- if (updatedAtMax) filters.updatedAtMax = updatedAtMax;
|
|
|
|
|
-
|
|
|
|
|
- // Fetch orders from ShopRenter API
|
|
|
|
|
- const response = await fetchOrders(shop_id, 0, actualLimit, filters);
|
|
|
|
|
- const orders = Array.isArray(response) ? response : (response.items || response.data || response.orders || []);
|
|
|
|
|
|
|
+ // Check if Qdrant is enabled for this store
|
|
|
|
|
+ const qdrantConfig = await getStoreQdrantConfig(shop_id);
|
|
|
|
|
+
|
|
|
|
|
+ if (qdrantConfig && qdrantConfig.enabled && qdrantConfig.syncOrders) {
|
|
|
|
|
+ console.log('[MCP ShopRenter] Using Qdrant for orders');
|
|
|
|
|
|
|
|
- // Apply limit
|
|
|
|
|
- const limitedOrders = orders.slice(0, actualLimit);
|
|
|
|
|
|
|
+ // Query from Qdrant with filters
|
|
|
|
|
+ const qdrantOrders = await queryQdrantOrders(
|
|
|
|
|
+ shop_id,
|
|
|
|
|
+ qdrantConfig.shopname,
|
|
|
|
|
+ { email, status },
|
|
|
|
|
+ actualLimit
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- // Format for LLM
|
|
|
|
|
- const formattedOrders = limitedOrders.map(formatOrderForLlm);
|
|
|
|
|
|
|
+ // Apply date filters client-side if provided
|
|
|
|
|
+ let filteredOrders = qdrantOrders;
|
|
|
|
|
|
|
|
- // Clean response data
|
|
|
|
|
- const cleanedOrders = cleanResponseData(formattedOrders);
|
|
|
|
|
|
|
+ if (createdAtMin) {
|
|
|
|
|
+ const minDate = new Date(createdAtMin);
|
|
|
|
|
+ filteredOrders = filteredOrders.filter((o: any) => {
|
|
|
|
|
+ const created = new Date(o.dateCreated || o.createdAt || 0);
|
|
|
|
|
+ return created >= minDate;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return {
|
|
|
|
|
- content: [{
|
|
|
|
|
- type: 'text',
|
|
|
|
|
- text: JSON.stringify({
|
|
|
|
|
- count: formattedOrders.length,
|
|
|
|
|
- limit: actualLimit,
|
|
|
|
|
- source: 'api',
|
|
|
|
|
- filters_applied: {
|
|
|
|
|
- createdAtMin,
|
|
|
|
|
- createdAtMax,
|
|
|
|
|
- updatedAtMin,
|
|
|
|
|
- updatedAtMax,
|
|
|
|
|
- email,
|
|
|
|
|
- status
|
|
|
|
|
- },
|
|
|
|
|
- orders: cleanedOrders
|
|
|
|
|
- })
|
|
|
|
|
- }]
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ if (createdAtMax) {
|
|
|
|
|
+ const maxDate = new Date(createdAtMax);
|
|
|
|
|
+ filteredOrders = filteredOrders.filter((o: any) => {
|
|
|
|
|
+ const created = new Date(o.dateCreated || o.createdAt || 0);
|
|
|
|
|
+ return created <= maxDate;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (updatedAtMin) {
|
|
|
|
|
+ const minDate = new Date(updatedAtMin);
|
|
|
|
|
+ filteredOrders = filteredOrders.filter((o: any) => {
|
|
|
|
|
+ const updated = new Date(o.dateUpdated || o.updatedAt || 0);
|
|
|
|
|
+ return updated >= minDate;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (updatedAtMax) {
|
|
|
|
|
+ const maxDate = new Date(updatedAtMax);
|
|
|
|
|
+ filteredOrders = filteredOrders.filter((o: any) => {
|
|
|
|
|
+ const updated = new Date(o.dateUpdated || o.updatedAt || 0);
|
|
|
|
|
+ return updated <= maxDate;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Limit after filtering
|
|
|
|
|
+ filteredOrders = filteredOrders.slice(0, actualLimit);
|
|
|
|
|
+
|
|
|
|
|
+ // Clean response data
|
|
|
|
|
+ const cleanedOrders = cleanResponseData(filteredOrders);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ count: filteredOrders.length,
|
|
|
|
|
+ limit: actualLimit,
|
|
|
|
|
+ source: 'qdrant',
|
|
|
|
|
+ filters_applied: {
|
|
|
|
|
+ createdAtMin,
|
|
|
|
|
+ createdAtMax,
|
|
|
|
|
+ updatedAtMin,
|
|
|
|
|
+ updatedAtMax,
|
|
|
|
|
+ email,
|
|
|
|
|
+ status
|
|
|
|
|
+ },
|
|
|
|
|
+ orders: cleanedOrders
|
|
|
|
|
+ })
|
|
|
|
|
+ }]
|
|
|
|
|
+ };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log('[MCP ShopRenter] Qdrant not enabled, using ShopRenter API');
|
|
|
|
|
+
|
|
|
|
|
+ // Build filters using correct parameter names
|
|
|
|
|
+ const filters: ShopRenterOrderFilters = {};
|
|
|
|
|
+ if (status) filters.status = status;
|
|
|
|
|
+ if (email) filters.email = email;
|
|
|
|
|
+ if (createdAtMin) filters.createdAtMin = createdAtMin;
|
|
|
|
|
+ if (createdAtMax) filters.createdAtMax = createdAtMax;
|
|
|
|
|
+ if (updatedAtMin) filters.updatedAtMin = updatedAtMin;
|
|
|
|
|
+ if (updatedAtMax) filters.updatedAtMax = updatedAtMax;
|
|
|
|
|
+
|
|
|
|
|
+ // Fetch orders from ShopRenter API
|
|
|
|
|
+ const response = await fetchOrders(shop_id, 0, actualLimit, filters);
|
|
|
|
|
+ const orders = Array.isArray(response) ? response : (response.items || response.data || response.orders || []);
|
|
|
|
|
+
|
|
|
|
|
+ console.log('[MCP ShopRenter] API response:', { ordersCount: orders.length });
|
|
|
|
|
+
|
|
|
|
|
+ // Apply limit
|
|
|
|
|
+ const limitedOrders = orders.slice(0, actualLimit);
|
|
|
|
|
+
|
|
|
|
|
+ // Format for LLM (now preserves all ShopRenter fields)
|
|
|
|
|
+ const formattedOrders = limitedOrders.map(formatOrderForLlm);
|
|
|
|
|
+
|
|
|
|
|
+ // Clean response data (removes URLs, empty values)
|
|
|
|
|
+ const cleanedOrders = cleanResponseData(formattedOrders);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ content: [{
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ count: formattedOrders.length,
|
|
|
|
|
+ limit: actualLimit,
|
|
|
|
|
+ source: 'api',
|
|
|
|
|
+ filters_applied: {
|
|
|
|
|
+ createdAtMin,
|
|
|
|
|
+ createdAtMax,
|
|
|
|
|
+ updatedAtMin,
|
|
|
|
|
+ updatedAtMax,
|
|
|
|
|
+ email,
|
|
|
|
|
+ status
|
|
|
|
|
+ },
|
|
|
|
|
+ orders: cleanedOrders
|
|
|
|
|
+ })
|
|
|
|
|
+ }]
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('[MCP ShopRenter] Error fetching orders:', error);
|
|
console.error('[MCP ShopRenter] Error fetching orders:', error);
|
|
|
return {
|
|
return {
|