From a727cc062240d9a4e60e05cec62ebce64bd2e4cd Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 30 Mar 2026 10:12:24 +0800 Subject: [PATCH] fix: support demand identity selection on task creation --- .../com/designhub/dto/PipelineTaskDTO.java | 1 + .../com/designhub/service/TaskService.java | 127 ++++++++------ .../com/designhub/service/UserService.java | 12 +- .../com/designhub/vo/UserDepartmentVO.java | 5 + frontend/src/api/auth.ts | 19 ++- frontend/src/api/task.ts | 2 + frontend/src/views/CreateTask.vue | 157 +++++++++++++++++- 7 files changed, 261 insertions(+), 62 deletions(-) diff --git a/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java b/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java index b9a445a..1f3bae9 100644 --- a/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java +++ b/backend/src/main/java/com/designhub/dto/PipelineTaskDTO.java @@ -38,6 +38,7 @@ public class PipelineTaskDTO { private Long brandId; private Long categoryId; private Long platformId; + private Long publisherDeptId; private String spuInfo; private String taskSubType; private String attachmentUrl; diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java index 1990db9..2b29647 100644 --- a/backend/src/main/java/com/designhub/service/TaskService.java +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -88,30 +88,10 @@ public class TaskService { // 检查是否是管理员(管理员可以同时拥有需求部门和接单部门权限) boolean isAdmin = isAdminUser(user.getId()); - - if (!isAdmin) { - if (user.getDeptId() == null) { - throw new BusinessException(ResultCode.FORBIDDEN, "用户部门信息不完整"); - } - - var department = departmentService.getDepartmentById(user.getDeptId()); - if (department == null) { - throw new BusinessException(ResultCode.FORBIDDEN, "用户部门不存在"); - } - - String deptType = department.getDeptType(); - if (!"demand".equals(deptType) && !"primary_demand".equals(deptType)) { - throw new BusinessException(ResultCode.FORBIDDEN, "只有需求部门或一级需求部门可以发起任务,您所在的部门是接单部门"); - } - - // 判断是否为一级需求部门发起的任务 - isPrimaryTask = "primary_demand".equals(deptType); - } - - // 设置发布人部门ID - if (task.getPublisherDeptId() == null && user.getDeptId() != null) { - task.setPublisherDeptId(user.getDeptId()); - } + + Department publisherDept = resolvePublishingDepartment(user, task.getPublisherDeptId(), isAdmin); + isPrimaryTask = "primary_demand".equals(publisherDept.getDeptType()); + task.setPublisherDeptId(publisherDept.getId()); } // 如果是一级需求部门发起的任务,设置相关字段 @@ -289,26 +269,8 @@ public class TaskService { // 检查是否是管理员 boolean isAdmin = isAdminUser(user.getId()); - boolean isPrimaryTask = false; - - if (!isAdmin) { - if (user.getDeptId() == null) { - throw new BusinessException(ResultCode.FORBIDDEN, "用户部门信息不完整"); - } - - var department = departmentService.getDepartmentById(user.getDeptId()); - if (department == null) { - throw new BusinessException(ResultCode.FORBIDDEN, "用户部门不存在"); - } - - String deptType = department.getDeptType(); - if (!"demand".equals(deptType) && !"primary_demand".equals(deptType)) { - throw new BusinessException(ResultCode.FORBIDDEN, "只有需求部门或一级需求部门可以发起任务"); - } - - // 判断是否为一级需求部门发起的任务 - isPrimaryTask = "primary_demand".equals(deptType); - } + Department publisherDept = resolvePublishingDepartment(user, dto.getPublisherDeptId(), isAdmin); + boolean isPrimaryTask = "primary_demand".equals(publisherDept.getDeptType()); // 验证流水线模式 if (!"sequential".equals(dto.getPipelineMode()) && !"parallel".equals(dto.getPipelineMode())) { @@ -358,12 +320,12 @@ public class TaskService { int totalScoreInt = totalScoreDecimal.intValue(); // 用于积分检查 // 检查部门积分是否充足(管理员跳过积分检查) - if (!isAdmin && user.getDeptId() != null) { - if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScoreInt)) { + if (!isAdmin && publisherDept.getId() != null) { + if (!teamScorePoolService.checkBalance(publisherDept.getId(), totalScoreInt)) { throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); } - if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScoreInt)) { + if (!teamScorePoolService.checkPeriodLimit(publisherDept.getId(), totalScoreInt)) { throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制"); } } @@ -378,7 +340,7 @@ public class TaskService { task.setThirdPartyTaskNo(generateThirdPartyTaskNo()); task.setTitle(dto.getTitle()); task.setPublisherId(publisherId); - task.setPublisherDeptId(user.getDeptId()); + task.setPublisherDeptId(publisherDept.getId()); task.setIsPipeline(1); task.setPipelineMode(dto.getPipelineMode()); task.setScore(totalScoreDecimal); @@ -428,9 +390,9 @@ public class TaskService { if (isPrimaryTask) { task.setIsPrimaryTask(1); task.setPrimaryPublisherId(publisherId); - task.setPrimaryPublisherDeptId(user.getDeptId()); + task.setPrimaryPublisherDeptId(publisherDept.getId()); task.setDetailCompleted(0); // 明细未完善 - log.info("一级需求部门发起流水线任务: publisherId={}, deptId={}", publisherId, user.getDeptId()); + log.info("一级需求部门发起流水线任务: publisherId={}, deptId={}", publisherId, publisherDept.getId()); } else { task.setIsPrimaryTask(0); task.setDetailCompleted(1); // 普通任务明细已完善 @@ -1318,6 +1280,62 @@ public class TaskService { return user != null ? user.getDeptId() : null; } + private Department resolvePublishingDepartment(User user, Long requestedDeptId, boolean isAdmin) { + if (user == null) { + throw new BusinessException(ResultCode.FORBIDDEN, "用户不存在"); + } + + List userDepartments = new java.util.ArrayList<>(userService.getUserDepartments(user.getId())); + if (user.getDeptId() != null && userDepartments.stream().noneMatch(dept -> user.getDeptId().equals(dept.getId()))) { + Department primaryDept = departmentService.getDepartmentById(user.getDeptId()); + if (primaryDept != null) { + userDepartments.add(primaryDept); + } + } + + List publishingDepartments = userDepartments.stream() + .filter(java.util.Objects::nonNull) + .filter(dept -> dept.getStatus() != null && dept.getStatus() == 1) + .filter(dept -> dept.getDeleted() == null || dept.getDeleted() == 0) + .filter(dept -> "demand".equals(dept.getDeptType()) || "primary_demand".equals(dept.getDeptType())) + .distinct() + .collect(Collectors.toList()); + + if (requestedDeptId != null) { + Department requestedDept = departmentService.getDepartmentById(requestedDeptId); + if (requestedDept == null) { + throw new BusinessException(ResultCode.PARAM_ERROR, "发起部门不存在"); + } + if (!"demand".equals(requestedDept.getDeptType()) && !"primary_demand".equals(requestedDept.getDeptType())) { + throw new BusinessException(ResultCode.PARAM_ERROR, "发起部门必须是需求部门或一级需求部门"); + } + if (!isAdmin && publishingDepartments.stream().noneMatch(dept -> requestedDeptId.equals(dept.getId()))) { + throw new BusinessException(ResultCode.FORBIDDEN, "您不属于所选发起部门"); + } + return requestedDept; + } + + if (user.getDeptId() != null) { + Department currentDept = departmentService.getDepartmentById(user.getDeptId()); + if (currentDept != null && ("demand".equals(currentDept.getDeptType()) || "primary_demand".equals(currentDept.getDeptType()))) { + return currentDept; + } + if (isAdmin && currentDept != null) { + return currentDept; + } + } + + if (publishingDepartments.size() == 1) { + return publishingDepartments.get(0); + } + + if (publishingDepartments.isEmpty()) { + throw new BusinessException(ResultCode.FORBIDDEN, "只有需求部门或一级需求部门可以发起任务"); + } + + throw new BusinessException(ResultCode.PARAM_ERROR, "请先选择发起部门"); + } + /** * 获取我发布的任务(支持多维度筛选) */ @@ -1823,7 +1841,7 @@ public class TaskService { .filter(java.util.Objects::nonNull) .anyMatch(dept -> "demand".equals(dept.getDeptType()) && dept.getStatus() != null && dept.getStatus() == 1 - && dept.getDeleted() != null && dept.getDeleted() == 0); + && (dept.getDeleted() == null || dept.getDeleted() == 0)); if (isDemandDeptMemberForPrimaryTask) { log.info("需求部门成员 {} 编辑一级需求任务 {}", userId, id); } @@ -1909,12 +1927,13 @@ public class TaskService { // 检查部门积分是否充足(管理员跳过积分检查,一级需求部门无明细提交场景跳过) User user = userService.getUserById(userId); - if (!isAdmin && !allowPrimaryTaskWithoutItems && user.getDeptId() != null) { - if (!teamScorePoolService.checkBalance(user.getDeptId(), totalScoreInt)) { + Department publisherDept = resolvePublishingDepartment(user, existTask.getPublisherDeptId(), isAdmin); + if (!isAdmin && !allowPrimaryTaskWithoutItems && publisherDept.getId() != null) { + if (!teamScorePoolService.checkBalance(publisherDept.getId(), totalScoreInt)) { throw new BusinessException(ResultCode.INSUFFICIENT_SCORE, "部门积分余额不足"); } - if (!teamScorePoolService.checkPeriodLimit(user.getDeptId(), totalScoreInt)) { + if (!teamScorePoolService.checkPeriodLimit(publisherDept.getId(), totalScoreInt)) { throw new BusinessException(ResultCode.SCORE_LIMIT_EXCEEDED, "超过本周期积分消耗限制"); } } diff --git a/backend/src/main/java/com/designhub/service/UserService.java b/backend/src/main/java/com/designhub/service/UserService.java index f2d682d..13247fb 100644 --- a/backend/src/main/java/com/designhub/service/UserService.java +++ b/backend/src/main/java/com/designhub/service/UserService.java @@ -1190,6 +1190,7 @@ public class UserService { return UserDepartmentVO.builder() .deptId(dept.getId()) .deptName(dept.getDeptName()) + .deptType(dept.getDeptType()) .isPrimary(ud.getIsPrimary()) .build(); } @@ -1328,9 +1329,16 @@ public class UserService { new LambdaQueryWrapper() .eq(UserDepartment::getUserId, userId) ); - return userDepts.stream() + LinkedHashSet deptIds = userDepts.stream() .map(UserDepartment::getDeptId) - .collect(Collectors.toList()); + .collect(Collectors.toCollection(LinkedHashSet::new)); + + User user = userMapper.selectById(userId); + if (user != null && user.getDeptId() != null) { + deptIds.add(user.getDeptId()); + } + + return new ArrayList<>(deptIds); } /** diff --git a/backend/src/main/java/com/designhub/vo/UserDepartmentVO.java b/backend/src/main/java/com/designhub/vo/UserDepartmentVO.java index 0752c26..651a297 100644 --- a/backend/src/main/java/com/designhub/vo/UserDepartmentVO.java +++ b/backend/src/main/java/com/designhub/vo/UserDepartmentVO.java @@ -18,6 +18,11 @@ public class UserDepartmentVO { * 部门名称 */ private String deptName; + + /** + * 部门类型:demand / receiving / primary_demand + */ + private String deptType; /** * 是否为主部门:0-否,1-是 diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index 2c368e1..28b481e 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -43,9 +43,27 @@ export interface UserInfo { avatar: string deptId: number deptName: string + deptType?: string + departments?: UserDepartment[] roleIds?: string // 角色ID列表(逗号分隔) roles: string[] permissions: string[] + isDepartmentLeader?: number | boolean + isAdmin?: number | boolean + managedDepartments?: ManagedDepartment[] +} + +export interface UserDepartment { + deptId: number + deptName: string + deptType?: string + isPrimary?: number +} + +export interface ManagedDepartment { + deptId: number + deptName: string + deptType: string } /** @@ -94,4 +112,3 @@ export const refreshToken = () => { `/auth/refresh` ) } - diff --git a/frontend/src/api/task.ts b/frontend/src/api/task.ts index 7ff76ca..1409141 100644 --- a/frontend/src/api/task.ts +++ b/frontend/src/api/task.ts @@ -129,11 +129,13 @@ export interface PipelineTaskRequest { title: string isPipeline: boolean pipelineMode: 'sequential' | 'parallel' + publisherDeptId?: number items: Array<{ taskTypeId: number quantity: number sortOrder: number receiverId?: number // 指定接收人 + selectedDeptId?: number }> brandId?: number categoryId?: number diff --git a/frontend/src/views/CreateTask.vue b/frontend/src/views/CreateTask.vue index b5f5854..9b950f5 100644 --- a/frontend/src/views/CreateTask.vue +++ b/frontend/src/views/CreateTask.vue @@ -8,6 +8,25 @@
团队积分余额
{{ teamScore }} 分
+
+
发起部门
+ + + {{ dept.deptName }}{{ dept.deptType === 'primary_demand' ? '(一级需求)' : '' }} + + +
任务积分和审批流按当前发起部门计算
+
@@ -644,6 +663,7 @@ const loading = ref(false) // 团队积分余额 const teamScore = ref(0) +const selectedPublishingDeptId = ref(undefined) // 判断是否是管理员(管理员跳过积分检查) const isAdmin = computed(() => { @@ -659,6 +679,54 @@ const isAdmin = computed(() => { return false }) +interface PublishingDepartmentOption { + deptId: number + deptName: string + deptType: string + isPrimary?: number +} + +const availablePublishingDepartments = computed(() => { + const userInfo = authStore.userInfo + if (!userInfo) return [] + + const result: PublishingDepartmentOption[] = [] + const seen = new Set() + + const departments = Array.isArray(userInfo.departments) ? userInfo.departments : [] + departments.forEach((dept) => { + if (!dept?.deptId || seen.has(dept.deptId)) return + if (dept.deptType !== 'demand' && dept.deptType !== 'primary_demand') return + result.push({ + deptId: dept.deptId, + deptName: dept.deptName, + deptType: dept.deptType, + isPrimary: dept.isPrimary + }) + seen.add(dept.deptId) + }) + + if ( + userInfo.deptId && + !seen.has(userInfo.deptId) && + (userInfo.deptType === 'demand' || userInfo.deptType === 'primary_demand') + ) { + result.unshift({ + deptId: userInfo.deptId, + deptName: userInfo.deptName, + deptType: userInfo.deptType, + isPrimary: 1 + }) + seen.add(userInfo.deptId) + } + + return result +}) + +const activePublishingDept = computed(() => { + return availablePublishingDepartments.value.find(dept => dept.deptId === selectedPublishingDeptId.value) +}) + // 任务子类型列表 const taskSubTypes = ref([]) @@ -676,9 +744,7 @@ const primaryDeptConfig = ref({ // 判断当前用户是否是一级需求部门用户 const isPrimaryDeptUser = computed(() => { - const userInfo = authStore.userInfo as any - if (!userInfo) return false - return userInfo.deptType === 'primary_demand' + return activePublishingDept.value?.deptType === 'primary_demand' }) // 判断是否应该显示任务明细区域 @@ -759,6 +825,37 @@ const formData = reactive({ timeLimit: undefined as number | undefined // 时效要求 }) +const initializePublishingDept = (preferredDeptId?: number) => { + const departments = availablePublishingDepartments.value + if (!departments.length) { + selectedPublishingDeptId.value = undefined + return + } + + if (preferredDeptId && departments.some(dept => dept.deptId === preferredDeptId)) { + selectedPublishingDeptId.value = preferredDeptId + return + } + + if (selectedPublishingDeptId.value && departments.some(dept => dept.deptId === selectedPublishingDeptId.value)) { + return + } + + const currentDeptId = authStore.userInfo?.deptId + const currentDeptType = authStore.userInfo?.deptType + if ( + currentDeptId && + (currentDeptType === 'demand' || currentDeptType === 'primary_demand') && + departments.some(dept => dept.deptId === currentDeptId) + ) { + selectedPublishingDeptId.value = currentDeptId + return + } + + const primaryPublishingDept = departments.find(dept => dept.isPrimary === 1) + selectedPublishingDeptId.value = primaryPublishingDept?.deptId || departments[0].deptId +} + // 新条码输入框 const newBarcodeInput = ref('') @@ -1503,6 +1600,7 @@ const handleSaveDraft = async () => { title: formData.title, isPipeline: true, pipelineMode: 'sequential', + publisherDeptId: selectedPublishingDeptId.value, items: validTaskTypes.map((item, index) => ({ taskTypeId: item.taskTypeId!, quantity: item.quantity || 1, @@ -1558,6 +1656,11 @@ const handleSubmit = async () => { // 验证表单 await formRef.value.validate() + if (!isAdmin.value && !selectedPublishingDeptId.value) { + message.error('请选择发起部门') + return + } + // 一级需求部门用户必须上传预览图 if (isPrimaryDeptUser.value && !formData.previewImage) { message.error('一级需求部门发起任务必须上传预览图') @@ -1646,6 +1749,7 @@ const handleSubmit = async () => { title: formData.title, isPipeline: !isPrimaryDeptWithoutDetail, // 一级需求部门不填明细时,不标记为流水线任务 pipelineMode: 'sequential', // 固定为顺序流水线 + publisherDeptId: selectedPublishingDeptId.value, items: validTaskTypes.map((item, index) => ({ taskTypeId: item.taskTypeId!, quantity: item.quantity, @@ -1675,6 +1779,7 @@ const handleSubmit = async () => { // 一级需求部门不填写任务明细时,需要设置 isPipeline=true 来触发审核流程 const taskData = { title: formData.title, + publisherDeptId: selectedPublishingDeptId.value, brandId: formData.brandId, platformId: formData.platformId, categoryId: formData.categoryId, @@ -1714,6 +1819,7 @@ const handleSubmit = async () => { // 任务明细将由需求部门在审核时填写 const taskData = { title: formData.title, + publisherDeptId: selectedPublishingDeptId.value, brandId: formData.brandId, platformId: formData.platformId, categoryId: formData.categoryId, @@ -1800,13 +1906,13 @@ const loadTeamScore = async () => { console.log('[loadTeamScore] 当前用户信息:', authStore.userInfo) // 确保用户信息是最新的 - if (!authStore.userInfo || !authStore.userInfo.deptId) { + if (!authStore.userInfo) { console.log('[loadTeamScore] 用户信息缺失或无部门ID,重新获取用户信息') await authStore.fetchUserInfo() console.log('[loadTeamScore] 重新获取后的用户信息:', authStore.userInfo) } - const deptId = authStore.userInfo?.deptId + const deptId = selectedPublishingDeptId.value console.log('[loadTeamScore] 部门ID:', deptId) if (!deptId) { @@ -2190,6 +2296,7 @@ const loadTaskForEdit = async (taskId: number) => { if (task) { console.log('加载任务数据:', task) + initializePublishingDept(task.publisherDeptId || task.primaryPublisherDeptId) // 填充基本表单数据 formData.title = task.title || '' @@ -2314,6 +2421,7 @@ const loadTaskForEdit = async (taskId: number) => { } console.log('任务数据加载成功,formData.taskTypes:', formData.taskTypes) + await loadTeamScore() } } catch (error) { console.error('加载任务数据失败:', error) @@ -2403,6 +2511,7 @@ const loadPrimaryTaskForEdit = async () => { } console.log('加载一级需求任务数据:', task) + initializePublishingDept(task.publisherDeptId || task.primaryPublisherDeptId) // 设置编辑模式 editMode.value = true @@ -2513,6 +2622,7 @@ const loadPrimaryTaskForEdit = async () => { formData.primaryPublisherDeptId = task.primaryPublisherDeptId message.info('请完善任务明细后提交') + await loadTeamScore() } catch (error) { console.error('加载一级需求任务失败:', error) @@ -2530,6 +2640,7 @@ onMounted(async () => { try { await authStore.fetchUserInfo() console.log('用户信息已刷新:', authStore.userInfo) + initializePublishingDept() } catch (error) { console.error('刷新用户信息失败:', error) } @@ -2561,6 +2672,22 @@ onMounted(async () => { window.addEventListener('configUpdated', handleConfigUpdate as EventListener) }) +watch(availablePublishingDepartments, (departments) => { + if (!departments.length) { + selectedPublishingDeptId.value = undefined + return + } + + if (!selectedPublishingDeptId.value || !departments.some(dept => dept.deptId === selectedPublishingDeptId.value)) { + initializePublishingDept() + } +}, { immediate: true }) + +watch(selectedPublishingDeptId, async (deptId, oldDeptId) => { + if (deptId === oldDeptId) return + await loadTeamScore() +}) + // 组件卸载 onUnmounted(() => { window.removeEventListener('configUpdated', handleConfigUpdate as EventListener) @@ -2643,6 +2770,26 @@ onUnmounted(() => { } } +.publisher-dept-card { + margin-bottom: $space-md; +} + +.publisher-dept-label { + margin-bottom: $space-xs; + font-size: $font-size-sm; + color: $text-secondary; +} + +.publisher-dept-select { + width: 100%; +} + +.publisher-dept-tip { + margin-top: $space-xs; + font-size: $font-size-xs; + color: $text-tertiary; +} + .time-estimate { display: flex; flex-wrap: wrap;