oa.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package work
  2. import (
  3. "encoding/json"
  4. "encoding/xml"
  5. "fmt"
  6. "github.com/silenceper/wechat/v2/credential"
  7. "github.com/silenceper/wechat/v2/util"
  8. "github.com/silenceper/wechat/v2/work/config"
  9. "github.com/silenceper/wechat/v2/work/context"
  10. "github.com/silenceper/wechat/v2/work/kf"
  11. "strconv"
  12. "time"
  13. )
  14. const (
  15. getTemplateDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/oa/gettemplatedetail?access_token=%s"
  16. applyEventURL = "https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=%s"
  17. getApprovalInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovalinfo?access_token=%s"
  18. getApprovalDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token=%s"
  19. )
  20. // Work 企业微信
  21. type Work struct {
  22. ctx *context.Context
  23. }
  24. // NewWork init work
  25. func NewWork(cfg *config.Config) *Work {
  26. defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
  27. ctx := &context.Context{
  28. Config: cfg,
  29. AccessTokenHandle: defaultAkHandle,
  30. }
  31. return &Work{ctx: ctx}
  32. }
  33. // GetOATemplateDetail 获取审批模板详情
  34. func (work *Work) GetOATemplateDetail(templateID string) (*OATemplateDetail, error) {
  35. accessToken, err := work.ctx.GetAccessToken()
  36. if err != nil {
  37. return nil, err
  38. }
  39. response, err := util.PostJSON(fmt.Sprintf(getTemplateDetailURL, accessToken), reqOAGetTemplateDetail{
  40. TemplateID: templateID,
  41. })
  42. if err != nil {
  43. return nil, err
  44. }
  45. var result respOAGetTemplateDetail
  46. err = json.Unmarshal(response, &result)
  47. if err != nil {
  48. return nil, err
  49. }
  50. if result.ErrCode != 0 {
  51. return nil, kf.NewSDKErr(result.ErrCode, result.ErrMsg)
  52. }
  53. return &result.OATemplateDetail, nil
  54. }
  55. // ApplyOAEvent 提交审批申请
  56. func (work *Work) ApplyOAEvent(applyInfo OAApplyEvent) (string, error) {
  57. accessToken, err := work.ctx.GetAccessToken()
  58. if err != nil {
  59. return "", err
  60. }
  61. response, err := util.PostJSON(fmt.Sprintf(applyEventURL, accessToken), applyInfo)
  62. if err != nil {
  63. return "", err
  64. }
  65. var result respOAApplyEvent
  66. err = json.Unmarshal(response, &result)
  67. if err != nil {
  68. return "", err
  69. }
  70. if result.ErrCode != 0 {
  71. return "", kf.NewSDKErr(result.ErrCode, result.ErrMsg)
  72. }
  73. return result.SpNo, nil
  74. }
  75. // GetOAApprovalInfo 批量获取审批单号
  76. func (work *Work) GetOAApprovalInfo(req GetOAApprovalInfoReq) ([]string, error) {
  77. accessToken, err := work.ctx.GetAccessToken()
  78. if err != nil {
  79. return nil, err
  80. }
  81. response, err := util.PostJSON(fmt.Sprintf(getApprovalInfoURL, accessToken), reqOAGetApprovalInfo{
  82. StartTime: strconv.FormatInt(req.StartTime.Unix(), 10),
  83. EndTime: strconv.FormatInt(req.EndTime.Unix(), 10),
  84. Cursor: req.Cursor,
  85. Size: req.Size,
  86. Filters: req.Filters,
  87. })
  88. if err != nil {
  89. return nil, err
  90. }
  91. var result respOAGetApprovalInfo
  92. err = json.Unmarshal(response, &result)
  93. if err != nil {
  94. return nil, err
  95. }
  96. if result.ErrCode != 0 {
  97. return nil, kf.NewSDKErr(result.ErrCode, result.ErrMsg)
  98. }
  99. return result.SpNoList, nil
  100. }
  101. // GetOAApprovalDetail 获取审批申请详情
  102. func (work *Work) GetOAApprovalDetail(spNo string) (*OAApprovalDetail, error) {
  103. accessToken, err := work.ctx.GetAccessToken()
  104. if err != nil {
  105. return nil, err
  106. }
  107. response, err := util.PostJSON(fmt.Sprintf(getApprovalDetailURL, accessToken), reqOAGetApprovalDetail{
  108. SpNo: spNo,
  109. })
  110. if err != nil {
  111. return nil, err
  112. }
  113. var result respOAGetApprovalDetail
  114. err = json.Unmarshal(response, &result)
  115. if err != nil {
  116. return nil, err
  117. }
  118. if result.ErrCode != 0 {
  119. return nil, kf.NewSDKErr(result.ErrCode, result.ErrMsg)
  120. }
  121. return &result.Info, nil
  122. }
  123. // GetOAApprovalInfoReq 批量获取审批单号请求
  124. type GetOAApprovalInfoReq struct {
  125. // StartTime 审批单提交的时间范围,开始时间,UNix时间戳
  126. StartTime time.Time
  127. // EndTime 审批单提交的时间范围,结束时间,Unix时间戳
  128. EndTime time.Time
  129. // Cursor 分页查询游标,默认为0,后续使用返回的next_cursor进行分页拉取
  130. Cursor int
  131. // Size 一次请求拉取审批单数量,默认值为100,上限值为100
  132. Size uint32
  133. // Filters 筛选条件,可对批量拉取的审批申请设置约束条件,支持设置多个条件
  134. Filters []OAApprovalInfoFilter
  135. }
  136. // OAApprovalInfo 审批申请状态变化回调通知
  137. type OAApprovalInfo struct {
  138. // SpNo 审批编号
  139. SpNo string `xml:"SpNo"`
  140. // SpName 审批申请类型名称(审批模板名称)
  141. SpName string `xml:"SpName"`
  142. // SpStatus 申请单状态:1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付
  143. SpStatus string `xml:"SpStatus"`
  144. // TemplateID 审批模板id。可在“获取审批申请详情”、“审批状态变化回调通知”中获得,也可在审批模板的模板编辑页面链接中获得。
  145. TemplateID string `xml:"TemplateId"`
  146. // ApplyTime 审批申请提交时间,Unix时间戳
  147. ApplyTime string `xml:"ApplyTime"`
  148. // Applicant 申请人信息
  149. Applicant OAApprovalInfoApplicant `xml:"Applyer"`
  150. // SpRecord 审批流程信息,可能有多个审批节点。
  151. SpRecord []OAApprovalInfoSpRecord `xml:"SpRecord"`
  152. // Notifier 抄送信息,可能有多个抄送节点
  153. Notifier OAApprovalInfoNotifier `xml:"Notifyer"`
  154. // Comments 审批申请备注信息,可能有多个备注节点
  155. Comments []OAApprovalInfoComment `xml:"Comments"`
  156. // StatusChangeEvent 审批申请状态变化类型:1-提单;2-同意;3-驳回;4-转审;5-催办;6-撤销;8-通过后撤销;10-添加备注
  157. StatusChangeEvent string `xml:"StatuChangeEvent"`
  158. }
  159. // OAApprovalInfoApplicant 申请人信息
  160. type OAApprovalInfoApplicant struct {
  161. // UserID 申请人userid
  162. UserID string `xml:"UserId"`
  163. // Party 申请人所在部门pid
  164. Party string `xml:"Party"`
  165. }
  166. // OAApprovalInfoSpRecord 审批流程信息,可能有多个审批节点。
  167. type OAApprovalInfoSpRecord struct {
  168. // SpStatus 审批节点状态:1-审批中;2-已同意;3-已驳回;4-已转审
  169. SpStatus string `xml:"SpStatus"`
  170. // ApproverAttr 节点审批方式:1-或签;2-会签
  171. ApproverAttr string `xml:"ApproverAttr"`
  172. // Details 审批节点详情。当节点为标签或上级时,一个节点可能有多个分支
  173. Details []OAApprovalInfoSpRecordDetail `xml:"Details"`
  174. }
  175. // OAApprovalInfoSpRecordDetail 审批节点详情。当节点为标签或上级时,一个节点可能有多个分支
  176. type OAApprovalInfoSpRecordDetail struct {
  177. // Approver 分支审批人
  178. Approver OAApprovalInfoSpRecordDetailApprover `xml:"Approver"`
  179. // Speech 审批意见字段
  180. Speech string `xml:"Speech"`
  181. // SpStatus 分支审批人审批状态:1-审批中;2-已同意;3-已驳回;4-已转审
  182. SpStatus string `xml:"SpStatus"`
  183. // SpTime 节点分支审批人审批操作时间,0为尚未操作
  184. SpTime string `xml:"SpTime"`
  185. // Attach 节点分支审批人审批意见附件,赋值为media_id具体使用请参考:文档-获取临时素材
  186. Attach []string `xml:"Attach"`
  187. }
  188. // OAApprovalInfoSpRecordDetailApprover 分支审批人
  189. type OAApprovalInfoSpRecordDetailApprover struct {
  190. // UserID 分支审批人userid
  191. UserID string `xml:"UserId"`
  192. }
  193. // OAApprovalInfoNotifier 抄送信息,可能有多个抄送节点
  194. type OAApprovalInfoNotifier struct {
  195. // UserID 节点抄送人userid
  196. UserID string `xml:"UserId"`
  197. }
  198. // OAApprovalInfoComment 审批申请备注信息,可能有多个备注节点
  199. type OAApprovalInfoComment struct {
  200. // CommentUserInfo 备注人信息
  201. CommentUserInfo OAApprovalInfoCommentUserInfo `xml:"CommentUserInfo"`
  202. // CommentTime 备注提交时间
  203. CommentTime string `xml:"CommentTime"`
  204. // CommentContent 备注文本内容
  205. CommentContent string `xml:"CommentContent"`
  206. // CommentID 备注id
  207. CommentID string `xml:"CommentId"`
  208. // Attach 备注意见附件,值是附件media_id具体使用请参考:文档-获取临时素材
  209. Attach []string `xml:"Attach"`
  210. }
  211. // OAApprovalInfoCommentUserInfo 备注人信息
  212. type OAApprovalInfoCommentUserInfo struct {
  213. // UserID 备注人userid
  214. UserID string `xml:"UserId"`
  215. }
  216. type SysApprovalChangeMessage struct {
  217. XMLName xml.Name `xml:"xml"`
  218. Text string `xml:",chardata"`
  219. ToUserName struct {
  220. Text string `xml:",chardata"`
  221. } `xml:"ToUserName"`
  222. FromUserName struct {
  223. Text string `xml:",chardata"`
  224. } `xml:"FromUserName"`
  225. CreateTime struct {
  226. Text string `xml:",chardata"`
  227. } `xml:"CreateTime"`
  228. MsgType struct {
  229. Text string `xml:",chardata"`
  230. } `xml:"MsgType"`
  231. Event struct {
  232. Text string `xml:",chardata"`
  233. } `xml:"Event"`
  234. AgentID struct {
  235. Text string `xml:",chardata"`
  236. } `xml:"AgentID"`
  237. ApprovalInfo struct {
  238. Text string `xml:",chardata"`
  239. SpNo struct {
  240. Text string `xml:",chardata"`
  241. } `xml:"SpNo"`
  242. SpName struct {
  243. Text string `xml:",chardata"`
  244. } `xml:"SpName"`
  245. SpStatus struct {
  246. Text string `xml:",chardata"`
  247. } `xml:"SpStatus"`
  248. TemplateId struct {
  249. Text string `xml:",chardata"`
  250. } `xml:"TemplateId"`
  251. ApplyTime struct {
  252. Text string `xml:",chardata"`
  253. } `xml:"ApplyTime"`
  254. Applyer struct {
  255. Text string `xml:",chardata"`
  256. UserId struct {
  257. Text string `xml:",chardata"`
  258. } `xml:"UserId"`
  259. Party struct {
  260. Text string `xml:",chardata"`
  261. } `xml:"Party"`
  262. } `xml:"Applyer"`
  263. SpRecord []struct {
  264. Text string `xml:",chardata"`
  265. SpStatus struct {
  266. Text string `xml:",chardata"`
  267. } `xml:"SpStatus"`
  268. ApproverAttr struct {
  269. Text string `xml:",chardata"`
  270. } `xml:"ApproverAttr"`
  271. Details []struct {
  272. Text string `xml:",chardata"`
  273. Approver struct {
  274. Text string `xml:",chardata"`
  275. UserId struct {
  276. Text string `xml:",chardata"`
  277. } `xml:"UserId"`
  278. } `xml:"Approver"`
  279. Speech struct {
  280. Text string `xml:",chardata"`
  281. } `xml:"Speech"`
  282. SpStatus struct {
  283. Text string `xml:",chardata"`
  284. } `xml:"SpStatus"`
  285. SpTime struct {
  286. Text string `xml:",chardata"`
  287. } `xml:"SpTime"`
  288. } `xml:"Details"`
  289. } `xml:"SpRecord"`
  290. Notifyer struct {
  291. Text string `xml:",chardata"`
  292. UserId struct {
  293. Text string `xml:",chardata"`
  294. } `xml:"UserId"`
  295. } `xml:"Notifyer"`
  296. Comments struct {
  297. Text string `xml:",chardata"`
  298. CommentUserInfo struct {
  299. Text string `xml:",chardata"`
  300. UserId struct {
  301. Text string `xml:",chardata"`
  302. } `xml:"UserId"`
  303. } `xml:"CommentUserInfo"`
  304. CommentTime struct {
  305. Text string `xml:",chardata"`
  306. } `xml:"CommentTime"`
  307. CommentContent struct {
  308. Text string `xml:",chardata"`
  309. } `xml:"CommentContent"`
  310. CommentId struct {
  311. Text string `xml:",chardata"`
  312. } `xml:"CommentId"`
  313. } `xml:"Comments"`
  314. StatuChangeEvent struct {
  315. Text string `xml:",chardata"`
  316. } `xml:"StatuChangeEvent"`
  317. } `xml:"ApprovalInfo"`
  318. }