diff --git a/app/Http/Controllers/Business/FinanceLedger.php b/app/Http/Controllers/Business/FinanceLedger.php index 458a98c5..37737c0d 100644 --- a/app/Http/Controllers/Business/FinanceLedger.php +++ b/app/Http/Controllers/Business/FinanceLedger.php @@ -386,11 +386,10 @@ class FinanceLedger extends Controller abort_if($accountShort === '', 422, '账户简称不能为空。'); $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); - abort_unless( - $connection->table('fin_accounts')->where('account_short', $accountShort)->exists(), - 422, - '账户简称不存在,请刷新账户总表后重试。', - ); + $account = $connection->table('fin_accounts') + ->where('account_short', $accountShort) + ->first(['account_no', 'account_last4']); + abort_if($account === null, 422, '账户简称不存在,请刷新账户总表后重试。'); if ($accountType !== '') { $allowedTypes = FinanceLedgerAccountType::options( @@ -412,21 +411,36 @@ class FinanceLedger extends Controller 'updated_by' => (string) user()->name, 'updated_at' => $now, ]; + $accountLast4 = FinanceLedgerAccountType::last4( + $accountType, + (string) ($account->account_no ?? ''), + (string) ($account->account_last4 ?? ''), + ); - if ($existing === null) { - $overrides->insert($values + [ - 'account_short' => $accountShort, - 'created_at' => $now, - ]); - } else { - $overrides->where('account_short', $accountShort)->update($values); - } + $connection->transaction(function () use ($connection, $overrides, $existing, $values, $accountShort, $now, $accountLast4): void { + if ($existing === null) { + $overrides->insert($values + [ + 'account_short' => $accountShort, + 'created_at' => $now, + ]); + } else { + $overrides->where('account_short', $accountShort)->update($values); + } + + $connection->table('fin_accounts') + ->where('account_short', $accountShort) + ->update([ + 'account_last4' => $accountLast4, + 'updated_at' => $now, + ]); + }); return response()->json([ 'ok' => true, 'account_short' => $accountShort, 'account_type' => $accountType, 'label' => $accountType !== '' ? $accountType : '未填写', + 'account_last4' => $accountLast4, ], 200, ['Cache-Control' => 'private, no-store']); } diff --git a/app/Utilities/FinanceLedgerAccountType.php b/app/Utilities/FinanceLedgerAccountType.php index 92518e36..a17d8eba 100644 --- a/app/Utilities/FinanceLedgerAccountType.php +++ b/app/Utilities/FinanceLedgerAccountType.php @@ -4,10 +4,14 @@ namespace App\Utilities; final class FinanceLedgerAccountType { + public const ALIPAY = '支付宝'; + + public const BANK_ACCOUNT_TYPES = ['公户', '个人卡', '网商银行']; + public const DEFAULT_OPTIONS = [ '个人卡', '公户', - '支付宝', + self::ALIPAY, '网商银行', ]; @@ -28,4 +32,23 @@ final class FinanceLedgerAccountType return $types; } + + public static function last4(?string $accountType, ?string $accountNo, ?string $fallback = null): string + { + $type = trim((string) $accountType); + if ($type === self::ALIPAY) { + return '/'; + } + + if (in_array($type, self::BANK_ACCOUNT_TYPES, true)) { + $digits = preg_replace('/\D+/', '', (string) $accountNo); + if (strlen((string) $digits) >= 4) { + return substr((string) $digits, -4); + } + } + + $fallback = trim((string) $fallback); + + return $fallback !== '' ? $fallback : '/'; + } } diff --git a/modules/EcommerceFinance/Services/FinanceLedgerBaseImporter.php b/modules/EcommerceFinance/Services/FinanceLedgerBaseImporter.php index 447d57ee..fca77f7c 100644 --- a/modules/EcommerceFinance/Services/FinanceLedgerBaseImporter.php +++ b/modules/EcommerceFinance/Services/FinanceLedgerBaseImporter.php @@ -3,6 +3,7 @@ namespace Modules\EcommerceFinance\Services; use App\Utilities\FinanceLedgerAccountShouldKeep; +use App\Utilities\FinanceLedgerAccountType; use Illuminate\Support\Facades\DB; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\IOFactory; @@ -12,8 +13,8 @@ use RuntimeException; /** * 导入财务台账的基础数据:账户、店铺、公司和期初余额。 * - * 源文件是财务维护的《银行当前余额-*.xlsx》。这里只做结构校验和全量 - * 覆盖,不推断业务口径;导入完成后由 FinanceLedgerCalculator 重算流水。 + * 源文件是财务维护的《银行当前余额-*.xlsx》。这里只做结构校验、后4位 + * 规范化和全量覆盖,不推断其他业务口径;导入完成后由计算器重算流水。 */ class FinanceLedgerBaseImporter { @@ -121,19 +122,25 @@ class FinanceLedgerBaseImporter } $keepIndex = $headerIndexes['账户是否保留'] ?? null; + $accountNo = $this->text($value('账号', 2)); + $accountType = $this->text($value('账户类型', 5)); $rows[] = [ 'company_name' => $company, 'account_short' => $short, 'region' => null, - 'account_no' => $this->text($value('账号', 2)), + 'account_no' => $accountNo, 'bank_name' => $this->text($value('开户行', 3)), 'bank_city_branch' => $this->text($value('开户城市/网点', 4)), - 'account_type' => $this->text($value('账户类型', 5)), + 'account_type' => $accountType, 'company_plan' => $this->text($value('公司规划', 6)), 'account_should_keep' => FinanceLedgerAccountShouldKeep::normalize( $keepIndex === null ? null : $this->text($row[$keepIndex] ?? null), ), - 'account_last4' => $this->text($value('后4位', 7)), + 'account_last4' => FinanceLedgerAccountType::last4( + $accountType, + $accountNo, + $this->text($value('后4位', 7)), + ), 'single_limit' => $this->text($value('单笔限额', 8)), 'daily_limit' => $this->text($value('日限额', 9)), 'monthly_limit' => $this->text($value('月限额', 10)), diff --git a/resources/views/business/pages/financial-ledger/ledger.blade.php b/resources/views/business/pages/financial-ledger/ledger.blade.php index 07d138b5..78f06150 100644 --- a/resources/views/business/pages/financial-ledger/ledger.blade.php +++ b/resources/views/business/pages/financial-ledger/ledger.blade.php @@ -323,7 +323,7 @@ @endif - {{ $row->account_last4 }}{{ $moneyWan($row->single_limit) }}{{ $moneyWan($row->daily_limit) }}{{ $moneyWan($row->monthly_limit) }}{{ $moneyWan($row->yearly_limit) }}{{ $moneyWan($row->current_balance) }}{{ $moneyWan($row->daily_available) }}{{ $moneyWan($row->blocked_amount) }}{{ $moneyWan($row->daily_available) }}{{ $row->clearing_days !== null ? number_format((float) $row->clearing_days, 2) : '' }}{{ $moneyWan($row->raise_target) }}{{ $row->raise_status }}{{ $row->raise_actions }}{{ $row->done_actions }}{{ $row->next_action }}{{ $row->remark }}{{ $row->owner }} + {{ $row->account_last4 }}{{ $moneyWan($row->single_limit) }}{{ $moneyWan($row->daily_limit) }}{{ $moneyWan($row->monthly_limit) }}{{ $moneyWan($row->yearly_limit) }}{{ $moneyWan($row->current_balance) }}{{ $moneyWan($row->daily_available) }}{{ $moneyWan($row->blocked_amount) }}{{ $moneyWan($row->daily_available) }}{{ $row->clearing_days !== null ? number_format((float) $row->clearing_days, 2) : '' }}{{ $moneyWan($row->raise_target) }}{{ $row->raise_status }}{{ $row->raise_actions }}{{ $row->done_actions }}{{ $row->next_action }}{{ $row->remark }}{{ $row->owner }} @empty
没有匹配的账户数据。
@@ -1001,6 +1001,10 @@ item.closest('td')?.setAttribute('data-account-should-keep', value); } }); + if (field === 'account_type') { + const last4Cell = select.closest('tr')?.querySelector('[data-account-last4]'); + if (last4Cell) last4Cell.textContent = payload.account_last4 || '/'; + } if (field === 'status') { const row = select.closest('tr'); const currentCell = row?.querySelector('[data-account-current-balance]'); diff --git a/tests/Feature/Business/FinanceLedgerAccountStatusTest.php b/tests/Feature/Business/FinanceLedgerAccountStatusTest.php index 21eeb3c8..2cd580a7 100644 --- a/tests/Feature/Business/FinanceLedgerAccountStatusTest.php +++ b/tests/Feature/Business/FinanceLedgerAccountStatusTest.php @@ -610,6 +610,42 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase ])->assertUnprocessable()->assertJsonPath('message', '请选择现有账户类型。'); } + public function test_account_type_change_derives_last4_and_alipay_uses_slash(): void + { + DB::connection('business_mirror')->table('fin_accounts') + ->where('account_short', '测试账户') + ->update([ + 'account_no' => '6222-1234 5678', + 'account_last4' => '0000', + ]); + + $this->loginAs()->postJson($this->typeUrl(), [ + 'account_short' => '测试账户', + 'account_type' => '网商银行', + ]) + ->assertOk() + ->assertJsonPath('account_last4', '5678'); + $this->assertDatabaseHas('fin_accounts', [ + 'account_short' => '测试账户', + 'account_last4' => '5678', + ], 'business_mirror'); + + $this->loginAs()->postJson($this->typeUrl(), [ + 'account_short' => '测试账户', + 'account_type' => '支付宝', + ]) + ->assertOk() + ->assertJsonPath('account_last4', '/'); + $this->assertDatabaseHas('fin_accounts', [ + 'account_short' => '测试账户', + 'account_last4' => '/', + ], 'business_mirror'); + + $this->loginAs()->get($this->pageUrl()) + ->assertOk() + ->assertSee('/', false); + } + public function test_account_status_can_be_saved_and_updated_without_duplicate_rows(): void { $payload = [ diff --git a/tests/Unit/FinanceLedgerAccountTypeTest.php b/tests/Unit/FinanceLedgerAccountTypeTest.php new file mode 100644 index 00000000..1610e023 --- /dev/null +++ b/tests/Unit/FinanceLedgerAccountTypeTest.php @@ -0,0 +1,27 @@ +assertSame('5678', FinanceLedgerAccountType::last4($type, '6222-1234 5678', '0000')); + } + } + + public function test_alipay_always_uses_a_slash(): void + { + $this->assertSame('/', FinanceLedgerAccountType::last4('支付宝', 'alipay@example.com', '1234')); + } + + public function test_other_types_keep_the_fallback_when_no_bank_last_four_can_be_derived(): void + { + $this->assertSame('1234', FinanceLedgerAccountType::last4('其他', 'alipay@example.com', '1234')); + $this->assertSame('/', FinanceLedgerAccountType::last4('公户', '123', '')); + } +}