[ 'text' => '安心验鉴定', 'sla_hours' => 48, 'default_package_name' => '安心验基础套餐', 'default_package_code' => 'anxinyan_basic', 'default_price' => 99.00, ], 'zhongjian' => [ 'text' => '中检鉴定', 'sla_hours' => 72, 'default_package_name' => '中检基础套餐', 'default_package_code' => 'zhongjian_basic', 'default_price' => 199.00, ], ]; public function adminIndex(): array { return [ 'providers' => $this->providerOptions(), 'list' => $this->list(false), ]; } public function serviceOptions(): array { $packages = $this->list(true); $grouped = []; foreach ($packages as $package) { $grouped[$package['service_provider']][] = $package; } return array_map(function (string $serviceProvider) use ($grouped) { $provider = self::PROVIDERS[$serviceProvider]; $items = $grouped[$serviceProvider] ?? []; $defaultPackage = $this->defaultFromList($items); return [ 'service_provider' => $serviceProvider, 'service_provider_text' => $provider['text'], 'price' => $defaultPackage ? (float)$defaultPackage['price'] : (float)$provider['default_price'], 'sla_hours' => (int)$provider['sla_hours'], 'default_package_id' => $defaultPackage ? (int)$defaultPackage['id'] : 0, 'default_package' => $defaultPackage, 'packages' => $items, ]; }, array_keys(self::PROVIDERS)); } public function list(bool $enabledOnly = false): array { $query = Db::name(self::TABLE); if ($enabledOnly) { $query->where('is_enabled', 1); } $rows = $query ->order('service_provider', 'asc') ->order('sort_order', 'asc') ->order('id', 'asc') ->select() ->toArray(); return array_map(fn (array $row) => $this->formatPackage($row), $rows); } public function enabledPackages(string $serviceProvider): array { $serviceProvider = $this->normalizeServiceProvider($serviceProvider); $rows = Db::name(self::TABLE) ->where('service_provider', $serviceProvider) ->where('is_enabled', 1) ->order('sort_order', 'asc') ->order('id', 'asc') ->select() ->toArray(); return array_map(fn (array $row) => $this->formatPackage($row), $rows); } public function save(array $payload, int $id = 0): int { $serviceProvider = $this->normalizeServiceProvider((string)($payload['service_provider'] ?? '')); $packageName = $this->limitText(trim((string)($payload['package_name'] ?? '')), 128); $packageCode = $this->normalizePackageCode((string)($payload['package_code'] ?? '')); $price = $this->normalizePrice($payload['price'] ?? null); $description = $this->limitText(trim((string)($payload['description'] ?? '')), 500); $isEnabled = !empty($payload['is_enabled']) ? 1 : 0; $isDefault = !empty($payload['is_default']) ? 1 : 0; $sortOrder = (int)($payload['sort_order'] ?? 0); if ($packageName === '') { throw new \RuntimeException('套餐名称不能为空'); } if ($packageCode === '') { throw new \RuntimeException('套餐编码不能为空,只能使用字母、数字、下划线或短横线'); } if ($isDefault && !$isEnabled) { throw new \RuntimeException('默认套餐必须保持启用'); } $exists = null; if ($id > 0) { $exists = Db::name(self::TABLE)->where('id', $id)->find(); if (!$exists) { throw new \RuntimeException('价格套餐不存在'); } if ((string)$exists['service_provider'] !== $serviceProvider) { throw new \RuntimeException('已创建套餐不支持切换服务方'); } } $duplicate = Db::name(self::TABLE) ->where('service_provider', $serviceProvider) ->where('package_code', $packageCode) ->when($id > 0, fn ($query) => $query->where('id', '<>', $id)) ->find(); if ($duplicate) { throw new \RuntimeException('同一服务方下套餐编码不能重复'); } if (!$isEnabled && $exists && (int)$exists['is_enabled'] === 1) { $enabledCount = (int)Db::name(self::TABLE) ->where('service_provider', $serviceProvider) ->where('is_enabled', 1) ->where('id', '<>', $id) ->count(); if ($enabledCount === 0) { throw new \RuntimeException('每个服务方至少需要保留一个启用套餐'); } } $now = date('Y-m-d H:i:s'); $data = [ 'service_provider' => $serviceProvider, 'package_name' => $packageName, 'package_code' => $packageCode, 'price' => $price, 'description' => $description, 'is_enabled' => $isEnabled, 'is_default' => $isDefault, 'sort_order' => $sortOrder, 'updated_at' => $now, ]; Db::startTrans(); try { if ($id > 0) { Db::name(self::TABLE)->where('id', $id)->update($data); $packageId = $id; } else { $data['created_at'] = $now; $packageId = (int)Db::name(self::TABLE)->insertGetId($data); } if ($isDefault) { $this->clearOtherDefaults($serviceProvider, $packageId); } $this->normalizeProviderDefault($serviceProvider); Db::commit(); } catch (\Throwable $e) { Db::rollback(); throw $e; } return $packageId; } public function setEnabled(int $id, bool $enabled): void { $package = Db::name(self::TABLE)->where('id', $id)->find(); if (!$package) { throw new \RuntimeException('价格套餐不存在'); } $serviceProvider = (string)$package['service_provider']; if (!$enabled) { $enabledCount = (int)Db::name(self::TABLE) ->where('service_provider', $serviceProvider) ->where('is_enabled', 1) ->where('id', '<>', $id) ->count(); if ($enabledCount === 0) { throw new \RuntimeException('每个服务方至少需要保留一个启用套餐'); } } Db::startTrans(); try { Db::name(self::TABLE)->where('id', $id)->update([ 'is_enabled' => $enabled ? 1 : 0, 'is_default' => $enabled ? (int)$package['is_default'] : 0, 'updated_at' => date('Y-m-d H:i:s'), ]); $this->normalizeProviderDefault($serviceProvider); Db::commit(); } catch (\Throwable $e) { Db::rollback(); throw $e; } } public function setDefault(int $id): void { $package = Db::name(self::TABLE)->where('id', $id)->find(); if (!$package) { throw new \RuntimeException('价格套餐不存在'); } if ((int)$package['is_enabled'] !== 1) { throw new \RuntimeException('停用套餐不能设为默认套餐'); } Db::startTrans(); try { Db::name(self::TABLE) ->where('service_provider', (string)$package['service_provider']) ->update([ 'is_default' => 0, 'updated_at' => date('Y-m-d H:i:s'), ]); Db::name(self::TABLE)->where('id', $id)->update([ 'is_default' => 1, 'updated_at' => date('Y-m-d H:i:s'), ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); throw $e; } } public function resolveForOrder(string $serviceProvider, int $packageId = 0, string $packageCode = ''): array { $serviceProvider = $this->normalizeServiceProvider($serviceProvider); $packageCode = trim($packageCode); $query = Db::name(self::TABLE)->where('service_provider', $serviceProvider); if ($packageId > 0) { $query->where('id', $packageId); } elseif ($packageCode !== '') { $query->where('package_code', $packageCode); } else { $query->where('is_enabled', 1) ->order('is_default', 'desc') ->order('sort_order', 'asc') ->order('id', 'asc'); } $package = $query->find(); if (!$package) { throw new \RuntimeException('当前服务暂无可用价格套餐'); } if ((int)$package['is_enabled'] !== 1) { throw new \RuntimeException('所选价格套餐已停用,请重新选择'); } return $this->formatPackage($package); } public function snapshotForOrder(string $serviceProvider, int $packageId = 0, string $packageCode = ''): array { $package = $this->resolveForOrder($serviceProvider, $packageId, $packageCode); return [ 'price_package_id' => (int)$package['id'], 'price_package_name' => (string)$package['package_name'], 'price_package_code' => (string)$package['package_code'], 'price_package_price' => (float)$package['price'], 'pay_amount' => (float)$package['price'], 'sla_hours' => (int)$package['sla_hours'], 'service_provider_text' => (string)$package['service_provider_text'], ]; } public function providerOptions(): array { return array_map(fn (string $serviceProvider) => [ 'service_provider' => $serviceProvider, 'service_provider_text' => self::PROVIDERS[$serviceProvider]['text'], 'sla_hours' => (int)self::PROVIDERS[$serviceProvider]['sla_hours'], ], array_keys(self::PROVIDERS)); } public function serviceProviderText(string $serviceProvider): string { $serviceProvider = isset(self::PROVIDERS[$serviceProvider]) ? $serviceProvider : 'anxinyan'; return self::PROVIDERS[$serviceProvider]['text']; } public function defaultSeeds(): array { return array_map(function (string $serviceProvider) { $provider = self::PROVIDERS[$serviceProvider]; return [ 'service_provider' => $serviceProvider, 'package_name' => $provider['default_package_name'], 'package_code' => $provider['default_package_code'], 'price' => (float)$provider['default_price'], 'description' => '默认服务价格套餐', 'is_enabled' => 1, 'is_default' => 1, 'sort_order' => 1, ]; }, array_keys(self::PROVIDERS)); } private function formatPackage(array $row): array { $serviceProvider = isset(self::PROVIDERS[(string)($row['service_provider'] ?? '')]) ? (string)$row['service_provider'] : 'anxinyan'; $provider = self::PROVIDERS[$serviceProvider]; return [ 'id' => (int)($row['id'] ?? 0), 'service_provider' => $serviceProvider, 'service_provider_text' => $provider['text'], 'package_name' => (string)($row['package_name'] ?? ''), 'package_code' => (string)($row['package_code'] ?? ''), 'price' => (float)($row['price'] ?? 0), 'description' => (string)($row['description'] ?? ''), 'is_enabled' => (bool)($row['is_enabled'] ?? false), 'is_default' => (bool)($row['is_default'] ?? false), 'sort_order' => (int)($row['sort_order'] ?? 0), 'sla_hours' => (int)$provider['sla_hours'], 'created_at' => (string)($row['created_at'] ?? ''), 'updated_at' => (string)($row['updated_at'] ?? ''), ]; } private function defaultFromList(array $packages): ?array { foreach ($packages as $package) { if (!empty($package['is_default'])) { return $package; } } return $packages[0] ?? null; } private function normalizeServiceProvider(string $serviceProvider): string { $serviceProvider = trim($serviceProvider); if (!isset(self::PROVIDERS[$serviceProvider])) { throw new \RuntimeException('服务类型不正确'); } return $serviceProvider; } private function normalizePackageCode(string $packageCode): string { $packageCode = strtolower(trim($packageCode)); if (preg_match('/^[a-z0-9_-]{2,64}$/', $packageCode) !== 1) { return ''; } return $packageCode; } private function normalizePrice(mixed $value): float { $value = trim((string)$value); if ($value === '' || preg_match('/^\d+(\.\d{1,2})?$/', $value) !== 1) { throw new \RuntimeException('套餐价格需填写大于 0 的金额,最多保留 2 位小数'); } $price = round((float)$value, 2); if ($price <= 0 || $price > 999999.99) { throw new \RuntimeException('套餐价格需大于 0 且不超过 999999.99'); } return $price; } private function limitText(string $value, int $maxLength): string { if (function_exists('mb_substr')) { return mb_substr($value, 0, $maxLength, 'UTF-8'); } return substr($value, 0, $maxLength); } private function clearOtherDefaults(string $serviceProvider, int $packageId): void { Db::name(self::TABLE) ->where('service_provider', $serviceProvider) ->where('id', '<>', $packageId) ->update([ 'is_default' => 0, 'updated_at' => date('Y-m-d H:i:s'), ]); } private function normalizeProviderDefault(string $serviceProvider): void { $candidate = Db::name(self::TABLE) ->where('service_provider', $serviceProvider) ->where('is_enabled', 1) ->order('is_default', 'desc') ->order('sort_order', 'asc') ->order('id', 'asc') ->find(); if (!$candidate) { Db::name(self::TABLE) ->where('service_provider', $serviceProvider) ->where('is_default', 1) ->update([ 'is_default' => 0, 'updated_at' => date('Y-m-d H:i:s'), ]); return; } $now = date('Y-m-d H:i:s'); $candidateId = (int)$candidate['id']; Db::name(self::TABLE) ->where('service_provider', $serviceProvider) ->where('id', '<>', $candidateId) ->where('is_default', 1) ->update([ 'is_default' => 0, 'updated_at' => $now, ]); if ((int)$candidate['is_default'] !== 1) { Db::name(self::TABLE)->where('id', $candidateId)->update([ 'is_default' => 1, 'updated_at' => $now, ]); } } }