CaiWuXiTong/app/Console/Commands/RunAccountBillsExportTask.php

148 lines
6.3 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Modules\EcommerceFinance\AccountBillColumns;
use Modules\EcommerceFinance\JdBillPlatform;
use Modules\EcommerceFinance\Repositories\DewuSettlementBillRepository;
use Modules\EcommerceFinance\Repositories\JdDailyBillRepository;
use Modules\EcommerceFinance\Repositories\PinduoduoSettlementBillRepository;
use Modules\EcommerceFinance\Repositories\StoreReceivingAlipayBillMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreReceivingDewuBillMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreReceivingDouyinBillMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreReceivingJdWalletBillRepository;
use Modules\EcommerceFinance\Repositories\StoreReceivingPinduoduoBillMirrorRepository;
use Modules\EcommerceFinance\Repositories\TaobaoWechatSettlementBillRepository;
use Modules\EcommerceFinance\Services\AccountBillsExportTasks;
use Modules\EcommerceFinance\Services\AccountLedgerRemarkMap;
use Throwable;
/**
* 后台执行「店铺收款账户账单」导出任务:生成 CSV 并更新任务状态,供页面列表下载。
*/
class RunAccountBillsExportTask extends Command
{
protected $signature = 'account-bills:export-run {token : 导出任务 token}';
protected $description = '执行店铺收款账户账单导出任务(后台进程调用)';
public function handle(AccountBillsExportTasks $tasks, AccountLedgerRemarkMap $remarkMap): int
{
$token = (string) $this->argument('token');
$task = $this->findTask($tasks, $token);
if ($task === null) {
$this->error('导出任务不存在:' . $token);
return self::FAILURE;
}
$task = $tasks->patch($task, ['status' => 'running', 'started_at' => now()->toDateTimeString()]);
try {
$columns = array_values((array) ($task['columns'] ?? []));
if ($columns === []) {
throw new \RuntimeException('导出任务没有可用字段。');
}
$csv = fopen($tasks->csvPath($task), 'w');
if ($csv === false) {
throw new \RuntimeException('无法创建导出文件。');
}
fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($csv, array_map(static fn (array $column): string => (string) ($column['label'] ?? ''), $columns));
$written = 0;
foreach ($this->rows($task, $remarkMap) as $row) {
fputcsv($csv, array_map(
static fn (array $column): string => self::cellValue($row, $column),
$columns,
));
$written++;
if ($written % 2000 === 0) {
$task = $tasks->patch($task, ['status' => 'running', 'rows' => $written]);
}
}
fclose($csv);
$task = $tasks->patch($task, [
'status' => 'done',
'rows' => $written,
'size' => (int) @filesize($tasks->csvPath($task)),
'finished_at' => now()->toDateTimeString(),
'error' => null,
]);
$this->info('导出完成:' . $written . ' 行');
return self::SUCCESS;
} catch (Throwable $exception) {
$tasks->patch($task, [
'status' => 'failed',
'finished_at' => now()->toDateTimeString(),
'error' => mb_substr($exception->getMessage(), 0, 200),
]);
$this->error('导出失败:' . $exception->getMessage());
return self::FAILURE;
}
}
private function findTask(AccountBillsExportTasks $tasks, string $token): ?array
{
$root = storage_path('app/account-bills-exports');
foreach (glob($root . '/*/' . $token . '.json') ?: [] as $path) {
$task = json_decode((string) file_get_contents($path), true);
if (is_array($task) && ($task['token'] ?? null) === $token) {
return $task;
}
}
return null;
}
/** @return iterable<int,array<string,mixed>> */
private function rows(array $task, AccountLedgerRemarkMap $remarkMap): iterable
{
$tab = (string) ($task['tab'] ?? '');
$filters = (array) ($task['filters'] ?? []);
$sort = (string) ($filters['sort'] ?? 'occurred_at');
$dir = strtolower((string) ($filters['dir'] ?? 'desc')) === 'asc' ? 'asc' : 'desc';
unset($filters['sort'], $filters['dir'], $filters['export'], $filters['token'], $filters['total'], $filters['columns']);
$rows = match ($tab) {
'taobao-alipay' => app(StoreReceivingAlipayBillMirrorRepository::class)->exportRows($filters, $sort, $dir),
'taobao-wechat' => app(TaobaoWechatSettlementBillRepository::class)->exportAccountBills($filters, $sort, $dir),
'pinduoduo' => app(PinduoduoSettlementBillRepository::class)->exportAccountBills($filters, $sort, $dir),
'dewu' => app(DewuSettlementBillRepository::class)->exportAccountBills($filters, $sort, $dir),
'jd-transactions', 'jingxi' => JdBillPlatform::isJdBillTab($tab)
? app(JdDailyBillRepository::class)->forPlatform(JdBillPlatform::platformForBillType($tab))->exportRows($filters, $sort, $dir)
: [],
'jd-wallet' => app(StoreReceivingJdWalletBillRepository::class)->exportRows($filters, $sort, $dir),
'douyin' => app(StoreReceivingDouyinBillMirrorRepository::class)->exportRows($filters, $sort, $dir),
default => throw new \RuntimeException('该页签暂不支持后台导出:' . $tab),
};
return $remarkMap->decorateAll($rows, $tab);
}
/** @param array<string,mixed> $row */
private static function cellValue(array $row, array $column): string
{
$field = (string) ($column['field'] ?? '');
$value = str_starts_with($field, 'payload.')
? data_get($row['payload'] ?? [], substr($field, 8))
: data_get($row, $field);
$text = trim((string) ($value ?? ''));
if ($text === '') {
return '';
}
if (preg_match('/^\d{12,}$/', $text) === 1) {
return '="' . $text . '"';
}
return preg_match('/^[=+\-@]/u', $text) === 1 ? "'" . $text : $text;
}
}