Files
dingtalk-workspace-cli/internal/shortcut/smart/pending_approvals.go
T
DennisandClaude Opus 4.8 4e4705c673 feat(shortcut): smart orchestration layer (68 shortcuts)
Multi-step / intelligent shortcuts under internal/shortcut/smart: name→ID
resolvers (user/base/table/dept/space), name-based actions (chat +dm/+broadcast/
+group-members, todo +assign, calendar +book with rollback/+free/+invite/
+suggest-time), time & self intelligence (calendar +today/+tomorrow/+week/
+next-event/+my-free and +conflicts/+free-slots scheduling intelligence),
convenience reads (contact +me, oa +pending/+done-approvals, todo +due-today/
+related-tasks, mail +recent-mail/+find-mail-user, attendance +this-month) and
aggregation (minutes +detail, aitable +record-share-links). Projections hardened
against real DingTalk responses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:04:10 +08:00

129 lines
5.1 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package smart
import (
"time"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
)
// PendingApprovals: READ-ONLY list of the approvals waiting for ME, projected to
// clean fields. Unlike `oa +approve-by` (which actually agrees to an approval),
// this shortcut only looks — it never approves / rejects / mutates anything.
//
// Steps (tool name + param keys copied verbatim from helpers/oa.go):
//
// 1. list my pending approvals via list_pending_approvals with starTime /
// endTime as float64 milliseconds over a recent (~90 day) window, mirroring
// `dws oa approval list-pending`; optional --limit maps to pageSize (float64).
//
// 2. defensively unwrap the returned instance list (multiple candidate
// container keys, one nested level) and project each entry to a readable
// shape {title, originatorName, processInstanceId, createTime} — every field
// probed across several candidate keys.
//
// 3. if nothing is pending, report "当前没有待我审批的任务" instead of an empty
// raw dump.
//
// dws oa +pending
// dws oa +pending --limit 10
var PendingApprovals = shortcut.Shortcut{
Service: "oa",
Command: "+pending",
Product: "oa",
Description: "只读列出待我审批的审批任务并投影为可读列表(只看不批)",
Intent: "当你只想快速看一眼「待我审批」的审批任务清单——每条的标题、发起人、审批实例 ID 和创建时间——" +
"而不想拿到一大坨原始字段时使用;内部拉取你近三个月待处理的审批单,再在本地投影出可读字段。" +
"这是纯只读操作,只做列出与本地投影,绝不会同意、拒绝或以任何方式提交/修改审批(要一键通过请改用 `dws oa +approve-by`);" +
"若当前没有待你审批的任务则提示「当前没有待我审批的任务」。",
Risk: shortcut.RiskRead,
Flags: []shortcut.Flag{
{Name: "limit", Type: shortcut.FlagInt, Desc: "最多列出多少条(可选)", Required: false},
},
Tips: []string{
`dws oa +pending`,
`dws oa +pending --limit 10`,
},
Execute: func(rt *shortcut.RuntimeContext) error {
// Step 1 — list my pending approvals. starTime/endTime are float64
// milliseconds; pageSize is optional. Keys copied verbatim from
// helpers.list-pending (list_pending_approvals). Window: last ~90 days.
now := time.Now()
params := map[string]any{
"starTime": float64(now.AddDate(0, 0, -90).UnixMilli()),
"endTime": float64(now.UnixMilli()),
}
if rt.Changed("limit") {
if n := rt.Int("limit"); n > 0 {
params["pageSize"] = float64(n)
}
}
data, err := rt.CallMCPData("oa", "list_pending_approvals", params)
if err != nil {
return err
}
// Step 2 — project entries. shortcutApproveItems (approve.go) defensively
// unwraps the list container (result/list/instances/items/data/records/
// processList, incl. one nested level).
items := shortcutApproveItems(data)
results := make([]map[string]any, 0, len(items))
for _, m := range items {
results = append(results, map[string]any{
// shortcutApproveTitle probes title/processTitle/name/subject/…
"title": shortcutApproveTitle(m),
// shortcutApproveInstanceID probes processInstanceId/instanceId/…
"processInstanceId": shortcutApproveInstanceID(m),
"originatorName": pendingApprovalsOriginator(m),
"createTime": pendingApprovalsCreateTime(m),
})
}
// Step 3 — empty result guard.
if len(results) == 0 {
return apperrors.NewValidation("当前没有待我审批的任务")
}
return rt.Output(map[string]any{"count": len(results), "pending": results})
},
}
// pendingApprovalsOriginator reads the initiator's display name, tolerating the
// common field names the gateway may use.
func pendingApprovalsOriginator(m map[string]any) string {
for _, key := range []string{"originatorName", "originatorUserName", "creatorName", "creator", "applicantName", "userName"} {
if s := shortcutApproveStr(m[key]); s != "" {
return s
}
}
return ""
}
// pendingApprovalsCreateTime reads a pending approval's create time, returning
// the raw value (usually epoch millis) under whichever candidate key is present.
func pendingApprovalsCreateTime(m map[string]any) any {
for _, key := range []string{"createTime", "gmtCreate", "createTimeStr", "startTime", "createdAt"} {
if v, ok := m[key]; ok && v != nil {
return v
}
}
return nil
}
func init() {
shortcut.Register(PendingApprovals)
}