CaiWuXiTong/modules/EcommerceFinance/View/SupplierNegativeAlertPageData.php
苏木 6eae3c6da5 feat: 供应商负数预警展示采购人员 (#734)
按供应商名称批量匹配供应商管理中的采购人员,并在负数原因后展示。
Co-authored-by: 苏木 <594326199@qq.com>
Co-committed-by: 苏木 <594326199@qq.com>
2026-10-28 14:41:10 +08:00

203 lines
7.6 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 Modules\EcommerceFinance\View;
use App\Http\Controllers\Business\PayablesFinalActions;
use Illuminate\Pagination\LengthAwarePaginator;
use Modules\EcommerceFinance\Repositories\SupplierManagementMirrorRepository;
use Modules\EcommerceFinance\Services\PayablesFinalRegistry;
use Modules\EcommerceFinance\Services\SupplierPayableOverrideStore;
use Throwable;
/**
* 供应商负数预警:直接读取「供应商应付(终版)→ 供应商应付表」,
* 仅保留「供应商余额 - 应付余额 + 预估金额 < 0」的记录。
*/
class SupplierNegativeAlertPageData
{
private const DATASET = 'supplier-payables-report';
private const PER_PAGE = 25;
public function __construct(
private readonly SupplierPayableOverrideStore $store,
private readonly PayablesFinalRegistry $registry,
private readonly SupplierManagementMirrorRepository $supplierManagement,
) {
}
/** @return array<string,mixed> */
public function page(): array
{
$rows = $this->store->listRows(
PayablesFinalActions::DATASET_PREFIX . 'supplier:' . self::DATASET,
(int) (company_id() ?: 1),
);
$negativeRows = [];
foreach ($rows as $row) {
$balance = $this->number($row['supplier_balance'] ?? null) ?? 0.0;
$shouldPay = $this->number($row['should_pay'] ?? null) ?? 0.0;
$estimatedPrice = $this->number($row['estimated_price'] ?? null) ?? 0.0;
$alertAmount = $balance - $shouldPay + $estimatedPrice;
if ($alertAmount >= 0) {
continue;
}
$row['supplier_balance'] = $balance;
$row['_negative_alert_amount'] = round($alertAmount, 2);
$negativeRows[] = $row;
}
usort($negativeRows, static function (array $left, array $right): int {
return strcmp(
(string) ($right['summary_month'] ?? ''),
(string) ($left['summary_month'] ?? ''),
) ?: strnatcasecmp(
(string) ($left['supplier'] ?? ''),
(string) ($right['supplier'] ?? ''),
);
});
$months = [];
foreach ($negativeRows as $row) {
$month = trim((string) ($row['summary_month'] ?? ''));
if (preg_match('/^\d{4}-\d{2}$/', $month) === 1) {
$months[$month] = true;
}
}
$months = array_keys($months);
rsort($months);
$requestedMonth = trim((string) request()->query('month', ''));
$month = in_array($requestedMonth, $months, true) ? $requestedMonth : '';
if ($month !== '') {
$negativeRows = array_values(array_filter(
$negativeRows,
static fn (array $row): bool => (string) ($row['summary_month'] ?? '') === $month,
));
}
$supplierNames = [];
foreach ($negativeRows as $row) {
$supplier = trim((string) ($row['supplier'] ?? ''));
if ($supplier !== '') {
$supplierNames[$supplier] = true;
}
}
$supplierNames = array_keys($supplierNames);
sort($supplierNames, SORT_NATURAL | SORT_FLAG_CASE);
$supplierQuery = mb_substr(trim((string) request()->query('supplier', '')), 0, 191);
if ($supplierQuery !== '') {
$needle = mb_strtolower($supplierQuery);
$negativeRows = array_values(array_filter(
$negativeRows,
static function (array $row) use ($needle): bool {
$haystack = mb_strtolower(implode("\n", [
(string) ($row['supplier'] ?? ''),
(string) ($row['company'] ?? ''),
]));
return str_contains($haystack, $needle);
},
));
}
$supplierCounts = [];
foreach ($negativeRows as $row) {
$key = trim((string) ($row['company'] ?? '')) . "\0" . trim((string) ($row['supplier'] ?? ''));
$supplierCounts[$key] = true;
}
$total = count($negativeRows);
$pages = max((int) ceil($total / self::PER_PAGE), 1);
$page = min(max((int) request()->query('page', 1), 1), $pages);
$pageRows = array_slice($negativeRows, ($page - 1) * self::PER_PAGE, self::PER_PAGE);
$pageSupplierNames = array_values(array_unique(array_filter(
array_map(
static fn (array $row): string => trim((string) ($row['supplier'] ?? '')),
$pageRows,
),
static fn (string $name): bool => $name !== '',
)));
$purchasersBySupplier = [];
if ($pageSupplierNames !== []) {
try {
foreach ($this->supplierManagement->suppliersByNames($pageSupplierNames) as $supplier) {
$name = trim((string) ($supplier->scmname ?? ''));
$buyer = trim((string) ($supplier->buyer ?? ''));
if ($name !== '' && $buyer !== '') {
$purchasersBySupplier[$name][$buyer] = true;
}
}
} catch (Throwable $exception) {
report($exception);
}
}
$purchasersBySupplier = array_map(
static fn (array $buyers): string => implode('、', array_keys($buyers)),
$purchasersBySupplier,
);
$items = array_map(
static function (array $row) use ($purchasersBySupplier): object {
$supplier = trim((string) ($row['supplier'] ?? ''));
$row['purchaser'] = $purchasersBySupplier[$supplier] ?? '';
return (object) $row;
},
$pageRows,
);
$negativeAlertFields = [];
$fields = collect($this->registry->fields(
PayablesFinalRegistry::SECTION_SUPPLIER,
self::DATASET,
))->map(
static fn (array $definition, string $name): array => ['name' => $name] + $definition,
)->values()->all();
foreach ($fields as $field) {
$negativeAlertFields[] = $field;
if (($field['name'] ?? '') === 'negative_reason') {
$negativeAlertFields[] = ['name' => 'purchaser', 'label' => '采购', 'type' => 'text'];
}
}
return [
'view' => 'business.pages.negative-alerts.supplier-negative-alert',
'negativeAlertFields' => $negativeAlertFields,
'negativeAlertRows' => new LengthAwarePaginator(
$items,
$total,
self::PER_PAGE,
$page,
[
'path' => request()->url(),
'query' => request()->except('page'),
],
),
'negativeAlertMonths' => $months,
'negativeAlertMonth' => $month,
'negativeAlertSupplier' => $supplierQuery,
'negativeAlertSupplierNames' => $supplierNames,
'negativeAlertSupplierCount' => count($supplierCounts),
'negativeAlertTotal' => $total,
'negativeAlertSourceUrl' => route('business.pages.show', [
'section' => 'supplier-payables-final',
'page' => 'supplier-payables-report',
]),
];
}
private function number(mixed $value): ?float
{
if ($value === null || trim((string) $value) === '') {
return null;
}
$normalized = str_replace([',', ',', ' '], '', trim((string) $value));
return is_numeric($normalized) ? (float) $normalized : null;
}
}