This commit is contained in:
seb
2026-10-23 01:38:08 +02:00
parent 5fc316461d
commit 919713fa53
7 changed files with 250 additions and 19 deletions

View File

@@ -117,7 +117,8 @@ WHERE id = @articleId
export const TOGGLE_PARENT = `
UPDATE article
SET is_parent = @isParent,
parentattributename_id = @parentAttributeNameId
parentattributename_id = @parentAttributeNameId,
updated_at = SYSUTCDATETIME()
WHERE id = @articleId
`;

View File

@@ -288,13 +288,15 @@ SELECT TOP (@limit)
) AS barcode,
a.is_parent AS isParent,
a.parentarticle_id AS parentArticleId,
a.parentsort AS parentsort,
(
SELECT TOP 1 CONCAT(fan.name, '|', av.value, '|', fan.sort, '|', a.parentsort)
FROM article fa
INNER JOIN attributename fan ON fan.id = fa.parentattributename_id
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = fan.id
WHERE a.parentarticle_id IS NOT NULL AND fa.id = a.parentarticle_id
SELECT TOP 1 av.value
FROM articleattributevalue aav
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id
LEFT JOIN article fa ON fa.id = a.parentarticle_id
WHERE aav.article_id = a.id
AND (fa.parentattributename_id IS NULL OR av.attribute_id = fa.parentattributename_id)
ORDER BY CASE WHEN av.attribute_id = fa.parentattributename_id THEN 0 ELSE 1 END, aav.sort ASC, aav.id ASC
) AS variantName,
CASE
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
@@ -502,6 +504,21 @@ LEFT JOIN tax opt_ctax ON opt_ctax.id = co.taxrate
ORDER BY ola.sort ASC, ola.id ASC
`;
export const LIST_POS_PARENT_OPTIONS = `
SELECT
fa.id AS parentArticleId,
COALESCE(fan.name, 'Variante') AS attributeName,
av.value AS variantValue
FROM article fa
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = fa.company_id
INNER JOIN article child ON child.parentarticle_id = fa.id
LEFT JOIN attributename fan ON fan.id = fa.parentattributename_id
LEFT JOIN articleattributevalue aav ON aav.article_id = child.id
LEFT JOIN attributevalue av ON av.id = aav.attributevalue_id AND (fa.parentattributename_id IS NULL OR av.attribute_id = fa.parentattributename_id)
WHERE fa.is_parent = 1
ORDER BY fa.id ASC, child.parentsort ASC, child.id ASC
`;
export const RECORD_OBITUARY = `
INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id)
SELECT pt.id, @entityType, @entityId

View File

@@ -9,7 +9,7 @@ export const {
FIND_BY_TOKEN, COUNT_PRODUCTS, COUNT_CATEGORIES, COUNT_CUSTOMERS,
COUNT_CUSTOMER_GROUPS, COUNT_COMPOSITE_PRODUCTS, MAX_ORDER_ID,
LIST_POS_PRODUCT_CATEGORIES, LIST_POS_CUSTOMER_GROUP_IDS, LIST_POS_PRODUCT_PRICES,
LIST_POS_MANDATED_OPTIONS,
LIST_POS_MANDATED_OPTIONS, LIST_POS_PARENT_OPTIONS,
TOUCH_LASTSEEN, RECORD_OBITUARY, COUNT_DELETED_ENTITIES,
} = queries;
@@ -176,13 +176,15 @@ SELECT
) AS barcode,
a.is_parent AS isParent,
a.parentarticle_id AS parentArticleId,
a.parentsort AS parentsort,
(
SELECT fan.name || '|' || av.value || '|' || fan.sort || '|' || a.parentsort
FROM article fa
INNER JOIN attributename fan ON fan.id = fa.parentattributename_id
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = fan.id
WHERE a.parentarticle_id IS NOT NULL AND fa.id = a.parentarticle_id
SELECT av.value
FROM articleattributevalue aav
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id
LEFT JOIN article fa ON fa.id = a.parentarticle_id
WHERE aav.article_id = a.id
AND (fa.parentattributename_id IS NULL OR av.attribute_id = fa.parentattributename_id)
ORDER BY CASE WHEN av.attribute_id = fa.parentattributename_id THEN 0 ELSE 1 END, aav.sort ASC, aav.id ASC
LIMIT 1
) AS variantName,
CASE

View File

@@ -271,6 +271,9 @@ export default function addChild(provider) {
await initArticleWarehouseSummary(runner, { companyId, articleId: childId });
}
// Touch parent article updated_at so POS and other consumers refresh parent options
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
return load(googleAuthId, childSku, runner);
};

View File

@@ -86,6 +86,9 @@ export default function removeChild(provider) {
childSku,
});
// Touch parent article updated_at so POS and other consumers refresh parent options
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
return {
success: true,
parentSku,
@@ -131,6 +134,9 @@ export default function removeChild(provider) {
parentSku,
});
// Touch parent article updated_at so POS and other consumers refresh parent options
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
return {
success: true,
parentSku,

View File

@@ -4,6 +4,7 @@ import {
LIST_POS_CUSTOMER_GROUP_IDS as MSSQL_CG_IDS,
LIST_POS_PRODUCT_PRICES as MSSQL_PRICES,
LIST_POS_MANDATED_OPTIONS as MSSQL_MANDATED_OPTIONS,
LIST_POS_PARENT_OPTIONS as MSSQL_PARENT_OPTIONS,
} from '../../providers/mssql/queries/posterminal.js';
import {
LIST_POS_PRODUCTS as SQLITE_PRODUCTS,
@@ -11,6 +12,7 @@ import {
LIST_POS_CUSTOMER_GROUP_IDS as SQLITE_CG_IDS,
LIST_POS_PRODUCT_PRICES as SQLITE_PRICES,
LIST_POS_MANDATED_OPTIONS as SQLITE_MANDATED_OPTIONS,
LIST_POS_PARENT_OPTIONS as SQLITE_PARENT_OPTIONS,
} from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
@@ -39,6 +41,7 @@ export default function getProductList(provider) {
const cgIdsSql = isSqlite ? SQLITE_CG_IDS : MSSQL_CG_IDS;
const pricesSql = isSqlite ? SQLITE_PRICES : MSSQL_PRICES;
const mandatedOptionsSql = isSqlite ? SQLITE_MANDATED_OPTIONS : MSSQL_MANDATED_OPTIONS;
const parentOptionsSql = isSqlite ? SQLITE_PARENT_OPTIONS : MSSQL_PARENT_OPTIONS;
/**
* Lists products for a POS terminal modified after the cursor.
@@ -55,7 +58,7 @@ export default function getProductList(provider) {
const runner = makeRunner(provider, tx);
const pid = Number(posterminalId);
const [prodRes, catRes, cgRes, priceRes, optRes] = await Promise.all([
const [prodRes, catRes, cgRes, priceRes, optRes, parentOptRes] = await Promise.all([
runner.query(productsSql, {
posterminalId: pid,
cursor: normalizeCursor(cursor),
@@ -65,6 +68,7 @@ export default function getProductList(provider) {
runner.query(cgIdsSql, { posterminalId: pid }),
runner.query(pricesSql, { posterminalId: pid }),
runner.query(mandatedOptionsSql, { posterminalId: pid }),
runner.query(parentOptionsSql, { posterminalId: pid }),
]);
const categoriesByArticle = new Map();
@@ -101,6 +105,21 @@ export default function getProductList(provider) {
}
}
const parentOptionsMap = new Map();
for (const row of parentOptRes.rows) {
if (!row.variantValue) continue;
if (!parentOptionsMap.has(row.parentArticleId)) {
parentOptionsMap.set(row.parentArticleId, {
name: row.attributeName || 'Variante',
values: [],
});
}
const opt = parentOptionsMap.get(row.parentArticleId);
if (!opt.values.includes(row.variantValue)) {
opt.values.push(row.variantValue);
}
}
const customerGroupIds = cgRes.rows.map((row) => row.id);
return prodRes.rows.map((product) => {
@@ -127,6 +146,24 @@ export default function getProductList(provider) {
const depositName = mandatedOpt ? mandatedOpt.names.join(' + ') : '';
const depositPrice = mandatedOpt ? mandatedOpt.totalPrice.toFixed(2) : '0.0';
const isParent = Boolean(product.isParent);
const parentArticleId = Number(product.parentArticleId || 0);
const isChild = parentArticleId > 0;
const parentOpt = parentOptionsMap.get(product.id);
const options = isParent && parentOpt && parentOpt.values.length > 0
? [
{
name: parentOpt.name,
value: parentOpt.values.join(','),
},
]
: null;
const sortValue = isChild
? String(product.parentsort ?? 0)
: String(product.sort ?? 0);
return {
_id: String(product.id),
imghash: product.imgHash ?? null,
@@ -138,15 +175,16 @@ export default function getProductList(provider) {
price: basePrice,
created_at: formatDateTime(product.createdAt),
lastChanged: String(product.lastChanged),
sort: String(product.sort ?? 0),
sort: sortValue,
categories_id: categoryIds[0] ?? '0',
categories: categoryIds.map((categoryId) => ({ categoryId })),
prices,
is_parent: product.isParent ? '1' : '0',
parent: product.parentArticleId > 0 ? String(product.parentArticleId) : '0',
variants: product.variantName ?? '',
is_parent: isParent ? '1' : '0',
parent: isChild ? String(parentArticleId) : '0',
variants: isChild ? (product.variantName ?? '') : '',
isCompositeProduct: product.isCompositeProduct,
attributes: [],
options,
use_stock: product.trackStock ? '1' : '0',
quantity: String(product.fBestand ?? 0),
deposit: hasDeposit ? '1' : '0',