CaiWuXiTong/modules/EcommerceFinance/Repositories/InternalFlowReportRepository.php
DoDo 450a9e42f9 feat: 完善经营报表汇总与基础资料状态标签
经营报表保留原表头并补齐本表汇总;基础资料只增加表内分色标签。
2026-10-26 16:13:27 +08:00

134 lines
4.5 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Illuminate\Database\ConnectionInterface;
use Modules\EcommerceFinance\Services\InternalFlowWorkbookImporter;
/**
* 内部流转报表读取共享业务镜像中的导入结果。
*
* 页面只读;数据由 InternalFlowWorkbookImporter 按整份工作簿替换导入。
*/
class InternalFlowReportRepository
{
public const TABLE = InternalFlowWorkbookImporter::TABLE;
/** 允许排序的列,防止把用户输入拼进 SQL。 */
public const SORTABLE = [
'company_name',
'tag',
'projected_sales',
'invoiced_to_buyer',
'internal_invoiced',
'required_input_cost',
'receipt_from_supplier',
'receipt_from_suofeiya',
'receipt_from_gujia',
'internal_transfer_1',
'internal_transfer_2',
'internal_transfer_3',
'receipt_total',
'remaining_gap',
'invoicing_ratio',
];
private readonly ConnectionInterface $connection;
public function __construct(GenericBusinessMirrorRepository $mirror)
{
$this->connection = $mirror->connection();
}
public function ready(): bool
{
return $this->connection->getSchemaBuilder()->hasTable(self::TABLE);
}
/**
* @param array{company_name?:string,tag?:string} $filters
* @return array{data: list<array<string,mixed>>, total: int, summary: array<string,int|float>}
*/
public function paginate(int $page, int $perPage, array $filters = [], ?string $sort = null, string $direction = 'asc'): array
{
if (! $this->ready()) {
return [
'data' => [],
'total' => 0,
'summary' => [
'companies' => 0,
'projected_sales' => 0.0,
'receipt_total' => 0.0,
'remaining_gap' => 0.0,
'average_invoicing_ratio' => 0.0,
],
];
}
$query = $this->connection->table(self::TABLE);
$companyName = trim((string) ($filters['company_name'] ?? ''));
if ($companyName !== '') {
$query->where('company_name', 'like', '%' . addcslashes($companyName, '%_\\') . '%');
}
$tag = trim((string) ($filters['tag'] ?? ''));
if ($tag !== '') {
$query->where('tag', '=', $tag);
}
$total = (clone $query)->count();
$totals = (clone $query)->selectRaw(
'COUNT(*) AS companies, '
. 'COALESCE(SUM(projected_sales), 0) AS projected_sales, '
. 'COALESCE(SUM(receipt_total), 0) AS receipt_total, '
. 'COALESCE(SUM(remaining_gap), 0) AS remaining_gap',
)->first();
$ratios = (clone $query)
->whereNotNull('invoicing_ratio')
->pluck('invoicing_ratio')
->map(static function (mixed $value): ?float {
$text = str_replace([',', ' ', '%'], '', trim((string) $value));
if ($text === '' || $text === '/' || ! is_numeric($text)) {
return null;
}
$ratio = (float) $text;
if (str_contains((string) $value, '%') || $ratio > 1) {
$ratio /= 100;
}
return $ratio >= 0 ? $ratio : null;
})
->filter(static fn (?float $value): bool => $value !== null)
->values();
$column = in_array($sort, self::SORTABLE, true) ? $sort : null;
$direction = strtolower($direction) === 'desc' ? 'desc' : 'asc';
if ($column !== null) {
$query->orderBy($column, $direction)->orderBy('sort_order');
} else {
$query->orderBy('sort_order')->orderBy('id');
}
$rows = $query
->forPage(max($page, 1), min(max($perPage, 1), 100))
->get()
->map(static fn (object $row): array => (array) $row)
->all();
return [
'data' => $rows,
'total' => $total,
'summary' => [
'companies' => (int) ($totals->companies ?? $total),
'projected_sales' => round((float) ($totals->projected_sales ?? 0), 2),
'receipt_total' => round((float) ($totals->receipt_total ?? 0), 2),
'remaining_gap' => round((float) ($totals->remaining_gap ?? 0), 2),
'average_invoicing_ratio' => round((float) ($ratios->avg() ?? 0) * 100, 2),
],
];
}
}