Odoo与企业微信对接(Odoo18对接企业微信)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 line
17KB

  1. # -*- coding: utf-8 -*-
  2. """
  3. @Time : 2022/12/23 08:44
  4. @Author : Jason Zou
  5. @Email : zou.jason@qq.com
  6. @ 一级部门/公司跳过不处理,需手动维护ODOO与企业微信一级部门名称一致
  7. """
  8. from odoo import models, fields, exceptions
  9. import os
  10. import json
  11. import logging
  12. import requests
  13. _logger = logging.getLogger(__name__)
  14. os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
  15. headers = {'content-type': 'application/json'}
  16. class EWIInterface(models.Model):
  17. _name = 'ewi.interface'
  18. _description = '企业微信接口'
  19. name = fields.Char(string='接口名称', required=True, tracking=True)
  20. description = fields.Char(string='接口说明', tracking=True)
  21. AgentId = fields.Char(string='应用AgentId', help='本项在应用对接的时候应填入')
  22. Secret = fields.Char(string='应用凭证密钥Secret', help='应用的凭证密钥,注意应用需要是启用状态,获取方式参考:术语说明-secret')
  23. url = fields.Text(string='TokenUrl')
  24. access_token = fields.Text(string='Token', help='获取到的凭证,最长为512字节,通过corp_id,Secret请求返回')
  25. errcode = fields.Char(string='errcode', help='出错返回码,为0表示成功,非0表示调用失败')
  26. errmsg = fields.Char(string='errmsg', help='返回码提示语')
  27. expires_in = fields.Char(string='expires_in', help='凭证的有效时间(秒)')
  28. def btn_execute(self):
  29. """
  30. 按钮执行函数,可用于触发一系列操作
  31. """
  32. if self.name == '通讯录同步授权':
  33. self.gen_contacts_access_token() # 获得连接Token,通讯录授权
  34. if self.name == 'R2':
  35. self.send_message() # 发送各种应用消息
  36. # # 示例:调用部门相关接口
  37. if self.name == '创建部门':
  38. self.new_department()
  39. # self.update_department()
  40. # self.delete_department()
  41. # # 调用人员相关接口
  42. # self.gen_employee_userid_list()
  43. if self.name == '创建成员':
  44. self.new_employee()
  45. def gen_contacts_access_token(self):
  46. """授权信息,获取企微通讯录Access Token"""
  47. access_obj = self.env['ewi.wechat.config']
  48. access_record = access_obj.search([('name', '=', '企业微信接口')])
  49. corp_id = access_record.corp_id
  50. corp_secret = self.Secret
  51. if not corp_id or not corp_secret:
  52. _logger.info(f"通讯录授权信息{corp_id}----{corp_secret}为空,请填入!")
  53. return
  54. token_url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corp_id}&corpsecret={corp_secret}"
  55. _logger.info(f"通讯录访问 Token_url ----- {token_url}")
  56. for line in self:
  57. try:
  58. ret = requests.get(token_url, headers=headers)
  59. ret.raise_for_status()
  60. result = ret.json()
  61. if result.get('errcode') == 0:
  62. line.write({'access_token': result['access_token'],
  63. 'errcode': result['errcode'],
  64. 'errmsg': result['errmsg'],
  65. 'expires_in': result['expires_in'],
  66. })
  67. return result['access_token']
  68. else:
  69. _logger.error(f"获取企微通讯录Access Token失败: {result.get('errmsg')}")
  70. return None
  71. except requests.RequestException as e:
  72. _logger.error(f"请求获取企微通讯录Access Token时出错: {str(e)}")
  73. return None
  74. def gen_application_access_token(self):
  75. """授权信息,获取企微应用授权Access Token"""
  76. access_obj = self.env['ewi.wechat.config']
  77. access_record = access_obj.search([('name', '=', '企业微信接口')])
  78. corp_id = access_record.corp_id
  79. corp_secret = self.Secret # 通过每个应用动态获得
  80. _logger.info(f"应用授权信息{corp_id} --- {corp_secret}")
  81. if not corp_id or not corp_secret:
  82. _logger.info(f"应用授权信息{corp_secret}为空,请填入!")
  83. return
  84. token_url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corp_id}&corpsecret={corp_secret}"
  85. for line in self:
  86. try:
  87. ret = requests.get(token_url, headers=headers)
  88. ret.raise_for_status()
  89. result = ret.json()
  90. if result.get('errcode') == 0:
  91. line.write({'access_token': result['access_token'],
  92. 'errcode': result['errcode'],
  93. 'errmsg': result['errmsg'],
  94. 'expires_in': result['expires_in'],
  95. })
  96. return result['access_token']
  97. else:
  98. _logger.error(f"获取企业应用Access Token失败: {result.get('errmsg')}")
  99. return None
  100. except requests.RequestException as e:
  101. _logger.error(f"获取企业应用Access Token时出错: {str(e)}")
  102. return None
  103. def send_message(self):
  104. """发送各种应用消息"""
  105. access_token = self.gen_application_access_token()
  106. if not access_token:
  107. _logger.info(f"创建应用消息-----access_token------凭证为空 {access_token}")
  108. return
  109. token_url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={access_token}"
  110. data = {
  111. "touser" : "18066043008",
  112. # "toparty" : "PartyID1|PartyID2",
  113. # "totag" : "TagID1 | TagID2",
  114. "msgtype" : "text",
  115. "agentid" : self.AgentId,
  116. "text" : {
  117. "content" : "你的快递已到,请携带工卡前往邮件中心领取。聪明避开排队。"
  118. },
  119. "safe":0,
  120. "enable_id_trans": 0,
  121. "enable_duplicate_check": 0,
  122. "duplicate_check_interval": 1800
  123. }
  124. ret = requests.post(token_url, data=json.dumps(data), headers=headers)
  125. if json.loads(ret.text)['errcode'] == 0:
  126. _logger.info("发送应用消息成功{}".format(json.loads(ret.text)))
  127. else:
  128. _logger.error("发送应用消息失败{}".format(json.loads(ret.text)))
  129. def new_department(self):
  130. """部门接口,创建部门"""
  131. access_token = self.gen_contacts_access_token()
  132. if not access_token:
  133. _logger.info(f"创建部门-----access_token------凭证为空 {access_token}")
  134. return
  135. # 去拿Odoo本地的组织架构
  136. depart_obj = self.with_user(2).env['hr.department']
  137. depart_records = depart_obj.search([])
  138. odoo_dept_list = [i['id'] for i in depart_records] # odoo中正常使用的部门ID
  139. # 去请求企业微信组织架构
  140. search_token_url = f"https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token={access_token}"
  141. response = requests.get(search_token_url, headers=headers) # 请求企业微信正常使用的部门ID
  142. _logger.info(f"企业微信请求部门状态 ----【{response.status_code}】 ----")
  143. create_dept_list = []
  144. try:
  145. if response.status_code == 200:
  146. ret = json.loads(response.text)
  147. wechat_dept_list = [i['id'] for i in ret['department_id']] # 企业微信正常使用的部门ID
  148. create_dept_list = [i for i in odoo_dept_list if i not in wechat_dept_list] # 需要创建的部门ID
  149. except Exception as e:
  150. _logger.info(f"企业微信请求部门有误 ---- {e} ----")
  151. # 需要创建的部门ID
  152. create_token_url = f"https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token={access_token}"
  153. print(create_dept_list)
  154. for wid in create_dept_list:
  155. # 一级部门/公司跳过不处理,需手动维护ODOO与企业微信一级部门 名称一致
  156. depart_id = depart_obj.search([('id', '=', wid)])
  157. if not depart_id.parent_id.id:
  158. continue
  159. data = {
  160. "name": depart_id.name,
  161. "parentid": depart_id.parent_id.id or False,
  162. "id": depart_id.id,
  163. "order": depart_id.id,
  164. }
  165. ret = requests.post(create_token_url, data=json.dumps(data), headers=headers)
  166. if ret.status_code == 200:
  167. _logger.info("创建部门成功{}".format(json.loads(ret.text)))
  168. else:
  169. _logger.error("创建部门失败{}".format(json.loads(ret.text)))
  170. def update_department(self):
  171. """部门接口,企微部门更新"""
  172. token = self.gen_access_token()
  173. if not token:
  174. return
  175. update_depart_url = self.with_user(2).env['ewi.interface'].search([('name', '=', '更新部门')]).url
  176. depart_records = self.with_user(2).env['hr.department'].search([]) # 换部门上下级、换部门负责人、部门更名
  177. odoo_dept_list = [i['id'] for i in depart_records] # odoo中正常使用的部门ID
  178. # odoo中正常使用的部门ID
  179. for i in odoo_dept_list: # 全量更新企业微信数据
  180. record_id = self.with_user(2).env['hr.department'].search([('id', '=', i)])
  181. if i == 1:
  182. continue
  183. data = {
  184. "id": record_id.id,
  185. "name": record_id.name, # 部门更名
  186. "parentid": record_id.parent_id.id or False, # 换部门上下级
  187. "order": record_id.ewc_dept_order,
  188. }
  189. ret = requests.post(update_depart_url.format(token), data=json.dumps(data), headers=headers)
  190. if json.loads(ret.text)['errcode'] == 0:
  191. _logger.info("更新部门成功{}".format(json.loads(ret.text)))
  192. else:
  193. _logger.error("更新部门失败{}".format(json.loads(ret.text)))
  194. def delete_department(self):
  195. """部门接口,企微部门删除"""
  196. token = self.gen_access_token()
  197. if not token:
  198. return
  199. delete_depart_url = self.with_user(2).env['ewi.interface'].search([('name', '=', '删除部门')]).url
  200. depart_records_is_leaf = self.with_user(2).env['hr.department'] \
  201. .search([('active', '=', False), ('is_leaf', '=', True)]) # 取部门标识为归档的数据,层级为末级数据
  202. odoo_dept_list_is_leaf = [i['id'] for i in depart_records_is_leaf] # odoo中已停用的部门ID
  203. depart_records_not_leaf = self.with_user(2).env['shr.department'] \
  204. .search([('active', '=', False), ('is_leaf', '=', False)]) # 取部门标识为归档的数据,层级为非末级数据
  205. odoo_dept_list_not_leaf = [i['id'] for i in depart_records_not_leaf] # odoo中已停用的部门ID
  206. # odoo中已停止使用的部门ID
  207. for i in odoo_dept_list_is_leaf: # 全取部门标识为归档的数据,层级为末级数据
  208. if i == 1:
  209. continue
  210. ret = requests.get(delete_depart_url.format(token, i), headers=headers)
  211. if json.loads(ret.text)['errcode'] == 0:
  212. _logger.info("删除部门成功{}".format(json.loads(ret.text)))
  213. else:
  214. _logger.error("删除部门失败{}".format(json.loads(ret.text)))
  215. # odoo中已停止使用的部门ID
  216. for i in odoo_dept_list_not_leaf: # 全取部门标识为归档的数据,层级为非末级数据
  217. if i == 1:
  218. continue
  219. ret = requests.get(delete_depart_url.format(token, i), headers=headers)
  220. if json.loads(ret.text)['errcode'] == 0:
  221. _logger.info("删除部门成功{}".format(json.loads(ret.text)))
  222. else:
  223. _logger.error("删除部门失败{}".format(json.loads(ret.text)))
  224. def gen_employee_userid_list(self):
  225. """
  226. 人员接口,获取部门成员ID列表
  227. """
  228. token = self.gen_access_token()
  229. if not token:
  230. return
  231. gen_employee_userid_url = self.with_user(2).env['ewi.interface'].search([('name', '=', '获取成员ID列表')]).url
  232. userid_args = {
  233. "limit": 100
  234. }
  235. try:
  236. ret = requests.post(gen_employee_userid_url.format(token), json=userid_args, headers=headers)
  237. if json.loads(ret.text)['errcode'] == 0:
  238. _logger.info("获取部门成员ID列表成功{}".format(json.loads(ret.text)))
  239. else:
  240. _logger.error("获取部门成员ID列表失败{}".format(json.loads(ret.text)))
  241. except Exception as e:
  242. _logger.error('企业微信获取部门成员ID列表时连接失败:' + str(e))
  243. return
  244. def new_employee(self):
  245. """
  246. 人员接口,获取部门成员
  247. """
  248. access_token = self.gen_contacts_access_token()
  249. if not access_token:
  250. _logger.info(f"创建成员-----access_token------凭证为空 {access_token}")
  251. return
  252. # 去拿Odoo本地的内部职工
  253. employee_obj = self.with_user(2).env['hr.employee']
  254. employee_records = employee_obj.search([])
  255. odoo_employee_list = [i['mobile_phone'] for i in employee_records] # odoo中在职人员ID,原则上要求职工号企业内唯一
  256. # 去请求企业微信部门职工
  257. search_token_url = f"https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token={access_token}"
  258. response = requests.get(search_token_url, headers=headers) # 请求企业微信正常使用的部门ID
  259. _logger.info(f"企业微信请求职工状态 ----【{response.status_code}】 ----")
  260. create_employee_list = []
  261. try:
  262. if response.status_code == 200:
  263. ret = json.loads(response.text)
  264. wechat_employee_list = [i['userid'] for i in ret['dept_user']] # 企业微信在职人员ID
  265. create_employee_list = [i for i in odoo_employee_list if i not in wechat_employee_list] # 需要创建的在职人员ID
  266. except Exception as e:
  267. _logger.info(f"企业微信请求职工有误 ---- {e} ----")
  268. # 需要创建的职工ID
  269. create_token_url = f"https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token={access_token}"
  270. print(create_employee_list)
  271. for employee in create_employee_list:
  272. employee_id = employee_obj.search([('mobile_phone', '=', '18066043008')])
  273. print(employee_id)
  274. # """填充主部门"""
  275. # depart_list = [employee_record.department_id.id]
  276. # """判断是否部门负责人"""
  277. # if employee_record.department_id.manager_id.id == employee_record.id:
  278. # is_leader_in_dept_list = [1]
  279. # else:
  280. # is_leader_in_dept_list = [0]
  281. # """设置”成员所属部门id列表“、是否”部门负责人“"""
  282. # for depart in employee_record.department_ids:
  283. # depart_list.append(depart.id)
  284. # is_leader_in_dept_list.append(0)
  285. data = {
  286. "userid": '18066043008', # 是, 成员UserID
  287. "name": employee_id.name, # 是, 成员名称
  288. # "alias": employee_record.work_no, # 是, 成员别名
  289. "mobile": employee_id.mobile_phone, # 手机号码
  290. "department": [22], # 成员所属部门id列表
  291. # "position": employee_record.job_id.name, # 职务信息
  292. # "gender": 1 if employee_record.gender == 'male' else 2, # 性别。1表示男性,2表示女性
  293. # "email": employee_record.work_email, # 邮箱
  294. # "is_leader_in_dept": is_leader_in_dept_list, # 1表示为部门负责人,0表示非部门负责人
  295. # "direct_leader": ["%s" % employee_record.parent_id.work_no], # 直属上级UserID
  296. # "enable": 0 if employee_record.ewc_enable else 1, # 启用/禁用成员。1表示启用成员,0表示禁用成员
  297. # "telephone": employee_record.work_phone or False, # 座机
  298. # "address": self.env['res.company'].search([('id', '=', '1')]).street or False, # 地址
  299. # "main_department": employee_record.department_id.id, # 主部门
  300. # "to_invite": to_invite, # 是否邀请该成员使用企业微信
  301. }
  302. # if employee_record.depart_code == '1' or not employee_record.parent_id.id or not employee_record.job_id.name:
  303. # del employee_args['direct_leader']
  304. # if not employee_record.job_id.name:
  305. # del employee_args['position']
  306. # if not employee_record.work_email:
  307. # del employee_args['email']
  308. ret = requests.post(create_token_url, data=json.dumps(data), headers=headers)
  309. if ret.status_code == 200:
  310. _logger.info("创建职工成功{}".format(json.loads(ret.text)))
  311. else:
  312. _logger.error("创建职工失败{}".format(json.loads(ret.text)))
上海开阖软件有限公司 沪ICP备12045867号-1