CaiWuXiTong/app/Business/CourierPayableSummaryMap.php
qiuxiang ddbf32a0ee 快递运费:其他附加费汇总后面新增「退件费」列(按交易单号取快递应付的退件费结算金额)
退件费快照的表头跟另外两个不一样:单号列叫「交易订单」、金额列叫「结算金额」,所以 CourierPayableSummaryMap 的列定位改成按候选关键字找(先全等、再包含),三个汇总页共用一套逻辑。
2026-10-08 15:40:27 +08:00

140 lines
4.9 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 App\Business;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Throwable;
/**
* 快递应付的两个汇总页 → 「交易单号 ⇒ 金额类取值」映射。
*
* 这些数据都是财务在「应付款项 › 快递应付 › 运费差 / 其他附加费汇总 / 退件费」页面上传的汇总快照
* (JSON 文件,不是数据库表),快递运费页面按交易单号把对应金额带出来展示。
* 快照重新导入后文件时间会变,缓存键跟着变,自动失效。
*/
class CourierPayableSummaryMap
{
/** 运费差:差异(结算运费-提供成本运费)。 */
public const PAGE_FREIGHT_DIFFERENCE = 'freight-difference';
/** 其他附加费汇总:金额。 */
public const PAGE_OTHER_SURCHARGE = 'other-surcharge-summary';
/** 退件费:结算金额(单号列叫「交易订单」)。 */
public const PAGE_RETURN_FEE = 'return-fee';
/**
* 汇总页 → [单号列的关键字候选, 取值列的关键字候选]。
*
* 各页表头写法不统一(「交易单号」/「交易订单」、「金额」/「结算金额」),
* 所以按候选顺序找:先全等匹配,再退化成包含匹配。
*/
private const PAGES = [
self::PAGE_FREIGHT_DIFFERENCE => [['交易单号', '交易订单'], ['差异', '金额']],
self::PAGE_OTHER_SURCHARGE => [['交易单号', '交易订单'], ['金额']],
self::PAGE_RETURN_FEE => [['交易订单', '交易单号'], ['结算金额', '金额']],
];
private const CACHE_MINUTES = 10;
public function __construct(private readonly CourierPayableSummaryStore $store) {}
/**
* @param string $page self::PAGE_* 之一
* @return array<string,string> 交易单号 => 金额(保留源文件里的原值字符串)
*/
public function map(int $companyId, string $page): array
{
if (! isset(self::PAGES[$page])) {
return [];
}
$disk = Storage::disk('uploads');
$path = $this->store->path($companyId, $page);
if (! $disk->exists($path)) {
return [];
}
// 文件时间戳进缓存键:财务重新导入运费差后不用等缓存过期。
$key = sprintf('courier-payable-summary-map:v1:%s:%d:%d', $page, $companyId, (int) $disk->lastModified($path));
return Cache::remember($key, now()->addMinutes(self::CACHE_MINUTES), function () use ($companyId, $page): array {
try {
return $this->build($companyId, $page);
} catch (Throwable $exception) {
report($exception);
return [];
}
});
}
/** @return array<string,string> */
private function build(int $companyId, string $page): array
{
$snapshot = $this->store->snapshot($companyId, $page);
if ($snapshot === null) {
return [];
}
[$orderHeaders, $valueHeaders] = self::PAGES[$page];
$headers = array_map(static fn ($value): string => trim((string) $value), (array) $snapshot['headers']);
$orderColumn = $this->columnIndex($headers, $orderHeaders);
$valueColumn = $this->columnIndex($headers, $valueHeaders);
if ($orderColumn === null || $valueColumn === null) {
return [];
}
$map = [];
foreach ((array) $snapshot['rows'] as $row) {
$orderNos = trim((string) ($row[$orderColumn] ?? ''));
if ($orderNos === '') {
continue;
}
$value = trim((string) ($row[$valueColumn] ?? ''));
if ($value === '') {
continue;
}
// 源表一个格子可能放多个交易单号(逗号分隔),逐个挂到同一个金额上。
foreach (explode(',', $orderNos) as $orderNo) {
$orderNo = trim($orderNo);
if ($orderNo !== '' && ! isset($map[$orderNo])) {
$map[$orderNo] = $value;
}
}
}
return $map;
}
/**
* 按表头名定位:候选顺序优先,每个候选先找完全相等的表头,再退化成包含匹配
* (例如「差异(结算运费-提供成本运费)」只能靠包含匹配)。
*
* @param list<string> $needles
*/
private function columnIndex(array $headers, array $needles): ?int
{
foreach ($needles as $needle) {
foreach ($headers as $index => $header) {
if ($header === $needle) {
return (int) $index;
}
}
}
foreach ($needles as $needle) {
foreach ($headers as $index => $header) {
if ($header !== '' && mb_strpos($header, $needle) !== false) {
return (int) $index;
}
}
}
return null;
}
}