u
This commit is contained in:
197
src/store/article/addChild.js
Normal file
197
src/store/article/addChild.js
Normal file
@@ -0,0 +1,197 @@
|
||||
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 * as mssqlAttrVal from '../../providers/mssql/queries/attributevalue.js';
|
||||
import * as sqliteAttrVal from '../../providers/sqlite3/queries/attributevalue.js';
|
||||
import * as mssqlArticleAttrVal from '../../providers/mssql/queries/articleattributevalue.js';
|
||||
import * as sqliteArticleAttrVal from '../../providers/sqlite3/queries/articleattributevalue.js';
|
||||
import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/categoryarticle.js';
|
||||
import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js';
|
||||
import { CREATE as MSSQL_CREATE_ARTICLETEXT } from '../../providers/mssql/queries/articletext.js';
|
||||
import { CREATE as SQLITE_CREATE_ARTICLETEXT } from '../../providers/sqlite3/queries/articletext.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 resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import getBySku from './getBySku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import slugify from '../slugify.js';
|
||||
|
||||
export default function addChild(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const load = getBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT;
|
||||
const attrValQueries = isSqlite ? sqliteAttrVal : mssqlAttrVal;
|
||||
const articleAttrValQueries = isSqlite ? sqliteArticleAttrVal : mssqlArticleAttrVal;
|
||||
const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE;
|
||||
const createArticleTextSql = isSqlite ? SQLITE_CREATE_ARTICLETEXT : MSSQL_CREATE_ARTICLETEXT;
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Adds a child article to a father article.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father article SKU.
|
||||
* @param {object} options
|
||||
* @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.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) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId: fatherId, companyId, languageId, companyuserId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
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 },
|
||||
);
|
||||
const father = fatherRes.rows[0];
|
||||
if (!father) {
|
||||
throw new Error('Father article not found');
|
||||
}
|
||||
if (!father.is_father || !father.fatherattributename_id) {
|
||||
throw new Error('Article is not a father 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 },
|
||||
);
|
||||
const fatherName = fatherTextRes.rows[0]?.name || father.sku;
|
||||
|
||||
// Determine attribute value
|
||||
let finalValueId = null;
|
||||
let finalValue = null;
|
||||
|
||||
if (options.attributeValueId != null) {
|
||||
const valRes = await runner.query(attrValQueries.GET_BY_ID_AND_COMPANY, {
|
||||
id: Number(options.attributeValueId),
|
||||
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');
|
||||
}
|
||||
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,
|
||||
value: valStr,
|
||||
});
|
||||
if (existingValRes.rows[0]) {
|
||||
finalValueId = existingValRes.rows[0].id;
|
||||
finalValue = existingValRes.rows[0].value;
|
||||
} else {
|
||||
const sortRes = await runner.query(attrValQueries.GET_NEXT_SORT, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
});
|
||||
const sort = sortRes.rows[0]?.sort ?? 0;
|
||||
const createdValRes = await runner.query(attrValQueries.CREATE, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
value: valStr,
|
||||
sort,
|
||||
});
|
||||
finalValueId = createdValRes.rows[0]?.id ?? createdValRes.insertId;
|
||||
finalValue = valStr;
|
||||
}
|
||||
} else {
|
||||
throw new Error('Attribute value is required for child article');
|
||||
}
|
||||
|
||||
// Compute child SKU: father.sku + '-' + finalValueId
|
||||
const childSku = `${father.sku}-${finalValueId}`;
|
||||
|
||||
// Check if child SKU already exists
|
||||
const existingChildRes = await runner.query(
|
||||
'SELECT id FROM article WHERE sku = @childSku AND company_id = @companyId',
|
||||
{ childSku, companyId },
|
||||
);
|
||||
if (existingChildRes.rows[0]) {
|
||||
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;
|
||||
|
||||
// Create child article
|
||||
const rawTrack = options.track_stock ?? options.trackStock;
|
||||
const finalTrackStock = rawTrack == null ? father.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,
|
||||
trackStock: finalTrackStock,
|
||||
brandId: father.brand_id,
|
||||
fatherArticleId: fatherId,
|
||||
fathersort,
|
||||
});
|
||||
const childId = childRes.rows[0]?.id ?? childRes.insertId;
|
||||
|
||||
// Link articleattributevalue
|
||||
await runner.query(articleAttrValQueries.INSERT_ROW, {
|
||||
articleId: childId,
|
||||
attributeValueId: finalValueId,
|
||||
sort: 0,
|
||||
});
|
||||
|
||||
// Copy father's categories
|
||||
const catRes = await runner.query(
|
||||
'SELECT category_id FROM categoryarticle WHERE article_id = @fatherId',
|
||||
{ fatherId },
|
||||
);
|
||||
for (const catRow of catRes.rows) {
|
||||
await runner.query(addCategoryArticleSql, {
|
||||
categoryId: catRow.category_id,
|
||||
articleId: childId,
|
||||
});
|
||||
}
|
||||
|
||||
// Create article text
|
||||
const childName = options.name?.trim() || `${fatherName} - ${finalValue}`;
|
||||
const childSlug = slugify(childName);
|
||||
await runner.query(createArticleTextSql, {
|
||||
articleId: childId,
|
||||
languageId,
|
||||
name: childName,
|
||||
slug: childSlug,
|
||||
});
|
||||
|
||||
// History
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: childId,
|
||||
companyuserId,
|
||||
text: `Artikel "${childName}" (${childSku}) als Variante von "${father.sku}" angelegt`,
|
||||
});
|
||||
|
||||
return load(googleAuthId, childSku, runner);
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
@@ -99,8 +99,20 @@ export default function getBySku(provider) {
|
||||
optionsCount: a.optionsCount,
|
||||
articleListCount: a.articleListCount,
|
||||
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,
|
||||
children: parseJsonArray(a.childrenJson).map((c) => ({
|
||||
id: c.id,
|
||||
sku: c.sku,
|
||||
fathersort: c.fathersort,
|
||||
name: c.name ?? null,
|
||||
attributeValueId: c.attributeValueId ?? null,
|
||||
attributeValue: c.attributeValue ?? null,
|
||||
})),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
64
src/store/article/listChildren.js
Normal file
64
src/store/article/listChildren.js
Normal file
@@ -0,0 +1,64 @@
|
||||
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';
|
||||
|
||||
export default function listChildren(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
|
||||
/**
|
||||
* Lists all children of a father article.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father 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}>>}
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId: fatherId, languageId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
runner,
|
||||
);
|
||||
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT is_father, fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
);
|
||||
const father = fatherRes.rows[0];
|
||||
if (!father || !father.is_father) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await runner.query(queries.LIST_CHILDREN_BY_FATHER_ID, {
|
||||
fatherArticleId: fatherId,
|
||||
languageId,
|
||||
attributeId: father.fatherattributename_id,
|
||||
});
|
||||
|
||||
return result.rows.map((row) => ({
|
||||
id: row.id,
|
||||
sku: row.sku,
|
||||
fathersort: Number(row.fathersort ?? 0),
|
||||
trackStock: Boolean(row.trackStock ?? 1),
|
||||
unitamount: Number(row.unitamount ?? 1),
|
||||
weight: Number(row.weight ?? 0),
|
||||
name: row.name ?? null,
|
||||
slug: row.slug ?? null,
|
||||
attributeValueId: row.attributeValueId ?? null,
|
||||
attributeValue: row.attributeValue ?? null,
|
||||
}));
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
105
src/store/article/removeChild.js
Normal file
105
src/store/article/removeChild.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import * as mssqlArticle from '../../providers/mssql/queries/article.js';
|
||||
import * as sqliteArticle from '../../providers/sqlite3/queries/article.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 resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import articleDelete from './delete.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function removeChild(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const del = articleDelete(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Removes a child article from a father 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} childSku - The child article SKU.
|
||||
* @param {object} [options]
|
||||
* @param {boolean} [options.deleteArticle=false] - Whether to delete the child article entity entirely.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>}
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, childSku, options = {}, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId: fatherId, companyuserId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
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',
|
||||
{ childId },
|
||||
);
|
||||
const child = childRes.rows[0];
|
||||
if (!child || child.fatherarticle_id !== fatherId) {
|
||||
throw new Error('Child article does not belong to this father');
|
||||
}
|
||||
|
||||
if (options.deleteArticle) {
|
||||
await del(googleAuthId, childSku, runner);
|
||||
return {
|
||||
success: true,
|
||||
fatherSku,
|
||||
childSku,
|
||||
deleted: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch father's attribute name id
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
);
|
||||
const fatherAttributeNameId = fatherRes.rows[0]?.fatherattributename_id;
|
||||
|
||||
// Unlink child from father
|
||||
await runner.query(queries.UNLINK_CHILD, {
|
||||
articleId: childId,
|
||||
fatherArticleId: fatherId,
|
||||
});
|
||||
|
||||
// Remove the child's attribute value link for the father's attribute
|
||||
if (fatherAttributeNameId) {
|
||||
await runner.query(
|
||||
`DELETE FROM articleattributevalue
|
||||
WHERE article_id = @childId
|
||||
AND attributevalue_id IN (
|
||||
SELECT id FROM attributevalue WHERE attribute_id = @fatherAttributeNameId
|
||||
)`,
|
||||
{ childId, fatherAttributeNameId },
|
||||
);
|
||||
}
|
||||
|
||||
// Add history
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: childId,
|
||||
companyuserId,
|
||||
text: `Artikel "${childSku}" von Vaterartikel "${fatherSku}" getrennt`,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
fatherSku,
|
||||
childSku,
|
||||
deleted: false,
|
||||
};
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
93
src/store/article/toggleFather.js
Normal file
93
src/store/article/toggleFather.js
Normal file
@@ -0,0 +1,93 @@
|
||||
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 * as mssqlAttr from '../../providers/mssql/queries/attributename.js';
|
||||
import * as sqliteAttr from '../../providers/sqlite3/queries/attributename.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import getBySku from './getBySku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function toggleFather(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const load = getBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT;
|
||||
const attrQueries = isSqlite ? sqliteAttr : mssqlAttr;
|
||||
|
||||
/**
|
||||
* Toggles an article into or out of father 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 {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The updated article detail.
|
||||
*/
|
||||
return async (googleAuthId, sku, { isFather, attributeNameId, fatherAttributeNameId } = {}, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId, companyId } = await resolve(googleAuthId, sku, runner);
|
||||
|
||||
const targetIsFather = Boolean(isFather);
|
||||
const targetAttrId = attributeNameId ?? fatherAttributeNameId;
|
||||
|
||||
if (targetIsFather) {
|
||||
if (!targetAttrId || Number(targetAttrId) <= 0) {
|
||||
throw new Error('attributeNameId is required to become a father article');
|
||||
}
|
||||
|
||||
// Verify article is not a child of another article
|
||||
const currentRes = await runner.query(
|
||||
'SELECT fatherarticle_id FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
if (currentRes.rows[0]?.fatherarticle_id != null) {
|
||||
throw new Error('A child article cannot become a father');
|
||||
}
|
||||
|
||||
// Verify attribute exists and belongs to company
|
||||
const attrRes = await runner.query(attrQueries.GET_BY_COMPANY_AND_ID, {
|
||||
id: Number(targetAttrId),
|
||||
companyId,
|
||||
});
|
||||
if (!attrRes.rows[0]) {
|
||||
throw new Error('Attribute name not found');
|
||||
}
|
||||
|
||||
await runner.query(queries.TOGGLE_FATHER, {
|
||||
articleId,
|
||||
isFather: 1,
|
||||
fatherAttributeNameId: Number(targetAttrId),
|
||||
});
|
||||
} else {
|
||||
// Disabling father: 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');
|
||||
}
|
||||
|
||||
await runner.query(queries.TOGGLE_FATHER, {
|
||||
articleId,
|
||||
isFather: 0,
|
||||
fatherAttributeNameId: null,
|
||||
});
|
||||
}
|
||||
|
||||
return load(googleAuthId, sku, runner);
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user