From a0a1108f3a8a35012cd694050e7dbdd58108c613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Mon, 29 Jun 2026 09:29:16 -0300 Subject: [PATCH] Use all-time client purchase patterns --- backend/services/analyticsService.js | 47 ++++++++++++++++++--------- backend/test/analyticsService.test.js | 6 +++- src/analytics/clients.ts | 15 ++++++--- src/pages/ClientDetails.tsx | 11 +++++-- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/backend/services/analyticsService.js b/backend/services/analyticsService.js index 74c232e..a4850d6 100644 --- a/backend/services/analyticsService.js +++ b/backend/services/analyticsService.js @@ -910,7 +910,7 @@ const getClientDetailsAnalytics = async (clientToken, range = {}) => { periodFilters.push(`data_pedido_date <= $${periodParams.length}::date`); } - const [summaryResult, periodResult] = await Promise.all([ + const [summaryResult, periodResult, patternResult] = await Promise.all([ pool.query(` ${CUSTOMER_IDENTITY_CTE} SELECT @@ -944,7 +944,20 @@ const getClientDetailsAnalytics = async (clientToken, range = {}) => { FROM identity_orders WHERE ${periodFilters.join(' AND ')} ORDER BY data_pedido_date DESC, COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text) DESC; - `, periodParams) + `, periodParams), + pool.query(` + ${CUSTOMER_IDENTITY_CTE} + SELECT + data_pedido, + data_pedido_date, + created_at, + pedido_id, + valor_pedido + FROM identity_orders + WHERE ${CUSTOMER_KEY_SQL} = $1 + AND data_pedido_date IS NOT NULL + ORDER BY data_pedido_date DESC, COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text) DESC; + `, [resolvedCustomerKey]) ]); const summary = summaryResult.rows[0] || {}; @@ -962,25 +975,29 @@ const getClientDetailsAnalytics = async (clientToken, range = {}) => { let periodSpent = 0; let periodItems = 0; + patternResult.rows.forEach(row => { + const groupKey = getOrderGroupKey(row); + + if (patternOrderKeys.has(groupKey)) return; + patternOrderKeys.add(groupKey); + + const weekdayIndex = getWeekdayIndex(row.data_pedido_date) ?? getWeekdayIndex(row.data_pedido); + if (weekdayIndex !== null) { + weekdayCounts[weekdayIndex].value += 1; + } + + const orderHour = getHourFromTimestamp(row.created_at) ?? getHourFromTimestamp(row.data_pedido); + if (orderHour !== null) { + hourCounts[orderHour].value += 1; + } + }); + periodResult.rows.forEach(row => { const itemRevenue = toNumber(row.quantidade) * toNumber(row.valor_unitario); const dateKey = getDateOnly(row.data_pedido_date) || getDateOnly(row.data_pedido) || ''; const dateLabel = row.data_pedido || dateKey; const groupKey = getOrderGroupKey(row); - if (!patternOrderKeys.has(groupKey)) { - patternOrderKeys.add(groupKey); - const weekdayIndex = getWeekdayIndex(row.data_pedido_date) ?? getWeekdayIndex(row.data_pedido); - if (weekdayIndex !== null) { - weekdayCounts[weekdayIndex].value += 1; - } - - const orderHour = getHourFromTimestamp(row.created_at) ?? getHourFromTimestamp(row.data_pedido); - if (orderHour !== null) { - hourCounts[orderHour].value += 1; - } - } - periodSpent += itemRevenue; periodItems += toNumber(row.quantidade); diff --git a/backend/test/analyticsService.test.js b/backend/test/analyticsService.test.js index fe8d7a9..5b5707c 100644 --- a/backend/test/analyticsService.test.js +++ b/backend/test/analyticsService.test.js @@ -805,7 +805,7 @@ test('getClientDetailsAnalytics resolves legacy name tokens to the canonical pho try { const details = await getClientDetailsAnalytics(clientToken, { start: '2026-06-01', end: '2026-06-15' }); - assert.equal(calls.length, 4); + assert.equal(calls.length, 5); assert.match(calls[0].sql, /FROM client_identity_tokens/); assert.deepEqual(calls[0].params, [clientToken]); assert.match(calls[1].sql, /SELECT customer_key/); @@ -816,6 +816,10 @@ test('getClientDetailsAnalytics resolves legacy name tokens to the canonical pho assert.match(calls[3].sql, /data_pedido_date >= \$2::date/); assert.match(calls[3].sql, /data_pedido_date <= \$3::date/); assert.deepEqual(calls[3].params, ['(16) 99999-9999', '2026-06-01', '2026-06-15']); + assert.match(calls[4].sql, /created_at/); + assert.doesNotMatch(calls[4].sql, /data_pedido_date >=/); + assert.doesNotMatch(calls[4].sql, /data_pedido_date <=/); + assert.deepEqual(calls[4].params, ['(16) 99999-9999']); assert.equal(details.clientName, 'Cliente Sem Fone'); assert.equal(details.clientPhone, '(16) 99999-9999'); assert.equal(details.allTimeOrderCount, 3); diff --git a/src/analytics/clients.ts b/src/analytics/clients.ts index 534b419..a2d6f6a 100644 --- a/src/analytics/clients.ts +++ b/src/analytics/clients.ts @@ -260,14 +260,21 @@ export const buildClientDetailsMetrics = (ordersData: OrderData[], customerKey: value: 0 })); - groupedOrders.forEach(group => { - const firstItem = group.items[0]; - const orderDate = parseOrderDate(group.date); + const patternOrders = new Map(); + clientOrders.forEach(order => { + const key = order.ID_Pedido || `${order.Data_Pedido}_${order.Valor_Pedido}`; + if (!patternOrders.has(key)) { + patternOrders.set(key, order); + } + }); + + patternOrders.forEach(order => { + const orderDate = parseOrderDate(order.Data_Pedido); if (!Number.isNaN(orderDate.getTime())) { weekdayCounts[orderDate.getDay()].value += 1; } - const orderHour = firstItem ? getOrderHour(firstItem) : null; + const orderHour = getOrderHour(order); if (orderHour !== null) { hourCounts[orderHour].value += 1; } diff --git a/src/pages/ClientDetails.tsx b/src/pages/ClientDetails.tsx index e1224d8..e9092d8 100644 --- a/src/pages/ClientDetails.tsx +++ b/src/pages/ClientDetails.tsx @@ -291,9 +291,14 @@ const ClientDetails = () => {
-
-

Padrão de Compra

-

Quando este cliente costuma comprar.

+
+
+

Padrão de Compra

+

Quando este cliente costuma comprar.

+
+ + Todo período +