This commit is contained in:
seb
2026-10-28 21:44:52 +01:00
parent 003b198b86
commit 2e69f49e4d
6 changed files with 494 additions and 233 deletions

View File

@@ -29,12 +29,17 @@
*/
import { Automat } from 'react-automat';
import { article as articleApi } from '../api';
import authAutomat from './authAutomat';
import categoryAutomat, { whenCategoryTreeReady } from './categoryAutomat';
import sseAutomat from './sseAutomat';
import {
categoryViews,
collectLoadedCategoryViewData,
loadAllCategoryViews,
invalidateAllCategoryViews,
updateArticleInCategoryViews,
removeArticleBySku,
} from './categoryViewAutomat';
/**
@@ -98,7 +103,7 @@ function scheduleRebuild() {
function toIndexRecord(article, categoryId) {
const eans = article.eans ?? article.eanCodes ?? article.ean ?? null;
const childCount = Number(article.childCount) || 0;
const price = article.price ?? article.net ?? article.vknetto ?? null;
const price = article.price ?? article.net ?? article.vknetto ?? (Array.isArray(article.prices) ? (article.prices.find((p) => p.minmultiplier === 1)?.net ?? article.prices[0]?.net) : null);
const warehouses = Array.isArray(article.warehouses) ? article.warehouses : [];
const trackStock = article.track_stock ?? article.trackStock;
const stock = trackStock === false
@@ -111,6 +116,9 @@ function toIndexRecord(article, categoryId) {
const available = article.available != null && !Number.isNaN(Number(article.available))
? Number(article.available)
: stock;
const categories = categoryId != null
? String(categoryId)
: (Array.isArray(article.categoryIds) ? article.categoryIds.join(',') : (article.categories ?? null));
return {
...article,
@@ -126,7 +134,7 @@ function toIndexRecord(article, categoryId) {
? article.articlewarehousesummary
: [],
eans: Array.isArray(eans) ? eans.filter(Boolean) : eans ? [eans] : [],
categories: categoryId != null ? String(categoryId) : null,
categories,
isParent: Boolean(article.isParent ?? article.is_parent ?? (childCount > 0)),
childCount,
};
@@ -258,6 +266,32 @@ const actions = {
return items;
},
/**
* Refetches and updates a single article in the index and category views by SKU.
*
* @param {string|number} sku Article SKU.
* @returns {Promise<object|null>} Fresh article or null.
*/
async updateArticleBySku(sku) {
const key = String(sku);
try {
const res = await articleApi.getBySku(key);
const article = res?.article;
if (article) {
updateArticleIndexRecord(article);
return article;
}
return null;
} catch (err) {
if (err?.status === 404 || err?.statusCode === 404) {
removeArticleBySku(key);
const nextItems = articleIndexAutomat.state.items.filter((r) => String(r.sku) !== key);
articleIndexAutomat.setState({ items: nextItems });
}
return null;
}
},
/** Reset to idle state (logout / company switch). */
reset() {
inflightEnsure = null;
@@ -294,6 +328,72 @@ authAutomat.subscribe((auth) => {
if (auth.status === 'anon') actions.reset();
});
// Upstream Trigger: sseAutomat
if (sseAutomat?.actions?.onArticle) {
sseAutomat.actions.onArticle(async (sku) => {
if (articleIndexAutomat.state.status === 'ready') {
await actions.updateArticleBySku(sku);
}
});
}
if (sseAutomat?.actions?.on) {
sseAutomat.actions.on('articles', () => {
invalidateAllCategoryViews();
void actions.reload().catch(() => {});
});
sseAutomat.actions.on('warehouse', () => {
invalidateAllCategoryViews();
void actions.reload().catch(() => {});
});
}
/**
* Updates a single article record in the derived index and loaded category views.
*
* @param {object} article Article detail or row.
* @returns {Array<ArticleIndexRecord>|undefined} Updated items array.
*/
export function updateArticleIndexRecord(article) {
if (!article || article.sku == null) return;
const key = String(article.sku);
updateArticleInCategoryViews(article);
const items = articleIndexAutomat.state.items;
if (!items || !items.length) return;
const existingIndex = items.findIndex((r) => String(r.sku) === key);
const categoryIds = Array.isArray(article.categoryIds)
? article.categoryIds.map(String)
: [];
const primaryCatId = categoryIds[0] ?? null;
const record = toIndexRecord(article, primaryCatId);
if (categoryIds.length > 0) {
record.categories = categoryIds.join(',');
}
let nextItems;
if (existingIndex >= 0) {
const existing = items[existingIndex];
nextItems = [...items];
nextItems[existingIndex] = {
...existing,
...record,
categories: record.categories ?? existing.categories,
smallThumbBinary: article.smallThumbBinary ?? article.photos?.[0]?.smallThumbBinary ?? existing.smallThumbBinary,
brand: article.brand ?? existing.brand,
brandName: article.brand?.name ?? article.brandName ?? existing.brandName,
};
} else {
nextItems = [...items, record];
}
articleIndexAutomat.setState({ items: nextItems });
return nextItems;
}
/**
* Patches a single record's name in the derived index (optimistic update).
*