From 3c68a603af8b80166c5e194eabc3e280189d1a23 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 4 Sep 2025 10:49:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChatWindow/components/Person/index.tsx | 597 +++++++++++++++--- .../api/controller/UserController.php | 12 +- Server/application/cunkebao/config/route.php | 9 +- .../controller/KeFuLoginController.php | 78 +++ .../application/job/WorkbenchGroupPushJob.php | 127 ++-- 5 files changed, 661 insertions(+), 162 deletions(-) create mode 100644 Server/application/cunkebao/controller/KeFuLoginController.php diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx index c96eea65..3803956f 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx @@ -8,6 +8,7 @@ import { Card, Tag, message, + Modal, } from "antd"; import { PhoneOutlined, @@ -22,9 +23,12 @@ import { StarOutlined, EditOutlined, CheckOutlined, + PlusOutlined, + NotificationOutlined, } from "@ant-design/icons"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; import styles from "./Person.module.scss"; const { Sider } = Layout; @@ -47,10 +51,27 @@ const Person: React.FC = ({ contract.labels || [], ); const [allAvailableTags, setAllAvailableTags] = useState([]); + const [isAddingTag, setIsAddingTag] = useState(false); + const [newTagValue, setNewTagValue] = useState(""); + + // 判断是否为群聊 + const isGroup = 'chatroomId' in contract; + + // 群聊相关状态 + const [isEditingGroupName, setIsEditingGroupName] = useState(false); + const [groupNameValue, setGroupNameValue] = useState(contract.name || ''); + const [isEditingGroupNotice, setIsEditingGroupNotice] = useState(false); + const [groupNoticeValue, setGroupNoticeValue] = useState(contract.notice || ''); + const [isEditingSelfDisplayName, setIsEditingSelfDisplayName] = useState(false); + const [selfDisplayNameValue, setSelfDisplayNameValue] = useState(contract.selfDisplyName || ''); + const [isGroupNoticeModalVisible, setIsGroupNoticeModalVisible] = useState(false); + + // 构建联系人或群聊详细信息 const kfSelectedUser = useCkChatStore(state => state.getKfUserInfo(contract.wechatAccountId || 0), ); + const { sendCommand } = useWebSocketStore(); // 获取所有可用标签 useEffect(() => { @@ -68,22 +89,102 @@ const Person: React.FC = ({ fetchAvailableTags(); }, [kfSelectedUser, contract.labels]); - // 当contract变化时更新备注值和标签 + // 当contract变化时更新各种值 useEffect(() => { setRemarkValue(contract.conRemark || ""); setIsEditingRemark(false); setSelectedTags(contract.labels || []); - }, [contract.conRemark, contract.labels]); + + if (isGroup) { + setGroupNameValue(contract.name || ''); + setIsEditingGroupName(false); + setGroupNoticeValue(contract.notice || ''); + setIsEditingGroupNotice(false); + setSelfDisplayNameValue(contract.selfDisplyName || ''); + setIsEditingSelfDisplayName(false); + } + }, [contract.conRemark, contract.labels, contract.name, contract.notice, contract.selfDisplyName, isGroup]); // 处理备注保存 const handleSaveRemark = () => { - // 这里应该调用API保存备注到后端 - // 暂时只更新本地状态 + if (isGroup) { + // 群聊备注修改 + sendCommand("CmdModifyGroupRemark", { + wechatAccountId: contract.wechatAccountId, + chatroomId: contract.chatroomId, + newRemark: remarkValue, + }); + } else { + // 好友备注修改 + sendCommand("CmdModifyFriendRemark", { + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract.id, + newRemark: remarkValue, + }); + } + messageApi.success("备注保存成功"); setIsEditingRemark(false); // 更新contract对象中的备注(实际项目中应该通过props回调或状态管理) }; + // 处理群名称保存 + const handleSaveGroupName = () => { + sendCommand("CmdChatroomOperate", { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract.id, + chatroomOperateType: 6, + extra: `{\"chatroomName\":\"${groupNameValue}\"}`, + }); + + messageApi.success("群名称修改成功"); + setIsEditingGroupName(false); + }; + + // 点击编辑群名称按钮 + const handleEditGroupName = () => { + setGroupNameValue(contract.name || ''); + setIsEditingGroupName(true); + }; + + // 处理群公告保存 + const handleSaveGroupNotice = () => { + sendCommand("CmdChatroomOperate", { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract.id, + chatroomOperateType: 5, + extra: `{\"announce\":\"${groupNoticeValue}\"}`, + }); + + messageApi.success("群公告修改成功"); + setIsEditingGroupNotice(false); + }; + + // 点击编辑群公告按钮 + const handleEditGroupNotice = () => { + setGroupNoticeValue(contract.notice || ''); + setIsGroupNoticeModalVisible(true); + }; + + // 处理我在本群中的昵称保存 + const handleSaveSelfDisplayName = () => { + sendCommand("CmdChatroomOperate", { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract.id, + chatroomOperateType: 8, + extra: `${selfDisplayNameValue}`, + }); + + messageApi.success("群昵称修改成功"); + setIsEditingSelfDisplayName(false); + }; + + // 点击编辑群昵称按钮 + const handleEditSelfDisplayName = () => { + setSelfDisplayNameValue(contract.selfDisplyName || ''); + setIsEditingSelfDisplayName(true); + }; + // 处理取消编辑 const handleCancelEdit = () => { setRemarkValue(contract.conRemark || ""); @@ -98,20 +199,90 @@ const Person: React.FC = ({ setSelectedTags(newSelectedTags); - // 这里应该调用API保存标签到后端 + // 使用WebSocket发送修改标签命令 + if (isGroup) { + // 群聊标签修改 + sendCommand("CmdModifyGroupLabel", { + labels: newSelectedTags, + seq: +new Date(), + wechatAccountId: contract.wechatAccountId, + chatroomId: contract.chatroomId, + }); + } else { + // 好友标签修改 + sendCommand("CmdModifyFriendLabel", { + labels: newSelectedTags, + seq: +new Date(), + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract.id, + }); + } + messageApi.success( `标签"${tagName}"${selectedTags.includes(tagName) ? "已取消" : "已选中"}`, ); }; - // 模拟联系人详细信息 + // 处理新增标签 + const handleAddTag = () => { + if (!newTagValue.trim()) { + messageApi.error("请输入标签名称"); + return; + } + + if (allAvailableTags.includes(newTagValue.trim())) { + messageApi.error("标签已存在"); + return; + } + + const newTag = newTagValue.trim(); + + // 添加到可用标签列表 + setAllAvailableTags(prev => [...prev, newTag]); + + // 自动选中新添加的标签 + setSelectedTags(prev => [...prev, newTag]); + + // 使用WebSocket发送新增标签命令 + if (isGroup) { + // 群聊标签修改 + sendCommand("CmdModifyGroupLabel", { + labels: [...selectedTags, newTag], + seq: +new Date(), + wechatAccountId: contract.wechatAccountId, + chatroomId: contract.chatroomId, + }); + } else { + // 好友标签修改 + sendCommand("CmdModifyFriendLabel", { + labels: [...selectedTags, newTag], + seq: +new Date(), + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract.id || contract.wechatId, + }); + } + + messageApi.success(`标签"${newTag}"添加成功`); + setNewTagValue(""); + setIsAddingTag(false); + }; + + // 处理取消新增标签 + const handleCancelAddTag = () => { + setNewTagValue(""); + setIsAddingTag(false); + }; + + // 构建联系人或群聊详细信息 const contractInfo = { - name: contract.name, + name: contract.name || contract.nickname, nickname: contract.nickname, conRemark: remarkValue, // 使用当前编辑的备注值 alias: contract.alias, wechatId: contract.wechatId, - avatar: contract.avatar, + chatroomId: isGroup ? contract.chatroomId : undefined, + chatroomOwner: isGroup ? contract.chatroomOwner : undefined, + avatar: contract.avatar || contract.chatroomAvatar, phone: contract.phone || "-", email: contract.email || "-", department: contract.department || "-", @@ -119,9 +290,11 @@ const Person: React.FC = ({ company: contract.company || "-", region: contract.region || "-", joinDate: contract.joinDate || "-", + notice: isGroup ? contract.notice : undefined, + selfDisplyName: isGroup ? contract.selfDisplyName : undefined, status: "在线", tags: selectedTags, - bio: contract.bio || "-", + bio: contract.bio || contract.signature || "-", }; if (!showProfile) { @@ -151,14 +324,59 @@ const Person: React.FC = ({ icon={} />
- -

- {contractInfo.nickname || contractInfo.name} -

-
+ {isGroup && isEditingGroupName ? ( +
+ setGroupNameValue(e.target.value)} + placeholder="请输入群名称" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( + +
+

+ {contractInfo.nickname || contractInfo.name} +

+ {isGroup && ( +
+
+ )}
@@ -169,58 +387,245 @@ const Person: React.FC = ({ {/* 详细信息卡片 */} -
- - 微信号: - - {contractInfo.alias || contractInfo.wechatId} - -
-
- - 电话: - {contractInfo.phone} -
-
- - 地区: - {contractInfo.region} -
-
- - 备注: -
- {isEditingRemark ? ( + {isGroup ? ( + // 群聊信息 + <> +
+ + 群ID: + + {contractInfo.chatroomId} + +
+
+ + 群主: + {contractInfo.chatroomOwner} +
+
+ + 群昵称: +
+ {isEditingSelfDisplayName ? ( +
+ setSelfDisplayNameValue(e.target.value)} + placeholder="请输入群昵称" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( +
+ {contractInfo.selfDisplyName || "点击添加群昵称"} +
+ )} +
+
+ + ) : ( + // 好友信息 + <> +
+ + 微信号: + + {contractInfo.alias || contractInfo.wechatId} + +
+
+ + 电话: + {contractInfo.phone} +
+
+ + 地区: + {contractInfo.region} +
+ + )} + {!isGroup && ( +
+ + 备注: +
+ {isEditingRemark ? ( +
+ setRemarkValue(e.target.value)} + placeholder="请输入备注" + size="small" + style={{ flex: 1 }} + /> +
+ ) : ( +
+ {contractInfo.conRemark || "点击添加备注"} +
+ )} +
+
+ )} + + + {/* 标签 - 仅在非群聊时显示 */} + {!isGroup && ( + +
+ {/* 渲染所有可用标签,选中的排在前面 */} + {[...new Set([...selectedTags, ...allAvailableTags])].map( + (tag, index) => { + const isSelected = selectedTags.includes(tag); + return ( + handleTagToggle(tag)} + > + {tag} + + ); + }, + )} + + {/* 新增标签区域 */} + {isAddingTag ? (
setRemarkValue(e.target.value)} - placeholder="请输入备注" + value={newTagValue} + onChange={e => setNewTagValue(e.target.value)} + placeholder="请输入标签名称" size="small" - style={{ flex: 1 }} + style={{ width: "120px" }} + onPressEnter={handleAddTag} />
) : ( + setIsAddingTag(true)} + > + 新增标签 + + )} + + {allAvailableTags.length === 0 && !isAddingTag && ( + + 暂无可用标签 + + )} +
+
+ )} + + {/* 个人简介或群公告 */} + + {isGroup ? ( + // 群聊简介(原群公告) +
+ {isEditingGroupNotice ? (
= ({ gap: "8px", }} > - {contractInfo.conRemark || "点击添加备注"} + setGroupNoticeValue(e.target.value)} + placeholder="请输入内容" + rows={6} + style={{ width: '100%' }} + /> +
+ ) : ( +
+
+ {contractInfo.notice || "点击添加群公告"} +
)}
-
- - - {/* 标签 */} - -
- {/* 渲染所有可用标签,选中的排在前面 */} - {[...new Set([...selectedTags, ...allAvailableTags])].map( - (tag, index) => { - const isSelected = selectedTags.includes(tag); - return ( - handleTagToggle(tag)} - > - {tag} - - ); - }, - )} - {allAvailableTags.length === 0 && ( - - 暂无可用标签 - - )} -
-
- - {/* 个人简介 */} - -

{contractInfo.bio}

+ ) : ( + // 个人简介 +

{contractInfo.bio}

+ )}
{/* 操作按钮 */} @@ -294,6 +689,36 @@ const Person: React.FC = ({
+ + {/* 群公告编辑弹窗 */} + setIsGroupNoticeModalVisible(false)} + footer={[ + , + , + ]} + > + setGroupNoticeValue(e.target.value)} + placeholder="请输入内容" + rows={6} + style={{ width: '100%' }} + /> + ); }; diff --git a/Server/application/api/controller/UserController.php b/Server/application/api/controller/UserController.php index b36c9554..f0fcdf5e 100644 --- a/Server/application/api/controller/UserController.php +++ b/Server/application/api/controller/UserController.php @@ -266,7 +266,7 @@ class UserController extends BaseController * 获取验证码 * @return \think\response\Json */ - public function getVerifyCode() + public function getVerifyCode($isJson = false) { $headerData = ['client:' . self::CLIENT_TYPE]; $header = setHeader($headerData, '', 'plain'); @@ -279,17 +279,19 @@ class UserController extends BaseController if (is_array($response)) { // 如果verifyCodeImage和verifySessionId都不为null,返回它们 if (!empty($response['verifyCodeImage']) && !empty($response['verifySessionId'])) { - return successJson([ + $returnData = [ 'verifyCodeImage' => $response['verifyCodeImage'], 'verifySessionId' => $response['verifySessionId'] - ]); + ]; + return !empty($isJson) ? json_encode(['code' => 200,'data' => $returnData]) : successJson($returnData); } } // 如果不是预期的格式,返回原始数据 - return successJson($response); + return !empty($isJson) ? json_encode(['code' => 500,'data' => $response]) : errorJson('无需验证码'); } catch (\Exception $e) { - return errorJson('获取验证码失败:' . $e->getMessage()); + $msg = '获取验证码失败'. $e->getMessage(); + return !empty($isJson) ? json_encode(['code' => 400,'msg' => $msg]) : errorJson($msg); } } diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 64a14985..d9c55088 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -141,11 +141,16 @@ Route::group('v1/', function () { Route::get('friendRequestTaskStats', 'app\cunkebao\controller\StatsController@getFriendRequestTaskStats'); Route::get('userInfoStats', 'app\cunkebao\controller\StatsController@userInfoStats'); }); - - })->middleware(['jwt']); +// 客服登录 +Route::group('v1/kefu', function () { + Route::post('login', 'app\cunkebao\controller\KeFuLoginController@index'); // 获取好友列表 +}); + + + Route::group('v1/api/scenarios', function () { Route::any('', 'app\cunkebao\controller\plan\PostExternalApiV1Controller@index'); diff --git a/Server/application/cunkebao/controller/KeFuLoginController.php b/Server/application/cunkebao/controller/KeFuLoginController.php new file mode 100644 index 00000000..45190e7b --- /dev/null +++ b/Server/application/cunkebao/controller/KeFuLoginController.php @@ -0,0 +1,78 @@ +request->param('username', ''); + $password = !empty($password) ? $password : $this->request->param('password', ''); + + $verifySessionId =!empty($verifySessionId) ? $verifySessionId : $this->request->param('verifySessionId', ''); + $verifyCode = !empty($verifyCode) ? $verifyCode : $this->request->param('verifyCode', ''); + + + if (empty($username) || empty($password)) { + return ResponseHelper::error('请输入账号密码'); + } + + + //登录参数 + $params = [ + 'grant_type' => 'password', + 'username' => $username, + 'password' => $password + ]; + + if (!empty($verifySessionId) && !empty($verifyCode)){ + $params[] = 'verifysessionid:' . $verifySessionId; + $params[] = 'verifycode:' . $verifyCode; + } + + + //获取验证码 + // $UserController = new UserController(); + // $verifyCode = $UserController->getVerifyCode(true); + // $verifyCode = json_decode($verifyCode, true); + // if ($verifyCode['code'] != 200) { + // exit_data($verifyCode); + // } + + try { + // 调用登录接口获取token + $headerData = ['client:kefu-client']; + $header = setHeader($headerData, '', 'plain'); + $result = requestCurl('https://s2.siyuguanli.com:9991/token', $params, 'POST', $header); + $token = handleApiResponse($result); + $userData['kefuData']['token'] = $token; + if (isset($token['access_token']) && !empty($token['access_token'])) { + $headerData = ['client:kefu-client']; + $header = setHeader($headerData, $token['access_token']); + $result = requestCurl('https://s2.siyuguanli.com:9991/api/account/self', [], 'GET', $header, 'json'); + $self = handleApiResponse($result); + $userData['kefuData']['self'] = $self; + } + + return ResponseHelper::success($userData, '登录成功'); + } catch (Exception $e) { + return ResponseHelper::error($e->getMessage(), $e->getCode()); + } + } +} \ No newline at end of file diff --git a/Server/application/job/WorkbenchGroupPushJob.php b/Server/application/job/WorkbenchGroupPushJob.php index 4cffc80d..1b080950 100644 --- a/Server/application/job/WorkbenchGroupPushJob.php +++ b/Server/application/job/WorkbenchGroupPushJob.php @@ -58,7 +58,7 @@ class WorkbenchGroupPushJob { try { // 获取所有工作台 - $workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0])->order('id desc')->select(); + $workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0,'id' => 178])->order('id desc')->select(); foreach ($workbenches as $workbench) { // 获取工作台配置 $config = WorkbenchGroupPush::where('workbenchId', $workbench->id)->find(); @@ -87,7 +87,7 @@ class WorkbenchGroupPushJob } - // 发微信消息 + // 发微信个人消息 public function sendMsgToGroup($workbench, $config, $msgConf) { // 消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包(gif、其他表情包) 49:小程序/其他:图文、文件) @@ -117,7 +117,6 @@ class WorkbenchGroupPushJob } // 建立WebSocket $wsController = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); - foreach ($msgConf as $content) { $sendData = []; $sqlData = []; @@ -294,82 +293,72 @@ class WorkbenchGroupPushJob return false; } - $limit = ($config['pushType'] == 1) ? 10 : 1; - $order = ($config['pushOrder'] == 1) ? 'ci.sendTime desc, ci.id asc' : 'ci.sendTime desc, ci.id desc'; - - // 基础查询构建器 - $baseQuery = function() use ($workbench, $contentids) { - return Db::name('content_library')->alias('cl') - ->join('content_item ci', 'ci.libraryId = cl.id') - ->where(['cl.isDel' => 0, 'ci.isDel' => 0]) - ->where('ci.sendTime <= ' . (time() + 60)) - ->whereIn('cl.id', $contentids) - ->field('ci.id,ci.libraryId,ci.contentType,ci.title,ci.content,ci.resUrls,ci.urls,ci.comment,ci.sendTime'); - }; - - // 获取未发送的内容 - $unsentContent = $baseQuery() - ->join('workbench_group_push_item wgpi', 'wgpi.contentId = ci.id and wgpi.workbenchId = ' . $workbench->id, 'left') - ->where('wgpi.id', 'null') - ->order($order) - ->limit($limit) - ->select(); - - if (!empty($unsentContent)) { - return $unsentContent; + if ($config['pushType'] == 1) { + $limit = 10; + } else { + $limit = 1; } - // 如果不允许循环发送,直接返回空 - if ($config['isLoop'] != 1) { - return []; + + //推送顺序 + if ($config['pushOrder'] == 1) { + $order = 'ci.sendTime desc, ci.id asc'; + } else { + $order = 'ci.sendTime desc, ci.id desc'; } - // 循环发送逻辑:检查是否需要标记循环完成 - $this->checkAndResetLoop($workbench->id, $contentids); - // 获取下一个要发送的内容(从内容库中查询,排除isLoop为0的数据) - $isPushIds = Db::name('workbench_group_push_item') - ->where(['workbenchId' => $workbench->id,'isLoop' => 0]) - ->column('contentId'); - $nextContent = $baseQuery() - ->whereNotIn('ci.id', $isPushIds) - ->group('ci.id') - ->order('ci.id asc') - ->limit($limit) - ->select(); - return $nextContent; - } - - /** - * 检查循环状态 - * @param int $workbenchId - * @param array $contentids - */ - private function checkAndResetLoop($workbenchId, $contentids) - { - // 统计总内容数 - $totalCount = Db::name('content_library')->alias('cl') + // 基础查询 + $query = Db::name('content_library')->alias('cl') ->join('content_item ci', 'ci.libraryId = cl.id') + ->join('workbench_group_push_item wgpi', 'wgpi.contentId = ci.id and wgpi.workbenchId = ' . $workbench->id, 'left') ->where(['cl.isDel' => 0, 'ci.isDel' => 0]) ->where('ci.sendTime <= ' . (time() + 60)) ->whereIn('cl.id', $contentids) - ->count(); + ->field([ + 'ci.id', + 'ci.libraryId', + 'ci.contentType', + 'ci.title', + 'ci.content', + 'ci.resUrls', + 'ci.urls', + 'ci.comment', + 'ci.sendTime' + ]); + // 复制 query + $query2 = clone $query; + $query3 = clone $query; + // 根据accountType处理不同的发送逻辑 + if ($config['isLoop'] == 1) { + // 可以循环发送 + // 1. 优先获取未发送的内容 + $unsentContent = $query->where('wgpi.id', 'null') + ->order($order) + ->limit(0, $limit) + ->select(); + exit_data($unsentContent); + if (!empty($unsentContent)) { + return $unsentContent; + } + $lastSendData = Db::name('workbench_group_push_item')->where('workbenchId', $workbench->id)->order('id desc')->find(); + $fastSendData = Db::name('workbench_group_push_item')->where('workbenchId', $workbench->id)->order('id asc')->find(); - // 统计已发送内容数(排除isLoop为0的数据) - $sentCount = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->join('content_item ci', 'ci.id = wgpi.contentId') - ->join('content_library cl', 'cl.id = ci.libraryId') - ->where('wgpi.workbenchId', $workbenchId) - ->where('wgpi.isLoop', 0) - ->whereIn('cl.id', $contentids) - ->count('DISTINCT wgpi.contentId'); + $sentContent = $query2->where('wgpi.contentId', '<', $lastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select(); - // 记录循环状态 - if ($sentCount >= $totalCount) { - Db::name('workbench_group_push_item') - ->where(['workbenchId' => $workbenchId, 'isLoop' => 0]) - ->update(['isLoop' => 1]); + if (empty($sentContent)) { + $sentContent = $query3->where('wgpi.contentId', '=', $fastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select(); + } + return $sentContent; + } else { + // 不能循环发送,只获取未发送的内容 + $list = $query->where('wgpi.id', 'null') + ->order($order) + ->limit(0, $limit) + ->select(); + + exit_data($list); + return $list; } } @@ -422,4 +411,4 @@ class WorkbenchGroupPushJob return false; } -} \ No newline at end of file +} \ No newline at end of file