xsgz 提交前预检必填字段,避免含糊失败
学校请假表单由后台动态配置,可能包含本平台无法提供值的必填字段 (如"是否离校"单选、请假类型细分下拉)。原先这类情况会直接提交, 由学校端返回业务错误,用户看到的提示不明确。 新增 checkXsgzUnfilledRequired:提交前检查所有 required 字段是否已有值 (空串/null 均视为缺失),有缺失则不发请求,直接提示 「该校请假表单还有必填项无法自动填写:X、Y,请前往学校学工系统办理」, 并写入日志。请假与销假两条路径都已接入。 新增 5 个单测覆盖:有值不报、无值报字段名、空串算缺失、 无 fieldName 回退 fieldId、非必填不报。
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user