CaiWuXiTong/app/Business/InvoiceReportsPageData.php

199 lines
7.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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);
}
}