fix(server): 补齐服务承诺字段的管理端保存与回显
build / build-push (push) Failing after 2m7s

上一批只落了实体与 oapi 出参,管理端 save 入参与编辑回显没接上,
导致 front 录入的 6 个字段被 Jackson 静默丢弃。

- SvcModels.SvcItemSaveRequest 补 7 字段(含 @Size 校验,与实体列长一致)
- SvcModels.SvcItemEditVo 补 7 字段 + of() 映射
- ServiceItemService.adminSave 补 7 行赋值(空串归一 null)
- 新增 SvcItemPromiseFieldsTest:反序列化字段名对齐 + EditVo 映射(3 例)
- .gitignore 排除 local-debug/(本地探针,不入库)
This commit is contained in:
chenxing
2026-10-20 18:13:00 +08:00
parent c1bcbf2f88
commit af760913fa
4 changed files with 134 additions and 0 deletions
+3
View File
@@ -3,3 +3,6 @@ target/
.idea/
*.iml
.DS_Store
# 本地调试脚本与探针输出(不入库)
local-debug/
@@ -174,6 +174,26 @@ public final class SvcModels {
private String flowGuid;
@Size(max = 500)
private String applyUrl;
// ===== 服务承诺(P1-1):管理端录入,全部可空 =====
/** 协办部门(顿号分隔文本快照) */
@Size(max = 500)
private String coopDepts;
/** 经办人员 */
@Size(max = 64)
private String handlerName;
/** 办理时间(办公时段,区别于办理时限) */
@Size(max = 200)
private String handleTimeDesc;
/** 跑现场次数(0=零跑动) */
private Integer runCount;
/** 是否快递送达:0 否 / 1 是(到付)/ 2 是(学校承担) */
private Integer expressFlag;
/** 收费标准 */
@Size(max = 200)
private String feeDesc;
/** 收费依据 */
@Size(max = 200)
private String feeBasis;
private Integer sortNo;
/** 指南附件 id 列表(保存时全量重绑 topic_type=svc_item_guide) */
private List<Integer> attachmentIds;
@@ -207,6 +227,14 @@ public final class SvcModels {
private String formCode;
private String flowGuid;
private String applyUrl;
// ===== 服务承诺(P1-1):编辑页回显,与 SvcItemSaveRequest 同名 =====
private String coopDepts;
private String handlerName;
private String handleTimeDesc;
private Integer runCount;
private Integer expressFlag;
private String feeDesc;
private String feeBasis;
private Integer sortNo;
private Integer status;
private LocalDateTime publishTime;
@@ -241,6 +269,13 @@ public final class SvcModels {
vo.setFormCode(item.getFormCode());
vo.setFlowGuid(item.getFlowGuid());
vo.setApplyUrl(item.getApplyUrl());
vo.setCoopDepts(item.getCoopDepts());
vo.setHandlerName(item.getHandlerName());
vo.setHandleTimeDesc(item.getHandleTimeDesc());
vo.setRunCount(item.getRunCount());
vo.setExpressFlag(item.getExpressFlag());
vo.setFeeDesc(item.getFeeDesc());
vo.setFeeBasis(item.getFeeBasis());
vo.setSortNo(item.getSortNo());
vo.setStatus(item.getStatus());
vo.setPublishTime(item.getPublishTime());
@@ -199,6 +199,14 @@ public class ServiceItemService {
item.setFormCode(emptyToNull(req.getFormCode()));
item.setFlowGuid(emptyToNull(req.getFlowGuid()));
item.setApplyUrl(applyUrl);
// 服务承诺(P1-1):空串归一为 null,避免库里出现 "" 与 null 两种空值
item.setCoopDepts(emptyToNull(req.getCoopDepts()));
item.setHandlerName(emptyToNull(req.getHandlerName()));
item.setHandleTimeDesc(emptyToNull(req.getHandleTimeDesc()));
item.setRunCount(req.getRunCount());
item.setExpressFlag(req.getExpressFlag());
item.setFeeDesc(emptyToNull(req.getFeeDesc()));
item.setFeeBasis(emptyToNull(req.getFeeBasis()));
item.setSortNo(req.getSortNo() == null ? 0 : req.getSortNo());
item = itemRepository.save(item);
@@ -0,0 +1,88 @@
package com.yunhai.svchall.model.svc;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.svchall.entity.ServiceItem;
import com.yunhai.svchall.model.svc.SvcModels.SvcItemEditVo;
import com.yunhai.svchall.model.svc.SvcModels.SvcItemSaveRequest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* 服务承诺字段(P1-1)的入参与回显契约测试。
* <p>
* 守两件事:
* ① 管理端前端用的字段名 → {@link SvcItemSaveRequest} 能反序列化进来(Jackson 默认忽略未知字段,
* 名字写错不会报错、只会静默丢数据,必须显式测);
* ② {@link SvcItemEditVo#of} 能把实体上的 7 个字段原样映射到编辑页回显。
* <p>
* 不测 adminSave 的落库:那需要 mock repository,价值低于上面两条(字段名对齐是最易错点)。
*/
class SvcItemPromiseFieldsTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
@DisplayName("SaveRequest 能接住前端传来的 7 个承诺字段(字段名不得漂移)")
void saveRequestDeserializesPromiseFields() throws Exception {
String json = "{"
+ "\"coopDepts\":\"组织部、教务处\","
+ "\"handlerName\":\"田老师\","
+ "\"handleTimeDesc\":\"工作日 08:00-11:45\","
+ "\"runCount\":1,"
+ "\"expressFlag\":2,"
+ "\"feeDesc\":\"50 元\","
+ "\"feeBasis\":\"浙价费〔2020〕1 号\""
+ "}";
SvcItemSaveRequest req = MAPPER.readValue(json, SvcItemSaveRequest.class);
assertEquals("组织部、教务处", req.getCoopDepts());
assertEquals("田老师", req.getHandlerName());
assertEquals("工作日 08:00-11:45", req.getHandleTimeDesc());
assertEquals(1, req.getRunCount());
assertEquals(2, req.getExpressFlag());
assertEquals("50 元", req.getFeeDesc());
assertEquals("浙价费〔2020〕1 号", req.getFeeBasis());
}
@Test
@DisplayName("EditVo.of 把实体 7 个承诺字段映射到回显")
void editVoMapsPromiseFields() {
ServiceItem item = new ServiceItem();
item.setId(12);
item.setSiteCode("yhms");
item.setItemCode("teacher-onboard");
item.setTitle("教师入职办理");
item.setCoopDepts("组织部、教务处");
item.setHandlerName("田老师");
item.setHandleTimeDesc("工作日 08:00-11:45");
item.setRunCount(0);
item.setExpressFlag(1);
item.setFeeDesc("无");
item.setFeeBasis(null);
SvcItemEditVo vo = SvcItemEditVo.of(item);
assertEquals("组织部、教务处", vo.getCoopDepts());
assertEquals("田老师", vo.getHandlerName());
assertEquals("工作日 08:00-11:45", vo.getHandleTimeDesc());
assertEquals(0, vo.getRunCount());
assertEquals(1, vo.getExpressFlag());
assertEquals("无", vo.getFeeDesc());
assertNull(vo.getFeeBasis());
}
@Test
@DisplayName("承诺字段全空时 SaveRequest 不报错(全部可空)")
void saveRequestAllowsNulls() throws Exception {
SvcItemSaveRequest req = MAPPER.readValue("{\"title\":\"x\"}", SvcItemSaveRequest.class);
assertNull(req.getCoopDepts());
assertNull(req.getRunCount());
assertNull(req.getExpressFlag());
assertNull(req.getFeeDesc());
}
}