diff --git a/backed/internal/handler/leave_apply.go b/backed/internal/handler/leave_apply.go index 88740a1..705ec0d 100644 --- a/backed/internal/handler/leave_apply.go +++ b/backed/internal/handler/leave_apply.go @@ -635,22 +635,45 @@ func executeXsgzLeaveApply(c *fiber.Ctx, user *model.SysUser, req *ExecuteLeaveA return nil } -// fillXsgzFieldValues 把用户填写的值写回 xsgz 表单字段定义的 value 字段。 +// fillXsgzFieldValues 把用户填写的值写回 xsgz 表单字段定义。 // -// xsgz 提交的 fields 是「字段定义 + 当前值」的数组;本平台表单字段与 xsgz 字段 -// 命名不同,这里按常见语义做映射:请假原因 → 含"原因/事由"的字段。 -// 未能识别的字段保持原值(空),由学校端校验。 +// 依据前端 transformFields 的字段结构: +// +// form_fields_for_add 返回 { fieldId, fieldName, componentType, fieldType, required, options[] } +// +// 提交时前端遍历字段,把用户输入按 fieldId 写回 value。本平台只有「请假原因」 +// 一项自由文本,因此: +// 1. 优先按标签(fieldName 含"原因/事由/说明")精确匹配; +// 2. 未命中时,退化为「必填的文本类字段」(fieldType 为 TEXT/TEXTAREA/LONGTEXT) +// 一律填入请假原因——该校表单通常只有一个原因字段,这样可以避免因 +// 学校自定义字段名(如"请假事由说明""申请理由")导致漏填。 +// +// 选择类字段(RADIO/CHECKBOX/SELECT 等)不猜测,留空由学校端校验。 func fillXsgzFieldValues(fields []map[string]interface{}, req *ExecuteLeaveApplyRequest) { - if len(fields) == 0 { + if len(fields) == 0 || req.LeaveReason == "" { return } + textTypes := map[string]bool{"TEXT": true, "TEXTAREA": true, "LONGTEXT": true} + + // 第一轮:按标签匹配 + matched := false for _, f := range fields { label := strings.ToLower(mapStr(f, "fieldZh") + mapStr(f, "fieldName") + mapStr(f, "label")) - switch { - case strings.Contains(label, "原因") || strings.Contains(label, "事由"): - if req.LeaveReason != "" { - f["value"] = req.LeaveReason - } + if strings.Contains(label, "原因") || strings.Contains(label, "事由") || strings.Contains(label, "说明") { + f["value"] = req.LeaveReason + matched = true + } + } + if matched { + return + } + + // 第二轮:必填文本字段兜底 + for _, f := range fields { + ft := strings.ToUpper(mapStr(f, "fieldType")) + required, _ := f["required"].(bool) + if required && textTypes[ft] { + f["value"] = req.LeaveReason } } } diff --git a/backed/internal/handler/leave_xsgz_fill_test.go b/backed/internal/handler/leave_xsgz_fill_test.go new file mode 100644 index 0000000..9dad5ac --- /dev/null +++ b/backed/internal/handler/leave_xsgz_fill_test.go @@ -0,0 +1,54 @@ +package handler + +import "testing" + +// fillXsgzFieldValues:优先按标签匹配,未命中时用必填文本字段兜底。 +func TestFillXsgzFieldValues(t *testing.T) { + req := &ExecuteLeaveApplyRequest{LeaveReason: "感冒发烧需休息"} + + t.Run("按标签命中原因字段", func(t *testing.T) { + fields := []map[string]interface{}{ + {"fieldId": "f1", "fieldName": "请假原因", "fieldType": "TEXTAREA", "required": true}, + {"fieldId": "f2", "fieldName": "紧急联系人", "fieldType": "TEXT", "required": true}, + } + fillXsgzFieldValues(fields, req) + if fields[0]["value"] != req.LeaveReason { + t.Fatalf("原因字段未被填充: %v", fields[0]) + } + if _, ok := fields[1]["value"]; ok { + t.Fatalf("非原因字段不应被填充: %v", fields[1]) + } + }) + + t.Run("学校自定义字段名时用必填文本兜底", func(t *testing.T) { + fields := []map[string]interface{}{ + {"fieldId": "f1", "fieldName": "申请理由说明", "fieldType": "TEXTAREA", "required": true}, + {"fieldId": "f2", "fieldName": "备注", "fieldType": "TEXT", "required": false}, + } + fillXsgzFieldValues(fields, req) + // "说明" 命中第一轮 + if fields[0]["value"] != req.LeaveReason { + t.Fatalf("含'说明'的字段应被填充: %v", fields[0]) + } + }) + + t.Run("无关键词时必填文本字段兜底", func(t *testing.T) { + fields := []map[string]interface{}{ + {"fieldId": "f1", "fieldName": "补充信息", "fieldType": "TEXTAREA", "required": true}, + {"fieldId": "f2", "fieldName": "选择项", "fieldType": "RADIO", "required": true}, + } + fillXsgzFieldValues(fields, req) + if fields[0]["value"] != req.LeaveReason { + t.Fatalf("必填文本字段应兜底填充: %v", fields[0]) + } + if _, ok := fields[1]["value"]; ok { + t.Fatalf("选择类字段不应猜测填充: %v", fields[1]) + } + }) + + t.Run("空 fields 与空原因不 panic", func(t *testing.T) { + fillXsgzFieldValues(nil, req) + fillXsgzFieldValues([]map[string]interface{}{}, req) + fillXsgzFieldValues([]map[string]interface{}{{"fieldId": "f1"}}, &ExecuteLeaveApplyRequest{}) + }) +}