|
|
@@ -0,0 +1,442 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace Longyi\RewardPoints\Services;
|
|
|
+
|
|
|
+use Longyi\RewardPoints\Models\GrowthValueCustomer;
|
|
|
+use Longyi\RewardPoints\Models\GrowthValueHistory;
|
|
|
+use Carbon\Carbon;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+use Illuminate\Support\Facades\Log;
|
|
|
+use Webkul\Customer\Models\Customer;
|
|
|
+use Webkul\Customer\Models\CustomerGroup;
|
|
|
+
|
|
|
+class GrowthValueService
|
|
|
+{
|
|
|
+ use LevelCalculationTrait;
|
|
|
+
|
|
|
+ // 缓存时间(秒)
|
|
|
+ const CACHE_TTL = 3600;
|
|
|
+
|
|
|
+ // 批量处理大小
|
|
|
+ const BATCH_SIZE = 100;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理订单完成事件
|
|
|
+ */
|
|
|
+ public function handleOrderCompleted($order): void
|
|
|
+ {
|
|
|
+ if (!$order->customer_id) {
|
|
|
+ Log::info('Order has no customer, skipping growth value calculation', ['order_id' => $order->id]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ DB::transaction(function () use ($order) {
|
|
|
+ $customerId = $order->customer_id;
|
|
|
+ $orderAmount = (float) $order->grand_total;
|
|
|
+
|
|
|
+ // 获取或创建成长值记录
|
|
|
+ $growthValueCustomer = $this->getOrCreateGrowthValueRecord($customerId);
|
|
|
+
|
|
|
+ // 标记首单完成
|
|
|
+ $isFirstOrder = $this->markFirstOrderIfNeeded($growthValueCustomer);
|
|
|
+
|
|
|
+ // 计算成长值
|
|
|
+ $earnedGrowthValue = $this->calculateGrowthValue($orderAmount);
|
|
|
+ $oldValue = $growthValueCustomer->growth_value;
|
|
|
+ $newValue = $oldValue + $earnedGrowthValue;
|
|
|
+ $oldLevel = $growthValueCustomer->growth_level;
|
|
|
+
|
|
|
+ // 更新成长值
|
|
|
+ $this->updateGrowthValue($growthValueCustomer, $newValue);
|
|
|
+
|
|
|
+ // 检查并更新等级
|
|
|
+ $levelUpdateResult = $this->checkAndUpdateLevel($customerId, $newValue, $oldLevel);
|
|
|
+
|
|
|
+ // 记录历史
|
|
|
+ $this->recordHistory([
|
|
|
+ 'customer_id' => $customerId,
|
|
|
+ 'order_id' => $order->id,
|
|
|
+ 'order_amount' => $orderAmount,
|
|
|
+ 'growth_value_earned' => $earnedGrowthValue,
|
|
|
+ 'total_growth_value' => $newValue,
|
|
|
+ 'level_before' => $levelUpdateResult['level_changed'] ? $oldLevel : null,
|
|
|
+ 'level_after' => $levelUpdateResult['new_level'] ?? null,
|
|
|
+ 'event_type' => 'order',
|
|
|
+ 'description' => "订单 #{$order->increment_id} 完成,获得 {$earnedGrowthValue} 成长值",
|
|
|
+ 'expires_at' => $growthValueCustomer->growth_value_expires_at,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $this->logGrowthValueChange('Growth value added from order', [
|
|
|
+ 'customer_id' => $customerId,
|
|
|
+ 'order_id' => $order->id,
|
|
|
+ 'earned' => $earnedGrowthValue,
|
|
|
+ 'old_value' => $oldValue,
|
|
|
+ 'new_value' => $newValue,
|
|
|
+ 'old_level' => $oldLevel,
|
|
|
+ 'new_level' => $levelUpdateResult['new_level'] ?? $oldLevel,
|
|
|
+ ]);
|
|
|
+ });
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('Error adding growth value from order', [
|
|
|
+ 'customer_id' => $order->customer_id ?? null,
|
|
|
+ 'order_id' => $order->id ?? null,
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ 'trace' => $e->getTraceAsString(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取或创建成长值记录
|
|
|
+ */
|
|
|
+ private function getOrCreateGrowthValueRecord($customerId): GrowthValueCustomer
|
|
|
+ {
|
|
|
+ $record = GrowthValueCustomer::where('customer_id', $customerId)
|
|
|
+ ->lockForUpdate()
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$record) {
|
|
|
+ $record = GrowthValueCustomer::create([
|
|
|
+ 'customer_id' => $customerId,
|
|
|
+ 'growth_value' => 0,
|
|
|
+ 'growth_level' => 'V0',
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $record;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 标记首单完成
|
|
|
+ */
|
|
|
+ private function markFirstOrderIfNeeded(GrowthValueCustomer $record): bool
|
|
|
+ {
|
|
|
+ if (is_null($record->first_order_completed_at)) {
|
|
|
+ $record->first_order_completed_at = now();
|
|
|
+ $record->save();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新成长值
|
|
|
+ */
|
|
|
+ private function updateGrowthValue(GrowthValueCustomer $record, int $newValue): void
|
|
|
+ {
|
|
|
+ $record->growth_value = $newValue;
|
|
|
+
|
|
|
+ // 设置过期时间
|
|
|
+ $validityDays = $this->getValidityDays();
|
|
|
+ if ($validityDays > 0) {
|
|
|
+ $record->growth_value_expires_at = Carbon::now()->addDays($validityDays);
|
|
|
+ }
|
|
|
+
|
|
|
+ $record->updated_at = now();
|
|
|
+ $record->save();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算订单应获得的成长值
|
|
|
+ */
|
|
|
+ protected function calculateGrowthValue(float $orderAmount): int
|
|
|
+ {
|
|
|
+ $ratio = $this->getGrowthValueRatio();
|
|
|
+ $value = floor($orderAmount * $ratio);
|
|
|
+ return max(1, $value);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取成长值计算比例
|
|
|
+ */
|
|
|
+ protected function getGrowthValueRatio(): float
|
|
|
+ {
|
|
|
+ return (float) $this->getSetting('growth_value_ratio', 1.0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取成长值有效期(天)
|
|
|
+ */
|
|
|
+ protected function getValidityDays(): int
|
|
|
+ {
|
|
|
+ return (int) $this->getSetting('growth_value_validity_days', 365);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查并更新客户等级
|
|
|
+ */
|
|
|
+ public function checkAndUpdateLevel($customerId, $currentGrowthValue, $oldLevel = null): array
|
|
|
+ {
|
|
|
+ $newLevel = $this->calculateLevel($currentGrowthValue);
|
|
|
+
|
|
|
+ if ($oldLevel === $newLevel) {
|
|
|
+ return [
|
|
|
+ 'level_changed' => false,
|
|
|
+ 'group_changed' => false,
|
|
|
+ 'old_level' => $oldLevel,
|
|
|
+ 'new_level' => $newLevel,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ $newGroupId = $this->getCustomerGroupIdByLevel($newLevel);
|
|
|
+
|
|
|
+ if (!$newGroupId) {
|
|
|
+ Log::warning('Cannot find customer group for level', [
|
|
|
+ 'level' => $newLevel,
|
|
|
+ 'customer_id' => $customerId,
|
|
|
+ ]);
|
|
|
+ return ['level_changed' => false];
|
|
|
+ }
|
|
|
+
|
|
|
+ DB::transaction(function () use ($customerId, $newLevel, $newGroupId, $oldLevel, &$result) {
|
|
|
+ // 更新成长值记录中的等级
|
|
|
+ GrowthValueCustomer::where('customer_id', $customerId)->update([
|
|
|
+ 'growth_level' => $newLevel,
|
|
|
+ 'level_updated_at' => now(),
|
|
|
+ 'updated_at' => now(),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 更新客户组
|
|
|
+ $customer = Customer::find($customerId);
|
|
|
+ $oldGroupId = $customer->customer_group_id ?? null;
|
|
|
+
|
|
|
+ if ($oldGroupId !== $newGroupId) {
|
|
|
+ Customer::where('id', $customerId)->update([
|
|
|
+ 'customer_group_id' => $newGroupId,
|
|
|
+ 'updated_at' => now(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ $result = [
|
|
|
+ 'level_changed' => true,
|
|
|
+ 'group_changed' => $oldGroupId !== $newGroupId,
|
|
|
+ 'old_level' => $oldLevel,
|
|
|
+ 'new_level' => $newLevel,
|
|
|
+ 'old_group_id' => $oldGroupId,
|
|
|
+ 'new_group_id' => $newGroupId,
|
|
|
+ ];
|
|
|
+ });
|
|
|
+
|
|
|
+ $this->logGrowthValueChange('Customer level updated', [
|
|
|
+ 'customer_id' => $customerId,
|
|
|
+ 'old_level' => $oldLevel,
|
|
|
+ 'new_level' => $newLevel,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量处理过期的成长值
|
|
|
+ */
|
|
|
+ public function checkExpiredGrowthValues(): array
|
|
|
+ {
|
|
|
+ $processed = 0;
|
|
|
+ $affected = 0;
|
|
|
+
|
|
|
+ GrowthValueCustomer::whereNotNull('growth_value_expires_at')
|
|
|
+ ->where('growth_value_expires_at', '<', now())
|
|
|
+ ->where('growth_value', '>', 0)
|
|
|
+ ->chunkById(self::BATCH_SIZE, function ($records) use (&$processed, &$affected) {
|
|
|
+ foreach ($records as $record) {
|
|
|
+ try {
|
|
|
+ $result = $this->expireSingleGrowthValue($record);
|
|
|
+ $processed++;
|
|
|
+ if ($result) {
|
|
|
+ $affected++;
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('Error processing expired growth value', [
|
|
|
+ 'customer_id' => $record->customer_id,
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ Log::info('Growth value expiration check completed', [
|
|
|
+ 'processed' => $processed,
|
|
|
+ 'affected' => $affected,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return ['processed' => $processed, 'affected' => $affected];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使单个成长值过期
|
|
|
+ */
|
|
|
+ private function expireSingleGrowthValue(GrowthValueCustomer $record): bool
|
|
|
+ {
|
|
|
+ return DB::transaction(function () use ($record) {
|
|
|
+ $oldValue = $record->growth_value;
|
|
|
+ $oldLevel = $record->growth_level;
|
|
|
+
|
|
|
+ // 清零成长值
|
|
|
+ $record->growth_value = 0;
|
|
|
+ $record->growth_value_expires_at = null;
|
|
|
+
|
|
|
+ // 重新计算等级
|
|
|
+ $levelUpdateResult = $this->checkAndUpdateLevel($record->customer_id, 0, $oldLevel);
|
|
|
+
|
|
|
+ $record->updated_at = now();
|
|
|
+ $record->save();
|
|
|
+
|
|
|
+ $this->recordHistory([
|
|
|
+ 'customer_id' => $record->customer_id,
|
|
|
+ 'growth_value_earned' => -$oldValue,
|
|
|
+ 'total_growth_value' => 0,
|
|
|
+ 'level_before' => $levelUpdateResult['level_changed'] ? $oldLevel : null,
|
|
|
+ 'level_after' => $levelUpdateResult['new_level'] ?? null,
|
|
|
+ 'event_type' => 'expiration',
|
|
|
+ 'description' => '成长值过期清零',
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取客户成长值信息
|
|
|
+ */
|
|
|
+ public function getCustomerGrowthValue($customerId): array
|
|
|
+ {
|
|
|
+ $growthValueCustomer = GrowthValueCustomer::where('customer_id', $customerId)->first();
|
|
|
+ $customer = Customer::with('group')->find($customerId);
|
|
|
+
|
|
|
+ if (!$growthValueCustomer) {
|
|
|
+ return $this->getDefaultGrowthValueInfo($customer);
|
|
|
+ }
|
|
|
+
|
|
|
+ $currentGroup = $customer->group;
|
|
|
+
|
|
|
+ // 获取下一个等级
|
|
|
+ $nextGroup = CustomerGroup::where('min_growth_value', '>', $growthValueCustomer->growth_value)
|
|
|
+ ->orderBy('min_growth_value', 'asc')
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'growth_value' => $growthValueCustomer->growth_value,
|
|
|
+ 'current_group' => $currentGroup,
|
|
|
+ 'growth_level_name' => $currentGroup->growth_level_name ?? 'V0',
|
|
|
+ 'growth_discount_rate' => $currentGroup->growth_discount_rate ?? 0,
|
|
|
+ 'growth_benefits' => $currentGroup->growth_benefits ?? [],
|
|
|
+ 'growth_level_icon' => $currentGroup->growth_level_icon ?? null,
|
|
|
+ 'next_level' => $nextGroup ? [
|
|
|
+ 'group_id' => $nextGroup->id,
|
|
|
+ 'group_name' => $nextGroup->name,
|
|
|
+ 'growth_level_name' => $nextGroup->growth_level_name,
|
|
|
+ 'min_growth_value' => $nextGroup->min_growth_value,
|
|
|
+ 'icon' => $nextGroup->growth_level_icon,
|
|
|
+ ] : null,
|
|
|
+ 'points_to_next_level' => $nextGroup
|
|
|
+ ? max(0, $nextGroup->min_growth_value - $growthValueCustomer->growth_value)
|
|
|
+ : null,
|
|
|
+ 'expires_at' => $growthValueCustomer->growth_value_expires_at,
|
|
|
+ 'first_order_completed' => !is_null($growthValueCustomer->first_order_completed_at),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取默认成长值信息
|
|
|
+ */
|
|
|
+ private function getDefaultGrowthValueInfo($customer): array
|
|
|
+ {
|
|
|
+ $defaultGroup = CustomerGroup::where('code', 'general')->first();
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'growth_value' => 0,
|
|
|
+ 'current_group' => $defaultGroup,
|
|
|
+ 'growth_level_name' => $defaultGroup->growth_level_name ?? 'V0',
|
|
|
+ 'growth_discount_rate' => $defaultGroup->growth_discount_rate ?? 0,
|
|
|
+ 'growth_benefits' => $defaultGroup->growth_benefits ?? [],
|
|
|
+ 'growth_level_icon' => $defaultGroup->growth_level_icon ?? null,
|
|
|
+ 'next_level' => null,
|
|
|
+ 'points_to_next_level' => null,
|
|
|
+ 'expires_at' => null,
|
|
|
+ 'first_order_completed' => false,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 手动调整成长值
|
|
|
+ */
|
|
|
+ public function adjustGrowthValue($customerId, $amount, $description = '管理员调整'): array
|
|
|
+ {
|
|
|
+ if ($amount === 0) {
|
|
|
+ throw new \InvalidArgumentException('调整金额不能为0');
|
|
|
+ }
|
|
|
+
|
|
|
+ return DB::transaction(function () use ($customerId, $amount, $description) {
|
|
|
+ $growthValueCustomer = GrowthValueCustomer::where('customer_id', $customerId)
|
|
|
+ ->lockForUpdate()
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ $oldValue = $growthValueCustomer ? $growthValueCustomer->growth_value : 0;
|
|
|
+ $newValue = max(0, $oldValue + $amount);
|
|
|
+ $actualChange = $newValue - $oldValue;
|
|
|
+ $oldLevel = $growthValueCustomer ? $growthValueCustomer->growth_level : null;
|
|
|
+
|
|
|
+ // 更新或创建记录
|
|
|
+ if ($growthValueCustomer) {
|
|
|
+ $growthValueCustomer->growth_value = $newValue;
|
|
|
+ $growthValueCustomer->updated_at = now();
|
|
|
+ $growthValueCustomer->save();
|
|
|
+ } else {
|
|
|
+ $growthValueCustomer = GrowthValueCustomer::create([
|
|
|
+ 'customer_id' => $customerId,
|
|
|
+ 'growth_value' => $newValue,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查并更新等级
|
|
|
+ $levelUpdateResult = $this->checkAndUpdateLevel($customerId, $newValue, $oldLevel);
|
|
|
+
|
|
|
+ // 记录历史
|
|
|
+ $history = $this->recordHistory([
|
|
|
+ 'customer_id' => $customerId,
|
|
|
+ 'growth_value_earned' => $actualChange,
|
|
|
+ 'total_growth_value' => $newValue,
|
|
|
+ 'level_before' => $levelUpdateResult['level_changed'] ? $oldLevel : null,
|
|
|
+ 'level_after' => $levelUpdateResult['new_level'] ?? null,
|
|
|
+ 'event_type' => 'admin_adjust',
|
|
|
+ 'description' => $description,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $this->logGrowthValueChange('Growth value adjusted by admin', [
|
|
|
+ 'customer_id' => $customerId,
|
|
|
+ 'old_value' => $oldValue,
|
|
|
+ 'new_value' => $newValue,
|
|
|
+ 'change' => $actualChange,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'history' => $history,
|
|
|
+ 'level_changed' => $levelUpdateResult['level_changed'],
|
|
|
+ 'old_level' => $oldLevel,
|
|
|
+ 'new_level' => $levelUpdateResult['new_level'] ?? $oldLevel,
|
|
|
+ ];
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录成长值历史
|
|
|
+ */
|
|
|
+ private function recordHistory(array $data): GrowthValueHistory
|
|
|
+ {
|
|
|
+ return GrowthValueHistory::create(array_merge([
|
|
|
+ 'order_id' => null,
|
|
|
+ 'order_amount' => 0,
|
|
|
+ 'expires_at' => null,
|
|
|
+ 'created_at' => now(),
|
|
|
+ ], $data));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录日志
|
|
|
+ */
|
|
|
+ private function logGrowthValueChange(string $message, array $context): void
|
|
|
+ {
|
|
|
+ Log::info($message, $context);
|
|
|
+ }
|
|
|
+}
|