Add seller analytics pages
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m47s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m47s
This commit is contained in:
@@ -6,6 +6,8 @@ const {
|
||||
getClientFilterOptions,
|
||||
getClientPurchasePatternAnalytics,
|
||||
getDashboardAnalytics,
|
||||
getSellerAnalytics,
|
||||
getSellerDetailsAnalytics,
|
||||
getProductAnalytics,
|
||||
getProductDetailsAnalytics,
|
||||
getRfmAnalytics
|
||||
@@ -35,6 +37,29 @@ router.get('/analytics/dashboard', verifyToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/analytics/sellers', verifyToken, async (req, res) => {
|
||||
try {
|
||||
res.json(await getSellerAnalytics(getRange(req.query)));
|
||||
} catch (error) {
|
||||
console.error('Error fetching seller analytics:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/analytics/sellers/:sellerId/details', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const details = await getSellerDetailsAnalytics(req.params.sellerId, getRange(req.query));
|
||||
if (!details) {
|
||||
res.status(404).json({ error: 'Seller not found' });
|
||||
return;
|
||||
}
|
||||
res.json(details);
|
||||
} catch (error) {
|
||||
console.error('Error fetching seller details analytics:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/analytics/products', verifyToken, async (req, res) => {
|
||||
try {
|
||||
res.json(await getProductAnalytics(getRange(req.query)));
|
||||
|
||||
@@ -694,6 +694,119 @@ const getDashboardAnalytics = async (range = {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
const getSellerAnalytics = async (range = {}) => {
|
||||
const { params, whereClause } = buildDateFilter(range);
|
||||
const result = await pool.query(`
|
||||
WITH seller_orders AS (
|
||||
SELECT
|
||||
COALESCE(${SELLER_ID_SQL}, 'name:' || ${SELLER_NAME_SQL}) as seller_id,
|
||||
COALESCE(${SELLER_NAME_SQL}, ${SELLER_ID_SQL}, 'Sem vendedor') as seller_name,
|
||||
quantidade,
|
||||
valor_unitario,
|
||||
pedido_id,
|
||||
data_pedido,
|
||||
valor_pedido,
|
||||
data_pedido_date,
|
||||
COALESCE(NULLIF(cliente_fone, ''), 'name:' || COALESCE(${NORMALIZED_CUSTOMER_NAME_SQL}, 'desconhecido')) as customer_key
|
||||
FROM orders
|
||||
${whereClause}
|
||||
AND (${SELLER_ID_SQL} IS NOT NULL OR ${SELLER_NAME_SQL} IS NOT NULL)
|
||||
)
|
||||
SELECT
|
||||
seller_id as id,
|
||||
MAX(seller_name) as name,
|
||||
COALESCE(SUM(quantidade * valor_unitario), 0) as revenue,
|
||||
COALESCE(SUM(quantidade), 0) as quantity_sold,
|
||||
COUNT(DISTINCT COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text))::int as order_count,
|
||||
COUNT(DISTINCT customer_key)::int as customer_count,
|
||||
MAX(data_pedido_date) as last_order_date
|
||||
FROM seller_orders
|
||||
GROUP BY seller_id
|
||||
ORDER BY revenue DESC, order_count DESC, name ASC;
|
||||
`, params);
|
||||
|
||||
return result.rows.map(row => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
revenue: toNumber(row.revenue),
|
||||
quantitySold: toNumber(row.quantity_sold),
|
||||
orderCount: toNumber(row.order_count),
|
||||
customerCount: toNumber(row.customer_count),
|
||||
lastOrderDate: row.last_order_date,
|
||||
averageTicket: toNumber(row.order_count) ? toNumber(row.revenue) / toNumber(row.order_count) : 0
|
||||
}));
|
||||
};
|
||||
|
||||
const getSellerDetailsAnalytics = async (sellerId, range = {}) => {
|
||||
const seller = normalizeSellerFilter(sellerId);
|
||||
if (!seller) return null;
|
||||
|
||||
const sellerRange = { ...range, seller: sellerId };
|
||||
const { params, whereClause } = buildDateFilter(sellerRange);
|
||||
const [summaryResult, trendResult, marketplaceResult, products, clients] = await Promise.all([
|
||||
pool.query(`
|
||||
SELECT
|
||||
COALESCE(MAX(${SELLER_NAME_SQL}), MAX(${SELLER_ID_SQL}), 'Sem vendedor') as name,
|
||||
COALESCE(SUM(quantidade * valor_unitario), 0) as revenue,
|
||||
COALESCE(SUM(quantidade), 0) as quantity_sold,
|
||||
COUNT(DISTINCT COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text))::int as order_count,
|
||||
MAX(data_pedido_date) as last_order_date
|
||||
FROM orders
|
||||
${whereClause};
|
||||
`, params),
|
||||
pool.query(`
|
||||
SELECT
|
||||
data_pedido_date::text as date,
|
||||
COALESCE(SUM(quantidade * valor_unitario), 0) as revenue,
|
||||
COALESCE(SUM(quantidade), 0) as quantity_sold,
|
||||
COUNT(DISTINCT COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text))::int as order_count
|
||||
FROM orders
|
||||
${whereClause}
|
||||
GROUP BY data_pedido_date
|
||||
ORDER BY data_pedido_date ASC;
|
||||
`, params),
|
||||
pool.query(`
|
||||
SELECT
|
||||
COALESCE(NULLIF(TRIM(marketplace), ''), 'Sem marketplace') as name,
|
||||
COALESCE(SUM(quantidade * valor_unitario), 0) as revenue,
|
||||
COUNT(DISTINCT COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text))::int as order_count
|
||||
FROM orders
|
||||
${whereClause}
|
||||
GROUP BY name
|
||||
ORDER BY revenue DESC
|
||||
LIMIT 6;
|
||||
`, params),
|
||||
getProductAnalytics(sellerRange),
|
||||
getClientAnalytics(sellerRange)
|
||||
]);
|
||||
|
||||
const summary = summaryResult.rows[0];
|
||||
if (!summary || (!toNumber(summary.revenue) && !toNumber(summary.order_count))) return null;
|
||||
const orderCount = toNumber(summary.order_count);
|
||||
|
||||
return {
|
||||
seller: {
|
||||
id: String(sellerId),
|
||||
name: summary.name,
|
||||
revenue: toNumber(summary.revenue),
|
||||
quantitySold: toNumber(summary.quantity_sold),
|
||||
orderCount,
|
||||
customerCount: clients.length,
|
||||
lastOrderDate: summary.last_order_date,
|
||||
averageTicket: orderCount ? toNumber(summary.revenue) / orderCount : 0
|
||||
},
|
||||
chartData: trendResult.rows.map(row => ({
|
||||
date: row.date,
|
||||
revenue: toNumber(row.revenue),
|
||||
quantitySold: toNumber(row.quantity_sold),
|
||||
orderCount: toNumber(row.order_count)
|
||||
})),
|
||||
topProducts: products.slice(0, 8),
|
||||
topClients: clients.slice(0, 8),
|
||||
marketplaces: marketplaceResult.rows.map(row => ({ name: row.name, revenue: toNumber(row.revenue), orderCount: toNumber(row.order_count) }))
|
||||
};
|
||||
};
|
||||
|
||||
const getProductAnalytics = async (range = {}) => {
|
||||
const { params, whereClause } = buildDateFilter(range);
|
||||
const result = await pool.query(`
|
||||
@@ -1533,6 +1646,8 @@ module.exports = {
|
||||
getRfmSegment,
|
||||
getClientAnalytics,
|
||||
getDashboardAnalytics,
|
||||
getSellerAnalytics,
|
||||
getSellerDetailsAnalytics,
|
||||
getProductDetailsAnalytics,
|
||||
getProductAnalytics,
|
||||
normalizeDateParam,
|
||||
|
||||
Reference in New Issue
Block a user