Documentation
¶
Overview ¶
Package oa registers strict declarative shortcuts for DingTalk OA approval.
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var Approve = shortcut.Shortcut{ Service: "oa", Command: "+approve-by", Product: "oa", Description: "按关键词把我的一条待审批单据一键通过(自动定位实例与任务 ID)", Intent: "高风险兼容编排:完整读取并唯一匹配待办、唯一解析 taskId、确认后同意,再精确读回该 taskId 已不在待处理集合;没有安全 fixture 前不进入 Agent 公开发现。", Risk: shortcut.RiskHighWrite, Safety: oaWriteSafety(), OutputRollout: output.RolloutUnifiedActive, Contract: oaContract( "+approve-by", "按关键词把我的一条待审批单据一键通过(自动定位实例与任务 ID)", "仅在用户明确确认同意、关键词唯一定位实例且任务读回可验证时使用;任何歧义、分页不完整或读回失败都会阻止成功。", true, oaWriteResult("同意审批并通过精确任务读回验证"), nil, []contract.ParamDecl{{Name: "keyword", Property: "query"}, {Name: "comment", Property: "remark"}}, "dws oa +approve-by --keyword 报销", ), Flags: []shortcut.Flag{ {Name: "keyword", Type: shortcut.FlagString, Desc: "待审批单据的单号或标题关键词", Required: true}, {Name: "comment", Type: shortcut.FlagString, Desc: "审批意见(可选)"}, }, Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"keyword"}, Description: "--keyword 去除空白后不能为空,且必须唯一匹配完整待办集合中的一条实例"}}, Tips: []string{`dws oa +approve-by --keyword 报销`, `dws oa +approve-by --keyword 出差单 --comment "同意"`}, Validate: func(rt *shortcut.RuntimeContext) error { keyword := strings.TrimSpace(rt.Str("keyword")) if keyword == "" { return apperrors.NewValidation("--keyword 不能为空") } return nil }, Execute: func(rt *shortcut.RuntimeContext) error { keyword := strings.TrimSpace(rt.Str("keyword")) now := time.Now() pending, err := rt.CallMCPData("oa", "get_todo_tasks", map[string]any{ "pageNumber": 1, "pageSize": 20, "createTimeFrom": now.AddDate(0, 0, -90).Format(oaApprovalListDateLayout), "createTimeTo": now.Format(oaApprovalListDateLayout), "query": keyword, }) if err != nil { return err } items, err := oaProjectInstances(pending, "oa/get_todo_tasks", "result.values") if err != nil { return err } result, _ := pending["result"].(map[string]any) page, err := oaHasMorePage(result, "oa/get_todo_tasks", 1) if err != nil { return err } if page.HasMore { return oaResponseError("oa/get_todo_tasks", "ambiguous_incomplete_search", "待办搜索仍有后续页,无法证明关键词唯一") } matches := oaMatchApprovals(items, keyword) if len(matches) != 1 { return apperrors.NewValidation(fmt.Sprintf("关键词必须唯一匹配一条待审批实例,当前匹配 %d 条", len(matches))) } instanceID := matches[0].id tasksData, err := rt.CallMCPData("oa", "list_pending_tasks", map[string]any{"processInstanceId": instanceID}) if err != nil { return err } tasks, err := oaProjectTasks(tasksData, "oa/list_pending_tasks") if err != nil { return err } if len(tasks) != 1 { return apperrors.NewValidation(fmt.Sprintf("审批实例必须唯一对应一条待处理任务,当前为 %d 条", len(tasks))) } taskID := tasks[0]["taskId"].(string) numericTaskID, err := strconv.ParseFloat(taskID, 64) if err != nil { return oaResponseError("oa/list_pending_tasks", "malformed_task_identity", "taskId 不是可写入审批接口的数字") } writeArgs := map[string]any{"processInstanceId": instanceID, "taskId": numericTaskID} if comment := rt.Str("comment"); comment != "" { writeArgs["remark"] = comment } receipt, err := rt.CallMCPWriteDataStrict("oa", "approve_processInstance", writeArgs) if err != nil { return err } if err := oaRequireSuccess(receipt, "oa/approve_processInstance"); err != nil { return err } readback, err := rt.CallMCPData("oa", "list_pending_tasks", map[string]any{"processInstanceId": instanceID}) if err != nil { return oaPostWriteError("oa/list_pending_tasks", "readback_failed", "审批写入后无法读取任务状态;远端效果未知") } remaining, err := oaProjectTasks(readback, "oa/list_pending_tasks") if err != nil { return oaPostWriteError("oa/list_pending_tasks", "readback_malformed", "审批写入后的任务读回无法验证;远端效果未知") } for _, task := range remaining { if task["taskId"] == taskID { return oaPostWriteError("oa/list_pending_tasks", "write_not_observed", "审批任务写后读回仍处于待处理集合") } } return rt.Output(map[string]any{"processInstanceId": instanceID, "taskId": taskID, "verified": true}) }, }
View Source
var DoneApprovals = shortcut.Shortcut{ Service: "oa", Command: "+done-approvals", Product: "oa", Description: "只读列出我已处理过的审批任务(审批历史)并投影为可读列表", Intent: "兼容入口:读取我已处理审批的首个完整页;新调用优先使用可分页的 +list-executed。", Risk: shortcut.RiskRead, Safety: oaReadSafety(), OutputRollout: output.RolloutUnifiedActive, Contract: oaContract( "+done-approvals", "只读列出我已处理过的审批任务(审批历史)并投影为可读列表", "兼容旧的已处理审批摘要入口;需要翻页或搜索时使用 +list-executed。", true, oaCollectionResult("done", "严格验证的已处理审批摘要"), nil, []contract.ParamDecl{{Name: "limit"}}, "dws oa +done-approvals --limit 10", ), Flags: []shortcut.Flag{{Name: "limit", Type: shortcut.FlagInt, Desc: "最多列出多少条(可选)"}}, Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"limit"}, Description: "显式 --limit 必须在 1-100"}}, Tips: []string{`dws oa +done-approvals --limit 10`}, Validate: func(rt *shortcut.RuntimeContext) error { if rt.Changed("limit") && (rt.Int("limit") <= 0 || rt.Int("limit") > 100) { return apperrors.NewValidation("--limit 必须在 1 到 100 之间") } return nil }, Execute: func(rt *shortcut.RuntimeContext) error { limit := 20 if rt.Changed("limit") { limit = rt.Int("limit") } return oaFirstPageOnly(rt, "get_done_tasks", "done", map[string]any{"pageNumber": 1, "pageSize": limit}) }, }
View Source
var ListCc = oaNumberedInstanceShortcut( "+list-cc", "get_noticed_instances", "获取抄送当前用户的审批单列表", "需要查看抄送给当前用户的审批实例时使用;没有安全非空抄送 fixture 前不会进入公开发现。", oaApprovalListOptions{includeUnreadOnly: true}, )
View Source
var ListExecuted = oaNumberedInstanceShortcut( "+list-executed", "get_done_tasks", "获取当前用户已经处理过的审批单列表", "需要回顾当前用户已同意或拒绝过的审批实例时使用;与待办、已发起和抄送列表分开。", oaApprovalListOptions{includeStatus: true}, )
View Source
var ListForms = shortcut.Shortcut{ Service: "oa", Command: "+list-forms", Product: "oa", Description: "获取当前用户可见的审批表单列表", Intent: "按服务端游标读取当前用户可发起的审批表单;缺少 hasMore/nextCursor 或游标不前进时失败,不把重复首页宣称为完整列表。", Risk: shortcut.RiskRead, Safety: oaReadSafety(), OutputRollout: output.RolloutUnifiedActive, Contract: oaContract( "+list-forms", "获取当前用户可见的审批表单列表", "需要枚举可发起审批定义并取得稳定 processCode 时使用;当前下游不返回可验证 continuation,故不进入 Agent 公开发现。", true, oaCollectionResult("forms", "严格验证的可见审批表单页"), oaPagePagination("cursor"), []contract.ParamDecl{{Name: "cursor", Property: "cursor"}, {Name: "limit", Property: "limit"}}, "dws oa +list-forms --cursor 0 --limit 100", ), Flags: []shortcut.Flag{ {Name: "cursor", Type: shortcut.FlagInt, Default: "0", Desc: "分页游标,首次传 0"}, {Name: "limit", Type: shortcut.FlagInt, Default: "100", Desc: "每页大小,最大 100"}, }, Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"cursor", "limit"}, Description: "--cursor 不能小于 0;--limit 必须在 1-100"}}, Tips: []string{`dws oa +list-forms --cursor 0 --limit 100`}, Validate: func(rt *shortcut.RuntimeContext) error { if rt.Int("cursor") < 0 { return apperrors.NewValidation("--cursor 不能小于 0") } if rt.Int("limit") <= 0 || rt.Int("limit") > 100 { return apperrors.NewValidation("--limit 必须在 1 到 100 之间") } return nil }, Execute: func(rt *shortcut.RuntimeContext) error { const operation = "oa/list_user_visible_process" data, err := rt.CallMCPData("oa", "list_user_visible_process", map[string]any{"cursor": rt.Int("cursor"), "pageSize": rt.Int("limit")}) if err != nil { return err } forms, err := oaProjectForms(data, operation, "result.processCodeList") if err != nil { return err } result, _ := data["result"].(map[string]any) page, err := oaCursorPage(result, operation, rt.Int("cursor")) if err != nil { return err } return outputOAPage(rt, "forms", forms, page) }, }
View Source
var ListPending = shortcut.Shortcut{ Service: "oa", Command: "+list-pending", Product: "oa", Description: "查询当前登录用户待处理的审批任务列表", Intent: "按页码、日期、模板和发起人等条件查询待处理审批;只有显式成功、严格实例数组与可续页证据齐全时才返回结果。", Risk: shortcut.RiskRead, Safety: oaReadSafety(), OutputRollout: output.RolloutUnifiedActive, Contract: oaContract( "+list-pending", "查询当前登录用户待处理的审批任务列表", "需要按日期、模板或发起人读取待我审批的实例,并取得稳定 processInstanceId 时使用;没有安全非空待办 fixture 前不会进入公开发现。", true, oaCollectionResult("instances", "严格验证的待处理审批实例页"), oaPagePagination("page"), oaApprovalListParamDecls(oaApprovalListOptions{includeCreateBefore: true, includeLegacyRange: true}), "dws oa +list-pending --start 1785513600000 --end 1788191999000 --page 1 --limit 20", ), Flags: oaApprovalListFlags(oaApprovalListOptions{includeCreateBefore: true, includeLegacyRange: true}), Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"start", "end", "page", "limit", "create-time-from", "create-time-to", "finish-time-from", "finish-time-to"}, Description: "--start/--end 必须是递增的正整数 epoch 毫秒;--page 必须大于 0;--limit 必须在 1-100;日期筛选必须为 yyyy-MM-dd 且起始不晚于截止"}}, Tips: []string{`dws oa +list-pending --start 1785513600000 --end 1788191999000 --page 1 --limit 20`}, Validate: func(rt *shortcut.RuntimeContext) error { return validateOAApprovalList(rt, oaApprovalListOptions{includeCreateBefore: true, includeLegacyRange: true}) }, Execute: func(rt *shortcut.RuntimeContext) error { page, _ := parseOAApprovalListPage(rt, "page", 1) return oaInstancePage(rt, "get_todo_tasks", oaApprovalListParams(rt, oaApprovalListOptions{includeCreateBefore: true, includeLegacyRange: true}), page) }, }
View Source
var ListSubmitted = oaNumberedInstanceShortcut( "+list-submitted", "get_submitted_instances", "获取当前用户已发起的审批单列表", "需要查看当前用户发起的审批实例和当前状态时使用;返回稳定 processInstanceId 与可续页证据。", oaApprovalListOptions{includeStatus: true}, )
View Source
var MyInitiated = shortcut.Shortcut{ Service: "oa", Command: "+my-initiated", Product: "oa", Description: "列出我发起(提交)的审批单据", Intent: "需要兼容旧的 initiated 输出字段时使用;一般列表与分页可直接使用 +list-submitted。", Risk: shortcut.RiskRead, Safety: oaReadSafety(), OutputRollout: output.RolloutUnifiedActive, Contract: oaContract( "+my-initiated", "列出我发起(提交)的审批单据", "需要兼容旧的 initiated 输出字段时使用;一般列表与分页可直接使用 +list-submitted。", true, oaCollectionResult("initiated", "严格验证的已发起审批实例页"), oaPagePagination("page"), []contract.ParamDecl{{Name: "query", Property: "query"}, {Name: "page"}, {Name: "limit"}}, "dws oa +my-initiated --page 1 --limit 20", "dws oa +my-initiated --query 报销", ), Flags: []shortcut.Flag{ {Name: "query", Type: shortcut.FlagString, Desc: "关键字搜索(可选)"}, {Name: "page", Type: shortcut.FlagInt, Desc: "分页页码(可选,默认 1);--page 必须大于 0", Default: "1"}, {Name: "limit", Type: shortcut.FlagInt, Desc: "每页大小(可选,默认 20);--limit 必须在 1-100", Default: "20"}, }, Constraints: []shortcut.Constraint{ {Kind: shortcut.ConstraintCustom, Flags: []string{"page"}, Description: "--page 必须大于 0"}, {Kind: shortcut.ConstraintCustom, Flags: []string{"limit"}, Description: "--limit 必须在 1-100"}, }, Tips: []string{`dws oa +my-initiated`, `dws oa +my-initiated --query 报销`, `dws oa +my-initiated --page 2 --limit 50`}, Validate: func(rt *shortcut.RuntimeContext) error { return validateOAPage(rt.Int("page"), rt.Int("limit")) }, Execute: func(rt *shortcut.RuntimeContext) error { const operation = "oa/get_submitted_instances" params := map[string]any{"pageNumber": float64(rt.Int("page")), "pageSize": float64(rt.Int("limit"))} if query := rt.Str("query"); query != "" { params["query"] = query } data, err := rt.CallMCPData("oa", "get_submitted_instances", params) if err != nil { return err } items, err := oaProjectInstances(data, operation, "result.values") if err != nil { return err } result, _ := data["result"].(map[string]any) page, err := oaHasMorePage(result, operation, rt.Int("page")) if err != nil { return err } return outputOAPage(rt, "initiated", items, page) }, }
View Source
var PendingApprovals = shortcut.Shortcut{ Service: "oa", Command: "+pending", Product: "oa", Description: "只读列出待我审批的审批任务并投影为可读列表(只看不批)", Intent: "兼容入口:读取近三个月待处理审批的首个完整页;无法证明完整或缺少非空 fixture 时不进入 Agent 公开发现。", Risk: shortcut.RiskRead, Safety: oaReadSafety(), OutputRollout: output.RolloutUnifiedActive, Contract: oaContract( "+pending", "只读列出待我审批的审批任务并投影为可读列表(只看不批)", "兼容旧的待审批摘要入口;新调用优先使用支持显式时间和页码的 +list-pending。", true, oaCollectionResult("pending", "严格验证的待审批摘要"), nil, []contract.ParamDecl{{Name: "limit"}}, "dws oa +pending --limit 10", ), Flags: []shortcut.Flag{{Name: "limit", Type: shortcut.FlagInt, Desc: "最多列出多少条(可选)"}}, Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"limit"}, Description: "显式 --limit 必须在 1-100"}}, Tips: []string{`dws oa +pending --limit 10`}, Validate: func(rt *shortcut.RuntimeContext) error { if rt.Changed("limit") && (rt.Int("limit") <= 0 || rt.Int("limit") > 100) { return apperrors.NewValidation("--limit 必须在 1 到 100 之间") } return nil }, Execute: func(rt *shortcut.RuntimeContext) error { now := time.Now() limit := 20 if rt.Changed("limit") { limit = rt.Int("limit") } params := map[string]any{ "pageNumber": 1, "pageSize": limit, "createTimeFrom": now.AddDate(0, 0, -90).Format(oaApprovalListDateLayout), "createTimeTo": now.Format(oaApprovalListDateLayout), } return oaFirstPageOnly(rt, "get_todo_tasks", "pending", params) }, }
View Source
var SearchForms = shortcut.Shortcut{ Service: "oa", Command: "+search-forms", Product: "oa", Description: "按关键字模糊搜索当前用户可见的审批表单", Intent: "已知审批定义关键字,需要取得一个或多个稳定 processCode 时使用;要无条件遍历全部定义不要使用本命令。", Risk: shortcut.RiskRead, Safety: oaReadSafety(), OutputRollout: output.RolloutUnifiedActive, Contract: oaContract( "+search-forms", "按关键字模糊搜索当前用户可见的审批表单", "已知审批定义关键字,需要取得一个或多个稳定 processCode 时使用;要无条件遍历全部定义不要使用本命令。", true, oaCollectionResult("forms", "严格验证的审批表单搜索结果"), nil, []contract.ParamDecl{{Name: "query", Property: "query"}}, "dws oa +search-forms --query 报销", ), Flags: []shortcut.Flag{{Name: "query", Type: shortcut.FlagString, Desc: "关键字(匹配 processCode 或表单名称);去除空白后不能为空", Required: true}}, Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"query"}, Description: "--query 去除空白后不能为空"}}, Tips: []string{`dws oa +search-forms --query 报销`}, Validate: func(rt *shortcut.RuntimeContext) error { if strings.TrimSpace(rt.Str("query")) == "" { return apperrors.NewValidation("--query 不能为空") } return nil }, Execute: func(rt *shortcut.RuntimeContext) error { const operation = "oa/search_form" data, err := rt.CallMCPData("oa", "search_form", map[string]any{"query": strings.TrimSpace(rt.Str("query"))}) if err != nil { return err } forms, err := oaProjectForms(data, operation, "result") if err != nil { return err } return outputOACompleteCollection(rt, "forms", forms) }, }
Functions ¶
This section is empty.
Types ¶
This section is empty.
Click to show internal directories.
Click to hide internal directories.