$row @return array */ 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> $rows @return list> */ 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); } }