|
|
@@ -1,7 +1,7 @@
|
|
|
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
|
|
import { wrapHandler, logError } from '../_shared/error-handler.ts'
|
|
|
-import { fetchProducts } from '../_shared/shoprenter-client.ts'
|
|
|
+import { fetchProducts, fetchOrders, fetchCustomers } from '../_shared/shoprenter-client.ts'
|
|
|
import { detectCountryCode } from '../_shared/phone-formatter.ts'
|
|
|
import {
|
|
|
collectionExists,
|
|
|
@@ -265,6 +265,326 @@ async function syncProductsToQdrant(
|
|
|
return { synced, errors }
|
|
|
}
|
|
|
|
|
|
+// Sync orders to Qdrant
|
|
|
+async function syncOrdersToQdrant(
|
|
|
+ storeId: string,
|
|
|
+ storeName: string,
|
|
|
+ orders: any[],
|
|
|
+ supabaseAdmin: any
|
|
|
+): Promise<{ synced: number; errors: number }> {
|
|
|
+ const startTime = new Date()
|
|
|
+ const collectionName = getCollectionName(storeName, 'orders')
|
|
|
+
|
|
|
+ // Filter out orders without valid IDs
|
|
|
+ const validOrders = orders.filter(o => o && o.innerId)
|
|
|
+
|
|
|
+ if (validOrders.length === 0) {
|
|
|
+ console.log('[Qdrant] No valid orders to sync (all orders missing innerId)')
|
|
|
+ return { synced: 0, errors: 0 }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[Qdrant] Syncing ${validOrders.length} orders to ${collectionName}`)
|
|
|
+
|
|
|
+ let synced = 0
|
|
|
+ let errors = 0
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (!(await collectionExists(collectionName))) {
|
|
|
+ await createCollection(collectionName, [
|
|
|
+ { field: 'store_id', type: 'keyword' },
|
|
|
+ { field: 'order_id', type: 'keyword' },
|
|
|
+ { field: 'platform', type: 'keyword' },
|
|
|
+ { field: 'status', type: 'keyword' },
|
|
|
+ { field: 'total_price', type: 'float' },
|
|
|
+ { field: 'customer_email', type: 'keyword' },
|
|
|
+ { field: 'customer_phone', type: 'keyword' },
|
|
|
+ { field: 'billing_city', type: 'keyword' },
|
|
|
+ { field: 'billing_country', type: 'keyword' },
|
|
|
+ { field: 'shipping_city', type: 'keyword' },
|
|
|
+ { field: 'shipping_country', type: 'keyword' },
|
|
|
+ ])
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get existing order IDs from Qdrant to detect deletions
|
|
|
+ const existingPoints = await scrollPoints(collectionName, {
|
|
|
+ must: [{ key: 'store_id', match: { value: storeId } }]
|
|
|
+ }, 1000)
|
|
|
+
|
|
|
+ const existingOrderIds = new Set(
|
|
|
+ existingPoints.points.map((p: any) => p.payload?.order_id).filter(Boolean)
|
|
|
+ )
|
|
|
+
|
|
|
+ const currentOrderIds = new Set(validOrders.map(o => o.innerId.toString()))
|
|
|
+ const deletedOrderIds = Array.from(existingOrderIds).filter(
|
|
|
+ id => !currentOrderIds.has(id)
|
|
|
+ )
|
|
|
+
|
|
|
+ // Delete orders that no longer exist
|
|
|
+ if (deletedOrderIds.length > 0) {
|
|
|
+ console.log(`[Qdrant] Deleting ${deletedOrderIds.length} orders that no longer exist`)
|
|
|
+ await deletePointsByFilter(collectionName, {
|
|
|
+ must: [
|
|
|
+ { key: 'store_id', match: { value: storeId } },
|
|
|
+ { key: 'order_id', match: { any: deletedOrderIds } }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ await logQdrantSync(
|
|
|
+ supabaseAdmin,
|
|
|
+ storeId,
|
|
|
+ 'orders',
|
|
|
+ collectionName,
|
|
|
+ 'delete',
|
|
|
+ deletedOrderIds.length,
|
|
|
+ deletedOrderIds.length,
|
|
|
+ 0,
|
|
|
+ startTime
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate embeddings for all orders
|
|
|
+ const orderTexts = validOrders.map(order => createOrderText(order))
|
|
|
+ const embeddings = await generateEmbeddingBatch(orderTexts)
|
|
|
+
|
|
|
+ // Create Qdrant points
|
|
|
+ const points: QdrantPoint[] = validOrders.map((order, index) => {
|
|
|
+ return {
|
|
|
+ id: generatePointId('shoprenter', storeId, order.innerId),
|
|
|
+ vector: embeddings[index],
|
|
|
+ payload: {
|
|
|
+ // Basic identification
|
|
|
+ store_id: storeId,
|
|
|
+ order_id: order.innerId.toString(),
|
|
|
+ platform: 'shoprenter',
|
|
|
+ order_number: order.orderNumber || order.innerId.toString(),
|
|
|
+
|
|
|
+ // Customer information
|
|
|
+ customer_name: `${order.firstname || ''} ${order.lastname || ''}`.trim() || null,
|
|
|
+ customer_email: order.email || null,
|
|
|
+ customer_phone: order.phone || null,
|
|
|
+
|
|
|
+ // Billing address
|
|
|
+ billing_address: {
|
|
|
+ address1: order.billingAddress || null,
|
|
|
+ city: order.billingCity || null,
|
|
|
+ zip: order.billingZipCode || null,
|
|
|
+ country: order.billingCountryName || null,
|
|
|
+ },
|
|
|
+
|
|
|
+ // Shipping address
|
|
|
+ shipping_address: {
|
|
|
+ address1: order.shippingAddress || null,
|
|
|
+ city: order.shippingCity || null,
|
|
|
+ zip: order.shippingZipCode || null,
|
|
|
+ country: order.shippingCountryName || null,
|
|
|
+ },
|
|
|
+
|
|
|
+ // Pricing
|
|
|
+ total_price: parseFloat(order.total || '0') || 0,
|
|
|
+ currency: order.currency || 'HUF',
|
|
|
+
|
|
|
+ // Status
|
|
|
+ status: order.orderStatus?.name || 'unknown',
|
|
|
+
|
|
|
+ // Order items
|
|
|
+ line_items: order.orderProducts || [],
|
|
|
+
|
|
|
+ // Metadata
|
|
|
+ synced_at: new Date().toISOString(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ await upsertPoints(collectionName, points)
|
|
|
+ synced = points.length
|
|
|
+
|
|
|
+ await logQdrantSync(
|
|
|
+ supabaseAdmin,
|
|
|
+ storeId,
|
|
|
+ 'orders',
|
|
|
+ collectionName,
|
|
|
+ 'upsert',
|
|
|
+ orders.length,
|
|
|
+ synced,
|
|
|
+ errors,
|
|
|
+ startTime
|
|
|
+ )
|
|
|
+
|
|
|
+ console.log(`[Qdrant] Orders sync complete: ${synced} synced, ${deletedOrderIds.length} deleted`)
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('[Qdrant] Order sync error:', error)
|
|
|
+ errors = orders.length
|
|
|
+ await logQdrantSync(
|
|
|
+ supabaseAdmin,
|
|
|
+ storeId,
|
|
|
+ 'orders',
|
|
|
+ collectionName,
|
|
|
+ 'upsert',
|
|
|
+ orders.length,
|
|
|
+ synced,
|
|
|
+ errors,
|
|
|
+ startTime,
|
|
|
+ error.message
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return { synced, errors }
|
|
|
+}
|
|
|
+
|
|
|
+// Sync customers to Qdrant
|
|
|
+async function syncCustomersToQdrant(
|
|
|
+ storeId: string,
|
|
|
+ storeName: string,
|
|
|
+ customers: any[],
|
|
|
+ supabaseAdmin: any
|
|
|
+): Promise<{ synced: number; errors: number }> {
|
|
|
+ const startTime = new Date()
|
|
|
+ const collectionName = getCollectionName(storeName, 'customers')
|
|
|
+
|
|
|
+ // Filter out customers without valid IDs
|
|
|
+ const validCustomers = customers.filter(c => c && c.innerId)
|
|
|
+
|
|
|
+ if (validCustomers.length === 0) {
|
|
|
+ console.log('[Qdrant] No valid customers to sync (all customers missing innerId)')
|
|
|
+ return { synced: 0, errors: 0 }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[Qdrant] Syncing ${validCustomers.length} customers to ${collectionName}`)
|
|
|
+
|
|
|
+ let synced = 0
|
|
|
+ let errors = 0
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (!(await collectionExists(collectionName))) {
|
|
|
+ await createCollection(collectionName, [
|
|
|
+ { field: 'store_id', type: 'keyword' },
|
|
|
+ { field: 'customer_id', type: 'keyword' },
|
|
|
+ { field: 'platform', type: 'keyword' },
|
|
|
+ { field: 'email', type: 'keyword' },
|
|
|
+ { field: 'phone', type: 'keyword' },
|
|
|
+ { field: 'city', type: 'keyword' },
|
|
|
+ { field: 'country', type: 'keyword' },
|
|
|
+ ])
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get existing customer IDs from Qdrant to detect deletions
|
|
|
+ const existingPoints = await scrollPoints(collectionName, {
|
|
|
+ must: [{ key: 'store_id', match: { value: storeId } }]
|
|
|
+ }, 1000)
|
|
|
+
|
|
|
+ const existingCustomerIds = new Set(
|
|
|
+ existingPoints.points.map((p: any) => p.payload?.customer_id).filter(Boolean)
|
|
|
+ )
|
|
|
+
|
|
|
+ const currentCustomerIds = new Set(validCustomers.map(c => c.innerId.toString()))
|
|
|
+ const deletedCustomerIds = Array.from(existingCustomerIds).filter(
|
|
|
+ id => !currentCustomerIds.has(id)
|
|
|
+ )
|
|
|
+
|
|
|
+ // Delete customers that no longer exist
|
|
|
+ if (deletedCustomerIds.length > 0) {
|
|
|
+ console.log(`[Qdrant] Deleting ${deletedCustomerIds.length} customers that no longer exist`)
|
|
|
+ await deletePointsByFilter(collectionName, {
|
|
|
+ must: [
|
|
|
+ { key: 'store_id', match: { value: storeId } },
|
|
|
+ { key: 'customer_id', match: { any: deletedCustomerIds } }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ await logQdrantSync(
|
|
|
+ supabaseAdmin,
|
|
|
+ storeId,
|
|
|
+ 'customers',
|
|
|
+ collectionName,
|
|
|
+ 'delete',
|
|
|
+ deletedCustomerIds.length,
|
|
|
+ deletedCustomerIds.length,
|
|
|
+ 0,
|
|
|
+ startTime
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate embeddings for all customers
|
|
|
+ const customerTexts = validCustomers.map(customer => createCustomerText(customer))
|
|
|
+ const embeddings = await generateEmbeddingBatch(customerTexts)
|
|
|
+
|
|
|
+ // Create Qdrant points
|
|
|
+ const points: QdrantPoint[] = validCustomers.map((customer, index) => {
|
|
|
+ return {
|
|
|
+ id: generatePointId('shoprenter', storeId, customer.innerId),
|
|
|
+ vector: embeddings[index],
|
|
|
+ payload: {
|
|
|
+ // Basic identification
|
|
|
+ store_id: storeId,
|
|
|
+ customer_id: customer.innerId.toString(),
|
|
|
+ platform: 'shoprenter',
|
|
|
+
|
|
|
+ // Customer information
|
|
|
+ first_name: customer.firstname || null,
|
|
|
+ last_name: customer.lastname || null,
|
|
|
+ email: customer.email || null,
|
|
|
+ phone: customer.phone || null,
|
|
|
+
|
|
|
+ // Address
|
|
|
+ default_address: {
|
|
|
+ address1: customer.shippingAddress || null,
|
|
|
+ city: customer.shippingCity || null,
|
|
|
+ zip: customer.shippingZipCode || null,
|
|
|
+ country: customer.shippingCountryName || null,
|
|
|
+ },
|
|
|
+
|
|
|
+ // Billing address
|
|
|
+ billing_address: {
|
|
|
+ address1: customer.billingAddress || null,
|
|
|
+ city: customer.billingCity || null,
|
|
|
+ zip: customer.billingZipCode || null,
|
|
|
+ country: customer.billingCountryName || null,
|
|
|
+ },
|
|
|
+
|
|
|
+ // Customer stats (if available)
|
|
|
+ orders_count: customer.ordersCount || 0,
|
|
|
+ total_spent: parseFloat(customer.totalSpent || '0') || 0,
|
|
|
+
|
|
|
+ // Metadata
|
|
|
+ synced_at: new Date().toISOString(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ await upsertPoints(collectionName, points)
|
|
|
+ synced = points.length
|
|
|
+
|
|
|
+ await logQdrantSync(
|
|
|
+ supabaseAdmin,
|
|
|
+ storeId,
|
|
|
+ 'customers',
|
|
|
+ collectionName,
|
|
|
+ 'upsert',
|
|
|
+ customers.length,
|
|
|
+ synced,
|
|
|
+ errors,
|
|
|
+ startTime
|
|
|
+ )
|
|
|
+
|
|
|
+ console.log(`[Qdrant] Customers sync complete: ${synced} synced, ${deletedCustomerIds.length} deleted`)
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('[Qdrant] Customer sync error:', error)
|
|
|
+ errors = customers.length
|
|
|
+ await logQdrantSync(
|
|
|
+ supabaseAdmin,
|
|
|
+ storeId,
|
|
|
+ 'customers',
|
|
|
+ collectionName,
|
|
|
+ 'upsert',
|
|
|
+ customers.length,
|
|
|
+ synced,
|
|
|
+ errors,
|
|
|
+ startTime,
|
|
|
+ error.message
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return { synced, errors }
|
|
|
+}
|
|
|
+
|
|
|
serve(wrapHandler('shoprenter-sync', async (req) => {
|
|
|
if (req.method === 'OPTIONS') {
|
|
|
return new Response('ok', { headers: corsHeaders })
|
|
|
@@ -337,11 +657,22 @@ serve(wrapHandler('shoprenter-sync', async (req) => {
|
|
|
userId = user.id
|
|
|
}
|
|
|
|
|
|
- // Now fetch store with proper authorization
|
|
|
+ // Now fetch store with proper authorization (include sync config)
|
|
|
const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey)
|
|
|
const { data: store, error: storeError } = await supabaseAdmin
|
|
|
.from('stores')
|
|
|
- .select('id, store_name, platform_name, store_url, qdrant_sync_enabled, data_access_permissions')
|
|
|
+ .select(`
|
|
|
+ id,
|
|
|
+ store_name,
|
|
|
+ platform_name,
|
|
|
+ store_url,
|
|
|
+ qdrant_sync_enabled,
|
|
|
+ data_access_permissions,
|
|
|
+ store_sync_config (
|
|
|
+ sync_orders,
|
|
|
+ sync_customers
|
|
|
+ )
|
|
|
+ `)
|
|
|
.eq('id', storeId)
|
|
|
.eq('user_id', userId)
|
|
|
.eq('platform_name', 'shoprenter')
|
|
|
@@ -367,6 +698,11 @@ serve(wrapHandler('shoprenter-sync', async (req) => {
|
|
|
const canSyncCustomers = permissions.allow_customer_access !== false
|
|
|
const qdrantEnabled = store.qdrant_sync_enabled !== false
|
|
|
|
|
|
+ // Check store_sync_config for orders/customers sync flags
|
|
|
+ const syncConfig = (store as any).store_sync_config?.[0] || {}
|
|
|
+ const shouldSyncOrders = syncConfig.sync_orders === true
|
|
|
+ const shouldSyncCustomers = syncConfig.sync_customers === true
|
|
|
+
|
|
|
console.log('[ShopRenter] Sync permissions:', {
|
|
|
products: canSyncProducts,
|
|
|
orders: canSyncOrders,
|
|
|
@@ -374,9 +710,15 @@ serve(wrapHandler('shoprenter-sync', async (req) => {
|
|
|
qdrant: qdrantEnabled
|
|
|
})
|
|
|
|
|
|
+ console.log('[ShopRenter] Sync config:', {
|
|
|
+ syncOrders: shouldSyncOrders,
|
|
|
+ syncCustomers: shouldSyncCustomers
|
|
|
+ })
|
|
|
+
|
|
|
const syncStats = {
|
|
|
- products: { synced: 0, errors: 0 }
|
|
|
- // Note: orders and customers removed for GDPR compliance (migration 20251031_160300)
|
|
|
+ products: { synced: 0, errors: 0 },
|
|
|
+ orders: { synced: 0, errors: 0 },
|
|
|
+ customers: { synced: 0, errors: 0 }
|
|
|
}
|
|
|
|
|
|
// supabaseAdmin already created above, reuse it
|
|
|
@@ -461,22 +803,128 @@ serve(wrapHandler('shoprenter-sync', async (req) => {
|
|
|
syncStats.products.errors++
|
|
|
}
|
|
|
|
|
|
- // Note: Customer and Order caching removed for GDPR compliance (migration 20251031_160300)
|
|
|
- // Customer/order data is now accessed in real-time via webshop-data-api endpoint
|
|
|
- console.log('[ShopRenter] Skipping customer/order caching (GDPR compliance - use real-time API access)')
|
|
|
+ // Sync Orders to Qdrant (if enabled)
|
|
|
+ // Note: Not cached to database for GDPR compliance, only synced to Qdrant for AI access
|
|
|
+ const allOrders: any[] = []
|
|
|
+ let orderSyncError: Error | null = null
|
|
|
+ if (qdrantEnabled && shouldSyncOrders && canSyncOrders) {
|
|
|
+ try {
|
|
|
+ console.log('[ShopRenter] Syncing orders to Qdrant...')
|
|
|
+ let page = 0
|
|
|
+ let hasMore = true
|
|
|
+ const limit = 50
|
|
|
+
|
|
|
+ while (hasMore) {
|
|
|
+ const ordersData = await fetchOrders(storeId, page, limit)
|
|
|
+
|
|
|
+ if (ordersData.items && ordersData.items.length > 0) {
|
|
|
+ allOrders.push(...ordersData.items)
|
|
|
+
|
|
|
+ // Check if there are more pages
|
|
|
+ if (ordersData.pagination && ordersData.pagination.total) {
|
|
|
+ const totalPages = Math.ceil(ordersData.pagination.total / limit)
|
|
|
+ hasMore = page < totalPages - 1
|
|
|
+ } else {
|
|
|
+ hasMore = ordersData.items.length === limit
|
|
|
+ }
|
|
|
+
|
|
|
+ page++
|
|
|
+ } else {
|
|
|
+ hasMore = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (allOrders.length > 0) {
|
|
|
+ const qdrantResult = await syncOrdersToQdrant(storeId, store.store_name, allOrders, supabaseAdmin)
|
|
|
+ syncStats.orders.synced = qdrantResult.synced
|
|
|
+ syncStats.orders.errors = qdrantResult.errors
|
|
|
+ console.log(`[ShopRenter] Orders synced to Qdrant: ${qdrantResult.synced} synced, ${qdrantResult.errors} errors`)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[ShopRenter] Order sync error:', error)
|
|
|
+ orderSyncError = error as Error
|
|
|
+ syncStats.orders.errors++
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.log('[ShopRenter] Order sync skipped:', {
|
|
|
+ qdrantEnabled,
|
|
|
+ shouldSyncOrders,
|
|
|
+ canSyncOrders
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sync Customers to Qdrant (if enabled)
|
|
|
+ // Note: Not cached to database for GDPR compliance, only synced to Qdrant for AI access
|
|
|
+ const allCustomers: any[] = []
|
|
|
+ let customerSyncError: Error | null = null
|
|
|
+ if (qdrantEnabled && shouldSyncCustomers && canSyncCustomers) {
|
|
|
+ try {
|
|
|
+ console.log('[ShopRenter] Syncing customers to Qdrant...')
|
|
|
+ let page = 0
|
|
|
+ let hasMore = true
|
|
|
+ const limit = 50
|
|
|
+
|
|
|
+ while (hasMore) {
|
|
|
+ const customersData = await fetchCustomers(storeId, page, limit)
|
|
|
+
|
|
|
+ if (customersData.items && customersData.items.length > 0) {
|
|
|
+ allCustomers.push(...customersData.items)
|
|
|
+
|
|
|
+ // Check if there are more pages
|
|
|
+ if (customersData.pagination && customersData.pagination.total) {
|
|
|
+ const totalPages = Math.ceil(customersData.pagination.total / limit)
|
|
|
+ hasMore = page < totalPages - 1
|
|
|
+ } else {
|
|
|
+ hasMore = customersData.items.length === limit
|
|
|
+ }
|
|
|
+
|
|
|
+ page++
|
|
|
+ } else {
|
|
|
+ hasMore = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (allCustomers.length > 0) {
|
|
|
+ const qdrantResult = await syncCustomersToQdrant(storeId, store.store_name, allCustomers, supabaseAdmin)
|
|
|
+ syncStats.customers.synced = qdrantResult.synced
|
|
|
+ syncStats.customers.errors = qdrantResult.errors
|
|
|
+ console.log(`[ShopRenter] Customers synced to Qdrant: ${qdrantResult.synced} synced, ${qdrantResult.errors} errors`)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[ShopRenter] Customer sync error:', error)
|
|
|
+ customerSyncError = error as Error
|
|
|
+ syncStats.customers.errors++
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.log('[ShopRenter] Customer sync skipped:', {
|
|
|
+ qdrantEnabled,
|
|
|
+ shouldSyncCustomers,
|
|
|
+ canSyncCustomers
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
// Update BOTH stores table and store_sync_config to maintain consistency
|
|
|
const syncCompletedAt = new Date().toISOString()
|
|
|
|
|
|
- // Check if any critical errors occurred (only products now, customer/order removed for GDPR)
|
|
|
- const hasErrors = productSyncError !== null
|
|
|
- const totalErrors = syncStats.products.errors
|
|
|
- const totalSynced = syncStats.products.synced
|
|
|
+ // Check if any critical errors occurred
|
|
|
+ const hasErrors = productSyncError !== null || orderSyncError !== null || customerSyncError !== null
|
|
|
+ const totalErrors = syncStats.products.errors + syncStats.orders.errors + syncStats.customers.errors
|
|
|
+ const totalSynced = syncStats.products.synced + syncStats.orders.synced + syncStats.customers.synced
|
|
|
|
|
|
// Build error message if any errors occurred
|
|
|
let errorMessage: string | null = null
|
|
|
- if (hasErrors && productSyncError) {
|
|
|
- errorMessage = `Products: ${productSyncError.message}`
|
|
|
+ const errorParts: string[] = []
|
|
|
+ if (productSyncError) {
|
|
|
+ errorParts.push(`Products: ${productSyncError.message}`)
|
|
|
+ }
|
|
|
+ if (orderSyncError) {
|
|
|
+ errorParts.push(`Orders: ${orderSyncError.message}`)
|
|
|
+ }
|
|
|
+ if (customerSyncError) {
|
|
|
+ errorParts.push(`Customers: ${customerSyncError.message}`)
|
|
|
+ }
|
|
|
+ if (errorParts.length > 0) {
|
|
|
+ errorMessage = errorParts.join('; ')
|
|
|
}
|
|
|
|
|
|
// Update stores table (for Web UI display)
|