Remove data health dashboard
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s
This commit is contained in:
@@ -1,85 +0,0 @@
|
||||
const { pool } = require('../db');
|
||||
|
||||
const normalizeText = (value) => String(value || '').replace(/\s+/g, ' ').trim();
|
||||
const normalizeSku = (value) => normalizeText(value).toUpperCase();
|
||||
const isRawOrService = (value) => /\b(?:MALHA|RIBANA|FIO|TECIDO|SERVICO|SERVIÇO|TINTURARIA|TECELAGEM|FRETE)\b/i.test(value);
|
||||
|
||||
const getDataHealthSummary = async () => {
|
||||
const [productsResult, compositionsResult, componentsResult, stockResult] = await Promise.all([
|
||||
pool.query(`
|
||||
SELECT produto_id AS id, MAX(NULLIF(produto_descricao, '')) AS name FROM orders GROUP BY produto_id
|
||||
UNION
|
||||
SELECT produto_id AS id, MAX(NULLIF(nome, '')) AS name FROM stock GROUP BY produto_id;
|
||||
`),
|
||||
pool.query(`
|
||||
SELECT id, finished_tiny_product_id, finished_product_sku, finished_product_description, finished_product_unit
|
||||
FROM product_compositions WHERE source = 'tiny_olist_v3';
|
||||
`),
|
||||
pool.query(`
|
||||
SELECT product_composition_id, component_tiny_id, component_sku, component_name, quantity_per_unit, unit
|
||||
FROM product_composition_components;
|
||||
`),
|
||||
pool.query(`SELECT produto_id, nome, COALESCE(saldo, 0)::numeric AS saldo FROM stock;`)
|
||||
]);
|
||||
|
||||
const products = Array.from(new Map(productsResult.rows.map(row => [normalizeSku(row.id), row])).values());
|
||||
const compositions = compositionsResult.rows;
|
||||
const components = componentsResult.rows;
|
||||
const stock = stockResult.rows;
|
||||
const stockKeys = new Set(stock.flatMap(item => [normalizeSku(item.produto_id), normalizeSku(item.nome)]).filter(Boolean));
|
||||
const compositionKeys = new Set(compositions.flatMap(item => [normalizeSku(item.finished_tiny_product_id), normalizeSku(item.finished_product_sku)]).filter(Boolean));
|
||||
const productKeys = new Set(products.map(item => normalizeSku(item.id)).filter(Boolean));
|
||||
const linkedComponentKeys = new Set([...stockKeys, ...productKeys]);
|
||||
const materialKeys = new Set(components.map(item => normalizeSku(item.component_tiny_id) || normalizeSku(item.component_sku) || normalizeSku(item.component_name)).filter(Boolean));
|
||||
const materialStockKeys = new Set([...materialKeys].filter(key => stockKeys.has(key)));
|
||||
const rowsByComposition = new Map(compositions.map(item => [Number(item.id), item]));
|
||||
const issues = [];
|
||||
const addIssue = (type, severity, title, detail, productSku = '', component = '') => {
|
||||
issues.push({ type, severity, title, detail, productSku, component });
|
||||
};
|
||||
|
||||
compositions.filter(row => !normalizeText(row.finished_product_sku)).forEach(row => {
|
||||
addIssue('missing_product_sku', 'critical', 'Produto sem SKU', normalizeText(row.finished_product_description) || 'Composição sem descrição', '', '');
|
||||
});
|
||||
components.filter(row => {
|
||||
const key = normalizeSku(row.component_tiny_id) || normalizeSku(row.component_sku) || normalizeSku(row.component_name);
|
||||
return !key || !linkedComponentKeys.has(key);
|
||||
}).forEach(row => {
|
||||
const parent = rowsByComposition.get(Number(row.product_composition_id));
|
||||
addIssue('component_without_stock_link', 'attention', 'Componente sem vínculo de estoque', normalizeText(row.component_name) || 'Componente sem nome', normalizeText(parent?.finished_product_sku), normalizeText(row.component_sku || row.component_tiny_id));
|
||||
});
|
||||
components.filter(row => {
|
||||
const quantity = Number(row.quantity_per_unit || 0);
|
||||
return !Number.isFinite(quantity) || quantity <= 0 || quantity > 100 || !normalizeText(row.unit);
|
||||
}).forEach(row => {
|
||||
const parent = rowsByComposition.get(Number(row.product_composition_id));
|
||||
addIssue('suspicious_quantity', 'attention', 'Quantidade suspeita', `${normalizeText(row.component_name)} · ${row.quantity_per_unit || 0} ${normalizeText(row.unit) || '(sem unidade)'}`, normalizeText(parent?.finished_product_sku), normalizeText(row.component_sku || row.component_tiny_id));
|
||||
});
|
||||
compositions.filter(row => isRawOrService(row.finished_product_description) || normalizeText(row.finished_product_unit).toLowerCase() !== 'un').forEach(row => {
|
||||
addIssue('raw_or_service_structure', 'info', 'Estrutura de matéria-prima/serviço', normalizeText(row.finished_product_description), normalizeText(row.finished_product_sku), '');
|
||||
});
|
||||
products.filter(row => !compositionKeys.has(normalizeSku(row.id))).forEach(row => {
|
||||
addIssue('product_without_composition', 'attention', 'Produto sem composição', normalizeText(row.name) || normalizeText(row.id), normalizeText(row.id), '');
|
||||
});
|
||||
|
||||
const productsWithComposition = products.filter(row => compositionKeys.has(normalizeSku(row.id))).length;
|
||||
const productsWithStockLink = products.filter(row => stockKeys.has(normalizeSku(row.id))).length;
|
||||
|
||||
return {
|
||||
totals: {
|
||||
products: productKeys.size,
|
||||
productsWithComposition,
|
||||
productsWithStockLink,
|
||||
materials: materialKeys.size,
|
||||
materialsWithStock: materialStockKeys.size,
|
||||
compositions: compositions.length,
|
||||
components: components.length
|
||||
},
|
||||
issues: issues.sort((a, b) => {
|
||||
const severityOrder = { critical: 0, attention: 1, info: 2 };
|
||||
return severityOrder[a.severity] - severityOrder[b.severity] || a.title.localeCompare(b.title);
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = { getDataHealthSummary };
|
||||
Reference in New Issue
Block a user