Files

153 lines
5.1 KiB
PHP
Raw Permalink Normal View History

<?php
namespace App\Service\business;
/**
* 价格可见性与倍率
*
* 报价单的 routine 列是一串用 @ 分隔的价格,每段要么是纯数字,要么是「名称:价格」,
* 乘倍率时只能乘价格段,把整段当数字乘会把名称吃掉。
* show_price 为假时整个价格数组换成 ['****'],并回 is_show_price=false 给前端。
*
* 这段逻辑原先只存在于 lgp-wx-api ProductService后台完全没有
* 于是后台看到的是原价、小程序看到的是倍率价,对账时无从复现。收在这里两边共用。
*/
class PriceService
{
private static mixed $_instance;
public const MASK = '****';
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 拆分 routine
*/
public function split(?string $routine): array
{
$routine = trim((string) $routine);
if ($routine === '') {
return [];
}
return array_values(array_filter(array_map('trim', explode('@', $routine)), fn ($v) => $v !== ''));
}
/**
* 按用户可见性与倍率格式化 routine
*
* @param bool $showPrice 是否可见价格
* @param int|float|string $multiplier 价格倍率
* @return array<int, string>
*/
public function formatRoutine(?string $routine, bool $showPrice, mixed $multiplier = 1): array
{
if (!$showPrice) {
return [self::MASK];
}
$items = $this->split($routine);
foreach ($items as &$item) {
$item = $this->formatPrice($item, $multiplier);
}
unset($item);
return $items;
}
/**
* 单段价格乘倍率。「名称:价格」只乘价格部分,兼容半角冒号与空格
*/
public function formatPrice(string $value, mixed $multiplier = 1): string
{
$multiplier = $this->normalizeMultiplier($multiplier);
try {
if (preg_match('/^[0-9.]+$/', $value)) {
return bcmul($value, $multiplier, 0);
}
$normalized = str_replace([':', ' '], ['', ''], $value);
$parts = explode('', $normalized);
if (array_key_exists(1, $parts) && preg_match('/^[0-9.]+$/', $parts[1])) {
$parts[1] = bcmul($parts[1], $multiplier, 0);
}
return implode('', $parts);
} catch (\Throwable) {
return $value;
}
}
/**
* 给一组报价单行套上价格规则,返回值里 routine 变成数组
*
* @param array $rows price_sheet
* @return array{rows: array, is_show_price: bool}
*/
public function applyToRows(array $rows, bool $showPrice, mixed $multiplier = 1): array
{
foreach ($rows as &$row) {
$row['routine'] = $this->formatRoutine($row['routine'] ?? '', $showPrice, $multiplier);
}
unset($row);
return ['rows' => $rows, 'is_show_price' => $showPrice];
}
/**
* 取某个材质的单价,返回「分」
*
* 下单要的是一个确定的数字,而 routine 是给人看的字符串(可能是 "1200"
* 也可能是 "布艺1200@皮艺1800")。这里按 materialKey 找对应段,
* 找不到就退回第一个能解析出数字的段;一个都没有返回 0,由调用方决定报错还是放过。
*
* 金额一律整数分:老库价格是整数元,乘完倍率再 ×100不引入浮点。
*/
public function resolveUnitPrice(?string $routine, string $materialKey = '', mixed $multiplier = 1): int
{
$items = $this->split($routine);
if (empty($items)) {
return 0;
}
$materialKey = trim($materialKey);
$fallback = 0;
foreach ($items as $item) {
$normalized = str_replace([':', ' '], ['', ''], $item);
$parts = explode('', $normalized);
$name = count($parts) > 1 ? $parts[0] : '';
$value = count($parts) > 1 ? $parts[1] : $parts[0];
if (!preg_match('/^[0-9.]+$/', $value)) {
continue;
}
$yuan = (int) bcmul($value, $this->normalizeMultiplier($multiplier), 0);
if ($materialKey !== '' && $materialKey !== 'routine' && $name === $materialKey) {
return $yuan * 100;
}
if ($fallback === 0) {
$fallback = $yuan * 100;
}
}
return $fallback;
}
/**
* 分转元字符串,仅用于展示与导出
*/
public function centsToYuan(int $cents): string
{
return number_format($cents / 100, 2, '.', '');
}
/**
* 倍率兜底库里可能是空串、0 或负数,直接拿去 bcmul 会把价格清零
*/
private function normalizeMultiplier(mixed $multiplier): string
{
if (!is_numeric($multiplier) || (float) $multiplier <= 0) {
return '1';
}
return (string) $multiplier;
}
}