118 lines
4.3 KiB
PHP
118 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\common\service\pharmacy;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
final class EjMedicineBootstrapItem
|
|
{
|
|
/** @param array<string,mixed> $row @return array<string,mixed> */
|
|
public static function fromRow(array $row): array
|
|
{
|
|
$sourceId = self::sourceId($row['id'] ?? null);
|
|
$name = self::text($row['name'] ?? null, '药材名称', 120);
|
|
$unit = self::text($row['unit'] ?? null, '药材单位', 24);
|
|
$status = $row['status'] ?? null;
|
|
if (!in_array($status, [0, 1, '0', '1'], true)) {
|
|
throw new InvalidArgumentException("本地药材 {$sourceId} 状态必须为 0 或 1");
|
|
}
|
|
|
|
return [
|
|
'source_medicine_id' => $sourceId,
|
|
'name' => $name,
|
|
'brand' => '',
|
|
'unit' => $unit,
|
|
'settlement_price' => self::roundPrice($row['settlement_price'] ?? null),
|
|
'retail_price' => self::roundPrice($row['retail_price'] ?? null),
|
|
'status' => (int) $status,
|
|
];
|
|
}
|
|
|
|
/** @param array<int,array<string,mixed>> $rows @return list<array<string,mixed>> */
|
|
public static function fromRows(array $rows): array
|
|
{
|
|
$items = array_map([self::class, 'fromRow'], $rows);
|
|
usort($items, static fn (array $left, array $right): int => self::compareIds(
|
|
(string) $left['source_medicine_id'],
|
|
(string) $right['source_medicine_id']
|
|
));
|
|
|
|
$previousId = null;
|
|
foreach ($items as $item) {
|
|
$sourceId = (string) $item['source_medicine_id'];
|
|
if ($previousId !== null && hash_equals($previousId, $sourceId)) {
|
|
throw new InvalidArgumentException("本地药材 source_medicine_id 重复:{$sourceId}");
|
|
}
|
|
$previousId = $sourceId;
|
|
}
|
|
return $items;
|
|
}
|
|
|
|
public static function roundPrice(mixed $value): string
|
|
{
|
|
if (!is_string($value) || preg_match('/^(0|[1-9]\d*)\.(\d{1,6})$/D', $value, $matches) !== 1) {
|
|
throw new InvalidArgumentException('药材价格必须是最多六位小数的非负十进制字符串');
|
|
}
|
|
$whole = ltrim($matches[1], '0');
|
|
$whole = $whole === '' ? '0' : $whole;
|
|
$fraction = str_pad($matches[2], 6, '0');
|
|
$fourDecimals = substr($fraction, 0, 4);
|
|
if ((int) $fraction[4] < 5) {
|
|
return $whole . '.' . $fourDecimals;
|
|
}
|
|
|
|
$digits = self::addOne($whole . $fourDecimals);
|
|
if (strlen($digits) < 5) {
|
|
$digits = str_pad($digits, 5, '0', STR_PAD_LEFT);
|
|
}
|
|
return substr($digits, 0, -4) . '.' . substr($digits, -4);
|
|
}
|
|
|
|
public static function compareIds(string $left, string $right): int
|
|
{
|
|
return strlen($left) <=> strlen($right) ?: strcmp($left, $right);
|
|
}
|
|
|
|
private static function sourceId(mixed $value): string
|
|
{
|
|
if (is_int($value)) {
|
|
$value = (string) $value;
|
|
}
|
|
if (!is_string($value) || preg_match('/^\d+$/D', $value) !== 1) {
|
|
throw new InvalidArgumentException('本地药材 id 必须是正整数');
|
|
}
|
|
$value = ltrim($value, '0');
|
|
if ($value === '') {
|
|
throw new InvalidArgumentException('本地药材 id 必须是正整数');
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
private static function text(mixed $value, string $field, int $maxLength): string
|
|
{
|
|
if (!is_string($value)) {
|
|
throw new InvalidArgumentException("{$field}必须是字符串");
|
|
}
|
|
$trimmed = preg_replace('/\A[\s\p{Z}\p{Cf}]+|[\s\p{Z}\p{Cf}]+\z/u', '', $value);
|
|
if (!is_string($trimmed) || $trimmed === '' || mb_strlen($trimmed) > $maxLength) {
|
|
throw new InvalidArgumentException("{$field}不能为空且不能超过 {$maxLength} 个字符");
|
|
}
|
|
return $trimmed;
|
|
}
|
|
|
|
private static function addOne(string $digits): string
|
|
{
|
|
$characters = str_split($digits);
|
|
for ($index = count($characters) - 1; $index >= 0; --$index) {
|
|
if ($characters[$index] !== '9') {
|
|
$characters[$index] = (string) ((int) $characters[$index] + 1);
|
|
return implode('', $characters);
|
|
}
|
|
$characters[$index] = '0';
|
|
}
|
|
return '1' . implode('', $characters);
|
|
}
|
|
}
|