211 lines
7.7 KiB
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;
|
|
}
|
|
}
|