This commit is contained in:
seb
2026-10-01 17:40:04 +02:00
parent 62c3e69ed4
commit 84e547660e
53 changed files with 4604 additions and 2059 deletions

View File

@@ -15,11 +15,12 @@ import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries
import resolveArticleBySku from './resolveArticleBySku.js';
import getBySku from './getBySku.js';
import makeRunner from '../runner.js';
import slugify from '../slugify.js';
import resolveUniqueSlug from './resolveUniqueSlug.js';
export default function addChild(provider) {
const resolve = resolveArticleBySku(provider);
const load = getBySku(provider);
const resolveSlug = resolveUniqueSlug(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const queries = isSqlite ? sqliteArticle : mssqlArticle;
@@ -117,10 +118,46 @@ export default function addChild(provider) {
throw new Error('Attribute value is required for child article');
}
// Compute child SKU: father.sku + '-' + finalValueId
const childSku = `${father.sku}-${finalValueId}`;
// Check if father 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
AND aav.attributevalue_id = @finalValueId`,
{ fatherId, finalValueId },
);
if (existingVariantRes.rows.length > 0) {
throw new Error('Variant with this attribute value already exists');
}
// Check if child SKU already exists
// Compute child SKU:
// If options.sku is provided, use it.
// Otherwise, derive using next free index: fatherSku-1, fatherSku-2, ...
let childSku = (typeof options.sku === 'string' && options.sku.trim()) || null;
if (!childSku) {
const existingSkusRes = await runner.query(
'SELECT sku FROM article WHERE company_id = @companyId AND sku LIKE @prefix',
{ companyId, prefix: `${father.sku}-%` },
);
const usedIndices = new Set();
const prefix = `${father.sku}-`;
for (const row of existingSkusRes.rows) {
if (row.sku && row.sku.startsWith(prefix)) {
const numPart = row.sku.slice(prefix.length);
if (/^\d+$/.test(numPart)) {
usedIndices.add(parseInt(numPart, 10));
}
}
}
let nextIndex = 1;
while (usedIndices.has(nextIndex)) {
nextIndex++;
}
childSku = `${father.sku}-${nextIndex}`;
}
// Check if child SKU already exists (e.g. orphan holding SKU or custom SKU)
const existingChildRes = await runner.query(
'SELECT id FROM article WHERE sku = @childSku AND company_id = @companyId',
{ childSku, companyId },
@@ -129,9 +166,16 @@ export default function addChild(provider) {
throw new Error(`Child article "${childSku}" already exists`);
}
// Next fathersort
const countRes = await runner.query(getChildCountSql, { articleId: fatherId });
const fathersort = countRes.rows[0]?.count ?? 0;
// Next free fathersort (smallest non-negative integer not used by any child of this father)
const existingSortsRes = await runner.query(
'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC',
{ fatherId },
);
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort)));
let fathersort = 0;
while (usedSorts.has(fathersort)) {
fathersort++;
}
// Create child article
const rawTrack = options.track_stock ?? options.trackStock;
@@ -186,7 +230,11 @@ export default function addChild(provider) {
// Create article text
const childName = options.name?.trim() || `${fatherName} - ${finalValue}`;
const childSlug = slugify(childName);
const childSlug = await resolveSlug(runner, {
companyId,
languageId,
name: childName,
});
await runner.query(createArticleTextSql, {
articleId: childId,
languageId,
@@ -194,13 +242,20 @@ export default function addChild(provider) {
slug: childSlug,
});
// History
// History on child
await runner.query(insertHistorySql, {
articleId: childId,
companyuserId,
text: `Artikel "${childName}" (${childSku}) als Variante von "${father.sku}" angelegt`,
});
// History on father
await runner.query(insertHistorySql, {
articleId: fatherId,
companyuserId,
text: `Variante "${childName}" (${childSku}) hinzugefügt`,
});
return load(googleAuthId, childSku, runner);
};

View File

@@ -11,12 +11,13 @@ import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
import ensureDefaultUnit from '../unit/ensureDefault.js';
import nextSku from '../numbers/nextSku.js';
import makeRunner from '../runner.js';
import slugify from '../slugify.js';
import resolveUniqueSlug from './resolveUniqueSlug.js';
export default function createUnderParent(provider) {
const access = resolveCategoryAccess(provider);
const ensureUnit = ensureDefaultUnit(provider);
const allocateSku = nextSku(provider);
const resolveSlug = resolveUniqueSlug(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const createArticleSql = isSqlite ? SQLITE_CREATE_ARTICLE : MSSQL_CREATE_ARTICLE;
@@ -53,7 +54,7 @@ export default function createUnderParent(provider) {
const unitId = await ensureUnit(runner, languageId, companyId);
const sku = await allocateSku(companyId, runner);
const slug = slugify(name);
const slug = await resolveSlug(runner, { companyId, languageId, name });
let article;
try {

View File

@@ -1,5 +1,7 @@
import * as mssqlQueries from '../../providers/mssql/queries/articleDelete.js';
import * as sqliteQueries from '../../providers/sqlite3/queries/articleDelete.js';
import * as mssqlArticle from '../../providers/mssql/queries/article.js';
import * as sqliteArticle from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from './resolveArticleBySku.js';
import makeRunner from '../runner.js';
@@ -7,6 +9,7 @@ export default function del(provider) {
const resolve = resolveArticleBySku(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const queries = isSqlite ? sqliteQueries : mssqlQueries;
const articleQueries = isSqlite ? sqliteArticle : mssqlArticle;
/**
* Deletes an article by its SKU, refusing when it still has children unless cascade is true.
@@ -40,6 +43,8 @@ export default function del(provider) {
const listIds = await runner.query(queries.GET_LIST_IDS, { articleId: id });
const { articlelistId, optionslistId } = listIds.rows[0] ?? {};
await runner.query(queries.CREATE_TOMBSTONE, { articleId: id });
await runner.query(queries.DELETE_PICS_BY_ARTICLE, { articleId: id });
const deleted = await runner.query(queries.DELETE_BY_ID, { articleId: id });
@@ -64,16 +69,38 @@ export default function del(provider) {
await deleteArticleAndLists(id);
};
const fatherRes = await runner.query(
'SELECT fatherarticle_id FROM article WHERE id = @articleId',
{ articleId },
);
const fatherArticleId = fatherRes.rows[0]?.fatherarticle_id;
const childCount = await runner.query(queries.GET_CHILD_COUNT, { articleId });
if (childCount.rows[0].count > 0) {
if (!cascade) {
throw new Error('Article has children');
}
await deleteSubtree(articleId);
return;
} else {
await deleteArticleAndLists(articleId);
}
await deleteArticleAndLists(articleId);
if (fatherArticleId != null) {
const remainingChildrenRes = await runner.query(
'SELECT id, fathersort FROM article WHERE fatherarticle_id = @fatherArticleId ORDER BY fathersort ASC, id ASC',
{ fatherArticleId },
);
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, {
articleId: childRow.id,
fatherArticleId,
fathersort: i,
});
}
}
}
};
if (tx) {

View File

@@ -1,28 +1,50 @@
import { LIST_BY_ARTICLE_ID as MSSQL_QUERY } from '../../providers/mssql/queries/articlehistory.js';
import { LIST_BY_ARTICLE_ID as SQLITE_QUERY } from '../../providers/sqlite3/queries/articlehistory.js';
import { LIST_BY_ARTICLE_ID as MSSQL_QUERY, LIST_BY_ARTICLE_ID_LIMITED as MSSQL_QUERY_LIMITED } from '../../providers/mssql/queries/articlehistory.js';
import { LIST_BY_ARTICLE_ID as SQLITE_QUERY, LIST_BY_ARTICLE_ID_LIMITED as SQLITE_QUERY_LIMITED } from '../../providers/sqlite3/queries/articlehistory.js';
import resolveArticleBySku from './resolveArticleBySku.js';
import makeRunner from '../runner.js';
export default function getHistoryBySku(provider) {
const resolve = resolveArticleBySku(provider);
const sql = provider?.dialect === 'sqlite3' ? SQLITE_QUERY : MSSQL_QUERY;
const isSqlite = provider?.dialect === 'sqlite3';
const sql = isSqlite ? SQLITE_QUERY : MSSQL_QUERY;
const sqlLimited = isSqlite ? SQLITE_QUERY_LIMITED : MSSQL_QUERY_LIMITED;
/**
* Returns the change history of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {object} [opts] - Optional options.
* @param {number} [opts.limit] - Maximum number of entries to return (newest first).
* @param {object} [tx] - Optional transaction.
* @returns {Promise<Array<{id: number, companyuserId: number|null, date: string, text: string}>>} The article history entries.
*
* @example
* const history = await store.article.getHistoryBySku(42, 'SKU-1');
* // => [{ id, companyuserId, date, text }]
*
* const recent = await store.article.getHistoryBySku(42, 'SKU-1', { limit: 5 });
* // => up to 5 most recent entries
*/
return async (googleAuthId, sku, tx) => {
const { articleId } = await resolve(googleAuthId, sku, tx);
const runner = makeRunner(provider, tx);
const result = await runner.query(sql, { articleId });
return async (googleAuthId, sku, opts, tx) => {
// Support legacy call signature: getHistoryBySku(googleAuthId, sku, tx)
let limit = null;
let resolvedTx = tx;
if (opts != null && typeof opts === 'object' && !opts._isTx) {
limit = opts.limit != null ? Number(opts.limit) : null;
} else if (opts != null) {
resolvedTx = opts;
}
const runner = makeRunner(provider, resolvedTx);
const { articleId } = await resolve(googleAuthId, sku, runner);
let result;
if (limit != null && Number.isInteger(limit) && limit > 0) {
result = await runner.query(sqlLimited, { articleId, limit });
} else {
result = await runner.query(sql, { articleId });
}
return result.rows.map((row) => ({
id: row.id,
@@ -32,4 +54,3 @@ export default function getHistoryBySku(provider) {
}));
};
}

View File

@@ -30,7 +30,7 @@ import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyA
import ensureByPath from '../category/ensureByPath.js';
import ensureDefaultUnit from '../unit/ensureDefault.js';
import makeRunner from '../runner.js';
import slugify from '../slugify.js';
import resolveUniqueSlug from './resolveUniqueSlug.js';
// Normalizes a numeric value that may arrive as a string using German decimal
// notation (e.g. "11,00"), a JS number, or null/undefined. Returns null when empty.
@@ -49,6 +49,7 @@ export default function importRows(provider) {
const companyAccess = resolveSelectedCompanyAccess(provider);
const ensureUnit = ensureDefaultUnit(provider);
const ensureCategoryByPath = ensureByPath(provider);
const resolveSlug = resolveUniqueSlug(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const articleQueries = isSqlite ? sqliteArticle : mssqlArticle;
@@ -167,7 +168,7 @@ export default function importRows(provider) {
try {
await runInTx(async (runner) => {
const unitId = await ensureUnit(runner, companyId, languageId);
const slug = slugify(name);
const slug = await resolveSlug(runner, { companyId, languageId, name });
const rawTrack = row.track_stock ?? row.trackStock;
const trackStock = rawTrack == null ? 1 : (rawTrack ? 1 : 0);
@@ -286,22 +287,52 @@ export default function importRows(provider) {
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(articleQueries.GET_ID_BY_SKU, { sku: fatherSku, companyId });
const fatherId = father.rows[0]?.articleId;
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 fatherRow = father.rows[0];
const fatherId = fatherRow?.id;
if (fatherId && fatherId !== childId) {
const childCount = await runner.query(getChildCountSql, {
articleId: fatherId,
});
const count = childCount.rows[0].count;
const wasFather = Boolean(fatherRow.is_father);
const existingSortsRes = await runner.query(
'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC',
{ fatherId },
);
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort)));
let fathersort = 0;
while (usedSorts.has(fathersort)) {
fathersort++;
}
await runner.query(articleQueries.UPDATE_FATHER_BY_SKU, {
sku,
companyId,
fatherArticleId: fatherId,
fathersort: count,
fathersort,
});
await runner.query(articleQueries.SET_IS_FATHER, {
articleId: fatherId,
});
if (!wasFather) {
await runner.query(insertHistorySql, {
articleId: fatherId,
companyuserId,
text: 'Artikel zu Vaterartikel konvertiert',
});
}
const childName = String(row?.name ?? sku).trim();
await runner.query(insertHistorySql, {
articleId: fatherId,
companyuserId,
text: `Variante "${childName}" (${sku}) hinzugefügt`,
});
await runner.query(insertHistorySql, {
articleId: childId,
companyuserId,
text: `Artikel "${childName}" (${sku}) als Variante von "${fatherSku}" zugewiesen`,
});
}
}
});

View File

@@ -28,7 +28,7 @@ export default function removeChild(provider) {
*/
return async (googleAuthId, fatherSku, childSku, options = {}, tx) => {
const run = async (runner) => {
const { articleId: fatherId, companyuserId } = await resolve(
const { articleId: fatherId, languageId, companyuserId } = await resolve(
googleAuthId,
fatherSku,
runner,
@@ -45,9 +45,45 @@ export default function removeChild(provider) {
throw new Error('Child article does not belong to this father');
}
// Fetch child name before any deletion (needed for history regardless of path)
const childNameRes = await runner.query(
`SELECT at.name FROM articletext at
INNER JOIN article a ON a.id = at.article_id
WHERE a.id = @childId AND at.language_id = @languageId`,
{ childId, languageId },
);
const childName = childNameRes.rows[0]?.name || childSku;
const recompactFatherSort = async () => {
const remainingChildrenRes = await runner.query(
'SELECT id, fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC, id ASC',
{ fatherId },
);
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, {
articleId: childRow.id,
fatherArticleId: fatherId,
fathersort: i,
});
}
}
};
if (options.deleteArticle) {
const cascade = options.cascade !== undefined ? Boolean(options.cascade) : true;
await del(googleAuthId, childSku, { cascade }, runner);
await recompactFatherSort();
// History on father (child article is gone so we can only log on father)
await runner.query(insertHistorySql, {
articleId: fatherId,
companyuserId,
text: `Variante "${childName}" (${childSku}) gelöscht`,
});
return {
success: true,
fatherSku,
@@ -81,13 +117,22 @@ export default function removeChild(provider) {
);
}
// Add history
await recompactFatherSort();
// Add history on child
await runner.query(insertHistorySql, {
articleId: childId,
companyuserId,
text: `Artikel "${childSku}" von Vaterartikel "${fatherSku}" getrennt`,
});
// Add history on father
await runner.query(insertHistorySql, {
articleId: fatherId,
companyuserId,
text: `Variante "${childName}" (${childSku}) getrennt`,
});
return {
success: true,
fatherSku,

View File

@@ -0,0 +1,51 @@
import { LIST_SLUGS_BY_COMPANY_AND_LANGUAGE as MSSQL_LIST_SLUGS } from '../../providers/mssql/queries/articletext.js';
import { LIST_SLUGS_BY_COMPANY_AND_LANGUAGE as SQLITE_LIST_SLUGS } from '../../providers/sqlite3/queries/articletext.js';
import slugify from '../slugify.js';
export default function resolveUniqueSlug(provider) {
const isSqlite = provider?.dialect === 'sqlite3';
const listSlugsSql = isSqlite ? SQLITE_LIST_SLUGS : MSSQL_LIST_SLUGS;
/**
* Resolves a company- and language-unique slug for an article.
* Disambiguates duplicate slugs with a numeric suffix (-2, -3, ...).
*
* @param {object} runner - DB runner or tx.
* @param {object} params
* @param {number} params.companyId - The company ID.
* @param {number} params.languageId - The language ID.
* @param {string} params.name - The article name.
* @param {number} [params.excludeArticleId] - Optional article ID to exclude (e.g. for updates).
* @returns {Promise<string>} The unique slug.
*/
return async (runner, { companyId, languageId, name, excludeArticleId }) => {
const baseSlug = slugify(name) || 'article';
const likePattern = `${baseSlug}-%`;
const res = await runner.query(listSlugsSql, {
companyId,
languageId,
baseSlug,
likePattern,
});
const existingSlugs = new Set();
for (const row of res.rows) {
const artId = row.articleId ?? row.article_id;
if (excludeArticleId != null && artId === excludeArticleId) {
continue;
}
existingSlugs.add(row.slug);
}
if (!existingSlugs.has(baseSlug)) {
return baseSlug;
}
let suffix = 2;
while (existingSlugs.has(`${baseSlug}-${suffix}`)) {
suffix++;
}
return `${baseSlug}-${suffix}`;
};
}

View File

@@ -47,25 +47,29 @@ export default function setBrandIdBySku(provider) {
throw new Error('Invalid brandId');
}
// Read old brand for history
// Read old brand name (null = no brand assigned)
const oldBrandRes = await runner.query(
`SELECT b.name FROM article a LEFT JOIN brand b ON b.id = a.brand_id WHERE a.id = @articleId`,
{ articleId },
);
const oldBrandName = oldBrandRes.rows[0]?.name ?? '';
const oldBrandName = oldBrandRes.rows[0]?.name ?? null;
let resolvedBrandId = null;
let newBrandName = '';
let newBrandName = null;
if (brandId !== null) {
const brand = await runner.query(getBrandSql, {
brandId,
companyId,
});
if (!brand.rows[0]) {
// Verify brand belongs to this company
const brandCheck = await runner.query(getBrandSql, { brandId, companyId });
if (!brandCheck.rows[0]) {
throw new Error('Brand not found');
}
resolvedBrandId = brandId;
newBrandName = brand.rows[0].name ?? '';
// GET_BRAND_BY_COMPANY_AND_ID only selects `id`, so fetch name separately
const brandNameRes = await runner.query(
'SELECT name FROM brand WHERE id = @brandId',
{ brandId },
);
newBrandName = brandNameRes.rows[0]?.name ?? null;
}
const result = await runner.query(updateBrandSql, {
@@ -77,12 +81,12 @@ export default function setBrandIdBySku(provider) {
throw new Error('Article not found');
}
// Write history if brand changed
// null = no brand; treat as distinct sentinel so first assignment is logged
if (oldBrandName !== newBrandName) {
await runner.query(insertHistorySql, {
articleId,
companyuserId,
text: `Marke geändert von "${oldBrandName}" auf "${newBrandName}"`,
text: `Marke ge\u00e4ndert von "${oldBrandName ?? ''}" auf "${newBrandName ?? ''}"`,
});
}
@@ -94,4 +98,3 @@ export default function setBrandIdBySku(provider) {
return article;
};
}

View File

@@ -2,6 +2,8 @@ import * as mssqlArticle from '../../providers/mssql/queries/article.js';
import * as sqliteArticle from '../../providers/sqlite3/queries/article.js';
import { GET_CHILD_COUNT as MSSQL_GET_CHILD_COUNT } from '../../providers/mssql/queries/articleDelete.js';
import { GET_CHILD_COUNT as SQLITE_GET_CHILD_COUNT } from '../../providers/sqlite3/queries/articleDelete.js';
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
import makeRunner from '../runner.js';
@@ -13,6 +15,7 @@ export default function setFatherBySku(provider) {
const queries = isSqlite ? sqliteArticle : mssqlArticle;
const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT;
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
/**
* Links an article to a father article (V/K compound) by their SKUs.
@@ -30,14 +33,47 @@ export default function setFatherBySku(provider) {
*/
return async (googleAuthId, sku, fatherSku, tx) => {
const runSet = async (runner) => {
const { articleId, languageId, companyId } = await resolve(googleAuthId, sku, runner);
const { articleId, languageId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
const childTextRes = await runner.query(
'SELECT name FROM articletext WHERE article_id = @articleId AND language_id = @languageId',
{ articleId, languageId },
);
const childName = childTextRes.rows[0]?.name || sku;
if (fatherSku == null || fatherSku === '') {
const prevFatherRes = await runner.query(
'SELECT fatherarticle_id FROM article WHERE id = @articleId',
{ articleId },
);
const prevFatherId = prevFatherRes.rows[0]?.fatherarticle_id;
await runner.query(queries.UPDATE_FATHER_BY_SKU, {
sku,
companyId,
fatherArticleId: null,
});
if (prevFatherId) {
const prevFatherTextRes = await runner.query(
'SELECT sku FROM article WHERE id = @prevFatherId',
{ prevFatherId },
);
const prevFatherSku = prevFatherTextRes.rows[0]?.sku || String(prevFatherId);
await runner.query(insertHistorySql, {
articleId,
companyuserId,
text: `Artikel "${sku}" von Vaterartikel "${prevFatherSku}" getrennt`,
});
await runner.query(insertHistorySql, {
articleId: prevFatherId,
companyuserId,
text: `Variante "${childName}" (${sku}) getrennt`,
});
}
const unlinked = await load(articleId, languageId, runner);
if (!unlinked) {
throw new Error('Article not found');
@@ -45,8 +81,12 @@ export default function setFatherBySku(provider) {
return unlinked;
}
const father = await runner.query(queries.GET_ID_BY_SKU, { sku: fatherSku, companyId });
const fatherId = father.rows[0]?.articleId;
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 fatherRow = father.rows[0];
const fatherId = fatherRow?.id;
if (!fatherId) {
throw new Error('Father not found');
}
@@ -54,22 +94,49 @@ export default function setFatherBySku(provider) {
throw new Error('Invalid father');
}
const childCount = await runner.query(getChildCountSql, {
articleId: fatherId,
});
const count = childCount.rows[0].count;
const wasFather = Boolean(fatherRow.is_father);
const existingSortsRes = await runner.query(
'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC',
{ fatherId },
);
const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort)));
let fathersort = 0;
while (usedSorts.has(fathersort)) {
fathersort++;
}
await runner.query(queries.UPDATE_FATHER_BY_SKU, {
sku,
companyId,
fatherArticleId: fatherId,
fathersort: count,
fathersort,
});
await runner.query(queries.SET_IS_FATHER, {
articleId: fatherId,
});
if (!wasFather) {
await runner.query(insertHistorySql, {
articleId: fatherId,
companyuserId,
text: 'Artikel zu Vaterartikel konvertiert',
});
}
await runner.query(insertHistorySql, {
articleId: fatherId,
companyuserId,
text: `Variante "${childName}" (${sku}) hinzugefügt`,
});
await runner.query(insertHistorySql, {
articleId,
companyuserId,
text: `Artikel "${sku}" als Variante von "${fatherSku}" zugewiesen`,
});
const article = await load(articleId, languageId, runner);
if (!article) {
throw new Error('Article not found');

View File

@@ -11,7 +11,7 @@ import { GET_FULL_BY_SI as SQLITE_GET_UNIT } from '../../providers/sqlite3/queri
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
import makeRunner from '../runner.js';
import slugify from '../slugify.js';
import resolveUniqueSlug from './resolveUniqueSlug.js';
function formatUnitLabel(amount, symbol) {
const sym = symbol ?? '';
@@ -24,6 +24,7 @@ function formatUnitLabel(amount, symbol) {
export default function updateBySku(provider) {
const resolve = resolveArticleBySku(provider);
const load = queryBySku(provider);
const resolveSlug = resolveUniqueSlug(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const articleQueries = isSqlite ? sqliteArticle : mssqlArticle;
@@ -144,7 +145,12 @@ export default function updateBySku(provider) {
}
if (hasName) {
const slug = slugify(patch.name);
const slug = await resolveSlug(runner, {
companyId,
languageId,
name: patch.name,
excludeArticleId: articleId,
});
const update = await runner.query(updateTextSql, {
articleId,
languageId,