CaiWuXiTong/app/Console/Commands/ImportInventoryInboundDetail.php

211 lines
7.7 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Business\InventoryWarehouseNameMapping;
use Carbon\CarbonImmutable;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\IOFactory;
/**
* 进销存报表 → 入库明细数据:按原始 Excel 原样导入,仓库简称用仓库名称映射表按「仓库」唯一匹配。
*/
class ImportInventoryInboundDetail extends Command
{
protected $signature = 'inventory:inbound-detail:import {file : 入库明细表 Excel} {--month= : 统一指定月份(不传则按创建时间判断)}';
protected $description = '导入进销存报表的入库明细数据';
public function handle(): int
{
$file = (string) $this->argument('file');
$connection = (string) config('upstream_mirrors.connection', 'business_mirror');
$table = 'inventory_inbound_detail_rows';
$forcedMonth = trim((string) ($this->option('month') ?? ''));
$now = now();
$maps = $this->shortNameMap();
$this->info('仓库名称映射:仓库名 ' . count($maps['warehouse']) . ' 个、仓储方 ' . count($maps['provider']) . ' 个可唯一匹配');
// 原样导入:以本次文件为准整体重建,不做去重、不做加工。
DB::connection($connection)->table($table)->delete();
$reader = IOFactory::createReaderForFile($file);
$reader->setReadDataOnly(true);
$sheet = $reader->load($file)->getSheet(0);
$batch = [];
$imported = 0;
$mapped = 0;
$skipped = 0;
foreach ($sheet->getRowIterator(2) as $row) {
$rowNumber = $row->getRowIndex();
$cells = [];
foreach ($row->getCellIterator('A', 'U') as $cell) {
$cells[] = $cell->getFormattedValue();
}
$warehouse = trim((string) ($cells[14] ?? ''));
$createdRaw = trim((string) ($cells[16] ?? ''));
if ($warehouse === '' && $createdRaw === '') {
$skipped++;
continue;
}
$month = $forcedMonth !== '' ? $forcedMonth : $this->monthOf($createdRaw);
if ($month === '') {
$skipped++;
continue;
}
// 一般用"仓库"匹配仓库简称;含"代发仓"字样的按"仓储方"匹配。
$shortName = '';
if ($warehouse !== '') {
$shortName = str_contains($warehouse, '代发仓')
? ($maps['provider'][$warehouse] ?? $maps['warehouse'][$warehouse] ?? '')
: ($maps['warehouse'][$warehouse] ?? '');
}
if ($shortName !== '') {
$mapped++;
}
$batch[] = [
'report_month' => $month . '-01',
// 唯一键按 Excel 行号生成,保留原表每一行(含重复行)。
'row_key' => sha1($month . '|' . $rowNumber),
'source_no' => $this->text($cells[0] ?? ''),
'document_no' => $this->text($cells[1] ?? ''),
'created_serial' => $this->text($cells[2] ?? ''),
'supplier' => $this->text($cells[3] ?? ''),
'item_no' => $this->text($cells[4] ?? ''),
'barcode' => $this->text($cells[5] ?? ''),
'spec_name' => $this->text($cells[6] ?? ''),
'quantity' => $this->number($cells[7] ?? ''),
'unit_price' => $this->number($cells[8] ?? ''),
'amount' => $this->number($cells[9] ?? ''),
'estimated_unit_price' => $this->number($cells[10] ?? ''),
'estimated_amount' => $this->number($cells[11] ?? ''),
'supplier_barcode' => $this->text($cells[12] ?? ''),
'remark' => $this->text($cells[13] ?? ''),
'warehouse' => $warehouse !== '' ? $warehouse : null,
'warehouse_short_name' => $shortName !== '' ? $shortName : null,
'created_at_raw' => $this->moment($createdRaw),
'express_name' => $this->text($cells[17] ?? ''),
'express_no' => $this->text($cells[18] ?? ''),
'not_sent' => $this->text($cells[19] ?? ''),
'owner_name' => $this->text($cells[20] ?? ''),
'imported_at' => $now,
];
$imported++;
if (count($batch) >= 1000) {
DB::connection($connection)->table($table)->insertOrIgnore($batch);
$this->info('已写入 ' . $imported . ' 行');
$batch = [];
}
}
if ($batch !== []) {
DB::connection($connection)->table($table)->insertOrIgnore($batch);
}
$this->info(sprintf(
'导入完成:%d 行,仓库简称匹配上 %d 行,跳过 %d 行;库中该表共 %d 行',
$imported,
$mapped,
$skipped,
DB::connection($connection)->table($table)->count(),
));
return self::SUCCESS;
}
/**
* @return array{warehouse:array<string,string>,provider:array<string,string>}
* 仅保留能唯一匹配的键
*/
private function shortNameMap(): array
{
$rows = InventoryWarehouseNameMapping::withoutGlobalScopes()
->get(['warehouse_provider', 'warehouse', 'warehouse_short_name']);
$grouped = [];
foreach ($rows as $row) {
$warehouse = trim((string) $row->warehouse);
if ($warehouse === '') {
continue;
}
$grouped[$warehouse][] = trim((string) $row->warehouse_short_name);
}
$byWarehouse = [];
foreach ($grouped as $warehouse => $names) {
$unique = array_values(array_unique($names));
if (count($unique) === 1 && $unique[0] !== '') {
$byWarehouse[$warehouse] = $unique[0];
}
}
$byProvider = [];
foreach ($rows as $row) {
$provider = trim((string) $row->warehouse_provider);
if ($provider === '') {
continue;
}
$byProvider[$provider][] = trim((string) $row->warehouse_short_name);
}
foreach ($byProvider as $provider => $names) {
$unique = array_values(array_unique($names));
if (count($unique) === 1 && $unique[0] !== '') {
$byProvider[$provider] = $unique[0];
}
}
return ['warehouse' => $byWarehouse, 'provider' => $byProvider];
}
private function monthOf(string $value): string
{
$value = trim($value);
if ($value === '') {
return '';
}
foreach (['Y/m/d H:i:s', 'Y-m-d H:i:s', 'Y/m/d', 'Y-m-d'] as $format) {
$parsed = \DateTimeImmutable::createFromFormat($format, $value);
if ($parsed instanceof \DateTimeImmutable) {
return $parsed->format('Y-m');
}
}
$timestamp = strtotime($value);
return $timestamp === false ? '' : date('Y-m', $timestamp);
}
private function moment(string $value): ?string
{
$value = trim($value);
if ($value === '') {
return null;
}
$timestamp = strtotime($value);
return $timestamp === false ? null : date('Y-m-d H:i:s', $timestamp);
}
private function text(mixed $value): ?string
{
$text = trim((string) $value);
return $text === '' ? null : $text;
}
private function number(mixed $value): ?float
{
$text = str_replace(',', '', trim((string) $value));
if ($text === '' || ! is_numeric($text)) {
return null;
}
return (float) $text;
}
}