Import Tiny product compositions

This commit is contained in:
Cauê Faleiros
2026-07-31 11:32:49 -03:00
parent b431802d50
commit 37d4a7732e
9 changed files with 368 additions and 15 deletions

View File

@@ -12,6 +12,7 @@ const getRouteHandler = (router, method, path) => {
};
const tinySyncHandler = getRouteHandler(productionOrderRouter, 'post', '/production-orders/tiny-sync');
const tinyCompositionImportHandler = getRouteHandler(productionOrderRouter, 'post', '/production-orders/tiny-compositions/import');
const productCompositionHandler = getRouteHandler(analyticsRouter, 'get', '/analytics/products/:productId/composition');
const productCompositionsHandler = getRouteHandler(analyticsRouter, 'get', '/analytics/product-compositions');
@@ -70,9 +71,15 @@ const withFakeDatabase = async (run) => {
const state = {
nextCompositionId: 1,
nextOrderId: 1,
nextCategoryId: 1,
nextCatalogProductId: 1,
nextConsumptionReferenceId: 1,
compositions: [],
compositionComponents: [],
productionOrders: []
productionOrders: [],
categories: [],
catalogProducts: [],
consumptionReferences: []
};
const query = async (sql, params = []) => {
@@ -80,6 +87,72 @@ const withFakeDatabase = async (run) => {
if (/^(BEGIN|COMMIT|ROLLBACK);?$/.test(normalizedSql)) return { rows: [] };
if (normalizedSql.startsWith('INSERT INTO catalog_categories')) {
let category = state.categories.find(item => item.name === params[0]);
if (!category) {
category = { id: state.nextCategoryId++, name: params[0], description: params[1] };
state.categories.push(category);
}
return { rows: [{ id: category.id }] };
}
if (normalizedSql.startsWith('INSERT INTO catalog_products')) {
let product = state.catalogProducts.find(item => item.sku === params[1]);
if (!product) {
product = {
id: state.nextCatalogProductId++,
type: params[0],
sku: params[1],
name: params[2],
category_id: params[3],
notes: params[4]
};
state.catalogProducts.push(product);
} else {
product.type = params[0];
product.name = params[2];
product.category_id = product.category_id || params[3];
product.notes = product.notes || params[4];
}
return { rows: [{ id: product.id }] };
}
if (normalizedSql.startsWith('SELECT id FROM consumption_references')) {
const reference = state.consumptionReferences.find(item => (
item.product_id === params[0]
&& (item.material_product_id || 0) === params[1]
&& (item.color || '') === ''
&& (item.source || 'manual') === params[2]
));
return { rows: reference ? [{ id: reference.id }] : [] };
}
if (normalizedSql.startsWith('UPDATE consumption_references')) {
const reference = state.consumptionReferences.find(item => item.id === params[5]);
if (reference) {
reference.general_yield = params[0];
reference.consumption_quantity = params[1];
reference.consumption_unit = params[2];
reference.last_production_order_id = params[3];
reference.source = params[4];
}
return { rows: [] };
}
if (normalizedSql.startsWith('INSERT INTO consumption_references')) {
state.consumptionReferences.push({
id: state.nextConsumptionReferenceId++,
product_id: params[0],
material_product_id: params[1],
general_yield: params[2],
consumption_quantity: params[3],
consumption_unit: params[4],
source: params[5],
last_production_order_id: params[6]
});
return { rows: [] };
}
if (normalizedSql.startsWith('SELECT id FROM product_compositions WHERE source')) {
const composition = state.compositions.find(item => item.source === params[0] && item.finished_product_identity === params[1]);
return { rows: composition ? [{ id: composition.id }] : [] };
@@ -197,10 +270,36 @@ test('Tiny/Olist V3 first import creates a composition without creating an OP',
assert.equal(state.compositions.length, 1);
assert.equal(state.compositionComponents.length, 2);
assert.equal(state.productionOrders.length, 0);
assert.equal(state.consumptionReferences.length, 2);
assert.equal(result.composition.finishedTinyProductId, '976058813');
});
});
test('Tiny/Olist V3 bulk import stores compositions and consumption references', async () => {
await withFakeDatabase(async (state) => {
const response = await invokeHandler(tinyCompositionImportHandler, {
body: {
exportedAt: '2026-07-31T13:37:01.340Z',
compositions: [{
finishedProductSku: 'BLCS.CAF.GG',
finishedProductDescription: 'BASE LISA CAMISETA COR CAFE TAMANHO - GG',
finishedProductUnit: 'UN',
finishedTinyProductId: '976058813',
sourceMetadata: { payload: compositionPayload() },
components: []
}]
}
});
assert.equal(response.statusCode, 201);
assert.equal(response.body.imported, 1);
assert.equal(response.body.referenceCount, 2);
assert.equal(state.compositions.length, 1);
assert.equal(state.consumptionReferences.length, 2);
assert.equal(state.consumptionReferences[0].source, 'tiny_structure');
});
});
test('Tiny/Olist V3 re-sync replaces composition components without duplicates', async () => {
await withFakeDatabase(async (state) => {
await invokeHandler(tinySyncHandler, { body: compositionPayload() });