新增供应商对账目录:财务端实时上传情况与行级差异(条码+单价匹配,四类口径),厂家端严格按模板上传(表头不一致/必填缺失/非数字/串厂一律失败);复用终版存储与流式读取。
175 lines
6.3 KiB
PHP
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);
|
|
}
|
|
}
|