oa

package
v1.0.63 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL