199 lines
7.1 KiB
PHP
199 lines
7.1 KiB
PHP
<?php
|
||
|
||
namespace App\Business;
|
||
|
||
use App\Models\Business\InputInvoiceReport;
|
||
use App\Models\Business\OutputInvoiceReport;
|
||
use Illuminate\Support\Collection;
|
||
use Throwable;
|
||
|
||
/**
|
||
* 发票数据报表:发票情况汇总在前,进项/销项明细在后。
|
||
*
|
||
* 汇总按公司名称合并进项报表与销项报表,金额均使用两张报表的合计口径;
|
||
* 差额 = 销项 − 进项,正数表示销项大于进项。
|
||
*/
|
||
class InvoiceReportsPageData
|
||
{
|
||
private const TABS = ['summary', 'input', 'output'];
|
||
|
||
public function page(?string $tab): array
|
||
{
|
||
$tab = in_array($tab, self::TABS, true) ? $tab : 'summary';
|
||
|
||
$inputRows = $this->reportRows(InputInvoiceReport::class);
|
||
$outputRows = $this->reportRows(OutputInvoiceReport::class);
|
||
|
||
if ($tab === 'summary') {
|
||
$allRows = $this->summaryRows($inputRows, $outputRows);
|
||
$filters = $this->summaryFilters();
|
||
$summaryRows = $this->filterSummaryRows($allRows, $filters);
|
||
|
||
return [
|
||
'view' => 'business.pages.invoice_reports',
|
||
'summaryRows' => $summaryRows,
|
||
'summaryTotals' => $this->summaryTotals($summaryRows),
|
||
'summaryFilters' => $filters,
|
||
'companyOptions' => $allRows->pluck('company_name')
|
||
->filter(static fn ($name): bool => (string) $name !== '')
|
||
->unique()
|
||
->sort()
|
||
->values(),
|
||
'taxpayerTypeOptions' => $allRows->pluck('taxpayer_type')
|
||
->filter(static fn ($type): bool => (string) $type !== '' && (string) $type !== '—')
|
||
->unique()
|
||
->sort()
|
||
->values(),
|
||
];
|
||
}
|
||
|
||
return [
|
||
'view' => 'business.pages.invoice_reports',
|
||
'items' => $tab === 'output' ? $outputRows : $inputRows,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 报表表在部分老环境可能尚未迁移,缺失时保持页面可用并展示空表。
|
||
*
|
||
* @param class-string $model
|
||
*/
|
||
private function reportRows(string $model): Collection
|
||
{
|
||
try {
|
||
return $model::query()
|
||
->orderBy('company_name')
|
||
->orderBy('id')
|
||
->get();
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
|
||
return collect();
|
||
}
|
||
}
|
||
|
||
private function summaryRows(Collection $inputRows, Collection $outputRows): Collection
|
||
{
|
||
$inputs = $inputRows->groupBy(fn ($row): string => trim((string) $row->company_name));
|
||
$outputs = $outputRows->groupBy(fn ($row): string => trim((string) $row->company_name));
|
||
|
||
$companyNames = $inputs->keys()
|
||
->merge($outputs->keys())
|
||
->filter(static fn ($name): bool => $name !== '')
|
||
->unique()
|
||
->sort()
|
||
->values();
|
||
|
||
$taxpayerTypes = $this->taxpayerTypes($companyNames);
|
||
|
||
return $companyNames->map(function (string $companyName) use ($inputs, $outputs, $taxpayerTypes): object {
|
||
$inputRows = $inputs->get($companyName, collect());
|
||
$outputRows = $outputs->get($companyName, collect());
|
||
|
||
$inputAmount = $this->sumField($inputRows, 'input_total_amount');
|
||
$inputTax = $this->sumField($inputRows, 'input_total_tax');
|
||
$outputAmount = $this->sumField($outputRows, 'output_total_amount');
|
||
$outputTax = $this->sumField($outputRows, 'output_total_tax');
|
||
$ratio = $outputAmount > 0 ? $inputAmount / $outputAmount : null;
|
||
|
||
return (object) [
|
||
'company_name' => $companyName,
|
||
'taxpayer_type' => $taxpayerTypes->get($companyName, '—'),
|
||
'input_total_amount' => $inputAmount,
|
||
'input_total_tax' => $inputTax,
|
||
'output_total_amount' => $outputAmount,
|
||
'output_total_tax' => $outputTax,
|
||
'input_output_ratio' => $ratio,
|
||
'is_warning' => $ratio !== null && $ratio < 0.8,
|
||
];
|
||
});
|
||
}
|
||
|
||
private function summaryTotals(Collection $rows): object
|
||
{
|
||
return (object) [
|
||
'input_total_amount' => round((float) $rows->sum('input_total_amount'), 2),
|
||
'input_total_tax' => round((float) $rows->sum('input_total_tax'), 2),
|
||
'output_total_amount' => round((float) $rows->sum('output_total_amount'), 2),
|
||
'output_total_tax' => round((float) $rows->sum('output_total_tax'), 2),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @return array{company:string,taxpayer_type:string,warning:string}
|
||
*/
|
||
private function summaryFilters(): array
|
||
{
|
||
$warning = strtolower(trim((string) request()->query('warning', '')));
|
||
|
||
return [
|
||
'company' => trim((string) request()->query('company', '')),
|
||
'taxpayer_type' => trim((string) request()->query('taxpayer_type', '')),
|
||
'warning' => in_array($warning, ['yes', 'no'], true) ? $warning : '',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param array{company:string,taxpayer_type:string,warning:string} $filters
|
||
*/
|
||
private function filterSummaryRows(Collection $rows, array $filters): Collection
|
||
{
|
||
return $rows->filter(function (object $row) use ($filters): bool {
|
||
if ($filters['company'] !== '' && $row->company_name !== $filters['company']) {
|
||
return false;
|
||
}
|
||
|
||
if ($filters['taxpayer_type'] !== '' && $row->taxpayer_type !== $filters['taxpayer_type']) {
|
||
return false;
|
||
}
|
||
|
||
if ($filters['warning'] === 'yes' && ! $row->is_warning) {
|
||
return false;
|
||
}
|
||
|
||
if ($filters['warning'] === 'no' && $row->is_warning) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
})->values();
|
||
}
|
||
|
||
/**
|
||
* 纳税人类型取公司基础信息共享镜像;镜像未就绪或公司未登记时显示「—」。
|
||
*/
|
||
private function taxpayerTypes(Collection $companyNames): Collection
|
||
{
|
||
$names = $companyNames
|
||
->map(static fn ($name): string => trim((string) $name))
|
||
->filter(static fn (string $name): bool => $name !== '')
|
||
->unique()
|
||
->values()
|
||
->all();
|
||
|
||
if ($names === []) {
|
||
return collect();
|
||
}
|
||
|
||
try {
|
||
$rows = app(\Modules\EcommerceFinance\Repositories\BusinessInformationMirrorRepository::class)
|
||
->allCompaniesForClassification(['_company_names' => $names]);
|
||
|
||
return collect($rows)
|
||
->mapWithKeys(static fn (array $row): array => [
|
||
trim((string) ($row['company_name'] ?? '')) => trim((string) ($row['taxpayer_category'] ?? '')),
|
||
])
|
||
->filter(static fn ($value, $key): bool => $key !== '' && (string) $value !== '');
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
|
||
return collect();
|
||
}
|
||
}
|
||
|
||
private function sumField(Collection $rows, string $field): float
|
||
{
|
||
return round($rows->sum(static fn ($row): float => (float) $row->{$field}), 2);
|
||
}
|
||
}
|