97 lines
4.6 KiB
PHP
97 lines
4.6 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Services;
|
||
|
||
use Illuminate\Validation\ValidationException;
|
||
|
||
class AccessoryReshipRateCalculator
|
||
{
|
||
/** 界面及文本导入均以百分数为单位;数据库保存万分比整数。 */
|
||
public static function basisPoints(string $percent): int
|
||
{
|
||
$percent = trim(str_replace(['%', '%'], '', $percent));
|
||
if (!preg_match('/^\d{1,3}(?:\.\d{1,2})?$/', $percent)) {
|
||
throw ValidationException::withMessages(['rate_percent' => '扣款比例须为0至100的数字,最多两位小数。']);
|
||
}
|
||
[$whole, $decimal] = array_pad(explode('.', $percent, 2), 2, '');
|
||
$points = (int) $whole * 100 + (int) str_pad($decimal, 2, '0');
|
||
if ($points > 10000) {
|
||
throw ValidationException::withMessages(['rate_percent' => '扣款比例不能超过100%。']);
|
||
}
|
||
return $points;
|
||
}
|
||
|
||
public static function amount(int $freightCents, int $basisPoints): int
|
||
{
|
||
return intdiv($freightCents * $basisPoints + 5000, 10000);
|
||
}
|
||
|
||
/** 名称精确匹配;不默认比例,不对多候选累计运费。 */
|
||
public function apply(array $row, array $configs, ?string $selectedCourier = null): array
|
||
{
|
||
// 同名快递只保留一个首发包裹;不同名称必须由业务选择,不能按比例猜归属。
|
||
$candidates = [];
|
||
foreach ($row['matches'] as $match) {
|
||
$name = trim((string) $match['courier']);
|
||
$candidates[$name] ??= array_replace($match, ['courier' => $name]);
|
||
}
|
||
$row['courier_candidates'] = array_values($candidates);
|
||
$selected = $selectedCourier !== null ? ($candidates[$selectedCourier] ?? null) : null;
|
||
if (count($candidates) === 1) $selected = reset($candidates);
|
||
$row['selected_courier'] = $selected['courier'] ?? null;
|
||
$row['matches'] = $selected === null ? array_values($candidates) : [$selected];
|
||
$row['deduction_cents'] = $row['status'] === 'unmatched' ? 0 : null;
|
||
$row['deduction_rate_basis_points'] = null;
|
||
$row['deduction_state'] = 'unmatched';
|
||
$row['deduction_note'] = '三个月内未匹配首发快递单号';
|
||
$reshipCourier = trim((string) (($row['express_name1'] ?? '') ?: ($row['express_name2'] ?? '')));
|
||
if (in_array($reshipCourier, ['外采快递', '线下快递'], true)) {
|
||
$row['deduction_cents'] = 0;
|
||
$row['deduction_state'] = 'excluded_courier';
|
||
$row['deduction_note'] = '补发快递为' . $reshipCourier . ',不参与扣款';
|
||
return $row;
|
||
}
|
||
if ($row['status'] === 'unmatched') {
|
||
return $row;
|
||
}
|
||
if ($selected === null) {
|
||
$row['deduction_state'] = 'selection_required';
|
||
$row['deduction_note'] = '请选择需要扣款的首发快递,补发运费仅计一次';
|
||
return $row;
|
||
}
|
||
$rates = [];
|
||
$missing = false;
|
||
foreach ($row['matches'] as &$match) {
|
||
$config = $configs[trim((string) $match['courier'])] ?? null;
|
||
$match['rate_basis_points'] = $config['rate_basis_points'] ?? null;
|
||
$match['courier_code'] = $config['courier_code'] ?? null;
|
||
$match['deduction_cents'] = $config !== null && $row['freight_cents'] !== null
|
||
? self::amount($row['freight_cents'], (int) $config['rate_basis_points']) : null;
|
||
if ($config === null) {
|
||
$missing = true;
|
||
} else {
|
||
$rates[] = (int) $config['rate_basis_points'];
|
||
}
|
||
}
|
||
unset($match);
|
||
if ($missing) {
|
||
$row['deduction_state'] = 'missing_config';
|
||
$row['deduction_note'] = '首发快递未配置扣款比例,暂不计入扣款合计';
|
||
} elseif ($row['freight_cents'] === null) {
|
||
$row['deduction_state'] = 'invalid_amount';
|
||
$row['deduction_note'] = '补发运费缺失或异常,待核实';
|
||
} else {
|
||
$rate = $rates[0];
|
||
$row['deduction_rate_basis_points'] = $rate;
|
||
$row['deduction_cents'] = self::amount($row['freight_cents'], $rate);
|
||
$row['deduction_state'] = $rate === 0 ? 'zero_rate' : 'ready';
|
||
$row['deduction_note'] = $rate === 0 ? '扣款比例0%,不扣款' : '按所选首发快递配置比例核算,仅计一次';
|
||
if ($rate > 0 && $row['deduction_cents'] === 0) {
|
||
$row['deduction_state'] = 'zero_amount';
|
||
$row['deduction_note'] = '扣款金额为0,不扣款';
|
||
}
|
||
}
|
||
return $row;
|
||
}
|
||
}
|