605 lines
23 KiB
PHP
605 lines
23 KiB
PHP
<?php
|
|
|
|
namespace Modules\EcommerceFinance\View;
|
|
|
|
use App\Http\Controllers\Business\PayablesFinalActions;
|
|
use Modules\EcommerceFinance\Services\SupplierDingTalkNotifier;
|
|
use Modules\EcommerceFinance\Services\SupplierPayableOverrideStore;
|
|
use Modules\EcommerceFinance\Services\SupplierReturnAccounting;
|
|
use Modules\EcommerceFinance\Services\SupplierReturnConfirmation;
|
|
use Modules\EcommerceFinance\Services\SupplierStatementMatcher;
|
|
use Modules\EcommerceFinance\Services\SupplierStatementStore;
|
|
|
|
/**
|
|
* 「供应商对账」页面数据:
|
|
* - 财务端数据:按供应商汇总 + 行级差异(采购单号 + 日期 + 条码匹配,四类结果带结算口径)
|
|
* - 厂家端数据:厂家上传的对账单(实时)与只读的退货明细
|
|
*/
|
|
class SupplierReconciliationPageData
|
|
{
|
|
/** 厂家上传的对账数据表:每页行数(短表 + 翻页)。 */
|
|
private const PER_PAGE = 15;
|
|
|
|
/** 退货明细行较高:每页 10 行,并在页面内固定表头滚动查看。 */
|
|
private const RETURN_PER_PAGE = 10;
|
|
|
|
/** 财务端比对明细:每页 15 条。 */
|
|
private const COMPARISON_PER_PAGE = 15;
|
|
|
|
public function __construct(
|
|
private readonly SupplierPayableOverrideStore $store,
|
|
private readonly SupplierStatementStore $statements,
|
|
private readonly SupplierStatementMatcher $matcher,
|
|
private readonly SupplierReturnConfirmation $returnConfirmations,
|
|
private readonly SupplierDingTalkNotifier $dingTalkNotifier,
|
|
private readonly SupplierReturnAccounting $returnAccounting,
|
|
) {
|
|
}
|
|
|
|
/** @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 = '';
|
|
}
|
|
|
|
$reconcilers = [];
|
|
foreach ($mine as $row) {
|
|
$name = trim((string) ($row['reconciler'] ?? ''));
|
|
if ($name !== '') {
|
|
$reconcilers[$name] = true;
|
|
}
|
|
}
|
|
$reconcilers = array_keys($reconcilers);
|
|
sort($reconcilers);
|
|
|
|
$reconciler = trim((string) request()->query('reconciler', ''));
|
|
if ($reconciler !== '') {
|
|
$mine = array_values(array_filter(
|
|
$mine,
|
|
static fn (array $row): bool => trim((string) ($row['reconciler'] ?? '')) === $reconciler,
|
|
));
|
|
}
|
|
|
|
$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;
|
|
}
|
|
foreach ($this->store->listRows(
|
|
PayablesFinalActions::DATASET_PREFIX . 'supplier:return-reconciliation',
|
|
$companyId,
|
|
) as $row) {
|
|
$name = trim((string) ($row['supplier'] ?? ''));
|
|
if ($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;
|
|
}
|
|
|
|
// 选中厂家时页面切换成「比对明细」视图(列表不再渲染,避免 100+ 家时往下翻)。
|
|
$supplierQuery = trim((string) request()->query('supplier', ''));
|
|
$selected = in_array($supplierQuery, $supplierNames, true) ? $supplierQuery : '';
|
|
$supplierNotFound = $supplierQuery !== '' && $selected === '';
|
|
$detail = null;
|
|
if ($selected !== '') {
|
|
$detail = $this->detail($mine, $selected, $companyId);
|
|
$totalRows = count($detail['rows']);
|
|
$pages = max((int) ceil($totalRows / self::COMPARISON_PER_PAGE), 1);
|
|
$currentPage = min(max((int) request()->query('compare_page', 1), 1), $pages);
|
|
$detail['total_rows'] = $totalRows;
|
|
$detail['page'] = $currentPage;
|
|
$detail['pages'] = $pages;
|
|
$detail['per_page'] = self::COMPARISON_PER_PAGE;
|
|
$detail['rows'] = array_slice(
|
|
$detail['rows'],
|
|
($currentPage - 1) * self::COMPARISON_PER_PAGE,
|
|
self::COMPARISON_PER_PAGE,
|
|
);
|
|
}
|
|
|
|
return [
|
|
'view' => 'business.pages.supplier-reconciliation.finance-data',
|
|
'supplierNames' => $supplierNames,
|
|
'supplierQuery' => $supplierQuery,
|
|
'supplierNotFound' => $supplierNotFound,
|
|
'uploads' => $uploads,
|
|
'months' => $months,
|
|
'month' => $month,
|
|
'reconcilers' => $reconcilers,
|
|
'reconciler' => $reconciler,
|
|
'selectedSupplier' => $selected,
|
|
'detail' => $detail,
|
|
'financeReturnPanel' => $detail !== null
|
|
? $this->financeReturnPanel($selected, $companyId, $month, $reconciler)
|
|
: null,
|
|
'robot' => $selected !== '' ? $this->dingTalkNotifier->configuration($selected) : null,
|
|
'listUrl' => route('business.pages.show', array_filter([
|
|
'section' => 'supplier-reconciliation',
|
|
'page' => 'finance-data',
|
|
'month' => $month,
|
|
'reconciler' => $reconciler,
|
|
], static fn (mixed $value): bool => $value !== '' && $value !== null)),
|
|
'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;
|
|
}
|
|
foreach ($this->store->listRows(
|
|
PayablesFinalActions::DATASET_PREFIX . 'supplier:return-reconciliation',
|
|
$companyId,
|
|
) as $row) {
|
|
$name = trim((string) ($row['supplier'] ?? ''));
|
|
if ($name !== '') {
|
|
$suppliers[$name] = true;
|
|
}
|
|
}
|
|
$supplierNames = array_keys($suppliers);
|
|
sort($supplierNames);
|
|
|
|
$supplierQuery = trim((string) request()->query('supplier', ''));
|
|
$selected = in_array($supplierQuery, $supplierNames, true) ? $supplierQuery : '';
|
|
$supplierNotFound = $supplierQuery !== '' && $selected === '';
|
|
if ($supplierQuery === '') {
|
|
$selected = $supplierNames[0] ?? '';
|
|
}
|
|
|
|
$page = max((int) request()->query('rows_page', 1), 1);
|
|
$statementRows = $selected !== '' ? $this->statements->rows($selected, $companyId) : [];
|
|
$returnData = $selected !== ''
|
|
? $this->returnPanelData($selected, $companyId, request()->query())
|
|
: ['returnPanel' => null, 'returnWarehouses' => []];
|
|
|
|
return [
|
|
'view' => 'business.pages.supplier-reconciliation.supplier-data',
|
|
'supplierNames' => $supplierNames,
|
|
'supplierQuery' => $supplierQuery,
|
|
'supplierNotFound' => $supplierNotFound,
|
|
'selectedSupplier' => $selected,
|
|
'uploads' => $selected !== '' ? $this->statements->uploads($selected, $companyId) : [],
|
|
'statementRows' => array_slice($statementRows, ($page - 1) * self::PER_PAGE, self::PER_PAGE),
|
|
'statementTotal' => count($statementRows),
|
|
'returnPanel' => $returnData['returnPanel'],
|
|
'returnWarehouses' => $returnData['returnWarehouses'],
|
|
'rowsPage' => $page,
|
|
'perPage' => self::PER_PAGE,
|
|
'statementTemplateUrl' => route('business.supplier-reconciliation.template'),
|
|
'uploadUrl' => route('business.supplier-reconciliation.statement.upload'),
|
|
'returnConfirmUrl' => route('business.supplier-reconciliation.return.confirm'),
|
|
'returnDisputeUrl' => route('business.supplier-reconciliation.return.dispute'),
|
|
'returnConfirmAllUrl' => route('business.supplier-reconciliation.return.confirm-all'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 厂家门户只读取当前登录联系人绑定的供应商数据。
|
|
*
|
|
* @return array<string,mixed>
|
|
*/
|
|
public function portalData(string $supplier, int $companyId): array
|
|
{
|
|
$page = max((int) request()->query('rows_page', 1), 1);
|
|
$statementRows = $this->statements->rows($supplier, $companyId);
|
|
$returnData = $this->returnPanelData($supplier, $companyId, request()->query());
|
|
|
|
return [
|
|
'statementRows' => array_slice($statementRows, ($page - 1) * self::PER_PAGE, self::PER_PAGE),
|
|
'statementTotal' => count($statementRows),
|
|
'uploads' => $this->statements->uploads($supplier, $companyId),
|
|
'returnPanel' => $returnData['returnPanel'],
|
|
'returnWarehouses' => $returnData['returnWarehouses'],
|
|
'rowsPage' => $page,
|
|
'perPage' => self::PER_PAGE,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 按页面当前月份/对账人筛选重新计算财务端比对结果,供差异推送使用。
|
|
*
|
|
* @param array<string,mixed> $input
|
|
* @return array<string,mixed>
|
|
*/
|
|
public function financeDetailForFilters(string $supplier, int $companyId, array $input): array
|
|
{
|
|
$mine = $this->store->listRows(
|
|
PayablesFinalActions::DATASET_PREFIX . 'supplier:payment-reconciliation',
|
|
$companyId,
|
|
);
|
|
|
|
$month = $this->filterText($input['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,
|
|
));
|
|
}
|
|
|
|
$reconciler = $this->filterText($input['reconciler'] ?? '');
|
|
if ($reconciler !== '') {
|
|
$mine = array_values(array_filter(
|
|
$mine,
|
|
static fn (array $row): bool => trim((string) ($row['reconciler'] ?? '')) === $reconciler,
|
|
));
|
|
}
|
|
|
|
return $this->detail($mine, $supplier, $companyId);
|
|
}
|
|
|
|
/**
|
|
* 财务端比对页的退货面板:与厂家端使用同一终版数据集,并保留月份/对账人筛选。
|
|
*
|
|
* @return array<string,mixed>
|
|
*/
|
|
private function financeReturnPanel(
|
|
string $supplier,
|
|
int $companyId,
|
|
string $month,
|
|
string $reconciler,
|
|
): array {
|
|
$allRows = $this->returnRows($supplier, $companyId);
|
|
$statuses = $this->returnConfirmations->statuses($supplier, $companyId);
|
|
$rows = [];
|
|
|
|
foreach ($allRows as $row) {
|
|
$rowMonth = substr(trim((string) ($row['send_time'] ?? '')), 0, 7);
|
|
if ($rowMonth === '') {
|
|
$rowMonth = substr(trim((string) ($row['summary_month'] ?? '')), 0, 7);
|
|
}
|
|
if ($month !== '' && $rowMonth !== $month) {
|
|
continue;
|
|
}
|
|
if ($reconciler !== '' && trim((string) ($row['reconciler'] ?? '')) !== $reconciler) {
|
|
continue;
|
|
}
|
|
|
|
$row['_status'] = $statuses[(string) ($row['_action_key'] ?? '')] ?? '';
|
|
$rows[] = $row;
|
|
}
|
|
|
|
return $this->returnConfirmationPanel(
|
|
$supplier,
|
|
$rows,
|
|
$this->returnFilters([]),
|
|
count($allRows),
|
|
'finance_return_page',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string,mixed> $input
|
|
* @return array{returnPanel:array<string,mixed>,returnWarehouses:list<string>}
|
|
*/
|
|
private function returnPanelData(string $supplier, int $companyId, array $input): array
|
|
{
|
|
$allReturnRows = $this->returnRows($supplier, $companyId);
|
|
$returnWarehouses = [];
|
|
foreach ($allReturnRows as $row) {
|
|
$warehouse = $this->filterText($row['warehouse'] ?? '');
|
|
if ($warehouse !== '') {
|
|
$returnWarehouses[$warehouse] = true;
|
|
}
|
|
}
|
|
$returnWarehouses = array_keys($returnWarehouses);
|
|
sort($returnWarehouses, SORT_NATURAL | SORT_FLAG_CASE);
|
|
|
|
$filters = $this->returnFilters($input);
|
|
$returnRows = $this->filterReturnRows(
|
|
$allReturnRows,
|
|
$filters,
|
|
$this->returnConfirmations->statuses($supplier, $companyId),
|
|
);
|
|
|
|
return [
|
|
'returnPanel' => $this->returnConfirmationPanel($supplier, $returnRows, $filters, count($allReturnRows)),
|
|
'returnWarehouses' => $returnWarehouses,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 退货扣减确认面板:按筛选结果汇总并分页,保留导入顺序。
|
|
*
|
|
* @param list<array<string,mixed>> $returnRows
|
|
* @param array{keyword:string,date_from:string,date_to:string,warehouse:string,status:string} $filters
|
|
* @return array<string,mixed>
|
|
*/
|
|
private function returnConfirmationPanel(
|
|
string $supplier,
|
|
array $returnRows,
|
|
array $filters,
|
|
int $allRows,
|
|
string $pageQueryKey = 'return_page',
|
|
): array
|
|
{
|
|
$total = 0.0;
|
|
$confirmedAmount = 0.0;
|
|
$disputedAmount = 0.0;
|
|
$returnQuantity = 0.0;
|
|
$confirmedQuantity = 0.0;
|
|
$disputedQuantity = 0.0;
|
|
$disputedCount = 0;
|
|
$confirmedCount = 0;
|
|
|
|
$rows = [];
|
|
foreach ($returnRows as $row) {
|
|
$row = $this->returnAccounting->enrich($row);
|
|
$key = (string) ($row['_action_key'] ?? '');
|
|
$deduction = $row['_accounting_target'] === '不记账'
|
|
? 0.0
|
|
: (float) $row['_supplier_amount'];
|
|
$quantity = (float) $row['_supplier_qty'];
|
|
$status = (string) ($row['_status'] ?? '');
|
|
|
|
$total += $deduction;
|
|
$returnQuantity += $quantity;
|
|
if ($status === SupplierReturnConfirmation::STATUS_CONFIRMED) {
|
|
$confirmedAmount += $deduction;
|
|
$confirmedQuantity += $quantity;
|
|
$confirmedCount++;
|
|
} elseif ($status === SupplierReturnConfirmation::STATUS_DISPUTED) {
|
|
$disputedAmount += $deduction;
|
|
$disputedQuantity += $quantity;
|
|
$disputedCount++;
|
|
}
|
|
|
|
$rows[] = ['_key' => $key, 'status' => $status, 'deduction' => $deduction] + $row;
|
|
}
|
|
|
|
// 全部退货行按导入顺序分页展示(每页 10 行),不做金额排序。
|
|
$perPage = self::RETURN_PER_PAGE;
|
|
$pages = max((int) ceil(count($rows) / $perPage), 1);
|
|
$page = min(max((int) request()->query($pageQueryKey, 1), 1), $pages);
|
|
|
|
$filterQuery = $this->returnFilterQuery($filters);
|
|
|
|
return [
|
|
'supplier' => $supplier,
|
|
'rows' => array_slice($rows, ($page - 1) * $perPage, $perPage),
|
|
'page' => $page,
|
|
'pages' => $pages,
|
|
'per_page' => $perPage,
|
|
'total_rows' => count($returnRows),
|
|
'all_rows' => $allRows,
|
|
'total_amount' => round($total, 2),
|
|
'return_qty' => round($returnQuantity, 2),
|
|
'confirmed_qty' => round($confirmedQuantity, 2),
|
|
'disputed_qty' => round($disputedQuantity, 2),
|
|
'pending_qty' => round(max($returnQuantity - $confirmedQuantity - $disputedQuantity, 0), 2),
|
|
'pending_amount' => round(max($total - $confirmedAmount - $disputedAmount, 0), 2),
|
|
'confirmed_count' => $confirmedCount,
|
|
'confirmed_amount' => round($confirmedAmount, 2),
|
|
'disputed_count' => $disputedCount,
|
|
'disputed_amount' => round($disputedAmount, 2),
|
|
'accounting_summary' => $this->returnAccounting->summarize($returnRows),
|
|
'filters' => $filters,
|
|
'filter_query' => $filterQuery,
|
|
'is_filtered' => $filterQuery !== [],
|
|
];
|
|
}
|
|
|
|
/** @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>> 该厂家的退货明细(我方数据,展示给厂家确认扣减) */
|
|
public 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,
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 厂家端当前查询条件下的全部退货行(用于导出,不分页)。
|
|
*
|
|
* @param array<string,mixed> $input
|
|
* @return list<array<string,mixed>>
|
|
*/
|
|
public function returnRowsForFilters(string $supplier, int $companyId, array $input): array
|
|
{
|
|
return $this->filterReturnRows(
|
|
$this->returnRows($supplier, $companyId),
|
|
$this->returnFilters($input),
|
|
$this->returnConfirmations->statuses($supplier, $companyId),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 解析厂家退货查询条件,空值会被忽略。
|
|
*
|
|
* @param array<string,mixed> $input
|
|
* @return array{keyword:string,date_from:string,date_to:string,warehouse:string,status:string}
|
|
*/
|
|
public function returnFilters(array $input): array
|
|
{
|
|
$dateFrom = $this->normalizeFilterDate($input['return_date_from'] ?? '');
|
|
$dateTo = $this->normalizeFilterDate($input['return_date_to'] ?? '');
|
|
if ($dateFrom !== '' && $dateTo !== '' && $dateFrom > $dateTo) {
|
|
[$dateFrom, $dateTo] = [$dateTo, $dateFrom];
|
|
}
|
|
|
|
$status = $this->filterText($input['return_status'] ?? '');
|
|
if (! in_array($status, ['pending', SupplierReturnConfirmation::STATUS_CONFIRMED, SupplierReturnConfirmation::STATUS_DISPUTED], true)) {
|
|
$status = '';
|
|
}
|
|
|
|
return [
|
|
'keyword' => $this->filterText($input['return_keyword'] ?? ''),
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'warehouse' => $this->filterText($input['return_warehouse'] ?? ''),
|
|
'status' => $status,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string,mixed>> $rows
|
|
* @param array{keyword:string,date_from:string,date_to:string,warehouse:string,status:string} $filters
|
|
* @param array<string,string> $statuses
|
|
* @return list<array<string,mixed>>
|
|
*/
|
|
private function filterReturnRows(array $rows, array $filters, array $statuses): array
|
|
{
|
|
$filtered = [];
|
|
foreach ($rows as $row) {
|
|
$key = (string) ($row['_action_key'] ?? '');
|
|
$status = $statuses[$key] ?? '';
|
|
|
|
if ($filters['status'] !== '') {
|
|
$matchesStatus = $filters['status'] === 'pending'
|
|
? $status === ''
|
|
: $status === $filters['status'];
|
|
if (! $matchesStatus) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$rowDate = $this->normalizeFilterDate($row['send_time'] ?? '');
|
|
if ($filters['date_from'] !== '' && ($rowDate === '' || $rowDate < $filters['date_from'])) {
|
|
continue;
|
|
}
|
|
if ($filters['date_to'] !== '' && ($rowDate === '' || $rowDate > $filters['date_to'])) {
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
$filters['warehouse'] !== ''
|
|
&& $this->filterText($row['warehouse'] ?? '') !== $filters['warehouse']
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if ($filters['keyword'] !== '') {
|
|
$haystack = implode("\n", [
|
|
$row['send_time'] ?? '',
|
|
$row['supplier'] ?? '',
|
|
$row['barcode'] ?? '',
|
|
$row['sku_name'] ?? '',
|
|
$row['supplier_barcode'] ?? '',
|
|
$row['remark'] ?? '',
|
|
$row['title'] ?? '',
|
|
$row['wms_pick_no'] ?? '',
|
|
$row['warehouse'] ?? '',
|
|
$row['express_no'] ?? '',
|
|
]);
|
|
if (stripos($haystack, $filters['keyword']) === false) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$row['_status'] = $status;
|
|
$filtered[] = $row;
|
|
}
|
|
|
|
return $filtered;
|
|
}
|
|
|
|
/** @param array{keyword:string,date_from:string,date_to:string,warehouse:string,status:string} $filters */
|
|
private function returnFilterQuery(array $filters): array
|
|
{
|
|
return array_filter([
|
|
'return_keyword' => $filters['keyword'],
|
|
'return_date_from' => $filters['date_from'],
|
|
'return_date_to' => $filters['date_to'],
|
|
'return_warehouse' => $filters['warehouse'],
|
|
'return_status' => $filters['status'],
|
|
], static fn (string $value): bool => $value !== '');
|
|
}
|
|
|
|
private function filterText(mixed $value): string
|
|
{
|
|
return is_scalar($value) ? trim((string) $value) : '';
|
|
}
|
|
|
|
private function normalizeFilterDate(mixed $value): string
|
|
{
|
|
$raw = $this->filterText($value);
|
|
if (preg_match('/^(\d{4})[-\/.](\d{1,2})[-\/.](\d{1,2})/', $raw, $matches) !== 1) {
|
|
return '';
|
|
}
|
|
|
|
$year = (int) $matches[1];
|
|
$month = (int) $matches[2];
|
|
$day = (int) $matches[3];
|
|
|
|
return checkdate($month, $day, $year)
|
|
? sprintf('%04d-%02d-%02d', $year, $month, $day)
|
|
: '';
|
|
}
|
|
|
|
private function companyId(): int
|
|
{
|
|
return (int) (company_id() ?: 1);
|
|
}
|
|
}
|