Kaynağa Gözat

Merge branch 'master' of http://192.168.1.25:3000/lbyzx123/huimv-employment

xsh_1997 2 hafta önce
ebeveyn
işleme
ea6b9c8428

+ 61 - 1
huimv-employment/fe-api/src/main/java/com/huimv/employment/controller/mp/ConversationController.java

@@ -13,6 +13,12 @@ import com.huimv.employment.service.conversation.dto.ConversationChatRequest;
13 13
 import com.huimv.employment.service.conversation.dto.ConversationCreateRequest;
14 14
 import com.huimv.employment.service.conversation.dto.ConversationMessagePageResponse;
15 15
 import com.huimv.employment.service.conversation.dto.ConversationSummaryResponse;
16
+import com.huimv.employment.service.draft.DraftService;
17
+import com.huimv.employment.service.draft.dto.McpDraftCalculateResponse;
18
+import com.huimv.employment.service.order.EmploymentOrderService;
19
+import com.huimv.employment.service.order.dto.EmploymentOrderItemResponse;
20
+import com.huimv.employment.service.registration.RegistrationBatchService;
21
+import com.huimv.employment.service.registration.dto.RegistrationBatchDetailResponse;
16 22
 import io.swagger.v3.oas.annotations.Operation;
17 23
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
18 24
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -54,13 +60,22 @@ public class ConversationController {
54 60
     private final ConversationService conversationService;
55 61
     private final ConversationChatService conversationChatService;
56 62
     private final ConversationMessageService conversationMessageService;
63
+    private final DraftService draftService;
64
+    private final RegistrationBatchService registrationBatchService;
65
+    private final EmploymentOrderService employmentOrderService;
57 66
 
58 67
     public ConversationController(ConversationService conversationService,
59 68
                                   ConversationChatService conversationChatService,
60
-                                  ConversationMessageService conversationMessageService) {
69
+                                  ConversationMessageService conversationMessageService,
70
+                                  DraftService draftService,
71
+                                  RegistrationBatchService registrationBatchService,
72
+                                  EmploymentOrderService employmentOrderService) {
61 73
         this.conversationService = conversationService;
62 74
         this.conversationChatService = conversationChatService;
63 75
         this.conversationMessageService = conversationMessageService;
76
+        this.draftService = draftService;
77
+        this.registrationBatchService = registrationBatchService;
78
+        this.employmentOrderService = employmentOrderService;
64 79
     }
65 80
 
66 81
     /**
@@ -106,6 +121,51 @@ public class ConversationController {
106 121
                 loginUser.getUserId(), conversationId, page, size));
107 122
     }
108 123
 
124
+    /**
125
+     * 查询指定会话下全部草稿的成本测算(方案对比)。
126
+     */
127
+    @GetMapping("/{id}/drafts/cost-comparison")
128
+    @Operation(summary = "按会话查询全部草稿成本测算",
129
+            description = "返回该会话下可测算草稿的方案对比列表;"
130
+                    + "每个元素结构与 GET /api/v1/mp/drafts/{id}/cost-comparison 一致(含 draft_id、schemes)。"
131
+                    + "字段不完整无法测算的草稿自动跳过。仅可查询本企业用户自己的会话。")
132
+    public R<List<McpDraftCalculateResponse>> listDraftCostComparisons(
133
+            @PathVariable("id") Long conversationId) {
134
+        LoginUser loginUser = requireEnterprise();
135
+        return R.ok(draftService.listCostComparisonsByConversation(
136
+                loginUser.getUserId(), conversationId));
137
+    }
138
+
139
+    /**
140
+     * 查询指定会话下全部草稿的登记进度。
141
+     */
142
+    @GetMapping("/{id}/drafts/registration-progress")
143
+    @Operation(summary = "按会话查询全部草稿登记进度",
144
+            description = "返回该会话下已确认方案并建有登记批次的草稿进度列表;"
145
+                    + "每个元素结构与 GET /api/v1/mp/registration-batches?draft_id= 一致。"
146
+                    + "尚未确认方案、无批次的草稿自动跳过。仅可查询本企业用户自己的会话。")
147
+    public R<List<RegistrationBatchDetailResponse>> listDraftRegistrationProgress(
148
+            @PathVariable("id") Long conversationId) {
149
+        LoginUser loginUser = requireEnterprise();
150
+        return R.ok(registrationBatchService.listDetailsByConversation(
151
+                loginUser.getUserId(), conversationId));
152
+    }
153
+
154
+    /**
155
+     * 查询指定会话下全部草稿已转成正式订单的列表。
156
+     */
157
+    @GetMapping("/{id}/drafts/orders")
158
+    @Operation(summary = "按会话查询草稿关联订单列表",
159
+            description = "返回该会话下草稿已确认并生成的正式订单;"
160
+                    + "仅「有订单」的草稿会出现在结果中,尚未确认方案的草稿不返回。"
161
+                    + "仅可查询本企业用户自己的会话。")
162
+    public R<List<EmploymentOrderItemResponse>> listDraftOrders(
163
+            @PathVariable("id") Long conversationId) {
164
+        LoginUser loginUser = requireEnterprise();
165
+        return R.ok(employmentOrderService.listByConversation(
166
+                loginUser.getUserId(), conversationId));
167
+    }
168
+
109 169
     /**
110 170
      * 在已有会话内发起 AI 对话(SSE 流式,流结束后落库)。
111 171
      */

+ 45 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/draft/DraftService.java

@@ -24,6 +24,7 @@ import com.huimv.employment.service.draft.dto.DraftFieldResponse;
24 24
 import com.huimv.employment.service.draft.dto.DraftUpdateRequest;
25 25
 import com.huimv.employment.service.draft.dto.McpDraftCalculateResponse;
26 26
 import com.huimv.employment.service.enterprise.EnterpriseService;
27
+import com.huimv.employment.service.conversation.ConversationService;
27 28
 import com.huimv.employment.service.registration.RegistrationBatchService;
28 29
 import org.springframework.dao.DuplicateKeyException;
29 30
 import org.springframework.stereotype.Service;
@@ -32,6 +33,7 @@ import org.springframework.util.StringUtils;
32 33
 
33 34
 import java.time.LocalDate;
34 35
 import java.time.LocalDateTime;
36
+import java.util.ArrayList;
35 37
 import java.util.Arrays;
36 38
 import java.util.Collections;
37 39
 import java.util.HashMap;
@@ -81,6 +83,7 @@ public class DraftService {
81 83
     private final FeEmploymentOrderMapper feEmploymentOrderMapper;
82 84
     private final FeRegistrationBatchMapper feRegistrationBatchMapper;
83 85
     private final EnterpriseService enterpriseService;
86
+    private final ConversationService conversationService;
84 87
     private final DraftCostCalculationEngine draftCostCalculationEngine;
85 88
     private final RegistrationBatchService registrationBatchService;
86 89
     private final ObjectMapper objectMapper;
@@ -91,6 +94,7 @@ public class DraftService {
91 94
                         FeEmploymentOrderMapper feEmploymentOrderMapper,
92 95
                         FeRegistrationBatchMapper feRegistrationBatchMapper,
93 96
                         EnterpriseService enterpriseService,
97
+                        ConversationService conversationService,
94 98
                         DraftCostCalculationEngine draftCostCalculationEngine,
95 99
                         RegistrationBatchService registrationBatchService,
96 100
                         ObjectMapper objectMapper) {
@@ -100,6 +104,7 @@ public class DraftService {
100 104
         this.feEmploymentOrderMapper = feEmploymentOrderMapper;
101 105
         this.feRegistrationBatchMapper = feRegistrationBatchMapper;
102 106
         this.enterpriseService = enterpriseService;
107
+        this.conversationService = conversationService;
103 108
         this.draftCostCalculationEngine = draftCostCalculationEngine;
104 109
         this.registrationBatchService = registrationBatchService;
105 110
         this.objectMapper = objectMapper;
@@ -173,6 +178,46 @@ public class DraftService {
173 178
         return response;
174 179
     }
175 180
 
181
+    /**
182
+     * 按会话查询其下全部草稿的成本测算结果。
183
+     * <p>返回元素结构与 {@link #calculateCostComparison} / {@code GET .../drafts/{id}/cost-comparison} 相同。
184
+     * 字段不足以测算的草稿跳过,不中断整次查询。</p>
185
+     */
186
+    @Transactional(rollbackFor = Exception.class)
187
+    public List<McpDraftCalculateResponse> listCostComparisonsByConversation(Long userId, Long conversationId) {
188
+        conversationService.requireOwned(userId, conversationId);
189
+        FeEnterprise enterprise = enterpriseService.findEnterpriseByUserId(userId);
190
+        if (enterprise == null) {
191
+            throw new BizException(ErrorCode.BAD_REQUEST, "请先完成企业登记");
192
+        }
193
+        List<FeEmploymentDraft> drafts = feEmploymentDraftMapper.selectList(
194
+                new LambdaQueryWrapper<FeEmploymentDraft>()
195
+                        .eq(FeEmploymentDraft::getConversationId, conversationId)
196
+                        .eq(FeEmploymentDraft::getEnterpriseId, enterprise.getId())
197
+                        .orderByAsc(FeEmploymentDraft::getId));
198
+        List<McpDraftCalculateResponse> result = new ArrayList<>();
199
+        if (drafts == null || drafts.isEmpty()) {
200
+            return result;
201
+        }
202
+        LocalDateTime now = LocalDateTime.now();
203
+        for (FeEmploymentDraft draft : drafts) {
204
+            try {
205
+                List<DraftCostCalculationEngine.CalculatedScheme> schemes =
206
+                        draftCostCalculationEngine.calculate(draft);
207
+                if (STATUS_PENDING.equals(draft.getStatus())) {
208
+                    persistCostSnapshots(draft, schemes);
209
+                    updateDraftEstimates(draft, schemes, now);
210
+                }
211
+                McpDraftCalculateResponse response = draftCostCalculationEngine.toApiResponse(schemes);
212
+                response.setDraftId(draft.getId());
213
+                result.add(response);
214
+            } catch (BizException ignored) {
215
+                // 草稿不完整或无法测算时跳过
216
+            }
217
+        }
218
+        return result;
219
+    }
220
+
176 221
     /**
177 222
      * 微信端确认此方案:选定 SCHEME_A/SCHEME_B,转正式订单。
178 223
      * <p>若尚未测算,将自动先测算再确认。</p>

+ 2 - 2
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/draft/McpDraftService.java

@@ -351,8 +351,8 @@ public class McpDraftService {
351 351
         draft.setUserId(conversation.getUserId());
352 352
         draft.setConversationId(conversation.getId());
353 353
         draft.setScenario(resolveScenario(request));
354
-        if (StringUtils.hasText(request.getIntentText())) {
355
-            String title = request.getIntentText().trim();
354
+        if (StringUtils.hasText(conversation.getTitle())) {
355
+            String title = conversation.getTitle().trim();
356 356
             draft.setTitle(title.length() <= 200 ? title : title.substring(0, 200));
357 357
         }
358 358
         draft.setStatus(DB_STATUS_PENDING);

+ 111 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/order/EmploymentOrderService.java

@@ -0,0 +1,111 @@
1
+package com.huimv.employment.service.order;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.huimv.employment.common.exception.BizException;
5
+import com.huimv.employment.common.exception.ErrorCode;
6
+import com.huimv.employment.dao.entity.FeEmploymentDraft;
7
+import com.huimv.employment.dao.entity.FeEmploymentOrder;
8
+import com.huimv.employment.dao.entity.FeEnterprise;
9
+import com.huimv.employment.dao.mapper.FeEmploymentDraftMapper;
10
+import com.huimv.employment.dao.mapper.FeEmploymentOrderMapper;
11
+import com.huimv.employment.service.conversation.ConversationService;
12
+import com.huimv.employment.service.enterprise.EnterpriseService;
13
+import com.huimv.employment.service.order.dto.EmploymentOrderItemResponse;
14
+import org.springframework.stereotype.Service;
15
+
16
+import java.util.ArrayList;
17
+import java.util.Collections;
18
+import java.util.List;
19
+
20
+/**
21
+ * 企业端用工订单查询。
22
+ */
23
+@Service
24
+public class EmploymentOrderService {
25
+
26
+    private final FeEmploymentOrderMapper feEmploymentOrderMapper;
27
+    private final FeEmploymentDraftMapper feEmploymentDraftMapper;
28
+    private final ConversationService conversationService;
29
+    private final EnterpriseService enterpriseService;
30
+
31
+    public EmploymentOrderService(FeEmploymentOrderMapper feEmploymentOrderMapper,
32
+                                  FeEmploymentDraftMapper feEmploymentDraftMapper,
33
+                                  ConversationService conversationService,
34
+                                  EnterpriseService enterpriseService) {
35
+        this.feEmploymentOrderMapper = feEmploymentOrderMapper;
36
+        this.feEmploymentDraftMapper = feEmploymentDraftMapper;
37
+        this.conversationService = conversationService;
38
+        this.enterpriseService = enterpriseService;
39
+    }
40
+
41
+    /**
42
+     * 按会话查询其下草稿已转成正式订单的列表。
43
+     * <p>仅返回已存在订单的草稿;无订单的草稿不出现在结果中。</p>
44
+     */
45
+    public List<EmploymentOrderItemResponse> listByConversation(Long userId, Long conversationId) {
46
+        conversationService.requireOwned(userId, conversationId);
47
+        FeEnterprise enterprise = enterpriseService.findEnterpriseByUserId(userId);
48
+        if (enterprise == null) {
49
+            throw new BizException(ErrorCode.BAD_REQUEST, "请先完成企业登记");
50
+        }
51
+
52
+        List<FeEmploymentDraft> drafts = feEmploymentDraftMapper.selectList(
53
+                new LambdaQueryWrapper<FeEmploymentDraft>()
54
+                        .eq(FeEmploymentDraft::getConversationId, conversationId)
55
+                        .eq(FeEmploymentDraft::getEnterpriseId, enterprise.getId())
56
+                        .orderByAsc(FeEmploymentDraft::getId));
57
+        if (drafts == null || drafts.isEmpty()) {
58
+            return Collections.emptyList();
59
+        }
60
+
61
+        List<Long> draftIds = new ArrayList<>(drafts.size());
62
+        for (FeEmploymentDraft draft : drafts) {
63
+            if (draft.getId() != null) {
64
+                draftIds.add(draft.getId());
65
+            }
66
+        }
67
+        if (draftIds.isEmpty()) {
68
+            return Collections.emptyList();
69
+        }
70
+
71
+        List<FeEmploymentOrder> orders = feEmploymentOrderMapper.selectList(
72
+                new LambdaQueryWrapper<FeEmploymentOrder>()
73
+                        .eq(FeEmploymentOrder::getEnterpriseId, enterprise.getId())
74
+                        .in(FeEmploymentOrder::getDraftId, draftIds)
75
+                        .orderByDesc(FeEmploymentOrder::getCreateTime)
76
+                        .orderByDesc(FeEmploymentOrder::getId));
77
+        if (orders == null || orders.isEmpty()) {
78
+            return Collections.emptyList();
79
+        }
80
+
81
+        List<EmploymentOrderItemResponse> result = new ArrayList<>(orders.size());
82
+        for (FeEmploymentOrder order : orders) {
83
+            result.add(toItem(order));
84
+        }
85
+        return result;
86
+    }
87
+
88
+    private static EmploymentOrderItemResponse toItem(FeEmploymentOrder order) {
89
+        EmploymentOrderItemResponse item = new EmploymentOrderItemResponse();
90
+        item.setDraftId(order.getDraftId());
91
+        item.setOrderId(order.getId());
92
+        item.setOrderNo(order.getOrderNo());
93
+        item.setTitle(order.getTitle());
94
+        item.setWorkerCount(order.getWorkerCount());
95
+        item.setWorkDays(order.getWorkDays());
96
+        item.setWorkType(order.getWorkType());
97
+        item.setWorkLocation(order.getWorkLocation());
98
+        item.setWorkStartDate(order.getWorkStartDate());
99
+        item.setWorkEndDate(order.getWorkEndDate());
100
+        item.setOrderStatus(order.getOrderStatus());
101
+        item.setCurrentStepCode(order.getCurrentStepCode());
102
+        item.setRegisteredCount(order.getRegisteredCount());
103
+        item.setPendingCount(order.getPendingCount());
104
+        item.setAbnormalCount(order.getAbnormalCount());
105
+        item.setTotalOutflow(order.getTotalOutflow());
106
+        item.setSelectedPlanId(order.getSelectedPlanId());
107
+        item.setCreateTime(order.getCreateTime());
108
+        item.setUpdateTime(order.getUpdateTime());
109
+        return item;
110
+    }
111
+}

+ 231 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/order/dto/EmploymentOrderItemResponse.java

@@ -0,0 +1,231 @@
1
+package com.huimv.employment.service.order.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonProperty;
4
+import io.swagger.v3.oas.annotations.media.Schema;
5
+
6
+import java.math.BigDecimal;
7
+import java.time.LocalDate;
8
+import java.time.LocalDateTime;
9
+
10
+@Schema(description = "用工订单列表项")
11
+public class EmploymentOrderItemResponse {
12
+
13
+    @JsonProperty("draft_id")
14
+    @Schema(description = "关联草稿 ID")
15
+    private Long draftId;
16
+
17
+    @JsonProperty("order_id")
18
+    @Schema(description = "订单主键 ID")
19
+    private Long orderId;
20
+
21
+    @JsonProperty("order_no")
22
+    @Schema(description = "订单编号", example = "ord_123")
23
+    private String orderNo;
24
+
25
+    @Schema(description = "订单标题")
26
+    private String title;
27
+
28
+    @JsonProperty("worker_count")
29
+    @Schema(description = "用工人数")
30
+    private Integer workerCount;
31
+
32
+    @JsonProperty("work_days")
33
+    @Schema(description = "工作天数")
34
+    private Integer workDays;
35
+
36
+    @JsonProperty("work_type")
37
+    @Schema(description = "岗位类型")
38
+    private String workType;
39
+
40
+    @JsonProperty("work_location")
41
+    @Schema(description = "工作地点")
42
+    private String workLocation;
43
+
44
+    @JsonProperty("work_start_date")
45
+    private LocalDate workStartDate;
46
+
47
+    @JsonProperty("work_end_date")
48
+    private LocalDate workEndDate;
49
+
50
+    @JsonProperty("order_status")
51
+    @Schema(description = "订单状态", example = "confirmed")
52
+    private String orderStatus;
53
+
54
+    @JsonProperty("current_step_code")
55
+    @Schema(description = "当前流程步骤")
56
+    private String currentStepCode;
57
+
58
+    @JsonProperty("registered_count")
59
+    private Integer registeredCount;
60
+
61
+    @JsonProperty("pending_count")
62
+    private Integer pendingCount;
63
+
64
+    @JsonProperty("abnormal_count")
65
+    private Integer abnormalCount;
66
+
67
+    @JsonProperty("total_outflow")
68
+    @Schema(description = "企业总支出")
69
+    private BigDecimal totalOutflow;
70
+
71
+    @JsonProperty("selected_plan_id")
72
+    private Long selectedPlanId;
73
+
74
+    @JsonProperty("create_time")
75
+    private LocalDateTime createTime;
76
+
77
+    @JsonProperty("update_time")
78
+    private LocalDateTime updateTime;
79
+
80
+    public Long getDraftId() {
81
+        return draftId;
82
+    }
83
+
84
+    public void setDraftId(Long draftId) {
85
+        this.draftId = draftId;
86
+    }
87
+
88
+    public Long getOrderId() {
89
+        return orderId;
90
+    }
91
+
92
+    public void setOrderId(Long orderId) {
93
+        this.orderId = orderId;
94
+    }
95
+
96
+    public String getOrderNo() {
97
+        return orderNo;
98
+    }
99
+
100
+    public void setOrderNo(String orderNo) {
101
+        this.orderNo = orderNo;
102
+    }
103
+
104
+    public String getTitle() {
105
+        return title;
106
+    }
107
+
108
+    public void setTitle(String title) {
109
+        this.title = title;
110
+    }
111
+
112
+    public Integer getWorkerCount() {
113
+        return workerCount;
114
+    }
115
+
116
+    public void setWorkerCount(Integer workerCount) {
117
+        this.workerCount = workerCount;
118
+    }
119
+
120
+    public Integer getWorkDays() {
121
+        return workDays;
122
+    }
123
+
124
+    public void setWorkDays(Integer workDays) {
125
+        this.workDays = workDays;
126
+    }
127
+
128
+    public String getWorkType() {
129
+        return workType;
130
+    }
131
+
132
+    public void setWorkType(String workType) {
133
+        this.workType = workType;
134
+    }
135
+
136
+    public String getWorkLocation() {
137
+        return workLocation;
138
+    }
139
+
140
+    public void setWorkLocation(String workLocation) {
141
+        this.workLocation = workLocation;
142
+    }
143
+
144
+    public LocalDate getWorkStartDate() {
145
+        return workStartDate;
146
+    }
147
+
148
+    public void setWorkStartDate(LocalDate workStartDate) {
149
+        this.workStartDate = workStartDate;
150
+    }
151
+
152
+    public LocalDate getWorkEndDate() {
153
+        return workEndDate;
154
+    }
155
+
156
+    public void setWorkEndDate(LocalDate workEndDate) {
157
+        this.workEndDate = workEndDate;
158
+    }
159
+
160
+    public String getOrderStatus() {
161
+        return orderStatus;
162
+    }
163
+
164
+    public void setOrderStatus(String orderStatus) {
165
+        this.orderStatus = orderStatus;
166
+    }
167
+
168
+    public String getCurrentStepCode() {
169
+        return currentStepCode;
170
+    }
171
+
172
+    public void setCurrentStepCode(String currentStepCode) {
173
+        this.currentStepCode = currentStepCode;
174
+    }
175
+
176
+    public Integer getRegisteredCount() {
177
+        return registeredCount;
178
+    }
179
+
180
+    public void setRegisteredCount(Integer registeredCount) {
181
+        this.registeredCount = registeredCount;
182
+    }
183
+
184
+    public Integer getPendingCount() {
185
+        return pendingCount;
186
+    }
187
+
188
+    public void setPendingCount(Integer pendingCount) {
189
+        this.pendingCount = pendingCount;
190
+    }
191
+
192
+    public Integer getAbnormalCount() {
193
+        return abnormalCount;
194
+    }
195
+
196
+    public void setAbnormalCount(Integer abnormalCount) {
197
+        this.abnormalCount = abnormalCount;
198
+    }
199
+
200
+    public BigDecimal getTotalOutflow() {
201
+        return totalOutflow;
202
+    }
203
+
204
+    public void setTotalOutflow(BigDecimal totalOutflow) {
205
+        this.totalOutflow = totalOutflow;
206
+    }
207
+
208
+    public Long getSelectedPlanId() {
209
+        return selectedPlanId;
210
+    }
211
+
212
+    public void setSelectedPlanId(Long selectedPlanId) {
213
+        this.selectedPlanId = selectedPlanId;
214
+    }
215
+
216
+    public LocalDateTime getCreateTime() {
217
+        return createTime;
218
+    }
219
+
220
+    public void setCreateTime(LocalDateTime createTime) {
221
+        this.createTime = createTime;
222
+    }
223
+
224
+    public LocalDateTime getUpdateTime() {
225
+        return updateTime;
226
+    }
227
+
228
+    public void setUpdateTime(LocalDateTime updateTime) {
229
+        this.updateTime = updateTime;
230
+    }
231
+}

+ 34 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/registration/RegistrationBatchService.java

@@ -17,6 +17,7 @@ import com.huimv.employment.dao.mapper.FeWorkerRegistrationMapper;
17 17
 import com.huimv.employment.dao.mapper.FeWorkflowDefinitionMapper;
18 18
 import com.huimv.employment.dao.mapper.FeWorkflowStepMapper;
19 19
 import com.huimv.employment.service.config.RegistrationQrProperties;
20
+import com.huimv.employment.service.conversation.ConversationService;
20 21
 import com.huimv.employment.service.enterprise.EnterpriseService;
21 22
 import com.huimv.employment.service.registration.dto.RegistrationBatchDetailResponse;
22 23
 import com.huimv.employment.service.registration.dto.RegistrationBatchProgressResponse;
@@ -76,6 +77,7 @@ public class RegistrationBatchService {
76 77
     private final FeWorkflowDefinitionMapper feWorkflowDefinitionMapper;
77 78
     private final FeWorkflowStepMapper feWorkflowStepMapper;
78 79
     private final EnterpriseService enterpriseService;
80
+    private final ConversationService conversationService;
79 81
     private final WeChatMiniAppService weChatMiniAppService;
80 82
     private final RegistrationQrProperties qrProperties;
81 83
 
@@ -86,6 +88,7 @@ public class RegistrationBatchService {
86 88
                                     FeWorkflowDefinitionMapper feWorkflowDefinitionMapper,
87 89
                                     FeWorkflowStepMapper feWorkflowStepMapper,
88 90
                                     EnterpriseService enterpriseService,
91
+                                    ConversationService conversationService,
89 92
                                     WeChatMiniAppService weChatMiniAppService,
90 93
                                     RegistrationQrProperties qrProperties) {
91 94
         this.feRegistrationBatchMapper = feRegistrationBatchMapper;
@@ -95,6 +98,7 @@ public class RegistrationBatchService {
95 98
         this.feWorkflowDefinitionMapper = feWorkflowDefinitionMapper;
96 99
         this.feWorkflowStepMapper = feWorkflowStepMapper;
97 100
         this.enterpriseService = enterpriseService;
101
+        this.conversationService = conversationService;
98 102
         this.weChatMiniAppService = weChatMiniAppService;
99 103
         this.qrProperties = qrProperties;
100 104
     }
@@ -180,6 +184,35 @@ public class RegistrationBatchService {
180 184
         return toDetailResponse(batch, order, draft);
181 185
     }
182 186
 
187
+    /**
188
+     * 按会话查询其下全部草稿的登记进度详情。
189
+     * <p>元素结构与 {@link #getDetailByDraftId} 相同;尚未确认方案、无批次的草稿跳过。</p>
190
+     */
191
+    public List<RegistrationBatchDetailResponse> listDetailsByConversation(Long userId, Long conversationId) {
192
+        conversationService.requireOwned(userId, conversationId);
193
+        FeEnterprise enterprise = enterpriseService.findEnterpriseByUserId(userId);
194
+        if (enterprise == null) {
195
+            throw new BizException(ErrorCode.NOT_FOUND, "企业信息不存在");
196
+        }
197
+        List<FeEmploymentDraft> drafts = feEmploymentDraftMapper.selectList(
198
+                new LambdaQueryWrapper<FeEmploymentDraft>()
199
+                        .eq(FeEmploymentDraft::getConversationId, conversationId)
200
+                        .eq(FeEmploymentDraft::getEnterpriseId, enterprise.getId())
201
+                        .orderByAsc(FeEmploymentDraft::getId));
202
+        List<RegistrationBatchDetailResponse> result = new ArrayList<>();
203
+        if (drafts == null || drafts.isEmpty()) {
204
+            return result;
205
+        }
206
+        for (FeEmploymentDraft draft : drafts) {
207
+            try {
208
+                result.add(getDetailByDraftId(userId, draft.getId()));
209
+            } catch (BizException ignored) {
210
+                // 未确认方案或无批次时跳过
211
+            }
212
+        }
213
+        return result;
214
+    }
215
+
183 216
     /**
184 217
      * 企业端登记进度详情:按批次主键 ID 或业务编号(如 bat_1)。
185 218
      */
@@ -215,6 +248,7 @@ public class RegistrationBatchService {
215 248
         String batchNo = batch.getBatchNo();
216 249
 
217 250
         RegistrationBatchDetailResponse response = new RegistrationBatchDetailResponse();
251
+        response.setDraftId(draft != null ? draft.getId() : order.getDraftId());
218 252
         response.setBatchId(batch.getId());
219 253
         response.setBatchNo(batchNo);
220 254
         response.setTitle(title);

+ 12 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/registration/dto/RegistrationBatchDetailResponse.java

@@ -10,6 +10,10 @@ import java.util.List;
10 10
 @Schema(description = "登记批次详情(进度管理)")
11 11
 public class RegistrationBatchDetailResponse {
12 12
 
13
+    @JsonProperty("draft_id")
14
+    @Schema(description = "关联草稿 ID")
15
+    private Long draftId;
16
+
13 17
     @JsonProperty("batch_id")
14 18
     @Schema(description = "批次主键 ID")
15 19
     private Long batchId;
@@ -69,6 +73,14 @@ public class RegistrationBatchDetailResponse {
69 73
     @Schema(description = "人员列表")
70 74
     private List<RegistrationBatchWorkerItemResponse> workers = new ArrayList<>();
71 75
 
76
+    public Long getDraftId() {
77
+        return draftId;
78
+    }
79
+
80
+    public void setDraftId(Long draftId) {
81
+        this.draftId = draftId;
82
+    }
83
+
72 84
     public Long getBatchId() {
73 85
         return batchId;
74 86
     }