This commit is contained in:
seb
2026-10-18 03:14:15 +02:00
parent 0a59a0987a
commit ab9d5fe413
13 changed files with 1018 additions and 271 deletions

View File

@@ -87,6 +87,7 @@ digraph automats {
fontcolor="#e2e8f0"; margin=14;
articleHistoryGridAutomat [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#3f4c5f"><TR><TD COLSPAN="3" BGCOLOR="#33415c" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#33415c" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="1" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#e2e8f0" POINT-SIZE="12"><B>articleHistoryGridAutomat</B></FONT><BR/><FONT COLOR="#8b95a7" POINT-SIZE="9">createGridAutomat (Article)</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#2a3242" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#2b1a42" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#c084fc" POINT-SIZE="8"><B>STATE</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>rows: [] / totalRows</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Block size 50 virtual rows</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#272e3d" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#143826" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#4ade80" POINT-SIZE="8"><B>ACTION</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>fetchRows(viewport)</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Infinite scroll query</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#2a3242" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#143826" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#4ade80" POINT-SIZE="8"><B>ACTION</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>reload()</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Reload on company switch</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#272e3d" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#3b2c12" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#fbbf24" POINT-SIZE="8"><B>API</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>GET /api/article/history</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Infinite audit window</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
globalHistoryGridAutomat [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#3f4c5f"><TR><TD COLSPAN="3" BGCOLOR="#33415c" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#33415c" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="1" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#e2e8f0" POINT-SIZE="12"><B>globalHistoryGridAutomat</B></FONT><BR/><FONT COLOR="#8b95a7" POINT-SIZE="9">createGridAutomat (Global)</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#2a3242" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#2b1a42" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#c084fc" POINT-SIZE="8"><B>STATE</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>rows: [] / totalRows</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Block size 50 virtual rows</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#272e3d" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#143826" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#4ade80" POINT-SIZE="8"><B>ACTION</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>fetchRows(viewport)</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Infinite scroll query</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#2a3242" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#143826" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#4ade80" POINT-SIZE="8"><B>ACTION</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>reload()</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Reload on company switch</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#272e3d" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#3b2c12" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#fbbf24" POINT-SIZE="8"><B>API</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>GET /api/globalhistory</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Company global audit</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
ledgerHistoryGridAutomat [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#3f4c5f"><TR><TD COLSPAN="3" BGCOLOR="#33415c" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#33415c" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="1" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#e2e8f0" POINT-SIZE="12"><B>ledgerHistoryGridAutomat</B></FONT><BR/><FONT COLOR="#8b95a7" POINT-SIZE="9">createGridAutomat (Ledger)</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#2a3242" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#2b1a42" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#c084fc" POINT-SIZE="8"><B>STATE</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>rows: [] / totalRows</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Block size 50 virtual rows</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#272e3d" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#143826" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#4ade80" POINT-SIZE="8"><B>ACTION</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>fetchRows(viewport)</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Infinite scroll query</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#2a3242" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#143826" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#4ade80" POINT-SIZE="8"><B>ACTION</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>reload()</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Reload on company switch</FONT></TD></TR></TABLE></TD></TR><TR><TD COLSPAN="3" BGCOLOR="#272e3d" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD BGCOLOR="#3b2c12" ALIGN="CENTER" CELLPADDING="3" WIDTH="48"><FONT COLOR="#fbbf24" POINT-SIZE="8"><B>API</B></FONT></TD><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#d8dee9" POINT-SIZE="10"><B>GET /api/ledger/history</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#8b95a7" POINT-SIZE="8">Append-only ledger audit</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
}
subgraph cluster_system_ui {
label="UI, Settings &amp; System"; labeljust="l"; fontsize=15;
@@ -120,43 +121,44 @@ digraph automats {
authAutomat:p_auth_status:e -> vfsAutomat:in0:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="authAutomat → vfsAutomat (load company vfs)", label=" load company vfs ", constraint=false, weight=1];
authAutomat:p_auth_status:e -> articleHistoryGridAutomat:in0:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="authAutomat → articleHistoryGridAutomat (reload history on authed)", label=" reload history on authed ", constraint=false, weight=1];
authAutomat:p_auth_status:e -> globalHistoryGridAutomat:in0:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="authAutomat → globalHistoryGridAutomat (reload audit on authed)", label=" reload audit on authed ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> categoryAutomat:in6:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="sseAutomat → categoryAutomat ('category' sse trigger)", label=" 'category' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> categoryViewAutomat:in1:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="sseAutomat → categoryViewAutomat (invalidate views on sse)", label=" invalidate views on sse ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> articleIndexAutomat:in2:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="sseAutomat → articleIndexAutomat ('articles' sse trigger)", label=" 'articles' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> artikelAutomat:in4:n [color="#c79bff", fontcolor="#c79bff", tooltip="sseAutomat → artikelAutomat ('article-&lt;sku&gt;' sse reload)", label=" 'article-&lt;sku&gt;' sse reload ", weight=3];
sseAutomat:p_sse_events:w -> articleDetailAutomat:in0:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="sseAutomat → articleDetailAutomat (invalidateArticle(sku))", label=" invalidateArticle(sku) ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> warehouseAutomat:in1:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="sseAutomat → warehouseAutomat ('warehouse' sse trigger)", label=" 'warehouse' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> supplierAutomat:in2:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="sseAutomat → supplierAutomat ('supplier' sse trigger)", label=" 'supplier' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> brandAutomat:in1:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="sseAutomat → brandAutomat ('brand' sse trigger)", label=" 'brand' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> attributeAutomat:in1:n [color="#58d18a", fontcolor="#58d18a", tooltip="sseAutomat → attributeAutomat ('attributename' sse)", label=" 'attributename' sse ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> propertyAutomat:in1:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="sseAutomat → propertyAutomat ('propertyname' sse)", label=" 'propertyname' sse ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> orderAutomat:in1:n [color="#a98cff", fontcolor="#a98cff", tooltip="sseAutomat → orderAutomat ('order'/'shipment' sse)", label=" 'order'/'shipment' sse ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> vfsAutomat:in2:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="sseAutomat → vfsAutomat ('vfs' sse mutation)", label=" 'vfs' sse mutation ", constraint=false, weight=1];
companyAutomat:p_comp_root:e -> categoryAutomat:in1:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="companyAutomat → categoryAutomat (rootCategoryId)", label=" rootCategoryId ", constraint=false, weight=1];
categoryAutomat:p_cat_loadTree:e -> categoryViewAutomat:in0:n [color="#ff7aa8", fontcolor="#ff7aa8", tooltip="categoryAutomat → categoryViewAutomat (attachTree &amp; warm slugs)", label=" attachTree &amp; warm slugs ", weight=3];
categoryViewAutomat:p_catview_collect:e -> articleIndexAutomat:in1:n [color="#e0b25a", fontcolor="#e0b25a", tooltip="categoryViewAutomat → articleIndexAutomat (derived union index)", label=" derived union index ", weight=3];
articleIndexAutomat:p_idx_items:e -> articleSearchAutomat:in1:n [color="#56c8c8", fontcolor="#56c8c8", tooltip="articleIndexAutomat → articleSearchAutomat (query index items)", label=" query index items ", weight=3];
categoryAutomat:p_cat_categories:e -> articleSearchAutomat:in0:n [color="#ff5f9e", fontcolor="#ff5f9e", tooltip="categoryAutomat → articleSearchAutomat (categoryFacets)", label=" categoryFacets ", constraint=false, weight=1];
artikelAutomat:p_art_setName:e -> articleIndexAutomat:in0:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="artikelAutomat → articleIndexAutomat (setArticleIndexName (optimistic))", label=" setArticleIndexName (optimistic) ", constraint=false, weight=1];
artikelAutomat:p_art_set:e -> categoryAutomat:in3:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="artikelAutomat → categoryAutomat (articleCount updates)", label=" articleCount updates ", weight=3];
articleDetailAutomat:p_artdet_hist:e -> artikelAutomat:in2:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="articleDetailAutomat → artikelAutomat (watch SKU details)", label=" watch SKU details ", weight=3];
deliveryEditorAutomat:p_deled_supplier:w -> supplierAutomat:in0:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="deliveryEditorAutomat → supplierAutomat (match vendor catalog)", label=" match vendor catalog ", constraint=false, weight=1];
deliveryEditorAutomat:p_deled_lines:e -> artikelAutomat:in1:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="deliveryEditorAutomat → artikelAutomat (lookup SKU &amp; pack units)", label=" lookup SKU &amp; pack units ", constraint=false, weight=1];
deliveryEditorAutomat:p_deled_commit:e -> deliveryAutomat:in0:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="deliveryEditorAutomat → deliveryAutomat (commit delivery receipt)", label=" commit delivery receipt ", weight=3];
quoteAutomat:p_quote_items:w -> orderAutomat:in2:n [color="#c79bff", fontcolor="#c79bff", tooltip="quoteAutomat → orderAutomat (convert quote to order)", label=" convert quote to order ", weight=3];
orderAutomat:p_order_items:e -> entityAutomat:in1:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="orderAutomat → entityAutomat (customer lookup)", label=" customer lookup ", weight=3];
companyAutomat:p_comp_curr:e -> customergroupAutomat:in0:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="companyAutomat → customergroupAutomat (poscustomergroup)", label=" poscustomergroup ", constraint=false, weight=1];
posterminalAutomat:p_pos_term:e -> categoryAutomat:in2:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="posterminalAutomat → categoryAutomat (filter enabled roots)", label=" filter enabled roots ", weight=3];
companyAutomat:p_comp_curr:e -> posterminalAutomat:in0:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="companyAutomat → posterminalAutomat (company context)", label=" company context ", weight=3];
chatAutomat:p_chat_msg:e -> vfsAutomat:in1:n [color="#58d18a", fontcolor="#58d18a", tooltip="chatAutomat → vfsAutomat (inspect / edit applets)", label=" inspect / edit applets ", constraint=false, weight=1];
chatAutomat:p_chat_tools:w -> navigationAutomat:in1:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="chatAutomat → navigationAutomat (set_user_route tool)", label=" set_user_route tool ", constraint=false, weight=1];
chatAutomat:p_chat_sse:e -> sseAutomat:in1:n [color="#a98cff", fontcolor="#a98cff", tooltip="chatAutomat → sseAutomat (getSseClientId())", label=" getSseClientId() ", weight=3];
navigationAutomat:p_nav_sku:e -> artikelAutomat:in0:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="navigationAutomat → artikelAutomat (restore last SKU)", label=" restore last SKU ", constraint=false, weight=1];
navigationAutomat:p_nav_catslug:e -> categoryAutomat:in0:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="navigationAutomat → categoryAutomat (restore last category)", label=" restore last category ", constraint=false, weight=1];
navigationAutomat:p_nav_delpath:e -> deliveryEditorAutomat:in0:n [color="#ff7aa8", fontcolor="#ff7aa8", tooltip="navigationAutomat → deliveryEditorAutomat (restore draft path)", label=" restore draft path ", weight=3];
userSettingsAutomat:p_usr_drawer:e -> chatAutomat:in0:n [color="#e0b25a", fontcolor="#e0b25a", tooltip="userSettingsAutomat → chatAutomat (chatDrawerWidth)", label=" chatDrawerWidth ", weight=3];
helpAutomat:p_help_active:e -> navigationAutomat:in0:n [color="#56c8c8", fontcolor="#56c8c8", tooltip="helpAutomat → navigationAutomat (route-contextual help)", label=" route-contextual help ", weight=3];
categoryAutomat:p_cat_categories:ne -> categoryAutomat:in4:n [color="#ff5f9e", fontcolor="#ff5f9e", tooltip="categoryAutomat → categoryAutomat (nested tree hierarchy)", label=" nested tree hierarchy ", weight=3];
artikelAutomat:p_art_set:ne -> artikelAutomat:in3:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="artikelAutomat → artikelAutomat (variants &amp; parent SKU)", label=" variants &amp; parent SKU ", weight=3];
chatAutomat:p_chat_msg:nw -> chatAutomat:in2:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="chatAutomat → chatAutomat (stream delta appends)", label=" stream delta appends ", weight=3];
authAutomat:p_auth_status:e -> ledgerHistoryGridAutomat:in0:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="authAutomat → ledgerHistoryGridAutomat (reload ledger on authed)", label=" reload ledger on authed ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> categoryAutomat:in6:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="sseAutomat → categoryAutomat ('category' sse trigger)", label=" 'category' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> categoryViewAutomat:in1:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="sseAutomat → categoryViewAutomat (invalidate views on sse)", label=" invalidate views on sse ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> articleIndexAutomat:in2:n [color="#c79bff", fontcolor="#c79bff", tooltip="sseAutomat → articleIndexAutomat ('articles' sse trigger)", label=" 'articles' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> artikelAutomat:in4:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="sseAutomat → artikelAutomat ('article-&lt;sku&gt;' sse reload)", label=" 'article-&lt;sku&gt;' sse reload ", weight=3];
sseAutomat:p_sse_events:w -> articleDetailAutomat:in0:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="sseAutomat → articleDetailAutomat (invalidateArticle(sku))", label=" invalidateArticle(sku) ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> warehouseAutomat:in1:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="sseAutomat → warehouseAutomat ('warehouse' sse trigger)", label=" 'warehouse' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> supplierAutomat:in2:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="sseAutomat → supplierAutomat ('supplier' sse trigger)", label=" 'supplier' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:w -> brandAutomat:in1:n [color="#58d18a", fontcolor="#58d18a", tooltip="sseAutomat → brandAutomat ('brand' sse trigger)", label=" 'brand' sse trigger ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> attributeAutomat:in1:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="sseAutomat → attributeAutomat ('attributename' sse)", label=" 'attributename' sse ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> propertyAutomat:in1:n [color="#a98cff", fontcolor="#a98cff", tooltip="sseAutomat → propertyAutomat ('propertyname' sse)", label=" 'propertyname' sse ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> orderAutomat:in1:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="sseAutomat → orderAutomat ('order'/'shipment' sse)", label=" 'order'/'shipment' sse ", constraint=false, weight=1];
sseAutomat:p_sse_events:e -> vfsAutomat:in2:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="sseAutomat → vfsAutomat ('vfs' sse mutation)", label=" 'vfs' sse mutation ", constraint=false, weight=1];
companyAutomat:p_comp_root:e -> categoryAutomat:in1:n [color="#ff7aa8", fontcolor="#ff7aa8", tooltip="companyAutomat → categoryAutomat (rootCategoryId)", label=" rootCategoryId ", constraint=false, weight=1];
categoryAutomat:p_cat_loadTree:e -> categoryViewAutomat:in0:n [color="#e0b25a", fontcolor="#e0b25a", tooltip="categoryAutomat → categoryViewAutomat (attachTree &amp; warm slugs)", label=" attachTree &amp; warm slugs ", weight=3];
categoryViewAutomat:p_catview_collect:e -> articleIndexAutomat:in1:n [color="#56c8c8", fontcolor="#56c8c8", tooltip="categoryViewAutomat → articleIndexAutomat (derived union index)", label=" derived union index ", weight=3];
articleIndexAutomat:p_idx_items:e -> articleSearchAutomat:in1:n [color="#ff5f9e", fontcolor="#ff5f9e", tooltip="articleIndexAutomat → articleSearchAutomat (query index items)", label=" query index items ", weight=3];
categoryAutomat:p_cat_categories:e -> articleSearchAutomat:in0:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="categoryAutomat → articleSearchAutomat (categoryFacets)", label=" categoryFacets ", constraint=false, weight=1];
artikelAutomat:p_art_setName:e -> articleIndexAutomat:in0:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="artikelAutomat → articleIndexAutomat (setArticleIndexName (optimistic))", label=" setArticleIndexName (optimistic) ", constraint=false, weight=1];
artikelAutomat:p_art_set:e -> categoryAutomat:in3:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="artikelAutomat → categoryAutomat (articleCount updates)", label=" articleCount updates ", weight=3];
articleDetailAutomat:p_artdet_hist:e -> artikelAutomat:in2:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="articleDetailAutomat → artikelAutomat (watch SKU details)", label=" watch SKU details ", weight=3];
deliveryEditorAutomat:p_deled_supplier:w -> supplierAutomat:in0:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="deliveryEditorAutomat → supplierAutomat (match vendor catalog)", label=" match vendor catalog ", constraint=false, weight=1];
deliveryEditorAutomat:p_deled_lines:e -> artikelAutomat:in1:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="deliveryEditorAutomat → artikelAutomat (lookup SKU &amp; pack units)", label=" lookup SKU &amp; pack units ", constraint=false, weight=1];
deliveryEditorAutomat:p_deled_commit:e -> deliveryAutomat:in0:n [color="#c79bff", fontcolor="#c79bff", tooltip="deliveryEditorAutomat → deliveryAutomat (commit delivery receipt)", label=" commit delivery receipt ", weight=3];
quoteAutomat:p_quote_items:w -> orderAutomat:in2:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="quoteAutomat → orderAutomat (convert quote to order)", label=" convert quote to order ", weight=3];
orderAutomat:p_order_items:e -> entityAutomat:in1:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="orderAutomat → entityAutomat (customer lookup)", label=" customer lookup ", weight=3];
companyAutomat:p_comp_curr:e -> customergroupAutomat:in0:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="companyAutomat → customergroupAutomat (poscustomergroup)", label=" poscustomergroup ", constraint=false, weight=1];
posterminalAutomat:p_pos_term:e -> categoryAutomat:in2:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="posterminalAutomat → categoryAutomat (filter enabled roots)", label=" filter enabled roots ", weight=3];
companyAutomat:p_comp_curr:e -> posterminalAutomat:in0:n [color="#58d18a", fontcolor="#58d18a", tooltip="companyAutomat → posterminalAutomat (company context)", label=" company context ", weight=3];
chatAutomat:p_chat_msg:e -> vfsAutomat:in1:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="chatAutomat → vfsAutomat (inspect / edit applets)", label=" inspect / edit applets ", constraint=false, weight=1];
chatAutomat:p_chat_tools:w -> navigationAutomat:in1:n [color="#a98cff", fontcolor="#a98cff", tooltip="chatAutomat → navigationAutomat (set_user_route tool)", label=" set_user_route tool ", constraint=false, weight=1];
chatAutomat:p_chat_sse:e -> sseAutomat:in1:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="chatAutomat → sseAutomat (getSseClientId())", label=" getSseClientId() ", weight=3];
navigationAutomat:p_nav_sku:e -> artikelAutomat:in0:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="navigationAutomat → artikelAutomat (restore last SKU)", label=" restore last SKU ", constraint=false, weight=1];
navigationAutomat:p_nav_catslug:e -> categoryAutomat:in0:n [color="#ff7aa8", fontcolor="#ff7aa8", tooltip="navigationAutomat → categoryAutomat (restore last category)", label=" restore last category ", constraint=false, weight=1];
navigationAutomat:p_nav_delpath:e -> deliveryEditorAutomat:in0:n [color="#e0b25a", fontcolor="#e0b25a", tooltip="navigationAutomat → deliveryEditorAutomat (restore draft path)", label=" restore draft path ", weight=3];
userSettingsAutomat:p_usr_drawer:e -> chatAutomat:in0:n [color="#56c8c8", fontcolor="#56c8c8", tooltip="userSettingsAutomat → chatAutomat (chatDrawerWidth)", label=" chatDrawerWidth ", weight=3];
helpAutomat:p_help_active:e -> navigationAutomat:in0:n [color="#ff5f9e", fontcolor="#ff5f9e", tooltip="helpAutomat → navigationAutomat (route-contextual help)", label=" route-contextual help ", weight=3];
categoryAutomat:p_cat_categories:ne -> categoryAutomat:in4:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="categoryAutomat → categoryAutomat (nested tree hierarchy)", label=" nested tree hierarchy ", weight=3];
artikelAutomat:p_art_set:ne -> artikelAutomat:in3:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="artikelAutomat → artikelAutomat (variants &amp; parent SKU)", label=" variants &amp; parent SKU ", weight=3];
chatAutomat:p_chat_msg:nw -> chatAutomat:in2:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="chatAutomat → chatAutomat (stream delta appends)", label=" stream delta appends ", weight=3];
}

View File

@@ -4,11 +4,11 @@
<!-- Generated by graphviz version 16.1.0 (0)
-->
<!-- Title: automats Pages: 1 -->
<svg width="8568pt" height="2129pt"
viewBox="0.00 0.00 8568.00 2129.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:#1e2430;">
<svg width="8916pt" height="2129pt"
viewBox="0.00 0.00 8916.00 2129.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:#1e2430;">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(36 2092.9)">
<title>automats</title>
<polygon fill="#1e2430" stroke="none" points="-36,36 -36,-2092.9 8531.76,-2092.9 8531.76,36 -36,36"/>
<polygon fill="#1e2430" stroke="none" points="-36,36 -36,-2092.9 8879.76,-2092.9 8879.76,36 -36,36"/>
<g id="clust1" class="cluster">
<title>cluster_session_realtime</title>
<path fill="#202a3a" stroke="#3f4c5f" d="M4581.76,-1554C4581.76,-1554 5270.76,-1554 5270.76,-1554 5276.76,-1554 5282.76,-1560 5282.76,-1566 5282.76,-1566 5282.76,-1863 5282.76,-1863 5282.76,-1869 5276.76,-1875 5270.76,-1875 5270.76,-1875 4581.76,-1875 4581.76,-1875 4575.76,-1875 4569.76,-1869 4569.76,-1863 4569.76,-1863 4569.76,-1566 4569.76,-1566 4569.76,-1560 4575.76,-1554 4581.76,-1554"/>
@@ -61,7 +61,7 @@
</g>
<g id="clust11" class="cluster">
<title>cluster_audit_grids</title>
<path fill="#202a3a" stroke="#3f4c5f" d="M7873.76,-1609C7873.76,-1609 8475.76,-1609 8475.76,-1609 8481.76,-1609 8487.76,-1615 8487.76,-1621 8487.76,-1621 8487.76,-1808 8487.76,-1808 8487.76,-1814 8481.76,-1820 8475.76,-1820 8475.76,-1820 7873.76,-1820 7873.76,-1820 7867.76,-1820 7861.76,-1814 7861.76,-1808 7861.76,-1808 7861.76,-1621 7861.76,-1621 7861.76,-1615 7867.76,-1609 7873.76,-1609"/>
<path fill="#202a3a" stroke="#3f4c5f" d="M7873.76,-1609C7873.76,-1609 8823.76,-1609 8823.76,-1609 8829.76,-1609 8835.76,-1615 8835.76,-1621 8835.76,-1621 8835.76,-1808 8835.76,-1808 8835.76,-1814 8829.76,-1820 8823.76,-1820 8823.76,-1820 7873.76,-1820 7873.76,-1820 7867.76,-1820 7861.76,-1814 7861.76,-1808 7861.76,-1808 7861.76,-1621 7861.76,-1621 7861.76,-1615 7867.76,-1609 7873.76,-1609"/>
<text xml:space="preserve" text-anchor="middle" x="7937.28" y="-1802.5" font-family="Helvetica,sans-Serif" font-size="15.00" fill="#e2e8f0">Audit &amp; Virtual Grids</text>
</g>
<g id="clust12" class="cluster">
@@ -344,7 +344,7 @@
<polygon fill="#ff7fc2" stroke="#ff7fc2" stroke-width="1.4" points="3466.8,-1791.03 3464.11,-1784.12 3461.9,-1791.2 3466.8,-1791.03"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="4040.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7fc2"> &#160;sync brands on authed &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="4129.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7fc2"> &#160;sync brands on authed &#160;</text>
</g>
<!-- unitAutomat -->
<g id="node8" class="node">
@@ -923,7 +923,7 @@
<polygon fill="#6bdf9a" stroke="#6bdf9a" stroke-width="1.4" points="2041.55,-1780.02 2038.84,-1773.12 2036.65,-1780.2 2041.55,-1780.02"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3116.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#6bdf9a"> &#160;load company profile &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3449.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#6bdf9a"> &#160;load company profile &#160;</text>
</g>
<!-- accountAutomat -->
<g id="node22" class="node">
@@ -960,7 +960,7 @@
<polygon fill="#56c8c8" stroke="#56c8c8" stroke-width="1.4" points="2390.49,-1747.04 2387.82,-1740.12 2385.59,-1747.19 2390.49,-1747.04"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2885.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#56c8c8"> &#160;sync payment accounts &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3620.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#56c8c8"> &#160;sync payment accounts &#160;</text>
</g>
<!-- numberAutomat -->
<g id="node23" class="node">
@@ -997,7 +997,7 @@
<polygon fill="#e0b25a" stroke="#e0b25a" stroke-width="1.4" points="2713.45,-1747.05 2710.81,-1740.12 2708.55,-1747.18 2713.45,-1747.05"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="4409.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e0b25a"> &#160;sync number series &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3220.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e0b25a"> &#160;sync number series &#160;</text>
</g>
<!-- posterminalAutomat -->
<g id="node24" class="node">
@@ -1234,7 +1234,7 @@
<polygon fill="#66b3ff" stroke="#66b3ff" stroke-width="1.4" points="8009.89,-1758.2 8007.68,-1751.12 8004.99,-1758.03 8009.89,-1758.2"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="5131.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#66b3ff"> &#160;reload history on authed &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="7317.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#66b3ff"> &#160;reload history on authed &#160;</text>
</g>
<!-- globalHistoryGridAutomat -->
<g id="node28" class="node">
@@ -1277,17 +1277,60 @@
<polygon fill="#5aa7ff" stroke="#5aa7ff" stroke-width="1.4" points="8342.89,-1758.2 8340.68,-1751.12 8337.99,-1758.03 8342.89,-1758.2"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="7483.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5aa7ff"> &#160;reload audit on authed &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="5131.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5aa7ff"> &#160;reload audit on authed &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;categoryAutomat -->
<!-- ledgerHistoryGridAutomat -->
<g id="node29" class="node">
<title>ledgerHistoryGridAutomat</title>
<polygon fill="#33415c" stroke="none" points="8549.55,-1715 8549.55,-1750 8813.97,-1750 8813.97,-1715 8549.55,-1715"/>
<polygon fill="none" stroke="#3f4c5f" points="8549.55,-1715 8549.55,-1750 8813.97,-1750 8813.97,-1715 8549.55,-1715"/>
<polygon fill="#33415c" stroke="none" points="8551.55,-1744 8551.55,-1748 8811.97,-1748 8811.97,-1744 8551.55,-1744"/>
<text xml:space="preserve" text-anchor="start" x="8606.41" y="-1731.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="12.00" fill="#e2e8f0">ledgerHistoryGridAutomat</text>
<text xml:space="preserve" text-anchor="start" x="8625.73" y="-1721.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8b95a7">createGridAutomat (Ledger)</text>
<polygon fill="#2a3242" stroke="none" points="8549.55,-1693 8549.55,-1715 8813.97,-1715 8813.97,-1693 8549.55,-1693"/>
<polygon fill="none" stroke="#3f4c5f" points="8549.55,-1693 8549.55,-1715 8813.97,-1715 8813.97,-1693 8549.55,-1693"/>
<polygon fill="#2b1a42" stroke="none" points="8550.55,-1694 8550.55,-1714 8604.01,-1714 8604.01,-1694 8550.55,-1694"/>
<text xml:space="preserve" text-anchor="start" x="8564.17" y="-1702.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="8.00" fill="#c084fc">STATE</text>
<text xml:space="preserve" text-anchor="start" x="8608.01" y="-1702" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="10.00" fill="#d8dee9">rows: [] / totalRows</text>
<text xml:space="preserve" text-anchor="start" x="8719.16" y="-1700.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Block size 50 virtual rows</text>
<polygon fill="#272e3d" stroke="none" points="8549.55,-1671 8549.55,-1693 8813.97,-1693 8813.97,-1671 8549.55,-1671"/>
<polygon fill="none" stroke="#3f4c5f" points="8549.55,-1671 8549.55,-1693 8813.97,-1693 8813.97,-1671 8549.55,-1671"/>
<polygon fill="#143826" stroke="none" points="8550.55,-1672 8550.55,-1692 8609.67,-1692 8609.67,-1672 8550.55,-1672"/>
<text xml:space="preserve" text-anchor="start" x="8564.78" y="-1680.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="8.00" fill="#4ade80">ACTION</text>
<text xml:space="preserve" text-anchor="start" x="8613.67" y="-1680" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="10.00" fill="#d8dee9">fetchRows(viewport)</text>
<text xml:space="preserve" text-anchor="start" x="8742.27" y="-1678.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Infinite scroll query</text>
<polygon fill="#2a3242" stroke="none" points="8549.55,-1649 8549.55,-1671 8813.97,-1671 8813.97,-1649 8549.55,-1649"/>
<polygon fill="none" stroke="#3f4c5f" points="8549.55,-1649 8549.55,-1671 8813.97,-1671 8813.97,-1649 8549.55,-1649"/>
<polygon fill="#143826" stroke="none" points="8550.55,-1650 8550.55,-1670 8620.59,-1670 8620.59,-1650 8550.55,-1650"/>
<text xml:space="preserve" text-anchor="start" x="8570.24" y="-1658.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="8.00" fill="#4ade80">ACTION</text>
<text xml:space="preserve" text-anchor="start" x="8624.59" y="-1658" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="10.00" fill="#d8dee9">reload()</text>
<text xml:space="preserve" text-anchor="start" x="8713.36" y="-1656.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Reload on company switch</text>
<polygon fill="#272e3d" stroke="none" points="8549.55,-1627 8549.55,-1649 8813.97,-1649 8813.97,-1627 8549.55,-1627"/>
<polygon fill="none" stroke="#3f4c5f" points="8549.55,-1627 8549.55,-1649 8813.97,-1649 8813.97,-1627 8549.55,-1627"/>
<polygon fill="#3b2c12" stroke="none" points="8550.55,-1628 8550.55,-1648 8598.55,-1648 8598.55,-1628 8550.55,-1628"/>
<text xml:space="preserve" text-anchor="start" x="8567.88" y="-1636.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="8.00" fill="#fbbf24">API</text>
<text xml:space="preserve" text-anchor="start" x="8602.55" y="-1636" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="10.00" fill="#d8dee9">GET /api/ledger/history</text>
<text xml:space="preserve" text-anchor="start" x="8720.01" y="-1634.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Append&#45;only ledger audit</text>
</g>
<!-- authAutomat&#45;&gt;ledgerHistoryGridAutomat -->
<g id="edge22" class="edge">
<title>sseAutomat:w&#45;&gt;categoryAutomat:n</title>
<g id="a_edge22"><a xlink:title="sseAutomat → categoryAutomat (&#39;category&#39; sse trigger)">
<path fill="none" stroke="#6bdf9a" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4959.41,-1571.11 4897.76,-1554 4836.57,-1537.02 2675.22,-1534.09 2611.76,-1532 2526.33,-1529.19 2505.17,-1524.18 2419.76,-1521.2 2411.62,-1520.92 1839,-1519 1833.28,-1513.2 1733.09,-1411.63 1740.3,-1300.41 1833.28,-1192.2 1864.85,-1155.46 2000.51,-1163.72 2048.76,-1159.4 2116.59,-1153.32 3232.54,-1161.48 3315.27,-1098.95"/>
<polygon fill="#6bdf9a" stroke="#6bdf9a" stroke-width="1.4" points="3317.26,-1100.4 3318.97,-1093.18 3313.13,-1097.75 3317.26,-1100.4"/>
<title>authAutomat:e&#45;&gt;ledgerHistoryGridAutomat:n</title>
<g id="a_edge22"><a xlink:title="authAutomat → ledgerHistoryGridAutomat (reload ledger on authed)">
<path fill="none" stroke="#6bdf9a" stroke-width="1.4" d="M4857.07,-1629C4970.33,-1629 4883.15,-1794.36 4980.76,-1851.82 5065.19,-1901.52 8413.25,-1877.65 8507.76,-1851.82 8591.36,-1828.96 8675.62,-1838.12 8681.44,-1758.11"/>
<polygon fill="#6bdf9a" stroke="#6bdf9a" stroke-width="1.4" points="8683.88,-1758.2 8681.68,-1751.12 8678.99,-1758.03 8683.88,-1758.2"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="1879.52" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#6bdf9a"> &#160;&#39;category&#39; sse trigger &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="7924.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#6bdf9a"> &#160;reload ledger on authed &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;categoryAutomat -->
<g id="edge23" class="edge">
<title>sseAutomat:w&#45;&gt;categoryAutomat:n</title>
<g id="a_edge23"><a xlink:title="sseAutomat → categoryAutomat (&#39;category&#39; sse trigger)">
<path fill="none" stroke="#f5c15c" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4959.41,-1571.11 4897.76,-1554 4836.57,-1537.02 2675.22,-1534.09 2611.76,-1532 2526.33,-1529.19 2505.17,-1524.18 2419.76,-1521.2 2411.62,-1520.92 1839,-1519 1833.28,-1513.2 1733.09,-1411.63 1740.3,-1300.41 1833.28,-1192.2 1864.85,-1155.46 2000.51,-1163.72 2048.76,-1159.4 2116.59,-1153.32 3232.54,-1161.48 3315.27,-1098.95"/>
<polygon fill="#f5c15c" stroke="#f5c15c" stroke-width="1.4" points="3317.26,-1100.4 3318.97,-1093.18 3313.13,-1097.75 3317.26,-1100.4"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="1879.52" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#f5c15c"> &#160;&#39;category&#39; sse trigger &#160;</text>
</g>
<!-- categoryViewAutomat -->
<g id="node4" class="node">
@@ -1342,14 +1385,14 @@
<text xml:space="preserve" text-anchor="start" x="3477.13" y="-587.4" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Category article batch</text>
</g>
<!-- sseAutomat&#45;&gt;categoryViewAutomat -->
<g id="edge23" class="edge">
<g id="edge24" class="edge">
<title>sseAutomat:w&#45;&gt;categoryViewAutomat:n</title>
<g id="a_edge23"><a xlink:title="sseAutomat → categoryViewAutomat (invalidate views on sse)">
<path fill="none" stroke="#f5c15c" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4959.11,-1572.13 4897.76,-1554 4826.13,-1532.83 4285.83,-1558.91 4226.76,-1513.2 4105.67,-1419.5 4227.48,-1279.58 4101.76,-1192.2 4036.1,-1146.57 2718.81,-1227.17 2662.71,-1170.2 2547.14,-1052.85 2617.73,-914.64 2743.76,-808.6 2804.56,-757.44 3435.85,-843.3 3482.62,-776.07"/>
<polygon fill="#f5c15c" stroke="#f5c15c" stroke-width="1.4" points="3484.88,-777.03 3484.7,-769.62 3480.22,-775.52 3484.88,-777.03"/>
<g id="a_edge24"><a xlink:title="sseAutomat → categoryViewAutomat (invalidate views on sse)">
<path fill="none" stroke="#ff7fc2" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4959.11,-1572.13 4897.76,-1554 4826.13,-1532.83 4285.83,-1558.91 4226.76,-1513.2 4105.67,-1419.5 4227.48,-1279.58 4101.76,-1192.2 4036.1,-1146.57 2718.81,-1227.17 2662.71,-1170.2 2547.14,-1052.85 2617.73,-914.64 2743.76,-808.6 2804.56,-757.44 3435.85,-843.3 3482.62,-776.07"/>
<polygon fill="#ff7fc2" stroke="#ff7fc2" stroke-width="1.4" points="3484.88,-777.03 3484.7,-769.62 3480.22,-775.52 3484.88,-777.03"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2713.73" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#f5c15c"> &#160;invalidate views on sse &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2713.73" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7fc2"> &#160;invalidate views on sse &#160;</text>
</g>
<!-- artikelAutomat -->
<g id="node5" class="node">
@@ -1413,14 +1456,14 @@
<text xml:space="preserve" text-anchor="start" x="3371.78" y="-1229" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Full article document</text>
</g>
<!-- sseAutomat&#45;&gt;artikelAutomat -->
<g id="edge25" class="edge">
<g id="edge26" class="edge">
<title>sseAutomat:w&#45;&gt;artikelAutomat:n</title>
<g id="a_edge25"><a xlink:title="sseAutomat → artikelAutomat (&#39;article&#45;&lt;sku&gt;&#39; sse reload)">
<path fill="none" stroke="#c79bff" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4958.91,-1572.82 4897.76,-1554 4743.29,-1506.46 3469.39,-1592.57 3422.17,-1440.11"/>
<polygon fill="#c79bff" stroke="#c79bff" stroke-width="1.4" points="3424.61,-1439.85 3421.14,-1433.3 3419.76,-1440.58 3424.61,-1439.85"/>
<g id="a_edge26"><a xlink:title="sseAutomat → artikelAutomat (&#39;article&#45;&lt;sku&gt;&#39; sse reload)">
<path fill="none" stroke="#5cd6d6" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4958.91,-1572.82 4897.76,-1554 4743.29,-1506.46 3469.39,-1592.57 3422.17,-1440.11"/>
<polygon fill="#5cd6d6" stroke="#5cd6d6" stroke-width="1.4" points="3424.61,-1439.85 3421.14,-1433.3 3419.76,-1440.58 3424.61,-1439.85"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3949.87" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#c79bff"> &#160;&#39;article&#45;&lt;sku&gt;&#39; sse reload &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3949.87" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5cd6d6"> &#160;&#39;article&#45;&lt;sku&gt;&#39; sse reload &#160;</text>
</g>
<!-- articleDetailAutomat -->
<g id="node6" class="node">
@@ -1468,24 +1511,24 @@
<text xml:space="preserve" text-anchor="start" x="3229.75" y="-1612.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Vendor links &amp; prices</text>
</g>
<!-- sseAutomat&#45;&gt;articleDetailAutomat -->
<g id="edge26" class="edge">
<g id="edge27" class="edge">
<title>sseAutomat:w&#45;&gt;articleDetailAutomat:n</title>
<g id="a_edge26"><a xlink:title="sseAutomat → articleDetailAutomat (invalidateArticle(sku))">
<path fill="none" stroke="#5cd6d6" stroke-width="1.4" d="M5021.83,-1627C4907.71,-1627 4996.04,-1793.8 4897.76,-1851.82 4823.84,-1895.45 3436.78,-1869.37 3352.76,-1851.82 3268.01,-1834.11 3174.58,-1858.87 3168.11,-1780.04"/>
<polygon fill="#5cd6d6" stroke="#5cd6d6" stroke-width="1.4" points="3170.56,-1780.02 3167.84,-1773.12 3165.67,-1780.21 3170.56,-1780.02"/>
<g id="a_edge27"><a xlink:title="sseAutomat → articleDetailAutomat (invalidateArticle(sku))">
<path fill="none" stroke="#ff9f5a" stroke-width="1.4" d="M5021.83,-1627C4907.71,-1627 4996.04,-1793.8 4897.76,-1851.82 4823.84,-1895.45 3436.78,-1869.37 3352.76,-1851.82 3268.01,-1834.11 3174.58,-1858.87 3168.11,-1780.04"/>
<polygon fill="#ff9f5a" stroke="#ff9f5a" stroke-width="1.4" points="3170.56,-1780.02 3167.84,-1773.12 3165.67,-1780.21 3170.56,-1780.02"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3562.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5cd6d6"> &#160;invalidateArticle(sku) &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="4467.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff9f5a"> &#160;invalidateArticle(sku) &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;brandAutomat -->
<g id="edge29" class="edge">
<g id="edge30" class="edge">
<title>sseAutomat:w&#45;&gt;brandAutomat:n</title>
<g id="a_edge29"><a xlink:title="sseAutomat → brandAutomat (&#39;brand&#39; sse trigger)">
<path fill="none" stroke="#ff6b6b" stroke-width="1.4" d="M5021.83,-1627C4907.71,-1627 4996.04,-1793.8 4897.76,-1851.82 4784.18,-1918.86 3837.5,-1888.28 3710.76,-1851.82 3657.71,-1836.56 3605.51,-1839.94 3599.95,-1791.03"/>
<polygon fill="#ff6b6b" stroke="#ff6b6b" stroke-width="1.4" points="3602.41,-1790.97 3599.58,-1784.12 3597.51,-1791.24 3602.41,-1790.97"/>
<g id="a_edge30"><a xlink:title="sseAutomat → brandAutomat (&#39;brand&#39; sse trigger)">
<path fill="none" stroke="#58d18a" stroke-width="1.4" d="M5021.83,-1627C4907.71,-1627 4996.04,-1793.8 4897.76,-1851.82 4784.18,-1918.86 3837.5,-1888.28 3710.76,-1851.82 3657.71,-1836.56 3605.51,-1839.94 3599.95,-1791.03"/>
<polygon fill="#58d18a" stroke="#58d18a" stroke-width="1.4" points="3602.41,-1790.97 3599.58,-1784.12 3597.51,-1791.24 3602.41,-1790.97"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="4258.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff6b6b"> &#160;&#39;brand&#39; sse trigger &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="4677.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#58d18a"> &#160;&#39;brand&#39; sse trigger &#160;</text>
</g>
<!-- articleIndexAutomat -->
<g id="node9" class="node">
@@ -1541,94 +1584,94 @@
<text xml:space="preserve" text-anchor="start" x="3636.84" y="-269.6" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Await warm views</text>
</g>
<!-- sseAutomat&#45;&gt;articleIndexAutomat -->
<g id="edge24" class="edge">
<g id="edge25" class="edge">
<title>sseAutomat:w&#45;&gt;articleIndexAutomat:n</title>
<g id="a_edge24"><a xlink:title="sseAutomat → articleIndexAutomat (&#39;articles&#39; sse trigger)">
<path fill="none" stroke="#ff7fc2" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4959.2,-1571.83 4897.76,-1554 4811.32,-1528.92 4155.67,-1573.39 4088.76,-1513.2 3979.6,-1415.02 4125.45,-1283.86 4010.76,-1192.2 3980.92,-1168.35 2673.84,-1173.14 2635.76,-1170.2 2220.06,-1138.15 1208.52,-859.87 794.76,-808.6 753.58,-803.5 77.38,-802.64 48.76,-772.6 -15.94,-704.71 -16.52,-628.93 48.76,-561.6 186.25,-419.77 3554.66,-643.54 3653.32,-458.33"/>
<polygon fill="#ff7fc2" stroke="#ff7fc2" stroke-width="1.4" points="3655.61,-459.24 3654.99,-451.85 3650.87,-458.01 3655.61,-459.24"/>
<g id="a_edge25"><a xlink:title="sseAutomat → articleIndexAutomat (&#39;articles&#39; sse trigger)">
<path fill="none" stroke="#c79bff" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4959.2,-1571.83 4897.76,-1554 4811.32,-1528.92 4155.67,-1573.39 4088.76,-1513.2 3979.6,-1415.02 4125.45,-1283.86 4010.76,-1192.2 3980.92,-1168.35 2673.84,-1173.14 2635.76,-1170.2 2220.06,-1138.15 1208.52,-859.87 794.76,-808.6 753.58,-803.5 77.38,-802.64 48.76,-772.6 -15.94,-704.71 -16.52,-628.93 48.76,-561.6 186.25,-419.77 3554.66,-643.54 3653.32,-458.33"/>
<polygon fill="#c79bff" stroke="#c79bff" stroke-width="1.4" points="3655.61,-459.24 3654.99,-451.85 3650.87,-458.01 3655.61,-459.24"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2519.99" y="-973.2" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7fc2"> &#160;&#39;articles&#39; sse trigger &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2519.99" y="-973.2" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#c79bff"> &#160;&#39;articles&#39; sse trigger &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;propertyAutomat -->
<g id="edge31" class="edge">
<g id="edge32" class="edge">
<title>sseAutomat:e&#45;&gt;propertyAutomat:n</title>
<g id="a_edge31"><a xlink:title="sseAutomat → propertyAutomat (&#39;propertyname&#39; sse)">
<path fill="none" stroke="#4fc4e8" stroke-width="1.4" d="M5261.68,-1627C5646.9,-1627 5716.85,-1801.42 6098.76,-1851.82 6378.99,-1888.79 6455.2,-1893.64 6734.76,-1851.82 6853.04,-1834.12 6991.55,-1890.72 6998.5,-1780"/>
<polygon fill="#4fc4e8" stroke="#4fc4e8" stroke-width="1.4" points="7000.95,-1780.19 6998.71,-1773.12 6996.05,-1780.04 7000.95,-1780.19"/>
<g id="a_edge32"><a xlink:title="sseAutomat → propertyAutomat (&#39;propertyname&#39; sse)">
<path fill="none" stroke="#a98cff" stroke-width="1.4" d="M5261.68,-1627C5646.9,-1627 5716.85,-1801.42 6098.76,-1851.82 6378.99,-1888.79 6455.2,-1893.64 6734.76,-1851.82 6853.04,-1834.12 6991.55,-1890.72 6998.5,-1780"/>
<polygon fill="#a98cff" stroke="#a98cff" stroke-width="1.4" points="7000.95,-1780.19 6998.71,-1773.12 6996.05,-1780.04 7000.95,-1780.19"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="5681.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#4fc4e8"> &#160;&#39;propertyname&#39; sse &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="5681.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#a98cff"> &#160;&#39;propertyname&#39; sse &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;attributeAutomat -->
<g id="edge30" class="edge">
<g id="edge31" class="edge">
<title>sseAutomat:e&#45;&gt;attributeAutomat:n</title>
<g id="a_edge30"><a xlink:title="sseAutomat → attributeAutomat (&#39;attributename&#39; sse)">
<path fill="none" stroke="#58d18a" stroke-width="1.4" d="M5261.68,-1627C5646.9,-1627 5716.85,-1801.42 6098.76,-1851.82 6322.59,-1881.35 6891.51,-1885.55 7114.76,-1851.82 7231.91,-1834.11 7368.92,-1889.63 7375.8,-1779.92"/>
<polygon fill="#58d18a" stroke="#58d18a" stroke-width="1.4" points="7378.25,-1780.19 7376.01,-1773.12 7373.35,-1780.04 7378.25,-1780.19"/>
<g id="a_edge31"><a xlink:title="sseAutomat → attributeAutomat (&#39;attributename&#39; sse)">
<path fill="none" stroke="#4fc4e8" stroke-width="1.4" d="M5261.68,-1627C5646.9,-1627 5716.85,-1801.42 6098.76,-1851.82 6322.59,-1881.35 6891.51,-1885.55 7114.76,-1851.82 7231.91,-1834.11 7368.92,-1889.63 7375.8,-1779.92"/>
<polygon fill="#4fc4e8" stroke="#4fc4e8" stroke-width="1.4" points="7378.25,-1780.19 7376.01,-1773.12 7373.35,-1780.04 7378.25,-1780.19"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="5591.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#58d18a"> &#160;&#39;attributename&#39; sse &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="5591.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#4fc4e8"> &#160;&#39;attributename&#39; sse &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;supplierAutomat -->
<g id="edge28" class="edge">
<g id="edge29" class="edge">
<title>sseAutomat:w&#45;&gt;supplierAutomat:n</title>
<g id="a_edge28"><a xlink:title="sseAutomat → supplierAutomat (&#39;supplier&#39; sse trigger)">
<path fill="none" stroke="#8f95ff" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4959.1,-1572.19 4897.76,-1554 4845.19,-1538.42 4442.65,-1571.63 4404.76,-1532 4401.44,-1528.53 4402.29,-1525.32 4404.76,-1521.2 4408.64,-1514.72 4415.87,-1519.68 4419.76,-1513.2 4493.16,-1390.86 4519.86,-1293.86 4419.76,-1192.2 4391.96,-1163.97 3033.28,-1172.84 2993.76,-1170.2 2953.12,-1167.48 2943.41,-1161.87 2902.76,-1159.4 2868.64,-1157.33 474.92,-1159.69 441.76,-1151.4 359.03,-1130.72 272.31,-1145.64 266.32,-1067.34"/>
<polygon fill="#8f95ff" stroke="#8f95ff" stroke-width="1.4" points="268.77,-1067.42 266.07,-1060.52 263.88,-1067.6 268.77,-1067.42"/>
<g id="a_edge29"><a xlink:title="sseAutomat → supplierAutomat (&#39;supplier&#39; sse trigger)">
<path fill="none" stroke="#ff6b6b" stroke-width="1.4" d="M5021.83,-1627C4957.85,-1627 4959.1,-1572.19 4897.76,-1554 4845.19,-1538.42 4442.65,-1571.63 4404.76,-1532 4401.44,-1528.53 4402.29,-1525.32 4404.76,-1521.2 4408.64,-1514.72 4415.87,-1519.68 4419.76,-1513.2 4493.16,-1390.86 4519.86,-1293.86 4419.76,-1192.2 4391.96,-1163.97 3033.28,-1172.84 2993.76,-1170.2 2953.12,-1167.48 2943.41,-1161.87 2902.76,-1159.4 2868.64,-1157.33 474.92,-1159.69 441.76,-1151.4 359.03,-1130.72 272.31,-1145.64 266.32,-1067.34"/>
<polygon fill="#ff6b6b" stroke="#ff6b6b" stroke-width="1.4" points="268.77,-1067.42 266.07,-1060.52 263.88,-1067.6 268.77,-1067.42"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="4529.93" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8f95ff"> &#160;&#39;supplier&#39; sse trigger &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="4529.93" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff6b6b"> &#160;&#39;supplier&#39; sse trigger &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;orderAutomat -->
<g id="edge32" class="edge">
<g id="edge33" class="edge">
<title>sseAutomat:e&#45;&gt;orderAutomat:n</title>
<g id="a_edge32"><a xlink:title="sseAutomat → orderAutomat (&#39;order&#39;/&#39;shipment&#39; sse)">
<path fill="none" stroke="#a98cff" stroke-width="1.4" d="M5261.68,-1627C5635.13,-1627 5725.99,-1576.57 6098.76,-1554 6134.71,-1551.82 6721.72,-1557.9 6746.76,-1532 6750.09,-1528.55 6749.71,-1524.99 6746.76,-1521.2 6741.02,-1513.84 6449.16,-1453.82 6396.77,-1435.36"/>
<polygon fill="#a98cff" stroke="#a98cff" stroke-width="1.4" points="6397.98,-1433.23 6390.64,-1432.18 6395.72,-1437.58 6397.98,-1433.23"/>
<g id="a_edge33"><a xlink:title="sseAutomat → orderAutomat (&#39;order&#39;/&#39;shipment&#39; sse)">
<path fill="none" stroke="#ff8f66" stroke-width="1.4" d="M5261.68,-1627C5635.13,-1627 5725.99,-1576.57 6098.76,-1554 6134.71,-1551.82 6721.72,-1557.9 6746.76,-1532 6750.09,-1528.55 6749.71,-1524.99 6746.76,-1521.2 6741.02,-1513.84 6449.16,-1453.82 6396.77,-1435.36"/>
<polygon fill="#ff8f66" stroke="#ff8f66" stroke-width="1.4" points="6397.98,-1433.23 6390.64,-1432.18 6395.72,-1437.58 6397.98,-1433.23"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="6795.82" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#a98cff"> &#160;&#39;order&#39;/&#39;shipment&#39; sse &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="6795.82" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff8f66"> &#160;&#39;order&#39;/&#39;shipment&#39; sse &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;warehouseAutomat -->
<g id="edge27" class="edge">
<g id="edge28" class="edge">
<title>sseAutomat:e&#45;&gt;warehouseAutomat:n</title>
<g id="a_edge27"><a xlink:title="sseAutomat → warehouseAutomat (&#39;warehouse&#39; sse trigger)">
<path fill="none" stroke="#ff9f5a" stroke-width="1.4" d="M5261.68,-1627C5646.9,-1627 5716.85,-1801.42 6098.76,-1851.82 6405.21,-1892.25 7184.64,-1901.36 7489.76,-1851.82 7598.74,-1834.12 7724.94,-1881.79 7731.76,-1779.95"/>
<polygon fill="#ff9f5a" stroke="#ff9f5a" stroke-width="1.4" points="7734.21,-1780.19 7731.98,-1773.12 7729.31,-1780.04 7734.21,-1780.19"/>
<g id="a_edge28"><a xlink:title="sseAutomat → warehouseAutomat (&#39;warehouse&#39; sse trigger)">
<path fill="none" stroke="#8f95ff" stroke-width="1.4" d="M5261.68,-1627C5646.9,-1627 5716.85,-1801.42 6098.76,-1851.82 6405.21,-1892.25 7184.64,-1901.36 7489.76,-1851.82 7598.74,-1834.12 7724.94,-1881.79 7731.76,-1779.95"/>
<polygon fill="#8f95ff" stroke="#8f95ff" stroke-width="1.4" points="7734.21,-1780.19 7731.98,-1773.12 7729.31,-1780.04 7734.21,-1780.19"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="5387.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff9f5a"> &#160;&#39;warehouse&#39; sse trigger &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="5387.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8f95ff"> &#160;&#39;warehouse&#39; sse trigger &#160;</text>
</g>
<!-- sseAutomat&#45;&gt;vfsAutomat -->
<g id="edge33" class="edge">
<g id="edge34" class="edge">
<title>sseAutomat:e&#45;&gt;vfsAutomat:n</title>
<g id="a_edge33"><a xlink:title="sseAutomat → vfsAutomat (&#39;vfs&#39; sse mutation)">
<path fill="none" stroke="#ff8f66" stroke-width="1.4" d="M5261.68,-1627C5287.91,-1627 5278.47,-1587.27 5260.68,-1568 5204.25,-1506.88 4831.14,-1580.68 4889.29,-1521.2 4896.14,-1514.2 5232.06,-1514.6 5241.76,-1513.2 5395.56,-1491.01 5578.72,-1575.29 5585.82,-1429.21"/>
<polygon fill="#ff8f66" stroke="#ff8f66" stroke-width="1.4" points="5588.26,-1429.37 5585.98,-1422.32 5583.37,-1429.26 5588.26,-1429.37"/>
<g id="a_edge34"><a xlink:title="sseAutomat → vfsAutomat (&#39;vfs&#39; sse mutation)">
<path fill="none" stroke="#4ecb8f" stroke-width="1.4" d="M5261.68,-1627C5287.91,-1627 5278.47,-1587.27 5260.68,-1568 5204.25,-1506.88 4831.14,-1580.68 4889.29,-1521.2 4896.14,-1514.2 5232.06,-1514.6 5241.76,-1513.2 5395.56,-1491.01 5578.72,-1575.29 5585.82,-1429.21"/>
<polygon fill="#4ecb8f" stroke="#4ecb8f" stroke-width="1.4" points="5588.26,-1429.37 5585.98,-1422.32 5583.37,-1429.26 5588.26,-1429.37"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="4928.52" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff8f66"> &#160;&#39;vfs&#39; sse mutation &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="4928.52" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#4ecb8f"> &#160;&#39;vfs&#39; sse mutation &#160;</text>
</g>
<!-- categoryAutomat&#45;&gt;categoryAutomat -->
<g id="edge58" class="edge">
<g id="edge59" class="edge">
<title>categoryAutomat:ne&#45;&gt;categoryAutomat:n</title>
<g id="a_edge58"><a xlink:title="categoryAutomat → categoryAutomat (nested tree hierarchy)">
<path fill="none" stroke="#ff5f9e" stroke-width="1.4" d="M3341.51,-1057.4C3372.18,-1016.93 3417.51,-1016.93 3417.51,-1073.9 3417.51,-1128.87 3320.96,-1130.8 3250.17,-1094.52"/>
<polygon fill="#ff5f9e" stroke="#ff5f9e" stroke-width="1.4" points="3251.73,-1092.58 3244.41,-1091.41 3249.4,-1096.89 3251.73,-1092.58"/>
<g id="a_edge59"><a xlink:title="categoryAutomat → categoryAutomat (nested tree hierarchy)">
<path fill="none" stroke="#ffb25a" stroke-width="1.4" d="M3341.51,-1057.4C3372.18,-1016.93 3417.51,-1016.93 3417.51,-1073.9 3417.51,-1128.87 3320.96,-1130.8 3250.17,-1094.52"/>
<polygon fill="#ffb25a" stroke="#ffb25a" stroke-width="1.4" points="3251.73,-1092.58 3244.41,-1091.41 3249.4,-1096.89 3251.73,-1092.58"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3464.78" y="-973.2" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff5f9e"> &#160;nested tree hierarchy &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3464.78" y="-973.2" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ffb25a"> &#160;nested tree hierarchy &#160;</text>
</g>
<!-- categoryAutomat&#45;&gt;categoryViewAutomat -->
<g id="edge35" class="edge">
<g id="edge36" class="edge">
<title>categoryAutomat:e&#45;&gt;categoryViewAutomat:n</title>
<g id="a_edge35"><a xlink:title="categoryAutomat → categoryViewAutomat (attachTree &amp; warm slugs)">
<path fill="none" stroke="#ff7aa8" stroke-width="1.4" d="M3342.51,-980.4C3434.14,-980.4 3346.03,-869.46 3340.44,-776.53"/>
<polygon fill="#ff7aa8" stroke="#ff7aa8" stroke-width="1.4" points="3342.89,-776.64 3340.23,-769.72 3337.99,-776.79 3342.89,-776.64"/>
<g id="a_edge36"><a xlink:title="categoryAutomat → categoryViewAutomat (attachTree &amp; warm slugs)">
<path fill="none" stroke="#e0b25a" stroke-width="1.4" d="M3342.51,-980.4C3434.14,-980.4 3346.03,-869.46 3340.44,-776.53"/>
<polygon fill="#e0b25a" stroke="#e0b25a" stroke-width="1.4" points="3342.89,-776.64 3340.23,-769.72 3337.99,-776.79 3342.89,-776.64"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3401.19" y="-811.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7aa8"> &#160;attachTree &amp; warm slugs &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3401.19" y="-811.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e0b25a"> &#160;attachTree &amp; warm slugs &#160;</text>
</g>
<!-- articleSearchAutomat -->
<g id="node10" class="node">
@@ -1671,74 +1714,74 @@
<text xml:space="preserve" text-anchor="start" x="3608.59" y="-33.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Reactive search outcome</text>
</g>
<!-- categoryAutomat&#45;&gt;articleSearchAutomat -->
<g id="edge38" class="edge">
<g id="edge39" class="edge">
<title>categoryAutomat:e&#45;&gt;articleSearchAutomat:n</title>
<g id="a_edge38"><a xlink:title="categoryAutomat → articleSearchAutomat (categoryFacets)">
<path fill="none" stroke="#ff5f9e" stroke-width="1.4" d="M3342.51,-1046.4C3427.4,-1046.4 3380.83,-930.63 3341.51,-855.4 3311.88,-798.7 3253.24,-829.38 3223.76,-772.6 3180.54,-689.38 3158.95,-629.38 3223.76,-561.6 3261.5,-522.13 3662.29,-554.76 3714.76,-539.6 3730.97,-534.91 3739.27,-535.39 3747.76,-520.8 3816.95,-401.75 3837.15,-313.99 3745.76,-211 3710.44,-171.2 3519.13,-220.1 3496.83,-178.8"/>
<polygon fill="#ff5f9e" stroke="#ff5f9e" stroke-width="1.4" points="3499.23,-178.29 3495.2,-172.06 3494.46,-179.44 3499.23,-178.29"/>
<g id="a_edge39"><a xlink:title="categoryAutomat → articleSearchAutomat (categoryFacets)">
<path fill="none" stroke="#ffb25a" stroke-width="1.4" d="M3342.51,-1046.4C3427.4,-1046.4 3380.83,-930.63 3341.51,-855.4 3311.88,-798.7 3253.24,-829.38 3223.76,-772.6 3180.54,-689.38 3158.95,-629.38 3223.76,-561.6 3261.5,-522.13 3662.29,-554.76 3714.76,-539.6 3730.97,-534.91 3739.27,-535.39 3747.76,-520.8 3816.95,-401.75 3837.15,-313.99 3745.76,-211 3710.44,-171.2 3519.13,-220.1 3496.83,-178.8"/>
<polygon fill="#ffb25a" stroke="#ffb25a" stroke-width="1.4" points="3499.23,-178.29 3495.2,-172.06 3494.46,-179.44 3499.23,-178.29"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3777.37" y="-531.5" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff5f9e"> &#160;categoryFacets &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3777.37" y="-531.5" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ffb25a"> &#160;categoryFacets &#160;</text>
</g>
<!-- categoryViewAutomat&#45;&gt;articleIndexAutomat -->
<g id="edge36" class="edge">
<g id="edge37" class="edge">
<title>categoryViewAutomat:e&#45;&gt;articleIndexAutomat:n</title>
<g id="a_edge36"><a xlink:title="categoryViewAutomat → articleIndexAutomat (derived union index)">
<path fill="none" stroke="#e0b25a" stroke-width="1.4" d="M3560.95,-634.6C3640.2,-634.6 3565.41,-539.03 3560.05,-458.53"/>
<polygon fill="#e0b25a" stroke="#e0b25a" stroke-width="1.4" points="3562.52,-458.83 3559.83,-451.92 3557.62,-459 3562.52,-458.83"/>
<g id="a_edge37"><a xlink:title="categoryViewAutomat → articleIndexAutomat (derived union index)">
<path fill="none" stroke="#56c8c8" stroke-width="1.4" d="M3560.95,-634.6C3640.2,-634.6 3565.41,-539.03 3560.05,-458.53"/>
<polygon fill="#56c8c8" stroke="#56c8c8" stroke-width="1.4" points="3562.52,-458.83 3559.83,-451.92 3557.62,-459 3562.52,-458.83"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3623.74" y="-531.5" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e0b25a"> &#160;derived union index &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3623.74" y="-531.5" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#56c8c8"> &#160;derived union index &#160;</text>
</g>
<!-- artikelAutomat&#45;&gt;categoryAutomat -->
<g id="edge40" class="edge">
<g id="edge41" class="edge">
<title>artikelAutomat:e&#45;&gt;categoryAutomat:n</title>
<g id="a_edge40"><a xlink:title="artikelAutomat → categoryAutomat (articleCount updates)">
<path fill="none" stroke="#66b3ff" stroke-width="1.4" d="M3451.59,-1386.2C3526.7,-1386.2 3490.02,-1281.13 3450.59,-1217.2 3387.59,-1115.06 3212.93,-1209.48 3204.1,-1100.35"/>
<polygon fill="#66b3ff" stroke="#66b3ff" stroke-width="1.4" points="3206.56,-1100.42 3203.84,-1093.52 3201.66,-1100.61 3206.56,-1100.42"/>
<g id="a_edge41"><a xlink:title="artikelAutomat → categoryAutomat (articleCount updates)">
<path fill="none" stroke="#5aa7ff" stroke-width="1.4" d="M3451.59,-1386.2C3526.7,-1386.2 3490.02,-1281.13 3450.59,-1217.2 3387.59,-1115.06 3212.93,-1209.48 3204.1,-1100.35"/>
<polygon fill="#5aa7ff" stroke="#5aa7ff" stroke-width="1.4" points="3206.56,-1100.42 3203.84,-1093.52 3201.66,-1100.61 3206.56,-1100.42"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3425.76" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#66b3ff"> &#160;articleCount updates &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3425.76" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5aa7ff"> &#160;articleCount updates &#160;</text>
</g>
<!-- artikelAutomat&#45;&gt;artikelAutomat -->
<g id="edge59" class="edge">
<g id="edge60" class="edge">
<title>artikelAutomat:ne&#45;&gt;artikelAutomat:n</title>
<g id="a_edge59"><a xlink:title="artikelAutomat → artikelAutomat (variants &amp; parent SKU)">
<path fill="none" stroke="#ffb25a" stroke-width="1.4" d="M3450.59,-1397.2C3481.25,-1356.72 3526.59,-1356.72 3526.59,-1413.7 3526.59,-1468.67 3443.71,-1470.6 3372.97,-1434.32"/>
<polygon fill="#ffb25a" stroke="#ffb25a" stroke-width="1.4" points="3374.48,-1432.35 3367.16,-1431.2 3372.17,-1436.67 3374.48,-1432.35"/>
<g id="a_edge60"><a xlink:title="artikelAutomat → artikelAutomat (variants &amp; parent SKU)">
<path fill="none" stroke="#66b3ff" stroke-width="1.4" d="M3450.59,-1397.2C3481.25,-1356.72 3526.59,-1356.72 3526.59,-1413.7 3526.59,-1468.67 3443.71,-1470.6 3372.97,-1434.32"/>
<polygon fill="#66b3ff" stroke="#66b3ff" stroke-width="1.4" points="3374.48,-1432.35 3367.16,-1431.2 3372.17,-1436.67 3374.48,-1432.35"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3576.11" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ffb25a"> &#160;variants &amp; parent SKU &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3576.11" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#66b3ff"> &#160;variants &amp; parent SKU &#160;</text>
</g>
<!-- artikelAutomat&#45;&gt;articleIndexAutomat -->
<g id="edge39" class="edge">
<g id="edge40" class="edge">
<title>artikelAutomat:e&#45;&gt;articleIndexAutomat:n</title>
<g id="a_edge39"><a xlink:title="artikelAutomat → articleIndexAutomat (setArticleIndexName (optimistic))">
<path fill="none" stroke="#ffb25a" stroke-width="1.4" d="M3451.59,-1320.2C3497.37,-1320.2 3481.85,-1250.65 3450.59,-1217.2 3358.23,-1118.38 2937.24,-1267.95 2843.76,-1170.2 2656.76,-974.67 2642.01,-755.43 2830.76,-561.6 2894.96,-495.66 3594.55,-605.54 3658.76,-539.6 3723.05,-473.58 3480.98,-540.38 3464.83,-458.75"/>
<polygon fill="#ffb25a" stroke="#ffb25a" stroke-width="1.4" points="3467.28,-458.65 3464.19,-451.91 3462.4,-459.11 3467.28,-458.65"/>
<g id="a_edge40"><a xlink:title="artikelAutomat → articleIndexAutomat (setArticleIndexName (optimistic))">
<path fill="none" stroke="#66b3ff" stroke-width="1.4" d="M3451.59,-1320.2C3497.37,-1320.2 3481.85,-1250.65 3450.59,-1217.2 3358.23,-1118.38 2937.24,-1267.95 2843.76,-1170.2 2656.76,-974.67 2642.01,-755.43 2830.76,-561.6 2894.96,-495.66 3594.55,-605.54 3658.76,-539.6 3723.05,-473.58 3480.98,-540.38 3464.83,-458.75"/>
<polygon fill="#66b3ff" stroke="#66b3ff" stroke-width="1.4" points="3467.28,-458.65 3464.19,-451.91 3462.4,-459.11 3467.28,-458.65"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2768.25" y="-811.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ffb25a"> &#160;setArticleIndexName (optimistic) &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2768.25" y="-811.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#66b3ff"> &#160;setArticleIndexName (optimistic) &#160;</text>
</g>
<!-- articleDetailAutomat&#45;&gt;artikelAutomat -->
<g id="edge41" class="edge">
<g id="edge42" class="edge">
<title>articleDetailAutomat:e&#45;&gt;artikelAutomat:n</title>
<g id="a_edge41"><a xlink:title="articleDetailAutomat → artikelAutomat (watch SKU details)">
<path fill="none" stroke="#5aa7ff" stroke-width="1.4" d="M3310.9,-1726C3373.72,-1726 3315,-1515.13 3310.08,-1440.06"/>
<polygon fill="#5aa7ff" stroke="#5aa7ff" stroke-width="1.4" points="3312.54,-1440.22 3309.83,-1433.32 3307.64,-1440.4 3312.54,-1440.22"/>
<g id="a_edge42"><a xlink:title="articleDetailAutomat → artikelAutomat (watch SKU details)">
<path fill="none" stroke="#6bdf9a" stroke-width="1.4" d="M3310.9,-1726C3373.72,-1726 3315,-1515.13 3310.08,-1440.06"/>
<polygon fill="#6bdf9a" stroke="#6bdf9a" stroke-width="1.4" points="3312.54,-1440.22 3309.83,-1433.32 3307.64,-1440.4 3312.54,-1440.22"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3365.01" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5aa7ff"> &#160;watch SKU details &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3365.01" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#6bdf9a"> &#160;watch SKU details &#160;</text>
</g>
<!-- articleIndexAutomat&#45;&gt;articleSearchAutomat -->
<g id="edge37" class="edge">
<g id="edge38" class="edge">
<title>articleIndexAutomat:e&#45;&gt;articleSearchAutomat:n</title>
<g id="a_edge37"><a xlink:title="articleIndexAutomat → articleSearchAutomat (query index items)">
<path fill="none" stroke="#56c8c8" stroke-width="1.4" d="M3706.41,-404.8C3771.75,-404.8 3736.23,-315.41 3705.41,-257.8 3691.94,-232.63 3669.89,-244.22 3652.22,-221.8 3639.03,-205.05 3634.12,-196.89 3633.04,-178.98"/>
<polygon fill="#56c8c8" stroke="#56c8c8" stroke-width="1.4" points="3635.5,-179.05 3632.86,-172.12 3630.6,-179.18 3635.5,-179.05"/>
<g id="a_edge38"><a xlink:title="articleIndexAutomat → articleSearchAutomat (query index items)">
<path fill="none" stroke="#ff5f9e" stroke-width="1.4" d="M3706.41,-404.8C3771.75,-404.8 3736.23,-315.41 3705.41,-257.8 3691.94,-232.63 3669.89,-244.22 3652.22,-221.8 3639.03,-205.05 3634.12,-196.89 3633.04,-178.98"/>
<polygon fill="#ff5f9e" stroke="#ff5f9e" stroke-width="1.4" points="3635.5,-179.05 3632.86,-172.12 3630.6,-179.18 3635.5,-179.05"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3692.49" y="-213.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#56c8c8"> &#160;query index items &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="3692.49" y="-213.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff5f9e"> &#160;query index items &#160;</text>
</g>
<!-- deliveryEditorAutomat -->
<g id="node15" class="node">
@@ -1798,127 +1841,127 @@
<text xml:space="preserve" text-anchor="start" x="676.23" y="-878.2" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Create goods receipt</text>
</g>
<!-- deliveryEditorAutomat&#45;&gt;artikelAutomat -->
<g id="edge43" class="edge">
<g id="edge44" class="edge">
<title>deliveryEditorAutomat:e&#45;&gt;artikelAutomat:n</title>
<g id="a_edge43"><a xlink:title="deliveryEditorAutomat → artikelAutomat (lookup SKU &amp; pack units)">
<path fill="none" stroke="#f5c15c" stroke-width="1.4" d="M756.5,-1035.4C1377.87,-1035.4 1529.1,-1121.67 2149.76,-1151.4 2184.83,-1153.08 2748.39,-1148.47 2781.76,-1159.4 2978.56,-1223.84 2964.06,-1365.95 3160.93,-1430.2 3197.11,-1442.01 3245.48,-1467.92 3253.17,-1440.02"/>
<polygon fill="#f5c15c" stroke="#f5c15c" stroke-width="1.4" points="3255.58,-1440.54 3253.97,-1433.3 3250.71,-1439.97 3255.58,-1440.54"/>
<g id="a_edge44"><a xlink:title="deliveryEditorAutomat → artikelAutomat (lookup SKU &amp; pack units)">
<path fill="none" stroke="#ff7fc2" stroke-width="1.4" d="M756.5,-1035.4C1377.87,-1035.4 1529.1,-1121.67 2149.76,-1151.4 2184.83,-1153.08 2748.39,-1148.47 2781.76,-1159.4 2978.56,-1223.84 2964.06,-1365.95 3160.93,-1430.2 3197.11,-1442.01 3245.48,-1467.92 3253.17,-1440.02"/>
<polygon fill="#ff7fc2" stroke="#ff7fc2" stroke-width="1.4" points="3255.58,-1440.54 3253.97,-1433.3 3250.71,-1439.97 3255.58,-1440.54"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2864.27" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#f5c15c"> &#160;lookup SKU &amp; pack units &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2864.27" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7fc2"> &#160;lookup SKU &amp; pack units &#160;</text>
</g>
<!-- deliveryEditorAutomat&#45;&gt;supplierAutomat -->
<g id="edge42" class="edge">
<g id="edge43" class="edge">
<title>deliveryEditorAutomat:w&#45;&gt;supplierAutomat:n</title>
<g id="a_edge42"><a xlink:title="deliveryEditorAutomat → supplierAutomat (match vendor catalog)">
<path fill="none" stroke="#6bdf9a" stroke-width="1.4" d="M483.02,-1013.4C324.2,-1013.4 129.3,-1211.16 119.88,-1069.67"/>
<polygon fill="#6bdf9a" stroke="#6bdf9a" stroke-width="1.4" points="122.26,-1067.44 119.59,-1060.52 117.36,-1067.59 122.26,-1067.44"/>
<g id="a_edge43"><a xlink:title="deliveryEditorAutomat → supplierAutomat (match vendor catalog)">
<path fill="none" stroke="#f5c15c" stroke-width="1.4" d="M483.02,-1013.4C324.2,-1013.4 129.3,-1211.16 119.88,-1069.67"/>
<polygon fill="#f5c15c" stroke="#f5c15c" stroke-width="1.4" points="122.26,-1067.44 119.59,-1060.52 117.36,-1067.59 122.26,-1067.44"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="394.31" y="-1048.07" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#6bdf9a"> &#160;match vendor catalog &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="394.31" y="-1048.07" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#f5c15c"> &#160;match vendor catalog &#160;</text>
</g>
<!-- deliveryEditorAutomat&#45;&gt;deliveryAutomat -->
<g id="edge44" class="edge">
<g id="edge45" class="edge">
<title>deliveryEditorAutomat:e&#45;&gt;deliveryAutomat:n</title>
<g id="a_edge44"><a xlink:title="deliveryEditorAutomat → deliveryAutomat (commit delivery receipt)">
<path fill="none" stroke="#ff7fc2" stroke-width="1.4" d="M756.5,-925.4C782.72,-925.4 767.95,-889.48 755.5,-866.4 711.73,-785.28 586.91,-837.21 578.67,-754.6"/>
<polygon fill="#ff7fc2" stroke="#ff7fc2" stroke-width="1.4" points="581.12,-754.59 578.34,-747.72 576.23,-754.83 581.12,-754.59"/>
<g id="a_edge45"><a xlink:title="deliveryEditorAutomat → deliveryAutomat (commit delivery receipt)">
<path fill="none" stroke="#c79bff" stroke-width="1.4" d="M756.5,-925.4C782.72,-925.4 767.95,-889.48 755.5,-866.4 711.73,-785.28 586.91,-837.21 578.67,-754.6"/>
<polygon fill="#c79bff" stroke="#c79bff" stroke-width="1.4" points="581.12,-754.59 578.34,-747.72 576.23,-754.83 581.12,-754.59"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="740.44" y="-811.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7fc2"> &#160;commit delivery receipt &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="740.44" y="-811.3" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#c79bff"> &#160;commit delivery receipt &#160;</text>
</g>
<!-- quoteAutomat&#45;&gt;orderAutomat -->
<g id="edge45" class="edge">
<g id="edge46" class="edge">
<title>quoteAutomat:w&#45;&gt;orderAutomat:n</title>
<g id="a_edge45"><a xlink:title="quoteAutomat → orderAutomat (convert quote to order)">
<path fill="none" stroke="#c79bff" stroke-width="1.4" d="M6474.28,-1704C6416.38,-1704 6470.58,-1510.46 6475.57,-1440.25"/>
<polygon fill="#c79bff" stroke="#c79bff" stroke-width="1.4" points="6478.01,-1440.41 6475.84,-1433.32 6473.12,-1440.21 6478.01,-1440.41"/>
<g id="a_edge46"><a xlink:title="quoteAutomat → orderAutomat (convert quote to order)">
<path fill="none" stroke="#5cd6d6" stroke-width="1.4" d="M6474.28,-1704C6416.38,-1704 6470.58,-1510.46 6475.57,-1440.25"/>
<polygon fill="#5cd6d6" stroke="#5cd6d6" stroke-width="1.4" points="6478.01,-1440.41 6475.84,-1433.32 6473.12,-1440.21 6478.01,-1440.41"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="6512.1" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#c79bff"> &#160;convert quote to order &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="6512.1" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5cd6d6"> &#160;convert quote to order &#160;</text>
</g>
<!-- orderAutomat&#45;&gt;entityAutomat -->
<g id="edge46" class="edge">
<g id="edge47" class="edge">
<title>orderAutomat:e&#45;&gt;entityAutomat:n</title>
<g id="a_edge46"><a xlink:title="orderAutomat → entityAutomat (customer lookup)">
<path fill="none" stroke="#5cd6d6" stroke-width="1.4" d="M6522.51,-1386.2C6675.1,-1386.2 6527.24,-1199.01 6521.7,-1045.09"/>
<polygon fill="#5cd6d6" stroke="#5cd6d6" stroke-width="1.4" points="6524.16,-1045.47 6521.58,-1038.52 6519.26,-1045.56 6524.16,-1045.47"/>
<g id="a_edge47"><a xlink:title="orderAutomat → entityAutomat (customer lookup)">
<path fill="none" stroke="#ff9f5a" stroke-width="1.4" d="M6522.51,-1386.2C6675.1,-1386.2 6527.24,-1199.01 6521.7,-1045.09"/>
<polygon fill="#ff9f5a" stroke="#ff9f5a" stroke-width="1.4" points="6524.16,-1045.47 6521.58,-1038.52 6519.26,-1045.56 6524.16,-1045.47"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="6584.05" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5cd6d6"> &#160;customer lookup &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="6584.05" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff9f5a"> &#160;customer lookup &#160;</text>
</g>
<!-- companyAutomat&#45;&gt;categoryAutomat -->
<g id="edge34" class="edge">
<g id="edge35" class="edge">
<title>companyAutomat:e&#45;&gt;categoryAutomat:n</title>
<g id="a_edge34"><a xlink:title="companyAutomat → categoryAutomat (rootCategoryId)">
<path fill="none" stroke="#4ecb8f" stroke-width="1.4" d="M2188.79,-1660C2239.3,-1660 2192.91,-1588.55 2229.76,-1554 2254.82,-1530.5 2323.71,-1545.83 2299.76,-1521.2 2277.71,-1498.52 2037.27,-1535.88 2015.22,-1513.2 1915.77,-1410.91 1922.28,-1300.44 2015.22,-1192.2 2094,-1100.46 3076.45,-1209.75 3124.37,-1100.05"/>
<polygon fill="#4ecb8f" stroke="#4ecb8f" stroke-width="1.4" points="3126.71,-1100.83 3125.75,-1093.47 3121.91,-1099.82 3126.71,-1100.83"/>
<g id="a_edge35"><a xlink:title="companyAutomat → categoryAutomat (rootCategoryId)">
<path fill="none" stroke="#ff7aa8" stroke-width="1.4" d="M2188.79,-1660C2239.3,-1660 2192.91,-1588.55 2229.76,-1554 2254.82,-1530.5 2323.71,-1545.83 2299.76,-1521.2 2277.71,-1498.52 2037.27,-1535.88 2015.22,-1513.2 1915.77,-1410.91 1922.28,-1300.44 2015.22,-1192.2 2094,-1100.46 3076.45,-1209.75 3124.37,-1100.05"/>
<polygon fill="#ff7aa8" stroke="#ff7aa8" stroke-width="1.4" points="3126.71,-1100.83 3125.75,-1093.47 3121.91,-1099.82 3126.71,-1100.83"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2049.99" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#4ecb8f"> &#160;rootCategoryId &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2049.99" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7aa8"> &#160;rootCategoryId &#160;</text>
</g>
<!-- companyAutomat&#45;&gt;customergroupAutomat -->
<g id="edge47" class="edge">
<g id="edge48" class="edge">
<title>companyAutomat:e&#45;&gt;customergroupAutomat:n</title>
<g id="a_edge47"><a xlink:title="companyAutomat → customergroupAutomat (poscustomergroup)">
<path fill="none" stroke="#ff9f5a" stroke-width="1.4" d="M2188.79,-1726C2247.6,-1726 2182.17,-1817.27 2229.76,-1851.82 2298.76,-1901.92 5214.65,-1857.16 5299.76,-1851.82 5498.47,-1839.34 6181.23,-1937.13 6201.85,-1746.97"/>
<polygon fill="#ff9f5a" stroke="#ff9f5a" stroke-width="1.4" points="6204.29,-1747.24 6202.22,-1740.12 6199.4,-1746.98 6204.29,-1747.24"/>
<g id="a_edge48"><a xlink:title="companyAutomat → customergroupAutomat (poscustomergroup)">
<path fill="none" stroke="#8f95ff" stroke-width="1.4" d="M2188.79,-1726C2247.6,-1726 2182.17,-1817.27 2229.76,-1851.82 2298.76,-1901.92 5214.65,-1857.16 5299.76,-1851.82 5498.47,-1839.34 6181.23,-1937.13 6201.85,-1746.97"/>
<polygon fill="#8f95ff" stroke="#8f95ff" stroke-width="1.4" points="6204.29,-1747.24 6202.22,-1740.12 6199.4,-1746.98 6204.29,-1747.24"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="3274.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff9f5a"> &#160;poscustomergroup &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="4824.76" y="-1885.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8f95ff"> &#160;poscustomergroup &#160;</text>
</g>
<!-- companyAutomat&#45;&gt;posterminalAutomat -->
<g id="edge49" class="edge">
<g id="edge50" class="edge">
<title>companyAutomat:e&#45;&gt;posterminalAutomat:n</title>
<g id="a_edge49"><a xlink:title="companyAutomat → posterminalAutomat (company context)">
<path fill="none" stroke="#ff6b6b" stroke-width="1.4" d="M2188.79,-1726C2256.44,-1726 2192.65,-1498.47 2187.56,-1418.23"/>
<polygon fill="#ff6b6b" stroke="#ff6b6b" stroke-width="1.4" points="2190.01,-1418.23 2187.32,-1411.32 2185.11,-1418.4 2190.01,-1418.23"/>
<g id="a_edge50"><a xlink:title="companyAutomat → posterminalAutomat (company context)">
<path fill="none" stroke="#58d18a" stroke-width="1.4" d="M2188.79,-1726C2256.44,-1726 2192.65,-1498.47 2187.56,-1418.23"/>
<polygon fill="#58d18a" stroke="#58d18a" stroke-width="1.4" points="2190.01,-1418.23 2187.32,-1411.32 2185.11,-1418.4 2190.01,-1418.23"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2243.51" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff6b6b"> &#160;company context &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2243.51" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#58d18a"> &#160;company context &#160;</text>
</g>
<!-- posterminalAutomat&#45;&gt;categoryAutomat -->
<g id="edge48" class="edge">
<g id="edge49" class="edge">
<title>posterminalAutomat:e&#45;&gt;categoryAutomat:n</title>
<g id="a_edge48"><a xlink:title="posterminalAutomat → categoryAutomat (filter enabled roots)">
<path fill="none" stroke="#8f95ff" stroke-width="1.4" d="M2374.78,-1364.2C2430.34,-1364.2 2401.8,-1287.17 2373.78,-1239.2 2341.47,-1183.87 2210.12,-1206.36 2253.71,-1159.4 2257.78,-1155.02 3063.46,-1104.14 3156.28,-1092.98"/>
<polygon fill="#8f95ff" stroke="#8f95ff" stroke-width="1.4" points="3156.44,-1095.44 3162.88,-1091.78 3155.56,-1090.62 3156.44,-1095.44"/>
<g id="a_edge49"><a xlink:title="posterminalAutomat → categoryAutomat (filter enabled roots)">
<path fill="none" stroke="#ff6b6b" stroke-width="1.4" d="M2374.78,-1364.2C2430.34,-1364.2 2401.8,-1287.17 2373.78,-1239.2 2341.47,-1183.87 2210.12,-1206.36 2253.71,-1159.4 2257.78,-1155.02 3063.46,-1104.14 3156.28,-1092.98"/>
<polygon fill="#ff6b6b" stroke="#ff6b6b" stroke-width="1.4" points="3156.44,-1095.44 3162.88,-1091.78 3155.56,-1090.62 3156.44,-1095.44"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2295.73" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8f95ff"> &#160;filter enabled roots &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2295.73" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff6b6b"> &#160;filter enabled roots &#160;</text>
</g>
<!-- chatAutomat&#45;&gt;sseAutomat -->
<g id="edge52" class="edge">
<g id="edge53" class="edge">
<title>chatAutomat:e&#45;&gt;sseAutomat:n</title>
<g id="a_edge52"><a xlink:title="chatAutomat → sseAutomat (getSseClientId())">
<path fill="none" stroke="#a98cff" stroke-width="1.4" d="M5200.85,-1331.2C5306.77,-1331.2 5343.03,-1727.56 5268.68,-1803 5249.74,-1822.22 5210.56,-1832.07 5201.93,-1812.84"/>
<polygon fill="#a98cff" stroke="#a98cff" stroke-width="1.4" points="5204.35,-1812.49 5200.62,-1806.08 5199.54,-1813.42 5204.35,-1812.49"/>
<g id="a_edge53"><a xlink:title="chatAutomat → sseAutomat (getSseClientId())">
<path fill="none" stroke="#ff8f66" stroke-width="1.4" d="M5200.85,-1331.2C5306.77,-1331.2 5343.03,-1727.56 5268.68,-1803 5249.74,-1822.22 5210.56,-1832.07 5201.93,-1812.84"/>
<polygon fill="#ff8f66" stroke="#ff8f66" stroke-width="1.4" points="5204.35,-1812.49 5200.62,-1806.08 5199.54,-1813.42 5204.35,-1812.49"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="5338.61" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#a98cff"> &#160;getSseClientId() &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="5338.61" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff8f66"> &#160;getSseClientId() &#160;</text>
</g>
<!-- chatAutomat&#45;&gt;chatAutomat -->
<g id="edge60" class="edge">
<g id="edge61" class="edge">
<title>chatAutomat:nw&#45;&gt;chatAutomat:n</title>
<g id="a_edge60"><a xlink:title="chatAutomat → chatAutomat (stream delta appends)">
<path fill="none" stroke="#66b3ff" stroke-width="1.4" d="M4907.66,-1364.2C4877,-1323.72 4831.66,-1323.72 4831.66,-1402.7 4831.66,-1479.05 5059.85,-1481.59 5141.9,-1445.1"/>
<polygon fill="#66b3ff" stroke="#66b3ff" stroke-width="1.4" points="5142.72,-1447.42 5147.92,-1442.14 5140.55,-1443.03 5142.72,-1447.42"/>
<g id="a_edge61"><a xlink:title="chatAutomat → chatAutomat (stream delta appends)">
<path fill="none" stroke="#5aa7ff" stroke-width="1.4" d="M4907.66,-1364.2C4877,-1323.72 4831.66,-1323.72 4831.66,-1402.7 4831.66,-1479.05 5059.85,-1481.59 5141.9,-1445.1"/>
<polygon fill="#5aa7ff" stroke="#5aa7ff" stroke-width="1.4" points="5142.72,-1447.42 5147.92,-1442.14 5140.55,-1443.03 5142.72,-1447.42"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="4783.38" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#66b3ff"> &#160;stream delta appends &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="4783.38" y="-1324" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#5aa7ff"> &#160;stream delta appends &#160;</text>
</g>
<!-- chatAutomat&#45;&gt;vfsAutomat -->
<g id="edge50" class="edge">
<g id="edge51" class="edge">
<title>chatAutomat:e&#45;&gt;vfsAutomat:n</title>
<g id="a_edge50"><a xlink:title="chatAutomat → vfsAutomat (inspect / edit applets)">
<path fill="none" stroke="#58d18a" stroke-width="1.4" d="M5200.85,-1353.2C5240.75,-1353.2 5328.67,-1411.59 5367.85,-1419.2 5422.45,-1429.81 5491.85,-1473.2 5501.6,-1431.54"/>
<polygon fill="#58d18a" stroke="#58d18a" stroke-width="1.4" points="5504.27,-1429.52 5502.54,-1422.31 5499.39,-1429.02 5504.27,-1429.52"/>
<g id="a_edge51"><a xlink:title="chatAutomat → vfsAutomat (inspect / edit applets)">
<path fill="none" stroke="#4fc4e8" stroke-width="1.4" d="M5200.85,-1353.2C5240.75,-1353.2 5328.67,-1411.59 5367.85,-1419.2 5422.45,-1429.81 5491.85,-1473.2 5501.6,-1431.54"/>
<polygon fill="#4fc4e8" stroke="#4fc4e8" stroke-width="1.4" points="5504.27,-1429.52 5502.54,-1422.31 5499.39,-1429.02 5504.27,-1429.52"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="5287.85" y="-1409.16" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#58d18a"> &#160;inspect / edit applets &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="5287.85" y="-1409.16" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#4fc4e8"> &#160;inspect / edit applets &#160;</text>
</g>
<!-- navigationAutomat -->
<g id="node29" class="node">
<g id="node30" class="node">
<title>navigationAutomat</title>
<polygon fill="#33415c" stroke="none" points="351.27,-1386.2 351.27,-1421.2 620.24,-1421.2 620.24,-1386.2 351.27,-1386.2"/>
<polygon fill="none" stroke="#3f4c5f" points="351.27,-1386.2 351.27,-1421.2 620.24,-1421.2 620.24,-1386.2 351.27,-1386.2"/>
@@ -1970,47 +2013,47 @@
<text xml:space="preserve" text-anchor="start" x="524.09" y="-1240" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Reset on company switch</text>
</g>
<!-- chatAutomat&#45;&gt;navigationAutomat -->
<g id="edge51" class="edge">
<g id="edge52" class="edge">
<title>chatAutomat:w&#45;&gt;navigationAutomat:n</title>
<g id="a_edge51"><a xlink:title="chatAutomat → navigationAutomat (set_user_route tool)">
<path fill="none" stroke="#4fc4e8" stroke-width="1.4" d="M4906.66,-1265.2C4774.13,-1265.2 4831.39,-1435.13 4710.76,-1490.02 4545.35,-1565.28 1621.34,-1497.15 1439.76,-1490.02 1245.11,-1482.37 573.44,-1613.88 552.5,-1429.23"/>
<polygon fill="#4fc4e8" stroke="#4fc4e8" stroke-width="1.4" points="554.95,-1429.17 552.11,-1422.32 550.06,-1429.44 554.95,-1429.17"/>
<g id="a_edge52"><a xlink:title="chatAutomat → navigationAutomat (set_user_route tool)">
<path fill="none" stroke="#a98cff" stroke-width="1.4" d="M4906.66,-1265.2C4774.13,-1265.2 4831.39,-1435.13 4710.76,-1490.02 4545.35,-1565.28 1621.34,-1497.15 1439.76,-1490.02 1245.11,-1482.37 573.44,-1613.88 552.5,-1429.23"/>
<polygon fill="#a98cff" stroke="#a98cff" stroke-width="1.4" points="554.95,-1429.17 552.11,-1422.32 550.06,-1429.44 554.95,-1429.17"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2942.76" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#4fc4e8"> &#160;set_user_route tool &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2942.76" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#a98cff"> &#160;set_user_route tool &#160;</text>
</g>
<!-- navigationAutomat&#45;&gt;categoryAutomat -->
<g id="edge54" class="edge">
<g id="edge55" class="edge">
<title>navigationAutomat:e&#45;&gt;categoryAutomat:n</title>
<g id="a_edge54"><a xlink:title="navigationAutomat → categoryAutomat (restore last category)">
<path fill="none" stroke="#4ecb8f" stroke-width="1.4" d="M621.24,-1331.2C990.23,-1331.2 1072.47,-1227.63 1439.76,-1192.2 1842.89,-1153.31 1946.61,-1196.65 2350.76,-1170.2 2370.01,-1168.94 3000.6,-1118.87 3079.35,-1095.66"/>
<polygon fill="#4ecb8f" stroke="#4ecb8f" stroke-width="1.4" points="3080.48,-1097.83 3085.52,-1092.39 3078.19,-1093.5 3080.48,-1097.83"/>
<g id="a_edge55"><a xlink:title="navigationAutomat → categoryAutomat (restore last category)">
<path fill="none" stroke="#ff7aa8" stroke-width="1.4" d="M621.24,-1331.2C990.23,-1331.2 1072.47,-1227.63 1439.76,-1192.2 1842.89,-1153.31 1946.61,-1196.65 2350.76,-1170.2 2370.01,-1168.94 3000.6,-1118.87 3079.35,-1095.66"/>
<polygon fill="#ff7aa8" stroke="#ff7aa8" stroke-width="1.4" points="3080.48,-1097.83 3085.52,-1092.39 3078.19,-1093.5 3080.48,-1097.83"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2520.29" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#4ecb8f"> &#160;restore last category &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2520.29" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7aa8"> &#160;restore last category &#160;</text>
</g>
<!-- navigationAutomat&#45;&gt;artikelAutomat -->
<g id="edge53" class="edge">
<g id="edge54" class="edge">
<title>navigationAutomat:e&#45;&gt;artikelAutomat:n</title>
<g id="a_edge53"><a xlink:title="navigationAutomat → artikelAutomat (restore last SKU)">
<path fill="none" stroke="#ff8f66" stroke-width="1.4" d="M621.24,-1353.2C990.07,-1353.2 1072.19,-1459.51 1439.76,-1490.02 1870.72,-1525.79 1980.61,-1506.16 2412.76,-1490.02 2584.7,-1483.59 3177.35,-1602.06 3198.13,-1440.16"/>
<polygon fill="#ff8f66" stroke="#ff8f66" stroke-width="1.4" points="3200.57,-1440.45 3198.56,-1433.31 3195.68,-1440.15 3200.57,-1440.45"/>
<g id="a_edge54"><a xlink:title="navigationAutomat → artikelAutomat (restore last SKU)">
<path fill="none" stroke="#4ecb8f" stroke-width="1.4" d="M621.24,-1353.2C990.07,-1353.2 1072.19,-1459.51 1439.76,-1490.02 1870.72,-1525.79 1980.61,-1506.16 2412.76,-1490.02 2584.7,-1483.59 3177.35,-1602.06 3198.13,-1440.16"/>
<polygon fill="#4ecb8f" stroke="#4ecb8f" stroke-width="1.4" points="3200.57,-1440.45 3198.56,-1433.31 3195.68,-1440.15 3200.57,-1440.45"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2666.76" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff8f66"> &#160;restore last SKU &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2666.76" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#4ecb8f"> &#160;restore last SKU &#160;</text>
</g>
<!-- navigationAutomat&#45;&gt;deliveryEditorAutomat -->
<g id="edge55" class="edge">
<g id="edge56" class="edge">
<title>navigationAutomat:e&#45;&gt;deliveryEditorAutomat:n</title>
<g id="a_edge55"><a xlink:title="navigationAutomat → deliveryEditorAutomat (restore draft path)">
<path fill="none" stroke="#ff7aa8" stroke-width="1.4" d="M621.24,-1375.2C749.32,-1375.2 625.58,-1218.7 619.95,-1089.28"/>
<polygon fill="#ff7aa8" stroke="#ff7aa8" stroke-width="1.4" points="622.41,-1089.46 619.8,-1082.52 617.51,-1089.57 622.41,-1089.46"/>
<g id="a_edge56"><a xlink:title="navigationAutomat → deliveryEditorAutomat (restore draft path)">
<path fill="none" stroke="#e0b25a" stroke-width="1.4" d="M621.24,-1375.2C749.32,-1375.2 625.58,-1218.7 619.95,-1089.28"/>
<polygon fill="#e0b25a" stroke="#e0b25a" stroke-width="1.4" points="622.41,-1089.46 619.8,-1082.52 617.51,-1089.57 622.41,-1089.46"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="673.66" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff7aa8"> &#160;restore draft path &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="673.66" y="-1162.1" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e0b25a"> &#160;restore draft path &#160;</text>
</g>
<!-- userSettingsAutomat -->
<g id="node30" class="node">
<g id="node31" class="node">
<title>userSettingsAutomat</title>
<polygon fill="#33415c" stroke="none" points="504.13,-1728 504.13,-1759 777.38,-1759 777.38,-1728 504.13,-1728"/>
<polygon fill="none" stroke="#3f4c5f" points="504.13,-1728 504.13,-1759 777.38,-1759 777.38,-1728 504.13,-1728"/>
@@ -2048,17 +2091,17 @@
<text xml:space="preserve" text-anchor="start" x="693.23" y="-1625.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Survives page reloads</text>
</g>
<!-- userSettingsAutomat&#45;&gt;chatAutomat -->
<g id="edge56" class="edge">
<g id="edge57" class="edge">
<title>userSettingsAutomat:e&#45;&gt;chatAutomat:n</title>
<g id="a_edge56"><a xlink:title="userSettingsAutomat → chatAutomat (chatDrawerWidth)">
<path fill="none" stroke="#e0b25a" stroke-width="1.4" d="M778.38,-1695C843.57,-1695 767.21,-1593.9 818.76,-1554 875.4,-1510.16 2036.11,-1522.22 2107.73,-1521.2 2180.03,-1520.17 4639.17,-1523.4 4710.76,-1513.2 4820.72,-1497.53 4950.39,-1553.21 4957.4,-1451.18"/>
<polygon fill="#e0b25a" stroke="#e0b25a" stroke-width="1.4" points="4959.84,-1451.39 4957.62,-1444.32 4954.94,-1451.23 4959.84,-1451.39"/>
<g id="a_edge57"><a xlink:title="userSettingsAutomat → chatAutomat (chatDrawerWidth)">
<path fill="none" stroke="#56c8c8" stroke-width="1.4" d="M778.38,-1695C843.57,-1695 767.21,-1593.9 818.76,-1554 875.4,-1510.16 2036.11,-1522.22 2107.73,-1521.2 2180.03,-1520.17 4639.17,-1523.4 4710.76,-1513.2 4820.72,-1497.53 4950.39,-1553.21 4957.4,-1451.18"/>
<polygon fill="#56c8c8" stroke="#56c8c8" stroke-width="1.4" points="4959.84,-1451.39 4957.62,-1444.32 4954.94,-1451.23 4959.84,-1451.39"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="2147.25" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#e0b25a"> &#160;chatDrawerWidth &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="2147.25" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#56c8c8"> &#160;chatDrawerWidth &#160;</text>
</g>
<!-- helpAutomat -->
<g id="node31" class="node">
<g id="node32" class="node">
<title>helpAutomat</title>
<polygon fill="#33415c" stroke="none" points="141.51,-1728 141.51,-1759 420.01,-1759 420.01,-1728 141.51,-1728"/>
<polygon fill="none" stroke="#3f4c5f" points="141.51,-1728 141.51,-1759 420.01,-1759 420.01,-1728 141.51,-1728"/>
@@ -2096,17 +2139,17 @@
<text xml:space="preserve" text-anchor="start" x="335.85" y="-1625.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Survives page reloads</text>
</g>
<!-- helpAutomat&#45;&gt;navigationAutomat -->
<g id="edge57" class="edge">
<g id="edge58" class="edge">
<title>helpAutomat:e&#45;&gt;navigationAutomat:n</title>
<g id="a_edge57"><a xlink:title="helpAutomat → navigationAutomat (route&#45;contextual help)">
<path fill="none" stroke="#56c8c8" stroke-width="1.4" d="M421.01,-1695C479.45,-1695 424.66,-1499.24 419.85,-1428.89"/>
<polygon fill="#56c8c8" stroke="#56c8c8" stroke-width="1.4" points="422.31,-1429.22 419.6,-1422.32 417.42,-1429.41 422.31,-1429.22"/>
<g id="a_edge58"><a xlink:title="helpAutomat → navigationAutomat (route&#45;contextual help)">
<path fill="none" stroke="#ff5f9e" stroke-width="1.4" d="M421.01,-1695C479.45,-1695 424.66,-1499.24 419.85,-1428.89"/>
<polygon fill="#ff5f9e" stroke="#ff5f9e" stroke-width="1.4" points="422.31,-1429.22 419.6,-1422.32 417.42,-1429.41 422.31,-1429.22"/>
</a>
</g>
<text xml:space="preserve" text-anchor="middle" x="482.6" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#56c8c8"> &#160;route&#45;contextual help &#160;</text>
<text xml:space="preserve" text-anchor="middle" x="482.6" y="-1523.9" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#ff5f9e"> &#160;route&#45;contextual help &#160;</text>
</g>
<!-- pwaAutomat -->
<g id="node32" class="node">
<g id="node33" class="node">
<title>pwaAutomat</title>
<polygon fill="#33415c" stroke="none" points="861.11,-1728 861.11,-1759 1122.4,-1759 1122.4,-1728 861.11,-1728"/>
<polygon fill="none" stroke="#3f4c5f" points="861.11,-1728 861.11,-1759 1122.4,-1759 1122.4,-1728 861.11,-1728"/>
@@ -2144,7 +2187,7 @@
<text xml:space="preserve" text-anchor="start" x="1065.38" y="-1625.8" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#8b95a7">Modal controls</text>
</g>
<!-- Legend -->
<g id="node33" class="node">
<g id="node34" class="node">
<title>Legend</title>
<polygon fill="#2a3242" stroke="none" points="1206.46,-1591.7 1206.46,-1785.3 1421.06,-1785.3 1421.06,-1591.7 1206.46,-1591.7"/>
<polygon fill="#33415c" stroke="none" points="1206.46,-1762.1 1206.46,-1785.3 1421.06,-1785.3 1421.06,-1762.1 1206.46,-1762.1"/>

Before

Width:  |  Height:  |  Size: 255 KiB

After

Width:  |  Height:  |  Size: 260 KiB

View File

@@ -106,7 +106,7 @@ const CLUSTERS = [
{
id: 'audit_grids',
label: 'Audit & Virtual Grids',
automats: ['articleHistoryGridAutomat', 'globalHistoryGridAutomat'],
automats: ['articleHistoryGridAutomat', 'globalHistoryGridAutomat', 'ledgerHistoryGridAutomat'],
},
{
id: 'system_ui',
@@ -520,6 +520,17 @@ const AUTOMAT_SPECS = [
{ type: 'API', name: 'GET /api/globalhistory', desc: 'Company global audit' },
],
},
{
id: 'ledgerHistoryGridAutomat',
title: 'ledgerHistoryGridAutomat',
subtitle: 'createGridAutomat (Ledger)',
entries: [
{ type: 'STATE', name: 'rows: [] / totalRows', desc: 'Block size 50 virtual rows' },
{ type: 'ACTION', name: 'fetchRows(viewport)', desc: 'Infinite scroll query' },
{ type: 'ACTION', name: 'reload()', desc: 'Reload on company switch' },
{ type: 'API', name: 'GET /api/ledger/history', desc: 'Append-only ledger audit' },
],
},
// 12. UI, Settings & System
{
@@ -605,6 +616,7 @@ const EDGE_DEFS = [
{ from: 'authAutomat', to: 'vfsAutomat', label: 'load company vfs', port: 'p_auth_status' },
{ from: 'authAutomat', to: 'articleHistoryGridAutomat', label: 'reload history on authed', port: 'p_auth_status' },
{ from: 'authAutomat', to: 'globalHistoryGridAutomat', label: 'reload audit on authed', port: 'p_auth_status' },
{ from: 'authAutomat', to: 'ledgerHistoryGridAutomat', label: 'reload ledger on authed', port: 'p_auth_status' },
// Real-time SSE dispatch
{ from: 'sseAutomat', to: 'categoryAutomat', label: '\'category\' sse trigger', port: 'p_sse_events' },

View File

@@ -7,6 +7,30 @@ import { http } from './http.js';
*/
export const ledger = {
/**
* GET /api/ledger/history?offset=0&limit=50&search=...&seekMonths=-1
* Append-only ledger audit history with chunk paging, search, and seek filtering
* @param {{ offset?: number, limit?: number, search?: string, seekMonths?: number, sortModel?: Array, filterModel?: object, sku?: string, transactionType?: string }} [params]
* → { ledger: LedgerEntry[], history?: LedgerEntry[], total: number, unfilteredTotal?: number }
*/
// #region GET /api/ledger/history — getHistory
getHistory({ offset = 0, limit = 50, search, seekMonths, sortModel, filterModel, sku, transactionType } = {}) {
const params = new URLSearchParams();
params.set('offset', String(Math.max(0, Math.floor(Number(offset) || 0))));
params.set('limit', String(Math.max(1, Math.floor(Number(limit) || 50))));
if (search && String(search).trim()) params.set('search', String(search).trim());
if (seekMonths != null && Number(seekMonths) < 0) {
params.set('seekMonths', String(Math.floor(Number(seekMonths))));
}
if (sku != null) params.set('sku', String(sku));
if (transactionType != null) params.set('transaction_type', String(transactionType));
if (sortModel && sortModel.length) params.set('sort', JSON.stringify(sortModel));
if (filterModel && Object.keys(filterModel).length) params.set('filter', JSON.stringify(filterModel));
const qs = params.toString();
return http(`/api/ledger/history${qs ? `?${qs}` : ''}`);
},
// #endregion
/**
* GET /api/ledger/manifestarticle/:manifestarticleId
* Query: ?transaction_type=...

View File

@@ -34,6 +34,7 @@ import OAuthAuthorizePage from './routes/oauth/OAuthAuthorizePage';
const ArticleHistoryPage = lazy(() => import('./routes/infos/ArticleHistoryPage'));
const GlobalHistoryPage = lazy(() => import('./routes/infos/GlobalHistoryPage'));
const LedgerHistoryPage = lazy(() => import('./routes/infos/LedgerHistoryPage'));
function LoginRedirect() {
const params = new URLSearchParams(window.location.search);
@@ -72,6 +73,7 @@ const routes = [
{ index: true, element: <ArticleHistoryPage /> },
{ path: 'articlehistory', element: <ArticleHistoryPage /> },
{ path: 'globalhistory', element: <GlobalHistoryPage /> },
{ path: 'ledgerhistory', element: <LedgerHistoryPage /> },
{ path: 'statistics', element: <StatisticsPage /> },
],
},

View File

@@ -391,6 +391,8 @@ export function createChunkListAutomat({
? res.items
: Array.isArray(res?.history)
? res.history
: Array.isArray(res?.ledger)
? res.ledger
: Array.isArray(res?.rows)
? res.rows
: Array.isArray(res?.entries)

View File

@@ -191,6 +191,7 @@ export {
export { default as articleHistoryGridAutomat } from './articleHistoryAutomat';
export { default as globalHistoryAutomat, globalHistoryGridAutomat } from './globalHistoryAutomat';
export { default as ledgerHistoryAutomat, ledgerHistoryGridAutomat } from './ledgerHistoryAutomat';
export { default as chatAutomat } from './chatAutomat';
export {

View File

@@ -0,0 +1,26 @@
/**
* @file ledgerHistoryAutomat.js
* @description Double-buffered chunk list automat (`ledgerHistoryAutomat`) for
* append-only ledger transactions and stock journal with multi-filter caching and LRU eviction.
*
* Upstream Triggers:
* - `authAutomat`: Subscribes to auth state changes — resets on `'anon'` or when
* `selectedCompanyId` changes.
* - Consumer UI: Calls `getItems()`, `requireChunk(start, end)`, `filter(search)`,
* `addSeekMonth()`, `addSeekYear()`, `resetNegativeSeek()`.
*/
import { createChunkListAutomat } from './createChunkListAutomat';
import { ledger } from '../api';
export const ledgerHistoryAutomat = createChunkListAutomat({
name: 'ledgerHistory',
chunkSize: 50,
maxLruChunks: 50,
fetchPage: (params) => ledger.getHistory(params),
});
// Alias for consistency with other grid/chunk automats
export const ledgerHistoryGridAutomat = ledgerHistoryAutomat;
export default ledgerHistoryAutomat;

View File

@@ -102,7 +102,7 @@ export default class BookDeliveryDialog extends PureComponent {
const articles = res?.articles || res?.supplier?.articles || [];
const map = buildSupplierArticlesMap(articles);
this.setState({ supplierArticlesMap: map });
}).catch(() => {});
}).catch(() => { });
}
}
@@ -122,7 +122,7 @@ export default class BookDeliveryDialog extends PureComponent {
this.unsubArticle = artikelAutomat.subscribe(() => {
this.forceUpdate();
});
void warehouseAutomat.actions.load().catch(() => {});
void warehouseAutomat.actions.load().catch(() => { });
this.loadSupplierArticlesIfNeeded();
if (this.props.open) {
@@ -225,7 +225,7 @@ export default class BookDeliveryDialog extends PureComponent {
supplierArticlesMap: currentSupplierMap,
},
() => {
void warehouseAutomat.actions.load().catch(() => {});
void warehouseAutomat.actions.load().catch(() => { });
this.loadSupplierArticlesIfNeeded();
if (this.props.summary) {
this.initAmountsFromSummary(this.props.summary, this.props.prefillLineId);
@@ -535,7 +535,7 @@ export default class BookDeliveryDialog extends PureComponent {
(line.sku ? String(line.sku).trim() : null);
if (targetSku && !cachedArticle) {
void artikelAutomat.actions.loadArticle(targetSku).catch(() => {});
void artikelAutomat.actions.loadArticle(targetSku).catch(() => { });
}
const isTracked =
@@ -791,11 +791,7 @@ export default class BookDeliveryDialog extends PureComponent {
</Alert>
)}
{isFullyBooked ? (
<Alert severity="success" icon={<CheckCircleOutlineOutlinedIcon />}>
Alle Positionen dieser Lieferung sind bereits vollständig im Ledger verbucht.
</Alert>
) : isWarehouseMissing ? (
{isFullyBooked ? null : isWarehouseMissing ? (
<Alert severity="warning">
Bitte wählen Sie ein <strong>Ziellager</strong> aus, um die lagergeführten Artikel zu buchen.
</Alert>

View File

@@ -22,6 +22,7 @@ import LabelIcon from '@mui/icons-material/Label';
import HistoryIcon from '@mui/icons-material/History';
import PublicIcon from '@mui/icons-material/Public';
import ReceiptLongIcon from '@mui/icons-material/ReceiptLong';
import BarChartIcon from '@mui/icons-material/BarChart';
import { isKategorienPath } from './components/category/categoryRoute';
import { deliveryEditorAutomat } from './automats/deliveryEditorAutomat';
@@ -273,6 +274,13 @@ export const SUB_NAV = {
icon: PublicIcon,
help: 'Globale System- und Änderungshistorie',
},
{
segment: 'ledgerhistory',
label: 'LedgerHistory',
title: 'LedgerHistory',
icon: ReceiptLongIcon,
help: 'Unveränderliches Buchungs- und Bestandsjournal (Audit-Ledger)',
},
{
segment: 'statistics',
label: 'Statistics',

View File

@@ -0,0 +1,629 @@
import { PureComponent, createRef } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import ListItemButton from '@mui/material/ListItemButton';
import Typography from '@mui/material/Typography';
import Skeleton from '@mui/material/Skeleton';
import RefreshIcon from '@mui/icons-material/Refresh';
import ViewModuleIcon from '@mui/icons-material/ViewModule';
import Chip from '@mui/material/Chip';
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
import FillSpace from 'dashgui/FillSpace';
import { PanelTile, PanelTileGrid } from 'dashgui/panels';
import BackendError from '../../components/BackendError';
import ledgerHistoryAutomat from '../../automats/ledgerHistoryAutomat';
import { router } from '../../appRouter';
import { backendErrorFrom } from '../../utils/backendError';
import { LINK } from '../../colors';
const CHUNK_SIZE = 50;
function formatDateTime(val) {
if (!val) return '–';
try {
const d = new Date(val);
if (isNaN(d.getTime())) return String(val);
return d.toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
} catch {
return String(val);
}
}
export function createDummyTestLedgerHistory() {
return {
id: 999999,
date: '2030-10-30T12:00:00Z',
created_at: '2030-10-30T12:00:00Z',
transaction_type: 'ACCEPTANCE',
sku: 'SKU-99999999',
amount: 9999,
transaction_text: 'Wareneingang Lieferschein LS-2030-999999 Musterbuchung',
user: 'Max Mustermann',
};
}
const rowGridSx = {
display: 'grid',
gridTemplateColumns:
'minmax(60px, max-content) minmax(150px, max-content) minmax(110px, max-content) minmax(130px, max-content) minmax(90px, max-content) minmax(220px, 2fr) minmax(120px, max-content)',
gridTemplateRows: '0px 1fr',
rowGap: 0,
columnGap: 2,
alignItems: 'center',
width: '100%',
boxSizing: 'border-box',
};
const ellipsisSx = { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
const measuringCellSx = {
visibility: 'hidden',
height: 0,
boxSizing: 'border-box',
pointerEvents: 'none',
textTransform: 'none',
letterSpacing: 'normal',
};
const headerCellSx = {
color: 'text.secondary',
typography: 'caption',
fontWeight: 600,
textTransform: 'uppercase',
letterSpacing: '0.04em',
userSelect: 'none',
};
function renderInvisibleDummyRow(dummy) {
const d = dummy || createDummyTestLedgerHistory();
return (
<>
<Box aria-hidden sx={{ gridColumn: 1, gridRow: 1, whiteSpace: 'nowrap', ...measuringCellSx }}>
<Typography variant="body2">{d.id}</Typography>
</Box>
<Box aria-hidden sx={{ gridColumn: 2, gridRow: 1, whiteSpace: 'nowrap', ...measuringCellSx }}>
<Typography variant="body2">{formatDateTime(d.created_at || d.date)}</Typography>
</Box>
<Box aria-hidden sx={{ gridColumn: 3, gridRow: 1, whiteSpace: 'nowrap', ...measuringCellSx }}>
<Typography variant="body2">{d.transaction_type}</Typography>
</Box>
<Box aria-hidden sx={{ gridColumn: 4, gridRow: 1, whiteSpace: 'nowrap', ...measuringCellSx }}>
<Typography variant="body2">{d.sku}</Typography>
</Box>
<Box aria-hidden sx={{ gridColumn: 5, gridRow: 1, whiteSpace: 'nowrap', ...measuringCellSx }}>
<Typography variant="body2">+{d.amount}</Typography>
</Box>
<Box aria-hidden sx={{ gridColumn: 6, gridRow: 1, whiteSpace: 'nowrap', ...measuringCellSx }}>
<Typography variant="body2">{d.transaction_text}</Typography>
</Box>
<Box aria-hidden sx={{ gridColumn: 7, gridRow: 1, whiteSpace: 'nowrap', ...measuringCellSx }}>
<Typography variant="body2">{d.user}</Typography>
</Box>
</>
);
}
function getTransactionTypeLabel(typeStr) {
const norm = String(typeStr || '').toUpperCase();
if (norm === 'ACCEPTANCE') return 'Annahme';
if (norm === 'WAREHOUSE') return 'Lager';
if (norm === 'ADJUSTMENT' || norm === 'STOCK_ADJUST') return 'Korrektur';
if (norm === 'SALE' || norm === 'OUTBOUND') return 'Verkauf';
if (norm === 'RETURN') return 'Retoure';
if (norm === 'DELIVERY') return 'Lieferung';
if (norm === 'INVENTORY') return 'Inventur';
return typeStr || '–';
}
class LedgerHistoryRow extends PureComponent {
render() {
const { item, disabled, headerItem } = this.props;
const dummy = headerItem || createDummyTestLedgerHistory();
if (!item) {
return (
<ListItemButton
disabled
divider
sx={{ ...rowGridSx, height: 56, minHeight: 56, maxHeight: 56, px: 2, py: 0 }}
>
{renderInvisibleDummyRow(dummy)}
<Box sx={{ gridColumn: 1, gridRow: 2 }}>
<Skeleton variant="text" width="80%" height={20} />
</Box>
<Box sx={{ gridColumn: 2, gridRow: 2 }}>
<Skeleton variant="text" width="90%" height={20} />
</Box>
<Box sx={{ gridColumn: 3, gridRow: 2 }}>
<Skeleton variant="rounded" width={70} height={22} />
</Box>
<Box sx={{ gridColumn: 4, gridRow: 2 }}>
<Skeleton variant="text" width="80%" height={20} />
</Box>
<Box sx={{ gridColumn: 5, gridRow: 2 }}>
<Skeleton variant="text" width="70%" height={20} />
</Box>
<Box sx={{ gridColumn: 6, gridRow: 2 }}>
<Skeleton variant="text" width="90%" height={20} />
</Box>
<Box sx={{ gridColumn: 7, gridRow: 2 }}>
<Skeleton variant="text" width="60%" height={20} />
</Box>
</ListItemButton>
);
}
const userName =
item.user ||
item.employee ||
item.created_by ||
(item.companyuserId ? `User #${item.companyuserId}` : '—');
const typeRaw = item.transaction_type || item.type || '–';
const typeLabel = getTransactionTypeLabel(typeRaw);
const sku = item.sku || null;
const hasSku = Boolean(sku && sku !== '–');
const amt = Number(item.amount);
const hasAmount = item.amount != null && !isNaN(amt);
const isPos = hasAmount && amt > 0;
const isNeg = hasAmount && amt < 0;
const amountStr = hasAmount ? (isPos ? `+${amt}` : `${amt}`) : (item.amount ?? '–');
const bookingText = item.transaction_text || item.text || item.reason || '–';
return (
<ListItemButton
divider
disabled={disabled}
sx={{ ...rowGridSx, height: 56, minHeight: 56, maxHeight: 56, px: 2, py: 0 }}
onClick={
hasSku
? () => {
void router.navigate(`/artikel/${encodeURIComponent(sku)}`);
}
: undefined
}
>
{renderInvisibleDummyRow(dummy)}
{/* 1. ID */}
<Box sx={{ gridColumn: 1, gridRow: 2, minWidth: 0 }}>
<Typography
variant="body2"
color="text.secondary"
title={item.prev_hash ? `Hash-Version: ${item.hash_version ?? 1}\nVorheriger Hash: ${item.prev_hash}` : undefined}
sx={{ fontVariantNumeric: 'tabular-nums', ...ellipsisSx }}
>
{item.id}
</Typography>
</Box>
{/* 2. Datum */}
<Box sx={{ gridColumn: 2, gridRow: 2, minWidth: 0 }}>
<Typography variant="body2" color="text.secondary" sx={{ fontVariantNumeric: 'tabular-nums', ...ellipsisSx }}>
{formatDateTime(item.created_at || item.date || item.createdAt)}
</Typography>
</Box>
{/* 3. Typ */}
<Box sx={{ gridColumn: 3, gridRow: 2, minWidth: 0 }}>
<Chip
size="small"
label={typeLabel}
sx={{
height: 22,
fontSize: '0.75rem',
fontWeight: 500,
bgcolor: 'action.hover',
textTransform: 'capitalize',
}}
/>
</Box>
{/* 4. SKU */}
<Box sx={{ gridColumn: 4, gridRow: 2, minWidth: 0 }}>
{hasSku ? (
<Typography
variant="body2"
component="a"
href={`/artikel/${encodeURIComponent(sku)}`}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
void router.navigate(`/artikel/${encodeURIComponent(sku)}`);
}}
sx={{
color: LINK,
fontWeight: 600,
textDecoration: 'none',
...ellipsisSx,
'&:hover': { textDecoration: 'underline' },
}}
>
{sku}
</Typography>
) : (
<Typography variant="body2" color="text.secondary" sx={ellipsisSx}>
–
</Typography>
)}
</Box>
{/* 5. Menge / Betrag */}
<Box sx={{ gridColumn: 5, gridRow: 2, minWidth: 0, textAlign: 'right', pr: 1 }}>
<Typography
variant="body2"
sx={{
fontWeight: 600,
fontVariantNumeric: 'tabular-nums',
color: isPos ? 'success.main' : isNeg ? 'error.main' : 'text.primary',
...ellipsisSx,
}}
>
{amountStr}
</Typography>
</Box>
{/* 6. Buchungstext */}
<Box sx={{ gridColumn: 6, gridRow: 2, minWidth: 0 }}>
<Typography variant="body2" title={bookingText} sx={ellipsisSx}>
{bookingText}
</Typography>
</Box>
{/* 7. Benutzer */}
<Box sx={{ gridColumn: 7, gridRow: 2, minWidth: 0 }}>
<Typography variant="body2" color="text.secondary" sx={ellipsisSx}>
{userName}
</Typography>
</Box>
</ListItemButton>
);
}
}
class LedgerHistorySearchField extends PureComponent {
constructor(props) {
super(props);
const s = ledgerHistoryAutomat.state;
const stats = ledgerHistoryAutomat.getChunkStats?.();
this.state = {
input: s.filter ?? '',
seekMonths: s.seekMonths ?? 0,
seekYears: s.seekYears ?? 0,
lruChunksCount: stats?.lruChunksCount || 0,
maxLruChunks: stats?.maxLruChunks || 50,
};
}
componentDidMount() {
this.unsub = ledgerHistoryAutomat.subscribe(this, (state) => ({
input: state.filter ?? '',
seekMonths: state.seekMonths ?? 0,
seekYears: state.seekYears ?? 0,
}));
if (import.meta.env.DEV) {
this.unsubStats = ledgerHistoryAutomat.subscribeChunkStats?.((stats) => {
this.setState({
lruChunksCount: stats.lruChunksCount,
maxLruChunks: stats.maxLruChunks,
});
});
}
}
componentWillUnmount() {
this.unsub?.();
this.unsubStats?.();
}
render() {
const { inputRef, onReload, loading } = this.props;
const { input, seekMonths, seekYears } = this.state;
const monthActive = seekMonths > 0;
const monthLabel = !monthActive ? '-1 Month' : `-${seekMonths} Month${seekMonths > 1 ? 's' : ''}`;
const yearActive = seekYears > 0;
const yearLabel = !yearActive ? '-1 Year' : `-${seekYears} Year${seekYears > 1 ? 's' : ''}`;
return (
<PanelTileGrid>
<Box
ref={inputRef}
sx={{
gridColumn: { xs: '1 / -1', sm: 'span 2', md: 'span 2' },
display: 'flex',
flexDirection: 'column',
gap: 1.25,
pb: 2,
}}
>
{/* Top row: search field */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SingleLineTextEdit
sx={{ flex: 1 }}
size="small"
search
clear
value={input}
placeholder="Artikel-Nr., Buchungstext, Typ oder Benutzer suchen…"
onChange={(next) => ledgerHistoryAutomat.actions.filter(next)}
/>
<Button
size="small"
variant="outlined"
startIcon={<RefreshIcon />}
onClick={onReload}
disabled={loading}
sx={{ height: 40, px: 2, whiteSpace: 'nowrap' }}
>
Aktualisieren
</Button>
</Box>
{/* Filter Bar: Negativeseek (-1 Month, -1 Year, Reset) */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
flexWrap: 'wrap',
gap: 1.5,
}}
>
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.75,
px: 1.25,
py: 0.25,
borderRadius: 2,
bgcolor: 'background.paper',
boxShadow: 1,
}}
>
<Button
size="small"
variant={monthActive ? 'contained' : 'outlined'}
color={monthActive ? 'primary' : 'inherit'}
onClick={() => ledgerHistoryAutomat.actions.addSeekMonth()}
sx={{
minWidth: 88,
px: 1.25,
py: 0.25,
height: 28,
fontSize: '0.75rem',
fontWeight: 600,
textTransform: 'none',
boxSizing: 'border-box',
border: '1px solid',
borderColor: monthActive ? 'primary.main' : 'divider',
whiteSpace: 'nowrap',
}}
>
{monthLabel}
</Button>
<Button
size="small"
variant={yearActive ? 'contained' : 'outlined'}
color={yearActive ? 'primary' : 'inherit'}
onClick={() => ledgerHistoryAutomat.actions.addSeekYear()}
sx={{
minWidth: 80,
px: 1.25,
py: 0.25,
height: 28,
fontSize: '0.75rem',
fontWeight: 600,
textTransform: 'none',
boxSizing: 'border-box',
border: '1px solid',
borderColor: yearActive ? 'primary.main' : 'divider',
whiteSpace: 'nowrap',
}}
>
{yearLabel}
</Button>
{(monthActive || yearActive) && (
<Button
size="small"
variant="text"
color="inherit"
onClick={() => ledgerHistoryAutomat.actions.resetNegativeSeek()}
sx={{
px: 1,
py: 0.25,
height: 28,
fontSize: '0.75rem',
fontWeight: 600,
textTransform: 'none',
color: 'text.secondary',
whiteSpace: 'nowrap',
}}
>
Zurücksetzen
</Button>
)}
</Box>
{/* DEV: Chunk-Cache Debug Monitor trigger */}
{import.meta.env.DEV && (
<Chip
size="small"
variant="outlined"
icon={<ViewModuleIcon sx={{ fontSize: '1rem !important' }} />}
label={
this.state.lruChunksCount > 0
? `Chunk-Cache (${this.state.lruChunksCount}/${this.state.maxLruChunks || 50})`
: 'Chunk-Cache'
}
onClick={() => window.dispatchEvent(new CustomEvent('dash:toggle-chunk-monitor'))}
sx={{
cursor: 'pointer',
height: 28,
fontWeight: 600,
fontSize: '0.75rem',
bgcolor: 'background.paper',
boxShadow: 1,
'&:hover': { bgcolor: 'action.hover' },
}}
/>
)}
</Box>
</Box>
</PanelTileGrid>
);
}
}
// MARK: calls getHistory from ./api/ledger.js via ledgerHistoryAutomat
export default class LedgerHistoryPage extends PureComponent {
constructor(props) {
super(props);
this.headerItem = createDummyTestLedgerHistory();
this.state = {
items: ledgerHistoryAutomat.getItems(),
unfilteredTotal: ledgerHistoryAutomat.state.unfilteredTotal ?? 0,
filterKey: ledgerHistoryAutomat.getFilterKey?.() ?? '',
status: ledgerHistoryAutomat.state.status,
error: ledgerHistoryAutomat.state.error,
};
this.searchRef = createRef();
this.handleReload = this.handleReload.bind(this);
this.handleRetry = this.handleRetry.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
}
componentDidMount() {
this.unsub = ledgerHistoryAutomat.subscribe(this, (state) => ({
items: state.items,
unfilteredTotal: state.unfilteredTotal ?? 0,
filterKey: ledgerHistoryAutomat.getFilterKey(state),
status: state.status,
error: state.error,
}));
}
componentWillUnmount() {
this.unsub?.();
}
handlePageChange(page, meta) {
const pageSize = meta?.pageSize ?? 10;
const startIndex = (page - 1) * pageSize;
const items = this.state.items;
const maxIndex = items ? items.length - 1 : startIndex;
const endIndex = Math.min(maxIndex, startIndex + pageSize - 1);
const startChunk = Math.floor(startIndex / CHUNK_SIZE);
const endChunk = Math.floor(endIndex / CHUNK_SIZE);
ledgerHistoryAutomat.requireChunk(startChunk, endChunk);
}
handleReload() {
void ledgerHistoryAutomat.actions.reload().catch(() => {});
}
handleRetry() {
void ledgerHistoryAutomat.actions.requireChunk(0, 0, { immediate: true }).catch(() => {});
}
render() {
const { items, unfilteredTotal, filterKey, status, error } = this.state;
const loading = status === 'loading';
if (items === null && status === 'failed') {
return (
<>
<LedgerHistorySearchField
inputRef={this.searchRef}
onReload={this.handleReload}
loading={loading}
/>
<PanelTileGrid>
<PanelTile wide={3} minHeight={0} sx={{ p: 2 }}>
<BackendError error={backendErrorFrom(error)} onRetry={this.handleRetry} />
</PanelTile>
</PanelTileGrid>
</>
);
}
return (
<>
<LedgerHistorySearchField
inputRef={this.searchRef}
onReload={this.handleReload}
loading={loading}
/>
<PanelTileGrid>
<PanelTile wide={3} minHeight={0} sx={{ pb: 0, position: 'relative' }}>
<FillSpace
aboveRef={[this.searchRef]}
reserveBottom={32}
minPageSize={8}
minHeight={0}
resetKey={filterKey}
items={items || []}
totalCount={items ? items.length : 0}
unfilteredTotal={unfilteredTotal}
headerItem={this.headerItem}
renderItem={LedgerHistoryRow}
virtualScrollbar
header={
<Box
sx={{
...rowGridSx,
height: 36,
minHeight: 36,
px: 2,
borderBottom: '1px solid',
borderColor: 'divider',
}}
>
{renderInvisibleDummyRow(this.headerItem)}
<Box sx={{ gridColumn: 1, gridRow: 2, ...headerCellSx }}>ID</Box>
<Box sx={{ gridColumn: 2, gridRow: 2, ...headerCellSx }}>Datum</Box>
<Box sx={{ gridColumn: 3, gridRow: 2, ...headerCellSx }}>Typ</Box>
<Box sx={{ gridColumn: 4, gridRow: 2, ...headerCellSx }}>Artikel-Nr.</Box>
<Box sx={{ gridColumn: 5, gridRow: 2, ...headerCellSx, textAlign: 'right', pr: 1 }}>Menge</Box>
<Box sx={{ gridColumn: 6, gridRow: 2, ...headerCellSx }}>Buchungstext</Box>
<Box sx={{ gridColumn: 7, gridRow: 2, ...headerCellSx }}>Benutzer</Box>
</Box>
}
onPageChange={this.handlePageChange}
/>
{items === null && (
<Box
sx={{
position: 'absolute',
top: 52,
left: 0,
right: 0,
bottom: 32,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none',
zIndex: 2,
}}
>
<CircularProgress size={32} />
</Box>
)}
</PanelTile>
</PanelTileGrid>
</>
);
}
}

View File

@@ -1,3 +1,4 @@
export { default as ArticleHistoryPage } from './ArticleHistoryPage';
export { default as GlobalHistoryPage } from './GlobalHistoryPage';
export { default as LedgerHistoryPage } from './LedgerHistoryPage';
export { default as StatisticsPage } from './StatisticsPage';

View File

@@ -290,6 +290,7 @@ const server = http.createServer(async (req, res) => {
if (ledgerSku) return json(res, 200, { ledger: [] });
const ledgerMa = p.match(/^\/api\/ledger\/manifestarticle\/(\d+)$/);
if (ledgerMa) return json(res, 200, { ledger: [] });
if (p === '/api/ledger/history') return json(res, 200, { ledger: misc.ledgerHistory ?? [], total: 0 });
if (p === '/api/ledger') return json(res, 200, { ledger: [] });
if (p === '/api/article/search') {
const q = (url.searchParams.get('target') ?? '').toLowerCase();