Explorar el Código

feat(merchant): 添加1688分销严选入库商品功能

- 新增alibaba/product路由组,包含商品列表、详情、删除和正式入库接口
- 实现Product控制器,提供lst、detail、delete和importStore方法
- 添加1688商品正式入库逻辑,支持单个和批量导入到store_product表
- 实现商品属性和规格数据的完整写入流程
- 修复商品列表查询时遗漏spu_id条件的问题
shichen hace 3 meses
padre
commit
aa617c6c2c

+ 1 - 1
app/common/repositories/store/product/ProductRepository.php

@@ -733,7 +733,7 @@ class ProductRepository extends BaseRepository
733 733
      */
734 734
     public function getList(?int $merId, array $where, int $page, int $limit)
735 735
     {
736
-        $query = $this->dao->search($merId, $where)->whereNull('spu_id')->with(['merCateId.category', 'storeCategory', 'brand']);
736
+        $query = $this->dao->search($merId, $where)->with(['merCateId.category', 'storeCategory', 'brand']);
737 737
         $count = $query->count($this->dao->getPk());
738 738
         $list = $query->page($page, $limit)->setOption('field', [])->field($this->filed)->select();
739 739
         $list->append(['max_extension', 'min_extension']);

+ 226 - 0
app/controller/merchant/alibaba/Product.php

@@ -0,0 +1,226 @@
1
+<?php
2
+
3
+namespace app\controller\merchant\alibaba;
4
+
5
+use think\facade\Db;
6
+use app\services\ThirdParty\AlibabaAgent\ProductService;
7
+
8
+class Product
9
+{
10
+    /**
11
+     * 已入库的1688商品列表
12
+     *
13
+     * 展示 rrx_alibaba_import_goods 表中的商品数据
14
+     * GET /merchant/alibaba/product/lst?page=1&limit=20&keyword=&status=
15
+     *
16
+     * @return \think\Response
17
+     */
18
+    public function lst()
19
+    {
20
+        $request = app('request');
21
+        $page    = (int)($request->param('page', 1));
22
+        $limit   = (int)($request->param('limit', 20));
23
+        $keyword = $request->param('keyword', '');
24
+        $status  = $request->param('status', '');
25
+
26
+        $query = Db::name('alibaba_import_goods');
27
+
28
+        // 关键词搜索(商品名称)
29
+        if (!empty($keyword)) {
30
+            $query->where('product_name', 'like', '%' . $keyword . '%');
31
+        }
32
+
33
+        // 状态筛选
34
+        if ($status !== '') {
35
+            $query->where('status', (int)$status);
36
+        }
37
+
38
+        $count = $query->count('id');
39
+
40
+        $list = $query
41
+            ->order('create_time', 'DESC')
42
+            ->page($page, $limit)
43
+            ->select()
44
+            ->toArray();
45
+
46
+        // 格式化返回数据
47
+        foreach ($list as &$item) {
48
+            $item['id']          = (int)$item['id'];
49
+            $item['product_id']  = (int)$item['product_id'];
50
+            $item['status']      = (int)$item['status'];
51
+            $item['create_time'] = (string)$item['create_time'];
52
+
53
+            // 解析 product_info JSON
54
+            $productInfo = [];
55
+            if (!empty($item['product_info'])) {
56
+                $productInfo = json_decode($item['product_info'], true) ?: [];
57
+            }
58
+            $item['product_info'] = $productInfo;
59
+
60
+            // 提取关键展示字段
61
+            $item['price']       = $productInfo['price'] ?? 0;
62
+            $item['minPrice']    = $productInfo['minPrice'] ?? 0;
63
+            $item['maxPrice']    = $productInfo['maxPrice'] ?? 0;
64
+            $item['stock']       = $productInfo['stock'] ?? 0;
65
+            $item['unit']        = $productInfo['unit'] ?? '件';
66
+            $item['sku_count']   = isset($productInfo['skuList']) ? count($productInfo['skuList']) : 0;
67
+            $item['image_list']  = $productInfo['imageList'] ?? [];
68
+            $item['shop_name']   = $productInfo['shopName'] ?? '';
69
+            $item['category_name'] = $productInfo['categoryName'] ?? '';
70
+
71
+            // 加价标记
72
+            $item['markup_rate'] = $productInfo['markupRate'] ?? 0;
73
+            $item['original_min_price'] = $productInfo['originalMinPrice'] ?? 0;
74
+            $item['original_max_price'] = $productInfo['originalMaxPrice'] ?? 0;
75
+        }
76
+        unset($item);
77
+
78
+        return json([
79
+            'code' => 200,
80
+            'message' => 'success',
81
+            'data' => [
82
+                'list'  => $list,
83
+                'count' => $count,
84
+                'page'  => $page,
85
+                'limit' => $limit,
86
+            ],
87
+        ]);
88
+    }
89
+
90
+    /**
91
+     * 获取单个入库商品的详细信息
92
+     *
93
+     * GET /merchant/alibaba/product/detail?id=123
94
+     *
95
+     * @return \think\Response
96
+     */
97
+    public function detail()
98
+    {
99
+        $request = app('request');
100
+        $id = (int)$request->param('id', 0);
101
+        if ($id <= 0) {
102
+            return json(['code' => 400, 'message' => '参数错误:id不能为空']);
103
+        }
104
+
105
+        $item = Db::name('alibaba_import_goods')
106
+            ->where('id', $id)
107
+            ->find();
108
+
109
+        if (!$item) {
110
+            return json(['code' => 404, 'message' => '商品不存在']);
111
+        }
112
+
113
+        $item['id']         = (int)$item['id'];
114
+        $item['product_id'] = (int)$item['product_id'];
115
+        $item['status']     = (int)$item['status'];
116
+
117
+        // 解析 product_info JSON
118
+        if (!empty($item['product_info'])) {
119
+            $item['product_info'] = json_decode($item['product_info'], true) ?: [];
120
+        }
121
+
122
+        return json([
123
+            'code' => 200,
124
+            'message' => 'success',
125
+            'data' => $item,
126
+        ]);
127
+    }
128
+
129
+    /**
130
+     * 删除入库商品(软删除:修改status为0)
131
+     *
132
+     * POST /merchant/alibaba/product/delete
133
+     * Body: id=123
134
+     *
135
+     * @return \think\Response
136
+     */
137
+    public function delete()
138
+    {
139
+        $request = app('request');
140
+        $id = (int)$request->param('id', 0);
141
+        if ($id <= 0) {
142
+            return json(['code' => 400, 'message' => '参数错误:id不能为空']);
143
+        }
144
+
145
+        $exists = Db::name('alibaba_import_goods')->where('id', $id)->find();
146
+        if (!$exists) {
147
+            return json(['code' => 404, 'message' => '商品不存在']);
148
+        }
149
+
150
+        Db::name('alibaba_import_goods')->where('id', $id)->update(['status' => 0]);
151
+
152
+        return json(['code' => 200, 'message' => '删除成功']);
153
+    }
154
+
155
+    /**
156
+     * 正式入库:将 alibaba_import_goods 表中的商品写入 store_product 系列表
157
+     *
158
+     * 业务场景:从 alibaba_import_goods 表中选择已导入的1688商品,
159
+     * 正式写入到 rrx_store_product、rrx_store_product_attr、rrx_store_product_attr_value 表中
160
+     *
161
+     * POST /merchant/alibaba/product/importStore
162
+     * Body: id=123   (alibaba_import_goods 表的主键ID)
163
+     * 或 Body: ids=123,456  (批量入库,逗号分隔)
164
+     *
165
+     * @param ProductService $productService
166
+     * @return \think\Response
167
+     */
168
+    public function importStore(ProductService $productService)
169
+    {
170
+        $request = app('request');
171
+        $id  = $request->param('id', '');
172
+        $ids = $request->param('ids', '');
173
+
174
+        // 解析要入库的ID列表
175
+        $importIds = [];
176
+        if (!empty($ids)) {
177
+            $parts = explode(',', $ids);
178
+            foreach ($parts as $part) {
179
+                $part = (int)trim($part);
180
+                if ($part > 0) $importIds[] = $part;
181
+            }
182
+        } elseif (!empty($id)) {
183
+            $importIds[] = (int)$id;
184
+        }
185
+
186
+        if (empty($importIds)) {
187
+            return json(['code' => 400, 'message' => '参数错误:请传入id或ids']);
188
+        }
189
+
190
+        // mer_id 写死 3447
191
+        $merId = 3447;
192
+
193
+        $successList = [];
194
+        $failList    = [];
195
+
196
+        foreach ($importIds as $importId) {
197
+            $result = $productService->importToStoreProduct($importId, $merId);
198
+            if ($result['code'] === 200) {
199
+                $successList[] = [
200
+                    'import_goods_id' => $importId,
201
+                    'product_id'      => $result['product_id'],
202
+                    'msg'             => $result['msg'],
203
+                ];
204
+            } else {
205
+                $failList[] = [
206
+                    'import_goods_id' => $importId,
207
+                    'msg'             => $result['msg'],
208
+                ];
209
+            }
210
+        }
211
+
212
+        $message = count($successList) . '个商品入库成功';
213
+        if (!empty($failList)) {
214
+            $message .= ',' . count($failList) . '个商品入库失败';
215
+        }
216
+
217
+        return json([
218
+            'code'    => 200,
219
+            'message' => $message,
220
+            'data'    => [
221
+                'success' => $successList,
222
+                'fail'    => $failList,
223
+            ],
224
+        ]);
225
+    }
226
+}

+ 257 - 0
app/services/ThirdParty/AlibabaAgent/ProductService.php

@@ -355,6 +355,263 @@ class ProductService extends AlibabaAgentBaseService
355 355
         return round($price * 1.5, 2);
356 356
     }
357 357
 
358
+    // ================================================================
359
+    //  商品入库到 store_product 表
360
+    // ================================================================
361
+
362
+    /**
363
+     * 将 alibaba_import_goods 表中的商品数据写入 store_product 系列表
364
+     *
365
+     * 业务场景:从 alibaba_import_goods 表中选择已入库的1688商品,正式写入到
366
+     * rrx_store_product、rrx_store_product_attr、rrx_store_product_attr_value 表中
367
+     *
368
+     * @param int $importGoodsId alibaba_import_goods 表的主键ID
369
+     * @param int $merId         商户ID(目前写死3447)
370
+     * @return array ['code' => 200|400, 'msg' => '...', 'product_id' => int]
371
+     */
372
+    public function importToStoreProduct(int $importGoodsId, int $merId): array
373
+    {
374
+        try {
375
+            // 1. 读取 alibaba_import_goods 记录
376
+            $importGoods = Db::name('alibaba_import_goods')
377
+                ->where('id', $importGoodsId)
378
+                ->where('status', 1)
379
+                ->find();
380
+
381
+            if (empty($importGoods)) {
382
+                return ['code' => 400, 'msg' => '商品不存在或已下架', 'product_id' => 0];
383
+            }
384
+
385
+            // 2. 检查是否已入库(按 product_id 去重)
386
+            $exists = Db::name('store_product')
387
+                ->where('mer_id', $merId)
388
+                ->where('spu_id', (string)$importGoods['product_id'])
389
+                ->where('is_del', 0)
390
+                ->value('product_id');
391
+
392
+            if ($exists) {
393
+                return ['code' => 400, 'msg' => '商品已入库,product_id=' . $exists, 'product_id' => $exists];
394
+            }
395
+
396
+            // 3. 解析 product_info JSON
397
+            $productInfo = json_decode($importGoods['product_info'], true) ?: [];
398
+            if (empty($productInfo)) {
399
+                return ['code' => 400, 'msg' => '商品数据解析失败', 'product_id' => 0];
400
+            }
401
+
402
+            $skuList = $productInfo['skuList'] ?? [];
403
+            $imageList = $productInfo['imageList'] ?? [];
404
+            $mainImage = $productInfo['mainImage'] ?? $importGoods['image'] ?? '';
405
+            $title = $productInfo['title'] ?? $importGoods['product_name'] ?? '';
406
+            $description = $productInfo['description'] ?? '';
407
+
408
+            // 4. 判断是否为多规格
409
+            $specType = count($skuList) > 1 ? 1 : 0;
410
+
411
+            // 5. 轮播图处理
412
+            $sliderImage = !empty($imageList) ? implode(',', $imageList) : $mainImage;
413
+
414
+            // 6. 取最低价和最高价作为 price 和 ot_price
415
+            $prices = array_column($skuList, 'price');
416
+            $minPrice = !empty($prices) ? (float)min($prices) : 0;
417
+            $maxPrice = !empty($prices) ? (float)max($prices) : 0;
418
+
419
+            // 成本价取第一个SKU的成本价
420
+            $costPrice = $skuList[0]['costPrice'] ?? 0;
421
+
422
+            // 7. 写入 store_product 表
423
+            $productData = [
424
+                'mer_id'         => $merId,
425
+                'image'          => $mainImage,
426
+                'slider_image'   => $sliderImage,
427
+                'store_name'     => $title,
428
+                'store_info'     => mb_substr($title, 0, 100),
429
+                'keyword'        => mb_substr($title, 0, 10),
430
+                'is_show'        => 1,
431
+                'status'         => 0,
432
+                'cate_id'        => 0,
433
+                'unit_name'      => $productInfo['unit'] ?? '件',
434
+                'price'          => $minPrice,
435
+                'cost'           => $costPrice,
436
+                'ot_price'       => $maxPrice,
437
+                'stock'          => 9999,
438
+                'spec_type'      => $specType,
439
+                'extension_type' => 0,
440
+                'mer_status'     => 1,
441
+                'is_used'        => 1,
442
+                'volunteer'      => 0,
443
+                'type'           => 1,
444
+                'pension'        => '0.00',
445
+                'commission'     => '',
446
+                'spu_id'         => (string)$importGoods['product_id'],
447
+                'temp_id'        => 105,
448
+                'concession_pri' => '0.00',
449
+                'cost_price'     => $costPrice,
450
+            ];
451
+            $productId = Db::name('store_product')->insertGetId($productData);
452
+
453
+            if (!$productId) {
454
+                return ['code' => 400, 'msg' => '写入store_product表失败', 'product_id' => 0];
455
+            }
456
+
457
+            // 8. 写入 store_product_content 表(商品详情)
458
+            $contentHtml = $description;
459
+            // Db::name('store_product_content')->insert([
460
+            //     'product_id' => $productId,
461
+            //     'content'    => $contentHtml,
462
+            //     'type'       => 0,
463
+            // ]);
464
+
465
+            // 9. 写入 store_product_attr 和 store_product_attr_value
466
+            $this->insertStoreSkus($productId, $skuList, $merId);
467
+
468
+            Log::channel('alibaba')->info("[1688正式入库1] 商品{$importGoods['product_id']}入库成功,product_id={$productId},mer_id={$merId}");
469
+
470
+            return [
471
+                'code'       => 200,
472
+                'msg'        => '入库成功',
473
+                'product_id' => $productId,
474
+            ];
475
+        } catch (\Throwable $e) {
476
+            Log::channel('alibaba')->error("[1688正式入库2] 异常: " . $e->getMessage());
477
+            return ['code' => 400, 'msg' => '入库异常: ' . $e->getMessage(), 'product_id' => 0];
478
+        }
479
+    }
480
+
481
+    /**
482
+     * 写入 store_product_attr 和 store_product_attr_value
483
+     *
484
+     * @param int   $productId
485
+     * @param array $skuList    formatDetailResult 返回的 skuList
486
+     * @param int   $merId
487
+     */
488
+    protected function insertStoreSkus(int $productId, array $skuList, int $merId): void
489
+    {
490
+        if (empty($skuList)) {
491
+            // 单规格:写入默认规格
492
+            Db::name('store_product_attr')->insert([
493
+                'product_id'  => $productId,
494
+                'attr_name'   => '规格',
495
+                'attr_values' => '',
496
+                'type'        => 0,
497
+            ]);
498
+
499
+            $unique = $this->generateUnique($productId, 'default');
500
+            Db::name('store_product_attr_value')->insert([
501
+                'product_id'  => $productId,
502
+                'detail'      => json_encode(['规格' => '默认'], JSON_UNESCAPED_UNICODE),
503
+                'sku'         => '默认',
504
+                'image'       => '',
505
+                'cost'        => 0,
506
+                'ot_price'    => 0,
507
+                'price'       => 0,
508
+                'unique'      => $unique,
509
+                'stock'       => 9999,
510
+                'cost_price'  => 0,
511
+                'extension_one' => 5,
512
+                'gong_sku_id' => '',
513
+                'gong_mer_profit' => 0,
514
+                'gong_pension'    => 0,
515
+                'gong_market_price' => 0,
516
+                'plate_mer_profit' => 0,
517
+                'volume' => 0,
518
+                'weight' => 0,
519
+                'dacang_price' => 0
520
+            ]);
521
+            return;
522
+        }
523
+
524
+        // 多规格处理
525
+        // 收集所有规格名称和值
526
+        $attrNames = [];    // ['颜色', '尺寸']
527
+        $attrValueMap = []; // ['颜色' => ['红', '蓝'], '尺寸' => ['S', 'M']]
528
+
529
+        foreach ($skuList as $sku) {
530
+            $skuAttrs = $sku['skuAttrs'] ?? [];
531
+            foreach ($skuAttrs as $attr) {
532
+                $name = $attr['name'] ?? '';
533
+                $value = $attr['value'] ?? '';
534
+                if ($name === '') continue;
535
+                if (!in_array($name, $attrNames)) {
536
+                    $attrNames[] = $name;
537
+                }
538
+                if (!isset($attrValueMap[$name])) {
539
+                    $attrValueMap[$name] = [];
540
+                }
541
+                if (!in_array($value, $attrValueMap[$name])) {
542
+                    $attrValueMap[$name][] = $value;
543
+                }
544
+            }
545
+        }
546
+
547
+        // 写入 store_product_attr
548
+        foreach ($attrNames as $attrName) {
549
+            $attrValues = $attrValueMap[$attrName] ?? [];
550
+            Db::name('store_product_attr')->insert([
551
+                'product_id'  => $productId,
552
+                'attr_name'   => $attrName,
553
+                'attr_values' => implode('-!-', $attrValues),
554
+                'type'        => 0,
555
+            ]);
556
+        }
557
+
558
+        // 写入 store_product_attr_value
559
+        foreach ($skuList as $sku) {
560
+            $skuAttrs = $sku['skuAttrs'] ?? [];
561
+            $detailArr = [];
562
+            $skuKey = '';
563
+            foreach ($skuAttrs as $attr) {
564
+                $name = $attr['name'] ?? '';
565
+                $value = $attr['value'] ?? '';
566
+                if ($name !== '') {
567
+                    $detailArr[$name] = $value;
568
+                    $skuKey .= $value;
569
+                }
570
+            }
571
+            if (empty($detailArr)) {
572
+                $detailArr = ['规格' => '默认'];
573
+                $skuKey = '默认';
574
+            }
575
+
576
+            $skuId = $sku['skuId'] ?? '';
577
+            $unique = $this->generateUnique($productId, $skuId ?: $skuKey);
578
+
579
+            Db::name('store_product_attr_value')->insert([
580
+                'product_id'  => $productId,
581
+                'detail'      => json_encode($detailArr, JSON_UNESCAPED_UNICODE),
582
+                'sku'         => $skuKey,
583
+                'image'       => $sku['image'] ?? '',
584
+                'cost'        => $sku['costPrice'] ?? 0,
585
+                'ot_price'    => $sku['consignPrice'] ?? 0,
586
+                'price'       => $sku['costPrice'] ?? 0,
587
+                'unique'      => $unique,
588
+                'stock'       => $sku['stock'] ?? 9999,
589
+                'cost_price'  => $sku['costPrice'] ?? 0,
590
+                'extension_one' => 5,
591
+                'gong_sku_id' => (string)$skuId,
592
+                'gong_mer_profit' => 0,
593
+                'gong_pension'    => 0,
594
+                'gong_market_price' => $sku['consignPrice'] ?? 0,
595
+                'plate_mer_profit' => 0,
596
+                'volume' => 0,
597
+                'weight' => 0,
598
+                'dacang_price' => 0
599
+            ]);
600
+        }
601
+    }
602
+
603
+    /**
604
+     * 生成唯一值 unique
605
+     *
606
+     * @param int    $productId
607
+     * @param string $sku
608
+     * @return string
609
+     */
610
+    protected function generateUnique(int $productId, string $sku): string
611
+    {
612
+        return substr(md5($sku . $productId), 12, 11) . '0';
613
+    }
614
+
358 615
     /**
359 616
      * 批量导入1688商品到 alibaba_import_goods 表
360 617
      *

+ 8 - 0
route/merchant.php

@@ -596,6 +596,14 @@ Route::group(config('admin.api_merchant_prefix') . '/', function () {
596 596
             Route::get('status_filter', '/getStatusFilter')->name('getStatusFilter');
597 597
         })->prefix('merchant.gong.Goods');
598 598
 
599
+        // 1688分销严选 - 入库商品
600
+        Route::group('alibaba/product', function () {
601
+            Route::get('lst', '/lst');             // 商品列表(alibaba_import_goods表)
602
+            Route::get('detail', '/detail');       // 商品详情
603
+            Route::post('delete', '/delete');      // 删除(软删除)
604
+            Route::post('importStore', '/importStore'); // 正式入库到store_product表
605
+        })->prefix('merchant.alibaba.Product');
606
+
599 607
 
600 608
         Route::get('envet', 'merchant.user.User/envt');
601 609