Derive consumption references from Tiny OP sync
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m16s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m16s
This commit is contained in:
@@ -65,6 +65,157 @@ const normalizeInteger = (value) => {
|
||||
return Number.isInteger(number) ? number : null;
|
||||
};
|
||||
|
||||
const normalizeSku = (value) => normalizeText(value).toUpperCase();
|
||||
|
||||
const normalizeUnit = (value) => normalizeText(value).toLowerCase();
|
||||
|
||||
const normalizeCategoryName = (name) => normalizeText(name)
|
||||
.normalize('NFD')
|
||||
.replace(/\p{Diacritic}/gu, '')
|
||||
.toUpperCase();
|
||||
|
||||
const classifyCatalogCategoryName = (name, type) => {
|
||||
const normalizedName = normalizeCategoryName(name);
|
||||
if (type === 'finished_product') {
|
||||
if (/\bDTF\b/.test(normalizedName)) return 'DTF';
|
||||
if (/\b(?:MOLETOM|CANGURU)\b/.test(normalizedName)) return 'Moletom';
|
||||
if (/\b(?:OVERSIZE|OVERSIZED)\b/.test(normalizedName)) return 'Oversized';
|
||||
if (/\bINFANTIL\b/.test(normalizedName)) return 'Camiseta infantil';
|
||||
if (/\b(?:BONE|ACESSORIO|ACESSORIOS)\b/.test(normalizedName)) return 'Acessórios';
|
||||
return 'Camiseta regular';
|
||||
}
|
||||
if (/\b(?:MALHA|TECIDO|RIBANA|FIO)\b/.test(normalizedName)) return 'Malha';
|
||||
if (/\b(?:ETIQUETA|TAG|EMBALAGEM|SACO|SACOLA)\b/.test(normalizedName)) return 'Embalagens';
|
||||
if (/\b(?:ATACADOR|ILHOS|LINHA|FITA)\b/.test(normalizedName)) return 'Aviamentos';
|
||||
if (/\bDTF\b/.test(normalizedName)) return 'DTF';
|
||||
return 'Insumos gerais';
|
||||
};
|
||||
|
||||
const ensureCategoryId = async (client, name) => {
|
||||
const result = await client.query(`
|
||||
INSERT INTO catalog_categories (name, description, updated_at)
|
||||
VALUES ($1, $2, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (name) DO UPDATE
|
||||
SET updated_at = catalog_categories.updated_at
|
||||
RETURNING id;
|
||||
`, [name, 'Categoria criada automaticamente pela sincronização de produção.']);
|
||||
|
||||
return result.rows[0].id;
|
||||
};
|
||||
|
||||
const upsertCatalogProductFromSync = async (client, { sku, name, type, categoryName, notes }) => {
|
||||
const normalizedSku = normalizeSku(sku);
|
||||
const normalizedName = normalizeText(name);
|
||||
if (!normalizedSku || !normalizedName) return null;
|
||||
|
||||
const categoryId = await ensureCategoryId(client, categoryName || classifyCatalogCategoryName(normalizedName, type));
|
||||
const result = await client.query(`
|
||||
INSERT INTO catalog_products (
|
||||
type, sku, name, category_id, notes, updated_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (sku) DO UPDATE
|
||||
SET type = EXCLUDED.type,
|
||||
name = EXCLUDED.name,
|
||||
category_id = COALESCE(catalog_products.category_id, EXCLUDED.category_id),
|
||||
notes = COALESCE(NULLIF(catalog_products.notes, ''), EXCLUDED.notes),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
RETURNING id;
|
||||
`, [
|
||||
type,
|
||||
normalizedSku,
|
||||
normalizedName,
|
||||
categoryId,
|
||||
normalizeText(notes) || null
|
||||
]);
|
||||
|
||||
return result.rows[0].id;
|
||||
};
|
||||
|
||||
const upsertConsumptionReferencesFromComponents = async (client, orderId, order, components) => {
|
||||
if (!order.productSku || !components.length) {
|
||||
return { referenceCount: 0, skippedReferenceCount: components.length };
|
||||
}
|
||||
|
||||
const productId = await upsertCatalogProductFromSync(client, {
|
||||
sku: order.productSku,
|
||||
name: order.productDescription,
|
||||
type: 'finished_product',
|
||||
categoryName: classifyCatalogCategoryName(order.productDescription, 'finished_product'),
|
||||
notes: `Sincronizado da OP ${order.number || order.tinyId || orderId}.`
|
||||
});
|
||||
|
||||
if (!productId) return { referenceCount: 0, skippedReferenceCount: components.length };
|
||||
|
||||
let referenceCount = 0;
|
||||
let skippedReferenceCount = 0;
|
||||
|
||||
for (const component of components) {
|
||||
const materialId = await upsertCatalogProductFromSync(client, {
|
||||
sku: component.componentSku,
|
||||
name: component.componentName,
|
||||
type: 'raw_material',
|
||||
categoryName: classifyCatalogCategoryName(component.componentName, 'raw_material'),
|
||||
notes: `Sincronizado da composição da OP ${order.number || order.tinyId || orderId}.`
|
||||
});
|
||||
|
||||
if (!materialId || !component.quantityPerUnit) {
|
||||
skippedReferenceCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const unit = normalizeUnit(component.unit);
|
||||
const generalYield = unit === 'kg' ? 1 / component.quantityPerUnit : null;
|
||||
|
||||
const existingReferenceResult = await client.query(`
|
||||
SELECT id
|
||||
FROM consumption_references
|
||||
WHERE product_id = $1
|
||||
AND COALESCE(material_product_id, 0) = $2
|
||||
AND COALESCE(color, '') = ''
|
||||
AND COALESCE(source, 'manual') = 'tiny_op'
|
||||
LIMIT 1;
|
||||
`, [productId, materialId]);
|
||||
|
||||
if (existingReferenceResult.rows.length) {
|
||||
await client.query(`
|
||||
UPDATE consumption_references
|
||||
SET general_yield = $1,
|
||||
consumption_quantity = $2,
|
||||
consumption_unit = $3,
|
||||
last_production_order_id = $4,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $5;
|
||||
`, [
|
||||
generalYield,
|
||||
component.quantityPerUnit,
|
||||
component.unit || null,
|
||||
orderId,
|
||||
existingReferenceResult.rows[0].id
|
||||
]);
|
||||
} else {
|
||||
await client.query(`
|
||||
INSERT INTO consumption_references (
|
||||
product_id, material_product_id, general_yield, consumption_quantity,
|
||||
consumption_unit, source, last_production_order_id, updated_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, 'tiny_op', $6, CURRENT_TIMESTAMP);
|
||||
`, [
|
||||
productId,
|
||||
materialId,
|
||||
generalYield,
|
||||
component.quantityPerUnit,
|
||||
component.unit || null,
|
||||
orderId
|
||||
]);
|
||||
}
|
||||
|
||||
referenceCount += 1;
|
||||
}
|
||||
|
||||
return { referenceCount, skippedReferenceCount };
|
||||
};
|
||||
|
||||
const mapComponent = (component) => ({
|
||||
id: component.id,
|
||||
componentTinyId: component.component_tiny_id || '',
|
||||
@@ -640,11 +791,15 @@ const upsertTinyProductionOrderDetail = async (payload = {}) => {
|
||||
]);
|
||||
}
|
||||
|
||||
const referenceSync = await upsertConsumptionReferencesFromComponents(client, orderId, order, components);
|
||||
|
||||
await client.query('COMMIT');
|
||||
return {
|
||||
order: await getOrderById(orderId),
|
||||
componentCount: components.length,
|
||||
stepCount: steps.length
|
||||
stepCount: steps.length,
|
||||
referenceCount: referenceSync.referenceCount,
|
||||
skippedReferenceCount: referenceSync.skippedReferenceCount
|
||||
};
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
|
||||
Reference in New Issue
Block a user