u
This commit is contained in:
@@ -34,24 +34,24 @@ export default function addChild(provider) {
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Adds a child article to a father article (creates and attaches with auto-generated SKU).
|
||||
* Adds a child article to a parent article (creates and attaches with auto-generated SKU).
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father article SKU.
|
||||
* @param {string} parentSku - The parent article SKU.
|
||||
* @param {object} options
|
||||
* @param {boolean} [options.attach=false] - If true, attaches an existing article instead of creating.
|
||||
* @param {number} [options.attributeValueId] - Existing attribute value ID for father's attribute.
|
||||
* @param {string} [options.value] - String value to find or create under father's attribute.
|
||||
* @param {string} [options.name] - Custom name for child article (defaults to father name + value).
|
||||
* @param {number} [options.attributeValueId] - Existing attribute value ID for parent's attribute.
|
||||
* @param {string} [options.value] - String value to find or create under parent's attribute.
|
||||
* @param {string} [options.name] - Custom name for child article (defaults to parent name + value).
|
||||
* @param {number} [options.unitamount] - Optional unitamount override.
|
||||
* @param {number} [options.weight] - Optional weight override.
|
||||
* @param {boolean|number} [options.track_stock] - Optional track_stock override.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The created child article detail.
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, options = {}, tx) => {
|
||||
return async (googleAuthId, parentSku, options = {}, tx) => {
|
||||
if (options.attach) {
|
||||
return attach(googleAuthId, fatherSku, options, tx);
|
||||
return attach(googleAuthId, parentSku, options, tx);
|
||||
}
|
||||
|
||||
const run = async (runner) => {
|
||||
@@ -59,31 +59,31 @@ export default function addChild(provider) {
|
||||
throw new Error('sku is not allowed when creating a child article; omit sku to auto-generate or use PUT to attach an existing article');
|
||||
}
|
||||
|
||||
const { articleId: fatherId, companyId, languageId, companyuserId } = await resolve(
|
||||
const { articleId: parentId, companyId, languageId, companyuserId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
parentSku,
|
||||
runner,
|
||||
);
|
||||
|
||||
// Fetch father article fields
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT id, sku, company_id, unit_id, unitamount, weight, track_stock, brand_id, is_father, fatherarticle_id, fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
// Fetch parent article fields
|
||||
const parentRes = await runner.query(
|
||||
'SELECT id, sku, company_id, unit_id, unitamount, weight, track_stock, brand_id, is_parent, parentarticle_id, parentattributename_id FROM article WHERE id = @parentId',
|
||||
{ parentId },
|
||||
);
|
||||
const father = fatherRes.rows[0];
|
||||
if (!father) {
|
||||
throw new Error('Father article not found');
|
||||
const parent = parentRes.rows[0];
|
||||
if (!parent) {
|
||||
throw new Error('Parent article not found');
|
||||
}
|
||||
if (!father.is_father || !father.fatherattributename_id) {
|
||||
throw new Error('Article is not a father article');
|
||||
if (!parent.is_parent || !parent.parentattributename_id) {
|
||||
throw new Error('Article is not a parent article');
|
||||
}
|
||||
|
||||
// Fetch father's text / name
|
||||
const fatherTextRes = await runner.query(
|
||||
'SELECT name FROM articletext WHERE article_id = @fatherId AND language_id = @languageId',
|
||||
{ fatherId, languageId },
|
||||
// Fetch parent's text / name
|
||||
const parentTextRes = await runner.query(
|
||||
'SELECT name FROM articletext WHERE article_id = @parentId AND language_id = @languageId',
|
||||
{ parentId, languageId },
|
||||
);
|
||||
const fatherName = fatherTextRes.rows[0]?.name || father.sku;
|
||||
const parentName = parentTextRes.rows[0]?.name || parent.sku;
|
||||
|
||||
// Determine attribute value
|
||||
let finalValueId = null;
|
||||
@@ -98,15 +98,15 @@ export default function addChild(provider) {
|
||||
companyId,
|
||||
});
|
||||
const row = valRes.rows[0];
|
||||
if (!row || row.attributeId !== father.fatherattributename_id) {
|
||||
throw new Error('Attribute value not found or does not match father attribute');
|
||||
if (!row || row.attributeId !== parent.parentattributename_id) {
|
||||
throw new Error('Attribute value not found or does not match parent attribute');
|
||||
}
|
||||
finalValueId = row.id;
|
||||
finalValue = row.value;
|
||||
} else if (typeof options.value === 'string' && options.value.trim()) {
|
||||
const valStr = options.value.trim();
|
||||
const existingValRes = await runner.query(attrValQueries.GET_BY_ATTRIBUTE_AND_VALUE, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
attributeId: parent.parentattributename_id,
|
||||
value: valStr,
|
||||
});
|
||||
if (existingValRes.rows[0]) {
|
||||
@@ -114,11 +114,11 @@ export default function addChild(provider) {
|
||||
finalValue = existingValRes.rows[0].value;
|
||||
} else {
|
||||
const sortRes = await runner.query(attrValQueries.GET_NEXT_SORT, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
attributeId: parent.parentattributename_id,
|
||||
});
|
||||
const sort = sortRes.rows[0]?.sort ?? 0;
|
||||
const createdValRes = await runner.query(attrValQueries.CREATE, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
attributeId: parent.parentattributename_id,
|
||||
value: valStr,
|
||||
sort,
|
||||
});
|
||||
@@ -129,26 +129,26 @@ export default function addChild(provider) {
|
||||
throw new Error('Attribute value is required for child article');
|
||||
}
|
||||
|
||||
// Check if father already has a variant with this attribute value
|
||||
// Check if parent already has a variant with this attribute value
|
||||
const existingVariantRes = await runner.query(
|
||||
`SELECT a.id
|
||||
FROM article a
|
||||
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
|
||||
WHERE a.fatherarticle_id = @fatherId
|
||||
WHERE a.parentarticle_id = @parentId
|
||||
AND aav.attributevalue_id = @finalValueId`,
|
||||
{ fatherId, finalValueId },
|
||||
{ parentId, finalValueId },
|
||||
);
|
||||
if (existingVariantRes.rows.length > 0) {
|
||||
throw new Error('Variant with this attribute value already exists');
|
||||
}
|
||||
|
||||
// Compute child SKU using next free index: fatherSku-1, fatherSku-2, ...
|
||||
// Compute child SKU using next free index: parentSku-1, parentSku-2, ...
|
||||
const existingSkusRes = await runner.query(
|
||||
'SELECT sku FROM article WHERE company_id = @companyId AND sku LIKE @prefix',
|
||||
{ companyId, prefix: `${father.sku}-%` },
|
||||
{ companyId, prefix: `${parent.sku}-%` },
|
||||
);
|
||||
const usedIndices = new Set();
|
||||
const prefix = `${father.sku}-`;
|
||||
const prefix = `${parent.sku}-`;
|
||||
for (const row of existingSkusRes.rows) {
|
||||
if (row.sku && row.sku.startsWith(prefix)) {
|
||||
const numPart = row.sku.slice(prefix.length);
|
||||
@@ -161,42 +161,42 @@ export default function addChild(provider) {
|
||||
while (usedIndices.has(nextIndex)) {
|
||||
nextIndex++;
|
||||
}
|
||||
const childSku = `${father.sku}-${nextIndex}`;
|
||||
const childSku = `${parent.sku}-${nextIndex}`;
|
||||
|
||||
// Next free fathersort (smallest non-negative integer not used by any child of this father)
|
||||
// Next free parentsort (smallest non-negative integer not used by any child of this parent)
|
||||
const existingSortsRes = await runner.query(
|
||||
'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC',
|
||||
{ fatherId },
|
||||
'SELECT parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC',
|
||||
{ parentId },
|
||||
);
|
||||
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort)));
|
||||
let fathersort = 0;
|
||||
while (usedSorts.has(fathersort)) {
|
||||
fathersort++;
|
||||
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.parentsort)));
|
||||
let parentsort = 0;
|
||||
while (usedSorts.has(parentsort)) {
|
||||
parentsort++;
|
||||
}
|
||||
|
||||
// Auto-create child article
|
||||
const rawTrack = options.track_stock ?? options.trackStock;
|
||||
const finalTrackStock = rawTrack == null ? father.track_stock : (rawTrack ? 1 : 0);
|
||||
const finalTrackStock = rawTrack == null ? parent.track_stock : (rawTrack ? 1 : 0);
|
||||
|
||||
const childRes = await runner.query(queries.CREATE_CHILD, {
|
||||
companyId,
|
||||
sku: childSku,
|
||||
unitId: father.unit_id,
|
||||
unitamount: options.unitamount ?? father.unitamount,
|
||||
weight: options.weight ?? father.weight,
|
||||
unitId: parent.unit_id,
|
||||
unitamount: options.unitamount ?? parent.unitamount,
|
||||
weight: options.weight ?? parent.weight,
|
||||
trackStock: finalTrackStock,
|
||||
brandId: father.brand_id,
|
||||
fatherArticleId: fatherId,
|
||||
fathersort,
|
||||
brandId: parent.brand_id,
|
||||
parentArticleId: parentId,
|
||||
parentsort,
|
||||
});
|
||||
const childId = childRes.rows[0]?.id ?? childRes.insertId;
|
||||
|
||||
// Copy existing attribute values from father (ancestor attributes that father has)
|
||||
const fatherAttrsRes = await runner.query(
|
||||
'SELECT attributevalue_id, sort FROM articleattributevalue WHERE article_id = @fatherId',
|
||||
{ fatherId },
|
||||
// Copy existing attribute values from parent (ancestor attributes that parent has)
|
||||
const parentAttrsRes = await runner.query(
|
||||
'SELECT attributevalue_id, sort FROM articleattributevalue WHERE article_id = @parentId',
|
||||
{ parentId },
|
||||
);
|
||||
for (const faRow of fatherAttrsRes.rows) {
|
||||
for (const faRow of parentAttrsRes.rows) {
|
||||
if (faRow.attributevalue_id !== finalValueId) {
|
||||
await runner.query(articleAttrValQueries.INSERT_ROW, {
|
||||
articleId: childId,
|
||||
@@ -213,10 +213,10 @@ export default function addChild(provider) {
|
||||
sort: 0,
|
||||
});
|
||||
|
||||
// Copy father's categories
|
||||
// Copy parent's categories
|
||||
const catRes = await runner.query(
|
||||
'SELECT category_id FROM categoryarticle WHERE article_id = @fatherId',
|
||||
{ fatherId },
|
||||
'SELECT category_id FROM categoryarticle WHERE article_id = @parentId',
|
||||
{ parentId },
|
||||
);
|
||||
for (const catRow of catRes.rows) {
|
||||
await runner.query(addCategoryArticleSql, {
|
||||
@@ -226,7 +226,7 @@ export default function addChild(provider) {
|
||||
}
|
||||
|
||||
// Create article text
|
||||
const childName = options.name?.trim() || `${fatherName} - ${finalValue}`;
|
||||
const childName = options.name?.trim() || `${parentName} - ${finalValue}`;
|
||||
const childSlug = await resolveSlug(runner, {
|
||||
companyId,
|
||||
languageId,
|
||||
@@ -243,12 +243,12 @@ export default function addChild(provider) {
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: childId,
|
||||
companyuserId,
|
||||
text: `Artikel "${childName}" (${childSku}) als Variante von "${father.sku}" angelegt`,
|
||||
text: `Artikel "${childName}" (${childSku}) als Variante von "${parent.sku}" angelegt`,
|
||||
});
|
||||
|
||||
// History on father
|
||||
// History on parent
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: fatherId,
|
||||
articleId: parentId,
|
||||
companyuserId,
|
||||
text: `Variante "${childName}" (${childSku}) hinzugefügt`,
|
||||
});
|
||||
|
||||
@@ -26,23 +26,23 @@ export default function attachChild(provider) {
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Attaches an existing article as a child to a father article.
|
||||
* Attaches an existing article as a child to a parent article.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father article SKU.
|
||||
* @param {string} parentSku - The parent article SKU.
|
||||
* @param {object} options
|
||||
* @param {string} options.sku - The child article SKU to attach.
|
||||
* @param {string} [options.childSku] - Alias for options.sku.
|
||||
* @param {number} [options.attributeValueId] - Existing attribute value ID for father's attribute.
|
||||
* @param {string} [options.value] - String value to find or create under father's attribute.
|
||||
* @param {boolean} [options.reparent=false] - Whether to allow re-parenting if child belongs to another father.
|
||||
* @param {number} [options.attributeValueId] - Existing attribute value ID for parent's attribute.
|
||||
* @param {string} [options.value] - String value to find or create under parent's attribute.
|
||||
* @param {boolean} [options.reparent=false] - Whether to allow re-parenting if child belongs to another parent.
|
||||
* @param {number} [options.unitamount] - Optional unitamount override.
|
||||
* @param {number} [options.weight] - Optional weight override.
|
||||
* @param {boolean|number} [options.track_stock] - Optional track_stock override.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{article: object, children: Array<object>}>}
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, options = {}, tx) => {
|
||||
return async (googleAuthId, parentSku, options = {}, tx) => {
|
||||
const run = async (runner) => {
|
||||
const childSku = (typeof options.sku === 'string' && options.sku.trim())
|
||||
|| (typeof options.childSku === 'string' && options.childSku.trim())
|
||||
@@ -52,35 +52,35 @@ export default function attachChild(provider) {
|
||||
throw new Error('sku is required to attach an existing article');
|
||||
}
|
||||
|
||||
const { articleId: fatherId, companyId, languageId, companyuserId } = await resolve(
|
||||
const { articleId: parentId, companyId, languageId, companyuserId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
parentSku,
|
||||
runner,
|
||||
);
|
||||
|
||||
// Fetch father article fields
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT id, sku, company_id, is_father, fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
// Fetch parent article fields
|
||||
const parentRes = await runner.query(
|
||||
'SELECT id, sku, company_id, is_parent, parentattributename_id FROM article WHERE id = @parentId',
|
||||
{ parentId },
|
||||
);
|
||||
const father = fatherRes.rows[0];
|
||||
if (!father) {
|
||||
throw new Error('Father article not found');
|
||||
const parent = parentRes.rows[0];
|
||||
if (!parent) {
|
||||
throw new Error('Parent article not found');
|
||||
}
|
||||
if (!father.is_father || !father.fatherattributename_id) {
|
||||
throw new Error('Article is not a father article');
|
||||
if (!parent.is_parent || !parent.parentattributename_id) {
|
||||
throw new Error('Article is not a parent article');
|
||||
}
|
||||
|
||||
// Check child article exists in company
|
||||
const childRes = await runner.query(
|
||||
'SELECT id, sku, company_id, fatherarticle_id, is_father FROM article WHERE sku = @childSku AND company_id = @companyId',
|
||||
'SELECT id, sku, company_id, parentarticle_id, is_parent FROM article WHERE sku = @childSku AND company_id = @companyId',
|
||||
{ childSku, companyId },
|
||||
);
|
||||
const child = childRes.rows[0];
|
||||
if (!child) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
if (child.id === fatherId) {
|
||||
if (child.id === parentId) {
|
||||
throw new Error('An article cannot be a child of itself');
|
||||
}
|
||||
|
||||
@@ -97,15 +97,15 @@ export default function attachChild(provider) {
|
||||
companyId,
|
||||
});
|
||||
const row = valRes.rows[0];
|
||||
if (!row || row.attributeId !== father.fatherattributename_id) {
|
||||
throw new Error('Attribute value not found or does not match father attribute');
|
||||
if (!row || row.attributeId !== parent.parentattributename_id) {
|
||||
throw new Error('Attribute value not found or does not match parent attribute');
|
||||
}
|
||||
finalValueId = row.id;
|
||||
finalValue = row.value;
|
||||
} else if (typeof options.value === 'string' && options.value.trim()) {
|
||||
const valStr = options.value.trim();
|
||||
const existingValRes = await runner.query(attrValQueries.GET_BY_ATTRIBUTE_AND_VALUE, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
attributeId: parent.parentattributename_id,
|
||||
value: valStr,
|
||||
});
|
||||
if (existingValRes.rows[0]) {
|
||||
@@ -113,11 +113,11 @@ export default function attachChild(provider) {
|
||||
finalValue = existingValRes.rows[0].value;
|
||||
} else {
|
||||
const sortRes = await runner.query(attrValQueries.GET_NEXT_SORT, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
attributeId: parent.parentattributename_id,
|
||||
});
|
||||
const sort = sortRes.rows[0]?.sort ?? 0;
|
||||
const createdValRes = await runner.query(attrValQueries.CREATE, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
attributeId: parent.parentattributename_id,
|
||||
value: valStr,
|
||||
sort,
|
||||
});
|
||||
@@ -128,78 +128,78 @@ export default function attachChild(provider) {
|
||||
throw new Error('Attribute value is required for child article');
|
||||
}
|
||||
|
||||
const isAlreadyChildOfThisFather = child.fatherarticle_id === fatherId;
|
||||
const isAlreadyChildOfThisParent = child.parentarticle_id === parentId;
|
||||
|
||||
if (child.fatherarticle_id != null && !isAlreadyChildOfThisFather && !options.reparent) {
|
||||
throw new Error('Article already belongs to another father');
|
||||
if (child.parentarticle_id != null && !isAlreadyChildOfThisParent && !options.reparent) {
|
||||
throw new Error('Article already belongs to another parent');
|
||||
}
|
||||
|
||||
// Check if father already has a different child with this attribute value
|
||||
// Check if parent already has a different child with this attribute value
|
||||
const existingVariantRes = await runner.query(
|
||||
`SELECT a.id
|
||||
FROM article a
|
||||
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
|
||||
WHERE a.fatherarticle_id = @fatherId
|
||||
WHERE a.parentarticle_id = @parentId
|
||||
AND a.id != @childId
|
||||
AND aav.attributevalue_id = @finalValueId`,
|
||||
{ fatherId, childId: child.id, finalValueId },
|
||||
{ parentId, childId: child.id, finalValueId },
|
||||
);
|
||||
if (existingVariantRes.rows.length > 0) {
|
||||
throw new Error('Variant with this attribute value already exists');
|
||||
}
|
||||
|
||||
// If re-parenting from another father, unparent and recompact previous father
|
||||
if (child.fatherarticle_id != null && !isAlreadyChildOfThisFather && options.reparent) {
|
||||
const prevFatherId = child.fatherarticle_id;
|
||||
// If re-parenting from another parent, unparent and recompact previous parent
|
||||
if (child.parentarticle_id != null && !isAlreadyChildOfThisParent && options.reparent) {
|
||||
const prevParentId = child.parentarticle_id;
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: prevFatherId,
|
||||
articleId: prevParentId,
|
||||
companyuserId,
|
||||
text: `Variante (${childSku}) getrennt (umgehängt zu "${father.sku}")`,
|
||||
text: `Variante (${childSku}) getrennt (umgehängt zu "${parent.sku}")`,
|
||||
});
|
||||
|
||||
// Recompact previous father's children
|
||||
// Recompact previous parent's children
|
||||
const prevRemainingRes = await runner.query(
|
||||
'SELECT id, fathersort FROM article WHERE fatherarticle_id = @prevFatherId AND id != @childId ORDER BY fathersort ASC, id ASC',
|
||||
{ prevFatherId, childId: child.id },
|
||||
'SELECT id, parentsort FROM article WHERE parentarticle_id = @prevParentId AND id != @childId ORDER BY parentsort ASC, id ASC',
|
||||
{ prevParentId, childId: child.id },
|
||||
);
|
||||
for (let i = 0; i < prevRemainingRes.rows.length; i++) {
|
||||
const row = prevRemainingRes.rows[i];
|
||||
if (Number(row.fathersort) !== i) {
|
||||
await runner.query(queries.SET_FATHER_SORT, {
|
||||
if (Number(row.parentsort) !== i) {
|
||||
await runner.query(queries.SET_PARENT_SORT, {
|
||||
articleId: row.id,
|
||||
fatherArticleId: prevFatherId,
|
||||
fathersort: i,
|
||||
parentArticleId: prevParentId,
|
||||
parentsort: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign fathersort if not already child of this father
|
||||
let fathersort;
|
||||
if (isAlreadyChildOfThisFather) {
|
||||
// Assign parentsort if not already child of this parent
|
||||
let parentsort;
|
||||
if (isAlreadyChildOfThisParent) {
|
||||
const currentSortRes = await runner.query(
|
||||
'SELECT fathersort FROM article WHERE id = @childId',
|
||||
'SELECT parentsort FROM article WHERE id = @childId',
|
||||
{ childId: child.id },
|
||||
);
|
||||
fathersort = Number(currentSortRes.rows[0]?.fathersort ?? 0);
|
||||
parentsort = Number(currentSortRes.rows[0]?.parentsort ?? 0);
|
||||
} else {
|
||||
const existingSortsRes = await runner.query(
|
||||
'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC',
|
||||
{ fatherId },
|
||||
'SELECT parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC',
|
||||
{ parentId },
|
||||
);
|
||||
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort)));
|
||||
fathersort = 0;
|
||||
while (usedSorts.has(fathersort)) {
|
||||
fathersort++;
|
||||
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.parentsort)));
|
||||
parentsort = 0;
|
||||
while (usedSorts.has(parentsort)) {
|
||||
parentsort++;
|
||||
}
|
||||
}
|
||||
|
||||
// Link child to father
|
||||
await runner.query(queries.UPDATE_FATHER_BY_SKU, {
|
||||
// Link child to parent
|
||||
await runner.query(queries.UPDATE_PARENT_BY_SKU, {
|
||||
sku: childSku,
|
||||
companyId,
|
||||
fatherArticleId: fatherId,
|
||||
fathersort,
|
||||
parentArticleId: parentId,
|
||||
parentsort,
|
||||
});
|
||||
|
||||
// Apply optional field overrides
|
||||
@@ -223,15 +223,15 @@ export default function attachChild(provider) {
|
||||
});
|
||||
}
|
||||
|
||||
// Remove existing attribute value for this father's attribute on child (if any)
|
||||
if (father.fatherattributename_id) {
|
||||
// Remove existing attribute value for this parent's attribute on child (if any)
|
||||
if (parent.parentattributename_id) {
|
||||
await runner.query(
|
||||
`DELETE FROM articleattributevalue
|
||||
WHERE article_id = @childId
|
||||
AND attributevalue_id IN (
|
||||
SELECT id FROM attributevalue WHERE attribute_id = @attributeId
|
||||
)`,
|
||||
{ childId: child.id, attributeId: father.fatherattributename_id },
|
||||
{ childId: child.id, attributeId: parent.parentattributename_id },
|
||||
);
|
||||
}
|
||||
await runner.query(articleAttrValQueries.INSERT_ROW, {
|
||||
@@ -240,12 +240,12 @@ export default function attachChild(provider) {
|
||||
sort: 0,
|
||||
});
|
||||
|
||||
// Copy ancestor attributes from father (if missing on child)
|
||||
const fatherAttrsRes = await runner.query(
|
||||
'SELECT attributevalue_id, sort FROM articleattributevalue WHERE article_id = @fatherId',
|
||||
{ fatherId },
|
||||
// Copy ancestor attributes from parent (if missing on child)
|
||||
const parentAttrsRes = await runner.query(
|
||||
'SELECT attributevalue_id, sort FROM articleattributevalue WHERE article_id = @parentId',
|
||||
{ parentId },
|
||||
);
|
||||
for (const faRow of fatherAttrsRes.rows) {
|
||||
for (const faRow of parentAttrsRes.rows) {
|
||||
if (faRow.attributevalue_id !== finalValueId) {
|
||||
const hasAttrRes = await runner.query(
|
||||
'SELECT 1 FROM articleattributevalue WHERE article_id = @childId AND attributevalue_id = @attrValId',
|
||||
@@ -261,10 +261,10 @@ export default function attachChild(provider) {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy father's categories (if missing on child)
|
||||
// Copy parent's categories (if missing on child)
|
||||
const catRes = await runner.query(
|
||||
'SELECT category_id FROM categoryarticle WHERE article_id = @fatherId',
|
||||
{ fatherId },
|
||||
'SELECT category_id FROM categoryarticle WHERE article_id = @parentId',
|
||||
{ parentId },
|
||||
);
|
||||
for (const catRow of catRes.rows) {
|
||||
const hasCatRes = await runner.query(
|
||||
@@ -287,22 +287,22 @@ export default function attachChild(provider) {
|
||||
const childName = childTextRes.rows[0]?.name || childSku;
|
||||
|
||||
// History only if not already child
|
||||
if (!isAlreadyChildOfThisFather) {
|
||||
if (!isAlreadyChildOfThisParent) {
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: child.id,
|
||||
companyuserId,
|
||||
text: `Artikel "${childName}" (${childSku}) als Variante von "${father.sku}" zugewiesen`,
|
||||
text: `Artikel "${childName}" (${childSku}) als Variante von "${parent.sku}" zugewiesen`,
|
||||
});
|
||||
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: fatherId,
|
||||
articleId: parentId,
|
||||
companyuserId,
|
||||
text: `Variante "${childName}" (${childSku}) hinzugefügt`,
|
||||
});
|
||||
}
|
||||
|
||||
const updated = await load(googleAuthId, childSku, runner);
|
||||
const children = await list(googleAuthId, fatherSku, runner);
|
||||
const children = await list(googleAuthId, parentSku, runner);
|
||||
|
||||
return {
|
||||
article: updated.article,
|
||||
|
||||
@@ -69,11 +69,11 @@ export default function del(provider) {
|
||||
await deleteArticleAndLists(id);
|
||||
};
|
||||
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT fatherarticle_id FROM article WHERE id = @articleId',
|
||||
const parentRes = await runner.query(
|
||||
'SELECT parentarticle_id FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
const fatherArticleId = fatherRes.rows[0]?.fatherarticle_id;
|
||||
const parentArticleId = parentRes.rows[0]?.parentarticle_id;
|
||||
|
||||
const childCount = await runner.query(queries.GET_CHILD_COUNT, { articleId });
|
||||
if (childCount.rows[0].count > 0) {
|
||||
@@ -85,18 +85,18 @@ export default function del(provider) {
|
||||
await deleteArticleAndLists(articleId);
|
||||
}
|
||||
|
||||
if (fatherArticleId != null) {
|
||||
if (parentArticleId != null) {
|
||||
const remainingChildrenRes = await runner.query(
|
||||
'SELECT id, fathersort FROM article WHERE fatherarticle_id = @fatherArticleId ORDER BY fathersort ASC, id ASC',
|
||||
{ fatherArticleId },
|
||||
'SELECT id, parentsort FROM article WHERE parentarticle_id = @parentArticleId ORDER BY parentsort ASC, id ASC',
|
||||
{ parentArticleId },
|
||||
);
|
||||
for (let i = 0; i < remainingChildrenRes.rows.length; i++) {
|
||||
const childRow = remainingChildrenRes.rows[i];
|
||||
if (Number(childRow.fathersort) !== i) {
|
||||
await runner.query(articleQueries.SET_FATHER_SORT, {
|
||||
if (Number(childRow.parentsort) !== i) {
|
||||
await runner.query(articleQueries.SET_PARENT_SORT, {
|
||||
articleId: childRow.id,
|
||||
fatherArticleId,
|
||||
fathersort: i,
|
||||
parentArticleId,
|
||||
parentsort: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function getBySku(provider) {
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} sku - The article SKU.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null}}>} The full article detail.
|
||||
* @returns {Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>} The full article detail.
|
||||
* @throws {Error} If the article is not found or the user is not authorized.
|
||||
*
|
||||
* @example
|
||||
@@ -118,16 +118,16 @@ export default function getBySku(provider) {
|
||||
sort: Number(item.sort ?? 0),
|
||||
})),
|
||||
childCount: a.childCount,
|
||||
isFather: Boolean(a.isFather),
|
||||
fatherAttributeNameId: a.fatherAttributeNameId ?? null,
|
||||
fatherAttributeName: a.fatherAttributeName ?? null,
|
||||
fatherAttributeExclusive: a.fatherAttributeExclusive != null ? Boolean(a.fatherAttributeExclusive) : null,
|
||||
fatherArticleId: a.fatherArticleId,
|
||||
fatherSku: a.fatherSku,
|
||||
isParent: Boolean(a.isParent),
|
||||
parentAttributeNameId: a.parentAttributeNameId ?? null,
|
||||
parentAttributeName: a.parentAttributeName ?? null,
|
||||
parentAttributeExclusive: a.parentAttributeExclusive != null ? Boolean(a.parentAttributeExclusive) : null,
|
||||
parentArticleId: a.parentArticleId,
|
||||
parentSku: a.parentSku,
|
||||
children: parseJsonArray(a.childrenJson).map((c) => ({
|
||||
id: c.id,
|
||||
sku: c.sku,
|
||||
fathersort: c.fathersort,
|
||||
parentsort: c.parentsort,
|
||||
name: c.name ?? null,
|
||||
attributeValueId: c.attributeValueId ?? null,
|
||||
attributeValue: c.attributeValue ?? null,
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function importRows(provider) {
|
||||
* Imports article rows, creating articles by their SKU and returning per-row
|
||||
* import, skip and error outcomes.
|
||||
*
|
||||
* The import runs in **two passes** so a `fatherSku` that references a row
|
||||
* The import runs in **two passes** so a `parentSku` that references a row
|
||||
* appearing later in the file still resolves. A row is **skipped** when its
|
||||
* SKU already exists in the company (upsert mode is not applied), and an EAN
|
||||
* is skipped when already used by another row.
|
||||
@@ -97,7 +97,7 @@ export default function importRows(provider) {
|
||||
* @param {Array<object>} input.rows - The article rows to import.
|
||||
* @param {string} input.rows[].sku - The article SKU.
|
||||
* @param {string} input.rows[].name - The article name.
|
||||
* @param {string} [input.rows[].vk] - `'V'` (father/compound) or `'K'` (variant) marker.
|
||||
* @param {string} [input.rows[].vk] - `'V'` (parent/compound) or `'K'` (variant) marker.
|
||||
* @param {number|string} [input.rows[].parentId] - The id of the parent category.
|
||||
* @param {string} [input.rows[].parentCategory] - The category breadcrumb path, e.g. `'Flowers | CBN'`.
|
||||
* @param {string} [input.rows[].brand] - The brand name to upsert.
|
||||
@@ -105,7 +105,7 @@ export default function importRows(provider) {
|
||||
* @param {number|string} [input.rows[].tax] - The tax rate in percent (e.g. `19`).
|
||||
* @param {Array<string>|string} [input.rows[].eans] - The EANs to set.
|
||||
* @param {boolean|number} [input.rows[].track_stock=true] - Whether inventory stock is tracked.
|
||||
* @param {string} [input.rows[].fatherSku] - The father article SKU for a V/K compound link.
|
||||
* @param {string} [input.rows[].parentSku] - The parent article SKU for a V/K compound link.
|
||||
* @param {number|string} [input.rows[].deposit] - The deposit (Pfand) value; currently ignored.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{imported: Array<object>, skipped: Array<object>, errors: Array<object>}>} The import outcome.
|
||||
@@ -114,7 +114,7 @@ export default function importRows(provider) {
|
||||
* const result = await store.article.importRows({
|
||||
* googleAuthId: 42,
|
||||
* rows: [
|
||||
* { sku: '42001190-1', name: '420Strainz 1g', vk: 'K', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '11,00', tax: 19, eans: ['4262557690455'], fatherSku: '42001190' },
|
||||
* { sku: '42001190-1', name: '420Strainz 1g', vk: 'K', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '11,00', tax: 19, eans: ['4262557690455'], parentSku: '42001190' },
|
||||
* { sku: '42001190', name: '420Strainz', vk: 'V', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '0,00', tax: 19 },
|
||||
* ],
|
||||
* });
|
||||
@@ -275,63 +275,63 @@ export default function importRows(provider) {
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: wire V/K father links (fathers may be created in pass 1).
|
||||
// Pass 2: wire V/K parent links (parents may be created in pass 1).
|
||||
for (const row of rows) {
|
||||
const sku = String(row?.sku ?? '').trim();
|
||||
const fatherSku =
|
||||
row?.fatherSku != null ? String(row.fatherSku).trim() : '';
|
||||
if (!sku || !fatherSku) continue;
|
||||
const parentSku =
|
||||
row?.parentSku != null ? String(row.parentSku).trim() : '';
|
||||
if (!sku || !parentSku) continue;
|
||||
|
||||
try {
|
||||
await runInTx(async (runner) => {
|
||||
const child = await runner.query(articleQueries.GET_ID_BY_SKU, { sku, companyId });
|
||||
const childId = child.rows[0]?.articleId;
|
||||
if (childId) {
|
||||
const father = await runner.query(
|
||||
'SELECT id, is_father FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId)',
|
||||
{ sku: fatherSku, companyId },
|
||||
const parent = await runner.query(
|
||||
'SELECT id, is_parent FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId)',
|
||||
{ sku: parentSku, companyId },
|
||||
);
|
||||
const fatherRow = father.rows[0];
|
||||
const fatherId = fatherRow?.id;
|
||||
if (fatherId && fatherId !== childId) {
|
||||
const wasFather = Boolean(fatherRow.is_father);
|
||||
const parentRow = parent.rows[0];
|
||||
const parentId = parentRow?.id;
|
||||
if (parentId && parentId !== childId) {
|
||||
const wasParent = Boolean(parentRow.is_parent);
|
||||
|
||||
const existingSortsRes = await runner.query(
|
||||
'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC',
|
||||
{ fatherId },
|
||||
'SELECT parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC',
|
||||
{ parentId },
|
||||
);
|
||||
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort)));
|
||||
let fathersort = 0;
|
||||
while (usedSorts.has(fathersort)) {
|
||||
fathersort++;
|
||||
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.parentsort)));
|
||||
let parentsort = 0;
|
||||
while (usedSorts.has(parentsort)) {
|
||||
parentsort++;
|
||||
}
|
||||
await runner.query(articleQueries.UPDATE_FATHER_BY_SKU, {
|
||||
await runner.query(articleQueries.UPDATE_PARENT_BY_SKU, {
|
||||
sku,
|
||||
companyId,
|
||||
fatherArticleId: fatherId,
|
||||
fathersort,
|
||||
parentArticleId: parentId,
|
||||
parentsort,
|
||||
});
|
||||
await runner.query(articleQueries.SET_IS_FATHER, {
|
||||
articleId: fatherId,
|
||||
await runner.query(articleQueries.SET_IS_PARENT, {
|
||||
articleId: parentId,
|
||||
});
|
||||
|
||||
if (!wasFather) {
|
||||
if (!wasParent) {
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: fatherId,
|
||||
articleId: parentId,
|
||||
companyuserId,
|
||||
text: 'Artikel zu Vaterartikel konvertiert',
|
||||
});
|
||||
}
|
||||
const childName = String(row?.name ?? sku).trim();
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: fatherId,
|
||||
articleId: parentId,
|
||||
companyuserId,
|
||||
text: `Variante "${childName}" (${sku}) hinzugefügt`,
|
||||
});
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: childId,
|
||||
companyuserId,
|
||||
text: `Artikel "${childName}" (${sku}) als Variante von "${fatherSku}" zugewiesen`,
|
||||
text: `Artikel "${childName}" (${sku}) als Variante von "${parentSku}" zugewiesen`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,40 +9,40 @@ export default function listChildren(provider) {
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
|
||||
/**
|
||||
* Lists all children of a father article.
|
||||
* Lists all children of a parent article.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father article SKU.
|
||||
* @param {string} parentSku - The parent article SKU.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<Array<{id: number, sku: string, fathersort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>}
|
||||
* @returns {Promise<Array<{id: number, sku: string, parentsort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>}
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, tx) => {
|
||||
return async (googleAuthId, parentSku, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId: fatherId, languageId } = await resolve(
|
||||
const { articleId: parentId, languageId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
parentSku,
|
||||
runner,
|
||||
);
|
||||
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT is_father, fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
const parentRes = await runner.query(
|
||||
'SELECT is_parent, parentattributename_id FROM article WHERE id = @parentId',
|
||||
{ parentId },
|
||||
);
|
||||
const father = fatherRes.rows[0];
|
||||
if (!father || !father.is_father) {
|
||||
const parent = parentRes.rows[0];
|
||||
if (!parent || !parent.is_parent) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await runner.query(queries.LIST_CHILDREN_BY_FATHER_ID, {
|
||||
fatherArticleId: fatherId,
|
||||
const result = await runner.query(queries.LIST_CHILDREN_BY_PARENT_ID, {
|
||||
parentArticleId: parentId,
|
||||
languageId,
|
||||
attributeId: father.fatherattributename_id,
|
||||
attributeId: parent.parentattributename_id,
|
||||
});
|
||||
|
||||
return result.rows.map((row) => ({
|
||||
id: row.id,
|
||||
sku: row.sku,
|
||||
fathersort: Number(row.fathersort ?? 0),
|
||||
parentsort: Number(row.parentsort ?? 0),
|
||||
trackStock: Boolean(row.trackStock ?? 1),
|
||||
unitamount: Number(row.unitamount ?? 1),
|
||||
weight: Number(row.weight ?? 0),
|
||||
|
||||
@@ -35,8 +35,8 @@ function shapeArticleDetail(row, categoryIds, photos, eans, prices) {
|
||||
optionsCount: row.optionsCount,
|
||||
articleListCount: row.articleListCount,
|
||||
childCount: row.childCount,
|
||||
fatherArticleId: row.fatherArticleId,
|
||||
fatherSku: row.fatherSku,
|
||||
parentArticleId: row.parentArticleId,
|
||||
parentSku: row.parentSku,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,34 +15,34 @@ export default function removeChild(provider) {
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Removes a child article from a father article, either by unlinking or deleting it.
|
||||
* Removes a child article from a parent article, either by unlinking or deleting it.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father article SKU.
|
||||
* @param {string} parentSku - The parent article SKU.
|
||||
* @param {string} childSku - The child article SKU.
|
||||
* @param {object} [options]
|
||||
* @param {boolean} [options.deleteArticle=false] - Whether to delete the child article entity entirely.
|
||||
* @param {boolean} [options.cascade=true] - Whether to cascade delete child subtrees when deleteArticle is true.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>}
|
||||
* @returns {Promise<{success: boolean, parentSku: string, childSku: string, deleted: boolean}>}
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, childSku, options = {}, tx) => {
|
||||
return async (googleAuthId, parentSku, childSku, options = {}, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId: fatherId, languageId, companyuserId } = await resolve(
|
||||
const { articleId: parentId, languageId, companyuserId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
parentSku,
|
||||
runner,
|
||||
);
|
||||
const { articleId: childId } = await resolve(googleAuthId, childSku, runner);
|
||||
|
||||
// Verify child relationship
|
||||
const childRes = await runner.query(
|
||||
'SELECT fatherarticle_id FROM article WHERE id = @childId',
|
||||
'SELECT parentarticle_id FROM article WHERE id = @childId',
|
||||
{ childId },
|
||||
);
|
||||
const child = childRes.rows[0];
|
||||
if (!child || child.fatherarticle_id !== fatherId) {
|
||||
throw new Error('Child article does not belong to this father');
|
||||
if (!child || child.parentarticle_id !== parentId) {
|
||||
throw new Error('Child article does not belong to this parent');
|
||||
}
|
||||
|
||||
// Fetch child name before any deletion (needed for history regardless of path)
|
||||
@@ -54,18 +54,18 @@ export default function removeChild(provider) {
|
||||
);
|
||||
const childName = childNameRes.rows[0]?.name || childSku;
|
||||
|
||||
const recompactFatherSort = async () => {
|
||||
const recompactParentSort = async () => {
|
||||
const remainingChildrenRes = await runner.query(
|
||||
'SELECT id, fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC, id ASC',
|
||||
{ fatherId },
|
||||
'SELECT id, parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC, id ASC',
|
||||
{ parentId },
|
||||
);
|
||||
for (let i = 0; i < remainingChildrenRes.rows.length; i++) {
|
||||
const childRow = remainingChildrenRes.rows[i];
|
||||
if (Number(childRow.fathersort) !== i) {
|
||||
await runner.query(queries.SET_FATHER_SORT, {
|
||||
if (Number(childRow.parentsort) !== i) {
|
||||
await runner.query(queries.SET_PARENT_SORT, {
|
||||
articleId: childRow.id,
|
||||
fatherArticleId: fatherId,
|
||||
fathersort: i,
|
||||
parentArticleId: parentId,
|
||||
parentsort: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -75,67 +75,67 @@ export default function removeChild(provider) {
|
||||
const cascade = options.cascade !== undefined ? Boolean(options.cascade) : true;
|
||||
await del(googleAuthId, childSku, { cascade }, runner);
|
||||
|
||||
await recompactFatherSort();
|
||||
await recompactParentSort();
|
||||
|
||||
// History on father (child article is gone so we can only log on father)
|
||||
// History on parent (child article is gone so we can only log on parent)
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: fatherId,
|
||||
articleId: parentId,
|
||||
companyuserId,
|
||||
text: `Variante "${childName}" (${childSku}) gelöscht`,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
fatherSku,
|
||||
parentSku,
|
||||
childSku,
|
||||
deleted: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch father's attribute name id
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
// Fetch parent's attribute name id
|
||||
const parentRes = await runner.query(
|
||||
'SELECT parentattributename_id FROM article WHERE id = @parentId',
|
||||
{ parentId },
|
||||
);
|
||||
const fatherAttributeNameId = fatherRes.rows[0]?.fatherattributename_id;
|
||||
const parentAttributeNameId = parentRes.rows[0]?.parentattributename_id;
|
||||
|
||||
// Unlink child from father
|
||||
// Unlink child from parent
|
||||
await runner.query(queries.UNLINK_CHILD, {
|
||||
articleId: childId,
|
||||
fatherArticleId: fatherId,
|
||||
parentArticleId: parentId,
|
||||
});
|
||||
|
||||
// Remove the child's attribute value link for the father's attribute
|
||||
if (fatherAttributeNameId) {
|
||||
// Remove the child's attribute value link for the parent's attribute
|
||||
if (parentAttributeNameId) {
|
||||
await runner.query(
|
||||
`DELETE FROM articleattributevalue
|
||||
WHERE article_id = @childId
|
||||
AND attributevalue_id IN (
|
||||
SELECT id FROM attributevalue WHERE attribute_id = @fatherAttributeNameId
|
||||
SELECT id FROM attributevalue WHERE attribute_id = @parentAttributeNameId
|
||||
)`,
|
||||
{ childId, fatherAttributeNameId },
|
||||
{ childId, parentAttributeNameId },
|
||||
);
|
||||
}
|
||||
|
||||
await recompactFatherSort();
|
||||
await recompactParentSort();
|
||||
|
||||
// Add history on child
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: childId,
|
||||
companyuserId,
|
||||
text: `Artikel "${childSku}" von Vaterartikel "${fatherSku}" getrennt`,
|
||||
text: `Artikel "${childSku}" von Vaterartikel "${parentSku}" getrennt`,
|
||||
});
|
||||
|
||||
// Add history on father
|
||||
// Add history on parent
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: fatherId,
|
||||
articleId: parentId,
|
||||
companyuserId,
|
||||
text: `Variante "${childName}" (${childSku}) getrennt`,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
fatherSku,
|
||||
parentSku,
|
||||
childSku,
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import queryBySku from './queryBySku.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function setFatherBySku(provider) {
|
||||
export default function setParentBySku(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const load = queryBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
@@ -18,20 +18,20 @@ export default function setFatherBySku(provider) {
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Links an article to a father article (V/K compound) by their SKUs.
|
||||
* Links an article to a parent article (V/K compound) by their SKUs.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} sku - The child article SKU.
|
||||
* @param {string|null} fatherSku - The father article SKU, or `null` to unlink.
|
||||
* @param {string|null} parentSku - The parent article SKU, or `null` to unlink.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<object>} The updated child article.
|
||||
* @throws {Error} If the article or father is not found, or the link is invalid.
|
||||
* @throws {Error} If the article or parent is not found, or the link is invalid.
|
||||
*
|
||||
* @example
|
||||
* const article = await store.article.setFatherBySku(42, '42001190-1', '42001190');
|
||||
* // => { id, sku, name, fatherSku: '42001190', ... }
|
||||
* const article = await store.article.setParentBySku(42, '42001190-1', '42001190');
|
||||
* // => { id, sku, name, parentSku: '42001190', ... }
|
||||
*/
|
||||
return async (googleAuthId, sku, fatherSku, tx) => {
|
||||
return async (googleAuthId, sku, parentSku, tx) => {
|
||||
const runSet = async (runner) => {
|
||||
const { articleId, languageId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||
|
||||
@@ -41,34 +41,34 @@ export default function setFatherBySku(provider) {
|
||||
);
|
||||
const childName = childTextRes.rows[0]?.name || sku;
|
||||
|
||||
if (fatherSku == null || fatherSku === '') {
|
||||
const prevFatherRes = await runner.query(
|
||||
'SELECT fatherarticle_id FROM article WHERE id = @articleId',
|
||||
if (parentSku == null || parentSku === '') {
|
||||
const prevParentRes = await runner.query(
|
||||
'SELECT parentarticle_id FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
const prevFatherId = prevFatherRes.rows[0]?.fatherarticle_id;
|
||||
const prevParentId = prevParentRes.rows[0]?.parentarticle_id;
|
||||
|
||||
await runner.query(queries.UPDATE_FATHER_BY_SKU, {
|
||||
await runner.query(queries.UPDATE_PARENT_BY_SKU, {
|
||||
sku,
|
||||
companyId,
|
||||
fatherArticleId: null,
|
||||
parentArticleId: null,
|
||||
});
|
||||
|
||||
if (prevFatherId) {
|
||||
const prevFatherTextRes = await runner.query(
|
||||
'SELECT sku FROM article WHERE id = @prevFatherId',
|
||||
{ prevFatherId },
|
||||
if (prevParentId) {
|
||||
const prevParentTextRes = await runner.query(
|
||||
'SELECT sku FROM article WHERE id = @prevParentId',
|
||||
{ prevParentId },
|
||||
);
|
||||
const prevFatherSku = prevFatherTextRes.rows[0]?.sku || String(prevFatherId);
|
||||
const prevParentSku = prevParentTextRes.rows[0]?.sku || String(prevParentId);
|
||||
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId,
|
||||
companyuserId,
|
||||
text: `Artikel "${sku}" von Vaterartikel "${prevFatherSku}" getrennt`,
|
||||
text: `Artikel "${sku}" von Vaterartikel "${prevParentSku}" getrennt`,
|
||||
});
|
||||
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: prevFatherId,
|
||||
articleId: prevParentId,
|
||||
companyuserId,
|
||||
text: `Variante "${childName}" (${sku}) getrennt`,
|
||||
});
|
||||
@@ -81,52 +81,52 @@ export default function setFatherBySku(provider) {
|
||||
return unlinked;
|
||||
}
|
||||
|
||||
const father = await runner.query(
|
||||
'SELECT id, is_father, sku FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId)',
|
||||
{ sku: fatherSku, companyId },
|
||||
const parent = await runner.query(
|
||||
'SELECT id, is_parent, sku FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId)',
|
||||
{ sku: parentSku, companyId },
|
||||
);
|
||||
const fatherRow = father.rows[0];
|
||||
const fatherId = fatherRow?.id;
|
||||
if (!fatherId) {
|
||||
throw new Error('Father not found');
|
||||
const parentRow = parent.rows[0];
|
||||
const parentId = parentRow?.id;
|
||||
if (!parentId) {
|
||||
throw new Error('Parent not found');
|
||||
}
|
||||
if (fatherId === articleId) {
|
||||
throw new Error('Invalid father');
|
||||
if (parentId === articleId) {
|
||||
throw new Error('Invalid parent');
|
||||
}
|
||||
|
||||
const wasFather = Boolean(fatherRow.is_father);
|
||||
const wasParent = Boolean(parentRow.is_parent);
|
||||
|
||||
const existingSortsRes = await runner.query(
|
||||
'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC',
|
||||
{ fatherId },
|
||||
'SELECT parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC',
|
||||
{ parentId },
|
||||
);
|
||||
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort)));
|
||||
let fathersort = 0;
|
||||
while (usedSorts.has(fathersort)) {
|
||||
fathersort++;
|
||||
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.parentsort)));
|
||||
let parentsort = 0;
|
||||
while (usedSorts.has(parentsort)) {
|
||||
parentsort++;
|
||||
}
|
||||
|
||||
await runner.query(queries.UPDATE_FATHER_BY_SKU, {
|
||||
await runner.query(queries.UPDATE_PARENT_BY_SKU, {
|
||||
sku,
|
||||
companyId,
|
||||
fatherArticleId: fatherId,
|
||||
fathersort,
|
||||
parentArticleId: parentId,
|
||||
parentsort,
|
||||
});
|
||||
|
||||
await runner.query(queries.SET_IS_FATHER, {
|
||||
articleId: fatherId,
|
||||
await runner.query(queries.SET_IS_PARENT, {
|
||||
articleId: parentId,
|
||||
});
|
||||
|
||||
if (!wasFather) {
|
||||
if (!wasParent) {
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: fatherId,
|
||||
articleId: parentId,
|
||||
companyuserId,
|
||||
text: 'Artikel zu Vaterartikel konvertiert',
|
||||
});
|
||||
}
|
||||
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: fatherId,
|
||||
articleId: parentId,
|
||||
companyuserId,
|
||||
text: `Variante "${childName}" (${sku}) hinzugefügt`,
|
||||
});
|
||||
@@ -134,7 +134,7 @@ export default function setFatherBySku(provider) {
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId,
|
||||
companyuserId,
|
||||
text: `Artikel "${sku}" als Variante von "${fatherSku}" zugewiesen`,
|
||||
text: `Artikel "${sku}" als Variante von "${parentSku}" zugewiesen`,
|
||||
});
|
||||
|
||||
const article = await load(articleId, languageId, runner);
|
||||
|
||||
@@ -86,9 +86,9 @@ export default function setPricesBySku(provider) {
|
||||
throw new Error('POS customer group required');
|
||||
}
|
||||
|
||||
const artCheck = await runner.query('SELECT is_father FROM article WHERE id = @articleId', { articleId });
|
||||
if (artCheck.rows[0]?.is_father) {
|
||||
throw new Error('A father article cannot have prices');
|
||||
const artCheck = await runner.query('SELECT is_parent FROM article WHERE id = @articleId', { articleId });
|
||||
if (artCheck.rows[0]?.is_parent) {
|
||||
throw new Error('A parent article cannot have prices');
|
||||
}
|
||||
|
||||
// Read old prices for history
|
||||
|
||||
@@ -10,7 +10,7 @@ import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import getBySku from './getBySku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function toggleFather(provider) {
|
||||
export default function toggleParent(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const load = getBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
@@ -21,39 +21,39 @@ export default function toggleFather(provider) {
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Toggles an article into or out of father mode.
|
||||
* Toggles an article into or out of parent mode.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} sku - The article SKU.
|
||||
* @param {object} options
|
||||
* @param {boolean} options.isFather - Whether the article should be a father article.
|
||||
* @param {number} [options.attributeNameId] - The attribute name id required when enabling father mode.
|
||||
* @param {number} [options.fatherAttributeNameId] - Alias for attributeNameId.
|
||||
* @param {boolean} options.isParent - Whether the article should be a parent article.
|
||||
* @param {number} [options.attributeNameId] - The attribute name id required when enabling parent mode.
|
||||
* @param {number} [options.parentAttributeNameId] - Alias for attributeNameId.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The updated article detail.
|
||||
*/
|
||||
return async (googleAuthId, sku, { isFather, attributeNameId, fatherAttributeNameId } = {}, tx) => {
|
||||
return async (googleAuthId, sku, { isParent, attributeNameId, parentAttributeNameId } = {}, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||
|
||||
const targetIsFather = Boolean(isFather);
|
||||
const targetAttrId = attributeNameId ?? fatherAttributeNameId;
|
||||
const targetIsParent = Boolean(isParent);
|
||||
const targetAttrId = attributeNameId ?? parentAttributeNameId;
|
||||
|
||||
if (targetIsFather) {
|
||||
if (targetIsParent) {
|
||||
if (!targetAttrId || Number(targetAttrId) <= 0) {
|
||||
throw new Error('attributeNameId is required to become a father article');
|
||||
throw new Error('attributeNameId is required to become a parent article');
|
||||
}
|
||||
|
||||
const currentRes = await runner.query(
|
||||
'SELECT fatherarticle_id, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
||||
'SELECT parentarticle_id, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
const currentArt = currentRes.rows[0];
|
||||
if (currentArt?.articlelist_id != null) {
|
||||
throw new Error('A Stückliste article cannot become a father');
|
||||
throw new Error('A Stückliste article cannot become a parent');
|
||||
}
|
||||
if (currentArt?.optionslist_id != null) {
|
||||
throw new Error('An article with options cannot become a father');
|
||||
throw new Error('An article with options cannot become a parent');
|
||||
}
|
||||
|
||||
const stockRes = await runner.query(
|
||||
@@ -70,7 +70,7 @@ export default function toggleFather(provider) {
|
||||
);
|
||||
const totalStock = Number(stockRes.rows[0]?.totalStock ?? 0);
|
||||
if (totalStock !== 0) {
|
||||
throw new Error('An article with existing stock cannot become a father article');
|
||||
throw new Error('An article with existing stock cannot become a parent article');
|
||||
}
|
||||
|
||||
const supRes = await runner.query(
|
||||
@@ -78,7 +78,7 @@ export default function toggleFather(provider) {
|
||||
{ articleId },
|
||||
);
|
||||
if ((supRes.rows[0]?.count ?? 0) > 0) {
|
||||
throw new Error('An article with assigned suppliers cannot become a father article');
|
||||
throw new Error('An article with assigned suppliers cannot become a parent article');
|
||||
}
|
||||
|
||||
// Fetch existing prices before deletion to record in articlehistory
|
||||
@@ -96,22 +96,22 @@ export default function toggleFather(provider) {
|
||||
await runner.query('DELETE FROM price WHERE article_id = @articleId', { articleId });
|
||||
await runner.query('UPDATE article SET track_stock = 0 WHERE id = @articleId', { articleId });
|
||||
|
||||
// If article is a child, verify attribute is not used by father or any father up the chain
|
||||
let currentFatherId = currentArt?.fatherarticle_id;
|
||||
// If article is a child, verify attribute is not used by parent or any parent up the chain
|
||||
let currentParentId = currentArt?.parentarticle_id;
|
||||
const visited = new Set();
|
||||
while (currentFatherId != null && !visited.has(currentFatherId)) {
|
||||
visited.add(currentFatherId);
|
||||
while (currentParentId != null && !visited.has(currentParentId)) {
|
||||
visited.add(currentParentId);
|
||||
const fRes = await runner.query(
|
||||
'SELECT a.fatherarticle_id, a.fatherattributename_id, an.name AS fatherAttributeName FROM article a LEFT JOIN attributename an ON an.id = a.fatherattributename_id WHERE a.id = @currentFatherId',
|
||||
{ currentFatherId },
|
||||
'SELECT a.parentarticle_id, a.parentattributename_id, an.name AS parentAttributeName FROM article a LEFT JOIN attributename an ON an.id = a.parentattributename_id WHERE a.id = @currentParentId',
|
||||
{ currentParentId },
|
||||
);
|
||||
const fRow = fRes.rows[0];
|
||||
if (!fRow) break;
|
||||
if (fRow.fatherattributename_id != null && Number(fRow.fatherattributename_id) === Number(targetAttrId)) {
|
||||
const attrName = fRow.fatherAttributeName || 'ancestor';
|
||||
throw new Error(`Attribute "${attrName}" is already used by an ancestor father article`);
|
||||
if (fRow.parentattributename_id != null && Number(fRow.parentattributename_id) === Number(targetAttrId)) {
|
||||
const attrName = fRow.parentAttributeName || 'ancestor';
|
||||
throw new Error(`Attribute "${attrName}" is already used by an ancestor parent article`);
|
||||
}
|
||||
currentFatherId = fRow.fatherarticle_id;
|
||||
currentParentId = fRow.parentarticle_id;
|
||||
}
|
||||
|
||||
// Verify attribute exists and belongs to company
|
||||
@@ -132,16 +132,16 @@ export default function toggleFather(provider) {
|
||||
{ articleId, attributeId: Number(targetAttrId) },
|
||||
);
|
||||
if (existingValRes.rows.length > 0) {
|
||||
throw new Error('A father article cannot have attribute values for its variant attribute');
|
||||
throw new Error('A parent article cannot have attribute values for its variant attribute');
|
||||
}
|
||||
|
||||
await runner.query(queries.TOGGLE_FATHER, {
|
||||
await runner.query(queries.TOGGLE_PARENT, {
|
||||
articleId,
|
||||
isFather: 1,
|
||||
fatherAttributeNameId: Number(targetAttrId),
|
||||
isParent: 1,
|
||||
parentAttributeNameId: Number(targetAttrId),
|
||||
});
|
||||
|
||||
// Add history entry recording father conversion and deleted prices
|
||||
// Add history entry recording parent conversion and deleted prices
|
||||
const historyLines = [
|
||||
`Artikel zu Vaterartikel konvertiert (Varianten-Attribut: "${attributeName}")`,
|
||||
];
|
||||
@@ -170,17 +170,17 @@ export default function toggleFather(provider) {
|
||||
text: historyLines.join('\n'),
|
||||
});
|
||||
} else {
|
||||
// Disabling father: verify zero children
|
||||
// Disabling parent: verify zero children
|
||||
const childCountRes = await runner.query(getChildCountSql, { articleId });
|
||||
const childCount = childCountRes.rows[0]?.count ?? 0;
|
||||
if (childCount > 0) {
|
||||
throw new Error('Cannot disable father mode on an article that still has children');
|
||||
throw new Error('Cannot disable parent mode on an article that still has children');
|
||||
}
|
||||
|
||||
await runner.query(queries.TOGGLE_FATHER, {
|
||||
await runner.query(queries.TOGGLE_PARENT, {
|
||||
articleId,
|
||||
isFather: 0,
|
||||
fatherAttributeNameId: null,
|
||||
isParent: 0,
|
||||
parentAttributeNameId: null,
|
||||
});
|
||||
|
||||
await runner.query(insertHistorySql, {
|
||||
|
||||
@@ -123,39 +123,39 @@ export default function setByArticleSku(provider) {
|
||||
});
|
||||
}
|
||||
|
||||
// Check father / child constraints
|
||||
// Check parent / child constraints
|
||||
const artRes = await runner.query(
|
||||
'SELECT id, is_father, fatherarticle_id, fatherattributename_id FROM article WHERE id = @articleId',
|
||||
'SELECT id, is_parent, parentarticle_id, parentattributename_id FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
const article = artRes.rows[0];
|
||||
|
||||
if (article?.is_father && article.fatherattributename_id) {
|
||||
const hasFatherAttr = validated.some((v) => v.attributeId === article.fatherattributename_id);
|
||||
if (hasFatherAttr) {
|
||||
throw new Error('A father article cannot have attribute values for its variant attribute');
|
||||
if (article?.is_parent && article.parentattributename_id) {
|
||||
const hasParentAttr = validated.some((v) => v.attributeId === article.parentattributename_id);
|
||||
if (hasParentAttr) {
|
||||
throw new Error('A parent article cannot have attribute values for its variant attribute');
|
||||
}
|
||||
}
|
||||
|
||||
if (article?.fatherarticle_id != null) {
|
||||
let currentFatherId = article.fatherarticle_id;
|
||||
if (article?.parentarticle_id != null) {
|
||||
let currentParentId = article.parentarticle_id;
|
||||
const visited = new Set();
|
||||
while (currentFatherId != null && !visited.has(currentFatherId)) {
|
||||
visited.add(currentFatherId);
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT a.fatherarticle_id, a.fatherattributename_id, an.name AS fatherAttributeName FROM article a LEFT JOIN attributename an ON an.id = a.fatherattributename_id WHERE a.id = @currentFatherId',
|
||||
{ currentFatherId },
|
||||
while (currentParentId != null && !visited.has(currentParentId)) {
|
||||
visited.add(currentParentId);
|
||||
const parentRes = await runner.query(
|
||||
'SELECT a.parentarticle_id, a.parentattributename_id, an.name AS parentAttributeName FROM article a LEFT JOIN attributename an ON an.id = a.parentattributename_id WHERE a.id = @currentParentId',
|
||||
{ currentParentId },
|
||||
);
|
||||
const fRow = fatherRes.rows[0];
|
||||
const fRow = parentRes.rows[0];
|
||||
if (!fRow) break;
|
||||
if (fRow.fatherattributename_id != null) {
|
||||
const hasFatherAttr = validated.some((v) => v.attributeId === fRow.fatherattributename_id);
|
||||
if (!hasFatherAttr) {
|
||||
const fatherAttrName = fRow.fatherAttributeName || 'ancestor variant attribute';
|
||||
throw new Error(`Child article must have an attribute value for the father attribute "${fatherAttrName}"`);
|
||||
if (fRow.parentattributename_id != null) {
|
||||
const hasParentAttr = validated.some((v) => v.attributeId === fRow.parentattributename_id);
|
||||
if (!hasParentAttr) {
|
||||
const parentAttrName = fRow.parentAttributeName || 'ancestor variant attribute';
|
||||
throw new Error(`Child article must have an attribute value for the parent attribute "${parentAttrName}"`);
|
||||
}
|
||||
}
|
||||
currentFatherId = fRow.fatherarticle_id;
|
||||
currentParentId = fRow.parentarticle_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function setItemsByArticleSku(provider) {
|
||||
const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||
|
||||
const artRes = await runner.query(
|
||||
'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
||||
'SELECT id, is_parent, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
const article = artRes.rows[0];
|
||||
@@ -72,8 +72,8 @@ export default function setItemsByArticleSku(provider) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
if (article.is_father) {
|
||||
throw new Error('A father article cannot be a Stückliste');
|
||||
if (article.is_parent) {
|
||||
throw new Error('A parent article cannot be a Stückliste');
|
||||
}
|
||||
|
||||
// If items is empty: clear Stückliste
|
||||
|
||||
@@ -91,9 +91,9 @@ export default function create(provider) {
|
||||
runner,
|
||||
);
|
||||
|
||||
const artCheck = await runner.query('SELECT is_father FROM article WHERE id = @articleId', { articleId });
|
||||
if (artCheck.rows[0]?.is_father) {
|
||||
throw new Error('A father article cannot have suppliers');
|
||||
const artCheck = await runner.query('SELECT is_parent FROM article WHERE id = @articleId', { articleId });
|
||||
if (artCheck.rows[0]?.is_parent) {
|
||||
throw new Error('A parent article cannot have suppliers');
|
||||
}
|
||||
|
||||
await assertSupplier(runner, companyId, entityId);
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function getCategoryArticles(provider) {
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} categoryId - The id of the category.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<Array<{id: number, sku: string, name: string, slug: string, net: number|null, taxRate: number|null, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null}>>} The articles in the category.
|
||||
* @returns {Promise<Array<{id: number, sku: string, name: string, slug: string, net: number|null, taxRate: number|null, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null}>>} The articles in the category.
|
||||
*
|
||||
* @example
|
||||
* const articles = await store.category.getCategoryArticles(42, 5);
|
||||
|
||||
@@ -26,8 +26,8 @@ export default function queryCategoryArticles(provider) {
|
||||
...rest,
|
||||
track_stock: Boolean(rest.track_stock ?? 1),
|
||||
// MSSQL returns BIT as boolean, SQLite as 0/1 integer. Normalize so both
|
||||
// dialects expose isFather as a real boolean, matching article/getBySku.
|
||||
isFather: Boolean(rest.isFather),
|
||||
// dialects expose isParent as a real boolean, matching article/getBySku.
|
||||
isParent: Boolean(rest.isParent),
|
||||
brand: brandName
|
||||
? {
|
||||
id: brandId ?? null,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
CHECK_ARTICLE_IS_FATHER as MSSQL_CHECK_ARTICLE_IS_FATHER,
|
||||
CHECK_ARTICLE_IS_PARENT as MSSQL_CHECK_ARTICLE_IS_PARENT,
|
||||
CREATE_LINE as MSSQL_CREATE_LINE,
|
||||
} from '../providers/mssql/queries/manifestDoc.js';
|
||||
import {
|
||||
CHECK_ARTICLE_IS_FATHER as SQLITE_CHECK_ARTICLE_IS_FATHER,
|
||||
CHECK_ARTICLE_IS_PARENT as SQLITE_CHECK_ARTICLE_IS_PARENT,
|
||||
CREATE_LINE as SQLITE_CREATE_LINE,
|
||||
} from '../providers/sqlite3/queries/manifestDoc.js';
|
||||
import makeRunner from './runner.js';
|
||||
@@ -122,20 +122,20 @@ export async function insertLines(
|
||||
const { manifestId, lines, ensureTax } = params;
|
||||
const isSqlite = runner?.dialect === 'sqlite3';
|
||||
const createLineSql = isSqlite ? SQLITE_CREATE_LINE : MSSQL_CREATE_LINE;
|
||||
const checkFatherSql = isSqlite ? SQLITE_CHECK_ARTICLE_IS_FATHER : MSSQL_CHECK_ARTICLE_IS_FATHER;
|
||||
const checkParentSql = isSqlite ? SQLITE_CHECK_ARTICLE_IS_PARENT : MSSQL_CHECK_ARTICLE_IS_PARENT;
|
||||
|
||||
for (const line of lines) {
|
||||
let resolvedName = line.name;
|
||||
let resolvedPu = line.pu;
|
||||
|
||||
if (line.sku) {
|
||||
const fatherCheck = await runner.query(checkFatherSql, {
|
||||
const parentCheck = await runner.query(checkParentSql, {
|
||||
manifestId,
|
||||
sku: line.sku,
|
||||
});
|
||||
const art = fatherCheck.rows[0];
|
||||
if (art?.isFather) {
|
||||
throw new Error(`A father article cannot be booked on documents (${line.sku})`);
|
||||
const art = parentCheck.rows[0];
|
||||
if (art?.isParent) {
|
||||
throw new Error(`A parent article cannot be booked on documents (${line.sku})`);
|
||||
}
|
||||
if (!resolvedName && art?.articleName) {
|
||||
resolvedName = art.articleName;
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function setOptionsByArticleSku(provider) {
|
||||
const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||
|
||||
const artRes = await runner.query(
|
||||
'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
||||
'SELECT id, is_parent, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
const article = artRes.rows[0];
|
||||
@@ -72,8 +72,8 @@ export default function setOptionsByArticleSku(provider) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
if (article.is_father) {
|
||||
throw new Error('A father article cannot have options');
|
||||
if (article.is_parent) {
|
||||
throw new Error('A parent article cannot have options');
|
||||
}
|
||||
|
||||
// If options is empty: clear optionslist
|
||||
|
||||
@@ -116,11 +116,11 @@ export default function adjust(provider) {
|
||||
const resolvedArticles = [];
|
||||
for (const item of normalizedLines) {
|
||||
const resolved = await resolveArticle(googleAuthId, item.sku, activeTx);
|
||||
const artRes = await runner.query('SELECT is_father, articlelist_id, track_stock FROM article WHERE id = @articleId', {
|
||||
const artRes = await runner.query('SELECT is_parent, articlelist_id, track_stock FROM article WHERE id = @articleId', {
|
||||
articleId: resolved.articleId,
|
||||
});
|
||||
if (artRes.rows[0]?.is_father) {
|
||||
throw new Error(`A father article cannot have stock (${item.sku})`);
|
||||
if (artRes.rows[0]?.is_parent) {
|
||||
throw new Error(`A parent article cannot have stock (${item.sku})`);
|
||||
}
|
||||
|
||||
if (artRes.rows[0]?.articlelist_id != null) {
|
||||
|
||||
Reference in New Issue
Block a user