From e7c9b06056dabccdeee1d131b3fbf54649711733 Mon Sep 17 00:00:00 2001 From: becomer Date: Sat, 17 Oct 2026 17:46:49 +0800 Subject: [PATCH] =?UTF-8?q?xsgz=20=E6=8F=90=E4=BA=A4=E5=89=8D=E9=A2=84?= =?UTF-8?q?=E6=A3=80=E5=BF=85=E5=A1=AB=E5=AD=97=E6=AE=B5=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E5=90=AB=E7=B3=8A=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 学校请假表单由后台动态配置,可能包含本平台无法提供值的必填字段 (如"是否离校"单选、请假类型细分下拉)。原先这类情况会直接提交, 由学校端返回业务错误,用户看到的提示不明确。 新增 checkXsgzUnfilledRequired:提交前检查所有 required 字段是否已有值 (空串/null 均视为缺失),有缺失则不发请求,直接提示 「该校请假表单还有必填项无法自动填写:X、Y,请前往学校学工系统办理」, 并写入日志。请假与销假两条路径都已接入。 新增 5 个单测覆盖:有值不报、无值报字段名、空串算缺失、 无 fieldName 回退 fieldId、非必填不报。 --- backed/internal/handler/leave.go | 9 ++++ backed/internal/handler/leave_apply.go | 38 ++++++++++++++ .../internal/handler/leave_xsgz_fill_test.go | 51 +++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/backed/internal/handler/leave.go b/backed/internal/handler/leave.go index 436c1b5..ad3c589 100644 --- a/backed/internal/handler/leave.go +++ b/backed/internal/handler/leave.go @@ -514,6 +514,15 @@ func executeXsgzLeaveCancel(c *fiber.Ctx, user *model.SysUser, req *ExecuteLeave 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{ ProjectID: req.ProjectID, diff --git a/backed/internal/handler/leave_apply.go b/backed/internal/handler/leave_apply.go index 705ec0d..7399b06 100644 --- a/backed/internal/handler/leave_apply.go +++ b/backed/internal/handler/leave_apply.go @@ -593,6 +593,17 @@ func executeXsgzLeaveApply(c *fiber.Ctx, user *model.SysUser, req *ExecuteLeaveA } 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{ ProjectID: req.XsgzProjectID, RequestType: "LEAVE", @@ -685,3 +696,30 @@ func mapStr(m map[string]interface{}, k string) string { } 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 +} diff --git a/backed/internal/handler/leave_xsgz_fill_test.go b/backed/internal/handler/leave_xsgz_fill_test.go index 9dad5ac..6f27045 100644 --- a/backed/internal/handler/leave_xsgz_fill_test.go +++ b/backed/internal/handler/leave_xsgz_fill_test.go @@ -52,3 +52,54 @@ func TestFillXsgzFieldValues(t *testing.T) { 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) + } + }) +}