wyyl/beike/Console/Commands/MigrateFromOpenCart.php

461 lines
16 KiB
PHP

<?php
/**
* MigrateFromOpenCart.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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();
}
}