支持京东钱包月初至截止日受控导入 #262
@ -10,8 +10,8 @@ use Throwable;
|
||||
class ImportJdWalletBills extends Command
|
||||
{
|
||||
protected $signature = 'business-mirror:import-jd-wallet
|
||||
{path : 胜算京东钱包完整月份 UTF-8 CSV} {month : YYYY-MM}
|
||||
{--count= : 核对笔数} {--income= : 核对收入} {--expense= : 核对支出}';
|
||||
{path : 胜算京东钱包指定月份月初至截止日期 UTF-8 CSV} {month : YYYY-MM}
|
||||
{--through= : 截止日期 YYYY-MM-DD,默认月末} {--count= : 核对笔数} {--income= : 核对收入} {--expense= : 核对支出}';
|
||||
|
||||
protected $description = '核对并启用胜算京东钱包月份镜像,保留旧版本';
|
||||
|
||||
@ -24,7 +24,7 @@ class ImportJdWalletBills extends Command
|
||||
}
|
||||
try {
|
||||
$result = $importer->import((string) $this->argument('path'), (string) $this->argument('month'),
|
||||
(int) $this->option('count'), (string) $this->option('income'), (string) $this->option('expense'));
|
||||
(int) $this->option('count'), (string) $this->option('income'), (string) $this->option('expense'), $this->option('through'));
|
||||
$this->info('京东钱包已启用,笔数:' . $result['row_count']);
|
||||
return self::SUCCESS;
|
||||
} catch (Throwable $e) {
|
||||
|
||||
@ -21,7 +21,7 @@ class JdWalletBillImporter
|
||||
|
||||
public function __construct(private readonly DatabaseManager $databases) {}
|
||||
|
||||
public function import(string $path, string $month, int $expectedCount, string $expectedIncome, string $expectedExpense): array
|
||||
public function import(string $path, string $month, int $expectedCount, string $expectedIncome, string $expectedExpense, ?string $through = null): array
|
||||
{
|
||||
if (! preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $month) || $expectedCount < 1) {
|
||||
throw new InvalidArgumentException('必须指定完整月份及核对笔数。');
|
||||
@ -30,6 +30,13 @@ class JdWalletBillImporter
|
||||
$expectedOut = $this->cents($expectedExpense);
|
||||
$start = $month . '-01';
|
||||
$end = Carbon::parse($start)->addMonth()->format('Y-m-d');
|
||||
if ($through !== null) {
|
||||
$date = \DateTimeImmutable::createFromFormat('!Y-m-d', $through);
|
||||
if (! $date || $date->format('Y-m-d') !== $through || $through < $start || $through >= $end) {
|
||||
throw new InvalidArgumentException('截止日期必须是指定月份内的有效日期。');
|
||||
}
|
||||
$end = $date->modify('+1 day')->format('Y-m-d');
|
||||
}
|
||||
$file = fopen($path, 'rb');
|
||||
if ($file === false) throw new RuntimeException('无法读取京东钱包明细。');
|
||||
$db = $this->databases->connection(config('upstream_mirrors.connection', 'business_mirror'));
|
||||
@ -95,8 +102,14 @@ class JdWalletBillImporter
|
||||
'row_count' => $count, 'income' => $this->decimal($income), 'expense' => $this->decimal($expense),
|
||||
'imported_at' => now()->format('Y-m-d H:i:s'),
|
||||
];
|
||||
// Publish only reconciled complete months; keep the preceding generation for rollback.
|
||||
$db->transaction(fn () => $db->table('jd_wallet_bill_periods')->updateOrInsert(['period_start' => $start], $state));
|
||||
// Activate only the reconciled range and retain the previous generation for rollback.
|
||||
$db->transaction(function () use ($db, $start, $end, $state): void {
|
||||
$previous = $db->table('jd_wallet_bill_periods')->where('period_start', $start)->lockForUpdate()->first();
|
||||
if ($previous !== null && $previous->period_end > $end) {
|
||||
throw new InvalidArgumentException('截止日期早于已导入范围,未替换现有账单。');
|
||||
}
|
||||
$db->table('jd_wallet_bill_periods')->updateOrInsert(['period_start' => $start], $state);
|
||||
});
|
||||
return $state;
|
||||
} catch (Throwable $e) {
|
||||
$db->table('jd_wallet_bill_rows')->where('generation', $generation)->delete();
|
||||
|
||||
@ -83,6 +83,37 @@ class JdWalletBillTest extends TestCase
|
||||
$this->assertSame(12.0, app(StoreReceivingJdWalletBillRepository::class)->paginate([], 'occurred_at', 'asc', 1, 10)['summary']['income']);
|
||||
}
|
||||
|
||||
public function test_month_to_date_import_rejects_shorter_replacement_and_out_of_range_rows(): void
|
||||
{
|
||||
$row = array_replace($this->row('2000000000000000001', '10.00'), ['费用结算时间' => '2026-08-23 12:00:00']);
|
||||
$first = $this->import([$row], 1, '10.00', '0.00', '2026-08-23');
|
||||
$this->assertSame('2026-08-24', $first['period_end']);
|
||||
|
||||
try {
|
||||
$earlier = array_replace($row, ['费用结算时间' => '2026-08-22 12:00:00']);
|
||||
$this->import([$earlier], 1, '10.00', '0.00', '2026-08-22');
|
||||
$this->fail('A shorter file must not replace the active range.');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->assertNotSame('', $e->getMessage());
|
||||
}
|
||||
$this->assertSame($first['active_generation'], DB::connection('business_mirror')->table('jd_wallet_bill_periods')->value('active_generation'));
|
||||
|
||||
$later = array_replace($row, ['记录编码' => '2000000000000000002', '费用结算时间' => '2026-08-24 12:00:00']);
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->import([$row, $later], 2, '20.00', '0.00', '2026-08-23');
|
||||
}
|
||||
|
||||
public function test_month_to_date_import_can_extend_a_verified_range(): void
|
||||
{
|
||||
$row = array_replace($this->row('2000000000000000001', '10.00'), ['费用结算时间' => '2026-08-23 12:00:00']);
|
||||
$first = $this->import([$row], 1, '10.00', '0.00', '2026-08-23');
|
||||
$later = array_replace($row, ['记录编码' => '2000000000000000002', '费用结算时间' => '2026-08-24 12:00:00']);
|
||||
$second = $this->import([$row, $later], 2, '20.00', '0.00', '2026-08-24');
|
||||
$this->assertSame('2026-08-25', $second['period_end']);
|
||||
$this->assertNotSame($first['active_generation'], $second['active_generation']);
|
||||
$this->assertSame(2, app(StoreReceivingJdWalletBillRepository::class)->paginate(['start' => '2026-08-01', 'end' => '2026-08-24'], 'occurred_at', 'asc', 1, 10)['total']);
|
||||
}
|
||||
|
||||
public function test_uninitialized_mirror_is_not_reported_as_empty_success(): void
|
||||
{
|
||||
$this->expectExceptionMessage('jd_wallet_bill_mirror_not_ready');
|
||||
@ -104,7 +135,7 @@ class JdWalletBillTest extends TestCase
|
||||
$this->assertSame(0.0, $repo->paginate(['start' => '2026-09-01'], 'occurred_at', 'desc', 1, 10)['summary']['balance']);
|
||||
}
|
||||
|
||||
private function import(array $rows, int $count, string $income, string $expense): array
|
||||
private function import(array $rows, int $count, string $income, string $expense, ?string $through = null): array
|
||||
{
|
||||
$path = tempnam(sys_get_temp_dir(), 'jd-daily-');
|
||||
$this->files[] = $path;
|
||||
@ -112,7 +143,7 @@ class JdWalletBillTest extends TestCase
|
||||
fputcsv($file, array_keys($rows[0]));
|
||||
foreach ($rows as $row) fputcsv($file, array_values($row));
|
||||
fclose($file);
|
||||
return app(JdWalletBillImporter::class)->import($path, '2026-08', $count, $income, $expense);
|
||||
return app(JdWalletBillImporter::class)->import($path, '2026-08', $count, $income, $expense, $through);
|
||||
}
|
||||
|
||||
private function row(string $code, string $income, string $expense = '0.00'): array
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user