134 lines
4.5 KiB
PHP
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),
|
|
],
|
|
];
|
|
}
|
|
}
|