diff --git a/modules/core/src/main/java/com/bytedesk/core/tag/TagEntity.java b/modules/core/src/main/java/com/bytedesk/core/tag/TagEntity.java index 835bc5bb22..90fdb86fff 100644 --- a/modules/core/src/main/java/com/bytedesk/core/tag/TagEntity.java +++ b/modules/core/src/main/java/com/bytedesk/core/tag/TagEntity.java @@ -44,8 +44,8 @@ import lombok.experimental.SuperBuilder; // @EntityListeners({TagEntityListener.class}) @Table(name = "bytedesk_core_tag") public class TagEntity extends BaseEntity { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; /** * Name of the tag diff --git a/modules/core/src/main/java/com/bytedesk/core/tag/TagRequest.java b/modules/core/src/main/java/com/bytedesk/core/tag/TagRequest.java index eac50b6d79..8474268841 100644 --- a/modules/core/src/main/java/com/bytedesk/core/tag/TagRequest.java +++ b/modules/core/src/main/java/com/bytedesk/core/tag/TagRequest.java @@ -31,7 +31,6 @@ public class TagRequest extends BaseRequest { private static final long serialVersionUID = 1L; - private String name; private String description; diff --git a/modules/core/src/main/java/com/bytedesk/core/tag/TagResponse.java b/modules/core/src/main/java/com/bytedesk/core/tag/TagResponse.java index 9ab314dc1d..5456cfb401 100644 --- a/modules/core/src/main/java/com/bytedesk/core/tag/TagResponse.java +++ b/modules/core/src/main/java/com/bytedesk/core/tag/TagResponse.java @@ -33,7 +33,6 @@ public class TagResponse extends BaseResponse { private static final long serialVersionUID = 1L; - private String name; private String description; diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskEntity.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskEntity.java new file mode 100644 index 0000000000..948260536f --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskEntity.java @@ -0,0 +1,73 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:14:28 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-04 15:35:31 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import com.bytedesk.core.base.BaseEntity; +import com.bytedesk.core.constant.I18Consts; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +// import jakarta.persistence.EntityListeners; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; + +/** + * 待办任务: + * 一个工单内可以关联多个任务, + * 每个人都可以创建待办任务 + * 可以单纯创建待办,也可以关联工单创建待办任务 + * + * Task entity for content categorization and organization + * Provides taskging functionality for various system entities + * + * Database Table: bytedesk_core_task + * Purpose: Stores task definitions, colors, and organization settings + */ +@Entity +@Data +@SuperBuilder +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +// @EntityListeners({TaskEntityListener.class}) +@Table(name = "bytedesk_core_task") +public class TaskEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * Name of the task + */ + private String name; + + /** + * Description of the task + */ + @Builder.Default + private String description = I18Consts.I18N_DESCRIPTION; + + /** + * Type of task (CUSTOMER, TICKET, ARTICLE, etc.) + */ + @Builder.Default + @Column(name = "task_type") + private String type = TaskTypeEnum.CUSTOMER.name(); + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskEntityListener.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskEntityListener.java new file mode 100644 index 0000000000..acb3001747 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskEntityListener.java @@ -0,0 +1,51 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 09:52:34 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-03-20 17:00:07 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import org.springframework.stereotype.Component; +import org.springframework.util.SerializationUtils; + +import com.bytedesk.core.config.BytedeskEventPublisher; +import com.bytedesk.core.task.event.TaskCreateEvent; +import com.bytedesk.core.task.event.TaskUpdateEvent; +import com.bytedesk.core.utils.ApplicationContextHolder; + +import jakarta.persistence.PostPersist; +import jakarta.persistence.PostUpdate; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class TaskEntityListener { + + @PostPersist + public void onPostPersist(TaskEntity task) { + log.info("onPostPersist: {}", task); + TaskEntity cloneTask = SerializationUtils.clone(task); + // + BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class); + bytedeskEventPublisher.publishEvent(new TaskCreateEvent(cloneTask)); + } + + @PostUpdate + public void onPostUpdate(TaskEntity task) { + log.info("onPostUpdate: {}", task); + TaskEntity cloneTask = SerializationUtils.clone(task); + // + BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class); + bytedeskEventPublisher.publishEvent(new TaskUpdateEvent(cloneTask)); + } + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskEventListener.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskEventListener.java new file mode 100644 index 0000000000..c90b76a44f --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskEventListener.java @@ -0,0 +1,44 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 09:44:18 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-04 15:50:06 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import com.bytedesk.core.rbac.organization.OrganizationEntity; +import com.bytedesk.core.rbac.organization.event.OrganizationCreateEvent; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@AllArgsConstructor +public class TaskEventListener { + + private final TaskRestService taskRestService; + + @Order(3) + @EventListener + public void onOrganizationCreateEvent(OrganizationCreateEvent event) { + OrganizationEntity organization = (OrganizationEntity) event.getSource(); + String orgUid = organization.getUid(); + log.info("thread - organization created: {}", organization.getName()); + taskRestService.initTasks(orgUid); + } + + +} + diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskExcel.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskExcel.java new file mode 100644 index 0000000000..cc1d9bbdfa --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskExcel.java @@ -0,0 +1,47 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-08-01 06:18:10 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-07-04 18:00:51 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import java.time.ZonedDateTime; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import com.alibaba.excel.annotation.write.style.ColumnWidth; + +import lombok.Data; + +/** + * https://github.com/alibaba/easyexcel + */ +@Data +public class TaskExcel { + + @ExcelProperty(index = 0, value = "标签名称") + @ColumnWidth(20) + private String name; + + @ExcelProperty(index = 1, value = "类型") + @ColumnWidth(20) + private String type; + + @ExcelProperty(index = 2, value = "颜色") + @ColumnWidth(20) + private String color; + + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + @ExcelProperty(value = "创建时间", converter = com.bytedesk.core.converter.ZonedDateTimeConverter.class) + @ColumnWidth(25) + private ZonedDateTime createdAt; + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskInitData.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskInitData.java new file mode 100644 index 0000000000..66e6870c6b --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskInitData.java @@ -0,0 +1,122 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-03-11 08:54:35 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-04 17:12:37 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +// import com.bytedesk.core.constant.I18Consts; + +public class TaskInitData { + + /** + * Technical Support Tasks + * 技术支持标签 + */ + public static final String[] TECHNICAL_SUPPORT = { + // I18Consts.I18N_PREFIX + "thread.task.technical_support", // parent + "技术支持", // parent + }; + + /** + * Service Request Tasks + * 服务请求标签 + */ + public static final String[] SERVICE_REQUEST = { + // I18Consts.I18N_PREFIX + "thread.task.service_request", // parent + "服务请求", // parent + }; + + /** + * Consultation Tasks + * 咨询标签 + */ + public static final String[] CONSULTATION = { + // I18Consts.I18N_PREFIX + "thread.task.consultation", // parent + "咨询", // parent + }; + + /** + * Complaint & Suggestion Tasks + * 投诉与建议标签 + */ + public static final String[] COMPLAINT_SUGGESTION = { + // I18Consts.I18N_PREFIX + "thread.task.complaint_suggestion", // parent + "投诉建议", // parent + }; + + /** + * Operation & Maintenance Tasks + * 运维标签 + */ + public static final String[] OPERATION_MAINTENANCE = { + // I18Consts.I18N_PREFIX + "thread.task.operation_maintenance", // parent + "运维", // parent + // 其他 + // I18Consts.I18N_PREFIX + "thread.task.other", + "其他", + }; + + /** + * Helper method to determine if a task is a parent task + * + * @param task The task key to check + * @return true if it's a parent task + */ + public static boolean isParentTask(String task) { + return !task.contains("."); + } + + /** + * Helper method to get parent task key for a child task + * + * @param childTask The child task key + * @return The parent task key + */ + public static String getParentTask(String childTask) { + if (isParentTask(childTask)) { + return null; + } + // 由于已将常量转为中文,此方法可能需要重新实现 + // 这里仅保留基本结构,具体实现需要根据新的标签体系来调整 + return null; + } + + /** + * Get all tasks as a single array + * + * @return Array containing all tasks + */ + public static String[] getAllTasks() { + int totalLength = TECHNICAL_SUPPORT.length + SERVICE_REQUEST.length + + CONSULTATION.length + COMPLAINT_SUGGESTION.length + + OPERATION_MAINTENANCE.length; + + String[] allTasks = new String[totalLength]; + int index = 0; + + System.arraycopy(TECHNICAL_SUPPORT, 0, allTasks, index, TECHNICAL_SUPPORT.length); + index += TECHNICAL_SUPPORT.length; + + System.arraycopy(SERVICE_REQUEST, 0, allTasks, index, SERVICE_REQUEST.length); + index += SERVICE_REQUEST.length; + + System.arraycopy(CONSULTATION, 0, allTasks, index, CONSULTATION.length); + index += CONSULTATION.length; + + System.arraycopy(COMPLAINT_SUGGESTION, 0, allTasks, index, COMPLAINT_SUGGESTION.length); + index += COMPLAINT_SUGGESTION.length; + + System.arraycopy(OPERATION_MAINTENANCE, 0, allTasks, index, OPERATION_MAINTENANCE.length); + + return allTasks; + } +} \ No newline at end of file diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskInitializer.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskInitializer.java new file mode 100644 index 0000000000..58cec2f7af --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskInitializer.java @@ -0,0 +1,46 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-11-06 21:43:58 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-08-26 10:52:24 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.stereotype.Component; + +import com.bytedesk.core.constant.BytedeskConsts; + +import lombok.AllArgsConstructor; + +@Component +@AllArgsConstructor +public class TaskInitializer implements SmartInitializingSingleton { + + private final TaskRestService taskRestService; + + @Override + public void afterSingletonsInstantiated() { + initPermissions(); + // create default + String orgUid = BytedeskConsts.DEFAULT_ORGANIZATION_UID; + taskRestService.initTasks(orgUid); + } + + private void initPermissions() { + // for (PermissionEnum permission : PermissionEnum.values()) { + // String permissionValue = TaskPermissions.ARTICLE_PREFIX + permission.name(); + // authorityService.createForPlatform(permissionValue); + // } + } + + + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskPermissions.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskPermissions.java new file mode 100644 index 0000000000..365ff955b8 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskPermissions.java @@ -0,0 +1,20 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-11-05 16:58:18 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-05-06 11:55:32 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import com.bytedesk.core.base.BasePermissions; + +public class TaskPermissions extends BasePermissions { + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskRepository.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskRepository.java new file mode 100644 index 0000000000..f6c2102b15 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskRepository.java @@ -0,0 +1,30 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:25:55 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-20 12:52:47 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface TaskRepository extends JpaRepository, JpaSpecificationExecutor { + + Optional findByUid(String uid); + + Boolean existsByUid(String uid); + + Optional findByNameAndOrgUidAndTypeAndDeletedFalse(String name, String orgUid, String type); + + // Boolean existsByPlatform(String platform); +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskRequest.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskRequest.java new file mode 100644 index 0000000000..89375b7f5a --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskRequest.java @@ -0,0 +1,42 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:26:04 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-20 14:24:05 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import com.bytedesk.core.base.BaseRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +@AllArgsConstructor +@NoArgsConstructor +public class TaskRequest extends BaseRequest { + + private static final long serialVersionUID = 1L; + + + private String name; + + private String description; + + // @Builder.Default + // private String type = TaskTypeEnum.CUSTOMER.name(); + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskResponse.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskResponse.java new file mode 100644 index 0000000000..b3ceb38cdd --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskResponse.java @@ -0,0 +1,42 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:26:12 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-04 15:36:28 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + + +import com.bytedesk.core.base.BaseResponse; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +public class TaskResponse extends BaseResponse { + + private static final long serialVersionUID = 1L; + + private String name; + + private String description; + + private String type; + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskRestController.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskRestController.java new file mode 100644 index 0000000000..fdba8f2056 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskRestController.java @@ -0,0 +1,123 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:25:36 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-08-20 17:05:57 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +// import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.context.annotation.Description; + +import com.bytedesk.core.annotation.ActionAnnotation; +import com.bytedesk.core.base.BaseRestController; +import com.bytedesk.core.utils.JsonResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/task") +@AllArgsConstructor +@Tag(name = "Task Management", description = "Task management APIs for organizing and categorizing content with tasks") +@Description("Task Management Controller - Content taskging and categorization APIs") +public class TaskRestController extends BaseRestController { + + private final TaskRestService taskRestService; + + @ActionAnnotation(title = "Task", action = "组织查询", description = "query task by org") + @Operation(summary = "Query Tasks by Organization", description = "Retrieve tasks for the current organization") + @Override + public ResponseEntity queryByOrg(TaskRequest request) { + + Page tasks = taskRestService.queryByOrg(request); + + return ResponseEntity.ok(JsonResult.success(tasks)); + } + + @ActionAnnotation(title = "Task", action = "用户查询", description = "query task by user") + @Operation(summary = "Query Tasks by User", description = "Retrieve tasks for the current user") + @Override + public ResponseEntity queryByUser(TaskRequest request) { + + Page tasks = taskRestService.queryByUser(request); + + return ResponseEntity.ok(JsonResult.success(tasks)); + } + + @ActionAnnotation(title = "Task", action = "查询详情", description = "query task by uid") + @Operation(summary = "Query Task by UID", description = "Retrieve a specific task by its unique identifier") + @Override + public ResponseEntity queryByUid(TaskRequest request) { + + TaskResponse task = taskRestService.queryByUid(request); + + return ResponseEntity.ok(JsonResult.success(task)); + } + + @ActionAnnotation(title = "Task", action = "新建", description = "create task") + @Operation(summary = "Create Task", description = "Create a new task") + @Override + // @PreAuthorize("hasAuthority('TAG_CREATE')") + public ResponseEntity create(TaskRequest request) { + + TaskResponse task = taskRestService.create(request); + + return ResponseEntity.ok(JsonResult.success(task)); + } + + @ActionAnnotation(title = "Task", action = "更新", description = "update task") + @Operation(summary = "Update Task", description = "Update an existing task") + @Override + // @PreAuthorize("hasAuthority('TAG_UPDATE')") + public ResponseEntity update(TaskRequest request) { + + TaskResponse task = taskRestService.update(request); + + return ResponseEntity.ok(JsonResult.success(task)); + } + + @ActionAnnotation(title = "Task", action = "删除", description = "delete task") + @Operation(summary = "Delete Task", description = "Delete a task") + @Override + // @PreAuthorize("hasAuthority('TAG_DELETE')") + public ResponseEntity delete(TaskRequest request) { + + taskRestService.delete(request); + + return ResponseEntity.ok(JsonResult.success()); + } + + @ActionAnnotation(title = "Task", action = "导出", description = "export task") + @Operation(summary = "Export Tasks", description = "Export tasks to Excel format") + @Override + // @PreAuthorize("hasAuthority('TAG_EXPORT')") + @GetMapping("/export") + public Object export(TaskRequest request, HttpServletResponse response) { + return exportTemplate( + request, + response, + taskRestService, + TaskExcel.class, + "Task", + "task" + ); + } + + + +} \ No newline at end of file diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskRestService.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskRestService.java new file mode 100644 index 0000000000..bb8f99dd73 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskRestService.java @@ -0,0 +1,194 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:25:45 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-08-22 07:04:17 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import java.util.Optional; + +import org.modelmapper.ModelMapper; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import com.bytedesk.core.base.BaseRestServiceWithExport; +import com.bytedesk.core.rbac.auth.AuthService; +import com.bytedesk.core.rbac.user.UserEntity; +import com.bytedesk.core.uid.UidUtils; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@AllArgsConstructor +public class TaskRestService extends BaseRestServiceWithExport { + + private final TaskRepository taskRepository; + + private final ModelMapper modelMapper; + + private final UidUtils uidUtils; + + private final AuthService authService; + + @Override + protected Specification createSpecification(TaskRequest request) { + return TaskSpecification.search(request, authService); + } + + @Override + protected Page executePageQuery(Specification spec, Pageable pageable) { + return taskRepository.findAll(spec, pageable); + } + + @Cacheable(value = "task", key = "#uid", unless="#result==null") + @Override + public Optional findByUid(String uid) { + return taskRepository.findByUid(uid); + } + + @Cacheable(value = "task", key = "#name + '_' + #orgUid + '_' + #type", unless="#result==null") + public Optional findByNameAndOrgUidAndType(String name, String orgUid, String type) { + return taskRepository.findByNameAndOrgUidAndTypeAndDeletedFalse(name, orgUid, type); + } + + public Boolean existsByUid(String uid) { + return taskRepository.existsByUid(uid); + } + + @Transactional + @Override + public TaskResponse create(TaskRequest request) { + // 判断是否已经存在 + if (StringUtils.hasText(request.getUid()) && existsByUid(request.getUid())) { + return convertToResponse(findByUid(request.getUid()).get()); + } + // 检查name+orgUid+type是否已经存在 + if (StringUtils.hasText(request.getName()) && StringUtils.hasText(request.getOrgUid()) && StringUtils.hasText(request.getType())) { + Optional task = findByNameAndOrgUidAndType(request.getName(), request.getOrgUid(), request.getType()); + if (task.isPresent()) { + return convertToResponse(task.get()); + } + } + // + UserEntity user = authService.getUser(); + if (user != null) { + request.setUserUid(user.getUid()); + } + // + TaskEntity entity = modelMapper.map(request, TaskEntity.class); + if (!StringUtils.hasText(request.getUid())) { + entity.setUid(uidUtils.getUid()); + } + // + TaskEntity savedEntity = save(entity); + if (savedEntity == null) { + throw new RuntimeException("Create task failed"); + } + return convertToResponse(savedEntity); + } + + @Transactional + @Override + public TaskResponse update(TaskRequest request) { + Optional optional = taskRepository.findByUid(request.getUid()); + if (optional.isPresent()) { + TaskEntity entity = optional.get(); + modelMapper.map(request, entity); + // + TaskEntity savedEntity = save(entity); + if (savedEntity == null) { + throw new RuntimeException("Update task failed"); + } + return convertToResponse(savedEntity); + } + else { + throw new RuntimeException("Task not found"); + } + } + + @Override + protected TaskEntity doSave(TaskEntity entity) { + return taskRepository.save(entity); + } + + @Override + public TaskEntity handleOptimisticLockingFailureException(ObjectOptimisticLockingFailureException e, TaskEntity entity) { + try { + Optional latest = taskRepository.findByUid(entity.getUid()); + if (latest.isPresent()) { + TaskEntity latestEntity = latest.get(); + // 合并需要保留的数据 + latestEntity.setName(entity.getName()); + // latestEntity.setOrder(entity.getOrder()); + // latestEntity.setDeleted(entity.isDeleted()); + return taskRepository.save(latestEntity); + } + } catch (Exception ex) { + log.error("无法处理乐观锁冲突: {}", ex.getMessage(), ex); + throw new RuntimeException("无法处理乐观锁冲突: " + ex.getMessage(), ex); + } + return null; + } + + @Transactional + @Override + public void deleteByUid(String uid) { + Optional optional = taskRepository.findByUid(uid); + if (optional.isPresent()) { + optional.get().setDeleted(true); + save(optional.get()); + // taskRepository.delete(optional.get()); + } + else { + throw new RuntimeException("Task not found"); + } + } + + @Override + public void delete(TaskRequest request) { + deleteByUid(request.getUid()); + } + + @Override + public TaskResponse convertToResponse(TaskEntity entity) { + return modelMapper.map(entity, TaskResponse.class); + } + + @Override + public TaskExcel convertToExcel(TaskEntity entity) { + return modelMapper.map(entity, TaskExcel.class); + } + + public void initTasks(String orgUid) { + // log.info("initThreadTask"); + // for (String task : TaskInitData.getAllTasks()) { + // TaskRequest taskRequest = TaskRequest.builder() + // .uid(Utils.formatUid(orgUid, task)) + // .name(task) + // .order(0) + // .type(TaskTypeEnum.THREAD.name()) + // .level(LevelEnum.ORGANIZATION.name()) + // .platform(BytedeskConsts.PLATFORM_BYTEDESK) + // .orgUid(orgUid) + // .build(); + // create(taskRequest); + // } + } + + + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskSpecification.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskSpecification.java new file mode 100644 index 0000000000..058b48ba93 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskSpecification.java @@ -0,0 +1,57 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-07-09 22:19:21 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-08-18 15:44:34 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import com.bytedesk.core.base.BaseSpecification; +import com.bytedesk.core.rbac.auth.AuthService; + +import jakarta.persistence.criteria.Predicate; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class TaskSpecification extends BaseSpecification { + + public static Specification search(TaskRequest request, AuthService authService) { + // log.info("request: {} orgUid: {} pageNumber: {} pageSize: {}", + // request, request.getOrgUid(), request.getPageNumber(), request.getPageSize()); + return (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + predicates.addAll(getBasicPredicates(root, criteriaBuilder, request, authService)); + // name + if (StringUtils.hasText(request.getName())) { + predicates.add(criteriaBuilder.like(root.get("name"), "%" + request.getName() + "%")); + } + // description + if (StringUtils.hasText(request.getDescription())) { + predicates.add(criteriaBuilder.like(root.get("description"), "%" + request.getDescription() + "%")); + } + // type + if (StringUtils.hasText(request.getType())) { + predicates.add(criteriaBuilder.equal(root.get("type"), request.getType())); + } + // + if (StringUtils.hasText(request.getUserUid())) { + predicates.add(criteriaBuilder.equal(root.get("userUid"), request.getUserUid())); + } + // + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + } +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/TaskTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/task/TaskTypeEnum.java new file mode 100644 index 0000000000..81368d64b4 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/TaskTypeEnum.java @@ -0,0 +1,20 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-07-23 17:02:46 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-03-11 08:57:11 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task; + +public enum TaskTypeEnum { + THREAD, + CUSTOMER, + TICKET +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/event/TaskCreateEvent.java b/modules/core/src/main/java/com/bytedesk/core/task/event/TaskCreateEvent.java new file mode 100644 index 0000000000..58ff50ba6d --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/event/TaskCreateEvent.java @@ -0,0 +1,36 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 09:59:29 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-02-25 10:00:34 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task.event; + +import org.springframework.context.ApplicationEvent; + +import com.bytedesk.core.task.TaskEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class TaskCreateEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + private TaskEntity task; + + public TaskCreateEvent(TaskEntity task) { + super(task); + this.task = task; + } + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/event/TaskDeleteEvent.java b/modules/core/src/main/java/com/bytedesk/core/task/event/TaskDeleteEvent.java new file mode 100644 index 0000000000..5b53240e00 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/event/TaskDeleteEvent.java @@ -0,0 +1,35 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 12:31:16 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-02-25 12:31:19 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task.event; + +import org.springframework.context.ApplicationEvent; + +import com.bytedesk.core.task.TaskEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class TaskDeleteEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + private TaskEntity task; + + public TaskDeleteEvent(TaskEntity task) { + super(task); + this.task = task; + } +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/event/TaskUpdateEvent.java b/modules/core/src/main/java/com/bytedesk/core/task/event/TaskUpdateEvent.java new file mode 100644 index 0000000000..a4cf654dea --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/event/TaskUpdateEvent.java @@ -0,0 +1,36 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 09:59:29 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-02-25 10:01:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.task.event; + +import org.springframework.context.ApplicationEvent; + +import com.bytedesk.core.task.TaskEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class TaskUpdateEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + private TaskEntity task; + + public TaskUpdateEvent(TaskEntity task) { + super(task); + this.task = task; + } + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/task/package-info.java b/modules/core/src/main/java/com/bytedesk/core/task/package-info.java new file mode 100644 index 0000000000..bcf6237b4d --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/task/package-info.java @@ -0,0 +1,5 @@ + +@NonNullApi +package com.bytedesk.core.task; + +import org.springframework.lang.NonNullApi; diff --git a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsEntity.java b/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsEntity.java index f6e3d5a674..6f09436598 100644 --- a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsEntity.java +++ b/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsEntity.java @@ -17,7 +17,6 @@ import java.time.ZonedDateTime; import com.bytedesk.core.base.BaseEntity; import com.bytedesk.core.constant.I18Consts; -import com.bytedesk.ticket.ticket_settings.sub.TicketBasicSettingsEntity; import com.bytedesk.ticket.ticket_settings.sub.TicketCategorySettingsEntity; import jakarta.persistence.CascadeType; @@ -82,8 +81,8 @@ public class TicketSettingsEntity extends BaseEntity { // // ====== 发布版本 ====== - @ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }) - private TicketBasicSettingsEntity basicSettings; + // @ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }) + // private TicketBasicSettingsEntity basicSettings; @ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }) private TicketCategorySettingsEntity categorySettings; @@ -104,8 +103,8 @@ public class TicketSettingsEntity extends BaseEntity { // private TicketCustomFieldSettingsEntity customFieldSettings; // ====== 草稿版本 ====== - @ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }) - private TicketBasicSettingsEntity draftBasicSettings; + // @ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }) + // private TicketBasicSettingsEntity draftBasicSettings; @ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }) private TicketCategorySettingsEntity draftCategorySettings; diff --git a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsRequest.java b/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsRequest.java index 978d99afac..cc125f3ab1 100644 --- a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsRequest.java +++ b/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsRequest.java @@ -61,7 +61,6 @@ public class TicketSettingsRequest extends BaseRequest { private TicketCustomFieldSettingsRequest customFieldSettings; private TicketCategorySettingsRequest categorySettings; - private TicketCategorySettingsRequest draftCategorySettings; } diff --git a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsRestService.java b/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsRestService.java index b9a9eea917..d78694f030 100644 --- a/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsRestService.java +++ b/modules/ticket/src/main/java/com/bytedesk/ticket/ticket_settings/TicketSettingsRestService.java @@ -34,7 +34,6 @@ import com.bytedesk.core.rbac.user.UserEntity; import com.bytedesk.core.uid.UidUtils; import com.bytedesk.ticket.ticket_settings.binding.TicketSettingsBindingEntity; import com.bytedesk.ticket.ticket_settings.binding.TicketSettingsBindingRepository; -import com.bytedesk.ticket.ticket_settings.sub.TicketBasicSettingsEntity; import com.bytedesk.ticket.ticket_settings.sub.TicketCategorySettingsEntity; import com.bytedesk.ticket.ticket_settings.sub.dto.TicketCategorySettingsRequest; import com.bytedesk.ticket.ticket_settings.sub.dto.TicketCategorySettingsResponse; @@ -114,12 +113,12 @@ public class TicketSettingsRestService extends // 初始化并绑定发布 + 草稿子配置,参考 WorkgroupSettings 的 create 逻辑 // Basic - TicketBasicSettingsEntity basic = TicketBasicSettingsEntity.fromRequest(request.getBasicSettings(), modelMapper); - basic.setUid(uidUtils.getUid()); - entity.setBasicSettings(basic); - TicketBasicSettingsEntity draftBasic = TicketBasicSettingsEntity.fromRequest(request.getBasicSettings(), modelMapper); - draftBasic.setUid(uidUtils.getUid()); - entity.setDraftBasicSettings(draftBasic); + // TicketBasicSettingsEntity basic = TicketBasicSettingsEntity.fromRequest(request.getBasicSettings(), modelMapper); + // basic.setUid(uidUtils.getUid()); + // entity.setBasicSettings(basic); + // TicketBasicSettingsEntity draftBasic = TicketBasicSettingsEntity.fromRequest(request.getBasicSettings(), modelMapper); + // draftBasic.setUid(uidUtils.getUid()); + // entity.setDraftBasicSettings(draftBasic); TicketCategorySettingsEntity category = TicketCategorySettingsEntity .fromRequest(request.getCategorySettings(), uidUtils::getUid);