feat: update appraisal return address and test packaging assets
This commit is contained in:
@@ -18,6 +18,7 @@ class OrdersController
|
||||
public function index(Request $request)
|
||||
{
|
||||
$keyword = trim((string)$request->input('keyword', ''));
|
||||
$externalOrderNo = trim((string)$request->input('external_order_no', ''));
|
||||
$trackingNo = trim((string)$request->input('tracking_no', ''));
|
||||
$userMobile = trim((string)$request->input('user_mobile', ''));
|
||||
$status = trim((string)$request->input('status', ''));
|
||||
@@ -30,10 +31,12 @@ class OrdersController
|
||||
$query = Db::name('orders')
|
||||
->alias('o')
|
||||
->leftJoin('order_products p', 'p.order_id = o.id')
|
||||
->leftJoin('enterprise_customer_order_refs ecor', 'ecor.order_id = o.id')
|
||||
->field([
|
||||
'o.id',
|
||||
'o.order_no',
|
||||
'o.appraisal_no',
|
||||
'ecor.external_order_no',
|
||||
'o.service_provider',
|
||||
'o.order_status',
|
||||
'o.display_status',
|
||||
@@ -64,6 +67,12 @@ class OrdersController
|
||||
});
|
||||
}
|
||||
|
||||
if ($externalOrderNo !== '') {
|
||||
$query->whereRaw('ecor.external_order_no LIKE :external_order_no', [
|
||||
'external_order_no' => "%{$externalOrderNo}%",
|
||||
]);
|
||||
}
|
||||
|
||||
if ($trackingNo !== '') {
|
||||
$query->whereRaw(
|
||||
"EXISTS (SELECT 1 FROM order_logistics ol WHERE ol.order_id = o.id AND ol.logistics_type IN ('send_to_center', 'return_to_user') AND ol.tracking_no LIKE :tracking_no)",
|
||||
@@ -155,6 +164,7 @@ class OrdersController
|
||||
'id' => $orderId,
|
||||
'order_no' => $item['order_no'],
|
||||
'appraisal_no' => $item['appraisal_no'],
|
||||
'external_order_no' => (string)($item['external_order_no'] ?? ''),
|
||||
'product_name' => $item['product_name'] ?: '待完善物品信息',
|
||||
'category_name' => $item['category_name'] ?: '',
|
||||
'brand_name' => $item['brand_name'] ?: '',
|
||||
@@ -255,6 +265,7 @@ class OrdersController
|
||||
->where('order_id', $id)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
$enterpriseOrderRef = Db::name('enterprise_customer_order_refs')->where('order_id', $id)->find();
|
||||
$timeline = Db::name('order_timelines')
|
||||
->where('order_id', $id)
|
||||
->order('occurred_at', 'asc')
|
||||
@@ -343,6 +354,7 @@ class OrdersController
|
||||
'id' => (int)$order['id'],
|
||||
'order_no' => $order['order_no'],
|
||||
'appraisal_no' => $order['appraisal_no'],
|
||||
'external_order_no' => (string)($enterpriseOrderRef['external_order_no'] ?? ''),
|
||||
'service_provider' => $order['service_provider'],
|
||||
'service_provider_text' => $order['service_provider'] === 'zhongjian' ? '中检鉴定' : '实物鉴定',
|
||||
'price_package_name' => (string)($order['price_package_name'] ?? ''),
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace app\controller\app;
|
||||
|
||||
use app\support\ContentService;
|
||||
use app\support\FileStorageService;
|
||||
use support\Request;
|
||||
use support\think\Db;
|
||||
|
||||
@@ -9,11 +11,19 @@ class CatalogController
|
||||
{
|
||||
public function categories(Request $request)
|
||||
{
|
||||
$categoryVisuals = $this->categoryVisualMap($request);
|
||||
$list = Db::name('catalog_categories')
|
||||
->field(['id AS category_id', 'name AS category_name', 'code AS category_code'])
|
||||
->where('is_enabled', 1)
|
||||
->order('sort_order', 'asc')
|
||||
->select()
|
||||
->map(function ($item) use ($categoryVisuals) {
|
||||
$codeKey = $this->categoryMatchKey((string)$item['category_code']);
|
||||
$nameKey = $this->categoryMatchKey((string)$item['category_name']);
|
||||
$item['image_url'] = $categoryVisuals['code:' . $codeKey] ?? $categoryVisuals['name:' . $nameKey] ?? '';
|
||||
|
||||
return $item;
|
||||
})
|
||||
->toArray();
|
||||
|
||||
return api_success(['list' => $list]);
|
||||
@@ -39,4 +49,45 @@ class CatalogController
|
||||
]);
|
||||
}
|
||||
|
||||
private function categoryVisualMap(Request $request): array
|
||||
{
|
||||
$items = (new ContentService())->getHomeConfig()['category_visuals'] ?? [];
|
||||
if (!is_array($items)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$map = [];
|
||||
$storage = new FileStorageService();
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$imageUrl = trim((string)($item['image_url'] ?? ''));
|
||||
if ($imageUrl === '') {
|
||||
continue;
|
||||
}
|
||||
$imageUrl = $storage->normalizeUrl($imageUrl, $request);
|
||||
|
||||
$categoryCode = $this->categoryMatchKey((string)($item['category_code'] ?? ''));
|
||||
if ($categoryCode !== '') {
|
||||
$map['code:' . $categoryCode] = $imageUrl;
|
||||
}
|
||||
|
||||
$categoryName = $this->categoryMatchKey((string)($item['category_name'] ?? ''));
|
||||
if ($categoryName !== '') {
|
||||
$map['name:' . $categoryName] = $imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
private function categoryMatchKey(string $value): string
|
||||
{
|
||||
$value = trim($value);
|
||||
$normalized = preg_replace('/[\s\p{Cf}]+/u', '', $value);
|
||||
|
||||
return strtolower($normalized ?? $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,34 @@ class OrdersController
|
||||
return api_success($result, '运单已提交');
|
||||
}
|
||||
|
||||
public function saveReturnAddress(Request $request)
|
||||
{
|
||||
try {
|
||||
$auth = (new EnterpriseOpenApiAuthService())->authenticate($request);
|
||||
} catch (\Throwable $e) {
|
||||
return api_error($e->getMessage(), 401);
|
||||
}
|
||||
|
||||
$payload = json_decode($request->rawBody(), true);
|
||||
if (!is_array($payload)) {
|
||||
return api_error('请求体必须是合法 JSON 对象', 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = (new EnterpriseOrderService())->saveReturnAddress($auth['customer'], $payload);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return api_error($e->getMessage(), 422);
|
||||
} catch (\RuntimeException $e) {
|
||||
return api_error($e->getMessage(), 404);
|
||||
} catch (\Throwable $e) {
|
||||
return api_error('寄回地址保存失败', 500, [
|
||||
'detail' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
return api_success($result, '寄回地址已保存');
|
||||
}
|
||||
|
||||
public function servicePricePackages(Request $request)
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -299,6 +299,93 @@ class EnterpriseOrderService
|
||||
];
|
||||
}
|
||||
|
||||
public function saveReturnAddress(array $customer, array $payload): array
|
||||
{
|
||||
$externalOrderNo = trim((string)($payload['external_order_no'] ?? ''));
|
||||
if ($externalOrderNo === '') {
|
||||
throw new \InvalidArgumentException('external_order_no 不能为空');
|
||||
}
|
||||
|
||||
$returnAddress = $this->normalizeReturnAddress((array)($payload['return_address'] ?? []));
|
||||
if (!$returnAddress) {
|
||||
throw new \InvalidArgumentException('return_address 不能为空');
|
||||
}
|
||||
|
||||
$ref = Db::name('enterprise_customer_order_refs')
|
||||
->where('customer_id', (int)$customer['id'])
|
||||
->where('external_order_no', $externalOrderNo)
|
||||
->find();
|
||||
if (!$ref) {
|
||||
throw new \RuntimeException('订单不存在');
|
||||
}
|
||||
|
||||
$order = Db::name('orders')->where('id', (int)$ref['order_id'])->find();
|
||||
if (!$order) {
|
||||
throw new \RuntimeException('订单不存在');
|
||||
}
|
||||
|
||||
$returnLogistics = Db::name('order_logistics')
|
||||
->where('order_id', (int)$order['id'])
|
||||
->where('logistics_type', 'return_to_user')
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if (!empty($returnLogistics['tracking_no'])) {
|
||||
throw new \InvalidArgumentException('回寄运单已生成,当前不可再修改寄回地址');
|
||||
}
|
||||
|
||||
$existing = Db::name('order_return_addresses')->where('order_id', (int)$order['id'])->find();
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$updated = (bool)$existing;
|
||||
$snapshot = array_merge($returnAddress, [
|
||||
'user_address_id' => null,
|
||||
]);
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
if ($existing) {
|
||||
Db::name('order_return_addresses')->where('order_id', (int)$order['id'])->update(array_merge($snapshot, [
|
||||
'updated_at' => $now,
|
||||
]));
|
||||
$nodeText = '已更新寄回地址';
|
||||
} else {
|
||||
Db::name('order_return_addresses')->insert(array_merge($snapshot, [
|
||||
'order_id' => (int)$order['id'],
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]));
|
||||
$nodeText = '已确认寄回地址';
|
||||
}
|
||||
|
||||
Db::name('order_timelines')->insert([
|
||||
'order_id' => (int)$order['id'],
|
||||
'node_code' => 'return_address_selected',
|
||||
'node_text' => $nodeText,
|
||||
'node_desc' => sprintf(
|
||||
'大客户已确认寄回地址:%s%s%s%s',
|
||||
$returnAddress['province'],
|
||||
$returnAddress['city'],
|
||||
$returnAddress['district'],
|
||||
$returnAddress['detail_address']
|
||||
),
|
||||
'operator_type' => 'system',
|
||||
'operator_id' => null,
|
||||
'occurred_at' => $now,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return [
|
||||
'updated' => $updated,
|
||||
'return_address' => $this->formatReturnAddress($snapshot),
|
||||
'order' => $this->buildOrderProgress((int)$customer['id'], $ref, (string)$customer['customer_code']),
|
||||
];
|
||||
}
|
||||
|
||||
public function buildOrderProgress(int $customerId, array $ref, string $customerCode = ''): array
|
||||
{
|
||||
$order = Db::name('orders')->where('id', (int)$ref['order_id'])->find();
|
||||
@@ -309,6 +396,7 @@ class EnterpriseOrderService
|
||||
$timeline = Db::name('order_timelines')->where('order_id', (int)$order['id'])->order('occurred_at', 'asc')->select()->toArray();
|
||||
$sendLogistics = Db::name('order_logistics')->where('order_id', (int)$order['id'])->where('logistics_type', 'send_to_center')->order('id', 'desc')->find();
|
||||
$returnLogistics = Db::name('order_logistics')->where('order_id', (int)$order['id'])->where('logistics_type', 'return_to_user')->order('id', 'desc')->find();
|
||||
$returnAddress = Db::name('order_return_addresses')->where('order_id', (int)$order['id'])->find();
|
||||
$report = Db::name('reports')
|
||||
->where('order_id', (int)$order['id'])
|
||||
->where('report_status', 'published')
|
||||
@@ -339,6 +427,7 @@ class EnterpriseOrderService
|
||||
'occurred_at' => (string)$item['occurred_at'],
|
||||
], $timeline),
|
||||
'inbound_logistics' => $this->formatLogistics($sendLogistics),
|
||||
'return_address' => $returnAddress ? $this->formatReturnAddress($returnAddress) : null,
|
||||
'return_logistics' => $this->formatLogistics($returnLogistics),
|
||||
'report_summary' => $report ? [
|
||||
'report_no' => (string)$report['report_no'],
|
||||
@@ -586,4 +675,23 @@ class EnterpriseOrderService
|
||||
'latest_time' => (string)($logistics['latest_time'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
private function formatReturnAddress(array $item): array
|
||||
{
|
||||
return [
|
||||
'consignee' => (string)($item['consignee'] ?? ''),
|
||||
'mobile' => (string)($item['mobile'] ?? ''),
|
||||
'province' => (string)($item['province'] ?? ''),
|
||||
'city' => (string)($item['city'] ?? ''),
|
||||
'district' => (string)($item['district'] ?? ''),
|
||||
'detail_address' => (string)($item['detail_address'] ?? ''),
|
||||
'full_address' => trim(sprintf(
|
||||
'%s%s%s%s',
|
||||
$item['province'] ?? '',
|
||||
$item['city'] ?? '',
|
||||
$item['district'] ?? '',
|
||||
$item['detail_address'] ?? ''
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ Route::post('/api/app/address/default', [AppAddressesController::class, 'setDefa
|
||||
Route::post('/api/app/address/delete', [AppAddressesController::class, 'delete']);
|
||||
|
||||
Route::post('/api/open/v1/orders', [OpenOrdersController::class, 'create']);
|
||||
Route::post('/api/open/v1/orders/return-address', [OpenOrdersController::class, 'saveReturnAddress']);
|
||||
Route::post('/api/open/v1/orders/shipping', [OpenOrdersController::class, 'shipping']);
|
||||
Route::get('/api/open/v1/orders', [OpenOrdersController::class, 'detail']);
|
||||
Route::get('/api/open/v1/orders/{external_order_no}', [OpenOrdersController::class, 'detail']);
|
||||
|
||||
699
server-api/resources/catalog/known_brands.php
Normal file
699
server-api/resources/catalog/known_brands.php
Normal file
@@ -0,0 +1,699 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
[
|
||||
'code' => 'lv',
|
||||
'name' => '路易威登',
|
||||
'en_name' => 'Louis Vuitton',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 10,
|
||||
'source_tags' => ['interbrand', 'lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'chanel',
|
||||
'name' => '香奈儿',
|
||||
'en_name' => 'Chanel',
|
||||
'category_codes' => ['luxury_bag', 'jewelry', 'beauty'],
|
||||
'category_names' => ['奢侈品箱包', '首饰配饰', '高端美妆'],
|
||||
'sort_order' => 20,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'hermes',
|
||||
'name' => '爱马仕',
|
||||
'en_name' => 'Hermes',
|
||||
'category_codes' => ['luxury_bag', 'jewelry'],
|
||||
'category_names' => ['奢侈品箱包', '首饰配饰'],
|
||||
'sort_order' => 30,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'gucci',
|
||||
'name' => '古驰',
|
||||
'en_name' => 'Gucci',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 40,
|
||||
'source_tags' => ['interbrand', 'kering'],
|
||||
],
|
||||
[
|
||||
'code' => 'dior',
|
||||
'name' => '迪奥',
|
||||
'en_name' => 'Dior',
|
||||
'category_codes' => ['luxury_bag', 'beauty'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰', '高端美妆'],
|
||||
'sort_order' => 50,
|
||||
'source_tags' => ['interbrand', 'lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'prada',
|
||||
'name' => '普拉达',
|
||||
'en_name' => 'Prada',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 60,
|
||||
'source_tags' => ['interbrand', 'lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'celine',
|
||||
'name' => '思琳',
|
||||
'en_name' => 'Celine',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 70,
|
||||
'source_tags' => ['lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'loewe',
|
||||
'name' => '罗意威',
|
||||
'en_name' => 'Loewe',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 80,
|
||||
'source_tags' => ['lvmh', 'lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'fendi',
|
||||
'name' => '芬迪',
|
||||
'en_name' => 'Fendi',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 90,
|
||||
'source_tags' => ['lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'balenciaga',
|
||||
'name' => '巴黎世家',
|
||||
'en_name' => 'Balenciaga',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 100,
|
||||
'source_tags' => ['kering', 'lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'bottega_veneta',
|
||||
'name' => '葆蝶家',
|
||||
'en_name' => 'Bottega Veneta',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 110,
|
||||
'source_tags' => ['kering'],
|
||||
],
|
||||
[
|
||||
'code' => 'saint_laurent',
|
||||
'name' => '圣罗兰',
|
||||
'en_name' => 'Saint Laurent',
|
||||
'category_codes' => ['luxury_bag', 'beauty'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰', '高端美妆'],
|
||||
'sort_order' => 120,
|
||||
'source_tags' => ['kering', 'loreal'],
|
||||
],
|
||||
[
|
||||
'code' => 'burberry',
|
||||
'name' => '博柏利',
|
||||
'en_name' => 'Burberry',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 130,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'coach',
|
||||
'name' => '蔻驰',
|
||||
'en_name' => 'Coach',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 140,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'michael_kors',
|
||||
'name' => '迈克高仕',
|
||||
'en_name' => 'Michael Kors',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 150,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'miu_miu',
|
||||
'name' => '缪缪',
|
||||
'en_name' => 'Miu Miu',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 160,
|
||||
'source_tags' => ['lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'givenchy',
|
||||
'name' => '纪梵希',
|
||||
'en_name' => 'Givenchy',
|
||||
'category_codes' => ['luxury_bag', 'beauty'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰', '高端美妆'],
|
||||
'sort_order' => 170,
|
||||
'source_tags' => ['lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'valentino',
|
||||
'name' => '华伦天奴',
|
||||
'en_name' => 'Valentino',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 180,
|
||||
'source_tags' => ['lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'versace',
|
||||
'name' => '范思哲',
|
||||
'en_name' => 'Versace',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 190,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'alexander_mcqueen',
|
||||
'name' => '亚历山大麦昆',
|
||||
'en_name' => 'Alexander McQueen',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 200,
|
||||
'source_tags' => ['kering'],
|
||||
],
|
||||
[
|
||||
'code' => 'marc_jacobs',
|
||||
'name' => '马克雅可布',
|
||||
'en_name' => 'Marc Jacobs',
|
||||
'category_codes' => ['luxury_bag'],
|
||||
'category_names' => ['奢侈品箱包', '潮流服饰'],
|
||||
'sort_order' => 210,
|
||||
'source_tags' => ['lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'nike',
|
||||
'name' => '耐克',
|
||||
'en_name' => 'Nike',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 300,
|
||||
'source_tags' => ['interbrand', 'stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'jordan',
|
||||
'name' => '乔丹',
|
||||
'en_name' => 'Jordan',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 310,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'adidas',
|
||||
'name' => '阿迪达斯',
|
||||
'en_name' => 'Adidas',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 320,
|
||||
'source_tags' => ['interbrand', 'stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'new_balance',
|
||||
'name' => '新百伦',
|
||||
'en_name' => 'New Balance',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 330,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'asics',
|
||||
'name' => '亚瑟士',
|
||||
'en_name' => 'ASICS',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 340,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'puma',
|
||||
'name' => '彪马',
|
||||
'en_name' => 'Puma',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 350,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'converse',
|
||||
'name' => '匡威',
|
||||
'en_name' => 'Converse',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 360,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'vans',
|
||||
'name' => '范斯',
|
||||
'en_name' => 'Vans',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 370,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'reebok',
|
||||
'name' => '锐步',
|
||||
'en_name' => 'Reebok',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 380,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'salomon',
|
||||
'name' => '萨洛蒙',
|
||||
'en_name' => 'Salomon',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 390,
|
||||
'source_tags' => ['lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'on_running',
|
||||
'name' => '昂跑',
|
||||
'en_name' => 'On',
|
||||
'category_codes' => ['sneaker'],
|
||||
'category_names' => ['潮流鞋类', '潮流服饰'],
|
||||
'sort_order' => 400,
|
||||
'source_tags' => ['lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'supreme',
|
||||
'name' => 'Supreme',
|
||||
'en_name' => 'Supreme',
|
||||
'category_codes' => [],
|
||||
'category_names' => ['潮流服饰'],
|
||||
'sort_order' => 430,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'stussy',
|
||||
'name' => 'Stussy',
|
||||
'en_name' => 'Stussy',
|
||||
'category_codes' => [],
|
||||
'category_names' => ['潮流服饰'],
|
||||
'sort_order' => 440,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'off_white',
|
||||
'name' => 'Off-White',
|
||||
'en_name' => 'Off-White',
|
||||
'category_codes' => [],
|
||||
'category_names' => ['潮流服饰'],
|
||||
'sort_order' => 450,
|
||||
'source_tags' => ['lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'fear_of_god',
|
||||
'name' => 'Fear of God',
|
||||
'en_name' => 'Fear of God',
|
||||
'category_codes' => [],
|
||||
'category_names' => ['潮流服饰'],
|
||||
'sort_order' => 460,
|
||||
'source_tags' => ['stockx'],
|
||||
],
|
||||
[
|
||||
'code' => 'arc_teryx',
|
||||
'name' => '始祖鸟',
|
||||
'en_name' => 'Arc\'teryx',
|
||||
'category_codes' => [],
|
||||
'category_names' => ['潮流服饰'],
|
||||
'sort_order' => 470,
|
||||
'source_tags' => ['lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'moncler',
|
||||
'name' => '盟可睐',
|
||||
'en_name' => 'Moncler',
|
||||
'category_codes' => [],
|
||||
'category_names' => ['潮流服饰'],
|
||||
'sort_order' => 480,
|
||||
'source_tags' => ['lyst'],
|
||||
],
|
||||
[
|
||||
'code' => 'rolex',
|
||||
'name' => '劳力士',
|
||||
'en_name' => 'Rolex',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 500,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'omega',
|
||||
'name' => '欧米茄',
|
||||
'en_name' => 'Omega',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 510,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'cartier',
|
||||
'name' => '卡地亚',
|
||||
'en_name' => 'Cartier',
|
||||
'category_codes' => ['watch', 'jewelry'],
|
||||
'category_names' => ['腕表', '首饰配饰'],
|
||||
'sort_order' => 520,
|
||||
'source_tags' => ['richemont', 'interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'patek_philippe',
|
||||
'name' => '百达翡丽',
|
||||
'en_name' => 'Patek Philippe',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 530,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'audemars_piguet',
|
||||
'name' => '爱彼',
|
||||
'en_name' => 'Audemars Piguet',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 540,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'iwc',
|
||||
'name' => '万国',
|
||||
'en_name' => 'IWC Schaffhausen',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 550,
|
||||
'source_tags' => ['richemont'],
|
||||
],
|
||||
[
|
||||
'code' => 'jaeger_lecoultre',
|
||||
'name' => '积家',
|
||||
'en_name' => 'Jaeger-LeCoultre',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 560,
|
||||
'source_tags' => ['richemont'],
|
||||
],
|
||||
[
|
||||
'code' => 'vacheron_constantin',
|
||||
'name' => '江诗丹顿',
|
||||
'en_name' => 'Vacheron Constantin',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 570,
|
||||
'source_tags' => ['richemont'],
|
||||
],
|
||||
[
|
||||
'code' => 'panerai',
|
||||
'name' => '沛纳海',
|
||||
'en_name' => 'Panerai',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 580,
|
||||
'source_tags' => ['richemont'],
|
||||
],
|
||||
[
|
||||
'code' => 'tag_heuer',
|
||||
'name' => '泰格豪雅',
|
||||
'en_name' => 'TAG Heuer',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 590,
|
||||
'source_tags' => ['lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'longines',
|
||||
'name' => '浪琴',
|
||||
'en_name' => 'Longines',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 600,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'tissot',
|
||||
'name' => '天梭',
|
||||
'en_name' => 'Tissot',
|
||||
'category_codes' => ['watch'],
|
||||
'category_names' => ['腕表'],
|
||||
'sort_order' => 610,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'tiffany_co',
|
||||
'name' => '蒂芙尼',
|
||||
'en_name' => 'Tiffany & Co.',
|
||||
'category_codes' => ['jewelry'],
|
||||
'category_names' => ['首饰配饰'],
|
||||
'sort_order' => 700,
|
||||
'source_tags' => ['interbrand', 'lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'van_cleef_arpels',
|
||||
'name' => '梵克雅宝',
|
||||
'en_name' => 'Van Cleef & Arpels',
|
||||
'category_codes' => ['jewelry'],
|
||||
'category_names' => ['首饰配饰'],
|
||||
'sort_order' => 710,
|
||||
'source_tags' => ['richemont'],
|
||||
],
|
||||
[
|
||||
'code' => 'bulgari',
|
||||
'name' => '宝格丽',
|
||||
'en_name' => 'Bulgari',
|
||||
'category_codes' => ['jewelry', 'watch'],
|
||||
'category_names' => ['首饰配饰', '腕表'],
|
||||
'sort_order' => 720,
|
||||
'source_tags' => ['lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'chaumet',
|
||||
'name' => '尚美巴黎',
|
||||
'en_name' => 'Chaumet',
|
||||
'category_codes' => ['jewelry'],
|
||||
'category_names' => ['首饰配饰'],
|
||||
'sort_order' => 730,
|
||||
'source_tags' => ['lvmh'],
|
||||
],
|
||||
[
|
||||
'code' => 'boucheron',
|
||||
'name' => '宝诗龙',
|
||||
'en_name' => 'Boucheron',
|
||||
'category_codes' => ['jewelry'],
|
||||
'category_names' => ['首饰配饰'],
|
||||
'sort_order' => 740,
|
||||
'source_tags' => ['kering'],
|
||||
],
|
||||
[
|
||||
'code' => 'pomellato',
|
||||
'name' => '宝曼兰朵',
|
||||
'en_name' => 'Pomellato',
|
||||
'category_codes' => ['jewelry'],
|
||||
'category_names' => ['首饰配饰'],
|
||||
'sort_order' => 750,
|
||||
'source_tags' => ['kering'],
|
||||
],
|
||||
[
|
||||
'code' => 'chopard',
|
||||
'name' => '萧邦',
|
||||
'en_name' => 'Chopard',
|
||||
'category_codes' => ['jewelry', 'watch'],
|
||||
'category_names' => ['首饰配饰', '腕表'],
|
||||
'sort_order' => 760,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'swarovski',
|
||||
'name' => '施华洛世奇',
|
||||
'en_name' => 'Swarovski',
|
||||
'category_codes' => ['jewelry'],
|
||||
'category_names' => ['首饰配饰'],
|
||||
'sort_order' => 770,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'pandora',
|
||||
'name' => '潘多拉',
|
||||
'en_name' => 'Pandora',
|
||||
'category_codes' => ['jewelry'],
|
||||
'category_names' => ['首饰配饰'],
|
||||
'sort_order' => 780,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'lancome',
|
||||
'name' => '兰蔻',
|
||||
'en_name' => 'Lancome',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 900,
|
||||
'source_tags' => ['loreal'],
|
||||
],
|
||||
[
|
||||
'code' => 'ysl_beauty',
|
||||
'name' => '圣罗兰美妆',
|
||||
'en_name' => 'Yves Saint Laurent Beauty',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 910,
|
||||
'source_tags' => ['loreal'],
|
||||
],
|
||||
[
|
||||
'code' => 'armani_beauty',
|
||||
'name' => '阿玛尼美妆',
|
||||
'en_name' => 'Armani Beauty',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 920,
|
||||
'source_tags' => ['loreal'],
|
||||
],
|
||||
[
|
||||
'code' => 'kiehls',
|
||||
'name' => '科颜氏',
|
||||
'en_name' => 'Kiehl\'s',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 930,
|
||||
'source_tags' => ['loreal'],
|
||||
],
|
||||
[
|
||||
'code' => 'shiseido',
|
||||
'name' => '资生堂',
|
||||
'en_name' => 'Shiseido',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 940,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'sk_ii',
|
||||
'name' => 'SK-II',
|
||||
'en_name' => 'SK-II',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 950,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'la_mer',
|
||||
'name' => '海蓝之谜',
|
||||
'en_name' => 'La Mer',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 960,
|
||||
'source_tags' => ['estee_lauder'],
|
||||
],
|
||||
[
|
||||
'code' => 'estee_lauder',
|
||||
'name' => '雅诗兰黛',
|
||||
'en_name' => 'Estee Lauder',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 970,
|
||||
'source_tags' => ['estee_lauder'],
|
||||
],
|
||||
[
|
||||
'code' => 'clinique',
|
||||
'name' => '倩碧',
|
||||
'en_name' => 'Clinique',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 980,
|
||||
'source_tags' => ['estee_lauder'],
|
||||
],
|
||||
[
|
||||
'code' => 'mac',
|
||||
'name' => '魅可',
|
||||
'en_name' => 'MAC',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 990,
|
||||
'source_tags' => ['estee_lauder'],
|
||||
],
|
||||
[
|
||||
'code' => 'bobbi_brown',
|
||||
'name' => '芭比波朗',
|
||||
'en_name' => 'Bobbi Brown',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 1000,
|
||||
'source_tags' => ['estee_lauder'],
|
||||
],
|
||||
[
|
||||
'code' => 'tom_ford_beauty',
|
||||
'name' => '汤姆福特美妆',
|
||||
'en_name' => 'Tom Ford Beauty',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 1010,
|
||||
'source_tags' => ['estee_lauder'],
|
||||
],
|
||||
[
|
||||
'code' => 'nars',
|
||||
'name' => 'NARS',
|
||||
'en_name' => 'NARS',
|
||||
'category_codes' => ['beauty'],
|
||||
'category_names' => ['高端美妆'],
|
||||
'sort_order' => 1020,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'apple',
|
||||
'name' => '苹果',
|
||||
'en_name' => 'Apple',
|
||||
'category_codes' => ['digital'],
|
||||
'category_names' => ['3C 数码'],
|
||||
'sort_order' => 1200,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'samsung',
|
||||
'name' => '三星',
|
||||
'en_name' => 'Samsung',
|
||||
'category_codes' => ['digital'],
|
||||
'category_names' => ['3C 数码'],
|
||||
'sort_order' => 1210,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'huawei',
|
||||
'name' => '华为',
|
||||
'en_name' => 'Huawei',
|
||||
'category_codes' => ['digital'],
|
||||
'category_names' => ['3C 数码'],
|
||||
'sort_order' => 1220,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'xiaomi',
|
||||
'name' => '小米',
|
||||
'en_name' => 'Xiaomi',
|
||||
'category_codes' => ['digital'],
|
||||
'category_names' => ['3C 数码'],
|
||||
'sort_order' => 1230,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
[
|
||||
'code' => 'sony',
|
||||
'name' => '索尼',
|
||||
'en_name' => 'Sony',
|
||||
'category_codes' => ['digital'],
|
||||
'category_names' => ['3C 数码'],
|
||||
'sort_order' => 1240,
|
||||
'source_tags' => ['interbrand'],
|
||||
],
|
||||
];
|
||||
296
server-api/tools/import_known_brands.php
Normal file
296
server-api/tools/import_known_brands.php
Normal file
@@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
const LEGACY_CODE_ALIASES = [
|
||||
'tiffany_co' => ['1'],
|
||||
];
|
||||
|
||||
$dryRun = in_array('--dry-run', $argv, true);
|
||||
$brandFile = dirname(__DIR__) . '/resources/catalog/known_brands.php';
|
||||
|
||||
if (!is_file($brandFile)) {
|
||||
fwrite(STDERR, "Known brand file not found: {$brandFile}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$brands = require $brandFile;
|
||||
if (!is_array($brands)) {
|
||||
fwrite(STDERR, "Known brand file must return an array.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->safeLoad();
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||
$_ENV['DB_PORT'] ?? '3306',
|
||||
$_ENV['DB_DATABASE'] ?? '',
|
||||
$_ENV['DB_CHARSET'] ?? 'utf8mb4'
|
||||
);
|
||||
|
||||
$pdo = new PDO(
|
||||
$dsn,
|
||||
$_ENV['DB_USERNAME'] ?? '',
|
||||
$_ENV['DB_PASSWORD'] ?? '',
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
function normalize_list(mixed $value): array
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($value as $item) {
|
||||
$text = trim((string)$item);
|
||||
if ($text !== '') {
|
||||
$items[] = $text;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($items));
|
||||
}
|
||||
|
||||
function decode_json_list(mixed $value): array
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return normalize_list($value);
|
||||
}
|
||||
if (!is_string($value) || trim($value) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($value, true);
|
||||
return normalize_list(is_array($decoded) ? $decoded : []);
|
||||
}
|
||||
|
||||
function stable_json_list(array $value): string
|
||||
{
|
||||
$items = normalize_list($value);
|
||||
sort($items, SORT_STRING);
|
||||
return json_encode($items, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
function load_enabled_categories(PDO $pdo): array
|
||||
{
|
||||
$rows = $pdo->query(
|
||||
"SELECT id, name, code, supported_service_types
|
||||
FROM catalog_categories
|
||||
WHERE is_enabled = 1
|
||||
ORDER BY sort_order ASC, id ASC"
|
||||
)->fetchAll();
|
||||
|
||||
$byCode = [];
|
||||
$byName = [];
|
||||
$byId = [];
|
||||
foreach ($rows as $row) {
|
||||
$category = [
|
||||
'id' => (int)$row['id'],
|
||||
'name' => (string)$row['name'],
|
||||
'code' => (string)$row['code'],
|
||||
'supported_service_types' => decode_json_list($row['supported_service_types'] ?? null),
|
||||
];
|
||||
$byId[$category['id']] = $category;
|
||||
$byCode[strtolower($category['code'])] = $category;
|
||||
$byName[$category['name']] = $category;
|
||||
}
|
||||
|
||||
return [$byCode, $byName, $byId];
|
||||
}
|
||||
|
||||
function match_categories(array $brand, array $categoriesByCode, array $categoriesByName): array
|
||||
{
|
||||
$matched = [];
|
||||
|
||||
foreach (normalize_list($brand['category_codes'] ?? []) as $code) {
|
||||
$key = strtolower($code);
|
||||
if (isset($categoriesByCode[$key])) {
|
||||
$matched[$categoriesByCode[$key]['id']] = $categoriesByCode[$key];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (normalize_list($brand['category_names'] ?? []) as $name) {
|
||||
if (isset($categoriesByName[$name])) {
|
||||
$matched[$categoriesByName[$name]['id']] = $categoriesByName[$name];
|
||||
}
|
||||
}
|
||||
|
||||
return $matched;
|
||||
}
|
||||
|
||||
function infer_supported_service_types(array $categories): array
|
||||
{
|
||||
$serviceTypes = [];
|
||||
foreach ($categories as $category) {
|
||||
foreach ($category['supported_service_types'] as $serviceType) {
|
||||
$serviceTypes[$serviceType] = $serviceType;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$serviceTypes) {
|
||||
$serviceTypes['anxinyan'] = 'anxinyan';
|
||||
}
|
||||
|
||||
return array_values($serviceTypes);
|
||||
}
|
||||
|
||||
function find_existing_brand(PDO $pdo, string $code): ?array
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT * FROM catalog_brands WHERE code = ? LIMIT 1');
|
||||
$stmt->execute([$code]);
|
||||
$row = $stmt->fetch();
|
||||
if ($row) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
foreach (LEGACY_CODE_ALIASES[$code] ?? [] as $legacyCode) {
|
||||
$stmt->execute([$legacyCode]);
|
||||
$legacy = $stmt->fetch();
|
||||
if ($legacy) {
|
||||
return $legacy;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function existing_relation_ids(PDO $pdo, int $brandId): array
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT category_id FROM catalog_brand_categories WHERE brand_id = ?');
|
||||
$stmt->execute([$brandId]);
|
||||
|
||||
$ids = [];
|
||||
foreach ($stmt->fetchAll() as $row) {
|
||||
$ids[(int)$row['category_id']] = true;
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
[$categoriesByCode, $categoriesByName, $categoriesById] = load_enabled_categories($pdo);
|
||||
|
||||
$stats = [
|
||||
'brands_total' => count($brands),
|
||||
'inserted' => 0,
|
||||
'updated' => 0,
|
||||
'enabled_existing' => 0,
|
||||
'legacy_code_repaired' => 0,
|
||||
'relations_inserted' => 0,
|
||||
'skipped_no_enabled_category' => 0,
|
||||
'skipped_invalid' => 0,
|
||||
];
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$insertBrand = $pdo->prepare(
|
||||
'INSERT INTO catalog_brands (name, en_name, code, logo, sort_order, is_enabled, supported_service_types, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?)'
|
||||
);
|
||||
$updateBrand = $pdo->prepare(
|
||||
'UPDATE catalog_brands
|
||||
SET name = ?, en_name = ?, code = ?, sort_order = ?, is_enabled = 1, supported_service_types = ?, updated_at = ?
|
||||
WHERE id = ?'
|
||||
);
|
||||
$insertRelation = $pdo->prepare(
|
||||
'INSERT INTO catalog_brand_categories (brand_id, category_id, created_at)
|
||||
VALUES (?, ?, ?)'
|
||||
);
|
||||
|
||||
if (!$dryRun) {
|
||||
$pdo->beginTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($brands as $brand) {
|
||||
$code = trim((string)($brand['code'] ?? ''));
|
||||
$name = trim((string)($brand['name'] ?? ''));
|
||||
$enName = trim((string)($brand['en_name'] ?? ''));
|
||||
if ($code === '' || $name === '' || $enName === '') {
|
||||
$stats['skipped_invalid']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$matchedCategories = match_categories($brand, $categoriesByCode, $categoriesByName);
|
||||
if (!$matchedCategories) {
|
||||
$stats['skipped_no_enabled_category']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$serviceTypes = infer_supported_service_types($matchedCategories);
|
||||
$serviceTypesJson = stable_json_list($serviceTypes);
|
||||
$sortOrder = (int)($brand['sort_order'] ?? 0);
|
||||
$existing = find_existing_brand($pdo, $code);
|
||||
|
||||
if ($existing) {
|
||||
$brandId = (int)$existing['id'];
|
||||
$wasDisabled = (int)$existing['is_enabled'] !== 1;
|
||||
$wasLegacyCode = (string)$existing['code'] !== $code;
|
||||
|
||||
$existingJson = stable_json_list(decode_json_list($existing['supported_service_types'] ?? null));
|
||||
$needsUpdate = $wasDisabled
|
||||
|| $wasLegacyCode
|
||||
|| (string)$existing['name'] !== $name
|
||||
|| (string)$existing['en_name'] !== $enName
|
||||
|| (int)$existing['sort_order'] !== $sortOrder
|
||||
|| $existingJson !== $serviceTypesJson;
|
||||
|
||||
if ($needsUpdate) {
|
||||
$stats['updated']++;
|
||||
if ($wasDisabled) {
|
||||
$stats['enabled_existing']++;
|
||||
}
|
||||
if ($wasLegacyCode) {
|
||||
$stats['legacy_code_repaired']++;
|
||||
}
|
||||
if (!$dryRun) {
|
||||
$updateBrand->execute([$name, $enName, $code, $sortOrder, $serviceTypesJson, $now, $brandId]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$stats['inserted']++;
|
||||
if ($dryRun) {
|
||||
$brandId = 0;
|
||||
} else {
|
||||
$insertBrand->execute([$name, $enName, $code, '', $sortOrder, $serviceTypesJson, $now, $now]);
|
||||
$brandId = (int)$pdo->lastInsertId();
|
||||
}
|
||||
}
|
||||
|
||||
$existingCategoryIds = $brandId > 0 ? existing_relation_ids($pdo, $brandId) : [];
|
||||
foreach (array_keys($matchedCategories) as $categoryId) {
|
||||
if (isset($existingCategoryIds[(int)$categoryId])) {
|
||||
continue;
|
||||
}
|
||||
$stats['relations_inserted']++;
|
||||
if (!$dryRun && $brandId > 0) {
|
||||
$insertRelation->execute([$brandId, (int)$categoryId, $now]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$dryRun) {
|
||||
$pdo->commit();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
if (!$dryRun && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
fwrite(STDERR, "IMPORT_KNOWN_BRANDS_FAILED\n");
|
||||
fwrite(STDERR, $e->getMessage() . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo $dryRun ? "DRY_RUN\n" : "IMPORT_KNOWN_BRANDS_OK\n";
|
||||
echo "ENABLED_CATEGORIES=" . count($categoriesById) . PHP_EOL;
|
||||
foreach ($stats as $key => $value) {
|
||||
echo strtoupper($key) . '=' . $value . PHP_EOL;
|
||||
}
|
||||
Reference in New Issue
Block a user