From 8d61e26e000bce6d58695a622c540f723ae8f26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=AE=81?= Date: Wed, 31 Mar 2021 15:57:00 +0800 Subject: [PATCH] process instance --- .gitignore | 3 +- dingtalk/dingtalk.go | 60 ++++++++++++++++- dingtalk/dingtalk_test.go | 71 +++++++++++++++++--- dingtalk/types.go | 135 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 5f3e193..d142be2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ # Dependency directories (remove the comment below to include it) vendor/ -.idea/ \ No newline at end of file +.idea/ +.env \ No newline at end of file diff --git a/dingtalk/dingtalk.go b/dingtalk/dingtalk.go index 0f81b2c..8e93223 100644 --- a/dingtalk/dingtalk.go +++ b/dingtalk/dingtalk.go @@ -186,7 +186,7 @@ func (ding *Client) GetUserInfoV2(ctx context.Context, req *RequestUserGet) (*Re } // 根据手机号获取userid v2 https://ding-doc.dingtalk.com/document#/org-dev-guide/query-users-by-phone-number -func (ding *Client) GetUserInfoByMobileV2(ctx context.Context, mobile string) (string, *http.Response, error){ +func (ding *Client) GetUserInfoByMobileV2(ctx context.Context, mobile string) (string, *http.Response, error) { ret := new(ResponseUserByMobile) var res *http.Response var err error @@ -207,7 +207,7 @@ func (ding *Client) GetUserInfoByMobileV2(ctx context.Context, mobile string) (s } // 获取指定用户的所有父部门列表 https://ding-doc.dingtalk.com/document#/org-dev-guide/obtains-the-list-of-all-parent-departments-of-a-user -func (ding *Client) ListParentDeptByUserV2 (ctx context.Context, userid string) (*DeptListParent, *http.Response, error){ +func (ding *Client) ListParentDeptByUserV2(ctx context.Context, userid string) (*DeptListParent, *http.Response, error) { ret := new(ResponseGetUserIdByUnionid) var res *http.Response var err error @@ -225,7 +225,7 @@ func (ding *Client) ListParentDeptByUserV2 (ctx context.Context, userid string) } // 获取部门详情 https://ding-doc.dingtalk.com/document#/org-dev-guide/queries-department-details-v1 -func (ding *Client) GetDepartment(ctx context.Context, req *RequestDepartmentInfo) (*DepartmentInfo, *http.Response, error){ +func (ding *Client) GetDepartment(ctx context.Context, req *RequestDepartmentInfo) (*DepartmentInfo, *http.Response, error) { ret := new(ResponseDeptInfo) var res *http.Response var err error @@ -245,3 +245,57 @@ func (ding *Client) GetDepartment(ctx context.Context, req *RequestDepartmentInf }) return ret.DepartmentInfo, res, err } + +// 发起审批实例 https://developers.dingtalk.com/document/app/initiate-approval +func (ding *Client) CreateProcessInstance(ctx context.Context, req *RequestCreateProcessInstance) (string, *http.Response, error) { + ret := new(ResponseCreateProcessInstance) + var err error + var res *http.Response + + err = ding.RetryOnAccessTokenExpired(ctx, 1, func() error { + res, _, err = ding.client.PostWithContext( + ctx, + ding.url+"/topapi/processinstance/create", + requests.Params{Query: requests.Any{"access_token": ding.AccessToken()}, Json: req}, + UnmarshalAndParseError(ret), + ) + return err + }) + return ret.ProcessInstanceID, res, err +} + +// 获取审批实例详情 https://developers.dingtalk.com/document/app/obtains-the-details-of-a-single-approval-instance +func (ding *Client) GetProcessInstance(ctx context.Context, processInstanceID string) (*ProcessInstance, *http.Response, error) { + ret := new(ResponseGetProcessInstance) + var err error + var res *http.Response + + err = ding.RetryOnAccessTokenExpired(ctx, 1, func() error { + res, _, err = ding.client.PostWithContext( + ctx, + ding.url+"/topapi/processinstance/get", + requests.Params{Query: requests.Any{"access_token": ding.AccessToken()}, Json: requests.Any{"process_instance_id": processInstanceID}}, + UnmarshalAndParseError(ret), + ) + return err + }) + return ret.ProcessInstance, res, err +} + +// 获取部门详情,可以得到部门主管 +func (ding *Client) GetDepartmentV2(ctx context.Context, deptID int) (*DepartmentInfoV2, *http.Response, error) { + ret := new(ResponseDeptInfoV2) + var res *http.Response + var err error + + err = ding.RetryOnAccessTokenExpired(ctx, 1, func() error { + res, _, err = ding.client.PostWithContext( + ctx, + ding.url+"/topapi/v2/department/get", + requests.Params{Query: requests.Any{"access_token": ding.AccessToken()}, Json: &RequestDepartmentInfoV2{DeptID: deptID}}, + UnmarshalAndParseError(ret), + ) + return err + }) + return ret.Result, res, err +} diff --git a/dingtalk/dingtalk_test.go b/dingtalk/dingtalk_test.go index f1f43fd..e49c9e3 100644 --- a/dingtalk/dingtalk_test.go +++ b/dingtalk/dingtalk_test.go @@ -2,6 +2,8 @@ package dingtalk import ( "context" + "encoding/json" + "fmt" "github.com/stretchr/testify/assert" "os" "testing" @@ -12,16 +14,19 @@ var ctx = context.Background() var testUser *ResponseGetUserInfo var ( - AgentID = os.Getenv("AgentID") - AppKey = os.Getenv("AppKey") + AgentID = os.Getenv("AgentID") + AppKey = os.Getenv("AppKey") AppSecret = os.Getenv("AppSecret") - UserID = os.Getenv("UserID") + UserID = os.Getenv("UserID") ) -func init() { +func init() { DingClient = NewClient(Option{AgentID: AgentID, AppKey: AppKey, AppSecret: AppSecret}) - var err error - testUser, _ , err = DingClient.GetUserInfoV2(ctx, &RequestUserGet{UserID: UserID}) + _, _, err := DingClient.GetAccessToken(ctx) + if err != nil { + panic("get access token fail:" + err.Error()) + } + testUser, _, err = DingClient.GetUserInfoV2(ctx, &RequestUserGet{UserID: UserID}) if err != nil { panic("userid not exit" + err.Error()) } @@ -62,13 +67,13 @@ func TestClient_GetUserInfoByMobileV2(t *testing.T) { } func TestClient_GetUserInfoV2(t *testing.T) { - user, _ , err := DingClient.GetUserInfoV2(ctx, &RequestUserGet{UserID: testUser.Result.UserID}) + user, _, err := DingClient.GetUserInfoV2(ctx, &RequestUserGet{UserID: testUser.Result.UserID}) assert.Nil(t, err) assert.Equal(t, user.Result.UserID, testUser.Result.UserID) } func TestClient_ListParentDeptByUserV2(t *testing.T) { - _, _ , err := DingClient.ListParentDeptByUserV2(ctx, testUser.Result.UserID) + _, _, err := DingClient.ListParentDeptByUserV2(ctx, testUser.Result.UserID) assert.Nil(t, err) } @@ -77,4 +82,54 @@ func TestClient_GetOrganizationUserCount(t *testing.T) { assert.Nil(t, err) } +func TestClient_CreateProcessInstance(t *testing.T) { + req := &RequestCreateProcessInstance{ + FormComponentValues: []*FormComponentValue{ + { + Name: "补卡时间", + Value: "2021-03-31 09:00", + }, + { + Name: "补卡理由", + Value: "忘打卡", + }, + }, + AgentID: AgentID, + DeptID: "477274342", + ProcessCode: "PROC-0F200284-842E-46FF-9ACC-64DB3176150C", + OriginatorUserID: UserID, + ApproversV2: []ProcessApprovers{ + { + TaskActionType: "OR", + UserIDs: []string{UserID, "1824661867777911"}, + }, + { + TaskActionType: "NONE", + UserIDs: []string{UserID}, + }, + }, + } + id, _, _ := DingClient.CreateProcessInstance(ctx, req) + fmt.Println(id) +} + +func TestClient_GetProcessInstance(t *testing.T) { + //"f4b924d1-b13b-4dc1-ae5a-aea4716ecb5c" + //"01b3a55b-d92d-40c7-ae70-87b13daf11e5" + pi, _, _ := DingClient.GetProcessInstance(ctx, "01b3a55b-d92d-40c7-ae70-87b13daf11e5") + b, _ := json.Marshal(pi) + fmt.Println(string(b)) +} +func TestClient_1(t *testing.T) { + // 获取用户父部门列表 + res, _, _ := DingClient.ListParentDeptByUserV2(ctx, UserID) + b, _ := json.Marshal(res) + fmt.Println(string(b)) + for _, dept := range res.ParentDeptList[0].ParentDeptIdList { + // 获取部门详情 + resp, _, _ := DingClient.GetDepartmentV2(ctx, dept) + b, _ = json.Marshal(resp) + fmt.Println(string(b)) + } +} diff --git a/dingtalk/types.go b/dingtalk/types.go index 272981e..2008a96 100644 --- a/dingtalk/types.go +++ b/dingtalk/types.go @@ -210,6 +210,125 @@ type ( BasicResponse `json:",inline"` *DepartmentInfo } + + RequestDepartmentInfoV2 struct { + DeptID int `json:"dept_id"` + Language string `json:"language,omitempty"` + } + + DepartmentInfoV2 struct { + DeptID int `json:"dept_id"` + Name string `json:"name,omitempty"` + ParentID int `json:"parent_id,omitempty"` + SourceIdentifier string `json:"source_identifier,omitempty"` + CreateDeptGroup bool `json:"create_dept_group,omitempty"` + AutoAddUser bool `json:"auto_add_user,omitempty"` + FromUnionOrg bool `json:"from_union_org,omitempty"` + Tags string `json:"tags,omitempty"` + Order int `json:"order,omitempty"` + DeptGroupChatID string `json:"dept_group_chat_id,omitempty"` + GroupContainSubDept bool `json:"group_contain_sub_dept,omitempty"` + OrgDeptOwner string `json:"org_dept_owner,omitempty"` + DeptManagerUseridList []string `json:"dept_manager_userid_list,omitempty"` + OuterDept bool `json:"outer_dept,omitempty"` + OuterPermitDepts []int `json:"outer_permit_depts,omitempty"` + OuterPermitUsers []string `json:"outer_permit_users,omitempty"` + HideDept bool `json:"hide_dept,omitempty"` + UserPermits []string `json:"user_permits,omitempty"` + DeptPermits []int `json:"dept_permits,omitempty"` + } + + ResponseDeptInfoV2 struct { + BasicResponse `json:",inline"` + Result *DepartmentInfoV2 `json:"result,omitempty"` + } + + // 钉钉的时间格式 + DingTime struct { + time.Time + } + + FormComponentValue struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + ExtValue string `json:"ext_value,omitempty"` + ID string `json:"id,omitempty"` + ComponentType string `json:"component_type,omitempty"` + } + + ProcessApprovers struct { + TaskActionType string `json:"task_action_type,omitempty"` + UserIDs []string `json:"user_ids,omitempty"` + } + + // 审批请求参数 + RequestCreateProcessInstance struct { + FormComponentValues []*FormComponentValue `json:"form_component_values,omitempty"` + AgentID string `json:"agent_id,omitempty"` + DeptID string `json:"dept_id,omitempty"` + ProcessCode string `json:"process_code,omitempty"` + OriginatorUserID string `json:"originator_user_id,omitempty"` // 审批发起人ID + ApproversV2 []ProcessApprovers `json:"approvers_v2,omitempty"` + CCList string `json:"cc_list,omitempty"` // 抄送人userid列表 + CCPosition string `json:"cc_position,omitempty"` // 抄送时间,分为(START, FINISH, START_FINISH) + } + + ResponseCreateProcessInstance struct { + BasicResponse `json:",inline"` + ProcessInstanceID string `json:"process_instance_id,omitempty"` + } + + ProcessAttachment struct { + FileName string `json:"file_name,omitempty"` + FileSize string `json:"file_size,omitempty"` + FileID string `json:"file_id,omitempty"` + FileType string `json:"file_type,omitempty"` + } + + ProcessOperationRecord struct { + UserID string `json:"user_id,omitempty"` + Date DingTime `json:"date,omitempty"` + OperationType string `json:"operation_type,omitempty"` + OperationResult string `json:"operation_result,omitempty"` + Remark string `json:"remark,omitempty"` + Attachments []*ProcessAttachment `json:"attachments,omitempty"` + } + + ProcessTask struct { + UserID string `json:"userid,omitempty"` + TaskStatus string `json:"task_status,omitempty"` + TaskResult string `json:"task_result,omitempty"` + CreateTime DingTime `json:"create_time,omitempty"` + FinishTime DingTime `json:"finish_time,omitempty"` + TaskID string `json:"taskid,omitempty"` + URL string `json:"url,omitempty"` + } + + // 审批实例详情 + ProcessInstance struct { + Title string `json:"title,omitempty"` + CreateTime DingTime `json:"create_time,omitempty"` + FinishTime DingTime `json:"finish_time,omitempty"` + OriginatorUserID string `json:"originator_userid,omitempty"` + OriginatorDeptID string `json:"originator_dept_id,omitempty"` + Status string `json:"status,omitempty"` // RUNNING审批中COMPLETED完成 + ApproverUserIDs []string `json:"approver_userids,omitempty"` + CCUserIDs []string `json:"cc_userids,omitempty"` + Result string `json:"result,omitempty"` // agree同意refuse拒绝 + BusinessID string `json:"business_id,omitempty"` + OperationRecords []*ProcessOperationRecord `json:"operation_records,omitempty"` + Tasks []*ProcessTask `json:"tasks,omitempty"` + OriginatorDeptName string `json:"originator_dept_name,omitempty"` + BizAction string `json:"biz_action,omitempty"` + AttachedProcessInstanceIDs []string `json:"attached_process_instance_ids,omitempty"` + FormComponentValues []*FormComponentValue `json:"form_component_values,omitempty"` + MainProcessInstanceID string `json:"main_process_instance_id,omitempty"` + } + + ResponseGetProcessInstance struct { + BasicResponse `json:",inline"` + ProcessInstance *ProcessInstance `json:"process_instance,omitempty"` + } ) func (ts *UnixTimestamp) UnmarshalJSON(data []byte) error { @@ -228,3 +347,19 @@ func (ts *UnixTimestamp) Time() time.Time { func (ts *UnixTimestamp) MarshalJSON() ([]byte, error) { return json.Marshal(ts.ts) } + +func (dt *DingTime) UnmarshalJSON(b []byte) error { + if string(b) == "null" { + return nil + } + ti, err := time.Parse("\"2006-01-02 15:04:05\"", string(b)) + if err != nil { + return err + } + dt.Time = ti + return nil +} + +func (dt DingTime) MarshalJSON() ([]byte, error) { + return []byte(dt.Time.Format("\"2006-01-02 15:04:05\"")), nil +}