141 lines
4.6 KiB
PHP
141 lines
4.6 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use App\Business\XlsxSheetReader;
|
||
use Illuminate\Console\Command;
|
||
use Illuminate\Support\Facades\Cache;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Modules\EcommerceFinance\Repositories\PromotionCostRowRepository;
|
||
|
||
class ImportPromotionCostRows extends Command
|
||
{
|
||
protected $signature = 'order-data:import-promotion-cost
|
||
{file : Excel 文件路径}
|
||
{--period= : 数据月份,格式 YYYY-MM}
|
||
{--chunk=500 : 每批写入行数}
|
||
{--dry-run : 只解析不入库}';
|
||
|
||
protected $description = '导入聚水潭商品推广费用明细 Excel';
|
||
|
||
public function handle(XlsxSheetReader $reader): int
|
||
{
|
||
$path = (string) $this->argument('file');
|
||
$period = trim((string) $this->option('period'));
|
||
|
||
if (! is_file($path)) {
|
||
$this->error('文件不存在:' . $path);
|
||
|
||
return self::FAILURE;
|
||
}
|
||
|
||
if (preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $period) !== 1) {
|
||
$this->error('数据月份格式必须是 YYYY-MM:' . ($period !== '' ? $period : '(空)'));
|
||
|
||
return self::FAILURE;
|
||
}
|
||
|
||
$headers = (array) config('order_promotion_cost.headers', []);
|
||
if ($headers === []) {
|
||
$this->error('缺少表头映射:config/order_promotion_cost.php');
|
||
|
||
return self::FAILURE;
|
||
}
|
||
|
||
$this->info('流式读取文件:' . $path);
|
||
|
||
$chunkSize = max((int) $this->option('chunk'), 100);
|
||
$dryRun = (bool) $this->option('dry-run');
|
||
$importedAt = now()->toDateTimeString();
|
||
$buffer = [];
|
||
$total = 0;
|
||
$written = 0;
|
||
$skipped = 0;
|
||
$promotionFee = 0.0;
|
||
$shops = [];
|
||
|
||
try {
|
||
foreach ($reader->rows($path, array_keys($headers)) as $row) {
|
||
$total++;
|
||
$payload = [];
|
||
|
||
foreach ($headers as $header => $key) {
|
||
$value = trim((string) ($row[$header] ?? ''));
|
||
if ($value === '') {
|
||
continue;
|
||
}
|
||
$payload[$key] = $key === 'promotion_fee'
|
||
? (is_numeric($value) ? (float) $value : 0.0)
|
||
: $value;
|
||
}
|
||
|
||
$shop = trim((string) ($payload['shop_name'] ?? ''));
|
||
$product = trim((string) ($payload['product_code'] ?? ''));
|
||
if ($shop === '' || $product === '') {
|
||
$skipped++;
|
||
|
||
continue;
|
||
}
|
||
|
||
$shops[$shop] = true;
|
||
$promotionFee += (float) ($payload['promotion_fee'] ?? 0);
|
||
$buffer[] = PromotionCostRowRepository::toRecord($payload, $period, $importedAt);
|
||
|
||
if (! $dryRun && count($buffer) >= $chunkSize) {
|
||
$written += $this->flush($buffer);
|
||
$buffer = [];
|
||
$this->output->write("\r写入 " . $written . ' 行');
|
||
}
|
||
|
||
if ($dryRun && $buffer !== []) {
|
||
$buffer = [];
|
||
}
|
||
}
|
||
} catch (\Throwable $exception) {
|
||
if (! $dryRun) {
|
||
Cache::forget('order-data.promotion.imported-shop-options:' . $period);
|
||
}
|
||
$this->error('读取失败:' . $exception->getMessage());
|
||
|
||
return self::FAILURE;
|
||
}
|
||
|
||
if (! $dryRun && $buffer !== []) {
|
||
$written += $this->flush($buffer);
|
||
}
|
||
if (! $dryRun) {
|
||
Cache::forget('order-data.promotion.imported-shop-options:' . $period);
|
||
}
|
||
|
||
$this->newLine(2);
|
||
$this->info(sprintf(
|
||
'完成:读取 %d 行,写入 %d 行,跳过 %d 行;店铺 %d 个;数据月份 %s;推广费合计 %.2f;峰值内存 %.1f MB',
|
||
$total,
|
||
$written,
|
||
$skipped,
|
||
count($shops),
|
||
$period,
|
||
$promotionFee,
|
||
memory_get_peak_usage(true) / 1048576,
|
||
));
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
/** @param list<array<string,mixed>> $rows */
|
||
private function flush(array $rows): int
|
||
{
|
||
DB::table('ops_promotion_cost_rows')->upsert(
|
||
$rows,
|
||
['row_key'],
|
||
[
|
||
'source_period', 'shop_name', 'product_code', 'style_code',
|
||
'shop_product_code', 'shop_style_code', 'online_color_spec',
|
||
'promotion_fee', 'payload', 'imported_at', 'updated_at',
|
||
],
|
||
);
|
||
|
||
return count($rows);
|
||
}
|
||
}
|