Export production orders with missing data
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m2s

This commit is contained in:
Cauê Faleiros
2026-08-12 11:41:37 -03:00
parent cfdcc4f2f4
commit e7edb5c483
5 changed files with 117 additions and 5 deletions

View File

@@ -9,7 +9,7 @@ const {
requestOlistSyncStop,
startOlistSync
} = require('../services/olistService');
const { importFinalizedProductionOrderCsv, listProductionOrderImportData } = require('../services/productionOrderCsvImportService');
const { exportMissingProductionOrderDataCsv, importFinalizedProductionOrderCsv, listProductionOrderImportData } = require('../services/productionOrderCsvImportService');
const router = express.Router();
@@ -57,6 +57,14 @@ router.get('/olist/production-order-imports', verifySuperAdmin, async (req, res,
}
});
router.get('/olist/production-order-imports/pending-csv', verifySuperAdmin, async (req, res, next) => {
try {
res.json(await exportMissingProductionOrderDataCsv());
} catch (error) {
next(error);
}
});
router.post('/olist/production-order-imports', verifySuperAdmin, async (req, res, next) => {
try {
res.status(201).json(await importFinalizedProductionOrderCsv(req.body || {}));

View File

@@ -31,6 +31,11 @@ const normalizeHeader = (header) => String(header || '')
.toLowerCase()
.replace(/\s+/g, '_');
const csvCell = (value) => {
const normalized = value === null || value === undefined ? '' : String(value);
return /[",\r\n]/.test(normalized) ? `"${normalized.replace(/"/g, '""')}"` : normalized;
};
const parseCsv = (csv) => {
const rows = [];
let row = [];
@@ -129,6 +134,12 @@ const parseFinalizedProductionOrderCsv = (csv) => {
const familyKey = productSku.split('.')[0];
const extracted = extractObservationFields(notes);
const supplier = normalizeText(value('fornecedor')) || extracted.supplier;
const lotCode = normalizeText(value('lote')) || extracted.lotCode;
const rollQuantity = normalizeNumber(value('quantidade_rolos')) || extracted.rollQuantity;
const fabricKg = normalizeNumber(value('quilos_malha')) || extracted.fabricKg;
const ribKg = normalizeNumber(value('quilos_ribana')) || extracted.ribKg;
const yieldPiecesPerKg = normalizeNumber(value('rendimento')) || extracted.yieldPiecesPerKg;
records.push({
tinyId,
number,
@@ -140,7 +151,12 @@ const parseFinalizedProductionOrderCsv = (csv) => {
orderReference: normalizeText(value('pedido_relacionado')),
notes,
familyKey: FAMILY_KEYS.has(familyKey) ? familyKey : null,
...extracted
supplier,
lotCode,
rollQuantity,
fabricKg,
ribKg,
yieldPiecesPerKg
});
});
@@ -195,6 +211,45 @@ const listProductionOrderImportData = async () => {
};
};
const exportMissingProductionOrderDataCsv = async () => {
const result = await pool.query(`
SELECT tiny_id, number, product_sku, product_description, quantity, issue_date, completed_date, notes,
order_reference, supplier, lot_code, roll_quantity, fabric_kg, rib_kg, yield_pieces_per_kg
FROM production_orders
WHERE status = 'finished'
AND (
supplier IS NULL OR lot_code IS NULL OR roll_quantity IS NULL
OR fabric_kg IS NULL OR yield_pieces_per_kg IS NULL
)
ORDER BY completed_date DESC NULLS LAST, id DESC;
`);
const headers = [
'id_op', 'numero_op', 'sku_produto', 'descricao_produto', 'quantidade_produzida', 'data_emissao',
'data_conclusao', 'situacao', 'observacoes', 'pedido_relacionado', 'fornecedor', 'lote',
'quantidade_rolos', 'quilos_malha', 'quilos_ribana', 'rendimento', 'campos_pendentes'
];
const rows = result.rows.map((row) => {
const missing = [
!row.supplier && 'fornecedor',
!row.lot_code && 'lote',
!row.roll_quantity && 'quantidade_rolos',
!row.fabric_kg && 'quilos_malha',
!row.yield_pieces_per_kg && 'rendimento'
].filter(Boolean).join(', ');
return [
row.tiny_id, row.number, row.product_sku, row.product_description, row.quantity, row.issue_date,
row.completed_date, 'Finalizada', row.notes, row.order_reference, row.supplier, row.lot_code,
row.roll_quantity, row.fabric_kg, row.rib_kg, row.yield_pieces_per_kg, missing
];
});
return {
filename: `ops-finalizadas-pendentes-${new Date().toISOString().slice(0, 10)}.csv`,
totalRows: rows.length,
csv: [headers, ...rows].map(row => row.map(csvCell).join(',')).join('\r\n')
};
};
const importFinalizedProductionOrderCsv = async ({ filename, csv }) => {
const parsed = parseFinalizedProductionOrderCsv(csv);
const client = await pool.connect();
@@ -260,6 +315,7 @@ const importFinalizedProductionOrderCsv = async ({ filename, csv }) => {
};
module.exports = {
exportMissingProductionOrderDataCsv,
extractObservationFields,
importFinalizedProductionOrderCsv,
listProductionOrderImportData,

View File

@@ -28,3 +28,16 @@ test('parseFinalizedProductionOrderCsv supports multiline observations and skips
assert.equal(result.records[0].fabricKg, 577.9);
assert.equal(result.records[0].yieldPiecesPerKg, 5.44);
});
test('parseFinalizedProductionOrderCsv accepts structured production fields from a completed template', () => {
const csv = 'id_op,numero_op,sku_produto,descricao_produto,quantidade_produzida,data_emissao,data_conclusao,situacao,observacoes,fornecedor,lote,quantidade_rolos,quilos_malha,quilos_ribana,rendimento\n1,10,BLCS.PTO.G,Camiseta preta G,100,2026-08-01,2026-08-02,Finalizada,,TECEBEM,0245163,30,"577,90","28,89","5,44"';
const result = parseFinalizedProductionOrderCsv(csv);
assert.equal(result.records.length, 1);
assert.equal(result.records[0].supplier, 'TECEBEM');
assert.equal(result.records[0].lotCode, '0245163');
assert.equal(result.records[0].rollQuantity, 30);
assert.equal(result.records[0].fabricKg, 577.9);
assert.equal(result.records[0].ribKg, 28.89);
assert.equal(result.records[0].yieldPiecesPerKg, 5.44);
});