u
This commit is contained in:
@@ -54,8 +54,16 @@ export default function toggleFather(provider) {
|
||||
}
|
||||
|
||||
const stockRes = await runner.query(
|
||||
'SELECT COALESCE(SUM(l.amount), 0) AS totalStock FROM ledger l WHERE l.article_id = @articleId',
|
||||
{ articleId },
|
||||
`SELECT COALESCE(SUM(l.amount), 0) AS totalStock
|
||||
FROM ledger l
|
||||
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||
WHERE m.company_id = @companyId
|
||||
AND (
|
||||
ma.sku = @sku
|
||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = @articleId)
|
||||
)`,
|
||||
{ companyId, sku, articleId },
|
||||
);
|
||||
const totalStock = Number(stockRes.rows[0]?.totalStock ?? 0);
|
||||
if (totalStock !== 0) {
|
||||
|
||||
@@ -4,6 +4,39 @@ import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||
import getItemsByArticleSku from './getItemsByArticleSku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
async function hasArticlelistCycle(runner, rootArticleId, candidateComponentId) {
|
||||
const visited = new Set();
|
||||
const queue = [candidateComponentId];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const currentId = queue.shift();
|
||||
if (currentId === rootArticleId) {
|
||||
return true;
|
||||
}
|
||||
if (visited.has(currentId)) continue;
|
||||
visited.add(currentId);
|
||||
|
||||
const res = await runner.query(
|
||||
`SELECT ala.article_id
|
||||
FROM article a
|
||||
INNER JOIN articlelistarticle ala ON ala.articlelist_id = a.articlelist_id
|
||||
WHERE a.id = @currentId`,
|
||||
{ currentId },
|
||||
);
|
||||
|
||||
for (const row of res.rows) {
|
||||
if (row.article_id === rootArticleId) {
|
||||
return true;
|
||||
}
|
||||
if (!visited.has(row.article_id)) {
|
||||
queue.push(row.article_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default function setItemsByArticleSku(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const getItems = getItemsByArticleSku(provider);
|
||||
@@ -75,8 +108,8 @@ export default function setItemsByArticleSku(provider) {
|
||||
throw new Error(`Item at index ${i} must specify a valid articleId or sku`);
|
||||
}
|
||||
|
||||
if (itemArticleId === articleId) {
|
||||
throw new Error('A Stückliste article cannot contain itself');
|
||||
if (itemArticleId === articleId || await hasArticlelistCycle(runner, articleId, itemArticleId)) {
|
||||
throw new Error('A Stückliste article cannot contain itself or form a circular reference');
|
||||
}
|
||||
|
||||
if (seenArticleIds.has(itemArticleId)) {
|
||||
@@ -93,7 +126,16 @@ export default function setItemsByArticleSku(provider) {
|
||||
throw new Error(`Article not found for component: ${itemArticleId}`);
|
||||
}
|
||||
|
||||
const count = item?.count != null && Number(item.count) > 0 ? Number(item.count) : 1;
|
||||
const rawCount = item?.count;
|
||||
if (
|
||||
typeof rawCount !== 'number' ||
|
||||
!Number.isInteger(rawCount) ||
|
||||
rawCount <= 0 ||
|
||||
rawCount > 2147483647
|
||||
) {
|
||||
throw new Error(`Invalid count for Stückliste item at index ${i}: must be a positive integer`);
|
||||
}
|
||||
const count = rawCount;
|
||||
let sort = item?.sort != null && Number.isInteger(Number(item.sort)) ? Number(item.sort) : i;
|
||||
|
||||
while (seenSorts.has(sort)) {
|
||||
|
||||
@@ -4,6 +4,39 @@ import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||
import getOptionsByArticleSku from './getOptionsByArticleSku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
async function hasOptionslistCycle(runner, rootArticleId, candidateOptionId) {
|
||||
const visited = new Set();
|
||||
const queue = [candidateOptionId];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const currentId = queue.shift();
|
||||
if (currentId === rootArticleId) {
|
||||
return true;
|
||||
}
|
||||
if (visited.has(currentId)) continue;
|
||||
visited.add(currentId);
|
||||
|
||||
const res = await runner.query(
|
||||
`SELECT ola.article_id
|
||||
FROM article a
|
||||
INNER JOIN optionslistarticle ola ON ola.optionslist_id = a.optionslist_id
|
||||
WHERE a.id = @currentId`,
|
||||
{ currentId },
|
||||
);
|
||||
|
||||
for (const row of res.rows) {
|
||||
if (row.article_id === rootArticleId) {
|
||||
return true;
|
||||
}
|
||||
if (!visited.has(row.article_id)) {
|
||||
queue.push(row.article_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default function setOptionsByArticleSku(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const getOptions = getOptionsByArticleSku(provider);
|
||||
@@ -75,8 +108,8 @@ export default function setOptionsByArticleSku(provider) {
|
||||
throw new Error(`Option at index ${i} must specify a valid articleId or sku`);
|
||||
}
|
||||
|
||||
if (itemArticleId === articleId) {
|
||||
throw new Error('An article cannot have itself as an option');
|
||||
if (itemArticleId === articleId || await hasOptionslistCycle(runner, articleId, itemArticleId)) {
|
||||
throw new Error('An article cannot have itself as an option or form a circular reference in options');
|
||||
}
|
||||
|
||||
if (seenArticleIds.has(itemArticleId)) {
|
||||
@@ -93,7 +126,16 @@ export default function setOptionsByArticleSku(provider) {
|
||||
throw new Error(`Article not found for option: ${itemArticleId}`);
|
||||
}
|
||||
|
||||
const count = item?.count != null && Number(item.count) > 0 ? Number(item.count) : 1;
|
||||
const rawCount = item?.count;
|
||||
if (
|
||||
typeof rawCount !== 'number' ||
|
||||
!Number.isInteger(rawCount) ||
|
||||
rawCount <= 0 ||
|
||||
rawCount > 2147483647
|
||||
) {
|
||||
throw new Error(`Invalid count for options item at index ${i}: must be a positive integer`);
|
||||
}
|
||||
const count = rawCount;
|
||||
let sort = item?.sort != null && Number.isInteger(Number(item.sort)) ? Number(item.sort) : i;
|
||||
|
||||
while (seenSorts.has(sort)) {
|
||||
|
||||
Reference in New Issue
Block a user