378 lines
14 KiB
Go
378 lines
14 KiB
Go
// Copyright 2026 Alibaba Group
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
package helpers
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cobracmd"
|
||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
|
||
"github.com/spf13/cobra"
|
||
)
|
||
|
||
func init() {
|
||
RegisterPublic(func() Handler {
|
||
return attendanceHandler{}
|
||
})
|
||
}
|
||
|
||
type attendanceHandler struct{}
|
||
|
||
func (attendanceHandler) Name() string {
|
||
return "attendance"
|
||
}
|
||
|
||
func (attendanceHandler) Command(runner executor.Runner) *cobra.Command {
|
||
root := &cobra.Command{
|
||
Use: "attendance",
|
||
Short: "考勤打卡 / 排班 / 统计",
|
||
Long: `管理钉钉考勤:查询个人考勤详情、批量查询排班、获取考勤统计摘要、查询考勤组与规则。
|
||
|
||
子命令:
|
||
record 考勤记录(个人考勤详情)
|
||
shift 排班管理(批量查询班次 / 排班信息)
|
||
summary 获取考勤统计摘要
|
||
rules 查询考勤组与考勤规则`,
|
||
Args: cobra.NoArgs,
|
||
TraverseChildren: true,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return cmd.Help()
|
||
},
|
||
}
|
||
|
||
record := &cobra.Command{
|
||
Use: "record",
|
||
Short: "考勤记录",
|
||
Args: cobra.NoArgs,
|
||
TraverseChildren: true,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return cmd.Help()
|
||
},
|
||
}
|
||
record.AddCommand(
|
||
newAttendanceRecordGetCommand(runner),
|
||
newAttendanceRecordListCommand(runner),
|
||
)
|
||
|
||
shift := &cobra.Command{
|
||
Use: "shift",
|
||
Short: "排班管理",
|
||
Args: cobra.NoArgs,
|
||
TraverseChildren: true,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return cmd.Help()
|
||
},
|
||
}
|
||
shift.AddCommand(newAttendanceShiftListCommand(runner))
|
||
|
||
root.AddCommand(
|
||
record,
|
||
shift,
|
||
newAttendanceSummaryCommand(runner),
|
||
newAttendanceRulesCommand(runner),
|
||
)
|
||
return root
|
||
}
|
||
|
||
// ── record get ─────────────────────────────────────────────
|
||
|
||
func newAttendanceRecordGetCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "get",
|
||
Short: "查询个人考勤详情",
|
||
Example: " dws attendance record get --user USER_ID --date 2026-03-08",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
userID, _ := cmd.Flags().GetString("user")
|
||
dateStr, _ := cmd.Flags().GetString("date")
|
||
if userID == "" {
|
||
return apperrors.NewValidation("--user is required")
|
||
}
|
||
if dateStr == "" {
|
||
return apperrors.NewValidation("--date is required (format: YYYY-MM-DD)")
|
||
}
|
||
t, err := time.ParseInLocation("2006-01-02", dateStr, time.Local)
|
||
if err != nil {
|
||
return apperrors.NewValidation("--date format error, use YYYY-MM-DD, e.g. 2026-03-08")
|
||
}
|
||
params := map[string]any{
|
||
"userId": userID,
|
||
"workDate": t.UnixMilli(),
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "get_user_attendance_record", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "get_user_attendance_record", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("user", "", "钉钉用户 ID (必填)")
|
||
cmd.Flags().String("date", "", "查询日期,格式 YYYY-MM-DD (必填)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── record list ────────────────────────────────────────────
|
||
|
||
func newAttendanceRecordListCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "批量查询考勤打卡记录",
|
||
Long: "批量查询多个员工在指定日期范围的考勤打卡记录。",
|
||
Hidden: true,
|
||
Example: " dws attendance record list --users userId1,userId2 --start 2026-03-03 --end 2026-03-07",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
usersStr, _ := cmd.Flags().GetString("users")
|
||
startStr, _ := cmd.Flags().GetString("start")
|
||
endStr, _ := cmd.Flags().GetString("end")
|
||
if usersStr == "" {
|
||
return apperrors.NewValidation("--users is required")
|
||
}
|
||
if startStr == "" {
|
||
return apperrors.NewValidation("--start is required (format: YYYY-MM-DD)")
|
||
}
|
||
if endStr == "" {
|
||
return apperrors.NewValidation("--end is required (format: YYYY-MM-DD)")
|
||
}
|
||
startT, err := time.ParseInLocation("2006-01-02", startStr, time.Local)
|
||
if err != nil {
|
||
return apperrors.NewValidation("--start date format error, use YYYY-MM-DD")
|
||
}
|
||
endT, err := time.ParseInLocation("2006-01-02", endStr, time.Local)
|
||
if err != nil {
|
||
return apperrors.NewValidation("--end date format error, use YYYY-MM-DD")
|
||
}
|
||
var userIds []any
|
||
for _, u := range strings.Split(usersStr, ",") {
|
||
if s := strings.TrimSpace(u); s != "" {
|
||
userIds = append(userIds, s)
|
||
}
|
||
}
|
||
params := map[string]any{
|
||
"userIds": userIds,
|
||
"fromDateTime": startT.UnixMilli(),
|
||
"toDateTime": endT.UnixMilli(),
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "get_user_attendance_record", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "get_user_attendance_record", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("users", "", "Comma-separated user ID list (required)")
|
||
cmd.Flags().String("start", "", "Start date, format YYYY-MM-DD (required)")
|
||
cmd.Flags().String("end", "", "End date, format YYYY-MM-DD (required)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── shift list ─────────────────────────────────────────────
|
||
|
||
func newAttendanceShiftListCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "list",
|
||
Short: "批量查询员工班次信息",
|
||
Long: "批量查询多个员工在指定日期的班次信息。最多 7 天,最多 50 人。",
|
||
Example: " dws attendance shift list --users userId1,userId2 --start 2026-03-03 --end 2026-03-07",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
usersStr, _ := cmd.Flags().GetString("users")
|
||
startStr, _ := cmd.Flags().GetString("start")
|
||
endStr, _ := cmd.Flags().GetString("end")
|
||
if usersStr == "" {
|
||
return apperrors.NewValidation("--users is required")
|
||
}
|
||
if startStr == "" {
|
||
return apperrors.NewValidation("--start is required (format: YYYY-MM-DD)")
|
||
}
|
||
if endStr == "" {
|
||
return apperrors.NewValidation("--end is required (format: YYYY-MM-DD)")
|
||
}
|
||
startT, err := time.ParseInLocation("2006-01-02", startStr, time.Local)
|
||
if err != nil {
|
||
return apperrors.NewValidation("--start date format error, use YYYY-MM-DD")
|
||
}
|
||
endT, err := time.ParseInLocation("2006-01-02", endStr, time.Local)
|
||
if err != nil {
|
||
return apperrors.NewValidation("--end date format error, use YYYY-MM-DD")
|
||
}
|
||
var userIds []any
|
||
for _, u := range strings.Split(usersStr, ",") {
|
||
if s := strings.TrimSpace(u); s != "" {
|
||
userIds = append(userIds, s)
|
||
}
|
||
}
|
||
params := map[string]any{
|
||
"userIds": userIds,
|
||
"fromDateTime": startT.UnixMilli(),
|
||
"toDateTime": endT.UnixMilli(),
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "batch_get_employee_shifts", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "batch_get_employee_shifts", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("users", "", "Comma-separated user ID list, max 50 (required)")
|
||
cmd.Flags().String("start", "", "Start date, format YYYY-MM-DD (required)")
|
||
cmd.Flags().String("end", "", "End date, format YYYY-MM-DD (required)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── summary ────────────────────────────────────────────────
|
||
|
||
func newAttendanceSummaryCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "summary",
|
||
Short: "查询某个人的考勤统计摘要",
|
||
Long: `查询某个人的考勤统计摘要。
|
||
|
||
--user、--date、--stats-type 均必填。
|
||
钉钉服务端业务层强制要求 --stats-type(week/month),不填会返回 C0002 统计类型错误。`,
|
||
Example: ` dws attendance summary --user USER_ID --date "2026-03-12 15:00:00" --stats-type month
|
||
dws attendance summary --user USER_ID --date "2026-03-12 15:00:00" --stats-type week`,
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
userID, _ := cmd.Flags().GetString("user")
|
||
workDateStr, _ := cmd.Flags().GetString("date")
|
||
statsType, _ := cmd.Flags().GetString("stats-type")
|
||
if userID == "" {
|
||
return apperrors.NewValidation("--user is required, provide DingTalk user ID")
|
||
}
|
||
if workDateStr == "" {
|
||
return apperrors.NewValidation("--date is required, format yyyy-MM-dd HH:mm:ss")
|
||
}
|
||
_, err := time.ParseInLocation("2006-01-02 15:04:05", workDateStr, time.Local)
|
||
if err != nil {
|
||
return apperrors.NewValidation("--date format error, use yyyy-MM-dd HH:mm:ss")
|
||
}
|
||
if statsType == "" {
|
||
return apperrors.NewValidation(`--stats-type is required (week|month), enforced by DingTalk server`)
|
||
}
|
||
if statsType != "week" && statsType != "month" {
|
||
return apperrors.NewValidation(`--stats-type must be "week" or "month"`)
|
||
}
|
||
// Build nested structure QueryUserAttendVO
|
||
vo := map[string]any{
|
||
"userId": userID,
|
||
"queryDate": workDateStr,
|
||
"statsType": statsType,
|
||
}
|
||
params := map[string]any{
|
||
"QueryUserAttendVO": vo,
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "get_attendance_summary", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "get_attendance_summary", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("user", "", "钉钉用户 ID(必填)")
|
||
cmd.Flags().String("date", "", "工作日期,格式 yyyy-MM-dd HH:mm:ss,如 2026-03-12 15:00:00(必填)")
|
||
cmd.Flags().String("stats-type", "", "统计类型:week(周统计)或 month(月统计)(必填,钉钉服务端业务层强制要求)")
|
||
annotateFlagExample(cmd, "date", "2026-03-12 15:00:00")
|
||
annotateFlagEnum(cmd, "stats-type", "week", "month")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|
||
|
||
// ── rules ──────────────────────────────────────────────────
|
||
|
||
func newAttendanceRulesCommand(runner executor.Runner) *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "rules",
|
||
Short: "查询考勤组与考勤规则",
|
||
Long: "调用 MCP 工具 query_attendance_group_or_rules 查询考勤组/考勤规则。\n例如:我属于哪个考勤组、打卡范围是什么、弹性工时怎么算。",
|
||
Example: " dws attendance rules --date 2026-03-14\n dws attendance rules --date \"2026-03-14 09:00:00\"",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
dateStr, _ := cmd.Flags().GetString("date")
|
||
if dateStr == "" {
|
||
return apperrors.NewValidation("--date is required")
|
||
}
|
||
// Support YYYY-MM-DD or yyyy-MM-dd HH:mm:ss, normalize to 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 apperrors.NewValidation(fmt.Sprintf("--date format error, use YYYY-MM-DD or yyyy-MM-dd HH:mm:ss: %s", dateStr))
|
||
}
|
||
params := map[string]any{
|
||
"date": dateFormatted,
|
||
}
|
||
if commandDryRun(cmd) {
|
||
return writeCommandPayload(cmd, executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "query_attendance_group_or_rules", params,
|
||
))
|
||
}
|
||
result, err := runner.Run(cmd.Context(), executor.NewHelperInvocation(
|
||
cobracmd.LegacyCommandPath(cmd), "attendance", "query_attendance_group_or_rules", params,
|
||
))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return writeCommandPayload(cmd, result)
|
||
},
|
||
}
|
||
cmd.Flags().String("date", "", "考勤日期,格式 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss (必填)")
|
||
preferLegacyLeaf(cmd)
|
||
return cmd
|
||
}
|