where('order_item_id', $orderItem['id']) ->where('user_id', $userId) ->lock(true) ->find(); if ($existing) { Db::commit(); Log::info('用户产品记录已存在,跳过创建', [ 'order_item_id' => $orderItem['id'], 'user_id' => $userId, 'existing_id' => $existing['id'] ]); return $existing['id']; } // 2. 创建使用记录(原有逻辑) $usageData = [ 'user_id' => $userId, 'order_id' => $orderItem['order_id'], 'order_item_id' => $orderItem['id'], 'product_id' => $orderItem['product_id'], 'package_price_id' => $orderItem['package_price_id'], 'package_type' => $package['type'], 'card_type' => $package['card_type'], 'start_time' => date('Y-m-d H:i:s'), 'status' => 1, 'create_time' => date('Y-m-d H:i:s'), 'update_time' => date('Y-m-d H:i:s') ]; // 3. 根据卡类型和购买数量计算初始值 $quantity = $orderItem['quantity'] ?? 1; switch ($package['card_type']) { case 1: // 次卡 $unitValue = $package['unit_value'] ?? 0; $usageData['remaining_times'] = $unitValue * $quantity; break; case 2: // 积分卡 $pointsValue = $package['points_value'] ?? 0; $usageData['remaining_points'] = $pointsValue * $quantity; break; case 3: // 日卡 $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+1 day')); break; case 4: // 周卡 $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+7 days')); break; case 5: // 季卡 $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+90 days')); break; case 6: // 月卡 $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+30 days')); break; case 7: // 年卡 $usageData['end_time'] = date('Y-m-d 23:59:59', strtotime('+365 days')); break; } $usageId = Db::table('ba_user_product_usage')->insertGetId($usageData); // 4. 如果是个人套餐,同时更新ba_user_product表 if ($package['type'] == 1) { $this->updateUserProductTable($userId, $orderItem, $package, $usageData); } Db::commit(); Log::info('用户产品记录创建成功', [ 'usage_id' => $usageId, 'user_id' => $userId, 'order_item_id' => $orderItem['id'] ]); return $usageId; } catch (\Throwable $e) { Db::rollback(); Log::error('创建用户产品记录失败: ' . $e->getMessage()); throw $e; } } /** * 更新用户产品表(兼容原有表) */ private function updateUserProductTable($userId, $orderItem, $package, $usageData) { try { // 检查是否已有记录 $userProduct = Db::table('ba_user_product') ->where('user_id', $userId) ->where('product_id', $orderItem['product_id']) ->where('package_price_id', $orderItem['package_price_id']) ->find(); if ($userProduct) { // 更新现有记录 $updateData = [ 'update_time' => date('Y-m-d H:i:s') ]; // 根据卡类型累加 if ($package['card_type'] == 1) { // 次卡 $updateData['remaining_times'] = Db::raw('remaining_times + ' . $usageData['remaining_times']); } elseif ($package['card_type'] == 2) { // 积分卡 $updateData['remaining_points'] = Db::raw('remaining_points + ' . $usageData['remaining_points']); } elseif ($package['card_type'] >= 3) { // 日期卡 // 日期卡取最新的结束时间 if (empty($userProduct['expire_time']) || strtotime($usageData['end_time']) > strtotime($userProduct['expire_time'])) { $updateData['expire_time'] = $usageData['end_time']; } } Db::table('ba_user_product') ->where('id', $userProduct['id']) ->update($updateData); } else { // 创建新记录 $newData = [ 'user_id' => $userId, 'product_id' => $orderItem['product_id'], 'package_price_id' => $orderItem['package_price_id'], 'remaining_points' => $usageData['remaining_points'] ?? 0, 'remaining_times' => $usageData['remaining_times'] ?? 0, 'expire_time' => $usageData['end_time'] ?? null, 'create_time' => date('Y-m-d H:i:s') ]; Db::table('ba_user_product')->insert($newData); } } catch (\Throwable $e) { Log::error('更新用户产品表失败: ' . $e->getMessage()); // 这里不抛出异常,因为这是辅助功能,主功能不受影响 } } /** * 企业套餐分配时创建用户产品记录 */ public function createCompanyProductUsage($allocationRecord, $assignedToUserId,$card_type) { try { // 从分配记录创建用户产品使用记录 $usageData = [ 'user_id' => $assignedToUserId, 'order_id' => $allocationRecord['order_id'], 'order_item_id' => $allocationRecord['order_item_id'], 'product_id' => $allocationRecord['product_id'], 'package_price_id' => $allocationRecord['package_price_id'], 'package_type' => 2, // 企业套餐 'card_type' => $card_type, 'company_id' => $allocationRecord['company_id'], 'allocation_id' => $allocationRecord['id'], 'is_allocated' => 1, 'start_time' => date('Y-m-d H:i:s'), 'status' => 1, 'create_time' => date('Y-m-d H:i:s'), 'update_time' => date('Y-m-d H:i:s') ]; // 设置有效期和剩余量 if ($card_type >= 3) { // 日期卡 $usageData['end_time'] = $this->calculateEndTime($card_type); } elseif ($card_type == 1) { // 次卡 $usageData['remaining_times'] = $allocationRecord['remaining_times']; } elseif ($card_type == 2) { // 积分卡 $usageData['remaining_points'] = $allocationRecord['remaining_points']; } return Db::table('ba_user_product_usage')->insertGetId($usageData); } catch (\Throwable $e) { Log::error('创建企业产品使用记录失败: ' . $e->getMessage()); throw $e; } } /** * 获取我的产品列表(整合个人套餐和企业套餐) */ public function getUserProducts($userId) { try { // 1. 查询个人套餐(直接从使用记录表) $personalProducts = Db::table('ba_user_product_usage') ->alias('upu') ->leftJoin('ba_product p', 'p.id = upu.product_id') ->leftJoin('ba_package_price pp', 'pp.id = upu.package_price_id') ->where('upu.user_id', $userId) ->where('upu.package_type', 1) // 个人套餐 // ->where('upu.status', 1) // 有效 ->field('upu.*, p.name as product_name, p.type as product_type, p.link, pp.spec_name, pp.type as package_type, pp.card_type,pp.image') ->select() ->toArray(); // 2. 查询已分配给用户的企业套餐 $companyProducts = Db::table('ba_user_product_usage') ->alias('upu') ->leftJoin('ba_product p', 'p.id = upu.product_id') ->leftJoin('ba_package_price pp', 'pp.id = upu.package_price_id') ->leftJoin('ba_company_package_allocation cpa', 'cpa.id = upu.allocation_id') ->where('upu.user_id', $userId) ->where('upu.package_type', 2) // 企业套餐 // ->where('upu.status', 1) // 有效 ->where('upu.is_allocated', 1) // 已分配 ->field('upu.*, p.name as product_name, p.type as product_type, p.link, pp.spec_name, pp.type as package_type, pp.card_type, cpa.assigned_by_user_id,pp.image') ->select() ->toArray(); // 3. 合并结果 $allProducts = array_merge($personalProducts, $companyProducts); // 4. 按产品ID和套餐规格ID分组,合并相同产品(只合并次卡和积分卡) $groupedProducts = []; foreach ($allProducts as $product) { $key = $product['product_id'] . '_' . $product['package_price_id']; // 日期卡不合并,每次分配单独显示 if ($product['card_type'] >= 3) { $groupedProducts[] = $this->formatProductItem($product, false); continue; } // 次卡和积分卡合并 if (!isset($groupedProducts[$key])) { $groupedProducts[$key] = $this->formatProductItem($product, true); } else { // 累加剩余次数/积分 $groupedProducts[$key]['remaining_times'] += $product['remaining_times']; $groupedProducts[$key]['remaining_points'] += $product['remaining_points']; $groupedProducts[$key]['total_quantity'] += 1; } } // 5. 处理合并后的数组 $result = []; foreach ($groupedProducts as $product) { $result[] = $product; } return $result; } catch (\Throwable $e) { Log::error('获取用户产品列表失败: ' . $e->getMessage()); return []; } } /** * 用户使用产品(根据产品ID自动匹配可用套餐,优先级:日期卡 > 次卡 > 积分卡) * @param int $userId * @param int $productId * @return bool * @throws \Exception */ public function useProductByProductId($userId, $productId) { Db::startTrans(); try { // 1. 获取该产品关联的所有套餐规格ID $packagePriceIds = Db::table('ba_product') ->alias('p') ->leftJoin('ba_package_price pp', 'pp.package_id = p.package_id') ->where('p.id', $productId) ->where('p.status', 1) ->column('pp.id'); if (empty($packagePriceIds)) { throw new \Exception('产品未配置套餐'); } $now = date('Y-m-d H:i:s'); // 2. 查询用户所有可能有效的使用记录(加锁防止并发) $usageList = Db::table('ba_user_product_usage') ->where('user_id', $userId) ->whereIn('package_price_id', $packagePriceIds) ->where('status', 1) // 有效 ->lock(true) ->select() ->toArray(); if (empty($usageList)) { throw new \Exception('没有可用的套餐'); } // 3. 批量获取所有套餐规格的 cost_value 映射(用于积分卡) $priceIds = array_column($usageList, 'package_price_id'); $costMap = Db::table('ba_package_price') ->whereIn('id', $priceIds) ->column('cost_value', 'id'); // 4. 按卡类型分类并过滤出真正可用的记录 $dateCardUsages = []; // 日期卡 (card_type >= 3) $timesCardUsages = []; // 次卡 (card_type = 1) $pointsCardUsages = []; // 积分卡 (card_type = 2) foreach ($usageList as $usage) { $cardType = $usage['card_type']; if ($cardType >= 3) { // 日期卡 if (empty($usage['end_time']) || $usage['end_time'] > $now) { $dateCardUsages[] = $usage; } } elseif ($cardType == 1) { // 次卡 if ($usage['remaining_times'] > 0) { $timesCardUsages[] = $usage; } } elseif ($cardType == 2) { // 积分卡 $cost = $costMap[$usage['package_price_id']] ?? 0; if ($cost > 0 && $usage['remaining_points'] >= $cost) { $pointsCardUsages[] = $usage; } } } // 5. 按优先级选择一条记录 $selectedUsage = null; $consumeValue = 0; // 积分卡本次消耗值 if (!empty($dateCardUsages)) { // 日期卡按到期时间升序,优先使用即将到期的 usort($dateCardUsages, function ($a, $b) { $aEnd = $a['end_time'] ?? '9999-12-31 23:59:59'; $bEnd = $b['end_time'] ?? '9999-12-31 23:59:59'; return strcmp($aEnd, $bEnd); }); $selectedUsage = $dateCardUsages[0]; } elseif (!empty($timesCardUsages)) { // 次卡按剩余次数升序,优先使用剩余少的 usort($timesCardUsages, function ($a, $b) { return $a['remaining_times'] <=> $b['remaining_times']; }); $selectedUsage = $timesCardUsages[0]; } elseif (!empty($pointsCardUsages)) { // 积分卡按剩余积分升序,优先使用剩余少的 usort($pointsCardUsages, function ($a, $b) { return $a['remaining_points'] <=> $b['remaining_points']; }); $selectedUsage = $pointsCardUsages[0]; $consumeValue = $costMap[$selectedUsage['package_price_id']]; } else { throw new \Exception('没有可用的套餐'); } // 6. 执行扣减操作 $updateData = [ 'last_use_time' => $now, 'update_time' => $now ]; $cardType = $selectedUsage['card_type']; switch ($cardType) { case 1: // 次卡 $updateData['remaining_times'] = Db::raw('remaining_times - 1'); if ($selectedUsage['remaining_times'] - 1 <= 0) { $updateData['status'] = 3; // 已用完 } break; case 2: // 积分卡 $updateData['remaining_points'] = Db::raw('remaining_points - ' . $consumeValue); if ($selectedUsage['remaining_points'] - $consumeValue <= 0) { $updateData['status'] = 3; // 已用完 } break; // 日期卡无需扣减,仅记录使用时间 } Db::table('ba_user_product_usage') ->where('id', $selectedUsage['id']) ->update($updateData); // 7. 记录使用明细日志(可选) $this->logUsage($userId, $selectedUsage['id'], $productId, $consumeValue, $selectedUsage); Db::commit(); return true; } catch (\Throwable $e) { Db::rollback(); Log::error('用户使用产品失败:' . $e->getMessage(), [ 'user_id' => $userId, 'product_id' => $productId ]); throw $e; } } /** * 记录使用明细日志 */ private function logUsage($userId, $usageId, $productId, $consumeValue, $usage) { $logData = [ 'user_id' => $userId, 'usage_id' => $usageId, 'product_id' => $productId, 'consume_type' => $usage['card_type'] == 1 ? 1 : 2, // 1=次数,2=积分 'consume_value' => $consumeValue, 'before_value' => $usage['card_type'] == 1 ? $usage['remaining_times'] : $usage['remaining_points'], 'after_value' => $usage['card_type'] == 1 ? $usage['remaining_times'] - 1 : $usage['remaining_points'] - $consumeValue, 'create_time' => date('Y-m-d H:i:s') ]; Db::table('ba_user_usage_log')->insert($logData); } /** * 格式化产品项 */ private function formatProductItem($product, $isMerged = false) { $item = [ 'product_id' => $product['product_id'], 'product_name' => $product['product_name'], 'product_type' => $product['product_type'] ?? 1, // 默认1:网页 'product_link' => $product['link'] ?? '', 'image' => $product['image'] ?? '', 'package_spec' => $product['spec_name'], 'package_type' => $product['package_type'], 'package_type_text' => $product['package_type'] == 1 ? '个人套餐' : '企业套餐', 'card_type' => $product['card_type'], 'card_type_text' => $this->getCardTypeText($product['card_type']), 'total_quantity' => $isMerged ? 1 : 1, 'effective_end_time' => $product['end_time'] ?? null, 'remaining_times' => $product['remaining_times'] ?? 0, 'remaining_points' => $product['remaining_points'] ?? 0, 'display_info' => $this->getDisplayInfo($product), 'usage_id' => $product['id'], 'start_time' => $product['start_time'] ?? null, 'last_use_time' => $product['last_use_time'] ?? null, 'status' => $product['status'] ?? 1, 'buy_time' => $product['create_time'] ]; // 企业套餐特有字段 if ($product['package_type'] == 2) { $item['is_allocated'] = $product['is_allocated'] ?? 0; $item['company_id'] = $product['company_id'] ?? null; $item['allocation_id'] = $product['allocation_id'] ?? null; // 获取企业套餐的席位信息(如果有) if ($item['company_id']) { $seatsInfo = Db::name('company_package_seats') ->where('company_id', $item['company_id']) ->where('package_price_id', $product['package_price_id']) ->find(); if ($seatsInfo) { $item['total_seats'] = $seatsInfo['total_seats']; $item['assigned_seats'] = $seatsInfo['assigned_seats']; $item['available_seats'] = $seatsInfo['available_seats']; } else { $item['total_seats'] = 0; $item['assigned_seats'] = 0; $item['available_seats'] = 0; } } else { $item['total_seats'] = null; $item['assigned_seats'] = null; $item['available_seats'] = null; } } else { // 个人套餐没有席位信息 $item['is_allocated'] = 0; $item['company_id'] = null; $item['allocation_id'] = null; $item['total_seats'] = null; $item['assigned_seats'] = null; $item['available_seats'] = null; } return $item; } /** * 获取显示信息 */ private function getDisplayInfo($product) { if ($product['card_type'] >= 3) { // 日期卡 $endTime = $product['end_time'] ?? null; return $endTime ? '到期: ' . date('Y-m-d', strtotime($endTime)) : '永久有效'; } elseif ($product['card_type'] == 1) { // 次卡 $remainingTimes = $product['remaining_times'] ?? 0; return '剩余次数: ' . $remainingTimes; } elseif ($product['card_type'] == 2) { // 积分卡 $remainingPoints = $product['remaining_points'] ?? 0; return '剩余积分: ' . $remainingPoints; } return ''; } /** * 获取卡类型文本 */ private function getCardTypeText($type) { $map = [ 1 => '次卡', 2 => '积分卡', 3 => '日卡', 4 => '周卡', 5 => '季卡', 6 => '月卡', 7 => '年卡' ]; return $map[$type] ?? '其他'; } /** * 计算到期时间 */ private function calculateEndTime($cardType) { $start = time(); switch ($cardType) { case 3: // 日卡 return date('Y-m-d 23:59:59', strtotime('+1 day', $start)); case 4: // 周卡 return date('Y-m-d 23:59:59', strtotime('+7 days', $start)); case 5: // 季卡 return date('Y-m-d 23:59:59', strtotime('+90 days', $start)); case 6: // 月卡 return date('Y-m-d 23:59:59', strtotime('+30 days', $start)); case 7: // 年卡 return date('Y-m-d 23:59:59', strtotime('+365 days', $start)); default: return null; } } }