Add manual SKU planning overrides
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m18s

This commit is contained in:
Cauê Faleiros
2026-07-16 11:48:52 -03:00
parent 422828f343
commit 8eeadf00ae
11 changed files with 444 additions and 34 deletions

View File

@@ -136,6 +136,8 @@ const initDB = async () => {
family_key VARCHAR(20),
color VARCHAR(100),
size VARCHAR(40),
product_type VARCHAR(40),
planning_notes TEXT,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
`);
@@ -270,6 +272,12 @@ const initDB = async () => {
ALTER COLUMN updated_at SET DEFAULT CURRENT_TIMESTAMP;
`).catch(() => {});
await pool.query(`
ALTER TABLE cutting_product_overrides
ADD COLUMN IF NOT EXISTS product_type VARCHAR(40),
ADD COLUMN IF NOT EXISTS planning_notes TEXT;
`).catch(() => {});
await pool.query(`
ALTER TABLE catalog_categories
ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'America/Sao_Paulo',

View File

@@ -2,6 +2,21 @@ const { pool } = require('../db');
const FAMILY_KEYS = ['BLCS', 'BLOS', 'BLMC', 'BLPM'];
const FAMILY_KEY_SET = new Set(FAMILY_KEYS);
const PRODUCT_TYPE_KEYS = [
'finished_apparel',
'finished_accessory',
'raw_material',
'packaging',
'dtf_input',
'dtf_service',
'kit_bundle',
'service',
'machine_part',
'equipment',
'ignore_from_planning',
'unknown'
];
const PRODUCT_TYPE_KEY_SET = new Set(PRODUCT_TYPE_KEYS);
const normalizeFamilyKey = (value) => {
const familyKey = String(value || '').trim().toUpperCase();
@@ -15,6 +30,11 @@ const normalizeNumber = (value) => {
const normalizeText = (value) => String(value || '').replace(/\s+/g, ' ').trim();
const normalizeProductType = (value) => {
const productType = normalizeText(value);
return PRODUCT_TYPE_KEY_SET.has(productType) ? productType : '';
};
const normalizeFamilyYields = (familyYields = {}) => {
return FAMILY_KEYS.reduce((normalized, familyKey) => {
const unitsPerRoll = normalizeNumber(familyYields[familyKey]);
@@ -31,13 +51,17 @@ const normalizeProductOverrides = (productOverrides = {}) => {
const familyKey = normalizeFamilyKey(override.familyKey);
const color = normalizeText(override.color);
const size = normalizeText(override.size).toUpperCase();
const productType = normalizeProductType(override.productType);
const planningNotes = normalizeText(override.planningNotes);
if (!familyKey && !color && !size) return normalized;
if (!familyKey && !color && !size && !productType && !planningNotes) return normalized;
normalized[normalizedProductId] = {
familyKey,
color,
size
size,
productType,
planningNotes
};
return normalized;
@@ -53,7 +77,7 @@ const listCuttingSettings = async () => {
ORDER BY family_key
`),
pool.query(`
SELECT product_id, family_key, color, size
SELECT product_id, family_key, color, size, product_type, planning_notes
FROM cutting_product_overrides
ORDER BY product_id
`)
@@ -68,7 +92,9 @@ const listCuttingSettings = async () => {
settings[row.product_id] = {
familyKey: row.family_key || '',
color: row.color || '',
size: row.size || ''
size: row.size || '',
productType: row.product_type || '',
planningNotes: row.planning_notes || ''
};
return settings;
}, {})
@@ -94,13 +120,15 @@ const saveCuttingSettings = async ({ familyYields = {}, productOverrides = {} })
await client.query('DELETE FROM cutting_product_overrides');
for (const [productId, override] of Object.entries(normalizedProductOverrides)) {
await client.query(`
INSERT INTO cutting_product_overrides (product_id, family_key, color, size, updated_at)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
INSERT INTO cutting_product_overrides (product_id, family_key, color, size, product_type, planning_notes, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP)
`, [
productId,
override.familyKey || null,
override.color || null,
override.size || null
override.size || null,
override.productType || null,
override.planningNotes || null
]);
}
@@ -123,6 +151,7 @@ module.exports = {
listCuttingSettings,
normalizeFamilyKey,
normalizeFamilyYields,
normalizeProductType,
normalizeProductOverrides,
saveCuttingSettings
};

View File

@@ -4,6 +4,7 @@ const test = require('node:test');
const {
normalizeFamilyKey,
normalizeFamilyYields,
normalizeProductType,
normalizeProductOverrides
} = require('../services/cuttingSettingsService');
@@ -27,14 +28,23 @@ test('normalizeFamilyYields keeps positive numeric yield rules', () => {
});
});
test('normalizeProductType accepts only known planning product types', () => {
assert.equal(normalizeProductType('finished_apparel'), 'finished_apparel');
assert.equal(normalizeProductType('raw_material'), 'raw_material');
assert.equal(normalizeProductType('finished_product'), '');
assert.equal(normalizeProductType('unknown type'), '');
});
test('normalizeProductOverrides trims and removes empty overrides', () => {
assert.deepEqual(normalizeProductOverrides({
' SKU-1 ': { familyKey: 'blcs', color: ' Preto ', size: ' m ' },
' SKU-1 ': { familyKey: 'blcs', color: ' Preto ', size: ' m ', productType: 'finished_apparel', planningNotes: ' revisar corte ' },
'SKU-2': { familyKey: 'OUTROS', color: '', size: '' },
'SKU-3': { familyKey: '', color: ' Branco ', size: '' },
'SKU-4': null
'SKU-4': { productType: 'raw_material' },
'SKU-5': null
}), {
'SKU-1': { familyKey: 'BLCS', color: 'Preto', size: 'M' },
'SKU-3': { familyKey: '', color: 'Branco', size: '' }
'SKU-1': { familyKey: 'BLCS', color: 'Preto', size: 'M', productType: 'finished_apparel', planningNotes: 'revisar corte' },
'SKU-3': { familyKey: '', color: 'Branco', size: '', productType: '', planningNotes: '' },
'SKU-4': { familyKey: '', color: '', size: '', productType: 'raw_material', planningNotes: '' }
});
});