u
This commit is contained in:
@@ -54,8 +54,16 @@ export default function toggleFather(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stockRes = await runner.query(
|
const stockRes = await runner.query(
|
||||||
'SELECT COALESCE(SUM(l.amount), 0) AS totalStock FROM ledger l WHERE l.article_id = @articleId',
|
`SELECT COALESCE(SUM(l.amount), 0) AS totalStock
|
||||||
{ articleId },
|
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);
|
const totalStock = Number(stockRes.rows[0]?.totalStock ?? 0);
|
||||||
if (totalStock !== 0) {
|
if (totalStock !== 0) {
|
||||||
|
|||||||
@@ -4,6 +4,39 @@ import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
|||||||
import getItemsByArticleSku from './getItemsByArticleSku.js';
|
import getItemsByArticleSku from './getItemsByArticleSku.js';
|
||||||
import makeRunner from '../runner.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) {
|
export default function setItemsByArticleSku(provider) {
|
||||||
const resolve = resolveArticleBySku(provider);
|
const resolve = resolveArticleBySku(provider);
|
||||||
const getItems = getItemsByArticleSku(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`);
|
throw new Error(`Item at index ${i} must specify a valid articleId or sku`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemArticleId === articleId) {
|
if (itemArticleId === articleId || await hasArticlelistCycle(runner, articleId, itemArticleId)) {
|
||||||
throw new Error('A Stückliste article cannot contain itself');
|
throw new Error('A Stückliste article cannot contain itself or form a circular reference');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seenArticleIds.has(itemArticleId)) {
|
if (seenArticleIds.has(itemArticleId)) {
|
||||||
@@ -93,7 +126,16 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
throw new Error(`Article not found for component: ${itemArticleId}`);
|
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;
|
let sort = item?.sort != null && Number.isInteger(Number(item.sort)) ? Number(item.sort) : i;
|
||||||
|
|
||||||
while (seenSorts.has(sort)) {
|
while (seenSorts.has(sort)) {
|
||||||
|
|||||||
@@ -4,6 +4,39 @@ import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
|||||||
import getOptionsByArticleSku from './getOptionsByArticleSku.js';
|
import getOptionsByArticleSku from './getOptionsByArticleSku.js';
|
||||||
import makeRunner from '../runner.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) {
|
export default function setOptionsByArticleSku(provider) {
|
||||||
const resolve = resolveArticleBySku(provider);
|
const resolve = resolveArticleBySku(provider);
|
||||||
const getOptions = getOptionsByArticleSku(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`);
|
throw new Error(`Option at index ${i} must specify a valid articleId or sku`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemArticleId === articleId) {
|
if (itemArticleId === articleId || await hasOptionslistCycle(runner, articleId, itemArticleId)) {
|
||||||
throw new Error('An article cannot have itself as an option');
|
throw new Error('An article cannot have itself as an option or form a circular reference in options');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seenArticleIds.has(itemArticleId)) {
|
if (seenArticleIds.has(itemArticleId)) {
|
||||||
@@ -93,7 +126,16 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
throw new Error(`Article not found for option: ${itemArticleId}`);
|
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;
|
let sort = item?.sort != null && Number.isInteger(Number(item.sort)) ? Number(item.sort) : i;
|
||||||
|
|
||||||
while (seenSorts.has(sort)) {
|
while (seenSorts.has(sort)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user