|
|
@@ -48,7 +48,7 @@ import java.util.UUID;
|
|
48
|
48
|
|
|
49
|
49
|
/**
|
|
50
|
50
|
* MCP 草稿接口业务(对齐交互规则 v2 §6.3)。
|
|
51
|
|
- * <p>通过 {@code X-Session-Id}(= conversation_no)自动关联会话与草稿,Agent 无需传递 draft_id。</p>
|
|
|
51
|
+ * <p>通过 session_id 关联会话;update/info/calculate/submit 须传入 draft_id,并校验草稿归属当前会话。</p>
|
|
52
|
52
|
*/
|
|
53
|
53
|
@Service
|
|
54
|
54
|
public class McpDraftService {
|
|
|
@@ -134,9 +134,9 @@ public class McpDraftService {
|
|
134
|
134
|
@Transactional(rollbackFor = Exception.class)
|
|
135
|
135
|
public McpDraftUpdateResponse update(String sessionId, McpDraftUpdateRequest request) {
|
|
136
|
136
|
FeConversation conversation = conversationService.requireBySessionId(sessionId);
|
|
137
|
|
- FeEmploymentDraft draft = requirePendingDraft(conversation.getId());
|
|
|
137
|
+ FeEmploymentDraft draft = requireDraftInConversation(conversation.getId(), request.getDraftId());
|
|
138
|
138
|
if (!isDbEditable(draft)) {
|
|
139
|
|
- return banResponse();
|
|
|
139
|
+ return banResponse(draft.getId());
|
|
140
|
140
|
}
|
|
141
|
141
|
try {
|
|
142
|
142
|
LocalDateTime now = LocalDateTime.now();
|
|
|
@@ -145,38 +145,39 @@ public class McpDraftService {
|
|
145
|
145
|
feEmploymentDraftMapper.updateById(draft);
|
|
146
|
146
|
syncFieldMetadata(draft.getId(), updatedKeys, draft, now, SOURCE_AI_INFERRED);
|
|
147
|
147
|
recalculateMissingFields(draft, now);
|
|
148
|
|
- return successResponse();
|
|
|
148
|
+ return successResponse(draft.getId());
|
|
149
|
149
|
} catch (BizException ex) {
|
|
150
|
|
- log.debug("MCP 草稿更新失败 sessionId={}: {}", sessionId, ex.getMessage());
|
|
|
150
|
+ log.debug("MCP 草稿更新失败 sessionId={} draftId={}: {}",
|
|
|
151
|
+ sessionId, request.getDraftId(), ex.getMessage());
|
|
151
|
152
|
McpDraftUpdateResponse response = new McpDraftUpdateResponse();
|
|
|
153
|
+ response.setDraftId(request.getDraftId());
|
|
152
|
154
|
response.setStatus(MCP_UPDATE_FAIL);
|
|
153
|
155
|
return response;
|
|
154
|
156
|
}
|
|
155
|
157
|
}
|
|
156
|
158
|
|
|
157
|
|
- public McpDraftInfoResponse getInfo(String sessionId) {
|
|
|
159
|
+ public McpDraftInfoResponse getInfo(String sessionId, Long draftId) {
|
|
158
|
160
|
FeConversation conversation = conversationService.requireBySessionId(sessionId);
|
|
159
|
|
- FeEmploymentDraft draft = findLatestDraft(conversation.getId());
|
|
160
|
|
- if (draft == null) {
|
|
161
|
|
- throw new BizException(ErrorCode.NOT_FOUND, "当前会话暂无草稿,请先调用 generate");
|
|
162
|
|
- }
|
|
|
161
|
+ FeEmploymentDraft draft = requireDraftInConversation(conversation.getId(), draftId);
|
|
163
|
162
|
return toInfoResponse(draft);
|
|
164
|
163
|
}
|
|
165
|
164
|
|
|
166
|
165
|
@Transactional(rollbackFor = Exception.class)
|
|
167
|
|
- public McpDraftCalculateResponse calculate(String sessionId) {
|
|
|
166
|
+ public McpDraftCalculateResponse calculate(String sessionId, Long draftId) {
|
|
168
|
167
|
FeConversation conversation = conversationService.requireBySessionId(sessionId);
|
|
169
|
|
- FeEmploymentDraft draft = requireReadyDraft(conversation.getId());
|
|
|
168
|
+ FeEmploymentDraft draft = requireReadyDraft(conversation.getId(), draftId);
|
|
170
|
169
|
List<DraftCostCalculationEngine.CalculatedScheme> schemes = draftCostCalculationEngine.calculate(draft);
|
|
171
|
170
|
persistCostSnapshots(draft, schemes);
|
|
172
|
171
|
updateDraftEstimates(draft, schemes, LocalDateTime.now());
|
|
173
|
|
- return draftCostCalculationEngine.toApiResponse(schemes);
|
|
|
172
|
+ McpDraftCalculateResponse response = draftCostCalculationEngine.toApiResponse(schemes);
|
|
|
173
|
+ response.setDraftId(draft.getId());
|
|
|
174
|
+ return response;
|
|
174
|
175
|
}
|
|
175
|
176
|
|
|
176
|
177
|
@Transactional(rollbackFor = Exception.class)
|
|
177
|
178
|
public McpDraftSubmitResponse submit(String sessionId, McpDraftSubmitRequest request) {
|
|
178
|
179
|
FeConversation conversation = conversationService.requireBySessionId(sessionId);
|
|
179
|
|
- FeEmploymentDraft draft = requireReadyDraft(conversation.getId());
|
|
|
180
|
+ FeEmploymentDraft draft = requireReadyDraft(conversation.getId(), request.getDraftId());
|
|
180
|
181
|
String schemeCode = request.getSelectedSchemeCode().trim().toUpperCase();
|
|
181
|
182
|
if (!draftCostCalculationEngine.isAllowedSchemeCode(schemeCode)) {
|
|
182
|
183
|
throw new BizException(ErrorCode.BAD_REQUEST, "不支持的方案编码:" + request.getSelectedSchemeCode());
|
|
|
@@ -210,14 +211,34 @@ public class McpDraftService {
|
|
210
|
211
|
FeRegistrationBatch batch = registrationBatchService.createForOrder(order, draft, now);
|
|
211
|
212
|
|
|
212
|
213
|
McpDraftSubmitResponse response = new McpDraftSubmitResponse();
|
|
|
214
|
+ response.setDraftId(draft.getId());
|
|
213
|
215
|
response.setStatus(SUBMIT_SUCCESS);
|
|
214
|
216
|
response.setOrderId(order.getOrderNo());
|
|
215
|
217
|
response.setBatchId(batch.getBatchNo());
|
|
216
|
218
|
return response;
|
|
217
|
219
|
}
|
|
218
|
220
|
|
|
219
|
|
- private FeEmploymentDraft requireReadyDraft(Long conversationId) {
|
|
220
|
|
- FeEmploymentDraft draft = requirePendingDraft(conversationId);
|
|
|
221
|
+ private FeEmploymentDraft requireDraftInConversation(Long conversationId, Long draftId) {
|
|
|
222
|
+ if (draftId == null) {
|
|
|
223
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "draft_id 不能为空");
|
|
|
224
|
+ }
|
|
|
225
|
+ FeEmploymentDraft draft = feEmploymentDraftMapper.selectById(draftId);
|
|
|
226
|
+ if (draft == null || !conversationId.equals(draft.getConversationId())) {
|
|
|
227
|
+ throw new BizException(ErrorCode.NOT_FOUND, "草稿不存在或不属于当前会话");
|
|
|
228
|
+ }
|
|
|
229
|
+ return draft;
|
|
|
230
|
+ }
|
|
|
231
|
+
|
|
|
232
|
+ private FeEmploymentDraft requirePendingDraft(Long conversationId, Long draftId) {
|
|
|
233
|
+ FeEmploymentDraft draft = requireDraftInConversation(conversationId, draftId);
|
|
|
234
|
+ if (!DB_STATUS_PENDING.equals(draft.getStatus())) {
|
|
|
235
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "草稿已提交,不可继续操作");
|
|
|
236
|
+ }
|
|
|
237
|
+ return draft;
|
|
|
238
|
+ }
|
|
|
239
|
+
|
|
|
240
|
+ private FeEmploymentDraft requireReadyDraft(Long conversationId, Long draftId) {
|
|
|
241
|
+ FeEmploymentDraft draft = requirePendingDraft(conversationId, draftId);
|
|
221
|
242
|
List<String> missing = parseMissingFields(draft.getMissingFields());
|
|
222
|
243
|
if (!missing.isEmpty()) {
|
|
223
|
244
|
throw new BizException(ErrorCode.BAD_REQUEST,
|
|
|
@@ -371,21 +392,6 @@ public class McpDraftService {
|
|
371
|
392
|
.last("LIMIT 1"));
|
|
372
|
393
|
}
|
|
373
|
394
|
|
|
374
|
|
- private FeEmploymentDraft findLatestDraft(Long conversationId) {
|
|
375
|
|
- return feEmploymentDraftMapper.selectOne(new LambdaQueryWrapper<FeEmploymentDraft>()
|
|
376
|
|
- .eq(FeEmploymentDraft::getConversationId, conversationId)
|
|
377
|
|
- .orderByDesc(FeEmploymentDraft::getCreateTime)
|
|
378
|
|
- .last("LIMIT 1"));
|
|
379
|
|
- }
|
|
380
|
|
-
|
|
381
|
|
- private FeEmploymentDraft requirePendingDraft(Long conversationId) {
|
|
382
|
|
- FeEmploymentDraft draft = findPendingDraft(conversationId);
|
|
383
|
|
- if (draft == null) {
|
|
384
|
|
- throw new BizException(ErrorCode.NOT_FOUND, "当前会话暂无待完善草稿,请先调用 generate");
|
|
385
|
|
- }
|
|
386
|
|
- return draft;
|
|
387
|
|
- }
|
|
388
|
|
-
|
|
389
|
395
|
private Set<String> applyMcpFields(FeEmploymentDraft draft, Map<String, Object> fields) {
|
|
390
|
396
|
if (fields == null || fields.isEmpty()) {
|
|
391
|
397
|
throw new BizException(ErrorCode.BAD_REQUEST, "fields 不能为空");
|
|
|
@@ -510,6 +516,7 @@ public class McpDraftService {
|
|
510
|
516
|
List<String> missing = parseMissingFields(draft.getMissingFields());
|
|
511
|
517
|
boolean editable = isDbEditable(draft);
|
|
512
|
518
|
McpDraftInfoResponse response = new McpDraftInfoResponse();
|
|
|
519
|
+ response.setDraftId(draft.getId());
|
|
513
|
520
|
response.setData(toDataMap(draft));
|
|
514
|
521
|
response.setMissingFields(missing);
|
|
515
|
522
|
response.setEditable(editable);
|
|
|
@@ -552,14 +559,16 @@ public class McpDraftService {
|
|
552
|
559
|
return DB_STATUS_PENDING.equals(draft.getStatus());
|
|
553
|
560
|
}
|
|
554
|
561
|
|
|
555
|
|
- private McpDraftUpdateResponse successResponse() {
|
|
|
562
|
+ private McpDraftUpdateResponse successResponse(Long draftId) {
|
|
556
|
563
|
McpDraftUpdateResponse response = new McpDraftUpdateResponse();
|
|
|
564
|
+ response.setDraftId(draftId);
|
|
557
|
565
|
response.setStatus(MCP_UPDATE_SUCCESS);
|
|
558
|
566
|
return response;
|
|
559
|
567
|
}
|
|
560
|
568
|
|
|
561
|
|
- private McpDraftUpdateResponse banResponse() {
|
|
|
569
|
+ private McpDraftUpdateResponse banResponse(Long draftId) {
|
|
562
|
570
|
McpDraftUpdateResponse response = new McpDraftUpdateResponse();
|
|
|
571
|
+ response.setDraftId(draftId);
|
|
563
|
572
|
response.setStatus(MCP_UPDATE_BAN);
|
|
564
|
573
|
return response;
|
|
565
|
574
|
}
|