CaiWuXiTong/modules/EcommerceFinance/View/SupplierReconciliationPageData.php
renshen 87eaf48c2c 供应商对账:应付款项下新增对账页面(财务端数据 / 厂家端数据) (#364)
新增供应商对账目录:财务端实时上传情况与行级差异(条码+单价匹配,四类口径),厂家端严格按模板上传(表头不一致/必填缺失/非数字/串厂一律失败);复用终版存储与流式读取。
2026-10-17 08:49:17 +08:00

175 lines
6.3 KiB
PHP

<?php
namespace Modules\EcommerceFinance\View;
use App\Http\Controllers\Business\PayablesFinalActions;
use Modules\EcommerceFinance\Services\SupplierPayableOverrideStore;
use Modules\EcommerceFinance\Services\SupplierStatementMatcher;
use Modules\EcommerceFinance\Services\SupplierStatementStore;
/**
* 「供应商对账」页面数据:
* - 财务端数据:按供应商汇总 + 行级差异(条码 + 单价 匹配,四类结果带结算口径)
* - 厂家端数据:厂家上传的对账单(实时)与只读的退货明细
*/
class SupplierReconciliationPageData
{
private const PER_PAGE = 100;
public function __construct(
private readonly SupplierPayableOverrideStore $store,
private readonly SupplierStatementStore $statements,
private readonly SupplierStatementMatcher $matcher,
) {
}
/** @return array<string,mixed> */
public function financeData(): array
{
$companyId = $this->companyId();
$mine = $this->store->listRows(
PayablesFinalActions::DATASET_PREFIX . 'supplier:payment-reconciliation',
$companyId,
);
$months = [];
foreach ($mine as $row) {
$month = substr(trim((string) ($row['summary_month'] ?? '')), 0, 7);
if ($month !== '') {
$months[$month] = true;
}
}
$months = array_keys($months);
rsort($months);
$month = trim((string) request()->query('month', ''));
if ($month !== '' && preg_match('/^\d{4}-\d{2}$/', $month) === 1) {
$mine = array_values(array_filter(
$mine,
static fn (array $row): bool => substr(trim((string) ($row['summary_month'] ?? '')), 0, 7) === $month,
));
} else {
$month = '';
}
$suppliers = [];
foreach ($mine as $row) {
$name = trim((string) ($row['supplier'] ?? ''));
if ($name !== '') {
$suppliers[$name] = true;
}
}
foreach ($this->statements->uploadedSuppliers($companyId) as $name) {
$suppliers[$name] = true;
}
$supplierNames = array_keys($suppliers);
sort($supplierNames);
$uploads = [];
foreach ($supplierNames as $name) {
$latest = $this->statements->uploads($name, $companyId, 1);
$uploads[$name] = $latest[0] ?? null;
}
$selected = trim((string) request()->query('supplier', ''));
$detail = null;
if ($selected !== '' && in_array($selected, $supplierNames, true)) {
$detail = $this->detail($mine, $selected, $companyId);
}
return [
'view' => 'business.pages.supplier-reconciliation.finance-data',
'supplierNames' => $supplierNames,
'uploads' => $uploads,
'months' => $months,
'month' => $month,
'selectedSupplier' => $selected,
'detail' => $detail,
'statementTemplateUrl' => route('business.supplier-reconciliation.template'),
'uploadUrl' => route('business.supplier-reconciliation.statement.upload'),
];
}
/** @return array<string,mixed> */
public function supplierData(): array
{
$companyId = $this->companyId();
$mine = $this->store->listRows(
PayablesFinalActions::DATASET_PREFIX . 'supplier:payment-reconciliation',
$companyId,
);
$suppliers = [];
foreach ($mine as $row) {
$name = trim((string) ($row['supplier'] ?? ''));
if ($name !== '') {
$suppliers[$name] = true;
}
}
foreach ($this->statements->uploadedSuppliers($companyId) as $name) {
$suppliers[$name] = true;
}
$supplierNames = array_keys($suppliers);
sort($supplierNames);
$selected = trim((string) request()->query('supplier', ''));
if ($selected === '' || ! in_array($selected, $supplierNames, true)) {
$selected = $supplierNames[0] ?? '';
}
$page = max((int) request()->query('rows_page', 1), 1);
$statementRows = $selected !== '' ? $this->statements->rows($selected, $companyId) : [];
$returnRows = $selected !== '' ? $this->returnRows($selected, $companyId) : [];
return [
'view' => 'business.pages.supplier-reconciliation.supplier-data',
'supplierNames' => $supplierNames,
'selectedSupplier' => $selected,
'uploads' => $selected !== '' ? $this->statements->uploads($selected, $companyId) : [],
'statementRows' => array_slice($statementRows, ($page - 1) * self::PER_PAGE, self::PER_PAGE),
'statementTotal' => count($statementRows),
'returnRows' => array_slice($returnRows, ($page - 1) * self::PER_PAGE, self::PER_PAGE),
'returnTotal' => count($returnRows),
'rowsPage' => $page,
'perPage' => self::PER_PAGE,
'statementTemplateUrl' => route('business.supplier-reconciliation.template'),
'uploadUrl' => route('business.supplier-reconciliation.statement.upload'),
];
}
/** @return array<string,mixed> */
private function detail(array $mine, string $supplier, int $companyId): array
{
$mineRows = array_values(array_filter(
$mine,
static fn (array $row): bool => trim((string) ($row['supplier'] ?? '')) === $supplier,
));
$theirRows = $this->statements->rows($supplier, $companyId);
$compared = $this->matcher->compare($mineRows, $theirRows);
$compared['mineRows'] = count($mineRows);
$compared['theirsRows'] = count($theirRows);
return $compared;
}
/** @return list<array<string,mixed>> 该厂家的退货明细(我方数据,只读展示给厂家) */
private function returnRows(string $supplier, int $companyId): array
{
$rows = $this->store->listRows(
PayablesFinalActions::DATASET_PREFIX . 'supplier:return-reconciliation',
$companyId,
);
return array_values(array_filter(
$rows,
static fn (array $row): bool => trim((string) ($row['supplier'] ?? '')) === $supplier,
));
}
private function companyId(): int
{
return (int) (company_id() ?: 1);
}
}