Manage manual product compositions
This commit is contained in:
@@ -661,6 +661,7 @@ const normalizeSteps = (steps = []) => (
|
||||
|
||||
const TINY_OLIST_V3_COMPOSITION_SOURCE = 'tiny_olist_v3';
|
||||
const TINY_OLIST_V3_COMPOSITION_PREFIX = 'STRUCTURE-V3-';
|
||||
const MANUAL_COMPOSITION_SOURCE = 'manual';
|
||||
|
||||
const isTinyOlistV3Composition = (order) => order.tinyId.startsWith(TINY_OLIST_V3_COMPOSITION_PREFIX);
|
||||
|
||||
@@ -718,7 +719,7 @@ const mapProductCompositionRow = (row) => ({
|
||||
})) : []
|
||||
});
|
||||
|
||||
const findProductCompositions = async (productId, client = pool) => {
|
||||
const findProductCompositionsBySource = async (source, productId, client = pool) => {
|
||||
const identity = normalizeText(productId);
|
||||
|
||||
const result = await client.query(`
|
||||
@@ -774,11 +775,24 @@ const findProductCompositions = async (productId, client = pool) => {
|
||||
OR composition.finished_product_sku = UPPER($2)
|
||||
)
|
||||
ORDER BY composition.finished_product_sku, composition.finished_product_description;
|
||||
`, [TINY_OLIST_V3_COMPOSITION_SOURCE, identity]);
|
||||
`, [source, identity]);
|
||||
|
||||
return result.rows.map(mapProductCompositionRow);
|
||||
};
|
||||
|
||||
const findProductCompositions = async (productId, client = pool) => {
|
||||
const [manualCompositions, syncedCompositions] = await Promise.all([
|
||||
findProductCompositionsBySource(MANUAL_COMPOSITION_SOURCE, productId, client),
|
||||
findProductCompositionsBySource(TINY_OLIST_V3_COMPOSITION_SOURCE, productId, client)
|
||||
]);
|
||||
const compositionsByProduct = new Map();
|
||||
[...manualCompositions, ...syncedCompositions].forEach(composition => {
|
||||
const identity = composition.finishedTinyProductId || composition.finishedProductSku;
|
||||
if (identity && !compositionsByProduct.has(identity)) compositionsByProduct.set(identity, composition);
|
||||
});
|
||||
return [...compositionsByProduct.values()];
|
||||
};
|
||||
|
||||
const getProductComposition = async (productId, client = pool) => (
|
||||
(await findProductCompositions(productId, client))[0] || null
|
||||
);
|
||||
@@ -968,6 +982,104 @@ const upsertTinyProductComposition = async (order, components) => {
|
||||
}
|
||||
};
|
||||
|
||||
const saveManualProductComposition = async (productId, payload = {}) => {
|
||||
const finishedProductIdentity = normalizeText(productId) || normalizeSku(payload.productSku);
|
||||
const finishedProductSku = normalizeSku(payload.productSku);
|
||||
const finishedProductDescription = normalizeText(payload.productDescription);
|
||||
const finishedProductUnit = normalizeText(payload.productUnit) || 'UN';
|
||||
const rawComponents = normalizeCompositionComponents(payload.components);
|
||||
const components = rawComponents.map(component => ({
|
||||
...component,
|
||||
componentName: normalizeText(component.componentName),
|
||||
unit: normalizeText(component.unit).toUpperCase(),
|
||||
quantityPerUnit: Number(component.quantityPerUnit)
|
||||
}));
|
||||
|
||||
if (!finishedProductIdentity || !finishedProductDescription) {
|
||||
const error = new Error('Produto da composição inválido.');
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
}
|
||||
if (!components.length || components.some(component => !component.componentName || !component.unit || !Number.isFinite(component.quantityPerUnit) || component.quantityPerUnit <= 0)) {
|
||||
const error = new Error('Adicione pelo menos um insumo à composição.');
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const existingResult = await client.query(`
|
||||
SELECT id FROM product_compositions
|
||||
WHERE source = $1 AND finished_product_identity = $2
|
||||
LIMIT 1;
|
||||
`, [MANUAL_COMPOSITION_SOURCE, finishedProductIdentity]);
|
||||
const values = [
|
||||
`MANUAL-${finishedProductIdentity}`,
|
||||
finishedProductIdentity,
|
||||
finishedProductSku || null,
|
||||
finishedProductDescription,
|
||||
finishedProductUnit,
|
||||
finishedProductIdentity,
|
||||
JSON.stringify({ provider: 'Graphs', notes: 'Composição cadastrada manualmente.' })
|
||||
];
|
||||
let compositionId = existingResult.rows[0]?.id;
|
||||
|
||||
if (compositionId) {
|
||||
await client.query(`
|
||||
UPDATE product_compositions
|
||||
SET external_source_id = $1, finished_product_identity = $2,
|
||||
finished_product_sku = $3, finished_product_description = $4,
|
||||
finished_product_unit = $5, finished_tiny_product_id = $6,
|
||||
source_metadata = $7::jsonb, last_synced_at = CURRENT_TIMESTAMP,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $8;
|
||||
`, [...values, compositionId]);
|
||||
} else {
|
||||
const insertResult = await client.query(`
|
||||
INSERT INTO product_compositions (
|
||||
source, external_source_id, finished_product_identity,
|
||||
finished_product_sku, finished_product_description, finished_product_unit,
|
||||
finished_tiny_product_id, source_metadata, last_synced_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, CURRENT_TIMESTAMP)
|
||||
RETURNING id;
|
||||
`, [MANUAL_COMPOSITION_SOURCE, ...values]);
|
||||
compositionId = insertResult.rows[0].id;
|
||||
}
|
||||
|
||||
await client.query('DELETE FROM product_composition_components WHERE product_composition_id = $1;', [compositionId]);
|
||||
for (const component of components) {
|
||||
await client.query(`
|
||||
INSERT INTO product_composition_components (
|
||||
product_composition_id, component_identity, component_tiny_id,
|
||||
component_sku, component_name, quantity_per_unit, unit
|
||||
) VALUES ($1, $2, NULLIF($3, ''), NULLIF($4, ''), $5, $6, NULLIF($7, ''));
|
||||
`, [
|
||||
compositionId, component.componentIdentity, component.componentTinyId,
|
||||
component.componentSku, component.componentName, component.quantityPerUnit, component.unit
|
||||
]);
|
||||
}
|
||||
await client.query('COMMIT');
|
||||
return getProductComposition(finishedProductIdentity);
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
|
||||
const deleteManualProductComposition = async (productId) => {
|
||||
const identity = normalizeText(productId);
|
||||
const result = await pool.query(`
|
||||
DELETE FROM product_compositions
|
||||
WHERE source = $1
|
||||
AND (finished_tiny_product_id = $2 OR finished_product_sku = UPPER($2))
|
||||
RETURNING id;
|
||||
`, [MANUAL_COMPOSITION_SOURCE, identity]);
|
||||
return { deleted: result.rows.length > 0 };
|
||||
};
|
||||
|
||||
const importTinyProductCompositionExport = async (payload = {}) => {
|
||||
const compositions = Array.isArray(payload)
|
||||
? payload
|
||||
@@ -1208,11 +1320,13 @@ const updateProductionOrderStatus = async (id, status) => {
|
||||
|
||||
module.exports = {
|
||||
createProductionOrders,
|
||||
deleteManualProductComposition,
|
||||
getProductComposition,
|
||||
importTinyProductCompositionExport,
|
||||
listProductCompositions,
|
||||
listProductionOrders,
|
||||
normalizeStatus,
|
||||
saveManualProductComposition,
|
||||
upsertTinyProductComposition,
|
||||
upsertTinyProductionOrderDetail,
|
||||
updateProductionOrderStatus
|
||||
|
||||
Reference in New Issue
Block a user