Files
dingtalk-workspace-cli/internal/helpers/attendance.go

4478 lines
208 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package helpers
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
)
// ──────────────────────────────────────────────────────────
// dws attendance — 考勤
// ──────────────────────────────────────────────────────────
// jsonStringToMap 尝试将 JSON 字符串解析为 map;如果解析失败则原样透传为字符串。
func jsonStringToMap(s string) any {
var m map[string]any
if err := json.Unmarshal([]byte(s), &m); err == nil {
return m
}
return s
}
// printGroupModifyDeeplink 从 create_group_setting 的响应中提取 corpId 和 groupId,
// 拼接并输出一条钉钉 PC 端跳转链接,帮助用户直接验证刚创建的考勤组。
// 响应字段可能位置:root / root.result / root.result.groupSettingVO 等。
// 任一关键字段缺失时不输出链接,避免误导。
func printGroupModifyDeeplink(resp map[string]any) {
if resp == nil {
return
}
corpID := pickStringField(resp, "corpId")
groupID := pickInt64StringField(resp, "groupId", "id")
if corpID == "" || groupID == "" {
return
}
link := fmt.Sprintf(
"dingtalk://dingtalkclient/page/link?url=https%%3A%%2F%%2Fhrmregister.dingtalk.com%%2Fsubapp%%2Fattend%%2Findex%%3Fcode%%3Dattend%%26corpId%%3D%s%%26ddtab%%3Dtrue%%26from%%3Dattend%%23%%2FgroupModify%%3Fid%%3D%s",
corpID, groupID,
)
deps.Out.PrintInfo("考勤组创建成功,点击以下链接在钉钉 PC 端查看:")
deps.Out.PrintDim(link)
}
// pickStringField 从 map 中按 root → result → result.groupSettingVO/groupVO 顺序查找指定字段,
// 能转为字符串则返回字符串。
func pickStringField(m map[string]any, key string) string {
if v, ok := m[key]; ok {
if s := toFlatString(v); s != "" {
return s
}
}
if r, ok := m["result"].(map[string]any); ok {
if v, ok := r[key]; ok {
if s := toFlatString(v); s != "" {
return s
}
}
for _, nestedKey := range []string{"groupSettingVO", "groupVO"} {
if vo, ok := r[nestedKey].(map[string]any); ok {
if v, ok := vo[key]; ok {
if s := toFlatString(v); s != "" {
return s
}
}
}
}
}
return ""
}
// pickInt64StringField 同 pickStringField,按顺序匹配首个有值的 key,适配 number/int64 类型 ID 字段。
func pickInt64StringField(m map[string]any, keys ...string) string {
for _, key := range keys {
if s := pickStringField(m, key); s != "" {
return s
}
}
return ""
}
// toFlatString 将常见 JSON 标量字段转为字符串形式。
func toFlatString(v any) string {
switch t := v.(type) {
case string:
return strings.TrimSpace(t)
case float64:
return strconv.FormatInt(int64(t), 10)
case int64:
return strconv.FormatInt(t, 10)
case int:
return strconv.Itoa(t)
case json.Number:
return t.String()
}
return ""
}
// parseUserList parses a comma-separated user ID string into a slice,
// trimming whitespace and filtering empty entries.
func parseUserList(usersStr string) []string {
parts := strings.Split(usersStr, ",")
userIds := make([]string, 0, len(parts))
for _, u := range parts {
if s := strings.TrimSpace(u); s != "" {
userIds = append(userIds, s)
}
}
return userIds
}
// parseDateToTimestamp parses a date string and returns millisecond timestamp.
// Supports formats: "YYYY-MM-DD" and "YYYY-MM-DD HH:mm:ss".
func parseDateToTimestamp(dateStr, paramName string) (int64, error) {
dateStr = strings.TrimSpace(dateStr)
// Try full datetime format first
if t, err := time.ParseInLocation("2006-01-02 15:04:05", dateStr, time.Local); err == nil {
return t.UnixMilli(), nil
}
// Try date-only format (set to start/end of day based on parameter)
if t, err := time.ParseInLocation("2006-01-02", dateStr, time.Local); err == nil {
// For begin time, use 00:00:00; for end time, use 23:59:59
if strings.Contains(strings.ToLower(paramName), "end") {
return t.Add(23*time.Hour + 59*time.Minute + 59*time.Second).UnixMilli(), nil
}
return t.UnixMilli(), nil
}
return 0, fmt.Errorf("invalid --%s format, use YYYY-MM-DD or YYYY-MM-DD HH:mm:ss (e.g. 2026-04-01)", paramName)
}
// int64FlagOrFallback reads an int64 flag by primary name; if zero/unchanged,
// falls back through aliases in order, returning the first non-zero value.
func int64FlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) int64 {
if cmd.Flags().Changed(primary) {
if v, _ := cmd.Flags().GetInt64(primary); v != 0 {
return v
}
}
for _, alias := range aliases {
if cmd.Flags().Changed(alias) {
if v, _ := cmd.Flags().GetInt64(alias); v != 0 {
return v
}
}
}
v, _ := cmd.Flags().GetInt64(primary)
return v
}
// intFlagOrFallback reads an int flag by primary name; if zero/unchanged,
// falls back through aliases in order, returning the first non-zero value.
func intFlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) int {
if cmd.Flags().Changed(primary) {
if v, _ := cmd.Flags().GetInt(primary); v != 0 {
return v
}
}
for _, alias := range aliases {
if cmd.Flags().Changed(alias) {
if v, _ := cmd.Flags().GetInt(alias); v != 0 {
return v
}
}
}
// Return the primary's default value
v, _ := cmd.Flags().GetInt(primary)
return v
}
// boolFlagOrFallback reads a bool flag by primary name; if unchanged/false,
// falls back through aliases in order, returning the first true value.
func boolFlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) bool {
if cmd.Flags().Changed(primary) {
if v, _ := cmd.Flags().GetBool(primary); v {
return v
}
}
for _, alias := range aliases {
if cmd.Flags().Changed(alias) {
if v, _ := cmd.Flags().GetBool(alias); v {
return v
}
}
}
v, _ := cmd.Flags().GetBool(primary)
return v
}
// normalizeWorkDate converts workDate to yyyy-MM-dd HH:mm:ss format.
// Supports: string (YYYY-MM-DD or YYYY-MM-dd HH:mm:ss), number (timestamp).
func normalizeWorkDate(workDate any) (string, error) {
switch v := workDate.(type) {
case string:
v = strings.TrimSpace(v)
// Already in correct format
if _, err := time.ParseInLocation("2006-01-02 15:04:05", v, time.Local); err == nil {
return v, nil
}
// Date only format, append 00:00:00
if t, err := time.ParseInLocation("2006-01-02", v, time.Local); err == nil {
return t.Format("2006-01-02 15:04:05"), nil
}
return "", fmt.Errorf("invalid date string format: %s, use YYYY-MM-DD or YYYY-MM-dd HH:mm:ss", v)
case float64:
// JSON number (timestamp in milliseconds)
ts := int64(v)
return time.UnixMilli(ts).Format("2006-01-02 15:04:05"), nil
case int64:
return time.UnixMilli(v).Format("2006-01-02 15:04:05"), nil
case int:
return time.UnixMilli(int64(v)).Format("2006-01-02 15:04:05"), nil
default:
return "", fmt.Errorf("workDate must be string or number, got: %T", workDate)
}
}
// convertClassCheckTime traverses classVO and converts any "HH:mm" string
// checkTime fields to millisecond timestamps (1970-01-01 HH:mm in UTC+8 的 Unix 毫秒时间戳).
// 例如 08:00 → 0, 09:00 → 3600000, 17:00 → 32400000
// Paths: classVO["sections"][*]["times"][*]["checkTime"]
//
// classVO["setting"]["topRestTimeList"][*]["checkTime"]
func convertClassCheckTime(classVO map[string]any) {
cst := time.FixedZone("CST", 8*3600)
// Helper: convert a single checkTime value
convertOne := func(obj map[string]any) {
v, ok := obj["checkTime"]
if !ok || v == nil {
return
}
switch ct := v.(type) {
case string:
ct = strings.TrimSpace(ct)
if t, err := time.ParseInLocation("2006-01-02 15:04", "1970-01-01 "+ct, cst); err == nil {
obj["checkTime"] = float64(t.UnixMilli())
}
case float64:
// already a number, no conversion needed
}
}
// Convert sections[*].times[*].checkTime
if sections, ok := classVO["sections"].([]any); ok {
for _, sec := range sections {
if secMap, ok := sec.(map[string]any); ok {
if times, ok := secMap["times"].([]any); ok {
for _, t := range times {
if tMap, ok := t.(map[string]any); ok {
convertOne(tMap)
}
}
}
}
}
}
// Convert setting.topRestTimeList[*].checkTime
if setting, ok := classVO["setting"].(map[string]any); ok {
if restList, ok := setting["topRestTimeList"].([]any); ok {
for _, item := range restList {
if itemMap, ok := item.(map[string]any); ok {
convertOne(itemMap)
}
}
}
}
}
type selfSettingSaveFlagSpec struct {
flagName string
requestField string
valueType string
description string
scenes []string
}
const (
selfSettingSaveBool = "bool"
selfSettingSaveInt = "int"
selfSettingSaveJSON = "json"
)
var selfSettingSaveFlagSpecs = []selfSettingSaveFlagSpec{
{flagName: "check-remind-setting", requestField: "checkRemindSetting", valueType: selfSettingSaveJSON, description: "打卡提醒 DING 渠道设置 JSON,如 {\"onDutyRemind\":{\"openRemind\":true,\"remindMinutes\":10}}", scenes: []string{"checkRemind"}},
{flagName: "check-remind-user-on-duty", requestField: "checkRemindUserOnDuty", valueType: selfSettingSaveBool, description: "打卡提醒工作通知渠道:用户个人上班打卡提醒开关", scenes: []string{"checkRemind"}},
{flagName: "check-remind-user-off-duty", requestField: "checkRemindUserOffDuty", valueType: selfSettingSaveBool, description: "打卡提醒工作通知渠道:用户个人下班打卡提醒开关", scenes: []string{"checkRemind"}},
{flagName: "enable-onduty-check-remind-of-pc", requestField: "enableOndutyCheckRemindOfPc", valueType: selfSettingSaveBool, description: "PC 端弹窗渠道:上班打卡提醒开关", scenes: []string{"checkRemind"}},
{flagName: "enable-offduty-check-remind-of-pc", requestField: "enableOffdutyCheckRemindOfPc", valueType: selfSettingSaveBool, description: "PC 端弹窗渠道:下班打卡提醒开关", scenes: []string{"checkRemind"}},
{flagName: "onduty-check-type", requestField: "ondutyCheckType", valueType: selfSettingSaveInt, description: "上班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡", scenes: []string{"fastCheck"}},
{flagName: "offduty-check-type", requestField: "offdutyCheckType", valueType: selfSettingSaveInt, description: "下班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡", scenes: []string{"fastCheck"}},
{flagName: "onduty-remind-start-min", requestField: "ondutyRemindStartMin", valueType: selfSettingSaveInt, description: "上班打卡提醒开始时间,单位:分钟", scenes: []string{"fastCheck"}},
{flagName: "onduty-remind-end-min", requestField: "ondutyRemindEndMin", valueType: selfSettingSaveInt, description: "上班打卡提醒结束时间,单位:分钟", scenes: []string{"fastCheck"}},
{flagName: "offduty-remind-start-min", requestField: "offdutyRemindStartMin", valueType: selfSettingSaveInt, description: "下班打卡提醒开始时间,单位:分钟", scenes: []string{"fastCheck"}},
{flagName: "offduty-remind-end-min", requestField: "offdutyRemindEndMin", valueType: selfSettingSaveInt, description: "下班打卡提醒结束时间,单位:分钟", scenes: []string{"fastCheck"}},
{flagName: "fast-check-late-need-confirm", requestField: "fastCheckLateNeedConfirm", valueType: selfSettingSaveBool, description: "上班极速打卡:迟到时是否需要二次确认", scenes: []string{"fastCheck"}},
{flagName: "can-update-off-duty", requestField: "canUpdateOffDuty", valueType: selfSettingSaveBool, description: "下班极速打卡:是否允许用户更新下班打卡设置", scenes: []string{"fastCheck"}},
{flagName: "voice-remind-switch", requestField: "voiceRemindSwitch", valueType: selfSettingSaveBool, description: "极速打卡提示音开关", scenes: []string{"fastCheck"}},
{flagName: "vibration-remind-switch", requestField: "vibrationRemindSwitch", valueType: selfSettingSaveBool, description: "极速打卡震动提醒开关", scenes: []string{"fastCheck"}},
{flagName: "check-result-msg", requestField: "checkResultMsg", valueType: selfSettingSaveInt, description: "打卡结果通知开关:0 关闭,1 开启", scenes: []string{"checkResultNotify"}},
{flagName: "lack-send-todo-msg", requestField: "lackSendTodoMsg", valueType: selfSettingSaveInt, description: "缺卡提醒待办渠道:0 关闭,null 或 1 开启", scenes: []string{"lackRemind"}},
{flagName: "lack-remind-user", requestField: "lackRemindUser", valueType: selfSettingSaveInt, description: "缺卡提醒工作通知渠道:0 关闭,null 或 1 开启", scenes: []string{"lackRemind"}},
{flagName: "person-daily-report-switch", requestField: "personDailyReportSwitch", valueType: selfSettingSaveInt, description: "个人考勤统计日报推送开关:0 关闭,1 开启", scenes: []string{"personalAttendStatNotify"}},
{flagName: "person-week-report-type", requestField: "personWeekReportType", valueType: selfSettingSaveInt, description: "个人考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮", scenes: []string{"personalAttendStatNotify"}},
{flagName: "person-month-report-type", requestField: "personMonthReportType", valueType: selfSettingSaveInt, description: "个人考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮", scenes: []string{"personalAttendStatNotify"}},
{flagName: "boss-push-start-min", requestField: "bossPushStartMin", valueType: selfSettingSaveInt, description: "团队考勤统计日报推送开始时间,单位:分钟;-1 表示关闭日报推送", scenes: []string{"bossAttendStatNotify"}},
{flagName: "boss-week-report-type", requestField: "bossWeekReportType", valueType: selfSettingSaveInt, description: "团队考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮", scenes: []string{"bossAttendStatNotify"}},
{flagName: "boss-month-report-type", requestField: "bossMonthReportType", valueType: selfSettingSaveInt, description: "团队考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮", scenes: []string{"bossAttendStatNotify"}},
}
func registerSelfSettingSaveFlags(cmd *cobra.Command) {
for _, spec := range selfSettingSaveFlagSpecs {
switch spec.valueType {
case selfSettingSaveBool:
cmd.Flags().Bool(spec.flagName, false, spec.description)
case selfSettingSaveInt:
cmd.Flags().Int(spec.flagName, 0, spec.description)
case selfSettingSaveJSON:
cmd.Flags().String(spec.flagName, "", spec.description)
}
}
}
func collectSelfSettingSaveRequest(cmd *cobra.Command, settingScene string, userID string) (map[string]any, error) {
request := map[string]any{
"settingScene": settingScene,
"userId": userID,
}
validSceneFieldCount := 0
invalidFlagNames := make([]string, 0)
for _, spec := range selfSettingSaveFlagSpecs {
if !cmd.Flags().Changed(spec.flagName) {
continue
}
if !selfSettingFlagSupportsScene(spec, settingScene) {
invalidFlagNames = append(invalidFlagNames, "--"+spec.flagName)
continue
}
value, err := readSelfSettingSaveFlagValue(cmd, spec)
if err != nil {
return nil, err
}
request[spec.requestField] = value
validSceneFieldCount++
}
if len(invalidFlagNames) > 0 {
return nil, fmt.Errorf("%s cannot be used with --setting-scene %s", strings.Join(invalidFlagNames, ", "), settingScene)
}
if validSceneFieldCount == 0 {
return nil, fmt.Errorf("at least one %s setting field is required", settingScene)
}
return request, nil
}
func selfSettingFlagSupportsScene(spec selfSettingSaveFlagSpec, settingScene string) bool {
for _, scene := range spec.scenes {
if scene == settingScene {
return true
}
}
return false
}
func readSelfSettingSaveFlagValue(cmd *cobra.Command, spec selfSettingSaveFlagSpec) (any, error) {
switch spec.valueType {
case selfSettingSaveBool:
value, _ := cmd.Flags().GetBool(spec.flagName)
return value, nil
case selfSettingSaveInt:
value, _ := cmd.Flags().GetInt(spec.flagName)
return value, nil
case selfSettingSaveJSON:
rawValue := strings.TrimSpace(mustGetFlag(cmd, spec.flagName))
if rawValue == "" {
return nil, fmt.Errorf("--%s cannot be empty", spec.flagName)
}
var value map[string]any
if err := json.Unmarshal([]byte(rawValue), &value); err != nil {
return nil, fmt.Errorf("invalid --%s JSON: %w", spec.flagName, err)
}
return value, nil
default:
return nil, fmt.Errorf("unsupported value type %q for --%s", spec.valueType, spec.flagName)
}
}
func isAttendanceSettingSceneAllowed(settingScene string) bool {
switch settingScene {
case "checkRemind", "fastCheck", "checkResultNotify", "lackRemind", "personalAttendStatNotify", "bossAttendStatNotify":
return true
default:
return false
}
}
func validateGlobalSettingScope(cmd *cobra.Command) error {
if err := validateRequiredFlags(cmd, "scope"); err != nil {
return err
}
scope := strings.TrimSpace(mustGetFlag(cmd, "scope"))
switch scope {
case "企业", "全公司", "所有人":
return nil
default:
return fmt.Errorf("invalid --scope %q, must explicitly be one of: 企业, 全公司, 所有人", scope)
}
}
type globalSettingSaveFlagSpec struct {
flagName string
requestField string
valueType string
description string
scenes []string
}
var globalSettingSaveFlagSpecs = []globalSettingSaveFlagSpec{
{flagName: "check-remind-corp", requestField: "checkRemindCorp", valueType: selfSettingSaveBool, description: "打卡提醒企业总开关", scenes: []string{"checkRemind"}},
{flagName: "check-remind-pc-corp", requestField: "checkRemindPcCorp", valueType: selfSettingSaveBool, description: "打卡提醒 PC 端弹窗企业总开关", scenes: []string{"checkRemind"}},
{flagName: "fast-check-corp", requestField: "fastCheckCorp", valueType: selfSettingSaveBool, description: "极速打卡企业总开关", scenes: []string{"fastCheck"}},
{flagName: "enable-check-cert-push", requestField: "enableCheckCertPush", valueType: selfSettingSaveBool, description: "打卡结果通知企业总开关", scenes: []string{"checkResultNotify"}},
{flagName: "lack-remind-corp", requestField: "lackRemindCorp", valueType: selfSettingSaveBool, description: "缺卡提醒企业总开关", scenes: []string{"lackRemind"}},
{flagName: "enable-personal-daily-report", requestField: "enablePersonalDailyReport", valueType: selfSettingSaveBool, description: "个人考勤统计通知日报企业总开关", scenes: []string{"personalAttendStatNotify"}},
{flagName: "enable-personal-weekly-report", requestField: "enablePersonalWeeklyReport", valueType: selfSettingSaveBool, description: "个人考勤统计通知周报企业开关,钉邮渠道", scenes: []string{"personalAttendStatNotify"}},
{flagName: "enable-personal-weekly-report-card", requestField: "enablePersonalWeeklyReportCard", valueType: selfSettingSaveBool, description: "个人考勤统计通知周报企业开关,工作通知渠道", scenes: []string{"personalAttendStatNotify"}},
{flagName: "enable-personal-monthly-report", requestField: "enablePersonalMonthlyReport", valueType: selfSettingSaveBool, description: "个人考勤统计通知月报企业总开关", scenes: []string{"personalAttendStatNotify"}},
{flagName: "boss-daily-report-type", requestField: "bossDailyReportType", valueType: selfSettingSaveInt, description: "团队考勤统计通知日报发送渠道类型:0 全关闭,1 开启", scenes: []string{"bossAttendStatNotify"}},
{flagName: "boss-weekly-report-type", requestField: "bossWeeklyReportType", valueType: selfSettingSaveInt, description: "团队考勤统计通知周报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮", scenes: []string{"bossAttendStatNotify"}},
{flagName: "boss-monthly-report-type", requestField: "bossMonthlyReportType", valueType: selfSettingSaveInt, description: "团队考勤统计通知月报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮", scenes: []string{"bossAttendStatNotify"}},
}
func registerGlobalSettingSaveFlags(cmd *cobra.Command) {
for _, spec := range globalSettingSaveFlagSpecs {
switch spec.valueType {
case selfSettingSaveBool:
cmd.Flags().Bool(spec.flagName, false, spec.description)
case selfSettingSaveInt:
cmd.Flags().Int(spec.flagName, 0, spec.description)
}
}
}
func collectGlobalSettingSaveRequest(cmd *cobra.Command, settingScene string) (map[string]any, error) {
request := map[string]any{
"settingScene": settingScene,
}
validSceneFieldCount := 0
invalidFlagNames := make([]string, 0)
for _, spec := range globalSettingSaveFlagSpecs {
if !cmd.Flags().Changed(spec.flagName) {
continue
}
if !globalSettingFlagSupportsScene(spec, settingScene) {
invalidFlagNames = append(invalidFlagNames, "--"+spec.flagName)
continue
}
value, _ := readGlobalSettingSaveFlagValue(cmd, spec)
request[spec.requestField] = value
validSceneFieldCount++
}
if len(invalidFlagNames) > 0 {
return nil, fmt.Errorf("%s cannot be used with --setting-scene %s", strings.Join(invalidFlagNames, ", "), settingScene)
}
if validSceneFieldCount == 0 {
return nil, fmt.Errorf("at least one %s global setting field is required", settingScene)
}
return request, nil
}
func globalSettingFlagSupportsScene(spec globalSettingSaveFlagSpec, settingScene string) bool {
for _, scene := range spec.scenes {
if scene == settingScene {
return true
}
}
return false
}
func readGlobalSettingSaveFlagValue(cmd *cobra.Command, spec globalSettingSaveFlagSpec) (any, error) {
switch spec.valueType {
case selfSettingSaveBool:
value, _ := cmd.Flags().GetBool(spec.flagName)
return value, nil
case selfSettingSaveInt:
value, _ := cmd.Flags().GetInt(spec.flagName)
return value, nil
default:
return nil, fmt.Errorf("unsupported value type %q for --%s", spec.valueType, spec.flagName)
}
}
func newAttendanceCommand() *cobra.Command {
// Product-level Agent routing Decl (migrated from selection/attendance.json
// products.attendance). Catalog assembly stamps provenance contract_final.
contract.RegisterProductDecl(contract.ProductDecl{
ID: "attendance",
Selection: contract.ProductSelectionDecl{
AgentSummary: "查询考勤记录、排班、班次、考勤组、审批、报表、个人规则和假期,并执行经确认的考勤配置变更。",
UseWhen: []string{
"用户要查询或管理钉钉考勤数据、规则、排班、考勤组、审批表单或假期余额。",
},
AvoidWhen: []string{
"用户只需处理普通日历日程、非考勤类活动签到或通用审批流状态,而不是钉钉考勤业务。",
},
},
})
root := &cobra.Command{
Use: "attendance",
Short: "考勤打卡 / 排班 / 统计",
Long: `管理钉钉考勤:查询个人考勤详情、班次查询、排班管理、获取考勤统计摘要、查询考勤组与规则。
子命令:
record 考勤记录(个人考勤详情)
check 打卡查询(打卡结果、打卡流水)
approve 审批单查询(请假、加班、出差、补卡)
shift 班次查询(员工当天打卡安排)
schedule 排班管理(排班制考勤组排班记录导入与查询)
class 班次规则(查询班次定义列表)
summary 获取考勤统计摘要
rules 查询考勤组与考勤规则
selfsetting 个人规则设置项(get 查询,save 更新,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知)
globalsetting 全局规则设置项(get 查询,save 更新,仅管理员可调用,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知)
vacation 查询当前用户假期规则列表、查询员工假期余额、查询假期余额变更记录`,
RunE: groupRunE,
}
// ── record ───────────────────────────────────────────────
attendanceRecordCmd := &cobra.Command{Use: "record", Short: "考勤记录", RunE: groupRunE}
attendanceRecordGetCmd := &cobra.Command{
Use: "get",
Short: "查询个人考勤详情",
Example: ` dws attendance record get --user USER_ID --date 2026-03-08 # 查询 userId: dws contact user search --query "姓名"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "user", "date"); err != nil {
return err
}
dateStr := mustGetFlag(cmd, "date")
t, err := time.ParseInLocation("2006-01-02", dateStr, time.Local)
if err != nil {
return fmt.Errorf("invalid date format, use YYYY-MM-DD (e.g. 2026-03-08): %w", err)
}
workDate := t.UnixMilli()
return callMCPTool("get_user_attendance_record", map[string]any{
"userId": mustGetFlag(cmd, "user"),
"workDate": workDate,
})
},
}
DeclareLeafMetadata(attendanceRecordGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "get_user_attendance_record",
CanonicalPath: "attendance.get_user_attendance_record",
CLIPath: "attendance record get",
PrimaryCLIPath: "attendance record get",
},
Description: "查询个人考勤详情",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "attendance", RPCName: "get_user_attendance_record"},
},
Selection: contract.SelectionSpec{
AgentSummary: "查询个人考勤详情",
UseWhen: []string{"需要查询指定用户某一天的考勤详情(打卡、班次、考勤组、工时、加班等,受权限限制)时"},
AvoidWhen: []string{
"要统计摘要时改用 summary",
"要改打卡结果时改用 boss-check",
},
Examples: []string{
"dws attendance record get --user 011769261608 --date 2026-03-08",
"dws attendance record get --user USER_ID --date 2026-03-08",
},
},
Parameters: []contract.ParamDecl{
{Name: "user", Property: "userId", Required: boolPtr(true)},
{Name: "date", Property: "workDate", Required: boolPtr(true)},
},
},
})
// ── check ────────────────────────────────────────────────
attendanceCheckCmd := &cobra.Command{Use: "check", Short: "打卡查询", RunE: groupRunE}
// MCP tool: query_check_result
attendanceCheckResultCmd := &cobra.Command{
Use: "result",
Short: "查询打卡结果",
Long: `查询指定用户的打卡结果记录。
返回每条记录含:用户 ID、工作日期、时间结果(Normal/Late/Early/Absenteeism/NotSigned)、
位置结果、计划打卡时间、实际打卡时间、打卡流水 ID。
时间跨度不超过 1 个月,最多 100 人。`,
Example: ` dws attendance check result --users userId1,userId2 --start 2026-04-01 --end 2026-04-30 --limit 50`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "start", "from"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "end", "to"); err != nil {
return err
}
fromStr := flagOrFallback(cmd, "start", "from")
toStr := flagOrFallback(cmd, "end", "to")
fromT, err := time.ParseInLocation("2006-01-02", fromStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --start date format, use YYYY-MM-DD: %w", err)
}
toT, err := time.ParseInLocation("2006-01-02", toStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --end date format, use YYYY-MM-DD: %w", err)
}
usersStr := mustGetFlag(cmd, "users")
var userIds []string
for _, u := range strings.Split(usersStr, ",") {
if s := strings.TrimSpace(u); s != "" {
userIds = append(userIds, s)
}
}
if len(userIds) > 100 {
return fmt.Errorf("userIds 不能超过 100 人")
}
offset, _ := cmd.Flags().GetInt("offset")
if offset < 0 {
return fmt.Errorf("offset 必须 >= 0")
}
limit, _ := cmd.Flags().GetInt("limit")
if limit < 1 || limit > 1000 {
return fmt.Errorf("limit 必须在 1-1000 之间")
}
return callMCPToolOnServer("attendance-wukong", "query_check_result", map[string]any{
"QueryCheckResultRequest": map[string]any{
"userIds": userIds,
"workDateFrom": fromT.Format("2006-01-02 15:04:05"),
"workDateTo": toT.Format("2006-01-02 15:04:05"),
"offset": offset,
"limit": limit,
},
})
},
}
DeclareLeafMetadata(attendanceCheckResultCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "check_result",
CanonicalPath: "attendance.check_result",
CLIPath: "attendance check result",
PrimaryCLIPath: "attendance check result",
},
Description: "查询打卡结果",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询打卡结果",
UseWhen: []string{"查询员工打卡结果或考勤结果"},
AvoidWhen: []string{
"查询原始打卡流水时使用 attendance check record",
"查询统计摘要时使用 attendance summary",
"修改打卡结果时使用 attendance boss-check",
},
Examples: []string{"dws attendance check result --users userId1,userId2 --start 2026-04-01 --end 2026-04-30 --limit 50"},
},
},
})
// MCP tool: query_check_record
attendanceCheckRecordCmd := &cobra.Command{
Use: "record",
Short: "查询打卡流水",
Long: `查询指定用户的打卡流水记录。
返回每条记录含:用户 ID、实际打卡时间、打卡地址、打卡经纬度、
打卡类型(OnDuty/OffDuty)、定位方式(Map/Wifi/etc)。
时间跨度不超过 1 个月。`,
Example: ` dws attendance check record --users userId1 --start 2026-04-01 --end 2026-04-30`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "start", "from"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "end", "to"); err != nil {
return err
}
fromStr := flagOrFallback(cmd, "start", "from")
toStr := flagOrFallback(cmd, "end", "to")
fromT, err := time.ParseInLocation("2006-01-02", fromStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --start date format, use YYYY-MM-DD: %w", err)
}
toT, err := time.ParseInLocation("2006-01-02", toStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --end date format, use YYYY-MM-DD: %w", err)
}
usersStr := mustGetFlag(cmd, "users")
var userIds []string
for _, u := range strings.Split(usersStr, ",") {
if s := strings.TrimSpace(u); s != "" {
userIds = append(userIds, s)
}
}
return callMCPToolOnServer("attendance-wukong", "query_check_record", map[string]any{
"QueryCheckRecordRequest": map[string]any{
"userIds": userIds,
"checkDateFrom": fromT.Format("2006-01-02 15:04:05"),
"checkDateTo": toT.Format("2006-01-02 15:04:05"),
},
})
},
}
DeclareLeafMetadata(attendanceCheckRecordCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "check_record",
CanonicalPath: "attendance.check_record",
CLIPath: "attendance check record",
PrimaryCLIPath: "attendance check record",
},
Description: "查询打卡记录",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询打卡记录",
UseWhen: []string{"查询员工打卡流水或打卡记录"},
AvoidWhen: []string{
"查询考勤详情时使用 attendance record get",
"查询考勤统计摘要时使用 attendance summary",
"修改打卡结果时使用 attendance boss-check",
},
Examples: []string{"dws attendance check record --users userId1 --start 2026-04-01 --end 2026-04-30"},
},
},
})
// ── approve ────────────────────────────────────────────────
attendanceApproveCmd := &cobra.Command{Use: "approve", Short: "审批单查询", RunE: groupRunE}
// 审批类型关键词到 bizType 数字映射
// 注意:服务端 bizType=2 同时覆盖 出差 与 外出(合并为同一类),
// 故 trip / travel / business_trip / 出差 / 外出 全部映射到 2。
// 如需在提交入口区分外出(TRAVEL)与出差(OUT),请使用 attendance approve templates。
approveTypeMapping := map[string]int{
"overtime": 1, // 加班
"加班": 1,
"trip": 2, // 出差/外出(合并)
"travel": 2,
"business_trip": 2,
"business-trip": 2,
"出差": 2,
"外出": 2,
"leave": 3, // 请假
"请假": 3,
"patch": 4, // 补卡
"repair_check": 4,
"repair-check": 4,
"补卡": 4,
}
approveTemplateTypeMapping := map[string]string{
"repair_check": "REPAIR_CHECK",
"repair-check": "REPAIR_CHECK",
"patch": "REPAIR_CHECK",
"补卡": "REPAIR_CHECK",
"leave": "LEAVE",
"请假": "LEAVE",
"overtime": "OVERTIME",
"加班": "OVERTIME",
"travel": "TRAVEL",
"外出": "TRAVEL",
"out": "OUT",
"trip": "OUT",
"business_trip": "OUT",
"business-trip": "OUT",
"出差": "OUT",
}
// MCP tool: query_user_approve
attendanceApproveListCmd := &cobra.Command{
Use: "list",
Short: "查询用户审批单(补卡/加班/请假/出差外出)",
Long: `查询指定用户的考勤业务审批单记录(补卡 / 加班 / 请假 / 出差外出)。
审批类型 --types 支持以下关键词(多个用英文逗号分隔):
- overtime / 加班 → bizType=1
- trip / travel / business_trip / business-trip / 出差 / 外出 → bizType=2(出差与外出在查询接口合并为同一类,不再细分)
- leave / 请假 → bizType=3
- patch / repair-check / repair_check / 补卡 → bizType=4
说明:
- 服务端查询接口 bizType=2 同时覆盖出差与外出,传入 trip / travel / 出差 / 外出 任一别名都会返回这两类合并的记录;
- 如需在提交入口区分外出(TRAVEL)与出差(OUT),请改用 dws attendance approve templates --type travel|out;
- 返回每条记录含:用户 ID、审批标签、审批子类型、审批类型、生效时间、时长、时长单位、流程实例 ID。`,
Example: ` dws attendance approve list --users userId1 --types overtime,leave --start 2026-04-01 --end 2026-04-30
dws attendance approve list --users userId1 --types trip --start 2026-04-01 --end 2026-04-30 # 同时返回出差与外出
dws attendance approve list --users userId1 --types 加班,请假,补卡 --start 2026-04-01 --end 2026-04-30`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users", "types"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "start", "from"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "end", "to"); err != nil {
return err
}
fromStr := flagOrFallback(cmd, "start", "from")
toStr := flagOrFallback(cmd, "end", "to")
fromT, err := time.ParseInLocation("2006-01-02", fromStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --start date format, use YYYY-MM-DD: %w", err)
}
toT, err := time.ParseInLocation("2006-01-02", toStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --end date format, use YYYY-MM-DD: %w", err)
}
usersStr := mustGetFlag(cmd, "users")
var userIds []string
for _, u := range strings.Split(usersStr, ",") {
if s := strings.TrimSpace(u); s != "" {
userIds = append(userIds, s)
}
}
typesStr := mustGetFlag(cmd, "types")
var bizTypes []int
for _, t := range strings.Split(typesStr, ",") {
if s := strings.TrimSpace(t); s != "" {
bizType, ok := approveTypeMapping[strings.ToLower(s)]
if !ok {
return fmt.Errorf("无效的审批类型: %s,支持: overtime/加班、trip/travel/business_trip/出差/外出(查询时合并为一类)、leave/请假、patch/repair-check/补卡", s)
}
bizTypes = append(bizTypes, bizType)
}
}
return callMCPToolOnServer("attendance-wukong", "query_user_approve", map[string]any{
"QueryUserApproveRequest": map[string]any{
"userIds": userIds,
"bizTypes": bizTypes,
"fromDate": fromT.Format("2006-01-02 15:04:05"),
"toDate": toT.Format("2006-01-02 15:04:05"),
},
})
},
}
DeclareLeafMetadata(attendanceApproveListCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "approve_list",
CanonicalPath: "attendance.approve_list",
CLIPath: "attendance approve list",
PrimaryCLIPath: "attendance approve list",
},
Description: "查询考勤相关审批单记录",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询考勤相关审批单记录",
UseWhen: []string{"查询补卡、加班、请假、出差外出等考勤审批记录"},
AvoidWhen: []string{
"需要获取可提交审批的模板/跳转链接时使用 attendance approve templates",
"查询实际打卡记录时使用 attendance record get 或 attendance check record",
"查询假期余额时使用 attendance vacation balance",
},
Examples: []string{
"dws attendance approve list --users userId1 --types overtime,leave --start 2026-04-01 --end 2026-04-30",
"dws attendance approve list --users userId1 --types trip --start 2026-04-01 --end 2026-04-30",
},
},
},
})
// MCP tool: query_at_approve_template
attendanceApproveTemplatesCmd := &cobra.Command{
Use: "templates",
Short: "查询补卡/请假/加班/外出/出差审批提交链接",
Long: `当用户需要提交补卡、请假、加班、外出、出差时,查询对应考勤审批表单模板,返回审批提交跳转链接。
审批类型支持:repair-check/patch/补卡、leave/请假、overtime/加班、travel/外出、out/trip/出差,也支持直接传 REPAIR_CHECK、LEAVE、OVERTIME、TRAVEL、OUT。
向用户展示提交入口时,不要直接裸露 submitUrl,应使用 Markdown 可点击链接格式:[表单名称](submitUrl)。`,
Example: ` dws attendance approve templates --type leave
dws attendance approve templates --type REPAIR_CHECK
dws attendance approve templates --type 加班
dws attendance approve templates --type travel # 外出,等价 --type TRAVEL
dws attendance approve templates --type 出差 # 出差,等价 --type OUT`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "type"); err != nil {
return err
}
approveTypeInput := strings.TrimSpace(mustGetFlag(cmd, "type"))
approveType, ok := approveTemplateTypeMapping[strings.ToLower(approveTypeInput)]
if !ok {
approveType = strings.ToUpper(approveTypeInput)
}
switch approveType {
case "REPAIR_CHECK", "LEAVE", "OVERTIME", "TRAVEL", "OUT":
default:
return fmt.Errorf("无效的审批类型: %s,支持: repair-check/patch/补卡、leave/请假、overtime/加班、travel/外出、out/trip/出差,或 REPAIR_CHECK/LEAVE/OVERTIME/TRAVEL/OUT", approveTypeInput)
}
return callMCPToolOnServer("attendance-wukong", "query_at_approve_template", map[string]any{
"approveType": approveType,
})
},
}
DeclareLeafMetadata(attendanceApproveTemplatesCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "approve_templates",
CanonicalPath: "attendance.approve_templates",
CLIPath: "attendance approve templates",
PrimaryCLIPath: "attendance approve templates",
},
Description: "查询考勤审批模板和提交入口",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询考勤审批模板和提交入口",
UseWhen: []string{"用户要发起补卡、请假、加班、外出或出差审批前查询可用模板"},
AvoidWhen: []string{
"查看已存在审批单记录时使用 attendance approve list",
"查询考勤记录/统计时使用 attendance record get 或 attendance summary",
"配置考勤规则时使用 attendance group/globalsetting/class 相关命令",
},
Examples: []string{
"dws attendance approve templates --type leave",
"dws attendance approve templates --type REPAIR_CHECK",
},
},
},
})
// ── shift ────────────────────────────────────────────────
attendanceShiftCmd := &cobra.Command{
Use: "shift",
Short: "班次查询",
Long: `查询员工班次信息(班次 = 员工当天的打卡安排)。
返回每条记录含:用户 ID、工作日期、打卡类型(OnDuty/OffDuty)、计划打卡时间、是否休息日。`,
RunE: groupRunE,
}
// MCP tool: batch_get_employee_shifts
attendanceShiftListCmd := &cobra.Command{
Use: "list",
Short: "批量查询员工班次信息",
Long: `批量查询多个员工在指定日期的考勤班次信息。
返回每条记录含:用户 ID、工作日期、打卡类型(OnDuty/OffDuty)、
计划打卡时间、是否休息日。间隔不超过 7 天,最多 50 人。`,
Example: ` dws attendance shift list --users userId1,userId2 --start 2026-03-03 --end 2026-03-07 # 查询 userId: dws contact user search --query "姓名"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users", "start", "end"); err != nil {
return err
}
startStr, endStr := mustGetFlag(cmd, "start"), mustGetFlag(cmd, "end")
startT, err := time.ParseInLocation("2006-01-02", startStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --start date format, use YYYY-MM-DD: %w", err)
}
endT, err := time.ParseInLocation("2006-01-02", endStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --end date format, use YYYY-MM-DD: %w", err)
}
userIds := parseUserList(mustGetFlag(cmd, "users"))
return callMCPTool("batch_get_employee_shifts", map[string]any{
"userIds": userIds,
"fromDateTime": startT.UnixMilli(),
"toDateTime": endT.UnixMilli(),
})
},
}
DeclareLeafMetadata(attendanceShiftListCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "batch_get_employee_shifts",
CanonicalPath: "attendance.batch_get_employee_shifts",
CLIPath: "attendance shift list",
PrimaryCLIPath: "attendance shift list",
},
Description: "批量查询员工班次信息",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "attendance", RPCName: "batch_get_employee_shifts"},
},
Selection: contract.SelectionSpec{
AgentSummary: "批量查询员工班次信息",
UseWhen: []string{"需要批量查询多个员工在日期范围内的考勤班次(userId/workDate/checkType/planCheckTime/isRest)时"},
AvoidWhen: []string{
"要查实际排班记录时改用 schedule get",
"要查班次定义时改用 class search/get",
"要查考勤组规则时改用 rules",
},
Examples: []string{"dws attendance shift list --users userId1,userId2 --start 2026-03-03 --end 2026-03-07"},
},
Parameters: []contract.ParamDecl{
{Name: "users", Property: "userIds", Required: boolPtr(true), InterfaceType: "array"},
{Name: "start", Property: "fromDateTime", Required: boolPtr(true)},
{Name: "end", Property: "toDateTime", Required: boolPtr(true)},
},
},
})
// ── class ────────────────────────────────────────────────
attendanceClassCmd := &cobra.Command{Use: "class", Short: "班次规则", RunE: groupRunE}
// MCP tool: get_class_list
attendanceClassSearchCmd := &cobra.Command{
Use: "search",
Short: "查询当前用户可管理的所有班次详情",
Long: `查询当前用户有权管理的班次列表(班次定义),支持按名称搜索和类型过滤。
所有参数均为可选项:
--page 页码(从 1 开始,默认 1)
--limit 每页条数(最大 200,默认 20)
--query 班次名称关键字,模糊搜索
--filter-type 班次类型:ALL(全部班次)/ MINE_OWN(我负责的),默认 ALL`,
Example: ` dws attendance class search
dws attendance class search --query "早班" --filter-type MINE_OWN
dws attendance class search --page 1 --limit 50`,
RunE: func(cmd *cobra.Command, args []string) error {
pageQuery := map[string]any{}
if v := intFlagOrFallback(cmd, "page", "page-index"); v > 0 {
pageQuery["pageIndex"] = v
}
if v := intFlagOrFallback(cmd, "limit", "page-size", "size"); v > 0 {
pageQuery["pageSize"] = v
}
shiftParam := map[string]any{}
if v := flagOrFallback(cmd, "query", "name"); v != "" {
shiftParam["searchName"] = v
}
if v, _ := cmd.Flags().GetString("filter-type"); v != "" {
shiftParam["filterType"] = v
}
params := map[string]any{}
if len(pageQuery) > 0 {
params["PageQuery"] = pageQuery
}
if len(shiftParam) > 0 {
params["ShiftParamVO"] = shiftParam
}
return callMCPToolOnServer("attendance-wukong", "get_class_list", params)
},
}
DeclareLeafMetadata(attendanceClassSearchCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "class_search",
CanonicalPath: "attendance.class_search",
CLIPath: "attendance class search",
PrimaryCLIPath: "attendance class search",
},
Description: "查询班次定义列表",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询班次定义列表",
UseWhen: []string{"搜索或列出可用班次/班次ID"},
AvoidWhen: []string{
"已知道班次ID并要看详情时使用 attendance class get",
"查询员工排班记录时使用 attendance schedule get",
"创建或修改班次定义时使用 attendance class create/update",
},
Examples: []string{
"dws attendance class search",
"dws attendance class search --query \"早班\" --filter-type MINE_OWN",
},
},
},
})
// MCP tool: get_class_detail
attendanceClassGetCmd := &cobra.Command{
Use: "get",
Short: "根据班次 ID 查询班次详情",
Long: `调用 MCP 工具 get_class_detail,根据班次 ID 查询该班次的详细信息。--class-id 必填。`,
Example: ` dws attendance class get --class-id 1170996821`,
RunE: func(cmd *cobra.Command, args []string) error {
classID, _ := cmd.Flags().GetInt64("class-id")
if classID == 0 {
return fmt.Errorf("flag --class-id is required")
}
return callMCPToolOnServer("attendance-wukong", "get_class_detail", map[string]any{
"classId": classID,
})
},
}
DeclareLeafMetadata(attendanceClassGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "class_get",
CanonicalPath: "attendance.class_get",
CLIPath: "attendance class get",
PrimaryCLIPath: "attendance class get",
},
Description: "查询单个班次定义详情",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询单个班次定义详情",
UseWhen: []string{"已取得班次ID,需要查看班次配置"},
AvoidWhen: []string{
"不知道班次ID时先用 attendance class search",
"查询员工排班记录时使用 attendance schedule get",
"修改班次定义时使用 attendance class update",
},
Examples: []string{
"dws attendance class get --class-id 1170996821",
"dws attendance class get --class-id 1170996821 --format json",
},
},
},
})
// MCP tool: create_class_setting
attendanceClassCreateCmd := &cobra.Command{
Use: "create",
Short: "创建班次",
Long: `调用 MCP 工具 create_class_setting,创建一个新的班次。
--name 和 --sections 必填,其余参数可选。由于保存班次耗时较久,指令需要带上 --timeout 10
--yes 需要用户二次确认之后才允许带入,参考attendance.md中对于创建班次的说明
常用 flag:
--name 班次名称(必填)
--owner 班次负责人 userId(可选)
--class-vo 完整 TopAtClassVO JSON 字符串(可选,用于传入复杂结构)
当使用 --class-vo 传入完整 JSON 时,--name 和 --owner 仍可覆写 JSON 中的同名字段。
checkTime 字段统一使用 "HH:mm" 格式(如 "09:00"),CLI 自动转换为服务端所需的时间戳。
--class-vo JSON 可用字段说明:
name string 班次名称(必填)
owner string 班次负责人 userId
sections []object 班次上下班时间段(必填),支持多段上下班,每段包含 times 数组:
- times []object 每段有且只能有两个对象(上班+下班),每个对象字段:
checkType string 打卡类型:OnDuty(上班)/ OffDuty(下班)(必填)
checkTime string 打卡时间,格式 "HH:mm"(必填,如 "09:00"、"17:30")
across number 是否跨天:0(不跨天)/ 1(跨天)(必填)
freeCheck bool 是否免打卡
beginMin number 允许最早提前打卡时间(分钟,-1 表示不限制)
endMin number 允许最晚打卡时间(分钟,-1 表示不限制)
setting object 班次其他配置:
- seriousLateMinutes number 晚到多少分钟记为严重迟到
- absenteeismLateMinutes number 晚到多少分钟记为旷工迟到
- attendDays number 本班次记为多少天出勤(支持两位小数)
- topRestTimeList []object 班次休息时间(仅 sections 只有一段上下班时可用,最多 3 段休息时间):
checkType string 休息类型:OnDuty(开始休息)/ OffDuty(结束休息)
checkTime string 休息时间,格式 "HH:mm"(如 "12:00"、"13:00")
across number 是否跨天:0/1`,
Example: ` dws attendance class create --name "早班" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:00","across":0},{"checkType":"OffDuty","checkTime":"17:00","across":0}]}]}' --timeout 10
dws attendance class create --name "晚班" --owner userId1 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"14:00","across":0},{"checkType":"OffDuty","checkTime":"22:00","across":0}]}]}' --timeout 10
# 带休息时段(12:00-13:00 午休)
dws attendance class create --name "标准班" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10`,
RunE: func(cmd *cobra.Command, args []string) error {
// 先解析 --class-vo JSON,作为基础
classVO := map[string]any{}
if raw, _ := cmd.Flags().GetString("class-vo"); raw != "" {
if err := json.Unmarshal([]byte(raw), &classVO); err != nil {
return fmt.Errorf("invalid --class-vo JSON: %w", err)
}
}
// 单字段 flag 覆写
if cmd.Flags().Changed("name") {
v, _ := cmd.Flags().GetString("name")
classVO["name"] = v
}
if cmd.Flags().Changed("owner") {
v, _ := cmd.Flags().GetString("owner")
classVO["owner"] = v
}
// 校验必填字段
if classVO["name"] == nil || classVO["name"] == "" {
return fmt.Errorf("--name 是必填项,请指定班次名称")
}
if classVO["sections"] == nil {
return fmt.Errorf("--class-vo 中必须包含 sections 字段(班次上下班时间段)")
}
// 自动转换 checkTime:"HH:mm" → 毫秒时间戳
convertClassCheckTime(classVO)
return callMCPToolOnServer("attendance-wukong", "create_class_setting", map[string]any{
"TopAtClassVO": classVO,
})
},
}
DeclareLeafMetadata(attendanceClassCreateCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "class_create",
CanonicalPath: "attendance.class_create",
CLIPath: "attendance class create",
PrimaryCLIPath: "attendance class create",
},
Description: "创建班次定义",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "创建班次定义",
UseWhen: []string{"需要新增班次及上下班时间规则,且名称与 class-vo 已确认时"},
AvoidWhen: []string{
"查询已有班次时使用 attendance class search/get",
"修改已有班次时使用 attendance class update",
"导入员工排班时使用 attendance schedule import",
},
Examples: []string{"dws attendance class create --name \"早班\" --class-vo '{\"sections\":[{\"times\":[{\"checkType\":\"OnDuty\",\"checkTime\":\"08:00\",\"across\":0},{\"checkType\":\"OffDuty\",\"checkTime\":\"17:00\",\"across\":0}]}]}' --timeout 10"},
},
},
})
// MCP tool: update_class_setting
attendanceClassUpdateCmd := &cobra.Command{
Use: "update",
Short: "更新班次",
Long: `调用 MCP 工具 update_class_setting,更新一个已有的班次。
--class-id 必填;其余参数均可选,仅需对要修改的字段进行赋値,未传的字段会自动从已有配置补充。由于保存班次耗时较久,指令需要带上 --timeout 10
--yes 需要用户二次确认之后才允许带入,参考attendance.md中对于更新班次的说明
常用 flag:
--class-id 班次 ID(必填)
--name 班次名称(可选,不传则保持原值)
--owner 班次负责人 userId(可选,不传则保持原值)
--class-vo 完整 TopAtClassVO JSON 字符串(可选,用于传入复杂结构)
当使用 --class-vo 传入完整 JSON 时,--name 和 --owner 仍可覆写 JSON 中的同名字段。
checkTime 字段统一使用 "HH:mm" 格式(如 "09:00"),CLI 自动转换为服务端所需的时间戳。
--class-vo JSON 可用字段说明:
name string 班次名称(可选,不传则保持原值)
owner string 班次负责人 userId(可选,不传则保持原值)
sections []object 班次上下班时间段(可选,不传则保持原值),支持多段上下班,每段包含 times 数组:
- times []object 每段有且只能有两个对象(上班+下班),每个对象字段:
checkType string 打卡类型:OnDuty(上班)/ OffDuty(下班)(必填)
checkTime string 打卡时间,格式 "HH:mm"(必填,如 "09:00"、"17:30")
across number 是否跨天:0(不跨天)/ 1(跨天)(必填)
freeCheck bool 是否免打卡
beginMin number 允许最早提前打卡时间(分钟,-1 表示不限制)
endMin number 允许最晚打卡时间(分钟,-1 表示不限制)
setting object 班次其他配置:
- seriousLateMinutes number 晚到多少分钟记为严重迟到
- absenteeismLateMinutes number 晚到多少分钟记为旷工迟到
- attendDays number 本班次记为多少天出勤(支持两位小数)
- topRestTimeList []object 班次休息时间(仅 sections 只有一段上下班时可用,最多 3 段休息时间):
checkType string 休息类型:OnDuty(开始休息)/ OffDuty(结束休息)
checkTime string 休息时间,格式 "HH:mm"(如 "12:00"、"13:00")
across number 是否跨天:0/1`,
Example: ` dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10
dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10
# 带休息时段(12:00-13:00 午休)
dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10`,
RunE: func(cmd *cobra.Command, args []string) error {
// 校验 --class-id 必填
classID := int64FlagOrFallback(cmd, "class-id", "id")
if classID == 0 {
return fmt.Errorf("flag --class-id is required")
}
// 先解析 --class-vo JSON,作为基础
classVO := map[string]any{}
if raw, _ := cmd.Flags().GetString("class-vo"); raw != "" {
if err := json.Unmarshal([]byte(raw), &classVO); err != nil {
return fmt.Errorf("invalid --class-vo JSON: %w", err)
}
}
// 将 classId 注入到 classVO 中
classVO["id"] = classID
// 单字段 flag 覆写
if cmd.Flags().Changed("name") {
v, _ := cmd.Flags().GetString("name")
classVO["name"] = v
}
if cmd.Flags().Changed("owner") {
v, _ := cmd.Flags().GetString("owner")
classVO["owner"] = v
}
// 自动转换 checkTime:"HH:mm" → 毫秒时间戳
convertClassCheckTime(classVO)
return callMCPToolOnServer("attendance-wukong", "update_class_setting", map[string]any{
"TopAtClassVO": classVO,
})
},
}
DeclareLeafMetadata(attendanceClassUpdateCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "class_update",
CanonicalPath: "attendance.class_update",
CLIPath: "attendance class update",
PrimaryCLIPath: "attendance class update",
},
Description: "更新班次定义",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "更新班次定义",
UseWhen: []string{"需要修改已有班次的名称、上下班时间或规则,且 classId 与变更内容已确认时"},
AvoidWhen: []string{
"查询班次详情时使用 attendance class get",
"创建新班次时使用 attendance class create",
"调整员工排班时使用 attendance schedule import",
},
Examples: []string{
"dws attendance class update --class-id 1170996821 --name \"新早班\" --timeout 10",
"dws attendance class update --class-id 1170996821 --class-vo '{\"sections\":[{\"times\":[{\"checkType\":\"OnDuty\",\"checkTime\":\"08:30\",\"across\":0},{\"checkType\":\"OffDuty\",\"checkTime\":\"17:30\",\"across\":0}]}]}' --timeout 10",
},
},
},
})
// ── adjustment-rule ────────────────────────────────────
attendanceAdjustmentCmd := &cobra.Command{Use: "adjustment", Short: "补卡规则", RunE: groupRunE}
// MCP tool: get_adjustment_rule_detail
attendanceAdjustmentGetCmd := &cobra.Command{
Use: "get",
Short: "根据补卡规则主键 ID 查询补卡规则详情",
Long: `调用 MCP 工具 get_adjustment_rule_detail,根据补卡规则主键 ID 查询对应的补卡规则详情。
--adjustment-id 必填。注意:已被删除或被更新覆盖的补卡规则无法查询到。`,
Example: ` dws attendance adjustment get --adjustment-id 12345`,
RunE: func(cmd *cobra.Command, args []string) error {
adjustmentID, _ := cmd.Flags().GetInt64("adjustment-id")
if adjustmentID == 0 {
return fmt.Errorf("flag --adjustment-id is required")
}
return callMCPToolOnServer("attendance-wukong", "get_adjustment_rule_detail", map[string]any{
"adjustmentId": adjustmentID,
})
},
}
DeclareLeafMetadata(attendanceAdjustmentGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "adjustment_get",
CanonicalPath: "attendance.adjustment_get",
CLIPath: "attendance adjustment get",
PrimaryCLIPath: "attendance adjustment get",
},
Description: "查询单个补卡规则详情",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询单个补卡规则详情",
UseWhen: []string{"已取得补卡规则ID,需要查看规则详情"},
AvoidWhen: []string{
"不知道补卡规则ID时先用 attendance adjustment search",
"查询加班规则详情时使用 attendance overtime get",
"查询补卡审批单记录时使用 attendance approve list",
},
Examples: []string{
"dws attendance adjustment get --adjustment-id 12345",
"dws attendance adjustment get --adjustment-id 12345 --format json",
},
},
},
})
// MCP tool: get_adjustment_rule
attendanceAdjustmentSearchCmd := &cobra.Command{
Use: "search",
Short: "查询当前用户可管理的补卡规则列表",
Long: `根据用户权限查询补卡规则列表,入参封装在 ATRuleQueryParam 对象中。
--query 补卡规则名称关键字,模糊搜索(可选)
--page 页码(从 1 开始,默认 1)(可选)
--limit 每页条数(200 以内,默认 20)(可选)`,
Example: ` dws attendance adjustment search --page 1 --limit 20
dws attendance adjustment search --query "标准" --page 1 --limit 50`,
RunE: func(cmd *cobra.Command, args []string) error {
param := map[string]any{}
if v := flagOrFallback(cmd, "query", "name"); v != "" {
param["name"] = v
}
currentPage := intFlagOrFallback(cmd, "page", "current-page")
if currentPage <= 0 {
currentPage = 1
}
pageSize := intFlagOrFallback(cmd, "limit", "size")
if pageSize <= 0 {
pageSize = 20
}
param["currentPage"] = currentPage
param["pageSize"] = pageSize
return callMCPToolOnServer("attendance-wukong", "get_adjustment_rule", map[string]any{
"ATRuleQueryParam": param,
})
},
}
DeclareLeafMetadata(attendanceAdjustmentSearchCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "adjustment_search",
CanonicalPath: "attendance.adjustment_search",
CLIPath: "attendance adjustment search",
PrimaryCLIPath: "attendance adjustment search",
},
Description: "查询补卡规则列表",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询补卡规则列表",
UseWhen: []string{"需要查看有哪些补卡规则或筛选补卡规则"},
AvoidWhen: []string{
"已知道补卡规则ID并要看详情时使用 attendance adjustment get",
"查询加班规则时使用 attendance overtime search",
"查询员工打卡/考勤记录时使用 attendance record get 或 attendance check record",
},
Examples: []string{
"dws attendance adjustment search --page 1 --limit 20",
"dws attendance adjustment search --query \"标准\" --page 1 --limit 50",
},
},
Parameters: []contract.ParamDecl{
{Name: "limit", Required: boolPtr(false)},
{Name: "page", Required: boolPtr(false)},
},
},
})
// ── overtime-rule ──────────────────────────────────────
attendanceOvertimeCmd := &cobra.Command{Use: "overtime", Short: "加班规则", RunE: groupRunE}
// MCP tool: get_overtime_rule_detail
attendanceOvertimeGetCmd := &cobra.Command{
Use: "get",
Short: "根据加班规则主键 ID 查询加班规则详情",
Long: `调用 MCP 工具 get_overtime_rule_detail,根据加班规则主键 ID 查询对应的加班规则详情。
--overtime-id 必填。已被删除或更新覆盖的加班规则也可以查到。`,
Example: ` dws attendance overtime get --overtime-id 12345`,
RunE: func(cmd *cobra.Command, args []string) error {
overtimeID, _ := cmd.Flags().GetInt64("overtime-id")
if overtimeID == 0 {
return fmt.Errorf("flag --overtime-id is required")
}
return callMCPToolOnServer("attendance-wukong", "get_overtime_rule_detail", map[string]any{
"overtimeId": overtimeID,
})
},
}
DeclareLeafMetadata(attendanceOvertimeGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "overtime_get",
CanonicalPath: "attendance.overtime_get",
CLIPath: "attendance overtime get",
PrimaryCLIPath: "attendance overtime get",
},
Description: "查询单个加班规则详情",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询单个加班规则详情",
UseWhen: []string{"已取得加班规则ID,需要查看规则详情"},
AvoidWhen: []string{
"不知道加班规则ID时先用 attendance overtime search",
"查询补卡规则详情时使用 attendance adjustment get",
"查询员工班次时使用 attendance shift list",
},
Examples: []string{
"dws attendance overtime get --overtime-id 12345",
"dws attendance overtime get --overtime-id 12345 --format json",
},
},
},
})
// MCP tool: get_overtime_rule
attendanceOvertimeSearchCmd := &cobra.Command{
Use: "search",
Short: "查询当前用户可管理的加班规则列表",
Long: `根据用户权限查询加班规则列表,入参封装在 ATRuleQueryParam 对象中。
--query 加班规则名称关键字,模糊搜索(可选)
--page 页码(从 1 开始,默认 1)(可选)
--limit 每页条数(200 以内,默认 20)(可选)`,
Example: ` dws attendance overtime search --page 1 --limit 20
dws attendance overtime search --query "节假日" --page 1 --limit 50`,
RunE: func(cmd *cobra.Command, args []string) error {
param := map[string]any{}
if v := flagOrFallback(cmd, "query", "name"); v != "" {
param["name"] = v
}
currentPage := intFlagOrFallback(cmd, "page", "current-page")
if currentPage <= 0 {
currentPage = 1
}
pageSize := intFlagOrFallback(cmd, "limit", "size")
if pageSize <= 0 {
pageSize = 20
}
param["currentPage"] = currentPage
param["pageSize"] = pageSize
return callMCPToolOnServer("attendance-wukong", "get_overtime_rule", map[string]any{
"ATRuleQueryParam": param,
})
},
}
DeclareLeafMetadata(attendanceOvertimeSearchCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "overtime_search",
CanonicalPath: "attendance.overtime_search",
CLIPath: "attendance overtime search",
PrimaryCLIPath: "attendance overtime search",
},
Description: "查询加班规则列表",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询加班规则列表",
UseWhen: []string{"需要查看可用加班规则或筛选加班规则"},
AvoidWhen: []string{
"已知道加班规则ID并要看详情时使用 attendance overtime get",
"查询补卡规则时使用 attendance adjustment search",
"查询加班审批单记录时使用 attendance approve list",
},
Examples: []string{
"dws attendance overtime search --page 1 --limit 20",
"dws attendance overtime search --query \"节假日\" --page 1 --limit 50",
},
},
Parameters: []contract.ParamDecl{
{Name: "limit", Required: boolPtr(false)},
{Name: "page", Required: boolPtr(false)},
},
},
})
// ── group ──────────────────────────────────────────────
attendanceGroupCmd := &cobra.Command{Use: "group", Short: "考勤组", RunE: groupRunE}
// MCP tool: get_simple_groups
attendanceGroupSearchCmd := &cobra.Command{
Use: "search",
Short: "查询当前用户可管理的考勤组列表",
Long: `根据当前用户的权限和筛选条件获取考勤组简要信息。
CLI 会在未传筛选条件时补齐默认查询字段,在未传分页参数时补齐 page=1、limit=20,以满足服务端非空对象约束。
--query 考勤组名称关键字,模糊搜索
--type 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)
--query-position 是否查询地理定位和 Wifi 名称(默认 false)
--query-ble 是否查询蓝牙设备列表(默认 false)
--page 页码(从 1 开始,默认 1)
--limit 每页条数(200 以内,默认 20)`,
Example: ` dws attendance group search --query "研发"
dws attendance group search --type FIXED --limit 50
dws attendance group search --page 1 --limit 20`,
RunE: func(cmd *cobra.Command, args []string) error {
searchParam := map[string]any{}
if v := flagOrFallback(cmd, "query", "name"); v != "" {
searchParam["queryStr"] = v
}
if v, _ := cmd.Flags().GetString("type"); v != "" {
searchParam["type"] = v
}
if cmd.Flags().Changed("query-position") {
v, _ := cmd.Flags().GetBool("query-position")
searchParam["queryPositionAndWifiNames"] = v
}
if cmd.Flags().Changed("query-ble") {
v, _ := cmd.Flags().GetBool("query-ble")
searchParam["queryBleDeviceList"] = v
}
// 服务端要求 param 不能为 null,如果用户未传任何过滤条件,补齐两个 bool 默认字段
if len(searchParam) == 0 {
searchParam["queryPositionAndWifiNames"] = false
searchParam["queryBleDeviceList"] = false
}
pageQuery := map[string]any{}
if cmd.Flags().Changed("page") || cmd.Flags().Changed("page-index") {
if v := intFlagOrFallback(cmd, "page", "page-index"); v > 0 {
pageQuery["pageIndex"] = v
}
}
if cmd.Flags().Changed("limit") || cmd.Flags().Changed("size") {
if v := intFlagOrFallback(cmd, "limit", "size"); v > 0 {
pageQuery["pageSize"] = v
}
}
// PageQuery 两个子字段均必填,未显式传参时补全默认值
if _, ok := pageQuery["pageIndex"]; !ok {
pageQuery["pageIndex"] = 1
}
if _, ok := pageQuery["pageSize"]; !ok {
pageQuery["pageSize"] = 20
}
return callMCPToolOnServer("attendance-wukong", "get_simple_groups", map[string]any{
"param": searchParam,
"pageQuery": pageQuery,
})
},
}
DeclareLeafMetadata(attendanceGroupSearchCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "group_search",
CanonicalPath: "attendance.group_search",
CLIPath: "attendance group search",
PrimaryCLIPath: "attendance group search",
},
Description: "查询考勤组列表",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询考勤组列表",
UseWhen: []string{"按权限或条件查看考勤组简要信息"},
AvoidWhen: []string{
"已知道考勤组ID并要看详情时使用 attendance group get",
"只需要部分字段时使用 attendance group filtered-get",
"创建/修改考勤组时使用 attendance group create/update",
},
Examples: []string{
"dws attendance group search --query \"<考勤组名称>\" --type TURN --format json",
"dws attendance group search --query \"研发组\" --type TURN --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "limit", Required: boolPtr(false)},
{Name: "page", Required: boolPtr(false)},
},
},
})
// MCP tool: get_group_detail
attendanceGroupGetCmd := &cobra.Command{
Use: "get",
Short: "根据考勤组 ID 查询考勤组全量信息",
Long: `调用 MCP 工具 get_group_detail,根据考勤组 ID 查询该考勤组的全量信息。
--group-id 必填。如果只需查询成员、打卡地址、蓝牙、Wifi 子集,请使用 group filtered-get 以节省查询成本。`,
Example: ` dws attendance group get --group-id 123456`,
RunE: func(cmd *cobra.Command, args []string) error {
groupID := int64FlagOrFallback(cmd, "group-id", "id")
if groupID == 0 {
return fmt.Errorf("flag --group-id (or --id) is required")
}
return callMCPToolOnServer("attendance-wukong", "get_group_detail", map[string]any{
"groupId": groupID,
})
},
}
DeclareLeafMetadata(attendanceGroupGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "group_get",
CanonicalPath: "attendance.group_get",
CLIPath: "attendance group get",
PrimaryCLIPath: "attendance group get",
},
Description: "查询考勤组完整详情",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询考勤组完整详情",
UseWhen: []string{"已取得考勤组ID,需要查看完整配置"},
AvoidWhen: []string{
"不知道考勤组ID时先用 attendance group search",
"只需要成员/地址等子集时使用 attendance group filtered-get",
"修改考勤组配置时使用 attendance group update",
},
Examples: []string{"dws attendance group get --group-id <groupId> --format json"},
},
},
})
// MCP tool: get_group_filtered_detail
attendanceGroupFilteredGetCmd := &cobra.Command{
Use: "filtered-get",
Short: "根据考勤组 ID 按需查询成员/打卡地址/蓝牙/Wifi 信息",
Long: `调用 MCP 工具 get_group_filtered_detail,根据考勤组 ID 仅返回关心的字段子集,
强烈建议在仅需查询成员信息、打卡地址、蓝牙、Wifi 时使用,节省查询成本。
--group-id 必填;四个过滤字段均可选(不传则默认 false,即不查询该字段)。
--member 是否查询考勤组成员信息
--position 是否查询打卡地址
--wifi 是否查询打卡 Wifi
--bles 是否查询打卡蓝牙`,
Example: ` dws attendance group filtered-get --group-id 123456 --member
dws attendance group filtered-get --group-id 123456 --position --wifi`,
RunE: func(cmd *cobra.Command, args []string) error {
groupID := int64FlagOrFallback(cmd, "group-id", "id")
if groupID == 0 {
return fmt.Errorf("flag --group-id (or --id) is required")
}
member, _ := cmd.Flags().GetBool("member")
position, _ := cmd.Flags().GetBool("position")
wifi, _ := cmd.Flags().GetBool("wifi")
bles, _ := cmd.Flags().GetBool("bles")
return callMCPToolOnServer("attendance-wukong", "get_group_filtered_detail", map[string]any{
"groupId": groupID,
"GroupResultFilter": map[string]any{
"includeMember": member,
"includePosition": position,
"includeWifi": wifi,
"includeBles": bles,
},
})
},
}
DeclareLeafMetadata(attendanceGroupFilteredGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "group_filtered_get",
CanonicalPath: "attendance.group_filtered_get",
CLIPath: "attendance group filtered-get",
PrimaryCLIPath: "attendance group filtered-get",
},
Description: "查询考勤组指定字段子集",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询考勤组指定字段子集",
UseWhen: []string{"只需要考勤组成员、打卡地址、蓝牙等部分字段,减少返回体积"},
AvoidWhen: []string{
"需要完整考勤组配置时使用 attendance group get",
"不知道考勤组ID时先用 attendance group search",
"需要修改成员时使用 attendance group update-members",
},
Examples: []string{"dws attendance group filtered-get --group-id <groupId> --member --format json"},
},
},
})
// MCP tool: update_group_member
attendanceGroupUpdateMembersCmd := &cobra.Command{
Use: "update-members",
Short: "更新考勤组成员(添加/删除考勤人员、部门、无需考勤人员)",
Long: `调用 MCP 工具 update_group_member,对指定考勤组的成员进行增删操作。
--group-id 必填;所有成员/部门参数均可选,但至少需要传一个变更项。由于保存考勤组耗时较久,指令需要带上--timeout 10
每次调用各参数最多传 20 个 ID。
--yes 需要用户二次确认之后才允许带入,参考attendance.md中对于更新考勤组成员的说明
--add-users 添加考勤人员 userId 列表,逗号分隔
--remove-users 删除考勤人员 userId 列表,逗号分隔
--add-extra-users 添加无需考勤的人员 userId 列表,逗号分隔
--remove-extra-users 删除无需考勤的成员 userId 列表,逗号分隔
--add-depts 添加考勤部门 ID 列表,逗号分隔,若要添加全公司,根部门id为-1
--remove-depts 删除考勤部门 ID 列表,逗号分隔,全公司根部门id为-1`,
Example: ` dws attendance group update-members --group-id 123456 --add-users userId1,userId2 --timeout 10
dws attendance group update-members --group-id 123456 --remove-users userId1 --timeout 10
dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2 --timeout 10`,
RunE: func(cmd *cobra.Command, args []string) error {
groupID, _ := cmd.Flags().GetInt64("group-id")
if groupID == 0 {
return fmt.Errorf("flag --group-id is required")
}
updateParam := map[string]any{}
if v, _ := cmd.Flags().GetString("add-users"); v != "" {
updateParam["addUsers"] = parseUserList(v)
}
if v, _ := cmd.Flags().GetString("remove-users"); v != "" {
updateParam["removeUsers"] = parseUserList(v)
}
if v, _ := cmd.Flags().GetString("add-extra-users"); v != "" {
updateParam["addExtraUsers"] = parseUserList(v)
}
if v, _ := cmd.Flags().GetString("remove-extra-users"); v != "" {
updateParam["removeExtraUsers"] = parseUserList(v)
}
if v, _ := cmd.Flags().GetString("add-depts"); v != "" {
updateParam["addDepts"] = parseUserList(v)
}
if v, _ := cmd.Flags().GetString("remove-depts"); v != "" {
updateParam["removeDepts"] = parseUserList(v)
}
if len(updateParam) == 0 {
return fmt.Errorf("至少需要指定一个变更项:--add-users / --remove-users / --add-extra-users / --remove-extra-users / --add-depts / --remove-depts")
}
return callMCPToolOnServer("attendance-wukong", "update_group_member", map[string]any{
"groupId": groupID,
"updateParam": updateParam,
})
},
}
DeclareLeafMetadata(attendanceGroupUpdateMembersCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "group_update_members",
CanonicalPath: "attendance.group_update_members",
CLIPath: "attendance group update-members",
PrimaryCLIPath: "attendance group update-members",
},
Description: "增删考勤组成员",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "增删考勤组成员",
UseWhen: []string{"需要向考勤组添加或移除成员,且 groupId 与成员列表已确认时"},
AvoidWhen: []string{
"只查询成员信息时使用 attendance group filtered-get 或 group get",
"修改考勤组规则配置时使用 attendance group update",
"创建新考勤组时使用 attendance group create",
},
Examples: []string{
"dws attendance group update-members --group-id 123456 --add-users userId1,userId2",
"dws attendance group update-members --group-id 123456 --remove-users userId1",
},
},
},
})
// MCP tool: create_group_setting
attendanceGroupCreateCmd := &cobra.Command{
Use: "create",
Short: "创建考勤组",
Long: `调用 MCP 工具 create_group_setting,创建一个新的考勤组。
--name 和 --type 必填,其余参数可选(未传字段使用默认值)。由于创建考勤组耗时较久,指令需要带上 --timeout 10
--yes 需要用户二次确认之后才允许带入,参考attendance.md中对于创建考勤组的说明
常用 flag:
--name 考勤组名称(必填)
--type 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)(必填,显式校验)
--owner 考勤组主负责人 userId(可选)
--group-vo 完整 groupVO JSON 字符串(可选,用于传入复杂子对象,会与 --name/--type/--owner 合并)
当使用 --group-vo 传入完整 JSON 时,--name、--type、--owner 仍可覆写 JSON 中的同名字段。
【无条件必填字段】
name string 考勤组名称
type string 考勤组类型:FIXED / TURN / NONE
【条件必填字段(type=FIXED 固定班制时)】
workDayClassList []number 工作日班次列表,不能为空。
一共7个值,分别代表周日到周六每天的班次id,为0表示当天休息。
例如 [0,1279240003,0,0,0,0,0] 表示周一的班次id为1279240003,其余日子都是休息
defaultClassId number 默认班次 ID,不能为 null
条件必填字段通过 --group-vo JSON 传入,CLI 在 --type=FIXED 时会显式校验这些字段。
--group-vo 可用字段说明(与更新考勤组一致,均可选,只需包含要设置的字段):
【基础信息】
id number 考勤组id(创建时禁止传入,由服务端分配)
name string 考勤组名称(必填)
type string 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)(必填)
owner string 考勤组主负责人 userId
managerList []string 考勤组子负责人 userId 列表
skipHolidays bool 节假日自动排休(只有固定班制和自由工时考勤组生效)
defaultGroup bool 是否默认考勤组
classIds []number 所选班次 id 列表(只有固定班制和排班制才有班次,自由工时没有)
【打卡范围与定位】
trimDistance number 定位允许微调距离(米)
enablePositionOfGps bool 打卡是否允许开启 GPS 定位
enablePositionOfWifi bool 是否开启 wifi 定位
enablePositionOfBle bool 是否启用蓝牙定位
positions []object 考勤打卡地址列表(地址定位打卡用),每项字段:
- title string 地址标题
- address string 地址详情(完整地址文本)
- latitude number 纬度
- longitude number 经度
- offset number 该地址允许的打卡范围,单位米
wifis []object 打卡方式中的 wifi 详情列表,每项字段:
- ssid string wifi 的 ssid(wifi名称)
- macAddr string wifi 的 macAddr(MAC 地址)
- groupId number 考勤组id
bleDeviceVOList []object 已选择的蓝牙设备列表,每项字段:
- name string 设备名称
- deviceUid number 设备 uid
- sn string 设备 sn 序列号
- productType string 对外的设备类型
- devServId number 内部设备类型
【外勤打卡设置】
enableOutsideCheck bool 是否可以外勤打卡
enableOutsideCameraCheck bool 是否开启外勤打卡必须拍照
enableOutsideRemark bool 是否开启外勤打卡必须填写备注
enableOutsideApply bool 是否开启外勤打卡必须审批
forbidHideOutSideAddress bool 禁止隐藏外勤打卡地址的功能,false表示允许用户隐藏外勤打卡地址
enableOutSideUpdateNormalCheck bool 下班时间:允许外勤卡更新内勤卡
enableOnDutyNormalUpdateOutsideCheck bool 上班时间:允许内勤卡更新外勤卡
outsideCheckApproveMode string 外勤打卡需审批执行模式:
NO_NEED_APPROVE(无需审批)
APPROVE_FIRST(先审批后打卡)
CHECK_FIRST(先打卡后审批)
APPROVE_EVERYTIME(每次打卡都需要审批)
outSideCheckApplyType number 外勤审批范围:1(全天外勤打卡)/ 2(上班外勤打卡)/ 3(下班外勤打卡)
【打卡方式】
enableCameraCheck bool 是否开启拍照打卡
openCameraCheck bool 是否开启拍照打卡(与 enableCameraCheck 含义相同)
openFaceCheck bool 是否开启人脸打卡
enableFaceStrictMode bool 是否开启人脸严格模式(强制活体检测)
enableFaceBeauty bool 是否开启美颜
onlyMachineCheck bool 只允许考勤机打卡
permitMaxBeaconCount number 允许绑定的智点设备数量
disableCheckWhenRest bool 休息日打卡需审批(只在固定班制和排班制生效,true表示打卡需要提交审批单)
【固定班制设置(FIXED)】
defaultClassId number 固定班制的默认班次 id(type=FIXED 时必填)
workDayClassList []number 固定班制的工作日班次 id 列表,一共7个值,分别代表周日到周六每天的班次id,为0表示当天休息,例如[0,1279240003,0,0,0,0,0]表示周一的班次id为1279240003,其余日子都是休息
【排班制设置(TURN)】
disableCheckWithoutSchedule bool 未排班时员工可打卡(true=未排班时禁止打卡)
enableEmpSelectClass bool 未排班时,员工可选班次打卡
enableScheduleAutoMatch bool 未排班时,系统自动匹配班次
----以下是排班制设置排班周期的,非必要,不是所有排班制考勤组都需要设置,不设置就是按照classIds中的班次由管理员自行排班-----
cycleDays number 排班循环天数,支持大小周(14天)
startCycleDate number 用户设置的大小周开始时间(时间戳,毫秒)
cycleScheduleList []object 循环排班设置列表,每项字段:
- cycleName string 周期名字(如"第一周")
- groupId number 考勤组 id
- isValid string 是否有效:Y(有效)/ N(无效)
- itemList []object 排班设置列表,每项字段:
- classId number 班次 id
- className string 班次名称
- isValid string 是否有效(Y/N)
【自由工时设置(NONE)】
workDays []number 自由工时的工作日,如 [1,2,3,4,5] 表示周一到周五(1=周一,7=周日)
freeCheckDayStartMinOffset number 自由打卡每日开始时间,距离 0 点的分钟数(如 480 表示 08:00 开始)
freeCheckCoreTime number 自由工时考勤组的打卡最短工作时长(分钟)
freeCheckDemandWorkMinutes number 自由工时考勤组要求的打卡时长(分钟)
freeCheckSettingVO object 自由工时打卡设置,包含字段:
- freeCheckType string 自由工时打卡类型:
CYCLE(上下班交替打卡)
MAX_TIME_UPDATE(最大时间打卡,第一次为上班,后续均为下班卡)
- freeWorkDayLackSwitch bool 自由工时打卡规则:工作日没打卡记为缺卡(true=记缺卡)
- freeOnDutyLackMinOffset number 自由工时打卡规则:上班缺卡生成时间偏移分钟数
- freeOffDutyLackMinOffset number 自由工时打卡规则:下班缺卡生成时间偏移分钟数
- delimitOffsetMinutesBetweenDays number 自由工时前后两天的分割线(距 0 点分钟数,超过此时间则归属第二天)
- freeCheckGapVO object 自由工时/休息日打卡间隔设置:
- onOffCheckGapMinutes number 上班打卡后,多久才可以打下班卡(分钟)
- offOnCheckGapMinutes number 下班打卡后,多久才可以打上班卡(分钟)
freeGroupSpecialDayVO object 自由工时考勤组特殊日期设置,包含字段:
- specialOnDutyDays []number 特殊需要打卡日期的时间戳列表(毫秒,原本休息日需额外打卡的日期)
- specialOffDutyDays []number 特殊无需打卡日期的时间戳列表(毫秒,原本工作日可免打卡的日期)`,
Example: ` dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10
dws attendance group create --name "排班组" --type TURN --timeout 10
dws attendance group create --name "自由工时组" --type NONE --timeout 10`,
RunE: func(cmd *cobra.Command, args []string) error {
// 校验 --type 必填且合法
groupType, _ := cmd.Flags().GetString("type")
if groupType == "" {
return fmt.Errorf("--type 是必填项,考勤组类型必须为 FIXED(固定班制)、TURN(排班制)或 NONE(自由工时)")
}
validTypes := map[string]bool{"FIXED": true, "TURN": true, "NONE": true}
if !validTypes[groupType] {
return fmt.Errorf("--type 值 %q 不合法,必须为 FIXED(固定班制)、TURN(排班制)或 NONE(自由工时)", groupType)
}
// 校验 --name 必填
groupName, _ := cmd.Flags().GetString("name")
if groupName == "" {
return fmt.Errorf("--name 是必填项,请指定考勤组名称")
}
// 解析 --group-vo JSON,作为基础
groupVO := map[string]any{}
if raw, _ := cmd.Flags().GetString("group-vo"); raw != "" {
if err := json.Unmarshal([]byte(raw), &groupVO); err != nil {
return fmt.Errorf("invalid --group-vo JSON: %w", err)
}
}
// 单字段 flag 覆写(优先级高于 --group-vo)
if cmd.Flags().Changed("name") {
groupVO["name"] = groupName
}
if cmd.Flags().Changed("type") {
groupVO["type"] = groupType
}
if cmd.Flags().Changed("owner") {
v, _ := cmd.Flags().GetString("owner")
groupVO["owner"] = v
}
// type=FIXED 时校验条件必填字段
if groupVO["type"] == "FIXED" {
workDayClassList, ok := groupVO["workDayClassList"]
if !ok || workDayClassList == nil {
return fmt.Errorf("固定班制(FIXED)时 --group-vo 必须包含 workDayClassList(工作日班次列表,不能为空)")
}
if list, ok := workDayClassList.([]any); ok && len(list) == 0 {
return fmt.Errorf("workDayClassList 不能为空数组,至少需包含一个班次 ID")
}
if defaultClassID, ok := groupVO["defaultClassId"]; !ok || defaultClassID == nil {
return fmt.Errorf("固定班制(FIXED)时 --group-vo 必须包含 defaultClassId(默认班次 ID,不能为 null)")
}
}
// 调用创建接口并拿到响应文本,DryRun 走原有路径(预览参数不调用)。
payload := map[string]any{"groupVO": groupVO}
if deps.Caller.DryRun() {
return callMCPToolOnServer("attendance-wukong", "create_group_setting", payload)
}
ctx := context.Background()
respText, err := callMCPToolReturnTextOnServer(ctx, "attendance-wukong", "create_group_setting", payload)
if err != nil {
return err
}
// 输出响应(与公共 callMCPToolOnServer 路径保持一致)
var parsed any
parsedOk := json.Unmarshal([]byte(respText), &parsed) == nil
if deps.Caller.Format() == "json" && parsedOk {
if err := deps.Out.PrintJSON(parsed); err != nil {
return err
}
} else {
deps.Out.PrintRaw(respText)
}
// 仅在响应中同时拿到 corpId 与 groupId 时,额外输出钉钉 PC 端跳转链接
if parsedOk {
if respMap, ok := parsed.(map[string]any); ok {
printGroupModifyDeeplink(respMap)
}
}
return nil
},
}
DeclareLeafMetadata(attendanceGroupCreateCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "group_create",
CanonicalPath: "attendance.group_create",
CLIPath: "attendance group create",
PrimaryCLIPath: "attendance group create",
},
Description: "创建考勤组",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "创建考勤组",
UseWhen: []string{"需要新建考勤组并配置规则,且名称、类型与 group-vo 已确认时"},
AvoidWhen: []string{
"查询已有考勤组时使用 attendance group search/get",
"修改已有考勤组时使用 attendance group update",
"仅修改成员时使用 attendance group update-members",
},
Examples: []string{
"dws attendance group create --name \"研发考勤组\" --type FIXED --group-vo '{\"defaultClassId\":1170996821,\"workDayClassList\":[0,1170996821,0,0,0,0,0]}' --timeout 10",
"dws attendance group create --name \"自由工时分组\" --type NONE --timeout 10",
},
},
},
})
// MCP tool: update_group
attendanceGroupUpdateCmd := &cobra.Command{
Use: "update",
Short: "更新考勤组配置(仅修改需要变更的字段)",
Long: `调用 MCP 工具 update_group_setting,更新考勤组配置信息。
--group-id 必填;其余参数均可选,仅需对要修改的字段进行赋値,未传的字段会自动从已有配置补充。由于保存考勤组耗时较久,指令需要带上--timeout 10
--yes 需要用户二次确认之后才允许带入,参考attendance.md中对于更新考勤组配置的说明
常用简单字段可直接用 flag 传入;复杂子对象(如打卡地址、wifi、蓝牙设备、循环排班等)请用 --group-vo JSON 字符串传入完整 groupVO 对象覆写。
常用 flag:
--name 考勤组名称
--type 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)
--owner 考勤组主负责人 userId
--classIds []number 所选班次 id 列表
--enable-outside-check 是否允许外勤打卡 (true/false)
--group-vo 完整 groupVO JSON 字符串(用于修改复杂子对象,会与其他 flag 合并,重复字段以 flag 为准)
--group-vo 可用字段说明(均可选,只需包含要修改的字段):
【基础信息】
id number 考勤组id,不随更新变化
name string 考勤组名称
type string 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)
owner string 考勤组主负责人 userId
managerList []string 考勤组子负责人 userId 列表
skipHolidays bool 节假日自动排休(只有固定班制和自由工时考勤组生效)
defaultGroup bool 是否默认考勤组
classIds []number 所选班次 id 列表(只有固定班制和排班制才有班次,自由工时没有)
【打卡范围与定位】
trimDistance number 定位允许微调距离(米)
enablePositionOfGps bool 打卡是否允许开启 GPS 定位
enablePositionOfWifi bool 是否开启 wifi 定位
enablePositionOfBle bool 是否启用蓝牙定位
positions []object 考勤打卡地址列表(地址定位打卡用),每项字段:
- title string 地址标题
- address string 地址详情(完整地址文本)
- latitude number 纬度
- longitude number 经度
- offset number 该地址允许的打卡范围,单位米
wifis []object 打卡方式中的 wifi 详情列表,每项字段:
- ssid string wifi 的 ssid(wifi名称)
- macAddr string wifi 的 macAddr(MAC 地址)
- groupId number 考勤组id
bleDeviceVOList []object 已选择的蓝牙设备列表,每项字段:
- name string 设备名称
- deviceUid number 设备 uid
- sn string 设备 sn 序列号
- productType string 对外的设备类型
- devServId number 内部设备类型
【外勤打卡设置】
enableOutsideCheck bool 是否可以外勤打卡
enableOutsideCameraCheck bool 是否开启外勤打卡必须拍照
enableOutsideRemark bool 是否开启外勤打卡必须填写备注
enableOutsideApply bool 是否开启外勤打卡必须审批
forbidHideOutSideAddress bool 禁止隐藏外勤打卡地址的功能,false表示允许用户隐藏外勤打卡地址
enableOutSideUpdateNormalCheck bool 下班时间:允许外勤卡更新内勤卡
enableOnDutyNormalUpdateOutsideCheck bool 上班时间:允许内勤卡更新外勤卡
outsideCheckApproveMode string 外勤打卡需审批执行模式:
NO_NEED_APPROVE(无需审批)
APPROVE_FIRST(先审批后打卡)
CHECK_FIRST(先打卡后审批)
APPROVE_EVERYTIME(每次打卡都需要审批)
outSideCheckApplyType number 外勤审批范围:1(全天外勤打卡)/ 2(上班外勤打卡)/ 3(下班外勤打卡)
【打卡方式】
enableCameraCheck bool 是否开启拍照打卡
openCameraCheck bool 是否开启拍照打卡(与 enableCameraCheck 含义相同)
openFaceCheck bool 是否开启人脸打卡
enableFaceStrictMode bool 是否开启人脸严格模式(强制活体检测)
enableFaceBeauty bool 是否开启美颜
onlyMachineCheck bool 只允许考勤机打卡
permitMaxBeaconCount number 允许绑定的智点设备数量
disableCheckWhenRest bool 休息日打卡需审批(只在固定班制和排班制生效,true表示打卡需要提交审批单)
【固定班制设置(FIXED)】
defaultClassId number 固定班制的默认班次 id
workDayClassList []number 固定班制的工作日班次 id 列表,一共7个值,分别代表周日到周六每天的班次id,为0表示当天休息,例如[0,1279240003,0,0,0,0,0]表示周一的班次id为1279240003,其余日子都是休息
【排班制设置(TURN)】
disableCheckWithoutSchedule bool 未排班时员工可打卡(true=未排班时禁止打卡)
enableEmpSelectClass bool 未排班时,员工可选班次打卡
enableScheduleAutoMatch bool 未排班时,系统自动匹配班次
----以下是排班制设置排班周期的,非必要,不是所有排班制考勤组都需要设置,不设置就是按照classIds中的班次由管理员自行排班-----
cycleDays number 排班循环天数,支持大小周(14天)
startCycleDate number 用户设置的大小周开始时间(时间戳,毫秒)
cycleScheduleList []object 循环排班设置列表,每项字段:
- cycleName string 周期名字(如"第一周")
- groupId number 考勤组 id
- isValid string 是否有效:Y(有效)/ N(无效)
- itemList []object 排班设置列表,每项字段:
- classId number 班次 id
- className string 班次名称
- isValid string 是否有效(Y/N)
【自由工时设置(NONE)】
workDays []number 自由工时的工作日,如 [1,2,3,4,5] 表示周一到周五(1=周一,7=周日)
freeCheckDayStartMinOffset number 自由打卡每日开始时间,距离 0 点的分钟数(如 480 表示 08:00 开始)
freeCheckCoreTime number 自由工时考勤组的打卡最短工作时长(分钟)
freeCheckDemandWorkMinutes number 自由工时考勤组要求的打卡时长(分钟)
freeCheckSettingVO object 自由工时打卡设置,包含字段:
- freeCheckType string 自由工时打卡类型:
CYCLE(上下班交替打卡)
MAX_TIME_UPDATE(最大时间打卡,第一次为上班,后续均为下班卡)
- freeWorkDayLackSwitch bool 自由工时打卡规则:工作日没打卡记为缺卡(true=记缺卡)
- freeOnDutyLackMinOffset number 自由工时打卡规则:上班缺卡生成时间偏移分钟数
- freeOffDutyLackMinOffset number 自由工时打卡规则:下班缺卡生成时间偏移分钟数
- delimitOffsetMinutesBetweenDays number 自由工时前后两天的分割线(距 0 点分钟数,超过此时间则归属第二天)
- freeCheckGapVO object 自由工时/休息日打卡间隔设置:
- onOffCheckGapMinutes number 上班打卡后,多久才可以打下班卡(分钟)
- offOnCheckGapMinutes number 下班打卡后,多久才可以打上班卡(分钟)
freeGroupSpecialDayVO object 自由工时考勤组特殊日期设置,包含字段:
- specialOnDutyDays []number 特殊需要打卡日期的时间戳列表(毫秒,原本休息日需额外打卡的日期)
- specialOffDutyDays []number 特殊无需打卡日期的时间戳列表(毫秒,原本工作日可免打卡的日期)`,
Example: ` dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10
dws attendance group update --group-id 123456 --owner userId1 --timeout 10
dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10
dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市...","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10`,
RunE: func(cmd *cobra.Command, args []string) error {
groupID, _ := cmd.Flags().GetInt64("group-id")
if groupID == 0 {
return fmt.Errorf("flag --group-id is required")
}
// 先解析 --group-vo JSON,作为基础
groupVO := map[string]any{}
if raw, _ := cmd.Flags().GetString("group-vo"); raw != "" {
if err := json.Unmarshal([]byte(raw), &groupVO); err != nil {
return fmt.Errorf("invalid --group-vo JSON: %w", err)
}
}
// 单字段 flag 覆写(优先级高于 --group-vo)
if cmd.Flags().Changed("name") {
v, _ := cmd.Flags().GetString("name")
groupVO["name"] = v
}
if cmd.Flags().Changed("type") {
v, _ := cmd.Flags().GetString("type")
groupVO["type"] = v
}
if cmd.Flags().Changed("owner") {
v, _ := cmd.Flags().GetString("owner")
groupVO["owner"] = v
}
if cmd.Flags().Changed("enable-outside-check") {
raw, _ := cmd.Flags().GetString("enable-outside-check")
v, err := strconv.ParseBool(raw)
if err != nil {
return fmt.Errorf("--enable-outside-check 值必须为 true 或 false")
}
groupVO["enableOutsideCheck"] = v
}
if cmd.Flags().Changed("classIds") {
raw, _ := cmd.Flags().GetString("classIds")
var ids []any
if err := json.Unmarshal([]byte(raw), &ids); err != nil {
return fmt.Errorf("invalid --classIds JSON array: %w", err)
}
groupVO["classIds"] = ids
}
if len(groupVO) == 0 {
return fmt.Errorf("至少需要指定一个修改项:--name / --type / --owner / --enable-outside-check / --classIds / --group-vo")
}
// 服务端要求 groupVO 中必须包含 id 字段
groupVO["id"] = groupID
return callMCPToolOnServer("attendance-wukong", "update_group_setting", map[string]any{
"groupId": groupID,
"groupVO": groupVO,
})
},
}
DeclareLeafMetadata(attendanceGroupUpdateCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "group_update",
CanonicalPath: "attendance.group_update",
CLIPath: "attendance group update",
PrimaryCLIPath: "attendance group update",
},
Description: "更新考勤组配置",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "更新考勤组配置",
UseWhen: []string{"需要修改已有考勤组的规则、班次、位置或范围,且 groupId 与变更内容已确认时"},
AvoidWhen: []string{
"查询考勤组详情时使用 attendance group get",
"只调整成员时使用 attendance group update-members",
"创建新考勤组时使用 attendance group create",
},
Examples: []string{
"dws attendance group update --group-id 123456 --name \"研发考勤组\" --timeout 10",
"dws attendance group update --group-id 123456 --owner userId1 --timeout 10",
},
},
},
})
// ── summary ──────────────────────────────────────────────
attendanceSummaryCmd := &cobra.Command{
Use: "summary",
Short: "查询某个人的考勤统计摘要",
Long: `查询某个人的考勤统计摘要。--user、--date、--stats-type 均必填。
statsType 统计类型支持:week(周统计)、month(月统计)。`,
Example: ` dws attendance summary --user USER_ID --date 2026-03-12 --stats-type week # 查询 userId: dws contact user search --query "姓名"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "user", "date", "stats-type"); err != nil {
return err
}
userID := mustGetFlag(cmd, "user")
dateStr := mustGetFlag(cmd, "date")
statsType := mustGetFlag(cmd, "stats-type")
// 解析日期为毫秒时间戳(queryDate 为 number 类型)
queryDate, err := parseDateToTimestamp(dateStr, "date")
if err != nil {
return err
}
vo := map[string]any{
"userId": userID,
"queryDate": queryDate,
"statsType": statsType,
}
return callMCPToolOnServer("attendance-wukong", "get_user_attendance_summary", vo)
},
}
DeclareLeafMetadata(attendanceSummaryCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "get_attendance_summary",
CanonicalPath: "attendance.get_attendance_summary",
CLIPath: "attendance summary",
PrimaryCLIPath: "attendance summary",
},
Description: "查询个人考勤统计摘要",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: the CLI calls attendance-wukong/get_user_attendance_summary, which is absent from the pinned MCP metadata snapshot; the incompatible attendance/get_attendance_summary contract must not be advertised.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询个人考勤统计摘要",
UseWhen: []string{"需要查看员工出勤/异常/请假等考勤统计摘要时"},
AvoidWhen: []string{
"要看单日明细时改用 record get",
"要看打卡流水时改用 check record",
},
Examples: []string{
"dws attendance summary --user USER_ID --date 2026-03-12 --stats-type week",
"dws attendance summary --user USER_ID --date \"2026-03-12 15:00:00\" --stats-type month",
},
},
Parameters: []contract.ParamDecl{
{Name: "user", Required: boolPtr(true)},
{Name: "date", Required: boolPtr(true)},
{Name: "stats-type", Required: boolPtr(true)},
},
},
})
// ── rules ────────────────────────────────────────────────
// MCP tool: query_attendance_group_or_rules
attendanceRulesCmd := &cobra.Command{
Use: "rules",
Short: "查询考勤组与考勤规则",
Long: `调用 MCP 工具 query_attendance_group_or_rules 查询考勤组/考勤规则。
例如:我属于哪个考勤组、打卡范围是什么、弹性工时怎么算。`,
Example: ` dws attendance rules --date 2026-03-14
dws attendance rules --date "2026-03-14 09:00:00"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "date"); err != nil {
return err
}
dateStr := mustGetFlag(cmd, "date")
// 支持 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss,统一转为 yyyy-MM-dd HH:mm:ss
var dateFormatted string
if t, err := time.ParseInLocation("2006-01-02 15:04:05", dateStr, time.Local); err == nil {
dateFormatted = t.Format("2006-01-02 15:04:05")
} else if t, err := time.ParseInLocation("2006-01-02", dateStr, time.Local); err == nil {
dateFormatted = t.Format("2006-01-02 15:04:05")
} else {
return fmt.Errorf("invalid --date format, use YYYY-MM-DD or yyyy-MM-dd HH:mm:ss (e.g. 2026-03-14 or 2026-03-14 09:00:00)")
}
return callMCPTool("query_attendance_group_or_rules", map[string]any{
"date": dateFormatted,
})
},
}
DeclareLeafMetadata(attendanceRulesCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "query_attendance_group_or_rules",
CanonicalPath: "attendance.query_attendance_group_or_rules",
CLIPath: "attendance rules",
PrimaryCLIPath: "attendance rules",
},
Description: "查询考勤组与考勤规则",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "attendance", RPCName: "query_attendance_group_or_rules"},
},
Selection: contract.SelectionSpec{
AgentSummary: "查询考勤组与考勤规则",
UseWhen: []string{"需要回答「我属于哪个考勤组/打卡范围/弹性工时怎么算」等规则概览问题时"},
AvoidWhen: []string{
"要完整考勤组配置时改用 group get",
"要补卡/加班规则列表时改用 adjustment/overtime search",
},
Examples: []string{
"dws attendance rules --date <今天日期> --format json",
"dws attendance rules --date 2026-03-14",
},
},
Parameters: []contract.ParamDecl{
{Name: "date", Required: boolPtr(true)},
},
},
})
// ── selfsetting ─────────────────────────────────────────────
attendanceSelfSettingCmd := &cobra.Command{
Use: "selfsetting",
Short: "个人规则设置",
Long: `个人规则设置相关命令。
子命令:
get 查询个人规则设置,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知等设置项。
save 更新保存个人规则设置;settingScene 必填,且对应场景至少传入一个设置字段。`,
RunE: groupRunE,
}
// MCP tool: query_self_setting
attendanceSelfSettingGetCmd := &cobra.Command{
Use: "get",
Short: "查询个人规则设置",
Long: `查询个人规则设置,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知等设置项。
入参字段:
--setting-scene 查询设置项,枚举值包括:
checkRemind(打卡提醒)
fastCheck(极速打卡)
checkResultNotify(打卡结果通知)
lackRemind(缺卡提醒)
personalAttendStatNotify(个人考勤统计通知)
bossAttendStatNotify(团队考勤统计通知)
--user 必填,查询用户 ID,对应 MCP 参数 userId
说明:corpId 和 opUserId 由当前登录上下文自动补齐,不需要通过 CLI 入参传入。
出参字段说明:
顶层返回 ServiceResult:
success 请求是否成功
code 请求结果码
message 请求结果说明
result 个人规则设置结果;服务端可能根据 --setting-scene 仅返回对应设置项相关字段
result 通用字段:
corpId 企业 ID
userId 用户 ID
settingScene=checkRemind(打卡提醒)相关字段:
checkRemindSetting DING 提醒渠道设置
checkRemindSetting.onDutyRemind 上班打卡提醒设置,包含 openRemind、remindMinutes(负数标识几分钟前提醒,正数几分钟后提醒)
checkRemindSetting.offDutyRemind 下班打卡提醒设置,包含 openRemind、remindMinutes(负数标识几分钟前提醒,正数几分钟后提醒)
checkRemindUserOnDuty 工作通知渠道:用户个人上班打卡提醒开关
checkRemindUserOffDuty 工作通知渠道:用户个人下班打卡提醒开关
enableOndutyCheckRemindOfPc PC 端弹窗渠道:上班打卡提醒开关
enableOffdutyCheckRemindOfPc PC 端弹窗渠道:下班打卡提醒开关
settingScene=fastCheck(极速打卡)相关字段:
ondutyCheckType 上班打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡
ondutyRemindStartMin 上班打卡提醒开始时间,单位:分钟
ondutyRemindEndMin 上班打卡提醒结束时间,单位:分钟
fastCheckLateNeedConfirm 迟到时是否需要二次确认
offdutyCheckType 下班打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡
offdutyRemindStartMin 下班打卡提醒开始时间,单位:分钟
offdutyRemindEndMin 下班打卡提醒结束时间,单位:分钟
canUpdateOffDuty 是否允许用户更新下班打卡设置
voiceRemindSwitch 极速打卡提示音开关
vibrationRemindSwitch 极速打卡震动提醒开关
settingScene=checkResultNotify(打卡结果通知)相关字段:
checkResultMsg 打卡结果通知开关:0 关闭,1 开启
settingScene=lackRemind(缺卡提醒)相关字段:
lackSendTodoMsg 待办提醒开关:0 关闭,null 或 1 开启
lackRemindUser 工作通知渠道:用户个人缺卡提醒开关:0 关闭,null 或 1 开启
settingScene=personalAttendStatNotify(个人考勤统计通知)相关字段:
personDailyReportSwitch 日报推送开关:0 关闭,1 开启
personWeekReportType 周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
personMonthReportType 月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
settingScene=bossAttendStatNotify(团队考勤统计通知)相关字段:
bossPushStartMin 日报推送开始时间,单位:分钟;-1 表示关闭日报推送
bossWeekReportType 周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
bossMonthReportType 月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮`,
Example: ` dws attendance selfsetting get --setting-scene checkRemind --user USER_ID
dws attendance selfsetting get --setting-scene fastCheck --user USER_ID`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "setting-scene", "user"); err != nil {
return err
}
settingScene := mustGetFlag(cmd, "setting-scene")
allowedSettingScenes := map[string]bool{
"checkRemind": true,
"fastCheck": true,
"checkResultNotify": true,
"lackRemind": true,
"personalAttendStatNotify": true,
"bossAttendStatNotify": true,
}
if !allowedSettingScenes[settingScene] {
return fmt.Errorf("invalid --setting-scene %q, supported: checkRemind, fastCheck, checkResultNotify, lackRemind, personalAttendStatNotify, bossAttendStatNotify", settingScene)
}
userID := mustGetFlag(cmd, "user")
request := map[string]any{
"settingScene": settingScene,
"userId": userID,
}
return callMCPToolOnServer("attendance-wukong", "query_self_setting", map[string]any{
"RuleMcpQuerySelfSettingRequest": request,
})
},
}
DeclareLeafMetadata(attendanceSelfSettingGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "selfsetting_get",
CanonicalPath: "attendance.selfsetting_get",
CLIPath: "attendance selfsetting get",
PrimaryCLIPath: "attendance selfsetting get",
},
Description: "查询个人考勤设置",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询个人考勤设置",
UseWhen: []string{"查看某个员工的个人打卡提醒、极速打卡、结果通知等设置"},
AvoidWhen: []string{
"修改个人设置时使用 attendance selfsetting save",
"查询企业全局设置时使用 attendance globalsetting get",
"查询个人考勤记录时使用 attendance record get",
},
Examples: []string{
"dws attendance selfsetting get --setting-scene checkRemind --user <USER_ID> --format json",
"dws attendance selfsetting get --setting-scene fastCheck --user <USER_ID> --format json",
},
},
},
})
// MCP tool: save_self_setting
attendanceSelfSettingSaveCmd := &cobra.Command{
Use: "save",
Short: "更新保存个人规则设置",
Long: `更新保存个人规则设置,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知等设置项。
入参字段:
--setting-scene 必填,枚举值包括 checkRemind、fastCheck、checkResultNotify、lackRemind、personalAttendStatNotify、bossAttendStatNotify
--user 必填,更新用户 ID,对应 MCP 参数 userId
按 settingScene 传入对应设置字段,且至少传一个字段:
checkRemind:
--check-remind-setting
--check-remind-user-on-duty
--check-remind-user-off-duty
--enable-onduty-check-remind-of-pc
--enable-offduty-check-remind-of-pc
fastCheck:
--onduty-check-type
--offduty-check-type
--onduty-remind-start-min
--onduty-remind-end-min
--offduty-remind-start-min
--offduty-remind-end-min
--fast-check-late-need-confirm
--can-update-off-duty
--voice-remind-switch
--vibration-remind-switch
checkResultNotify:
--check-result-msg
lackRemind:
--lack-send-todo-msg
--lack-remind-user
personalAttendStatNotify:
--person-daily-report-switch
--person-week-report-type
--person-month-report-type
bossAttendStatNotify:
--boss-push-start-min
--boss-week-report-type
--boss-month-report-type
说明:corpId 和 opUserId 由当前登录上下文自动补齐,不需要通过 CLI 入参传入。
返回 ServiceResult,包含 success、code、message、result;result 为 boolean,表示保存是否成功。`,
Example: ` dws attendance selfsetting save --setting-scene checkResultNotify --user USER_ID --check-result-msg 1
dws attendance selfsetting save --setting-scene fastCheck --user USER_ID --onduty-check-type 3 --voice-remind-switch=true
dws attendance selfsetting save --setting-scene checkRemind --user USER_ID --check-remind-user-on-duty=false --check-remind-setting '{"onDutyRemind":{"openRemind":true,"remindMinutes":10}}'`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "setting-scene", "user"); err != nil {
return err
}
settingScene := mustGetFlag(cmd, "setting-scene")
allowedSettingScenes := map[string]bool{
"checkRemind": true,
"fastCheck": true,
"checkResultNotify": true,
"lackRemind": true,
"personalAttendStatNotify": true,
"bossAttendStatNotify": true,
}
if !allowedSettingScenes[settingScene] {
return fmt.Errorf("invalid --setting-scene %q, supported: checkRemind, fastCheck, checkResultNotify, lackRemind, personalAttendStatNotify, bossAttendStatNotify", settingScene)
}
userID := mustGetFlag(cmd, "user")
request, err := collectSelfSettingSaveRequest(cmd, settingScene, userID)
if err != nil {
return err
}
return callMCPToolOnServer("attendance-wukong", "save_self_setting", map[string]any{
"RuleMcpSaveSelfSettingRequest": request,
})
},
}
DeclareLeafMetadata(attendanceSelfSettingSaveCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "selfsetting_save",
CanonicalPath: "attendance.selfsetting_save",
CLIPath: "attendance selfsetting save",
PrimaryCLIPath: "attendance selfsetting save",
},
Description: "更新个人考勤设置",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "更新个人考勤设置",
UseWhen: []string{"需要修改员工个人打卡提醒、极速打卡或结果通知设置,且 scene 与变更值已确认时"},
AvoidWhen: []string{
"只查看个人设置时使用 attendance selfsetting get",
"修改企业全局设置时使用 attendance globalsetting save",
"查询考勤记录时使用 attendance record get",
},
Examples: []string{
"dws attendance selfsetting save --setting-scene checkResultNotify --user <USER_ID> --check-result-msg 1 --format json",
"dws attendance selfsetting save --setting-scene fastCheck --user <USER_ID> --onduty-check-type 3 --voice-remind-switch=true --format json",
},
},
},
})
// ── globalsetting ────────────────────────────────────────
attendanceGlobalSettingCmd := &cobra.Command{
Use: "globalsetting",
Short: "全局规则设置(仅管理员)",
Long: `全局规则设置相关命令,仅管理员可以调用。
子命令:
get 查询全局规则设置,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知等设置项。
save 更新保存全局规则设置;settingScene 必填,且对应场景至少传入一个设置字段。`,
RunE: groupRunE,
}
// MCP tool: query_global_setting
attendanceGlobalSettingGetCmd := &cobra.Command{
Use: "get",
Short: "查询全局规则设置(仅管理员)",
Long: `查询全局规则设置,仅管理员可以调用。
入参字段:
--setting-scene 查询设置项,枚举值包括 checkRemind、fastCheck、checkResultNotify、lackRemind、personalAttendStatNotify、bossAttendStatNotify
--scope 必填,必须明确输入 企业、全公司 或 所有人,用于确认本次查询面向全局范围
说明:corpId 和 opUserId 由当前登录上下文自动补齐,不需要通过 CLI 入参传入。`,
Example: ` dws attendance globalsetting get --scope 企业 --setting-scene checkRemind
dws attendance globalsetting get --scope 全公司 --setting-scene bossAttendStatNotify`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "setting-scene"); err != nil {
return err
}
if err := validateGlobalSettingScope(cmd); err != nil {
return err
}
settingScene := mustGetFlag(cmd, "setting-scene")
if !isAttendanceSettingSceneAllowed(settingScene) {
return fmt.Errorf("invalid --setting-scene %q, supported: checkRemind, fastCheck, checkResultNotify, lackRemind, personalAttendStatNotify, bossAttendStatNotify", settingScene)
}
return callMCPToolOnServer("attendance-wukong", "query_global_setting", map[string]any{
"RuleMcpQueryGlobalSettingRequest": map[string]any{
"settingScene": settingScene,
},
})
},
}
DeclareLeafMetadata(attendanceGlobalSettingGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "globalsetting_get",
CanonicalPath: "attendance.globalsetting_get",
CLIPath: "attendance globalsetting get",
PrimaryCLIPath: "attendance globalsetting get",
},
Description: "查询企业全局考勤设置",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询企业全局考勤设置",
UseWhen: []string{"管理员查看企业级打卡提醒、极速打卡等全局设置"},
AvoidWhen: []string{
"修改全局设置时使用 attendance globalsetting save",
"查看个人设置时使用 attendance selfsetting get",
"查看考勤组规则时使用 attendance group get/rules",
},
Examples: []string{
"dws attendance globalsetting get --scope 企业 --setting-scene checkRemind --format json",
"dws attendance globalsetting get --scope 全公司 --setting-scene bossAttendStatNotify --format json",
},
},
},
})
// MCP tool: save_global_setting
attendanceGlobalSettingSaveCmd := &cobra.Command{
Use: "save",
Short: "更新保存全局规则设置(仅管理员)",
Long: `更新保存全局规则设置,仅管理员可以调用。
入参字段:
--setting-scene 必填,枚举值包括 checkRemind、fastCheck、checkResultNotify、lackRemind、personalAttendStatNotify、bossAttendStatNotify
--scope 必填,必须明确输入 企业、全公司 或 所有人,用于确认本次更新面向全局范围
按 settingScene 传入对应设置字段,且至少传一个字段:
checkRemind:
--check-remind-corp
--check-remind-pc-corp
fastCheck:
--fast-check-corp
checkResultNotify:
--enable-check-cert-push
lackRemind:
--lack-remind-corp
personalAttendStatNotify:
--enable-personal-daily-report
--enable-personal-weekly-report
--enable-personal-weekly-report-card
--enable-personal-monthly-report
bossAttendStatNotify:
--boss-daily-report-type
--boss-weekly-report-type
--boss-monthly-report-type
说明:corpId 和 opUserId 由当前登录上下文自动补齐,不需要通过 CLI 入参传入。
返回 ServiceResult,result 为 boolean,表示保存是否成功。`,
Example: ` dws attendance globalsetting save --scope 企业 --setting-scene checkRemind --check-remind-corp=true --yes
dws attendance globalsetting save --scope 全公司 --setting-scene fastCheck --fast-check-corp=false --yes
dws attendance globalsetting save --scope 所有人 --setting-scene bossAttendStatNotify --boss-daily-report-type 1 --boss-weekly-report-type 3 --yes`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "setting-scene"); err != nil {
return err
}
if err := validateGlobalSettingScope(cmd); err != nil {
return err
}
settingScene := mustGetFlag(cmd, "setting-scene")
if !isAttendanceSettingSceneAllowed(settingScene) {
return fmt.Errorf("invalid --setting-scene %q, supported: checkRemind, fastCheck, checkResultNotify, lackRemind, personalAttendStatNotify, bossAttendStatNotify", settingScene)
}
request, err := collectGlobalSettingSaveRequest(cmd, settingScene)
if err != nil {
return err
}
return callMCPToolOnServer("attendance-wukong", "save_global_setting", map[string]any{
"RuleMcpSaveGlobalSettingRequest": request,
})
},
}
DeclareLeafMetadata(attendanceGlobalSettingSaveCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "globalsetting_save",
CanonicalPath: "attendance.globalsetting_save",
CLIPath: "attendance globalsetting save",
PrimaryCLIPath: "attendance globalsetting save",
},
Description: "更新企业全局考勤设置",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "更新企业全局考勤设置",
UseWhen: []string{"管理员明确要求修改企业级打卡提醒、极速打卡等全局设置,且 scope/scene 与变更值已确认时"},
AvoidWhen: []string{
"只查看全局设置时使用 attendance globalsetting get",
"修改个人设置时使用 attendance selfsetting save",
"修改考勤组配置时使用 attendance group update",
},
Examples: []string{
"dws attendance globalsetting save --scope 企业 --setting-scene checkRemind --check-remind-corp=true",
"dws attendance globalsetting save --scope 全公司 --setting-scene fastCheck --fast-check-corp=false",
},
},
},
})
// ── report ──────────────────────────────────────────────
attendanceReportCmd := &cobra.Command{
Use: "report",
Short: "查询考勤报表和结果",
Long: `考勤 MCP 报表接口,仅对管理员开放
子命令:
columns 获取企业考勤字段列表
query-data 根据字段查询考勤数据
query-leave 查询用户假期数据`,
RunE: groupRunE,
}
// MCP tool: get_report_columns
reportColumnsCmd := &cobra.Command{
Use: "columns",
Short: "获取企业考勤字段列表",
Long: `根据操作者的列权限,过滤并返回其有权查看的考勤字段列表。
操作者必须是管理员,否则返回权限错误。`,
Example: ` dws attendance report columns`,
RunE: func(cmd *cobra.Command, args []string) error {
return callMCPToolOnServer("attendance-wukong", "get_report_columns", map[string]any{})
},
}
DeclareLeafMetadata(reportColumnsCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "report_columns",
CanonicalPath: "attendance.report_columns",
CLIPath: "attendance report columns",
PrimaryCLIPath: "attendance report columns",
},
Description: "查询可查看的考勤报表字段",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询可查看的考勤报表字段",
UseWhen: []string{"查看操作者有权限查询的考勤报表字段"},
AvoidWhen: []string{
"查询报表数据时使用 attendance report query-data",
"查询假期数据时使用 attendance report query-leave",
"查询个人考勤明细时使用 attendance record get",
},
Examples: []string{"dws attendance report columns"},
},
},
})
// MCP tool: get_report_columns_value
reportQueryDataCmd := &cobra.Command{
Use: "query-data",
Short: "根据字段查询考勤数据",
Long: `根据字段查询考勤数据,含列权限过滤和用户查看权限校验。
--columns 为字段 ID 列表(逗号分隔),可通过 dws attendance report columns 获取。
--users 最多 20 人,传入userId,--start 到 --end 不超过 32 天。`,
Example: ` dws attendance report query-data \
--users userId1,userId2 --columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users", "columns", "start", "end"); err != nil {
return err
}
startStr, endStr := mustGetFlag(cmd, "start"), mustGetFlag(cmd, "end")
if _, err := time.ParseInLocation("2006-01-02 15:04:05", startStr, time.Local); err != nil {
return fmt.Errorf("invalid --start date format, use yyyy-MM-dd HH:mm:ss: %w", err)
}
if _, err := time.ParseInLocation("2006-01-02 15:04:05", endStr, time.Local); err != nil {
return fmt.Errorf("invalid --end date format, use yyyy-MM-dd HH:mm:ss: %w", err)
}
var userIds []string
for _, u := range strings.Split(mustGetFlag(cmd, "users"), ",") {
if s := strings.TrimSpace(u); s != "" {
userIds = append(userIds, s)
}
}
var columnIds []int64
for _, c := range strings.Split(mustGetFlag(cmd, "columns"), ",") {
c = strings.TrimSpace(c)
if c == "" {
continue
}
var columnId int64
if _, err := fmt.Sscanf(c, "%d", &columnId); err != nil {
return fmt.Errorf("invalid column id %q, must be a number: %w", c, err)
}
columnIds = append(columnIds, columnId)
}
return callMCPToolOnServer("attendance-wukong", "get_report_columns_value", map[string]any{
"McpQueryParam": map[string]any{
"targetUserIds": userIds,
"columnIds": columnIds,
"fromDate": startStr,
"toDate": endStr,
},
})
},
}
DeclareLeafMetadata(reportQueryDataCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "report_query_data",
CanonicalPath: "attendance.report_query_data",
CLIPath: "attendance report query-data",
PrimaryCLIPath: "attendance report query-data",
},
Description: "查询考勤报表数据",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询考勤报表数据",
UseWhen: []string{"按字段查询考勤报表数据并进行权限过滤"},
AvoidWhen: []string{
"查询可用字段时先用 attendance report columns",
"查询假期数据时使用 attendance report query-leave",
"查询个人单日明细时使用 attendance record get",
},
Examples: []string{
"dws attendance report query-data --users userId1,userId2 --columns 1001,1002 --start \"2026-03-01 00:00:00\" --end \"2026-03-31 23:59:59\"",
"dws attendance report query-data --users userId1,userId2 --columns 1001,1002 --start \"2026-03-01 00:00:00\" --end \"2026-03-31 23:59:59\" --format json",
},
},
},
})
// MCP tool: get_leave_time_by_leave_names
reportQueryLeaveCmd := &cobra.Command{
Use: "query-leave",
Short: "查询用户假期数据",
Long: `查询用户假期数据,含用户查看权限校验。
--leave-names 为假期类型名称列表(逗号分隔),不传则查询所有假期类型。
--users 最多 20 人,传入userId,--start 到 --end 不超过 32 天。`,
Example: ` dws attendance report query-leave \
--users userId1,userId2 --leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users", "start", "end"); err != nil {
return err
}
startStr, endStr := mustGetFlag(cmd, "start"), mustGetFlag(cmd, "end")
if _, err := time.ParseInLocation("2006-01-02 15:04:05", startStr, time.Local); err != nil {
return fmt.Errorf("invalid --start date format, use yyyy-MM-dd HH:mm:ss: %w", err)
}
if _, err := time.ParseInLocation("2006-01-02 15:04:05", endStr, time.Local); err != nil {
return fmt.Errorf("invalid --end date format, use yyyy-MM-dd HH:mm:ss: %w", err)
}
var userIds []string
for _, u := range strings.Split(mustGetFlag(cmd, "users"), ",") {
if s := strings.TrimSpace(u); s != "" {
userIds = append(userIds, s)
}
}
var leaveNames []string
if raw := mustGetFlag(cmd, "leave-names"); raw != "" {
for _, n := range strings.Split(raw, ",") {
if s := strings.TrimSpace(n); s != "" {
leaveNames = append(leaveNames, s)
}
}
}
return callMCPToolOnServer("attendance-wukong", "get_leave_time_by_leave_names", map[string]any{
"McpLeaveQueryParam": map[string]any{
"targetUserIds": userIds,
"leaveNames": leaveNames,
"fromDate": startStr,
"toDate": endStr,
},
})
},
}
DeclareLeafMetadata(reportQueryLeaveCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "report_query_leave",
CanonicalPath: "attendance.report_query_leave",
CLIPath: "attendance report query-leave",
PrimaryCLIPath: "attendance report query-leave",
},
Description: "查询假期报表数据",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询假期报表数据",
UseWhen: []string{"查询用户假期/请假相关报表数据"},
AvoidWhen: []string{
"查询假期余额时使用 attendance vacation balance",
"查询假期余额变更记录时使用 attendance vacation records",
"查询通用考勤报表时使用 attendance report query-data",
},
Examples: []string{
"dws attendance report query-leave --users userId1,userId2 --leave-names 年假,病假 --start \"2026-03-01 00:00:00\" --end \"2026-03-31 23:59:59\"",
"dws attendance report query-leave --users userId1,userId2 --leave-names 年假,病假 --start \"2026-03-01 00:00:00\" --end \"2026-03-31 23:59:59\" --format json",
},
},
},
})
// ── 假期 vacation ───────────────────────────────────────────────
attendanceVacationCmd := &cobra.Command{
Use: "vacation",
Short: "假期管理",
Long: `管理钉钉假期:查询假期规则列表、查询员工假期余额、查询假期余额变更记录。
子命令:
types 查询当前用户假期规则列表
update-type 更新假期规则(仅支持无额度模式)
balance 查询指定员工假期余额
save-balance 更新员工假期余额
records 查询指定员工假期余额变更记录`,
RunE: groupRunE,
}
// ── 假期规则 types ─────────────────────────────────────────
// MCP tool: get_leave_types
vacationTypesCmd := &cobra.Command{
Use: "types",
Short: "查询当前用户假期规则列表",
Long: `调用 MCP 工具 get_leave_types 查询当前用户可用的假期规则列表。
例如:年假、事假、病假等假期类型及对应规则。`,
Example: ` dws attendance vacation types`,
RunE: func(cmd *cobra.Command, args []string) error {
return callMCPToolOnServer("attendance-wukong", "get_leave_types", map[string]any{
"McpLeaveTypeRequest": map[string]any{},
})
},
}
DeclareLeafMetadata(vacationTypesCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "vacation_types",
CanonicalPath: "attendance.vacation_types",
CLIPath: "attendance vacation types",
PrimaryCLIPath: "attendance vacation types",
},
Description: "查询可用假期类型",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询可用假期类型",
UseWhen: []string{"查看当前用户可用的假期规则/假期类型列表"},
AvoidWhen: []string{
"查询员工假期余额时使用 attendance vacation balance",
"修改假期规则时使用 attendance vacation update-type",
"修改假期余额时使用 attendance vacation save-balance",
},
Examples: []string{"dws attendance vacation types"},
},
},
})
// ── 假期余额 balance ───────────────────────────────────────
// MCP tool: get_leave_balance_quota
vacationBalanceCmd := &cobra.Command{
Use: "balance",
Short: "查询指定员工假期余额",
Long: `调用 MCP 工具 get_leave_balance_quota 查询指定员工的假期余额。
例如:查询某员工年假还剩多少、病假额度等。`,
Example: ` dws attendance vacation balance --users userId1,userId2 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 # 查询 userId: dws contact user search --query "姓名"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users"); err != nil {
return err
}
usersStr := mustGetFlag(cmd, "users")
var targetUserIds []string
for _, u := range strings.Split(usersStr, ",") {
if s := strings.TrimSpace(u); s != "" {
targetUserIds = append(targetUserIds, s)
}
}
req := map[string]any{
"targetUserIds": targetUserIds,
}
if leaveCode := mustGetFlag(cmd, "leave-code"); leaveCode != "" {
req["leaveCode"] = leaveCode
}
return callMCPToolOnServer("attendance-wukong", "get_leave_balance_quota", map[string]any{
"McpLeaveBalanceRequest": req,
})
},
}
DeclareLeafMetadata(vacationBalanceCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "vacation_balance",
CanonicalPath: "attendance.vacation_balance",
CLIPath: "attendance vacation balance",
PrimaryCLIPath: "attendance vacation balance",
},
Description: "查询员工假期余额",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询员工假期余额",
UseWhen: []string{"查询指定员工的年假、调休等假期余额"},
AvoidWhen: []string{
"查询假期余额变更明细时使用 attendance vacation records",
"查询假期类型时使用 attendance vacation types",
"修改余额时使用 attendance vacation save-balance",
},
Examples: []string{
"dws attendance vacation balance --users userId1,userId2 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"dws attendance vacation balance --users userId1,userId2 --leave-code <假期类型code> --format json",
},
},
},
})
// ── 假期记录 records ───────────────────────────────────────
// MCP tool: get_leave_balance_records
vacationRecordsCmd := &cobra.Command{
Use: "records",
Short: "查询指定员工假期余额变更记录",
Long: `调用 MCP 工具 get_leave_balance_records 查询指定员工的假期余额变更记录。
例如:查询某员工年假变更历史、请假扣减记录等。`,
Example: ` dws attendance vacation records --user USER_ID --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --start 2026-04-01 --end 2026-04-22 # 查询 userId: dws contact user search --query "姓名"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "user", "start", "end"); err != nil {
return err
}
startStr, endStr := mustGetFlag(cmd, "start"), mustGetFlag(cmd, "end")
startT, err := time.ParseInLocation("2006-01-02", startStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --start date format, use YYYY-MM-DD: %w", err)
}
endT, err := time.ParseInLocation("2006-01-02", endStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --end date format, use YYYY-MM-DD: %w", err)
}
req := map[string]any{
"targetUserId": mustGetFlag(cmd, "user"),
"startTimeStamp": startT.UnixMilli(),
"endTimeStamp": endT.UnixMilli(),
}
if leaveCode := mustGetFlag(cmd, "leave-code"); leaveCode != "" {
req["leaveCode"] = leaveCode
}
return callMCPToolOnServer("attendance-wukong", "get_leave_balance_records_v2", map[string]any{
"McpLeaveRecordRequest": req,
})
},
}
DeclareLeafMetadata(vacationRecordsCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "vacation_records",
CanonicalPath: "attendance.vacation_records",
CLIPath: "attendance vacation records",
PrimaryCLIPath: "attendance vacation records",
},
Description: "查询假期余额变更记录",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询假期余额变更记录",
UseWhen: []string{"查询员工假期余额的增减流水或变更历史"},
AvoidWhen: []string{
"只看当前余额时使用 attendance vacation balance",
"查询假期类型时使用 attendance vacation types",
"修改余额时使用 attendance vacation save-balance",
},
Examples: []string{
"dws attendance vacation records --user USER_ID --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --start 2026-04-01 --end 2026-04-22",
"dws attendance vacation records --user USER_ID --leave-code <假期类型code> --start 2026-04-01 --end 2026-04-22 --format json",
},
},
},
})
// ── 假期规则更新 update-type ─────────────────────────────────────────
// MCP tool: save_leave_type
vacationUpdateTypeCmd := &cobra.Command{
Use: "update-type",
Short: "更新假期规则",
Long: `调用 MCP 工具 save_leave_type 更新已有假期规则。
--leave-code 必填,指定要更新的假期规则编码。
其他字段均为可选,仅需传入要修改的字段。
参数说明:
--leave-code 假期编码(必填)
--name 假期名称(可选)
--unit 假期单位:day/halfDay/hour(可选)
--paid 是否带薪假期(可选)
--per-hours 一天折算小时数(可选)
--when-can-leave 新员工请假规则:entry/formal(可选)
--visibility-rules 适用范围规则 JSON 数组(可选)
--user-say-yes 用户已确认,跳过交互式确认提示(Agent 调用时传 true 前必须完成用户二次确认)
--visibility-rules 适用范围语义(HSF 反序列化约定,必读):
- 不传:不修改原有可见范围。
- 改为指定范围:传 [{"type":"staff|dept|label|employee_type","visible":["id1","id2"]}, ...],
每条规则 type 和 visible 都必须非空。
- 改为全公司可见(哨兵约定):必须显式传 [{"type":"dept","visible":["-1"]}],
服务端识别后会落库为空可见范围(即全公司)。
- 反例(CLI 会拦截报错,避免被服务端静默忽略):
* 空数组 []、[{}]、[{"type":"dept","visible":[]}] 等被视为未传,不会生效为全公司可见;
* 若要清空可见范围,必须显式传哨兵值 ["-1"]。`,
Example: ` # 更新假期规则名称
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --name "事假(修改版)"
# 更新假期单位
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --unit hour --per-hours 8
# 改为指定部门可见
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]'
# 改为全公司可见(必须显式传哨兵值 "-1",空数组 [] 不生效)
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["-1"]}]'`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "leave-code"); err != nil {
return err
}
// 检查除 leave-code 外是否至少传了一个更新字段
updateFieldCount := 0
if cmd.Flags().Changed("name") {
updateFieldCount++
}
if cmd.Flags().Changed("unit") {
updateFieldCount++
}
if cmd.Flags().Changed("paid") {
updateFieldCount++
}
if cmd.Flags().Changed("per-hours") {
updateFieldCount++
}
if cmd.Flags().Changed("when-can-leave") {
updateFieldCount++
}
if cmd.Flags().Changed("visibility-rules") {
updateFieldCount++
}
if updateFieldCount == 0 {
return fmt.Errorf("至少需要指定一个更新字段:--name / --unit / --paid / --per-hours / --when-can-leave / --visibility-rules")
}
// 提前解析并校验 --visibility-rules:HSF 端无法区分「空数组」与「未传」,
// 需在 CLI 层拦截空数组 / 全部无效规则,避免被服务端静默忽略。
// 任一规则满足 type=dept && visible 含 "-1" 则视为哨兵(改为全公司可见)。
var visibilityRules []map[string]any
var visibilityRulesSentinel bool
if cmd.Flags().Changed("visibility-rules") {
visibilityRulesJSON := strings.TrimSpace(mustGetFlag(cmd, "visibility-rules"))
if visibilityRulesJSON != "" {
var raw []map[string]any
if err := json.Unmarshal([]byte(visibilityRulesJSON), &raw); err != nil {
return fmt.Errorf("invalid --visibility-rules JSON format: %w", err)
}
if len(raw) == 0 {
return fmt.Errorf("--visibility-rules 不能是空数组 []:HSF 端无法区分 [] 与未传,会被视为「不修改可见范围」。如需改为全公司可见,请显式传 [{\"type\":\"dept\",\"visible\":[\"-1\"]}]")
}
for _, r := range raw {
if visibilityRulesSentinel {
break
}
t, _ := r["type"].(string)
if t != "dept" {
continue
}
vis, _ := r["visible"].([]any)
for _, v := range vis {
if s, ok := v.(string); ok && strings.TrimSpace(s) == "-1" {
visibilityRulesSentinel = true
break
}
}
}
if visibilityRulesSentinel {
// 命中哨兵:只传哨兵规则,服务端会落库为空可见范围(全公司)
visibilityRules = []map[string]any{
{"type": "dept", "visible": []string{"-1"}},
}
} else {
// 过滤无效规则(type 缺失或 visible 为空)
valid := make([]map[string]any, 0, len(raw))
for _, r := range raw {
t, _ := r["type"].(string)
if strings.TrimSpace(t) == "" {
continue
}
vis, ok := r["visible"].([]any)
if !ok || len(vis) == 0 {
continue
}
valid = append(valid, r)
}
if len(valid) == 0 {
return fmt.Errorf("--visibility-rules 中所有规则均无效(type 缺失或 visible 为空),服务端会视为「不修改可见范围」。如需改为全公司可见,请显式传 [{\"type\":\"dept\",\"visible\":[\"-1\"]}]")
}
visibilityRules = valid
}
}
}
// 构建 MCP 请求参数
req := map[string]any{
"leaveCode": mustGetFlag(cmd, "leave-code"),
}
// 可选参数(仅传入 Changed 的字段)
if cmd.Flags().Changed("name") {
req["leaveName"] = mustGetFlag(cmd, "name")
}
if cmd.Flags().Changed("unit") {
req["leaveUnit"] = mustGetFlag(cmd, "unit")
}
if cmd.Flags().Changed("paid") {
if paid, _ := cmd.Flags().GetBool("paid"); paid {
req["paidLeave"] = true
} else {
req["paidLeave"] = false
}
}
if cmd.Flags().Changed("per-hours") {
if perHours, _ := cmd.Flags().GetInt("per-hours"); perHours > 0 {
req["perHoursInDay"] = perHours
}
}
if cmd.Flags().Changed("when-can-leave") {
if whenCanLeave := mustGetFlag(cmd, "when-can-leave"); whenCanLeave != "" {
req["whenCanLeave"] = whenCanLeave
}
}
// visibility-rules 已在 RunE 顶部解析与校验,这里只需赋值
if visibilityRules != nil {
req["visibilityRules"] = visibilityRules
}
return callMCPToolOnServer("attendance-wukong", "save_leave_type", map[string]any{
"McpSaveLeaveTypeRequest": req,
})
},
}
DeclareLeafMetadata(vacationUpdateTypeCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "vacation_update_type",
CanonicalPath: "attendance.vacation_update_type",
CLIPath: "attendance vacation update-type",
PrimaryCLIPath: "attendance vacation update-type",
},
Description: "更新假期类型规则",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "更新假期类型规则",
UseWhen: []string{"管理员明确要求修改已有假期规则或假期类型配置,且 leave-code 与变更字段已确认时"},
AvoidWhen: []string{
"只查询假期类型时使用 attendance vacation types",
"调整员工假期余额时使用 attendance vacation save-balance",
"查询假期报表时使用 attendance report query-leave",
},
Examples: []string{
"dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --name \"事假(修改版)\"",
"dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --unit hour --per-hours 8",
},
},
},
})
// ── 假期余额保存 save-balance ─────────────────────────────────────────
// MCP tool: update_leave_balance
vacationSaveBalanceCmd := &cobra.Command{
Use: "save-balance",
Short: "设置员工假期余额",
Long: `调用 MCP 工具 update_leave_balance 设置指定员工的假期余额数量。
重要说明:这是设置(SET)接口,传入的值会替换当前余额,而不是增加(ADD)。
参数说明:
--target 目标员工工号(必填)
--leave-code 假期编码(必填)
--num 余额数量(必填,如8天传8,7.5天传7.5)
--reason 变更原因(必填,最长100字符)
--start 有效期开始日期 YYYY-MM-DD
--end 有效期结束日期 YYYY-MM-DD
--user-say-yes 用户已确认,跳过交互式确认提示(Agent 调用时传 true 前必须完成用户二次确认)
注意:
- 余额数量在传递给 MCP 时会自动乘以100(如8天传800,7.5天传750)
- 此接口为 SET 操作:传入值会直接设置为新的余额,而非累加到现有余额`,
Example: ` # 设置员工年假余额为8天
dws attendance vacation save-balance --target user001 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放"
# 设置有效期
dws attendance vacation save-balance --target user001 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放" --start 2024-01-01 --end 2024-12-31`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "target", "leave-code", "num", "reason"); err != nil {
return err
}
// 构建 MCP 请求参数
// quotaNum 需要乘以100(如8天传800,7.5天传750)
numStr := mustGetFlag(cmd, "num")
numFloat, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return fmt.Errorf("invalid --num format: %w (expected number like '8' or '7.5')", err)
}
quotaNum := int(numFloat * 100)
req := map[string]any{
"targetUserId": mustGetFlag(cmd, "target"),
"leaveCode": mustGetFlag(cmd, "leave-code"),
"quotaNum": strconv.Itoa(quotaNum),
"reason": mustGetFlag(cmd, "reason"),
}
// 可选参数:有效期日期转换为毫秒时间戳
if startStr := mustGetFlag(cmd, "start"); startStr != "" {
startTs, err := parseDateToTimestamp(startStr, "startTime")
if err != nil {
return err
}
req["startTime"] = startTs
}
if endStr := mustGetFlag(cmd, "end"); endStr != "" {
endTs, err := parseDateToTimestamp(endStr, "endTime")
if err != nil {
return err
}
req["endTime"] = endTs
}
return callMCPToolOnServer("attendance-wukong", "update_leave_balance", map[string]any{
"McpUpdateLeaveBalanceRequest": req,
})
},
}
DeclareLeafMetadata(vacationSaveBalanceCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "vacation_save_balance",
CanonicalPath: "attendance.vacation_save_balance",
CLIPath: "attendance vacation save-balance",
PrimaryCLIPath: "attendance vacation save-balance",
},
Description: "调整员工假期余额",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "调整员工假期余额",
UseWhen: []string{"管理员明确要求设置指定员工假期余额(SET 替换而非累加),且 target/leave-code/num/reason 已确认时"},
AvoidWhen: []string{
"只查询余额时使用 attendance vacation balance",
"查询变更流水时使用 attendance vacation records",
"修改假期类型/规则时使用 attendance vacation update-type",
},
Examples: []string{
"dws attendance vacation save-balance --target user001 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason \"年度发放\"",
"dws attendance vacation save-balance --target user001 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason \"年度发放\" --start 2024-01-01 --end 2024-12-31",
},
},
},
})
// record
attendanceRecordGetCmd.Flags().String("user", "", "钉钉用户 ID (必填)")
attendanceRecordGetCmd.Flags().String("date", "", "查询日期,格式 YYYY-MM-DD (必填)")
attendanceRecordCmd.AddCommand(attendanceRecordGetCmd)
// check result (query_check_result)
attendanceCheckResultCmd.Flags().String("users", "", "用户 ID 列表,逗号分隔,最多 100 个 (必填)")
attendanceCheckResultCmd.Flags().String("start", "", "起始日期,格式 YYYY-MM-DD (必填)")
attendanceCheckResultCmd.Flags().String("end", "", "结束日期,格式 YYYY-MM-DD,不超过 1 个月 (必填)")
attendanceCheckResultCmd.Flags().String("from", "", "起始日期(--start 的别名)")
attendanceCheckResultCmd.Flags().String("to", "", "结束日期(--end 的别名)")
_ = attendanceCheckResultCmd.Flags().MarkHidden("from")
_ = attendanceCheckResultCmd.Flags().MarkHidden("to")
attendanceCheckResultCmd.Flags().Int("offset", 0, "分页偏移量,默认 0(可选)")
attendanceCheckResultCmd.Flags().Int("limit", 100, "分页大小,默认 100,范围 1-1000(可选)")
attendanceCheckCmd.AddCommand(attendanceCheckResultCmd)
// check record (query_check_record)
attendanceCheckRecordCmd.Flags().String("users", "", "用户 ID 列表,逗号分隔 (必填)")
attendanceCheckRecordCmd.Flags().String("start", "", "起始日期,格式 YYYY-MM-DD (必填)")
attendanceCheckRecordCmd.Flags().String("end", "", "结束日期,格式 YYYY-MM-DD,不超过 1 个月 (必填)")
attendanceCheckRecordCmd.Flags().String("from", "", "起始日期(--start 的别名)")
attendanceCheckRecordCmd.Flags().String("to", "", "结束日期(--end 的别名)")
_ = attendanceCheckRecordCmd.Flags().MarkHidden("from")
_ = attendanceCheckRecordCmd.Flags().MarkHidden("to")
attendanceCheckCmd.AddCommand(attendanceCheckRecordCmd)
// approve list (query_user_approve)
attendanceApproveListCmd.Flags().String("users", "", "用户 ID 列表,逗号分隔 (必填)")
attendanceApproveListCmd.Flags().String("types", "", "审批类型,逗号分隔:overtime/trip/leave/patch (必填)")
attendanceApproveListCmd.Flags().String("start", "", "起始日期,格式 YYYY-MM-DD (必填)")
attendanceApproveListCmd.Flags().String("end", "", "结束日期,格式 YYYY-MM-DD (必填)")
attendanceApproveListCmd.Flags().String("from", "", "起始日期(--start 的别名)")
attendanceApproveListCmd.Flags().String("to", "", "结束日期(--end 的别名)")
_ = attendanceApproveListCmd.Flags().MarkHidden("from")
_ = attendanceApproveListCmd.Flags().MarkHidden("to")
attendanceApproveCmd.AddCommand(attendanceApproveListCmd)
// approve templates (query_at_approve_template)
attendanceApproveTemplatesCmd.Flags().String("type", "", "审批类型:repair-check/patch/补卡、leave/请假、overtime/加班,或 REPAIR_CHECK/LEAVE/OVERTIME(必填)")
attendanceApproveCmd.AddCommand(attendanceApproveTemplatesCmd)
// shift list (batch_get_employee_shifts)
attendanceShiftListCmd.Flags().String("users", "", "用户 ID 列表,逗号分隔,最多 50 个 (必填)")
attendanceShiftListCmd.Flags().String("start", "", "开始日期,格式 YYYY-MM-DD (必填)")
attendanceShiftListCmd.Flags().String("end", "", "结束日期,格式 YYYY-MM-DD (必填)")
attendanceShiftCmd.AddCommand(attendanceShiftListCmd)
// ── schedule ──────────────────────────────────────────────
attendanceScheduleCmd := &cobra.Command{
Use: "schedule",
Short: "排班管理",
Long: `排班制考勤组的排班记录导入与查询(排班 = 为员工安排具体工作日期和班次)。
子命令:
import 导入排班记录到排班制考勤组
get 获取指定用户的排班记录`,
RunE: groupRunE,
}
// schedule import (generateTurnSchedule)
scheduleImportCmd := &cobra.Command{
Use: "import",
Short: "导入排班记录到排班制考勤组",
Long: `为排班制考勤组导入排班记录。传入 JSON 数组格式排班数据,
每条记录必填字段:
- userId: 用户ID(必填)
- workDate: 排班日期,格式 yyyy-MM-dd HH:mm:ss(必填)
- classId: 班次ID(必填)
- isRest: 是否为排休,取值 Y/N(必填)
示例:
dws attendance schedule import --groupId 123456 \
--scheduleVOS '[{"userId":"user001","workDate":"2026-04-22 09:00:00","classId":123,"isRest":"N"}]' \
--user-say-yes`,
Example: ` dws attendance schedule import --groupId GROUP_ID --scheduleVOS JSON_STRING [--user-say-yes]`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlagWithAliases(cmd, "groupId", "group-id"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "scheduleVOS", "schedules"); err != nil {
return err
}
groupID := flagOrFallback(cmd, "groupId", "group-id")
schedulesJSON := flagOrFallback(cmd, "scheduleVOS", "schedules")
// 解析 scheduleVOS JSON
var scheduleVOS []map[string]any
if err := json.Unmarshal([]byte(schedulesJSON), &scheduleVOS); err != nil {
return fmt.Errorf("invalid --scheduleVOS JSON format: %w", err)
}
// 验证非空
if len(scheduleVOS) == 0 {
return fmt.Errorf("--scheduleVOS must contain at least one schedule entry")
}
// 验证每条记录的必填字段
requiredFields := []string{"userId", "workDate", "classId", "isRest"}
for i, sched := range scheduleVOS {
for _, field := range requiredFields {
if _, ok := sched[field]; !ok {
return fmt.Errorf("schedule[%d] missing required field: %s (required: userId, workDate, classId, isRest)", i, field)
}
}
}
// 转换 workDate 格式为 yyyy-MM-dd HH:mm:ss
for i, sched := range scheduleVOS {
if workDate, ok := sched["workDate"]; ok {
normalized, err := normalizeWorkDate(workDate)
if err != nil {
return fmt.Errorf("schedule[%d] workDate format error: %w", i, err)
}
sched["workDate"] = normalized
}
}
// 转换 groupId 为 int64
groupIDInt, err := strconv.ParseInt(groupID, 10, 64)
if err != nil {
return fmt.Errorf("invalid --groupId format, must be a number: %w", err)
}
return callMCPToolOnServer("attendance-wukong", "generateTurnSchedule", map[string]any{
"GenerateTurnScheduleRequest": map[string]any{
"groupId": groupIDInt,
"scheduleVOS": scheduleVOS,
"param": map[string]any{
"useHistoryGroupAndShift": false,
},
},
})
},
}
DeclareLeafMetadata(scheduleImportCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "schedule_import",
CanonicalPath: "attendance.schedule_import",
CLIPath: "attendance schedule import",
PrimaryCLIPath: "attendance schedule import",
},
Description: "导入排班制考勤组排班记录",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "导入排班制考勤组排班记录",
UseWhen: []string{"需要向排班制考勤组批量导入或覆盖员工排班,且 groupId 与 scheduleVOS 已确认时"},
AvoidWhen: []string{
"仅查看排班时使用 attendance schedule get 或 attendance shift list",
"查询班次定义时使用 attendance class search/get",
"不确定 groupId/classId/workDate 时先查询考勤组和班次",
},
Examples: []string{"dws attendance schedule import --groupId 123456 --scheduleVOS '[{\"userId\":\"user001\",\"workDate\":\"2026-04-22 09:00:00\",\"classId\":123,\"isRest\":\"N\"}]'"},
},
},
})
scheduleImportCmd.Flags().String("groupId", "", "考勤组ID(必填)")
scheduleImportCmd.Flags().String("group-id", "", "考勤组ID(--groupId 的别名)")
scheduleImportCmd.Flags().String("scheduleVOS", "", "排班记录 JSON 数组(必填)")
scheduleImportCmd.Flags().String("schedules", "", "排班记录 JSON 数组(--scheduleVOS 的别名)")
_ = scheduleImportCmd.Flags().MarkHidden("group-id")
_ = scheduleImportCmd.Flags().MarkHidden("schedules")
scheduleImportCmd.Flags().Bool("user-say-yes", false, "用户已确认,跳过交互式确认提示(Agent 调用时传 true 前必须完成用户二次确认)")
scheduleImportCmd.Flags().Bool("yes", false, "跳过确认提示(--user-say-yes 的隐藏别名)")
_ = scheduleImportCmd.Flags().MarkHidden("yes")
attendanceScheduleCmd.AddCommand(scheduleImportCmd)
// schedule get (getScheduleByRange)
scheduleGetCmd := &cobra.Command{
Use: "get",
Short: "获取指定用户的排班记录",
Long: `获取指定用户在一段时间内的排班记录。
返回每条记录包含 userId、classId、workDate、className、checkBeginTime、checkEndTime 等字段。
日期格式支持:
- YYYY-MM-DD(如 2026-04-01)
- YYYY-MM-DD HH:mm:ss(如 2026-04-01 09:00:00)
示例:
dws attendance schedule get --users user001,user002 --start 2026-04-01 --end 2026-04-30`,
Example: ` dws attendance schedule get --users USER_IDS --start BEGIN_DATE --end END_DATE`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlagWithAliases(cmd, "users", "userIdList"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "start", "workDateBegin"); err != nil {
return err
}
if err := validateRequiredFlagWithAliases(cmd, "end", "workDateEnd"); err != nil {
return err
}
// 解析用户ID列表(优先 --users,兼容 --userIdList)
userIds := parseUserList(flagOrFallback(cmd, "users", "userIdList"))
// 解析日期参数并转换为时间戳(毫秒)
beginStr := flagOrFallback(cmd, "start", "workDateBegin")
beginTs, err := parseDateToTimestamp(beginStr, "start")
if err != nil {
return err
}
endStr := flagOrFallback(cmd, "end", "workDateEnd")
endTs, err := parseDateToTimestamp(endStr, "end")
if err != nil {
return err
}
return callMCPToolOnServer("attendance-wukong", "getScheduleByRange", map[string]any{
"GetScheduleByRangeRequest": map[string]any{
"userIdList": userIds,
"workDateBegin": beginTs,
"workDateEnd": endTs,
},
})
},
}
DeclareLeafMetadata(scheduleGetCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "schedule_get",
CanonicalPath: "attendance.schedule_get",
CLIPath: "attendance schedule get",
PrimaryCLIPath: "attendance schedule get",
},
Description: "查询员工排班记录",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询员工排班记录",
UseWhen: []string{"查询指定员工在日期范围内的排班记录"},
AvoidWhen: []string{
"查询班次定义时使用 attendance class get/search",
"批量查询员工班次结果时使用 attendance shift list",
"导入或修改排班时使用 attendance schedule import",
},
Examples: []string{"dws attendance schedule get --userIdList 03642229451220076 --workDateBegin 2026-05-13 --workDateEnd 2026-05-13 -f json"},
},
},
})
scheduleGetCmd.Flags().String("users", "", "用户ID列表,逗号分隔(必填)")
scheduleGetCmd.Flags().String("userIdList", "", "用户ID列表(--users 的别名)")
scheduleGetCmd.Flags().String("start", "", "开始日期,格式 YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss(必填)")
scheduleGetCmd.Flags().String("workDateBegin", "", "开始日期(--start 的别名)")
scheduleGetCmd.Flags().String("end", "", "结束日期,格式 YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss(必填)")
scheduleGetCmd.Flags().String("workDateEnd", "", "结束日期(--end 的别名)")
// 隐藏别名 flag,不在 --help 中显示
_ = scheduleGetCmd.Flags().MarkHidden("userIdList")
_ = scheduleGetCmd.Flags().MarkHidden("workDateBegin")
_ = scheduleGetCmd.Flags().MarkHidden("workDateEnd")
attendanceScheduleCmd.AddCommand(scheduleGetCmd)
// class search (get_class_list)
attendanceClassSearchCmd.Flags().Int("page", 1, "页码,从 1 开始(可选)")
attendanceClassSearchCmd.Flags().Int("page-index", 0, "页码(--page 的别名)")
attendanceClassSearchCmd.Flags().Int("limit", 20, "每页条数,最大 200(可选)")
attendanceClassSearchCmd.Flags().Int("page-size", 0, "每页条数(--limit 的别名)")
attendanceClassSearchCmd.Flags().Int("size", 0, "每页条数(--limit 的别名)")
_ = attendanceClassSearchCmd.Flags().MarkHidden("page-index")
_ = attendanceClassSearchCmd.Flags().MarkHidden("page-size")
_ = attendanceClassSearchCmd.Flags().MarkHidden("size")
attendanceClassSearchCmd.Flags().String("query", "", "班次名称关键字,模糊搜索(可选)")
attendanceClassSearchCmd.Flags().String("name", "", "班次名称关键字(--query 的别名)")
_ = attendanceClassSearchCmd.Flags().MarkHidden("name")
attendanceClassSearchCmd.Flags().String("filter-type", "", "班次类型:ALL 全部班次 / MINE_OWN 我负责的(可选)")
attendanceClassCmd.AddCommand(attendanceClassSearchCmd)
// class get (get_class_detail)
attendanceClassGetCmd.Flags().Int64("class-id", 0, "班次 ID(必填)")
attendanceClassCmd.AddCommand(attendanceClassGetCmd)
// class create (create_class_setting)
attendanceClassCreateCmd.Flags().String("name", "", "班次名称(必填)")
attendanceClassCreateCmd.Flags().String("owner", "", "班次负责人 userId(可选)")
attendanceClassCreateCmd.Flags().String("class-vo", "", "完整 TopAtClassVO JSON 字符串,包含 sections 等复杂子对象(必填)")
attendanceClassCreateCmd.Flags().Bool("yes", false, "跳过确认提示")
attendanceClassCmd.AddCommand(attendanceClassCreateCmd)
// class update (update_class_setting)
attendanceClassUpdateCmd.Flags().Int64("class-id", 0, "班次 ID(必填)")
attendanceClassUpdateCmd.Flags().Int64("id", 0, "班次 ID(--class-id 的别名)")
_ = attendanceClassUpdateCmd.Flags().MarkHidden("id")
attendanceClassUpdateCmd.Flags().String("name", "", "班次名称(可选,不传则保持原值)")
attendanceClassUpdateCmd.Flags().String("owner", "", "班次负责人 userId(可选,不传则保持原值)")
attendanceClassUpdateCmd.Flags().String("class-vo", "", "完整 TopAtClassVO JSON 字符串,包含 sections 等复杂子对象(可选)")
attendanceClassUpdateCmd.Flags().Bool("yes", false, "跳过确认提示")
attendanceClassCmd.AddCommand(attendanceClassUpdateCmd)
// adjustment get (get_adjustment_rule_detail)
attendanceAdjustmentGetCmd.Flags().Int64("adjustment-id", 0, "补卡规则主键 ID(必填)")
attendanceAdjustmentCmd.AddCommand(attendanceAdjustmentGetCmd)
// adjustment-rule search (get_adjustment_rule)
attendanceAdjustmentSearchCmd.Flags().String("query", "", "补卡规则名称关键字,模糊搜索(可选)")
attendanceAdjustmentSearchCmd.Flags().String("name", "", "补卡规则名称关键字(--query 的别名)")
attendanceAdjustmentSearchCmd.Flags().Int("page", 1, "页码,从 1 开始(默认 1,可选)")
attendanceAdjustmentSearchCmd.Flags().Int("current-page", 0, "页码(--page 的别名)")
attendanceAdjustmentSearchCmd.Flags().Int("limit", 20, "每页条数,200 以内(默认 20,可选)")
attendanceAdjustmentSearchCmd.Flags().Int("size", 0, "每页条数(--limit 的别名)")
_ = attendanceAdjustmentSearchCmd.Flags().MarkHidden("name")
_ = attendanceAdjustmentSearchCmd.Flags().MarkHidden("current-page")
_ = attendanceAdjustmentSearchCmd.Flags().MarkHidden("size")
attendanceAdjustmentCmd.AddCommand(attendanceAdjustmentSearchCmd)
// overtime get (get_overtime_rule_detail)
attendanceOvertimeGetCmd.Flags().Int64("overtime-id", 0, "加班规则主键 ID(必填)")
attendanceOvertimeCmd.AddCommand(attendanceOvertimeGetCmd)
// overtime-rule search (get_overtime_rule)
attendanceOvertimeSearchCmd.Flags().String("query", "", "加班规则名称关键字,模糊搜索(可选)")
attendanceOvertimeSearchCmd.Flags().String("name", "", "加班规则名称关键字(--query 的别名)")
attendanceOvertimeSearchCmd.Flags().Int("page", 1, "页码,从 1 开始(默认 1,可选)")
attendanceOvertimeSearchCmd.Flags().Int("current-page", 0, "页码(--page 的别名)")
attendanceOvertimeSearchCmd.Flags().Int("limit", 20, "每页条数,200 以内(默认 20,可选)")
attendanceOvertimeSearchCmd.Flags().Int("size", 0, "每页条数(--limit 的别名)")
_ = attendanceOvertimeSearchCmd.Flags().MarkHidden("name")
_ = attendanceOvertimeSearchCmd.Flags().MarkHidden("current-page")
_ = attendanceOvertimeSearchCmd.Flags().MarkHidden("size")
attendanceOvertimeCmd.AddCommand(attendanceOvertimeSearchCmd)
// group search (get_simple_groups)
attendanceGroupSearchCmd.Flags().String("query", "", "考勤组名称关键字,模糊搜索(可选)")
attendanceGroupSearchCmd.Flags().String("name", "", "考勤组名称关键字(--query 的别名)")
attendanceGroupSearchCmd.Flags().String("type", "", "考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时(可选)")
attendanceGroupSearchCmd.Flags().Bool("query-position", false, "是否查询地理定位和 Wifi 名称(可选)")
attendanceGroupSearchCmd.Flags().Bool("query-ble", false, "是否查询蓝牙设备列表(可选)")
attendanceGroupSearchCmd.Flags().Int("page", 1, "页码,从 1 开始(默认 1,可选)")
attendanceGroupSearchCmd.Flags().Int("page-index", 0, "页码(--page 的别名)")
attendanceGroupSearchCmd.Flags().Int("limit", 20, "每页条数,200 以内(默认 20,可选)")
attendanceGroupSearchCmd.Flags().Int("size", 0, "每页条数(--limit 的别名)")
_ = attendanceGroupSearchCmd.Flags().MarkHidden("name")
_ = attendanceGroupSearchCmd.Flags().MarkHidden("page-index")
_ = attendanceGroupSearchCmd.Flags().MarkHidden("size")
attendanceGroupCmd.AddCommand(attendanceGroupSearchCmd)
// group get (get_group_detail)
attendanceGroupGetCmd.Flags().Int64("group-id", 0, "考勤组 ID(必填)")
attendanceGroupGetCmd.Flags().Int64("id", 0, "考勤组 ID(--group-id 的别名)")
_ = attendanceGroupGetCmd.Flags().MarkHidden("id")
attendanceGroupCmd.AddCommand(attendanceGroupGetCmd)
// group filtered-get (get_group_filtered_detail)
attendanceGroupFilteredGetCmd.Flags().Int64("group-id", 0, "考勤组 ID(必填)")
attendanceGroupFilteredGetCmd.Flags().Int64("id", 0, "考勤组 ID(--group-id 的别名)")
_ = attendanceGroupFilteredGetCmd.Flags().MarkHidden("id")
attendanceGroupFilteredGetCmd.Flags().Bool("member", false, "是否查询考勤组成员信息(可选)")
attendanceGroupFilteredGetCmd.Flags().Bool("position", false, "是否查询打卡地址(可选)")
attendanceGroupFilteredGetCmd.Flags().Bool("wifi", false, "是否查询打卡 Wifi(可选)")
attendanceGroupFilteredGetCmd.Flags().Bool("bles", false, "是否查询打卡蓝牙(可选)")
attendanceGroupCmd.AddCommand(attendanceGroupFilteredGetCmd)
// group update-members (update_group_member)
attendanceGroupUpdateMembersCmd.Flags().Int64("group-id", 0, "考勤组 ID(必填)")
attendanceGroupUpdateMembersCmd.Flags().String("add-users", "", "添加考勤人员 userId 列表,逗号分隔,最多 20 个(可选)")
attendanceGroupUpdateMembersCmd.Flags().String("remove-users", "", "删除考勤人员 userId 列表,逗号分隔,最多 20 个(可选)")
attendanceGroupUpdateMembersCmd.Flags().String("add-extra-users", "", "添加无需考勤的人员 userId 列表,逗号分隔,最多 20 个(可选)")
attendanceGroupUpdateMembersCmd.Flags().String("remove-extra-users", "", "删除无需考勤的成员 userId 列表,逗号分隔,最多 20 个(可选)")
attendanceGroupUpdateMembersCmd.Flags().String("add-depts", "", "添加考勤部门 ID 列表,逗号分隔,最多 20 个(可选)")
attendanceGroupUpdateMembersCmd.Flags().String("remove-depts", "", "删除考勤部门 ID 列表,逗号分隔,最多 20 个(可选)")
attendanceGroupUpdateMembersCmd.Flags().Bool("yes", false, "跳过确认提示")
attendanceGroupCmd.AddCommand(attendanceGroupUpdateMembersCmd)
// group update (update_group)
attendanceGroupUpdateCmd.Flags().Int64("group-id", 0, "考勤组 ID(必填)")
attendanceGroupUpdateCmd.Flags().String("name", "", "考勤组名称(可选)")
attendanceGroupUpdateCmd.Flags().String("type", "", "考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时(可选)")
attendanceGroupUpdateCmd.Flags().String("owner", "", "考勤组主负责人 userId(可选)")
attendanceGroupUpdateCmd.Flags().String("enable-outside-check", "", "是否允许外勤打卡,传 true 或 false(可选)")
attendanceGroupUpdateCmd.Flags().String("classIds", "", "所选班次 id 列表,JSON 数组格式,如 '[123,456]'(可选)")
attendanceGroupUpdateCmd.Flags().String("group-vo", "", "完整 groupVO JSON 字符串,用于修改复杂子对象(可选)")
attendanceGroupUpdateCmd.Flags().Bool("yes", false, "跳过确认提示")
attendanceGroupCmd.AddCommand(attendanceGroupUpdateCmd)
// group create (create_group_setting)
attendanceGroupCreateCmd.Flags().String("name", "", "考勤组名称(必填)")
attendanceGroupCreateCmd.Flags().String("type", "", "考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)(必填)")
attendanceGroupCreateCmd.Flags().String("owner", "", "考勤组主负责人 userId(可选)")
attendanceGroupCreateCmd.Flags().String("corp-id", "", "企业 corpId(可选,不传时由登录上下文自动补齐)")
attendanceGroupCreateCmd.Flags().String("group-vo", "", "完整 groupVO JSON 字符串(可选,用于传入复杂子对象,会与 --name/--type/--owner 合并)")
attendanceGroupCreateCmd.Flags().Bool("yes", false, "跳过确认提示")
attendanceGroupCmd.AddCommand(attendanceGroupCreateCmd)
// summary (get_attendance_summary)
attendanceSummaryCmd.Flags().String("user", "", "钉钉用户 ID(必填)")
attendanceSummaryCmd.Flags().String("date", "", "查询日期,格式 YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss(必填)")
attendanceSummaryCmd.Flags().String("stats-type", "", "统计类型:week(周统计)/ month(月统计)(必填)")
attendanceSummaryCmd.Flags().String("tag-name", "", "旧版兼容参数(已废弃,不参与考勤摘要查询)")
_ = attendanceSummaryCmd.Flags().MarkDeprecated("tag-name", "--tag-name 不再参与 attendance summary 查询,请移除该参数")
_ = attendanceSummaryCmd.Flags().MarkHidden("tag-name")
// rules (query_attendance_group_or_rules)
attendanceRulesCmd.Flags().String("date", "", "考勤日期,格式 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss (必填)")
// selfsetting get (query_self_setting)
attendanceSelfSettingGetCmd.Flags().String("setting-scene", "", "查询设置项:checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify(必填)")
attendanceSelfSettingGetCmd.Flags().String("user", "", "查询用户 ID(必填)")
// selfsetting save (save_self_setting)
attendanceSelfSettingSaveCmd.Flags().String("setting-scene", "", "更新设置项:checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify(必填)")
attendanceSelfSettingSaveCmd.Flags().String("user", "", "更新用户 ID(必填)")
attendanceSelfSettingSaveCmd.Flags().Bool("yes", false, "跳过确认提示")
registerSelfSettingSaveFlags(attendanceSelfSettingSaveCmd)
attendanceSelfSettingCmd.AddCommand(attendanceSelfSettingGetCmd, attendanceSelfSettingSaveCmd)
// globalsetting get (query_global_setting)
attendanceGlobalSettingGetCmd.Flags().String("setting-scene", "", "查询设置项:checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify(必填)")
attendanceGlobalSettingGetCmd.Flags().String("scope", "", "全局范围确认,必须明确输入:企业/全公司/所有人(必填)")
// globalsetting save (save_global_setting)
attendanceGlobalSettingSaveCmd.Flags().String("setting-scene", "", "更新设置项:checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify(必填)")
attendanceGlobalSettingSaveCmd.Flags().String("scope", "", "全局范围确认,必须明确输入:企业/全公司/所有人(必填)")
attendanceGlobalSettingSaveCmd.Flags().Bool("yes", false, "跳过确认提示")
registerGlobalSettingSaveFlags(attendanceGlobalSettingSaveCmd)
attendanceGlobalSettingCmd.AddCommand(attendanceGlobalSettingGetCmd, attendanceGlobalSettingSaveCmd)
// report query-data (query_data_for_mcp)
reportQueryDataCmd.Flags().String("users", "", "目标用户 userID 列表,逗号分隔,最多 20 人(必填)")
reportQueryDataCmd.Flags().String("columns", "", "字段 ID 列表,逗号分隔,可通过 report columns 获取(必填)")
reportQueryDataCmd.Flags().String("start", "", "开始日期,格式 yyyy-MM-dd HH:mm:ss(必填)")
reportQueryDataCmd.Flags().String("end", "", "结束日期,格式 yyyy-MM-dd HH:mm:ss(必填)")
// report query-leave (query_leave_data_for_mcp)
reportQueryLeaveCmd.Flags().String("users", "", "目标用户 userID 列表,逗号分隔,最多 20 人(必填)")
reportQueryLeaveCmd.Flags().String("leave-names", "", "假期类型名称列表,逗号分隔,不填则查询所有假期类型(选填)")
reportQueryLeaveCmd.Flags().String("start", "", "开始日期,格式 yyyy-MM-dd HH:mm:ss(必填)")
reportQueryLeaveCmd.Flags().String("end", "", "结束日期,格式 yyyy-MM-dd HH:mm:ss(必填)")
attendanceReportCmd.AddCommand(
reportColumnsCmd,
reportQueryDataCmd,
reportQueryLeaveCmd,
)
// vacation balance (get_leave_balance_quota)
vacationBalanceCmd.Flags().String("users", "", "目标员工 ID 列表,逗号分隔 (必填)")
vacationBalanceCmd.Flags().String("leave-code", "", "假期规则 code (必填,服务端要求非空,不传返回 INVALID_PARAMS)")
// vacation records (get_leave_balance_records)
vacationRecordsCmd.Flags().String("user", "", "指定查询员工 ID (必填)")
vacationRecordsCmd.Flags().String("leave-code", "", "假期规则 code (必填,服务端要求非空,不传返回 INVALID_PARAMS)")
vacationRecordsCmd.Flags().String("start", "", "查询开始日期,格式 YYYY-MM-DD (必填)")
vacationRecordsCmd.Flags().String("end", "", "查询结束日期,格式 YYYY-MM-DD (必填)")
// vacation update-type (save_leave_type)
vacationUpdateTypeCmd.Flags().String("leave-code", "", "假期编码(必填)")
vacationUpdateTypeCmd.Flags().String("name", "", "假期名称(可选)")
vacationUpdateTypeCmd.Flags().String("unit", "", "假期单位:day/halfDay/hour(可选)")
vacationUpdateTypeCmd.Flags().Bool("paid", false, "是否带薪假期(可选)")
vacationUpdateTypeCmd.Flags().Int("per-hours", 0, "一天折算小时数(可选)")
vacationUpdateTypeCmd.Flags().String("when-can-leave", "", "新员工请假规则:entry/formal(可选)")
vacationUpdateTypeCmd.Flags().String("visibility-rules", "", "适用范围规则 JSON 数组(可选)。不传=不改;[{\"type\":\"dept\",\"visible\":[\"-1\"]}]=全公司可见(哨兵);其余=改为指定范围。空数组/无效规则会报错")
vacationUpdateTypeCmd.Flags().Bool("user-say-yes", false, "用户已确认,跳过交互式确认提示(Agent 调用时传 true 前必须完成用户二次确认)")
vacationUpdateTypeCmd.Flags().Bool("yes", false, "跳过确认提示(--user-say-yes 的隐藏别名)")
_ = vacationUpdateTypeCmd.Flags().MarkHidden("yes")
// vacation save-balance (update_leave_balance)
vacationSaveBalanceCmd.Flags().String("target", "", "目标员工工号(必填)")
vacationSaveBalanceCmd.Flags().String("leave-code", "", "假期编码(必填)")
vacationSaveBalanceCmd.Flags().String("num", "", "余额数量(必填)")
vacationSaveBalanceCmd.Flags().String("reason", "", "变更原因(必填)")
vacationSaveBalanceCmd.Flags().String("start", "", "有效期开始日期 YYYY-MM-DD")
vacationSaveBalanceCmd.Flags().String("end", "", "有效期结束日期 YYYY-MM-DD")
vacationSaveBalanceCmd.Flags().Bool("user-say-yes", false, "用户已确认,跳过交互式确认提示(Agent 调用时传 true 前必须完成用户二次确认)")
vacationSaveBalanceCmd.Flags().Bool("yes", false, "跳过确认提示(--user-say-yes 的隐藏别名)")
_ = vacationSaveBalanceCmd.Flags().MarkHidden("yes")
attendanceVacationCmd.AddCommand(
vacationTypesCmd,
vacationUpdateTypeCmd,
vacationBalanceCmd,
vacationSaveBalanceCmd,
vacationRecordsCmd,
)
// ── checkin ──────────────────────────────────────────────
checkinCmd := &cobra.Command{
Use: "checkin",
Short: "签到管理",
Long: `签到记录的查询。
子命令:
records 查询指定员工的签到记录`,
RunE: groupRunE,
}
// MCP tool: queryUserRecordByStaffIds
checkinRecordsCmd := &cobra.Command{
Use: "records",
Short: "查询指定员工的签到记录",
Long: `查询指定员工在一段时间内的签到记录。
权限说明:
- Boss / 超级管理员 / 超级子管理员:可查看全公司员工
- 子管理员:可查看管理范围内的员工及自己
- 部门主管:可查看所管理部门的员工及自己
- 普通员工:只能查询自己
日期格式:yyyy-MM-dd HH:mm:ss(如 2026-04-01 09:00:00)`,
Example: ` dws attendance checkin records --operator-corp-id dingXXXXXX --operator-staff-id op001 --staff-ids user001,user002 --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "operator-corp-id", "operator-staff-id", "staff-ids", "start", "end"); err != nil {
return err
}
operatorCorpId := mustGetFlag(cmd, "operator-corp-id")
operatorStaffId := mustGetFlag(cmd, "operator-staff-id")
staffIds := parseUserList(mustGetFlag(cmd, "staff-ids"))
if len(staffIds) == 0 {
return fmt.Errorf("--staff-ids must contain at least one staff ID")
}
startStr := mustGetFlag(cmd, "start")
startT, err := time.ParseInLocation("2006-01-02 15:04:05", startStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --start format, use yyyy-MM-dd HH:mm:ss (e.g. 2026-04-01 09:00:00): %w", err)
}
endStr := mustGetFlag(cmd, "end")
endT, err := time.ParseInLocation("2006-01-02 15:04:05", endStr, time.Local)
if err != nil {
return fmt.Errorf("invalid --end format, use yyyy-MM-dd HH:mm:ss (e.g. 2026-04-30 23:59:59): %w", err)
}
if endT.Before(startT) {
return fmt.Errorf("--end must not be earlier than --start")
}
return callMCPToolOnServer("attendance-wukong", "get_checkin_record", map[string]any{
"QueryMcpUserRecordRequest": map[string]any{
"operatorCorpId": operatorCorpId,
"operatorStaffId": operatorStaffId,
"staffIds": staffIds,
"startTime": startStr,
"endTime": endStr,
},
})
},
}
DeclareLeafMetadata(checkinRecordsCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "checkin_records",
CanonicalPath: "attendance.checkin_records",
CLIPath: "attendance checkin records",
PrimaryCLIPath: "attendance checkin records",
},
Description: "查询打卡流水记录",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "查询打卡流水记录",
UseWhen: []string{"查询员工打卡流水、定位或打卡时间记录"},
AvoidWhen: []string{
"查询考勤汇总时使用 attendance summary",
"查询班次/排班时使用 attendance shift list 或 schedule get",
"修改打卡记录时使用 attendance boss-check",
},
Examples: []string{
"dws attendance checkin records --operator-corp-id corp001 --operator-staff-id op001 --staff-ids user001,user002 --start \"2026-04-01 00:00:00\" --end \"2026-04-07 00:00:00\"",
"dws attendance checkin records --operator-corp-id corp001 --operator-staff-id op001 --staff-ids user001,user002 --start \"2026-04-01 00:00:00\" --end \"2026-04-07 00:00:00\" --format json",
},
},
},
})
checkinRecordsCmd.Flags().String("operator-corp-id", "", "操作者企业 ID(必填)")
checkinRecordsCmd.Flags().String("operator-staff-id", "", "操作者员工 ID(必填)")
checkinRecordsCmd.Flags().String("staff-ids", "", "目标员工 ID 列表, 逗号分隔(必填),最多100人")
checkinRecordsCmd.Flags().String("start", "", "开始时间, 格式 yyyy-MM-dd HH:mm:ss(必填),开始到结束最多7天")
checkinRecordsCmd.Flags().String("end", "", "结束时间, 格式 yyyy-MM-dd HH:mm:ss(必填),开始到结束最多7天")
checkinCmd.AddCommand(checkinRecordsCmd)
// ── boss-check ──────────────────────────────────────────────
// MCP tool: boss_check
bossCheckCmd := &cobra.Command{
Use: "boss-check",
Short: "BOSS 改签打卡记录",
Long: `调用 MCP 工具 boss_check 修改员工打卡记录。管理员可修改打卡时间、打卡结果等信息。
参数说明:
--plan-id 排班ID(与 --result-id 二选一)
来源:通过 dws attendance schedule get 获取,返回结果的 id 字段
示例:id: 948964045503 表示某天下班打卡的排班记录ID
--result-id 打卡结果ID(与 --plan-id 二选一,优先使用)
来源:暂不支持(record get 未返回此字段),建议使用 --plan-id
--time 新打卡时间,格式 yyyy-MM-dd HH:mm(可选)
--result 打卡结果:Normal/TimesResultA/TimesResultB/TimesResultC/TimesResultD/TimesResultE/TimesResultF(可选)
--absent-min 缺勤时长(分钟),当 result 为异常状态时需传值(可选)
--remark 备注,最长500字符(可选)
--user-say-yes 用户已确认,跳过交互式确认提示
打卡结果枚举值:
Normal 正常
TimesResultA 迟到
TimesResultB 早退
TimesResultC 缺卡
TimesResultD 迟到+早退
TimesResultE 缺卡+早退
TimesResultF 迟到+缺卡
获取 planId 步骤:
1. 查询排班记录:dws attendance schedule get --userIdList USER_ID --workDateBegin DATE --workDateEnd DATE
2. 从返回结果中找到对应打卡类型(OnDuty=上班,OffDuty=下班)的 id 字段
3. 使用该 id 作为 --plan-id 参数`,
Example: ` # 通过排班ID改签,指定打卡时间
dws attendance boss-check --plan-id 123456 --time "2025-04-21 08:30"
# 通过结果ID改签,修改打卡结果为正常
dws attendance boss-check --result-id 789012 --result Normal
# 修改打卡结果为迟到,并设置缺勤时长
dws attendance boss-check --result-id 789012 --result TimesResultA --absent-min 30
# 添加备注
dws attendance boss-check --result-id 789012 --remark "管理员改签"`,
RunE: func(cmd *cobra.Command, args []string) error {
// 检查 plan-id 和 result-id 至少传一个
planID := mustGetFlag(cmd, "plan-id")
resultID := mustGetFlag(cmd, "result-id")
if planID == "" && resultID == "" {
return fmt.Errorf("--plan-id 和 --result-id 至少传一个")
}
// 构建 MCP 调用参数
req := map[string]any{}
// planId 和 resultId 二选一,优先 resultId
if resultID != "" {
req["resultId"] = resultID
} else if planID != "" {
req["planId"] = planID
}
// 可选参数
if cmd.Flags().Changed("time") {
req["bossCheckTime"] = mustGetFlag(cmd, "time")
}
if cmd.Flags().Changed("result") {
req["result"] = mustGetFlag(cmd, "result")
}
if cmd.Flags().Changed("absent-min") {
absentMin, _ := cmd.Flags().GetInt("absent-min")
req["absentMin"] = absentMin
}
if cmd.Flags().Changed("remark") {
req["remark"] = mustGetFlag(cmd, "remark")
}
return callMCPToolOnServer("attendance-wukong", "boss_check", map[string]any{
"BossCheckMcpRequest": req,
})
},
}
DeclareLeafMetadata(bossCheckCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "attendance",
Name: "boss_check",
CanonicalPath: "attendance.boss_check",
CLIPath: "attendance boss-check",
PrimaryCLIPath: "attendance boss-check",
},
Description: "管理员修正员工打卡结果",
Interface: &contract.InterfaceSpec{
Mode: "composite",
Availability: "available",
Reason: "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.",
},
Selection: contract.SelectionSpec{
AgentSummary: "管理员修正员工打卡结果",
UseWhen: []string{"管理员明确要求修正员工打卡时间或打卡结果,且已确认 planId/resultId 与变更内容时"},
AvoidWhen: []string{
"只查询打卡记录时使用 attendance check record/check result",
"查询考勤统计时使用 attendance summary",
"用户尚未确认改签目标与结果时不要执行",
},
Examples: []string{
"dws attendance boss-check --plan-id 123456 --time \"2025-04-21 08:30\"",
"dws attendance boss-check --result-id 789012 --result Normal",
},
},
Parameters: []contract.ParamDecl{
{Name: "plan-id", Required: boolPtr(true)},
},
},
})
bossCheckCmd.Flags().String("plan-id", "", "排班ID(与 --result-id 二选一)")
bossCheckCmd.Flags().String("result-id", "", "打卡结果ID(与 --plan-id 二选一,优先使用)")
bossCheckCmd.Flags().String("time", "", "新打卡时间,格式 yyyy-MM-dd HH:mm(可选)")
bossCheckCmd.Flags().String("result", "", "打卡结果:Normal/TimesResultA/TimesResultB/TimesResultC/TimesResultD/TimesResultE/TimesResultF(可选)")
bossCheckCmd.Flags().Int("absent-min", 0, "缺勤时长(分钟)(可选)")
bossCheckCmd.Flags().String("remark", "", "备注,最长500字符(可选)")
bossCheckCmd.Flags().Bool("user-say-yes", false, "用户已确认,跳过交互式确认提示")
root.AddCommand(
attendanceRecordCmd,
attendanceCheckCmd,
attendanceApproveCmd,
attendanceShiftCmd,
attendanceScheduleCmd,
attendanceClassCmd,
attendanceAdjustmentCmd,
attendanceOvertimeCmd,
attendanceGroupCmd,
attendanceSummaryCmd,
attendanceRulesCmd,
attendanceSelfSettingCmd,
attendanceGlobalSettingCmd,
attendanceReportCmd,
attendanceVacationCmd,
checkinCmd,
bossCheckCmd,
)
return root
}