|
|
@@ -2,6 +2,7 @@
|
|
2
|
2
|
|
|
3
|
3
|
namespace app\command;
|
|
4
|
4
|
|
|
|
5
|
+use app\common\repositories\store\product\ProductRepository;
|
|
5
|
6
|
use app\services\ThirdParty\AlibabaAgent\ProductService;
|
|
6
|
7
|
use think\console\Command;
|
|
7
|
8
|
use think\console\Input;
|
|
|
@@ -12,7 +13,8 @@ use think\facade\Db;
|
|
12
|
13
|
* 今日1688商品自动入库命令
|
|
13
|
14
|
*
|
|
14
|
15
|
* 从 alibaba_import_goods 表中获取今日创建的商品,
|
|
15
|
|
- * 调用 ProductService::importToStoreProduct() 进行正式入库到 store_product 表
|
|
|
16
|
+ * 调用 ProductService::importToStoreProduct() 进行正式入库到 store_product 表,
|
|
|
17
|
+ * 然后通过 ProductRepository::update() 更新原价和分润设置
|
|
16
|
18
|
*
|
|
17
|
19
|
* 用法:
|
|
18
|
20
|
* php think alibaba:today-import # 默认今天,mer_id=3447
|
|
|
@@ -29,7 +31,6 @@ use think\facade\Db;
|
|
29
|
31
|
* - extension_type: 1(开启分润)
|
|
30
|
32
|
* - concession_pri: 售价 × 15%(商家让利金额)
|
|
31
|
33
|
* - extension_one: 15(SKU级分润比例)
|
|
32
|
|
- * - plate_mer_profit: 0(平台商户利润)
|
|
33
|
34
|
* - commission: 保持原样(不修改)
|
|
34
|
35
|
*
|
|
35
|
36
|
* 原价计算规则(根据SKU售价阶梯加价):
|
|
|
@@ -37,7 +38,7 @@ use think\facade\Db;
|
|
37
|
38
|
* 21-50元 => 固定加20元
|
|
38
|
39
|
* 51-120元 => 固定加40元
|
|
39
|
40
|
* 121-200元 => 固定加45元
|
|
40
|
|
- * 201-300元 => 固定加50元
|
|
|
41
|
+ * 201元以上 => 固定加50元
|
|
41
|
42
|
*/
|
|
42
|
43
|
class AlibabaTodayImport extends Command
|
|
43
|
44
|
{
|
|
|
@@ -153,81 +154,13 @@ class AlibabaTodayImport extends Command
|
|
153
|
154
|
$productId = $result['product_id'];
|
|
154
|
155
|
$globalSuccess++;
|
|
155
|
156
|
|
|
156
|
|
- // 4b. 根据SKU售价阶梯计算原价(ot_price)
|
|
|
157
|
+ // 4b. 组装数据,调用 ProductRepository::update() 更新原价和分润
|
|
157
|
158
|
try {
|
|
158
|
|
- // 获取所有SKU的售价(用 unique 字段唯一标识每个SKU)
|
|
159
|
|
- $skuValues = Db::name('store_product_attr_value')
|
|
160
|
|
- ->where('product_id', $productId)
|
|
161
|
|
- ->field('unique, price')
|
|
162
|
|
- ->select()
|
|
163
|
|
- ->toArray();
|
|
164
|
|
-
|
|
165
|
|
- $skuPrices = [];
|
|
166
|
|
- $skuOtPrices = [];
|
|
167
|
|
- foreach ($skuValues as $sku) {
|
|
168
|
|
- $salePrice = (float)$sku['price'];
|
|
169
|
|
- $skuPrices[] = $salePrice;
|
|
170
|
|
-
|
|
171
|
|
- // 根据售价区间计算原价
|
|
172
|
|
- $otPrice = self::calcOtPrice($salePrice);
|
|
173
|
|
- $skuOtPrices[] = $otPrice;
|
|
174
|
|
-
|
|
175
|
|
- // 通过 unique 字段更新当前SKU的 ot_price
|
|
176
|
|
- Db::name('store_product_attr_value')
|
|
177
|
|
- ->where('unique', $sku['unique'])
|
|
178
|
|
- ->update(['ot_price' => $otPrice]);
|
|
179
|
|
- }
|
|
180
|
|
-
|
|
181
|
|
- // 更新 store_product 表的 price(最低售价)和 ot_price(最高原价)
|
|
182
|
|
- $minPrice = !empty($skuPrices) ? min($skuPrices) : 0;
|
|
183
|
|
- $maxOtPrice = !empty($skuOtPrices) ? max($skuOtPrices) : 0;
|
|
184
|
|
- Db::name('store_product')
|
|
185
|
|
- ->where('product_id', $productId)
|
|
186
|
|
- ->update([
|
|
187
|
|
- 'price' => $minPrice,
|
|
188
|
|
- 'ot_price' => $maxOtPrice,
|
|
189
|
|
- ]);
|
|
190
|
|
-
|
|
191
|
|
- $output->write("<info>[原价已计算]</info> ");
|
|
192
|
|
- } catch (\Throwable $priceE) {
|
|
193
|
|
- $output->write("<comment>[原价计算失败: {$priceE->getMessage()}]</comment> ");
|
|
194
|
|
- }
|
|
195
|
|
-
|
|
196
|
|
- // 4c. 设置分润(写死15%)
|
|
197
|
|
- try {
|
|
198
|
|
- // 获取商品最低售价
|
|
199
|
|
- $storeProduct = Db::name('store_product')
|
|
200
|
|
- ->where('product_id', $productId)
|
|
201
|
|
- ->field('price')
|
|
202
|
|
- ->find();
|
|
203
|
|
-
|
|
204
|
|
- $salePrice = $storeProduct ? (float)$storeProduct['price'] : 0;
|
|
205
|
|
-
|
|
206
|
|
- // 计算 concession_pri = 售价 × 15%,使用 bc 函数保留两位小数
|
|
207
|
|
- $concessionPri = '0.00';
|
|
208
|
|
- if ($salePrice > 0) {
|
|
209
|
|
- $concessionPri = bcmul((string)$salePrice, (string)$profitConfig['concession_rate'], 2);
|
|
210
|
|
- }
|
|
211
|
|
-
|
|
212
|
|
- // 更新 store_product 表的分润字段(commission保持原样不修改)
|
|
213
|
|
- Db::name('store_product')
|
|
214
|
|
- ->where('product_id', $productId)
|
|
215
|
|
- ->update([
|
|
216
|
|
- 'extension_type' => $profitConfig['extension_type'],
|
|
217
|
|
- 'concession_pri' => $concessionPri,
|
|
218
|
|
- ]);
|
|
219
|
|
-
|
|
220
|
|
- // 更新 store_product_attr_value 表的 SKU 分润比例
|
|
221
|
|
- Db::name('store_product_attr_value')
|
|
222
|
|
- ->where('product_id', $productId)
|
|
223
|
|
- ->update([
|
|
224
|
|
- 'extension_one' => $profitConfig['extension_one'],
|
|
225
|
|
- ]);
|
|
226
|
|
-
|
|
|
159
|
+ self::updateProductData($productId, $merId, $profitConfig);
|
|
227
|
160
|
$globalProfitSet++;
|
|
228
|
|
- $output->write("<info>[分润15%已设置]</info> ");
|
|
229
|
|
- } catch (\Throwable $profitE) {
|
|
230
|
|
- $output->write("<comment>[分润设置失败: {$profitE->getMessage()}]</comment> ");
|
|
|
161
|
+ $output->write("<info>[原价+分润已更新]</info> ");
|
|
|
162
|
+ } catch (\Throwable $updateE) {
|
|
|
163
|
+ $output->write("<comment>[更新失败: {$updateE->getMessage()}]</comment> ");
|
|
231
|
164
|
}
|
|
232
|
165
|
|
|
233
|
166
|
$output->writeln("<info>成功 => product_id={$productId}</info>");
|
|
|
@@ -246,7 +179,7 @@ class AlibabaTodayImport extends Command
|
|
246
|
179
|
// 每批结束后输出当前进度和耗时
|
|
247
|
180
|
$batchElapsed = time() - $batchStartTime;
|
|
248
|
181
|
$totalElapsed = time() - $startTime;
|
|
249
|
|
- $output->writeln(" 本批耗时: {$batchElapsed}s | 累计耗时: {$totalElapsed}s | 累计成功: {$globalSuccess} | 累计失败: {$globalFail} | 分润设置: {$globalProfitSet}");
|
|
|
182
|
+ $output->writeln(" 本批耗时: {$batchElapsed}s | 累计耗时: {$totalElapsed}s | 累计成功: {$globalSuccess} | 累计失败: {$globalFail} | 更新: {$globalProfitSet}");
|
|
250
|
183
|
$output->writeln('');
|
|
251
|
184
|
|
|
252
|
185
|
// 每批之间短暂休眠,避免数据库压力过大
|
|
|
@@ -266,7 +199,7 @@ class AlibabaTodayImport extends Command
|
|
266
|
199
|
$output->writeln("总处理: {$total} 条");
|
|
267
|
200
|
$output->writeln("成功: {$globalSuccess} 条");
|
|
268
|
201
|
$output->writeln("失败: {$globalFail} 条");
|
|
269
|
|
- $output->writeln("已设置分润15%: {$globalProfitSet} 条");
|
|
|
202
|
+ $output->writeln("已更新原价+分润: {$globalProfitSet} 条");
|
|
270
|
203
|
|
|
271
|
204
|
if ($globalFail > 0) {
|
|
272
|
205
|
$output->writeln('');
|
|
|
@@ -280,6 +213,169 @@ class AlibabaTodayImport extends Command
|
|
280
|
213
|
}
|
|
281
|
214
|
|
|
282
|
215
|
/**
|
|
|
216
|
+ * 组装数据并调用 ProductRepository::update() 更新商品
|
|
|
217
|
+ *
|
|
|
218
|
+ * 从数据库查询商品完整信息,组装成 update 接口所需的格式,
|
|
|
219
|
+ * 修改 ot_price(按阶梯计算)和 extension_one(15),
|
|
|
220
|
+ * 然后调用 ProductRepository::update() 执行更新。
|
|
|
221
|
+ *
|
|
|
222
|
+ * @param int $productId
|
|
|
223
|
+ * @param int $merId
|
|
|
224
|
+ * @param array $profitConfig
|
|
|
225
|
+ * @throws \Throwable
|
|
|
226
|
+ */
|
|
|
227
|
+ protected static function updateProductData(int $productId, int $merId, array $profitConfig): void
|
|
|
228
|
+ {
|
|
|
229
|
+ // 1. 查询 store_product 表获取商品基本信息
|
|
|
230
|
+ $product = Db::name('store_product')
|
|
|
231
|
+ ->where('product_id', $productId)
|
|
|
232
|
+ ->find();
|
|
|
233
|
+
|
|
|
234
|
+ if (!$product) {
|
|
|
235
|
+ throw new \RuntimeException("商品不存在 product_id={$productId}");
|
|
|
236
|
+ }
|
|
|
237
|
+
|
|
|
238
|
+ // 2. 查询 store_product_attr_value 表获取所有SKU
|
|
|
239
|
+ $skuValues = Db::name('store_product_attr_value')
|
|
|
240
|
+ ->where('product_id', $productId)
|
|
|
241
|
+ ->select()
|
|
|
242
|
+ ->toArray();
|
|
|
243
|
+
|
|
|
244
|
+ if (empty($skuValues)) {
|
|
|
245
|
+ throw new \RuntimeException("商品SKU数据为空 product_id={$productId}");
|
|
|
246
|
+ }
|
|
|
247
|
+
|
|
|
248
|
+ // 3. 查询 store_product_attr 表获取规格属性
|
|
|
249
|
+ $attrList = Db::name('store_product_attr')
|
|
|
250
|
+ ->where('product_id', $productId)
|
|
|
251
|
+ ->select()
|
|
|
252
|
+ ->toArray();
|
|
|
253
|
+
|
|
|
254
|
+ // 4. 查询 store_product_content 表获取商品详情
|
|
|
255
|
+ $content = Db::name('store_product_content')
|
|
|
256
|
+ ->where('product_id', $productId)
|
|
|
257
|
+ ->value('content');
|
|
|
258
|
+
|
|
|
259
|
+ // 5. 组装 attrValue 数据(参考 detail 接口返回格式)
|
|
|
260
|
+ $attrValue = [];
|
|
|
261
|
+ $skuPrices = [];
|
|
|
262
|
+ foreach ($skuValues as $sku) {
|
|
|
263
|
+ $salePrice = (float)$sku['price'];
|
|
|
264
|
+ $skuPrices[] = $salePrice;
|
|
|
265
|
+
|
|
|
266
|
+ // 根据售价区间计算原价
|
|
|
267
|
+ $otPrice = self::calcOtPrice($salePrice);
|
|
|
268
|
+
|
|
|
269
|
+ // detail 字段(规格明细)
|
|
|
270
|
+ $detail = [];
|
|
|
271
|
+ if (!empty($sku['detail'])) {
|
|
|
272
|
+ $detail = is_string($sku['detail']) ? json_decode($sku['detail'], true) : $sku['detail'];
|
|
|
273
|
+ }
|
|
|
274
|
+
|
|
|
275
|
+ $attrValue[] = [
|
|
|
276
|
+ 'product_id' => $productId,
|
|
|
277
|
+ 'detail' => $detail,
|
|
|
278
|
+ 'sku' => $sku['sku'] ?? '',
|
|
|
279
|
+ 'stock' => $sku['stock'] ?? 0,
|
|
|
280
|
+ 'sales' => $sku['sales'] ?? 0,
|
|
|
281
|
+ 'image' => $sku['image'] ?? '',
|
|
|
282
|
+ 'bar_code' => $sku['bar_code'] ?? '',
|
|
|
283
|
+ 'cost' => (string)($sku['cost'] ?? 0),
|
|
|
284
|
+ 'ot_price' => (string)$otPrice,
|
|
|
285
|
+ 'price' => (string)($sku['price'] ?? 0),
|
|
|
286
|
+ 'volume' => (string)($sku['volume'] ?? 0),
|
|
|
287
|
+ 'weight' => (string)($sku['weight'] ?? 0),
|
|
|
288
|
+ 'type' => $sku['type'] ?? 0,
|
|
|
289
|
+ 'extension_one' => (string)$profitConfig['extension_one'],
|
|
|
290
|
+ 'extension_two' => $sku['extension_two'] ?? null,
|
|
|
291
|
+ 'unique' => $sku['unique'] ?? '',
|
|
|
292
|
+ 'dacang_price' => (string)($sku['dacang_price'] ?? 0),
|
|
|
293
|
+ 'cost_price' => $sku['cost_price'] ?? null,
|
|
|
294
|
+ 'gong_sku_id' => $sku['gong_sku_id'] ?? null,
|
|
|
295
|
+ 'gong_mer_profit' => (string)($sku['gong_mer_profit'] ?? 0),
|
|
|
296
|
+ 'gong_pension' => $sku['gong_pension'] ?? null,
|
|
|
297
|
+ 'gong_market_price'=> (string)($sku['gong_market_price'] ?? 0),
|
|
|
298
|
+ 'plate_mer_profit' => (string)($sku['plate_mer_profit'] ?? 0),
|
|
|
299
|
+ 'pension_num' => (string)($sku['pension_num'] ?? 0),
|
|
|
300
|
+ 'share_num' => (string)($sku['share_num'] ?? 0),
|
|
|
301
|
+ 'stock_num' => (string)($sku['stock_num'] ?? 0),
|
|
|
302
|
+ 'shares_num' => (string)($sku['shares_num'] ?? 0),
|
|
|
303
|
+ 'is_usable' => $sku['is_usable'] ?? 0,
|
|
|
304
|
+ 'sku_id' => $sku['sku_id'] ?? '',
|
|
|
305
|
+ 'spec_id' => $sku['spec_id'] ?? '',
|
|
|
306
|
+ 'value0' => $sku['value0'] ?? ($sku['sku'] ?? ''),
|
|
|
307
|
+ 'dacang_cost_price'=> (string)($sku['dacang_cost_price'] ?? 0),
|
|
|
308
|
+ 'no_dacang_cost_price'=> (string)($sku['no_dacang_cost_price'] ?? 0),
|
|
|
309
|
+ ];
|
|
|
310
|
+ }
|
|
|
311
|
+
|
|
|
312
|
+ // 6. 组装 attr 数据(规格属性)
|
|
|
313
|
+ $attr = [];
|
|
|
314
|
+ foreach ($attrList as $attrItem) {
|
|
|
315
|
+ $detail = !empty($attrItem['attr_values'])
|
|
|
316
|
+ ? explode('-!-', $attrItem['attr_values'])
|
|
|
317
|
+ : [];
|
|
|
318
|
+ $attr[] = [
|
|
|
319
|
+ 'value' => $attrItem['attr_name'],
|
|
|
320
|
+ 'detail' => $detail,
|
|
|
321
|
+ ];
|
|
|
322
|
+ }
|
|
|
323
|
+
|
|
|
324
|
+ // 7. 计算商品级价格
|
|
|
325
|
+ $minPrice = !empty($skuPrices) ? min($skuPrices) : 0;
|
|
|
326
|
+ $maxOtPrice = self::calcOtPrice($minPrice);
|
|
|
327
|
+ // 取所有SKU中最大的ot_price
|
|
|
328
|
+ foreach ($skuPrices as $p) {
|
|
|
329
|
+ $ot = self::calcOtPrice($p);
|
|
|
330
|
+ if ($ot > $maxOtPrice) {
|
|
|
331
|
+ $maxOtPrice = $ot;
|
|
|
332
|
+ }
|
|
|
333
|
+ }
|
|
|
334
|
+
|
|
|
335
|
+ // 8. 组装完整数据(参考 detail 接口返回 + update 接口所需字段)
|
|
|
336
|
+ $sliderImages = !empty($product['slider_image'])
|
|
|
337
|
+ ? (is_string($product['slider_image']) ? explode(',', $product['slider_image']) : $product['slider_image'])
|
|
|
338
|
+ : [];
|
|
|
339
|
+
|
|
|
340
|
+ $updateData = [
|
|
|
341
|
+ 'volunteer' => $product['volunteer'] ?? 0,
|
|
|
342
|
+ 'image' => $product['image'] ?? '',
|
|
|
343
|
+ 'slider_image' => $sliderImages,
|
|
|
344
|
+ 'store_name' => $product['store_name'] ?? '',
|
|
|
345
|
+ 'store_info' => $product['store_info'] ?? '',
|
|
|
346
|
+ 'keyword' => $product['keyword'] ?? '',
|
|
|
347
|
+ 'brand_id' => $product['brand_id'] ?? null,
|
|
|
348
|
+ 'cate_id' => $product['cate_id'] ?? 0,
|
|
|
349
|
+ 'mer_cate_id' => [], // 商户分类,保持空数组
|
|
|
350
|
+ 'unit_name' => $product['unit_name'] ?? '件',
|
|
|
351
|
+ 'sort' => $product['sort'] ?? 0,
|
|
|
352
|
+ 'is_good' => $product['is_good'] ?? 0,
|
|
|
353
|
+ 'temp_id' => $product['temp_id'] ?? 0,
|
|
|
354
|
+ 'attr' => $attr,
|
|
|
355
|
+ 'content' => $content ?? '',
|
|
|
356
|
+ 'spec_type' => $product['spec_type'] ?? 1,
|
|
|
357
|
+ 'extension_type' => $profitConfig['extension_type'],
|
|
|
358
|
+ 'give_coupon_ids' => [],
|
|
|
359
|
+ 'is_gift_bag' => $product['is_gift_bag'] ?? 0,
|
|
|
360
|
+ 'type' => $product['type'] ?? 1,
|
|
|
361
|
+ 'commission' => $product['commission'] ?? '',
|
|
|
362
|
+ 'pension' => (string)($product['pension'] ?? '0.00'),
|
|
|
363
|
+ 'pension_is' => $product['pension_is'] ?? 0,
|
|
|
364
|
+ 'share_is' => $product['share_is'] ?? 0,
|
|
|
365
|
+ 'ficti' => $product['ficti'] ?? 0,
|
|
|
366
|
+ 'icon_type' => !empty($product['icon_type'])
|
|
|
367
|
+ ? (is_string($product['icon_type']) ? json_decode($product['icon_type'], true) : $product['icon_type'])
|
|
|
368
|
+ : [],
|
|
|
369
|
+ 'attrValue' => $attrValue,
|
|
|
370
|
+ ];
|
|
|
371
|
+
|
|
|
372
|
+ // 9. 调用 ProductRepository::update() 执行更新
|
|
|
373
|
+ /** @var ProductRepository $productRepository */
|
|
|
374
|
+ $productRepository = app()->make(ProductRepository::class);
|
|
|
375
|
+ $productRepository->update($productId, $updateData, $merId);
|
|
|
376
|
+ }
|
|
|
377
|
+
|
|
|
378
|
+ /**
|
|
283
|
379
|
* 根据售价计算原价(阶梯加价)
|
|
284
|
380
|
*
|
|
285
|
381
|
* @param float $salePrice SKU售价
|