Przeglądaj źródła

评论迁移忽略商品启用状态与库存,并默认导入禁用商品以便匹配。

Co-authored-by: Cursor <cursoragent@cursor.com>
chengwl 6 dni temu
rodzic
commit
d68eb11eac

+ 50 - 17
app/Console/Commands/MigrateAsteriaReviews.php

@@ -81,12 +81,14 @@ class MigrateAsteriaReviews extends Command
         }
 
         // ── Build product and customer lookup maps ────────────────────────────────
-        $this->info('Building product SKU map…');
+        // Matching ignores Magento/Bagisto product status and inventory — disabled or
+        // out-of-stock products still receive reviews when the SKU (or Asteria id) exists.
+        $this->info('Building product map (status/stock ignored)…');
         $productIdMap = $this->buildProductIdMap($connection);
-        $this->line('  '.count($productIdMap).' Magento products matched to Bagisto products by SKU.');
+        $this->line('  '.count($productIdMap).' Magento products matched to Bagisto products (SKU / migrated_from_asteria_id).');
 
         if (empty($productIdMap)) {
-            $this->warn('No matching products found. Are the SKUs consistent between the two stores?');
+            $this->warn('No matching products found. Import products first (including disabled: ASTERIA_PRODUCTS_ONLY_ENABLED=false).');
         }
 
         $this->info('Building customer e-mail map…');
@@ -258,30 +260,61 @@ class MigrateAsteriaReviews extends Command
     }
 
     /**
-     * Build a map of  magento_product_id → bagisto_product_id  by joining
-     * Magento's catalog_product_entity.sku against Bagisto's products.sku.
+     * Build magento_product_id → bagisto_product_id.
+     *
+     * Prefer products.migrated_from_asteria_id, then fall back to SKU.
+     * Does not filter Magento or Bagisto product status / inventory.
      *
      * @return array<int, int>
      */
     private function buildProductIdMap(string $connection): array
     {
-        // Fetch all Magento SKUs.
-        $magentoPairs = DB::connection($connection)
+        $magentoRows = DB::connection($connection)
             ->table('catalog_product_entity')
             ->select('entity_id', 'sku')
-            ->get()
-            ->pluck('entity_id', 'sku')  // sku → magento_id
-            ->toArray();
+            ->get();
 
-        if (empty($magentoPairs)) {
+        if ($magentoRows->isEmpty()) {
             return [];
         }
 
-        // Match against Bagisto's products table.
-        $skus = array_keys($magentoPairs);
-        $map  = [];
+        $map = [];
+
+        // 1) Match by migrated_from_asteria_id (covers disabled / out-of-stock Bagisto rows).
+        if (Schema::hasColumn('products', 'migrated_from_asteria_id')) {
+            $asteriaIds = $magentoRows->pluck('entity_id')->map(fn ($id) => (int) $id)->all();
+
+            foreach (array_chunk($asteriaIds, 500) as $chunk) {
+                $rows = DB::table('products')
+                    ->select('id', 'migrated_from_asteria_id')
+                    ->whereIn('migrated_from_asteria_id', $chunk)
+                    ->get();
+
+                foreach ($rows as $row) {
+                    $map[(int) $row->migrated_from_asteria_id] = (int) $row->id;
+                }
+            }
+        }
+
+        // 2) Fall back to SKU for Magento products not yet linked by Asteria id.
+        $magentoPairs = [];
+
+        foreach ($magentoRows as $row) {
+            $magentoId = (int) $row->entity_id;
+            $sku = trim((string) $row->sku);
+
+            if ($sku === '' || isset($map[$magentoId])) {
+                continue;
+            }
+
+            $magentoPairs[$sku] = $magentoId;
+        }
+
+        if ($magentoPairs === []) {
+            return $map;
+        }
 
-        foreach (array_chunk($skus, 500) as $chunk) {
+        foreach (array_chunk(array_keys($magentoPairs), 500) as $chunk) {
             $rows = DB::table('products')
                 ->select('id', 'sku')
                 ->whereIn('sku', $chunk)
@@ -290,8 +323,8 @@ class MigrateAsteriaReviews extends Command
             foreach ($rows as $row) {
                 $magentoId = $magentoPairs[$row->sku] ?? null;
 
-                if ($magentoId !== null) {
-                    $map[(int) $magentoId] = (int) $row->id;
+                if ($magentoId !== null && ! isset($map[$magentoId])) {
+                    $map[$magentoId] = (int) $row->id;
                 }
             }
         }

+ 14 - 2
app/Services/Asteria/Magento1ProductReader.php

@@ -72,7 +72,11 @@ class Magento1ProductReader
                 'Wig Color', 'Wig Style',
             ],
             'max_variants_warn'      => 500,
+            // Magento status attribute value for "Enabled". Used when only_enabled=true.
             'enabled_status'         => 1,
+            // false = also import disabled Magento products (status mapped to 0) so
+            // reviews / orders can still match by SKU. Stock is never used as a filter.
+            'only_enabled'           => false,
         ];
     }
 
@@ -172,7 +176,9 @@ class Magento1ProductReader
             $query->where('pe.sku', $sku);
         }
 
-        $this->applyEnabledStatusFilter($query);
+        if ($this->onlyEnabled()) {
+            $this->applyEnabledStatusFilter($query);
+        }
 
         $rows = $query->get();
 
@@ -203,6 +209,11 @@ class Magento1ProductReader
         })->values();
     }
 
+    private function onlyEnabled(): bool
+    {
+        return (bool) ($this->config['only_enabled'] ?? false);
+    }
+
     /**
      * @param  \Illuminate\Database\Query\Builder  $query
      */
@@ -295,7 +306,8 @@ class Magento1ProductReader
             'special_price'         => $eav['special_price'] ?? '',
             'cost'                  => $eav['cost'] ?? '',
             'weight'                => $eav['weight'] ?? '',
-            'status'                => 1,
+            // Magento: 1=Enabled, 2=Disabled. Keep disabled products importable for review matching.
+            'status'                => ((int) ($eav['status'] ?? 1) === 1) ? 1 : 0,
             'featured'              => 0,
             'new'                   => 0,
             'guest_checkout'        => 1,

+ 2 - 1
composer.json

@@ -137,7 +137,8 @@
             "Webkul\\Core\\Tests\\": "packages/Webkul/Core/tests",
             "Webkul\\DataGrid\\Tests\\": "packages/Webkul/DataGrid/tests",
             "Webkul\\Installer\\Tests\\": "packages/Webkul/Installer/tests",
-            "Webkul\\Shop\\Tests\\": "packages/Webkul/Shop/tests"
+            "Webkul\\Shop\\Tests\\": "packages/Webkul/Shop/tests",
+            "Webkul\\BagistoApi\\Tests\\": "packages/Webkul/BagistoApi/tests"
         }
     },
     "scripts": {

+ 6 - 0
config/asteria.php

@@ -63,7 +63,13 @@ return [
 
         'max_variants_warn' => 500,
 
+        // Magento status value meaning Enabled (usually 1).
         'enabled_status' => 1,
+
+        // false = import disabled Magento products as Bagisto status=0 so reviews can match by SKU.
+        // Stock / is_in_stock is never used as an import filter.
+        // Set ASTERIA_PRODUCTS_ONLY_ENABLED=true to keep the old "enabled only" behaviour.
+        'only_enabled' => filter_var(env('ASTERIA_PRODUCTS_ONLY_ENABLED', false), FILTER_VALIDATE_BOOLEAN),
     ],
 
 ];

+ 23 - 4
packages/Webkul/BagistoApi/tests/Unit/Migration/Magento1ProductReaderTest.php

@@ -80,7 +80,7 @@ class Magento1ProductReaderTest extends TestCase
         $this->assertSame('12"', $product['variants'][0]['hair_length']);
     }
 
-    public function test_it_pages_from_the_last_entity_id_and_skips_disabled_products(): void
+    public function test_it_pages_from_the_last_entity_id_and_includes_disabled_products_by_default(): void
     {
         MagentoSchema::seedProduct($this->connection, [
             'entity_id' => 11,
@@ -99,11 +99,30 @@ class Magento1ProductReaderTest extends TestCase
         $reader = new Magento1ProductReader($this->connection, $this->readerConfig);
 
         $page = $reader->fetchProducts(10, 50);
-        $this->assertCount(1, $page);
-        $this->assertSame('WIG-002', $page->first()['sku']);
+        $this->assertCount(2, $page);
+        $this->assertSame(['WIG-002', 'WIG-OFF'], $page->pluck('sku')->all());
+        $this->assertSame(0, (int) $page->firstWhere('sku', 'WIG-OFF')['status']);
 
         $skus = $reader->fetchProducts(0, 50)->pluck('sku')->all();
-        $this->assertSame(['WIG-001', 'WIG-002'], $skus);
+        $this->assertSame(['WIG-001', 'WIG-002', 'WIG-OFF'], $skus);
+    }
+
+    public function test_it_can_skip_disabled_products_when_only_enabled(): void
+    {
+        MagentoSchema::seedProduct($this->connection, [
+            'entity_id' => 12,
+            'sku'       => 'WIG-OFF',
+            'name'      => 'Disabled',
+            'price'     => 10,
+            'status'    => 2,
+        ]);
+
+        $reader = new Magento1ProductReader($this->connection, array_merge($this->readerConfig, [
+            'only_enabled' => true,
+        ]));
+
+        $skus = $reader->fetchProducts(0, 50)->pluck('sku')->all();
+        $this->assertSame(['WIG-001'], $skus);
     }
 
     public function test_it_exports_select_attribute_definitions_with_options(): void