Changeset 3738724
- Timestamp:
- 10/10/2026 09:28:56 PM (3 weeks ago)
- Location:
- dtpm-ai-sale-assistant-woocommerce
- Files:
-
- 39 added
- 7 edited
-
tags/1.1.3 (added)
-
tags/1.1.3/LICENSE (added)
-
tags/1.1.3/admin (added)
-
tags/1.1.3/admin/assets (added)
-
tags/1.1.3/admin/assets/banner-dashboard.svg (added)
-
tags/1.1.3/admin/availability-sync.php (added)
-
tags/1.1.3/admin/category-analysis-page.php (added)
-
tags/1.1.3/admin/causal-chain-page.php (added)
-
tags/1.1.3/admin/content-page.php (added)
-
tags/1.1.3/admin/content-processing-resume.php (added)
-
tags/1.1.3/admin/css (added)
-
tags/1.1.3/admin/css/ki-berater-design.css (added)
-
tags/1.1.3/admin/dashboard-page.php (added)
-
tags/1.1.3/admin/diagnosis-page.php (added)
-
tags/1.1.3/admin/help-page.php (added)
-
tags/1.1.3/admin/js (added)
-
tags/1.1.3/admin/js/category-analysis.js (added)
-
tags/1.1.3/admin/js/causal-chain.js (added)
-
tags/1.1.3/admin/js/connect.js (added)
-
tags/1.1.3/admin/js/content.js (added)
-
tags/1.1.3/admin/js/diagnosis.js (added)
-
tags/1.1.3/admin/js/settings.js (added)
-
tags/1.1.3/admin/js/status.js (added)
-
tags/1.1.3/admin/js/tabs.js (added)
-
tags/1.1.3/admin/lizenz-check.php (added)
-
tags/1.1.3/admin/settings-page.php (added)
-
tags/1.1.3/admin/status-page.php (added)
-
tags/1.1.3/admin/testmonat-check.php (added)
-
tags/1.1.3/changelog.txt (added)
-
tags/1.1.3/frontend (added)
-
tags/1.1.3/frontend/chat-widget.php (added)
-
tags/1.1.3/frontend/css (added)
-
tags/1.1.3/frontend/css/chat-widget.css (added)
-
tags/1.1.3/frontend/js (added)
-
tags/1.1.3/frontend/js/chat-widget.js (added)
-
tags/1.1.3/ki-berater.php (added)
-
tags/1.1.3/languages (added)
-
tags/1.1.3/languages/dtpm-ai-sale-assistant-woocommerce.pot (added)
-
tags/1.1.3/readme.txt (added)
-
trunk/admin/content-processing-resume.php (modified) (5 diffs)
-
trunk/admin/js/causal-chain.js (modified) (3 diffs)
-
trunk/admin/js/content.js (modified) (3 diffs)
-
trunk/admin/js/tabs.js (modified) (3 diffs)
-
trunk/changelog.txt (modified) (1 diff)
-
trunk/ki-berater.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
dtpm-ai-sale-assistant-woocommerce/trunk/admin/content-processing-resume.php
r3735498 r3738724 64 64 * ki-berater.php bei Plugin-Aktivierung/-Deaktivierung, ruft aber die 65 65 * Funktionen dieser Datei auf - gleiches Prinzip wie admin/availability-sync.php/ 66 * admin/testmonat-check.php. Braucht ki_berater_fetch_sample_products() aus 67 * admin/category-analysis-page.php - deshalb erst NACH dieser Datei in 68 * ki-berater.php eingebunden (siehe dort). 66 * admin/testmonat-check.php. Braucht ki_berater_fetch_content_products_for_category() 67 * aus admin/content-page.php (Fund 09.10.2026, siehe 68 * ki_berater_resume_content_processing_for_subcategory() unten fuer die volle 69 * Begruendung) - deshalb erst NACH dieser Datei in ki-berater.php eingebunden 70 * (siehe dort). 69 71 */ 70 72 … … 216 218 217 219 /** 220 * GET /variation-attributes (admin/variation_attributes.py) - die 221 * bestaetigten RELEVANTEN Variationsattribut-Namen einer Subkategorie, 222 * dieselbe Berechnung (bestaetigt UND relevant) wie 223 * fetchRelevantAttributeNames() in js/content.js fuer den Browser-Pfad 224 * (Fund 09.10.2026, siehe ki_berater_resume_content_processing_for_subcategory() 225 * unten). Leeres Array bei jedem Fehler (Netzwerk, Nicht-200, kaputtes 226 * JSON) - dieselbe konservative "zaehlt wie nicht relevant"-Regel wie in 227 * ki_berater_expand_variable_product_for_content() (admin/content-page.php): 228 * erzeugt im Zweifel WENIGER statt mehr Varianten-Zeilen, nie mehr. 229 */ 230 function ki_berater_fetch_relevant_attribute_names_for_resume( $subcategory_id, $tenant_id ) { 231 $response = wp_remote_get( 232 trailingslashit( KI_BERATER_API_URL ) . 'variation-attributes?subcategory_id=' . rawurlencode( $subcategory_id ) 233 . '&tenant_id=' . rawurlencode( $tenant_id ), 234 array( 235 'headers' => ki_berater_auth_headers(), 236 'timeout' => 20, 237 ) 238 ); 239 240 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { 241 return array(); 242 } 243 244 $body = json_decode( wp_remote_retrieve_body( $response ), true ); 245 if ( ! is_array( $body ) ) { 246 return array(); 247 } 248 249 $relevant_attributes = array(); 250 foreach ( $body as $entry ) { 251 if ( ! empty( $entry['bestaetigt'] ) && ! empty( $entry['relevant'] ) ) { 252 $relevant_attributes[] = $entry['attribut']; 253 } 254 } 255 return $relevant_attributes; 256 } 257 258 /** 218 259 * Ermittelt fuer EINE als "angefangen, aber unvollstaendig" erkannte 219 260 * Subkategorie (processed_count > 0, siehe ki_berater_run_content_processing_resume()) 220 * die aktuelle, vollstaendige WooCommerce-Produktliste (gleiches Muster wie 221 * ki_berater_sync_availability_for_subcategory() in admin/availability-sync.php - 222 * ueber vaterprodukt_id dedupliziert, -1 = kein Limit), vergleicht sie gegen 261 * die aktuelle, vollstaendige WooCommerce-Produktliste, vergleicht sie gegen 223 262 * die bereits gespeicherten SKUs (GET /content/state) und stoesst 224 263 * POST /content/process-subcategory NUR fuer die tatsaechlich noch … … 228 267 * still ab (return), gleiches "kein eigener Aufrufer/Fehlerkanal"-Prinzip 229 268 * wie die uebrigen Cron-Callbacks in diesem Plugin. 269 * 270 * BUGFIX (Fund 09.10.2026, Abgleich Shopify-Projekt gegen Kern-API): bisher 271 * ueber ki_berater_fetch_sample_products() (admin/category-analysis-page.php) 272 * ermittelt - die liefert IMMER genau EINE Zeile je WooCommerce-Vaterprodukt 273 * mit dessen eigener (oft leerer) SKU/Preis, OHNE jede Varianten- 274 * Aufschluesselung. Der Browser-Pfad (ki_berater_fetch_content_products_for_category(), 275 * admin/content-page.php) gruppiert dagegen korrekt nach den RELEVANTEN 276 * unterscheidenden Attributen (siehe ki_berater_expand_variable_product_for_content() 277 * dort). Ein Variationsprodukt mit mehreren relevanten Gruppen wurde dadurch 278 * im stuendlichen Cron faelschlich nur als EIN Eintrag mit der falschen 279 * SKU/dem falschen Preis (Vaterprodukt statt Variante) gezaehlt/gemeldet - 280 * jetzt dieselbe Funktion wie der Browser-Pfad, mit denselben bestaetigten 281 * relevant_attributes (siehe ki_berater_fetch_relevant_attribute_names_for_resume() 282 * oben). Dedup jetzt ueber sku statt vaterprodukt_id - echte, unterschiedliche 283 * Varianten-SKUs desselben Vaterprodukts waeren sonst faelschlich als 284 * "Duplikat" zusammengefallen (gleiche Begruendung wie 285 * ki_berater_ajax_fetch_content_products() in admin/content-page.php). 230 286 */ 231 287 function ki_berater_resume_content_processing_for_subcategory( $subcategory_id, $tenant_id, $processed_count ) { … … 235 291 } 236 292 237 $categories = array_map( 238 function ( $category ) { 239 return array( 'id' => $category['external_category_id'] ); 240 }, 241 $external_categories 242 ); 243 244 $products_per_category = ki_berater_fetch_sample_products( $categories, -1 ); 245 246 $products_by_vaterprodukt_id = array(); 247 foreach ( $products_per_category as $products ) { 293 $relevant_attributes = ki_berater_fetch_relevant_attribute_names_for_resume( $subcategory_id, $tenant_id ); 294 295 $products_by_sku = array(); 296 foreach ( $external_categories as $external_category ) { 297 $products = ki_berater_fetch_content_products_for_category( 298 $external_category['external_category_id'], 299 $relevant_attributes 300 ); 248 301 foreach ( $products as $product ) { 249 $products_by_ vaterprodukt_id[ $product['vaterprodukt_id'] ] = $product;302 $products_by_sku[ $product['sku'] ] = $product; 250 303 } 251 304 } 252 305 253 $total = count( $products_by_ vaterprodukt_id);306 $total = count( $products_by_sku ); 254 307 if ( 0 === $total || $processed_count >= $total ) { 255 308 // Entweder der Katalog ist gerade leer (voruebergehender … … 269 322 270 323 $remaining = array(); 271 foreach ( $products_by_ vaterprodukt_idas $product ) {324 foreach ( $products_by_sku as $product ) { 272 325 if ( ! isset( $already_processed_lookup[ $product['sku'] ] ) ) { 273 326 $remaining[] = $product; -
dtpm-ai-sale-assistant-woocommerce/trunk/admin/js/causal-chain.js
r3723458 r3738724 726 726 var picker = document.getElementById( 'ki-berater-causal-chain-subcategory-picker' ); 727 727 var hint = document.getElementById( 'ki-berater-causal-chain-no-subcategories-hint' ); 728 // BUGFIX (Fund 10.10.2026, siehe js/tabs.js "ki-berater-tab-activated"): 729 // dieser Aufruf laeuft jetzt bei JEDEM Wechsel zu diesem Tab erneut, 730 // nicht mehr nur einmal beim Seitenaufbau - populateSubcategorySelect() 731 // baut die <select>-Optionen dabei komplett neu auf UND wuerde ohne 732 // diese Zwischenspeicherung die Auswahl stillschweigend auf "Bitte 733 // auswaehlen" zuruecksetzen, selbst wenn der Haendler mitten in der 734 // Bearbeitung GENAU dieser Subkategorie war (selectedSubcategoryId 735 // selbst bleibt davon unberuehrt, nur die sichtbare <select>-Anzeige 736 // wuerde sonst davon abweichen). 737 var select = document.getElementById( 'ki-berater-causal-chain-subcategory-select' ); 738 var previouslySelectedId = select ? select.value : ''; 728 739 729 740 hideApiSecretHint(); … … 748 759 } 749 760 } else { 761 // BUGFIX (Fund 10.10.2026): dieser Zweig setzte picker/hint 762 // bisher NIE explizit zurueck - unschaedlich, SOLANGE 763 // loadSubcategories() nur einmal pro Seitenaufbau lief (der 764 // server-seitige HTML-Ausgangszustand zeigt den picker 765 // bereits/versteckt den hint bereits, siehe 766 // causal-chain-page.php). Seit dem erneuten Aufruf bei jedem 767 // Tab-Wechsel (siehe js/tabs.js) MUSS dieser Zweig den 768 // "keine Subkategorien"-Zustand eines fruehen Aufrufs 769 // explizit wieder aufheben koennen, sonst bleiben picker/hint 770 // auf dem letzten "leer"-Stand haengen, obwohl die neue 771 // Antwort bereits Subkategorien enthaelt. 772 if ( picker ) { 773 picker.style.display = 'block'; 774 } 775 if ( hint ) { 776 hint.style.display = 'none'; 777 } 750 778 populateSubcategorySelect( subcategories ); 779 // Nur wiederherstellen, wenn diese Subkategorie in der 780 // FRISCHEN Liste tatsaechlich noch vorkommt - sonst bleibt 781 // bewusst der Platzhalter stehen (z. B. tatsaechlich 782 // geloescht). 783 if ( previouslySelectedId && subcategoryNameById[ previouslySelectedId ] ) { 784 select.value = previouslySelectedId; 785 } 751 786 } 752 787 … … 2160 2195 loadSubcategories(); 2161 2196 loadOpenOverview(); 2197 2198 // BUGFIX (Fund 10.10.2026, Dirk, siehe js/tabs.js fuer die volle 2199 // Begruendung): ohne Seiten-Reload zeigte dieser Tab nach einem 2200 // rein client-seitigen Wechsel weiterhin den Stand von VOR einer 2201 // zwischenzeitlich in "Kategorien-Analyse" bestaetigten Analyse 2202 // ("Kategorie-Analyse noch nicht gemacht"). loadSubcategories()/ 2203 // loadOpenOverview() sind beide bereits vollstaendig 2204 // idempotent (bauen Auswahl/Uebersicht jedes Mal komplett neu 2205 // aus der Server-Antwort auf) - ein erneuter Aufruf bei JEDEM 2206 // Wechsel zu diesem Tab ist deshalb gefahrlos. 2207 if ( typeof document.addEventListener === 'function' ) { 2208 document.addEventListener( 'ki-berater-tab-activated', function ( event ) { 2209 if ( event.detail && event.detail.panelId === 'ki-berater-tab-panel-causal-chain' ) { 2210 loadSubcategories(); 2211 loadOpenOverview(); 2212 } 2213 } ); 2214 } 2162 2215 } ); 2163 2216 } -
dtpm-ai-sale-assistant-woocommerce/trunk/admin/js/content.js
r3735498 r3738724 726 726 var noSubcategoriesHint = document.getElementById( 'ki-berater-content-no-subcategories-hint' ); 727 727 var noCausalChainsHint = document.getElementById( 'ki-berater-content-no-causal-chains-hint' ); 728 // BUGFIX (Fund 10.10.2026, siehe js/tabs.js "ki-berater-tab-activated"): 729 // dieser Aufruf laeuft jetzt bei JEDEM Wechsel zu diesem Tab erneut - 730 // populateSubcategorySelect() baut die <select>-Optionen dabei 731 // komplett neu auf und wuerde ohne diese Zwischenspeicherung die 732 // Auswahl stillschweigend auf "Bitte auswaehlen" zuruecksetzen, selbst 733 // wenn der Haendler mitten in der Bearbeitung GENAU dieser 734 // Subkategorie war. 735 var select = document.getElementById( 'ki-berater-content-subcategory-select' ); 736 var previouslySelectedId = select ? select.value : ''; 728 737 729 738 if ( picker ) { … … 768 777 } 769 778 populateSubcategorySelect( subcategoriesWithCausalChain ); 779 // Nur wiederherstellen, wenn diese Subkategorie in der 780 // FRISCHEN, gefilterten Liste tatsaechlich noch vorkommt. 781 var bleibtGueltig = subcategoriesWithCausalChain.some( function ( subcategory ) { 782 return subcategory.id === previouslySelectedId; 783 } ); 784 if ( previouslySelectedId && bleibtGueltig ) { 785 select.value = previouslySelectedId; 786 } 770 787 } ); 771 788 } ) … … 1758 1775 loadSubcategories(); 1759 1776 loadOpenOverview(); 1777 1778 // BUGFIX (Fund 10.10.2026, Dirk, siehe js/tabs.js fuer die volle 1779 // Begruendung): ohne Seiten-Reload zeigte dieser Tab nach einem 1780 // rein client-seitigen Wechsel weiterhin den Stand von VOR einer 1781 // zwischenzeitlich in "Kausalketten" bestaetigten Kausalkette 1782 // ("noch keine Kausalkette bestaetigt"). loadSubcategories()/ 1783 // loadOpenOverview() sind beide bereits vollstaendig idempotent - 1784 // ein erneuter Aufruf bei JEDEM Wechsel zu diesem Tab ist deshalb 1785 // gefahrlos. 1786 if ( typeof document.addEventListener === 'function' ) { 1787 document.addEventListener( 'ki-berater-tab-activated', function ( event ) { 1788 if ( event.detail && event.detail.panelId === 'ki-berater-tab-panel-content' ) { 1789 loadSubcategories(); 1790 loadOpenOverview(); 1791 } 1792 } ); 1793 } 1760 1794 } ); 1761 1795 } -
dtpm-ai-sale-assistant-woocommerce/trunk/admin/js/tabs.js
r3723458 r3738724 1 1 /** 2 * Reines Tab-Umschalten fuer admin/dashboard-page.php - greift NICHT in 3 * settings.js/category-analysis.js/causal-chain.js/content.js ein, kennt 4 * nur die generischen .ki-berater-tab-trigger/.ki-berater-tab-panel- 5 * Elemente und deren data-tab-target/id-Kopplung. Gleicher Code-Stil wie 6 * die vier Feature-Skripte (IIFE, var, DOMContentLoaded). 2 * Reines Tab-Umschalten fuer admin/dashboard-page.php - kennt nur die 3 * generischen .ki-berater-tab-trigger/.ki-berater-tab-panel-Elemente und 4 * deren data-tab-target/id-Kopplung. Gleicher Code-Stil wie die vier 5 * Feature-Skripte (IIFE, var, DOMContentLoaded). 7 6 * 8 7 * Kein URL-Sync (siehe Auftrag - entspricht dem v0-Mockup, dort auch nur … … 10 9 * serverseitigen Standard-Tab (Einstellungen, siehe dashboard-page.php) 11 10 * zurueck. 11 * 12 * BUGFIX (Fund 10.10.2026, Dirk): category-analysis.js/causal-chain.js/ 13 * content.js laden ihren jeweiligen Anfangszustand (u. a. die 14 * Subkategorie-Liste, "Noch offen"-Uebersicht) bisher NUR EINMAL beim 15 * DOMContentLoaded dieser Seite - ein rein client-seitiger Tab-Wechsel 16 * (GENAU das, was dieses Skript tut) loeste seitdem NIE einen erneuten 17 * Abruf aus. Wer z. B. in "Kategorien-Analyse" etwas bestaetigt und DANN 18 * ohne Seiten-Reload zu "Kausalketten" wechselt, sah dort weiterhin den 19 * Stand von vor der Bestaetigung ("Kategorie-Analyse noch nicht gemacht") - 20 * derselbe Effekt beim Wechsel von "Kausalketten" zu "Produktabdeckung". 21 * activateTab() loest deshalb jetzt zusaetzlich ein generisches 22 * CustomEvent "ki-berater-tab-activated" aus (detail.panelId = die Ziel- 23 * Panel-ID) - jedes Feature-Skript kann darauf lauschen und seinen 24 * eigenen Anfangszustand bei Bedarf erneut laden, OHNE dass dieses Skript 25 * (oder ein anderes Feature-Skript) die Feature-Skripte direkt kennen 26 * oder aufrufen muss (bleibt bewusst entkoppelt, siehe Modul-Docstrings 27 * dort - "bewusst dupliziert, nicht importiert"). NUR bei einem 28 * tatsaechlichen Klick ausgeloest (siehe handleTriggerClick() unten), 29 * NICHT beim initialen Seitenaufbau - der server-seitig aktive Standard- 30 * Tab hat seinen Anfangszustand ohnehin schon ueber sein eigenes 31 * DOMContentLoaded geladen, ein zusaetzlicher Abruf dafuer waere 32 * redundant. 12 33 */ 13 34 ( function () { … … 35 56 panel.hidden = panel.id !== targetPanelId; 36 57 } ); 58 59 document.dispatchEvent( 60 new CustomEvent( 'ki-berater-tab-activated', { detail: { panelId: targetPanelId } } ) 61 ); 37 62 } 38 63 -
dtpm-ai-sale-assistant-woocommerce/trunk/changelog.txt
r3735498 r3738724 1 1 *** DT-PM AI Sale Assistant for WooCommerce Changelog *** 2 3 2026-10-10 - version 1.1.3 4 Requires Kern-API >= commit 3c31fd7 (unchanged from 1.1.2) - this is a pure frontend/JS fix, no Kern-API dependency. 5 * Fixed - `admin/js/causal-chain.js` and `admin/js/content.js` loaded their subcategory list exactly once, at page load (`DOMContentLoaded`) - a purely client-side tab switch (`admin/js/tabs.js`) never triggered a re-fetch. After confirming a Category Analysis and switching to the Causal Chains tab without reloading the page (or, equally, after confirming a Causal Chain and switching to Product Coverage), the newly confirmed subcategory still showed as "not done yet" until the page was reloaded. `tabs.js` now dispatches a `ki-berater-tab-activated` custom event whenever a tab is actually clicked, which both feature scripts listen for on their own panel to refresh their state - kept the two scripts decoupled (no direct cross-file calls, consistent with the existing "duplicated, not imported" convention). Also fixed a related, previously latent bug this exposed: `causal-chain.js`'s loader never explicitly reset its "no subcategories yet" hint/picker visibility back to normal once shown, relying on a one-time page load always starting from the correct default HTML state - harmless as long as it only ran once, but would have left the hint stuck visible forever on any later successful re-fetch. Also preserves the currently selected subcategory across a re-fetch in both tabs (previously, re-running the loader would have silently reset an in-progress selection back to the placeholder). 6 7 2026-10-10 - version 1.1.2 8 Requires Kern-API >= commit 3c31fd7 (unchanged from 1.1.1) - this fix calls only the already-existing `GET /variation-attributes` endpoint, no new Kern-API dependency. 9 * Fixed - the hourly catch-up cron for incomplete product processing (`admin/content-processing-resume.php`) used a different, simpler product-fetching routine (`ki_berater_fetch_sample_products()`) than the one the browser's "Process remaining" button uses (`ki_berater_fetch_content_products_for_category()`/`ki_berater_expand_variable_product_for_content()`) - for a variable product with at least one merchant-confirmed relevant distinguishing attribute (e.g. color), the cron reported only a single row using the parent product's own (often empty) SKU/price, instead of one row per actual relevant variant combination with that variant's own SKU/price. The cron now fetches the merchant's confirmed relevant attributes itself (`GET /variation-attributes`) and uses the same variant-aware logic as the browser path; deduplication switched from `vaterprodukt_id` to `sku` accordingly, since a variant-expanded product can now report multiple distinct SKUs. 2 10 3 11 2026-10-08 - version 1.1.1 -
dtpm-ai-sale-assistant-woocommerce/trunk/ki-berater.php
r3735498 r3738724 3 3 * Plugin Name: DT-PM AI Sale Assistant for WooCommerce 4 4 * Description: Thin wp-admin interface for the AI product advisor (ARCHITECTURE.md section 2.1). Renders no business logic itself, only calls the Kern-API. 5 * Version: 1.1. 15 * Version: 1.1.3 6 6 * Author: KI-Produktberater-Team 7 7 * License: GPLv2 or later … … 64 64 // BEIDE hochzaehlen (war zuletzt auseinandergelaufen - 0.1.13 im Header, 65 65 // 0.1.12 hier - hiermit wieder angeglichen). 66 define( 'KI_BERATER_VERSION', '1.1. 1' );66 define( 'KI_BERATER_VERSION', '1.1.3' ); 67 67 68 68 require_once KI_BERATER_PLUGIN_DIR . 'admin/diagnosis-page.php'; -
dtpm-ai-sale-assistant-woocommerce/trunk/readme.txt
r3735498 r3738724 8 8 WC tested up to: 11.1 9 9 Requires Plugins: woocommerce 10 Stable tag: 1.1. 110 Stable tag: 1.1.3 11 11 License: GPLv2 or later 12 12 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 94 94 == Changelog == 95 95 96 = 1.1.3 = 97 * Fixed: after confirming a Category Analysis, the Causal Chains tab (and, after confirming a Causal Chain, the Product Coverage tab) still said "not done yet" for it until the page was reloaded - switching tabs without reloading now refreshes that status correctly. 98 99 = 1.1.2 = 100 * Fixed: the hourly catch-up check for incomplete product processing could report the wrong price/SKU for a product with multiple customer-relevant option combinations (e.g. several colors), using the overall product's own (often blank) price/SKU instead of the matching option's - the manual "Process remaining" button already handled this correctly; the hourly check now uses the same logic. 101 96 102 = 1.1.1 = 97 103 * Fixed: an automatic catch-up run for a handful of still-missing products (the hourly background check, or clicking "Process remaining" right after a few products failed to process) could wipe out the rest of an already-processed subcategory's recommendations - the run now only clears out genuinely removed products when it actually has the complete, current product list, not a partial one.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)