where('deleteTime', 0)->find(); if (!$device) { throw new \Exception('设备不存在', 404); } $fromCompanyId = (int) $device->companyId; if ($fromCompanyId === $targetCompanyId) { throw new \Exception('设备已在目标项目中', 400); } $targetUser = UserModel::where([ 'companyId' => $targetCompanyId, 'isAdmin' => UserModel::ADMIN_STP, 'status' => UserModel::STATUS_ACTIVE, ])->where('deleteTime', 0)->find(); if (!$targetUser || empty($targetUser->s2_accountId)) { throw new \Exception('目标项目未绑定工作手机账号(s2_accountId)', 400); } $wechatIds = Db::name('device_wechat_login') ->where('deviceId', $deviceId) ->where('companyId', $fromCompanyId) ->column('wechatId'); $wechatIds = array_values(array_filter(array_unique($wechatIds))); $apiResult = (new ApiDeviceController())->updateaccount([ 'id' => $deviceId, 'accountId' => (int) $targetUser->s2_accountId, ], true); $decoded = is_string($apiResult) ? json_decode($apiResult, true) : null; if (is_array($decoded) && isset($decoded['code']) && (int) $decoded['code'] !== 200) { throw new \Exception($decoded['msg'] ?? '工作手机换绑失败', 500); } Db::startTrans(); try { $clearedPools = $this->clearCompanyTrafficForWechats($fromCompanyId, $wechatIds); DeviceModel::where('id', $deviceId)->update([ 'companyId' => $targetCompanyId, 'updateTime' => time(), ]); Db::name('device_wechat_login') ->where('deviceId', $deviceId) ->where('companyId', $fromCompanyId) ->update([ 'companyId' => $targetCompanyId, 'updateTime' => time(), ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); throw $e; } return [ 'deviceId' => $deviceId, 'fromCompanyId' => $fromCompanyId, 'targetCompanyId' => $targetCompanyId, 'clearedPools' => $clearedPools, 'wechatIds' => $wechatIds, ]; } /** * 批量迁移预检(§6.3):源→目标映射 + 冲突 IMEI 提示,不写库 */ public function previewBatch(array $deviceIds, int $targetCompanyId): array { $deviceIds = array_values(array_unique(array_filter(array_map('intval', $deviceIds)))); if (empty($deviceIds)) { throw new \Exception('请选择要迁移的设备', 400); } if ($targetCompanyId <= 0) { throw new \Exception('目标项目无效', 400); } $targetName = (string) Db::name('company')->where('companyId', $targetCompanyId)->value('name'); $devices = Db::name('device')->whereIn('id', $deviceIds)->where('deleteTime', 0) ->field('id, memo, imei, companyId')->select(); // 目标项目已有 IMEI(用于冲突检测) $targetImeis = Db::name('device')->where('companyId', $targetCompanyId)->where('deleteTime', 0) ->whereNotNull('imei')->where('imei', '<>', '')->column('imei'); $targetImeiSet = array_flip(array_filter($targetImeis)); $companyNameMap = []; $items = []; $conflicts = 0; $alreadyThere = 0; foreach ($devices ?: [] as $d) { $fromCid = (int) $d['companyId']; if (!isset($companyNameMap[$fromCid])) { $companyNameMap[$fromCid] = (string) Db::name('company')->where('companyId', $fromCid)->value('name'); } $imei = (string) ($d['imei'] ?? ''); $isConflict = $imei !== '' && isset($targetImeiSet[$imei]); $isSame = $fromCid === $targetCompanyId; if ($isConflict) { $conflicts++; } if ($isSame) { $alreadyThere++; } $items[] = [ 'deviceId' => (int) $d['id'], 'name' => $d['memo'] ?: ('设备#' . (int) $d['id']), 'imei' => $imei, 'fromCompanyId' => $fromCid, 'fromName' => $companyNameMap[$fromCid] ?: ('项目#' . $fromCid), 'imeiConflict' => $isConflict, 'sameProject' => $isSame, ]; } return [ 'targetCompanyId' => $targetCompanyId, 'targetName' => $targetName ?: ('项目#' . $targetCompanyId), 'total' => count($items), 'conflicts' => $conflicts, 'alreadyThere' => $alreadyThere, 'items' => $items, ]; } /** * 批量迁移(§6.3):逐台调用 transfer,逐条收集成败 */ public function migrateBatch(array $deviceIds, int $targetCompanyId): array { $deviceIds = array_values(array_unique(array_filter(array_map('intval', $deviceIds)))); if (empty($deviceIds)) { throw new \Exception('请选择要迁移的设备', 400); } if ($targetCompanyId <= 0) { throw new \Exception('目标项目无效', 400); } $ok = []; $failed = []; foreach ($deviceIds as $deviceId) { try { $this->transfer($deviceId, $targetCompanyId); $ok[] = $deviceId; } catch (\Throwable $e) { $failed[] = ['deviceId' => $deviceId, 'reason' => $e->getMessage()]; } } return [ 'targetCompanyId' => $targetCompanyId, 'requested' => count($deviceIds), 'migrated' => count($ok), 'okDeviceIds' => $ok, 'failed' => $failed, ]; } /** * 清除原项目下与设备微信号相关的流量池数据(软删) */ protected function clearCompanyTrafficForWechats(int $companyId, array $wechatIds): int { if ($companyId <= 0) { return 0; } $query = Db::name('traffic_pool_company') ->where('companyId', $companyId) ->where('isDel', 0); if (!empty($wechatIds)) { $query->where(function ($q) use ($wechatIds) { $q->whereIn('ownerWechatId', $wechatIds) ->whereOr(function ($sq) use ($wechatIds) { $sq->whereIn('identifier', $wechatIds); }); }); } else { return 0; } $poolCompanyIds = $query->column('id'); if (empty($poolCompanyIds)) { return 0; } $now = time(); Db::name('traffic_pool_company')->whereIn('id', $poolCompanyIds)->update([ 'isDel' => 1, 'updateTime' => $now, ]); Db::name('traffic_pool_tag')->whereIn('poolCompanyId', $poolCompanyIds)->update(['isDel' => 1]); Db::name('traffic_pool_group_member')->whereIn('poolCompanyId', $poolCompanyIds)->update(['isDel' => 1]); return count($poolCompanyIds); } }