xsgz 提交前预检必填字段,避免含糊失败

学校请假表单由后台动态配置,可能包含本平台无法提供值的必填字段
(如"是否离校"单选、请假类型细分下拉)。原先这类情况会直接提交,
由学校端返回业务错误,用户看到的提示不明确。

新增 checkXsgzUnfilledRequired:提交前检查所有 required 字段是否已有值
(空串/null 均视为缺失),有缺失则不发请求,直接提示
「该校请假表单还有必填项无法自动填写:X、Y,请前往学校学工系统办理」,
并写入日志。请假与销假两条路径都已接入。

新增 5 个单测覆盖:有值不报、无值报字段名、空串算缺失、
无 fieldName 回退 fieldId、非必填不报。
This commit is contained in:
becomer
2026-10-17 17:46:49 +08:00
parent c8ff086b7b
commit e7c9b06056
3 changed files with 98 additions and 0 deletions
+9
View File
@@ -514,6 +514,15 @@ func executeXsgzLeaveCancel(c *fiber.Ctx, user *model.SysUser, req *ExecuteLeave
f["value"] = req.Address f["value"] = req.Address
} }
} }
// 提交前预检:必填项无法自动填写时明确指出,避免含糊失败
if missing := checkXsgzUnfilledRequired(formFields); len(missing) > 0 {
msg := "该校销假表单还有必填项无法自动填写:" + strings.Join(missing, "、") +
",请前往学校学工系统办理"
slog.Warn("xsgz 销假表单存在未填必填项", "userId", user.ID, "missing", missing)
saveLog(false, msg)
sendEvent("task_error", map[string]interface{}{"status": "error", "message": msg})
return
}
body, err := sess.XsgzSubmitCancel(c.Context(), cpd.XsgzSubmitPayload{ body, err := sess.XsgzSubmitCancel(c.Context(), cpd.XsgzSubmitPayload{
ProjectID: req.ProjectID, ProjectID: req.ProjectID,
+38
View File
@@ -593,6 +593,17 @@ func executeXsgzLeaveApply(c *fiber.Ctx, user *model.SysUser, req *ExecuteLeaveA
} }
fillXsgzFieldValues(formFields, req) fillXsgzFieldValues(formFields, req)
// 提交前预检:学校表单可能包含本平台无法提供值的必填字段
// (如"是否离校"单选)。与其提交后得到含糊失败,不如明确指出。
if missing := checkXsgzUnfilledRequired(formFields); len(missing) > 0 {
msg := "该校请假表单还有必填项无法自动填写:" + strings.Join(missing, "、") +
",请前往学校学工系统办理"
slog.Warn("xsgz 表单存在未填必填项", "userId", user.ID, "missing", missing)
saveLog(false, msg)
sendEvent("task_error", map[string]interface{}{"status": "error", "message": msg})
return
}
payload := cpd.XsgzSubmitPayload{ payload := cpd.XsgzSubmitPayload{
ProjectID: req.XsgzProjectID, ProjectID: req.XsgzProjectID,
RequestType: "LEAVE", RequestType: "LEAVE",
@@ -685,3 +696,30 @@ func mapStr(m map[string]interface{}, k string) string {
} }
return "" return ""
} }
// checkXsgzUnfilledRequired 返回仍未填值的必填字段名称列表。
//
// 学校表单由后台配置,可能包含本平台无法提供值的必填字段
// (如"是否离校"单选、"请假类型细分"下拉)。与其让提交到学校端后
// 得到含糊的失败,不如在提交前明确告知缺哪些字段。
func checkXsgzUnfilledRequired(fields []map[string]interface{}) []string {
var missing []string
for _, f := range fields {
required, _ := f["required"].(bool)
if !required {
continue
}
if v, ok := f["value"]; ok && v != nil && fmt.Sprintf("%v", v) != "" {
continue
}
name := mapStr(f, "fieldName")
if name == "" {
name = mapStr(f, "fieldId")
}
if name == "" {
name = "未命名字段"
}
missing = append(missing, name)
}
return missing
}
@@ -52,3 +52,54 @@ func TestFillXsgzFieldValues(t *testing.T) {
fillXsgzFieldValues([]map[string]interface{}{{"fieldId": "f1"}}, &ExecuteLeaveApplyRequest{}) fillXsgzFieldValues([]map[string]interface{}{{"fieldId": "f1"}}, &ExecuteLeaveApplyRequest{})
}) })
} }
// checkXsgzUnfilledRequired:找出必填但无值的字段,用于提交前预检。
func TestCheckXsgzUnfilledRequired(t *testing.T) {
t.Run("必填有值时不报告", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "请假原因", "required": true, "value": "感冒"},
}
if got := checkXsgzUnfilledRequired(fields); len(got) != 0 {
t.Fatalf("不应报告缺失,实际: %v", got)
}
})
t.Run("必填无值时报告字段名", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "是否离校", "required": true},
{"fieldId": "f2", "fieldName": "备注", "required": false},
}
got := checkXsgzUnfilledRequired(fields)
if len(got) != 1 || got[0] != "是否离校" {
t.Fatalf("应只报告「是否离校」,实际: %v", got)
}
})
t.Run("value 为空串也算缺失", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "请假事由", "required": true, "value": ""},
}
if got := checkXsgzUnfilledRequired(fields); len(got) != 1 {
t.Fatalf("空串应算缺失,实际: %v", got)
}
})
t.Run("无 fieldName 时回退 fieldId", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "field_abc", "required": true},
}
got := checkXsgzUnfilledRequired(fields)
if len(got) != 1 || got[0] != "field_abc" {
t.Fatalf("应回退到 fieldId,实际: %v", got)
}
})
t.Run("非必填缺失不报告", func(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "fieldName": "备注", "required": false},
}
if got := checkXsgzUnfilledRequired(fields); len(got) != 0 {
t.Fatalf("非必填不应报告,实际: %v", got)
}
})
}