This commit is contained in:
seb
2026-09-30 21:27:27 +02:00
parent ed91ccbaee
commit bc467e29da
3 changed files with 100 additions and 8 deletions

View File

@@ -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) {

View File

@@ -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)) {

View File

@@ -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)) {