4478 lines
208 KiB
Go
4478 lines
208 KiB
Go
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
|
||
}
|