Files
anxinyan/server-api/app/support/AppraisalServicePricePackageService.php

451 lines
16 KiB
PHP

<?php
namespace app\support;
use support\think\Db;
class AppraisalServicePricePackageService
{
private const TABLE = 'appraisal_service_price_packages';
private const PROVIDERS = [
'anxinyan' => [
'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,
]);
}
}
}