* @created 2023-01-03 18:57:53 * @modified 2023-01-03 18:57:53 */ namespace Beike\Console\Commands; use Beike\Admin\Services\CategoryService; use Beike\Admin\Services\ProductService; use Beike\Models\Brand; use Beike\Models\Category; use Beike\Models\CategoryDescription; use Beike\Models\CategoryPath; use Beike\Models\Product; use Beike\Models\ProductDescription; use Beike\Models\ProductSku; use Illuminate\Console\Command; use Illuminate\Database\ConnectionInterface; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; class MigrateFromOpenCart extends Command { public const PER_PAGE = 1000; public const LANG_MAPPING = [ 'zh_cn' => 11, 'es' => 6, ]; protected $signature = 'migrate:oc'; protected $description = '从 OpenCart 迁移数据'; protected ConnectionInterface $ocdb; private $ocProductVariants; private $ocVariantDescriptions; private $ocVariantValues; private $ocVariantValueDescriptions; private $ocProductImages; private $ocProductCategories; private int $page = 1; public function __construct() { parent::__construct(); $this->ocdb = DB::connection('opencart'); } /** * 导入OC产品数据 */ public function handle() { $this->importCategories(); $this->importBrands(); $this->importProducts(); } /** * 导入分类数据 */ private function importCategories() { Category::query()->truncate(); CategoryDescription::query()->truncate(); CategoryPath::query()->truncate(); $this->ocdb->table('category') ->orderBy('category_id') ->chunk(self::PER_PAGE, function ($ocCategories) { $bkCategories = []; foreach ($ocCategories as $ocCategory) { $bkCategories[] = [ 'id' => $ocCategory->category_id, 'parent_id' => $ocCategory->parent_id, 'position' => $ocCategory->sort_order, 'active' => $ocCategory->status, 'created_at' => now(), 'updated_at' => now(), ]; } Category::query()->insert($bkCategories); }); $descriptions = []; $ocDescriptions = $this->ocdb->table('category_description')->orderBy('category_id')->get(); $langMapping = array_flip(self::LANG_MAPPING); foreach ($ocDescriptions as $description) { $descriptions[] = [ 'category_id' => $description->category_id, 'locale' => $langMapping[$description->language_id], 'name' => $description->name, 'content' => html_entity_decode($description->description, ENT_QUOTES), 'created_at' => now(), 'updated_at' => now(), ]; } CategoryDescription::query()->insert($descriptions); CategoryService::repairCategories(); } /** * 导入品牌数据 */ private function importBrands() { Brand::query()->truncate(); $this->ocdb->table('manufacturer') ->orderBy('manufacturer_id') ->chunk(self::PER_PAGE, function ($ocBrands) { $bkBrands = []; foreach ($ocBrands as $ocBrand) { $bkBrands[] = [ 'id' => $ocBrand->manufacturer_id, 'name' => $ocBrand->name, 'first' => mb_strtoupper(mb_substr($ocBrand->name, 0, 1)), 'logo' => $ocBrand->image, 'sort_order' => $ocBrand->sort_order, 'status' => $ocBrand->status, 'created_at' => now(), 'updated_at' => now(), ]; } Brand::query()->insert($bkBrands); }); } /** * 导入产品 */ private function importProducts() { $this->ocProductVariants = $this->ocdb->table('product_variant')->get()->groupBy('product_id'); $this->ocVariantDescriptions = $this->ocdb->table('variant_description')->get()->groupBy('variant_id'); $this->ocVariantValues = $this->ocdb->table('variant_value')->get()->keyBy('variant_value_id'); $this->ocVariantValueDescriptions = $this->ocdb->table('variant_value_description')->get()->groupBy('variant_value_id'); $this->ocProductImages = $this->ocdb->table('product_image')->get()->groupBy('product_id'); $this->ocProductCategories = $this->ocdb->table('product_to_category')->get()->groupBy('product_id'); $this->clearData(); $this->ocdb->table('product') ->where('parent_id', 0) // ->where('product_id', 1894) // ->where('sku', '10012378') ->orderBy('product_id') ->chunk(self::PER_PAGE, function ($ocProducts) { $this->importProduct($ocProducts); }); } /** * 导入单个产品 * @param Collection $ocProducts */ private function importProduct(Collection $ocProducts) { $total = $ocProducts->count(); $ocProductIds = $ocProducts->pluck('product_id'); $childProducts = $this->ocdb->table('product')->whereIn('parent_id', $ocProductIds)->get()->groupBy('parent_id'); foreach ($ocProducts as $index => $ocProduct) { dump("Start handle Page: $this->page - {$index}/{$total}"); $ocProductId = $ocProduct->product_id; $productVariants = $this->ocProductVariants[$ocProductId] ?? []; $childProducts = $childProducts[$ocProductId] ?? []; $bkProduct = $this->generateBeikeProduct($ocProduct, $productVariants, $childProducts); (new ProductService)->create($bkProduct); } $this->page++; } /** * 构造 beike 产品 */ private function generateBeikeProduct($ocProduct, $productVariants, $childProducts) { $variables = $this->generateVariables($ocProduct, $childProducts); $bkProduct = [ 'active' => $ocProduct->status, 'brand_id' => $ocProduct->manufacturer_id, 'position' => $ocProduct->sort_order, 'tax_class_id' => $ocProduct->tax_class_id, 'variables' => json_encode($variables), ]; $bkProduct['descriptions'] = $this->generateDescriptions($ocProduct); $bkProduct['images'] = [$ocProduct->image]; $bkProduct['skus'] = $this->generateSkus($ocProduct, $productVariants, $childProducts, $variables); $bkProduct['categories'] = $this->generateCategories($ocProduct); return $bkProduct; } /** * 生成 beike 产品规格 * @return array[] */ private function generateVariables($ocProduct, $childProducts): array { $productIds = [$ocProduct->product_id]; foreach ($childProducts as $childProduct) { $productIds[] = $childProduct->product_id; } $locales = locales(); $items = $values = []; foreach ($productIds as $productId) { $productVariants = $this->ocProductVariants[$productId] ?? []; foreach ($productVariants as $productVariant) { $productVariantId = $productVariant->variant_id; $productVariantValueId = $productVariant->variant_value_id; $variants = $this->ocVariantDescriptions[$productVariantId]; $variantValue = $this->ocVariantValues[$productVariantValueId]; $variantValueDescriptions = $this->ocVariantValueDescriptions[$productVariantValueId]; $variants = $variants->keyBy('language_id'); $names = []; foreach ($locales as $locale) { $variant = $variants[self::LANG_MAPPING[$locale['code']]]; $names[$locale['code']] = $variant->name; } $valueNames = []; $variantValueDescriptions = $variantValueDescriptions->keyBy('language_id'); foreach ($locales as $locale) { $variantValueDescription = $variantValueDescriptions[self::LANG_MAPPING[$locale['code']]]; $valueNames[$locale['code']] = $variantValueDescription->name; } $values[$productVariantValueId] = [ 'variant_value_id' => $productVariantValueId, 'name' => $valueNames, 'image' => $variantValue->image, ]; $items[$productVariantId] = [ 'variant_id' => $productVariantId, 'name' => $names, 'values' => $values, 'isImage' => (bool) $variantValue->image, ]; } } $items = array_values($items); foreach ($items as $index => $item) { $items[$index]['values'] = array_values($item['values']); } return $items; } /** * 生成 beike 产品描述 * @param $ocProduct * @return array */ private function generateDescriptions($ocProduct): array { $descriptions = []; $locales = locales(); $ocDescriptions = $this->ocdb->table('product_description') ->where('product_id', $ocProduct->product_id) ->get() ->keyBy('language_id') ->toArray(); foreach ($locales as $locale) { $ocDescription = $ocDescriptions[self::LANG_MAPPING[$locale['code']]]; $descriptions[$locale['code']] = [ 'name' => $ocDescription->name, 'content' => html_entity_decode($ocDescription->description, ENT_QUOTES), ]; } return $descriptions; } /** * 生成 beike 产品 SKU * * @param $ocProduct * @param $productVariants * @param $childProducts * @param array $variables * @return array */ private function generateSkus($ocProduct, $productVariants, $childProducts, array $variables): array { // 简单商品 if (count($productVariants) == 0) { return $this->generateSimpleSku($ocProduct); } $masterSku = $childSkus = []; // 有主商品 if (count($productVariants) > 0) { $masterSku = $this->generateMasterSku($ocProduct, $productVariants, $variables); } // 有子商品 if (count($childProducts) > 0) { $childSkus = $this->generateChildSkus($childProducts, $variables); } if ($masterSku) { array_push($childSkus, $masterSku); } return $childSkus; } /** * 获取简单商品 SKU * @param $ocProduct * @return array[] */ private function generateSimpleSku($ocProduct): array { $images = $this->ocProductImages[$ocProduct->product_id] ?? []; return [ [ 'image' => $images, 'model' => $ocProduct->model, 'sku' => $ocProduct->sku, 'price' => $ocProduct->price, 'origin_price' => $ocProduct->price, 'cost_price' => $ocProduct->cost_price, 'quantity' => $ocProduct->quantity, 'variants' => null, 'position' => $ocProduct->sort_order, 'is_default' => 1, ], ]; } /** * 获取主商品 SKU * @param $ocProduct * @param $productVariants * @param $variables * @return array */ private function generateMasterSku($ocProduct, $productVariants, $variables): array { $variants = $this->generateVariants($productVariants, $variables); $images = $this->ocProductImages[$ocProduct->product_id] ?? []; return [ 'image' => $images, 'model' => $ocProduct->model, 'sku' => $ocProduct->sku, 'price' => $ocProduct->price, 'origin_price' => $ocProduct->price, 'cost_price' => $ocProduct->cost_price, 'quantity' => $ocProduct->quantity, 'variants' => $variants, 'position' => $ocProduct->sort_order, 'is_default' => 1, ]; } /** * 获取子商品SKU * @param $childProducts * @param $variables * @return array */ private function generateChildSkus($childProducts, $variables): array { $items = []; foreach ($childProducts as $ocProduct) { $ocProductId = $ocProduct->product_id; $productVariants = $this->ocProductVariants[$ocProductId] ?? []; $images = $this->ocProductImages[$ocProductId] ?? []; $variants = $this->generateVariants($productVariants, $variables); $items[] = [ 'image' => $images, 'model' => $ocProduct->model, 'sku' => $ocProduct->sku, 'price' => $ocProduct->price, 'origin_price' => $ocProduct->price, 'cost_price' => $ocProduct->cost_price, 'quantity' => $ocProduct->quantity, 'variants' => $variants, 'position' => $ocProduct->sort_order, 'is_default' => 0, ]; } return $items; } /** * 生成商品 SKU 多规格数据 * * @param $productVariants * @param $variables * @return array */ private function generateVariants($productVariants, $variables): array { $items = []; foreach ($productVariants as $productVariant) { $variantId = $productVariant->variant_id; $variantValueId = $productVariant->variant_value_id; foreach ($variables as $index1 => $variable) { if ($variantId == $variable['variant_id']) { foreach ($variable['values'] as $index2 => $value) { if ($variantValueId == $value['variant_value_id']) { $items[$index1] = (string) $index2; } } } } } return $items; } /** * 生成商品分类关联 * * @param $ocProduct * @return array */ private function generateCategories($ocProduct): array { $ocProductCategories = $this->ocProductCategories[$ocProduct->product_id] ?? []; if ($ocProductCategories) { return $ocProductCategories->pluck('category_id')->toArray(); } return []; } /** * 清空产品相关数据 */ private function clearData() { Product::query()->truncate(); ProductSku::query()->truncate(); ProductDescription::query()->truncate(); } }