| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- <?php
- namespace app\common\service;
- use think\facade\Db;
- use think\facade\Log;
- class UserProductService
- {
- /**
- * 创建用户产品使用记录(支付成功后调用)
- */
- public function createUserProductRecord($userId, $orderItem, $package)
- {
- Db::startTrans();
- try {
- // 1. 首先检查是否已存在相同的记录
- $existing = Db::table('ba_user_product_usage')
- ->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;
- }
- }
- }
|