table('fin_accounts')->insert([ [ 'company_name' => '测试人员', 'account_short' => '测试人员支付宝', 'account_no' => '18800000001', 'account_type' => '支付宝', 'current_balance' => 0, ], [ 'company_name' => '其他公司', 'account_short' => '其他支付宝', 'account_no' => '18800000000', 'account_type' => '支付宝', 'current_balance' => 0, ], ]); $connection->table('fin_transactions')->insert([ [ 'txn_date' => '2026-10-02', 'account_short' => '测试人员支付宝', 'detail' => '待替换工单', 'income' => 10, 'expense' => 0, 'source' => 'workorder', 'source_value' => '测试人员支付宝', 'row_key' => 'old-workorder-alipay', ], [ 'txn_date' => '2026-10-02', 'account_short' => '其他支付宝', 'detail' => '其他账户保留', 'income' => 20, 'expense' => 0, 'source' => 'workorder', 'source_value' => '其他支付宝', 'row_key' => 'old-other-alipay', ], ]); $connection->table('fin_account_map')->insert([ [ 'source' => 'workorder', 'source_value' => '测试人员支付宝', 'account_short' => '测试人员支付宝', 'txn_count' => 1, 'income_sum' => 10, 'expense_sum' => 0, ], [ 'source' => 'workorder', 'source_value' => '其他支付宝', 'account_short' => '其他支付宝', 'txn_count' => 1, 'income_sum' => 20, 'expense_sum' => 0, ], ]); $file = $this->statementFile([ ['2026-10-02 09:00:00', 'ALI-1', '转账', '10.00', '', '110.00', '138******01', '*甲', '10.01 甲客户'], ['2026-10-03 10:00:00', 'ALI-2', '收款', '', '5.00', '105.00', '138******02', '*乙', '10.02 乙客户'], ['2026-11-01 11:00:00', 'ALI-3', '转账', '2.00', '', '107.00', '138******03', '*丙', '10.20 丙客户'], ]); $this->artisan('finance-ledger:import-alipay-statement', [ 'path' => $file, '--account' => '18800000001', '--account-short' => '测试人员支付宝', '--from' => '2026-10-01', '--to' => '2026-11-05', '--opening-balance' => '100.00', '--replace-sources' => 'workorder,alipay-company', ])->assertExitCode(0); $this->assertDatabaseMissing('fin_transactions', ['row_key' => 'old-workorder-alipay'], 'business_mirror'); $this->assertDatabaseHas('fin_transactions', ['row_key' => 'old-other-alipay'], 'business_mirror'); $this->assertDatabaseHas('fin_transactions', [ 'approval_no' => 'ALI-1', 'account_short' => '测试人员支付宝', 'counterparty_info' => '138******01 / *甲', 'income' => 10, 'expense' => 0, 'balance' => 110, 'payment_remark' => '10.01 甲客户', 'source' => 'alipay-company', ], 'business_mirror'); $this->assertDatabaseHas('fin_transactions', [ 'approval_no' => 'ALI-2', 'income' => 0, 'expense' => 5, 'balance' => 105, ], 'business_mirror'); $this->assertDatabaseHas('fin_account_opening', [ 'account_short' => '测试人员支付宝', 'opening_date' => '2026-10-01', 'opening_balance' => 100, ], 'business_mirror'); $this->assertDatabaseHas('fin_daily_flows', [ 'flow_date' => '2026-10-02', 'account_short' => '测试人员支付宝', 'opening_balance' => 100, 'inflow' => 10, 'closing_balance' => 110, ], 'business_mirror'); $this->assertDatabaseHas('fin_daily_flows', [ 'flow_date' => '2026-10-03', 'account_short' => '测试人员支付宝', 'opening_balance' => 110, 'outflow' => 5, 'closing_balance' => 105, ], 'business_mirror'); $this->assertDatabaseHas('fin_daily_flows', [ 'flow_date' => '2026-11-01', 'account_short' => '测试人员支付宝', 'opening_balance' => 105, 'inflow' => 2, 'closing_balance' => 107, ], 'business_mirror'); $this->assertSame(107.0, (float) $connection->table('fin_accounts') ->where('account_short', '测试人员支付宝') ->value('current_balance')); } /** @param list> $rows */ private function statementFile(array $rows): string { $matrix = [ ['#账号:18800000001'], ['#查询起始日期:2026-10-01 00:00:00查询终止日期:2026-11-05 20:45:14'], [ '序号', '入账时间', '支付宝交易号', '支付宝流水号', '商户订单号', '账务类型', '收入(+元)', '支出(-元)', '账户余额(元)', '对方账户', '对方名称', '银行订单号', '商品名称', '备注', '业务基础订单号', '付款备注', ], ]; foreach ($rows as $index => $row) { $matrix[] = [ (string) ($index + 1), $row[0], $row[1], '', '', $row[2], $row[3], $row[4], $row[5], $row[6], $row[7], '', '', '', '', $row[8], ]; } $xmlRows = ''; foreach ($matrix as $row) { $xmlRows .= ''; foreach ($row as $value) { $xmlRows .= '' . htmlspecialchars((string) $value, ENT_XML1 | ENT_QUOTES, 'UTF-8') . ''; } $xmlRows .= ''; } $xml = '' . '' . '' . $xmlRows . '
'; $path = tempnam(sys_get_temp_dir(), 'alipay-statement-') . '.xls'; file_put_contents($path, $xml); return $path; } }