Add seller performance dashboard charts
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 35s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 35s
This commit is contained in:
@@ -43,6 +43,13 @@ const CUSTOMER_IDENTITY_CTE = `
|
||||
`;
|
||||
const CUSTOMER_KEY_SQL = 'customer_key';
|
||||
const TRAILING_SELLER_ID_SQL_PATTERN = '[[:space:]]*#([0-9]+)[[:space:]]*$';
|
||||
const SELLER_ID_SQL = `
|
||||
COALESCE(
|
||||
NULLIF(TRIM(id_vendedor), ''),
|
||||
substring(NULLIF(TRIM(nome_vendedor), '') from '${TRAILING_SELLER_ID_SQL_PATTERN}')
|
||||
)
|
||||
`;
|
||||
const SELLER_NAME_SQL = `NULLIF(TRIM(regexp_replace(COALESCE(nome_vendedor, ''), '${TRAILING_SELLER_ID_SQL_PATTERN}', '')), '')`;
|
||||
|
||||
const getClientTokenSecret = () => (
|
||||
process.env.CLIENT_TOKEN_SECRET ||
|
||||
@@ -462,7 +469,7 @@ const buildRfmClients = (baseClients) => {
|
||||
|
||||
const getDashboardAnalytics = async (range = {}) => {
|
||||
const { params, whereClause } = buildDateFilter(range);
|
||||
const [totalsResult, salesResult, revenueResult] = await Promise.all([
|
||||
const [totalsResult, salesResult, revenueResult, sellerRevenueResult, sellerOrdersResult] = await Promise.all([
|
||||
pool.query(`
|
||||
SELECT
|
||||
COALESCE(SUM(quantidade * valor_unitario), 0) as total_revenue,
|
||||
@@ -492,6 +499,50 @@ const getDashboardAnalytics = async (range = {}) => {
|
||||
GROUP BY name
|
||||
ORDER BY value DESC
|
||||
LIMIT 10;
|
||||
`, params),
|
||||
pool.query(`
|
||||
WITH seller_orders AS (
|
||||
SELECT
|
||||
COALESCE(${SELLER_ID_SQL}, 'name:' || ${SELLER_NAME_SQL}) as seller_key,
|
||||
COALESCE(${SELLER_NAME_SQL}, ${SELLER_ID_SQL}, 'Sem vendedor') as seller_name,
|
||||
quantidade,
|
||||
valor_unitario,
|
||||
pedido_id,
|
||||
data_pedido,
|
||||
valor_pedido
|
||||
FROM orders
|
||||
${whereClause}
|
||||
AND (${SELLER_ID_SQL} IS NOT NULL OR ${SELLER_NAME_SQL} IS NOT NULL)
|
||||
)
|
||||
SELECT
|
||||
seller_key as id,
|
||||
MAX(seller_name) as name,
|
||||
COALESCE(SUM(quantidade * valor_unitario), 0) as value
|
||||
FROM seller_orders
|
||||
GROUP BY seller_key
|
||||
ORDER BY value DESC
|
||||
LIMIT 10;
|
||||
`, params),
|
||||
pool.query(`
|
||||
WITH seller_orders AS (
|
||||
SELECT
|
||||
COALESCE(${SELLER_ID_SQL}, 'name:' || ${SELLER_NAME_SQL}) as seller_key,
|
||||
COALESCE(${SELLER_NAME_SQL}, ${SELLER_ID_SQL}, 'Sem vendedor') as seller_name,
|
||||
pedido_id,
|
||||
data_pedido,
|
||||
valor_pedido
|
||||
FROM orders
|
||||
${whereClause}
|
||||
AND (${SELLER_ID_SQL} IS NOT NULL OR ${SELLER_NAME_SQL} IS NOT NULL)
|
||||
)
|
||||
SELECT
|
||||
seller_key as id,
|
||||
MAX(seller_name) as name,
|
||||
COUNT(DISTINCT COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text))::int as value
|
||||
FROM seller_orders
|
||||
GROUP BY seller_key
|
||||
ORDER BY value DESC
|
||||
LIMIT 10;
|
||||
`, params)
|
||||
]);
|
||||
|
||||
@@ -517,6 +568,16 @@ const getDashboardAnalytics = async (range = {}) => {
|
||||
name: row.name,
|
||||
id: row.id,
|
||||
value: toNumber(row.value)
|
||||
})),
|
||||
revenueBySeller: sellerRevenueResult.rows.map(row => ({
|
||||
name: row.name,
|
||||
id: row.id,
|
||||
value: toNumber(row.value)
|
||||
})),
|
||||
ordersBySeller: sellerOrdersResult.rows.map(row => ({
|
||||
name: row.name,
|
||||
id: row.id,
|
||||
value: toNumber(row.value)
|
||||
}))
|
||||
};
|
||||
};
|
||||
@@ -717,11 +778,8 @@ const getClientFilterOptions = async () => {
|
||||
pool.query(`
|
||||
WITH seller_options AS (
|
||||
SELECT
|
||||
COALESCE(
|
||||
NULLIF(TRIM(id_vendedor), ''),
|
||||
substring(NULLIF(TRIM(nome_vendedor), '') from '${TRAILING_SELLER_ID_SQL_PATTERN}')
|
||||
) as id,
|
||||
NULLIF(TRIM(regexp_replace(COALESCE(nome_vendedor, ''), '${TRAILING_SELLER_ID_SQL_PATTERN}', '')), '') as name
|
||||
${SELLER_ID_SQL} as id,
|
||||
${SELLER_NAME_SQL} as name
|
||||
FROM orders
|
||||
WHERE (
|
||||
NULLIF(TRIM(id_vendedor), '') IS NOT NULL
|
||||
|
||||
@@ -11,6 +11,7 @@ const {
|
||||
getClientAnalytics,
|
||||
getClientDetailsAnalytics,
|
||||
getClientFilterOptions,
|
||||
getDashboardAnalytics,
|
||||
getPreviousDate,
|
||||
getProductAnalytics,
|
||||
getProductDetailsAnalytics,
|
||||
@@ -135,6 +136,69 @@ test('getPreviousDate returns the calendar day before an ISO date', () => {
|
||||
assert.equal(getPreviousDate('invalid'), null);
|
||||
});
|
||||
|
||||
test('getDashboardAnalytics includes seller revenue and order metrics', async () => {
|
||||
const originalQuery = pool.query;
|
||||
const calls = [];
|
||||
|
||||
pool.query = async (sql, params = []) => {
|
||||
calls.push({ sql, params });
|
||||
|
||||
if (sql.includes('total_revenue')) {
|
||||
return {
|
||||
rows: [{
|
||||
total_revenue: 1000,
|
||||
total_items: 25,
|
||||
order_line_count: 5
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
if (sql.includes('seller_orders') && sql.includes('SUM(quantidade * valor_unitario)')) {
|
||||
return {
|
||||
rows: [{ id: '977226210', name: 'KEDMA DA SILVA', value: 750 }]
|
||||
};
|
||||
}
|
||||
|
||||
if (sql.includes('seller_orders') && sql.includes('COUNT(DISTINCT')) {
|
||||
return {
|
||||
rows: [{ id: '977226210', name: 'KEDMA DA SILVA', value: 3 }]
|
||||
};
|
||||
}
|
||||
|
||||
if (sql.includes('SUM(quantidade), 0) as value')) {
|
||||
return {
|
||||
rows: [{ id: 'P1', name: 'Produto A', value: 10 }]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
rows: [{ id: 'P1', name: 'Produto A', value: 1000 }]
|
||||
};
|
||||
};
|
||||
|
||||
try {
|
||||
const dashboard = await getDashboardAnalytics({ start: '2026-06-01', end: '2026-06-24' });
|
||||
const sellerRevenueQuery = calls.find(call => call.sql.includes('seller_orders') && call.sql.includes('SUM(quantidade * valor_unitario)'));
|
||||
const sellerOrdersQuery = calls.find(call => call.sql.includes('seller_orders') && call.sql.includes('COUNT(DISTINCT'));
|
||||
|
||||
assert.equal(calls.length, 5);
|
||||
calls.forEach(call => {
|
||||
assert.deepEqual(call.params, ['2026-06-01', '2026-06-24']);
|
||||
});
|
||||
assert.match(sellerRevenueQuery.sql, /substring\(NULLIF\(TRIM\(nome_vendedor\), ''\) from/);
|
||||
assert.match(sellerRevenueQuery.sql, /regexp_replace\(COALESCE\(nome_vendedor, ''\)/);
|
||||
assert.match(sellerOrdersQuery.sql, /COUNT\(DISTINCT COALESCE/);
|
||||
assert.deepEqual(dashboard.revenueBySeller, [
|
||||
{ id: '977226210', name: 'KEDMA DA SILVA', value: 750 }
|
||||
]);
|
||||
assert.deepEqual(dashboard.ordersBySeller, [
|
||||
{ id: '977226210', name: 'KEDMA DA SILVA', value: 3 }
|
||||
]);
|
||||
} finally {
|
||||
pool.query = originalQuery;
|
||||
}
|
||||
});
|
||||
|
||||
test('client tokens are opaque and stable for customer keys', () => {
|
||||
const phoneKey = '(16) 99103-6131';
|
||||
const nameKey = 'name:Cliente Sem Fone';
|
||||
|
||||
Reference in New Issue
Block a user