151 lines
5.3 KiB
PHP
151 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace Modules\EcommerceFinance\Services;
|
|
|
|
use App\Models\Auth\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* 按账户总表批量建立“供应商应付”公司名称到账户简称的映射。
|
|
*
|
|
* 公司只有唯一账户,或虽有多个账户但只有一个公户时自动关联;
|
|
* 多公户、只有私人卡等歧义情况保留人工选择。
|
|
*/
|
|
class FinanceLedgerPayableMappingBulk
|
|
{
|
|
public function preview(): array
|
|
{
|
|
$connection = $this->connection();
|
|
$accountsByCompany = [];
|
|
foreach ($connection->table('fin_accounts')->get(['company_name', 'account_short', 'account_type']) as $account) {
|
|
$company = trim((string) $account->company_name);
|
|
$accountShort = trim((string) $account->account_short);
|
|
if ($company === '' || $accountShort === '') {
|
|
continue;
|
|
}
|
|
$accountsByCompany[$company][$accountShort] = [
|
|
'account_short' => $accountShort,
|
|
'account_type' => trim((string) $account->account_type),
|
|
];
|
|
}
|
|
|
|
$rows = $connection->table('fin_account_map')
|
|
->where('source', 'payable')
|
|
->where(function ($query): void {
|
|
$query->whereNull('account_short')->orWhere('account_short', '');
|
|
})
|
|
->orderBy('source_value')
|
|
->get(['id', 'source_value']);
|
|
|
|
$candidates = [];
|
|
$ambiguous = [];
|
|
$missing = [];
|
|
foreach ($rows as $row) {
|
|
$company = trim((string) $row->source_value);
|
|
$accounts = array_values($accountsByCompany[$company] ?? []);
|
|
if ($accounts === []) {
|
|
$missing[] = $company;
|
|
continue;
|
|
}
|
|
if (count($accounts) === 1) {
|
|
$candidates[] = $this->candidate($row, $accounts[0]['account_short'], 'single_account');
|
|
continue;
|
|
}
|
|
|
|
$publicAccounts = array_values(array_filter(
|
|
$accounts,
|
|
static fn (array $account): bool => $account['account_type'] === '公户',
|
|
));
|
|
if (count($publicAccounts) === 1) {
|
|
$candidates[] = $this->candidate($row, $publicAccounts[0]['account_short'], 'single_public_account');
|
|
continue;
|
|
}
|
|
|
|
$ambiguous[] = [
|
|
'company' => $company,
|
|
'accounts' => array_values(array_map(
|
|
static fn (array $account): string => $account['account_short'],
|
|
$accounts,
|
|
)),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'total' => $rows->count(),
|
|
'eligible' => count($candidates),
|
|
'ambiguous' => count($ambiguous),
|
|
'missing' => count($missing),
|
|
'candidates' => $candidates,
|
|
'ambiguous_values' => $ambiguous,
|
|
'missing_values' => $missing,
|
|
];
|
|
}
|
|
|
|
public function apply(?User $user = null): array
|
|
{
|
|
$preview = $this->preview();
|
|
$connection = $this->connection();
|
|
$userName = (string) ($user?->name ?? '');
|
|
$userId = (int) ($user?->id ?? 0);
|
|
$now = now();
|
|
$mapped = 0;
|
|
|
|
if ($preview['candidates'] !== []) {
|
|
$connection->transaction(function () use ($connection, $preview, $userName, $userId, $now, &$mapped): void {
|
|
foreach ($preview['candidates'] as $candidate) {
|
|
$updated = $connection->table('fin_account_map')
|
|
->where('id', $candidate['id'])
|
|
->where(function ($query): void {
|
|
$query->whereNull('account_short')->orWhere('account_short', '');
|
|
})
|
|
->update([
|
|
'account_short' => $candidate['account_short'],
|
|
'updated_by' => $userName,
|
|
'updated_at' => $now,
|
|
]);
|
|
if ($updated !== 1) {
|
|
continue;
|
|
}
|
|
|
|
$connection->table('fin_account_map_events')->insert([
|
|
'source' => 'payable',
|
|
'source_value' => $candidate['source_value'],
|
|
'old_account_short' => null,
|
|
'new_account_short' => $candidate['account_short'],
|
|
'user_id' => $userId,
|
|
'user_name' => $userName,
|
|
'created_at' => $now,
|
|
]);
|
|
$mapped++;
|
|
}
|
|
});
|
|
}
|
|
|
|
if ($mapped > 0) {
|
|
app(FinanceLedgerCalculator::class)->recalculate();
|
|
}
|
|
|
|
return [
|
|
'total' => $preview['total'],
|
|
'mapped' => $mapped,
|
|
'ambiguous' => $preview['ambiguous'],
|
|
'missing' => $preview['missing'],
|
|
];
|
|
}
|
|
|
|
private function candidate(object $row, string $accountShort, string $matchType): array
|
|
{
|
|
return [
|
|
'id' => (int) $row->id,
|
|
'source_value' => trim((string) $row->source_value),
|
|
'account_short' => $accountShort,
|
|
'match_type' => $matchType,
|
|
];
|
|
}
|
|
|
|
private function connection()
|
|
{
|
|
return DB::connection((string) config('upstream_mirrors.connection', 'business_mirror'));
|
|
}
|
|
}
|