CaiWuXiTong/app/Console/Commands/ImportServiceCompanyCustomerMapping.php
DoDo 83ffec8d57 fix: 自动分配严格按主体维度报表收入
自动分配改用主体报表口径并支持错误来源校正。
2026-11-11 18:02:44 +08:00

112 lines
4.5 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Business\ServiceCompanyReconciliation\CustomerServiceMappingWorkbookImporter;
use App\Business\ServiceCompanyReconciliation\ServiceCompanyReconciliationRepository;
use Illuminate\Console\Command;
class ImportServiceCompanyCustomerMapping extends Command
{
protected $signature = 'service-company:import-customer-mapping
{file : 客服公司映射 Excel 路径}
{--company-id=1 : 租户公司 ID}
{--effective-from=2026-10-01 : 结算关系生效日期}
{--auto-assign-period= : 未分配店铺按该月收入自动平衡,默认上一个完整月}
{--deactivate-empty-auto : 校正错误自动分配,停用报表当前月份无收入的自动来源关系}
{--actor=客服公司映射导入 : 导入操作人}
{--dry-run : 只解析和校验,不写入数据库}';
protected $description = '导入客服公司与店铺公司的配置映射';
public function handle(
CustomerServiceMappingWorkbookImporter $importer,
ServiceCompanyReconciliationRepository $repository,
): int {
$companyId = (int) $this->option('company-id');
$effectiveFrom = trim((string) $this->option('effective-from'));
$autoAssignPeriod = trim((string) $this->option('auto-assign-period'));
if ($autoAssignPeriod === '') {
$autoAssignPeriod = now()->startOfMonth()->subMonthNoOverflow()->format('Y-m');
}
try {
$parsed = $importer->rows((string) $this->argument('file'));
} catch (\Throwable $exception) {
$this->error($exception->getMessage());
return self::FAILURE;
}
$this->table(['指标', '数量'], [
['店铺公司行数', $parsed['total_rows']],
['已分配客服公司', $parsed['assigned_rows']],
['暂不分配', $parsed['unassigned_rows']],
['无效行', count($parsed['invalid_rows'])],
]);
if ($parsed['invalid_rows'] !== []) {
$this->error(implode(PHP_EOL, $parsed['invalid_rows']));
return self::FAILURE;
}
if ((bool) $this->option('dry-run')) {
$this->info('校验通过,未写入数据库。');
return self::SUCCESS;
}
$result = $repository->syncCustomerServiceMappings(
$companyId,
$parsed['rows'],
$effectiveFrom,
trim((string) $this->option('actor')) ?: '客服公司映射导入',
$parsed['unassigned'],
$autoAssignPeriod,
);
if ($result['errors'] !== []) {
$this->error(implode(PHP_EOL, $result['errors']));
return self::FAILURE;
}
$this->table(['结果', '数量'], [
['新增', $result['created']],
['更新', $result['updated']],
['无变化', $result['unchanged']],
['停用', $result['inactive']],
['自动补分配', $result['auto_assigned']],
['有收入但无法分配', count($result['auto_errors'])],
]);
if ($result['auto_totals'] !== []) {
$this->table(
['客服公司', '自动补分配后收入'],
collect($result['auto_totals'])
->map(static fn (float $amount, string $name): array => [$name, number_format($amount, 2, '.', '')])
->values()
->all(),
);
}
if ($result['auto_errors'] !== []) {
$this->warn(implode(PHP_EOL, $result['auto_errors']));
}
if ((bool) $this->option('deactivate-empty-auto')) {
$cleanup = $repository->deactivateAutoMappingsWithoutReportIncome(
$companyId,
$autoAssignPeriod,
trim((string) $this->option('actor')) ?: '客服公司映射导入',
);
if ($cleanup['errors'] !== []) {
$this->error(implode(PHP_EOL, $cleanup['errors']));
return self::FAILURE;
}
$this->table(['自动分配校正', '数量'], [
['停用无收入关系', $cleanup['deactivated']],
['保留有收入关系', $cleanup['kept']],
]);
}
$this->info("客服公司映射已同步,生效日期:{$effectiveFrom}");
return self::SUCCESS;
}
}