Add product composition export
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m16s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m16s
This commit is contained in:
@@ -10,7 +10,7 @@ const {
|
||||
getProductDetailsAnalytics,
|
||||
getRfmAnalytics
|
||||
} = require('../services/analyticsService');
|
||||
const { getProductComposition } = require('../services/productionOrderService');
|
||||
const { getProductComposition, listProductCompositions } = require('../services/productionOrderService');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -69,6 +69,15 @@ router.get('/analytics/products/:productId/composition', verifyToken, async (req
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/analytics/product-compositions', verifyToken, async (req, res) => {
|
||||
try {
|
||||
res.json({ compositions: await listProductCompositions() });
|
||||
} catch (error) {
|
||||
console.error('Error exporting product compositions:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/analytics/clients', verifyToken, async (req, res) => {
|
||||
try {
|
||||
res.json(await getClientAnalytics(getClientAnalyticsFilters(req.query)));
|
||||
|
||||
@@ -699,9 +699,8 @@ const mapProductCompositionRow = (row) => ({
|
||||
})) : []
|
||||
});
|
||||
|
||||
const getProductComposition = async (productId, client = pool) => {
|
||||
const findProductCompositions = async (productId, client = pool) => {
|
||||
const identity = normalizeText(productId);
|
||||
if (!identity) return null;
|
||||
|
||||
const result = await client.query(`
|
||||
SELECT
|
||||
@@ -751,15 +750,22 @@ const getProductComposition = async (productId, client = pool) => {
|
||||
FROM product_compositions composition
|
||||
WHERE composition.source = $1
|
||||
AND (
|
||||
composition.finished_tiny_product_id = $2
|
||||
$2 = ''
|
||||
OR composition.finished_tiny_product_id = $2
|
||||
OR composition.finished_product_sku = UPPER($2)
|
||||
)
|
||||
LIMIT 1;
|
||||
ORDER BY composition.finished_product_sku, composition.finished_product_description;
|
||||
`, [TINY_OLIST_V3_COMPOSITION_SOURCE, identity]);
|
||||
|
||||
return result.rows[0] ? mapProductCompositionRow(result.rows[0]) : null;
|
||||
return result.rows.map(mapProductCompositionRow);
|
||||
};
|
||||
|
||||
const getProductComposition = async (productId, client = pool) => (
|
||||
(await findProductCompositions(productId, client))[0] || null
|
||||
);
|
||||
|
||||
const listProductCompositions = async (client = pool) => findProductCompositions('', client);
|
||||
|
||||
const upsertTinyProductComposition = async (order, components) => {
|
||||
const finishedTinyProductId = getTinyProductIdFromCompositionReference(order.tinyId);
|
||||
const finishedProductSku = normalizeSku(order.productSku);
|
||||
@@ -1051,6 +1057,7 @@ const updateProductionOrderStatus = async (id, status) => {
|
||||
module.exports = {
|
||||
createProductionOrders,
|
||||
getProductComposition,
|
||||
listProductCompositions,
|
||||
listProductionOrders,
|
||||
normalizeStatus,
|
||||
upsertTinyProductionOrderDetail,
|
||||
|
||||
@@ -13,6 +13,7 @@ const getRouteHandler = (router, method, path) => {
|
||||
|
||||
const tinySyncHandler = getRouteHandler(productionOrderRouter, 'post', '/production-orders/tiny-sync');
|
||||
const productCompositionHandler = getRouteHandler(analyticsRouter, 'get', '/analytics/products/:productId/composition');
|
||||
const productCompositionsHandler = getRouteHandler(analyticsRouter, 'get', '/analytics/product-compositions');
|
||||
|
||||
const invokeHandler = async (handler, req) => {
|
||||
let statusCode = 200;
|
||||
@@ -132,18 +133,17 @@ const withFakeDatabase = async (run) => {
|
||||
}
|
||||
|
||||
if (normalizedSql.includes('FROM product_compositions composition')) {
|
||||
const composition = state.compositions.find(item => (
|
||||
const compositions = state.compositions.filter(item => (
|
||||
item.source === params[0]
|
||||
&& (item.finished_tiny_product_id === params[1] || item.finished_product_sku === String(params[1]).toUpperCase())
|
||||
&& (params[1] === '' || item.finished_tiny_product_id === params[1] || item.finished_product_sku === String(params[1]).toUpperCase())
|
||||
));
|
||||
if (!composition) return { rows: [] };
|
||||
return {
|
||||
rows: [{
|
||||
rows: compositions.map(composition => ({
|
||||
...composition,
|
||||
components: state.compositionComponents
|
||||
.filter(item => item.product_composition_id === composition.id)
|
||||
.map(item => ({ ...item, component_product_id: null }))
|
||||
}]
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -243,6 +243,17 @@ test('product composition detail lookup returns stored components', async () =>
|
||||
});
|
||||
});
|
||||
|
||||
test('product composition export endpoint returns every stored composition', async () => {
|
||||
await withFakeDatabase(async () => {
|
||||
await invokeHandler(tinySyncHandler, { body: compositionPayload() });
|
||||
const response = await invokeHandler(productCompositionsHandler, { params: {} });
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(response.body.compositions.length, 1);
|
||||
assert.equal(response.body.compositions[0].components.length, 2);
|
||||
});
|
||||
});
|
||||
|
||||
test('ordinary Tiny production-order payloads continue to create production orders', async () => {
|
||||
await withFakeDatabase(async (state) => {
|
||||
const response = await invokeHandler(tinySyncHandler, { body: {
|
||||
|
||||
Reference in New Issue
Block a user