From 38c6c2034fda0d62237f77ec5dd2db5df50aeb37 Mon Sep 17 00:00:00 2001 From: lonelyleaves Date: Fri, 14 Jul 2023 23:15:12 +0800 Subject: [PATCH 1/6] =?UTF-8?q?FIXME:=20=E4=BF=AE=E5=A4=8D=E9=80=9A?= =?UTF-8?q?=E8=AE=AF=E5=BD=95=E5=90=8C=E6=AD=A5=EF=BC=8C=E4=BA=BA=E5=91=98?= =?UTF-8?q?userid=20=E5=8F=96=E6=B6=88=E8=BD=AC=E6=88=90=E5=B0=8F=E5=86=99?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8=E8=87=AA=E5=BB=BA=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=81=94=E7=B3=BB=E4=BA=BA=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=20=09=09=E8=B0=83=E6=95=B4=E5=91=98=E5=B7=A5=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=94=A8=E6=88=B7=EF=BC=8C=E7=9B=B8=E5=90=8C=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E7=9A=84=E5=90=88=E5=B9=B6=EF=BC=8C=E5=90=A6=E5=88=99=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=E7=94=A8=E6=88=B7=20=09=20=20=20=20=20=20=20=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E4=BB=A5=E5=8F=8A=E5=BE=AE=E4=BF=A1=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=85=8D=E7=99=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wecom_api/api/wecom_server_api.py | 34 +- wecom_api/models/wecom_server_api_list.py | 53 +- wecom_auth_oauth/controllers/main.py | 57 +- wecom_auth_oauth/models/res_users.py | 39 +- wecom_base/models/wecom_apps.py | 17 +- wecom_contacts/models/res_config_settings.py | 7 +- wecom_contacts_sync/i18n/zh_CN.po | 2 +- wecom_contacts_sync/models/wecom_apps.py | 150 ++--- wecom_contacts_sync/models/wecom_user.py | 522 +++++++----------- .../views/wecom_user_views.xml | 99 ++-- 10 files changed, 430 insertions(+), 550 deletions(-) diff --git a/wecom_api/api/wecom_server_api.py b/wecom_api/api/wecom_server_api.py index d4bcb87c..7ac6f72d 100644 --- a/wecom_api/api/wecom_server_api.py +++ b/wecom_api/api/wecom_server_api.py @@ -3,8 +3,10 @@ import json import requests from datetime import datetime, timedelta +from odoo import api, fields, models, tools, _ from odoo import api, fields, models, SUPERUSER_ID, _ + # from .wecom_abstract_api import ApiException @@ -29,31 +31,16 @@ class WecomServerApi(models.TransientModel): :param secret : 应用密钥 :returns 模型"wecom.service_api"对象 """ - api = self.search( - [ - ("corpid", "=", corpid), - ("secret", "=", secret), - ], - limit=1, - ) + api = self.search([("corpid", "=", corpid), ("secret", "=", secret), ], limit=1) if not api: # 创建API令牌记录 - api = self.sudo().create( - { - "corpid": corpid, - "secret": secret, - } - ) + api = self.sudo().create({"corpid": corpid, "secret": secret, }) if api["access_token"] is False or api["access_token"] == "": # token为空,刷新API令牌记录 api.refreshAccessToken() - if ( - api["token_expiration_time"] is False - or api["token_expiration_time"] < datetime.now() - ): + if api["token_expiration_time"] is False or api["token_expiration_time"] < datetime.now(): # token过期,刷新API令牌记录 api.refreshAccessToken() - return api def getAccessToken(self): @@ -69,10 +56,8 @@ class WecomServerApi(models.TransientModel): 刷新模型 'wecom.service_api' 的令牌 同时刷新发起请求的模型 'wecom.apps' 的令牌 """ - response = self.httpCall( - self.env["wecom.service_api_list"].get_server_api_call("GET_ACCESS_TOKEN"), - {"corpid": self.corpid, "corpsecret": self.secret}, - ) + response = self.httpCall(self.env["wecom.service_api_list"].get_server_api_call("GET_ACCESS_TOKEN"), + {"corpid": self.corpid, "corpsecret": self.secret}, ) expiration_second = timedelta(seconds=response["expires_in"]) dict = { @@ -86,9 +71,8 @@ class WecomServerApi(models.TransientModel): compay_id = self.env["res.company"].search( [("corpid", "=", self.corpid)], limit=1 ) - app_id = self.env["wecom.apps"].search( - [("company_id", "=", compay_id.id), ("secret", "=", self.secret)], limit=1 - ) + app_id = self.env["wecom.apps"].search([("company_id", "=", compay_id.id), ("secret", "=", self.secret)], + limit=1) app_id.sudo().write(dict) diff --git a/wecom_api/models/wecom_server_api_list.py b/wecom_api/models/wecom_server_api_list.py index 641f1388..08c7c39c 100644 --- a/wecom_api/models/wecom_server_api_list.py +++ b/wecom_api/models/wecom_server_api_list.py @@ -11,37 +11,37 @@ class WecomServerApiList(models.Model): type = fields.Selection( [ - ("1", "Base"), # 基础 - ("2", "Contacts"), # 通讯录, - ("3", "Customer contact"), # 客户联系 - ("4", "Wechat customer service"), # 微信客服 - ("5", "Identity authentication"), # 身份验证 - ("6", "Application management"), # 应用管理 - ("7", "Message push"), # 消息推送 - ("8", "Media material"), # 媒体素材 - ("9", "OA"), # OA - ("10", "Efficiency tools"), # 效率工具 - ("11", "externalpay"), # 企业支付 - ("12", "Corp Group"), # 企业互联 - ("13", "Corp Chain"), # 企业链,上下游 - ("14", "Session content archiving"), # 会话内容存档 - ("15", "Electronic invoice"), # 电子发票 - ("16", "School"), # 家校沟通 - ("17", "School Apps"), # 家校应用 + ("1", "Base"), # 基础 + ("2", "Contacts"), # 通讯录, + ("3", "Customer contact"), # 客户联系 + ("4", "Wechat customer service"), # 微信客服 + ("5", "Identity authentication"), # 身份验证 + ("6", "Application management"), # 应用管理 + ("7", "Message push"), # 消息推送 + ("8", "Media material"), # 媒体素材 + ("9", "OA"), # OA + ("10", "Efficiency tools"), # 效率工具 + ("11", "externalpay"), # 企业支付 + ("12", "Corp Group"), # 企业互联 + ("13", "Corp Chain"), # 企业链,上下游 + ("14", "Session content archiving"), # 会话内容存档 + ("15", "Electronic invoice"), # 电子发票 + ("16", "School"), # 家校沟通 + ("17", "School Apps"), # 家校应用 ("18", "Report"), # 政民沟通 ], string="Api Type", required=True, default="GET", - ) + ) - name = fields.Char("Request Name", required=True,) - function_name = fields.Char("Request Function Name", required=True, readonly=True,) + name = fields.Char("Request Name", required=True, ) + function_name = fields.Char("Request Function Name", required=True, readonly=True, ) - short_url = fields.Char("Request Short Url", required=True,) + short_url = fields.Char("Request Short Url", required=True, ) request_type = fields.Selection( - [("GET", "GET"), ("POST", "POST"),], + [("GET", "GET"), ("POST", "POST"), ], string="Api Request Type", required=True, default="GET", @@ -53,7 +53,7 @@ class WecomServerApiList(models.Model): ("function_name_uniq", "unique (function_name)", "The function is unique !",), ] - def get_server_api_call(self, function_name): + def get_server_api_call(self, function_name, debug=False): """ 根据函数名称获取 企业微信API的路由和请求方式 :param function_name : 函数名称 @@ -61,7 +61,10 @@ class WecomServerApiList(models.Model): """ # ['/cgi-bin/gettoken', 'GET'] data = [] - res = self.search([("function_name", "=", function_name)], limit=1,) - data.append(res.short_url) + res = self.search([("function_name", "=", function_name)], limit=1, ) + if debug: + data.append('%s&debug=%s' % (res.short_url, 1)) + else: + data.append(res.short_url) data.append(res.request_type) return data diff --git a/wecom_auth_oauth/controllers/main.py b/wecom_auth_oauth/controllers/main.py index b84dc275..c6a34894 100644 --- a/wecom_auth_oauth/controllers/main.py +++ b/wecom_auth_oauth/controllers/main.py @@ -41,20 +41,20 @@ class OAuthLogin(Home): try: providers = ( request.env["auth.oauth.provider"] - .sudo() - .search_read([("enabled", "=", True)]) + .sudo() + .search_read([("enabled", "=", True)]) ) except Exception: providers = [] # 构造企业微信一键登录(应用内免登录)和扫码登录链接 for provider in providers: if ( - "https://open.weixin.qq.com/connect/oauth2/authorize" - in provider["auth_endpoint"] + "https://open.weixin.qq.com/connect/oauth2/authorize" + in provider["auth_endpoint"] ): # 一键登录 return_url = ( - request.httprequest.url_root + "wxowrk_auth_oauth/authorize" + request.httprequest.url_root + "wxowrk_auth_oauth/authorize" ) state = self.get_state(provider) @@ -72,8 +72,8 @@ class OAuthLogin(Home): "#wechat_redirect", ) elif ( - "https://open.work.weixin.qq.com/wwopen/sso/qrConnect" - in provider["auth_endpoint"] + "https://open.work.weixin.qq.com/wwopen/sso/qrConnect" + in provider["auth_endpoint"] ): # 扫描登录 return_url = request.httprequest.url_root + "wxowrk_auth_oauth/qr" @@ -117,27 +117,25 @@ class OAuthController(http.Controller): state = json.loads(kw["state"]) company = ( request.env["res.company"] - .sudo() - .search( - [("corpid", "=", state["a"]), ("is_wecom_organization", "=", True),], + .sudo() + .search( + [("corpid", "=", state["a"]), ("is_wecom_organization", "=", True), ], ) ) try: wxapi = ( request.env["wecom.service_api"] - .sudo() - .InitServiceApi(company.corpid, company.sudo().auth_app_id.secret) + .sudo() + .InitServiceApi(company.corpid, company.sudo().auth_app_id.secret) ) # 根据code获取成员信息 response = wxapi.httpCall( - request.env["wecom.service_api_list"] - .sudo() - .get_server_api_call("GET_USER_INFO_BY_CODE"), - {"code": code,}, - ) - + request.env["wecom.service_api_list"].sudo().get_server_api_call("GET_USER_INFO_BY_CODE"), + {"code": code, }, + ) + _logger.info('根据code获取成员信息,返回报文:%s' % response) dbname = state["d"] if not http.db_filter([dbname]): return BadRequest() @@ -147,6 +145,7 @@ class OAuthController(http.Controller): with registry.cursor() as cr: try: env = api.Environment(cr, SUPERUSER_ID, context) + _logger.info('根据code获取成员信息,返回报文:%s' % response) db, login, key = env['res.users'].sudo().wecom_auth_oauth(provider, response) cr.commit() action = state.get("a") @@ -171,7 +170,7 @@ class OAuthController(http.Controller): # Since /web is hardcoded, verify user has right to land on it if werkzeug.urls.url_parse( - resp.location + resp.location ).path == "/web" and not request.env.user.has_group( "base.group_user" ): @@ -213,25 +212,25 @@ class OAuthController(http.Controller): code = kw.pop("code", None) company = ( request.env["res.company"] - .sudo() - .search( - [("corpid", "=", kw["appid"]), ("is_wecom_organization", "=", True),], + .sudo() + .search( + [("corpid", "=", kw["appid"]), ("is_wecom_organization", "=", True), ], ) ) try: wxapi = ( request.env["wecom.service_api"] - .sudo() - .InitServiceApi(company.corpid, company.sudo().auth_app_id.secret) + .sudo() + .InitServiceApi(company.corpid, company.sudo().auth_app_id.secret) ) response = wxapi.httpCall( request.env["wecom.service_api_list"] - .sudo() - .get_server_api_call("GET_USER_INFO_BY_CODE"), - {"code": code,}, + .sudo() + .get_server_api_call("GET_USER_INFO_BY_CODE"), + {"code": code, }, ) - + state = json.loads(kw["state"].replace("M", '"')) dbname = state["d"] if not http.db_filter([dbname]): @@ -265,7 +264,7 @@ class OAuthController(http.Controller): resp.autocorrect_location_header = False if werkzeug.urls.url_parse( - resp.location + resp.location ).path == "/web" and not request.env.user.has_group( "base.group_user" ): diff --git a/wecom_auth_oauth/models/res_users.py b/wecom_auth_oauth/models/res_users.py index 362c428e..fa6b4067 100644 --- a/wecom_auth_oauth/models/res_users.py +++ b/wecom_auth_oauth/models/res_users.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- +import logging from odoo import models, api, _ from odoo.exceptions import AccessDenied +_logger = logging.getLogger(__name__) + class ResUsers(models.Model): _inherit = "res.users" @@ -21,32 +24,30 @@ class ResUsers(models.Model): wecom_web_auth_endpoint = "https://open.weixin.qq.com/connect/oauth2/authorize" wecom_qr_auth_endpoint = "https://open.work.weixin.qq.com/wwopen/sso/qrConnect" - wecom_providers = ( - self.env["auth.oauth.provider"].sudo().search([("id", "=", provider),]) - ) + wecom_providers = self.env["auth.oauth.provider"].sudo().search([("id", "=", provider), ]) - if ( - wecom_web_auth_endpoint in wecom_providers["auth_endpoint"] - or wecom_qr_auth_endpoint in wecom_providers["auth_endpoint"] + if (wecom_web_auth_endpoint in wecom_providers["auth_endpoint"] + or wecom_qr_auth_endpoint in wecom_providers["auth_endpoint"] ): # 扫码登录 - oauth_userid = params["UserId"].lower() - oauth_user = self.search( - [ - # ("oauth_uid", "=", oauth_userid), - ("wecom_userid", "=", oauth_userid), - ("is_wecom_user", "=", True), - ("active", "=", True), - ] - ) - - if not oauth_user or len(oauth_user) > 1: + oauth_userid = params["UserId"] + oauth_user = self.search([("wecom_userid", "=", oauth_userid), + ("is_wecom_user", "=", True), + ("active", "=", True), + # ("oauth_uid", "=", oauth_userid), + ]) + + # if not oauth_user: + # return AccessDenied + # elif len(oauth_user) > 1: + # return AccessDenied + if oauth_user and len(oauth_user) == 1: + return (self.env.cr.dbname, oauth_user.login, oauth_userid) + else: return AccessDenied - return (self.env.cr.dbname, oauth_user.login, oauth_userid) else: return AccessDenied - def _check_credentials(self, password, env): # password为企业微信的用户ID try: diff --git a/wecom_base/models/wecom_apps.py b/wecom_base/models/wecom_apps.py index 913c97c1..9d6d8f18 100644 --- a/wecom_base/models/wecom_apps.py +++ b/wecom_base/models/wecom_apps.py @@ -136,7 +136,7 @@ class WeComApps(models.Model): string="Visible range (Department)", copy=False ) # 企业应用可见范围(部门) allow_tags = fields.Char(string="Visible range (Tag))", copy=False) # 企业应用可见范围(标签) - close = fields.Boolean(string="Disabled", copy=False) # 企业应用是否被停用 + close = fields.Boolean(string="Disabled", copy=False) # 企业应用是否被停用 redirect_domain = fields.Char(string="Trusted domain name", copy=True) # 企业应用可信域名 report_location_flag = fields.Boolean( string="Open the geographic location and report", copy=False @@ -397,8 +397,8 @@ class WeComApps(models.Model): ) try: if ( - self.jsapi_ticket_expiration_time - and self.jsapi_ticket_expiration_time > datetime.now() + self.jsapi_ticket_expiration_time + and self.jsapi_ticket_expiration_time > datetime.now() ): # 未过期, # print("未过期") @@ -420,16 +420,25 @@ class WeComApps(models.Model): {"type": "agent_config"}, ) except ApiException as ex: + msg = { + "title": _("Tips"), + "message": _("Not Successfully obtained application ticket,Error info :%s!" % ex), + "sticky": False, + "next": { + "type": "ir.actions.client", + "tag": "reload", + }} return self.env["wecomapi.tools.action"].ApiExceptionDialog( ex, raise_exception=True ) + else: if response["errcode"] == 0: self.write( { "jsapi_ticket": response["ticket"], "jsapi_ticket_expiration_time": datetime.now() - + timedelta(seconds=response["expires_in"]), + + timedelta(seconds=response["expires_in"]), } ) msg = { diff --git a/wecom_contacts/models/res_config_settings.py b/wecom_contacts/models/res_config_settings.py index 1960b8d7..187a8999 100644 --- a/wecom_contacts/models/res_config_settings.py +++ b/wecom_contacts/models/res_config_settings.py @@ -158,7 +158,10 @@ class ResConfigSettings(models.TransientModel): raise ValidationError( _("Please enable the function of join enterprise QR code!") ) - + if not self.company_id.corpid or not self.contacts_app_id.secret: + raise ValidationError( + _("Please enable corpid or secret!") + ) if debug: _logger.info( _("Start getting join enterprise QR code of company [%s]") @@ -222,6 +225,8 @@ class ResConfigSettings(models.TransientModel): if debug: _logger.info(_("Start to get enterprise wechat API domain name IP segment")) try: + if not self.contacts_app_id.secret: + raise ValidationError(_("Please bind contact app secret")) wecomapi = self.env["wecom.service_api"].InitServiceApi( self.company_id.corpid, self.contacts_app_id.secret ) diff --git a/wecom_contacts_sync/i18n/zh_CN.po b/wecom_contacts_sync/i18n/zh_CN.po index 18157dc0..3225c160 100644 --- a/wecom_contacts_sync/i18n/zh_CN.po +++ b/wecom_contacts_sync/i18n/zh_CN.po @@ -1233,7 +1233,7 @@ msgstr "个人二维码" #. module: wecom_contacts_sync #: code:addons/wecom_contacts_sync/models/res_config_settings.py:0 #, python-format -msgid "Please bind contact app!" +msgid "c!" msgstr "请绑定通讯录应用!" #. module: wecom_contacts_sync diff --git a/wecom_contacts_sync/models/wecom_apps.py b/wecom_contacts_sync/models/wecom_apps.py index 25010649..2d037fac 100644 --- a/wecom_contacts_sync/models/wecom_apps.py +++ b/wecom_contacts_sync/models/wecom_apps.py @@ -156,8 +156,8 @@ class WeComApps(models.Model): app_config_id = self.env["wecom.app_config"].search([("id", "=", id)]) app_config = ( self.env["wecom.app_config"] - .sudo() - .search([("app_id", "=", self.id), ("key", "=", app_config_id.key)]) + .sudo() + .search([("app_id", "=", self.id), ("key", "=", app_config_id.key)]) ) if not app_config: @@ -169,7 +169,7 @@ class WeComApps(models.Model): "ttype": app_config_id.ttype, "value": "" if app_config_id.key == "join_qrcode" - or app_config_id.key == "join_qrcode_last_time" + or app_config_id.key == "join_qrcode_last_time" else app_config_id.value, "description": app_config_id.description, } @@ -200,7 +200,7 @@ class WeComApps(models.Model): sync_start_time = time.time() for app in self.search( - [("company_id", "!=", False), ("type_code", "=", "['contacts']")] + [("company_id", "!=", False), ("type_code", "=", "['contacts']")] ): _logger.info( _( @@ -309,106 +309,72 @@ Synchronize Wecom tag results: """ 同步通讯录 """ - # result = { - - # } result = {} - result.update( - { - "company_name": self.company_id.name, - "sync_state": "completed", - "wecom_department_sync_state": "fail", - "wecom_department_sync_times": 0, - "wecom_department_sync_result": "", - "wecom_user_sync_state": "fail", - "wecom_user_sync_times": 0, - "wecom_user_sync_result": "", - "wecom_tag_sync_state": "fail", - "wecom_tag_sync_times": 0, - "wecom_tag_sync_result": "", - } - ) + result.update({"company_name": self.company_id.name, + "sync_state": "completed", + "wecom_department_sync_state": "fail", + "wecom_department_sync_times": 0, + "wecom_department_sync_result": "", + "wecom_user_sync_state": "fail", + "wecom_user_sync_times": 0, + "wecom_user_sync_result": "", + "wecom_tag_sync_state": "fail", + "wecom_tag_sync_times": 0, + "wecom_tag_sync_result": ""}) # 同步企微部门 - sync_department_result = ( - self.env["wecom.department"] - .with_context(company_id=self.company_id) - .download_wecom_deps() - ) + sync_department_result = self.env["wecom.department"].with_context( + company_id=self.company_id).download_wecom_deps() # [{'name': 'download_department_data', 'state': True, 'time': 0.3037421703338623, 'msg': 'Department list sync completed.'}] - ( - wecom_department_sync_state, - wecom_department_sync_times, - wecom_department_sync_result, - ) = self.handle_sync_task_state(sync_department_result, self.company_id) - - result.update( - { - "wecom_department_sync_state": wecom_department_sync_state, - "wecom_department_sync_times": wecom_department_sync_times, - "wecom_department_sync_result": wecom_department_sync_result, - } - ) + (wecom_department_sync_state, + wecom_department_sync_times, + wecom_department_sync_result, + ) = self.handle_sync_task_state(sync_department_result, self.company_id) + + result.update({"wecom_department_sync_state": wecom_department_sync_state, + "wecom_department_sync_times": wecom_department_sync_times, + "wecom_department_sync_result": wecom_department_sync_result, + }) if result["wecom_department_sync_state"] == "fail": return result # 同步企微用户 - sync_user_result = ( - self.env["wecom.user"] - .with_context(company_id=self.company_id) - .download_wecom_users() - ) - ( - wecom_user_sync_state, - wecom_user_sync_times, - wecom_user_sync_result, - ) = self.handle_sync_task_state(sync_user_result, self.company_id) - result.update( - { - "wecom_user_sync_state": wecom_user_sync_state, - "wecom_user_sync_times": wecom_user_sync_times, - "wecom_user_sync_result": wecom_user_sync_result, - } - ) + sync_user_result = (self.env["wecom.user"].with_context(company_id=self.company_id).download_wecom_users()) + (wecom_user_sync_state, + wecom_user_sync_times, + wecom_user_sync_result, + ) = self.handle_sync_task_state(sync_user_result, self.company_id) + result.update({"wecom_user_sync_state": wecom_user_sync_state, + "wecom_user_sync_times": wecom_user_sync_times, + "wecom_user_sync_result": wecom_user_sync_result, + }) if result["wecom_user_sync_state"] == "fail": return result # 同步企微标签 - sync_wecom_tag_result = ( - self.env["wecom.tag"] - .with_context(company_id=self.company_id) - .download_wecom_tags() - ) - ( - wecom_tag_sync_state, - wecom_tag_sync_times, - wecom_tag_sync_result, - ) = self.handle_sync_task_state(sync_wecom_tag_result, self.company_id) - result.update( - { - "wecom_tag_sync_state": wecom_tag_sync_state, - "wecom_tag_sync_times": wecom_tag_sync_times, - "wecom_tag_sync_result": wecom_tag_sync_result, - } - ) - + sync_wecom_tag_result = self.env["wecom.tag"].with_context(company_id=self.company_id).download_wecom_tags() + + (wecom_tag_sync_state, + wecom_tag_sync_times, + wecom_tag_sync_result, + ) = self.handle_sync_task_state(sync_wecom_tag_result, self.company_id) + result.update({"wecom_tag_sync_state": wecom_tag_sync_state, + "wecom_tag_sync_times": wecom_tag_sync_times, + "wecom_tag_sync_result": wecom_tag_sync_result}) if result["wecom_tag_sync_state"] == "fail": return result - return result def get_state_name(self, key): """ 获取状态名称 """ - STATE = { - "completed": _("All completed"), - "partially": _("Partially complete"), - "fail": _("All failed"), - } + STATE = {"completed": _("All completed"), + "partially": _("Partially complete"), + "fail": _("All failed")} return dict(STATE).get(key, _("Unknown")) # 如果没有找到,返回Unknown def handle_sync_result(self, index, rows, result): @@ -431,9 +397,7 @@ Synchronize Wecom tag results: fail_state_rows = len(df[df["sync_state"] == "fail"]) # 获取失败行数 # 获取部门失败行数 - fail_department_state_rows = len( - df[df["wecom_department_sync_state"] == "fail"] - ) + fail_department_state_rows = len(df[df["wecom_department_sync_state"] == "fail"]) # 获取用户失败行数 fail_user_state_rows = len(df[df["wecom_user_sync_state"] == "fail"]) @@ -456,10 +420,7 @@ Synchronize Wecom tag results: # 部门 if fail_department_state_rows == all_state_rows: wecom_department_sync_state = "fail" - elif ( - fail_department_state_rows > 0 - and fail_department_state_rows < all_state_rows - ): + elif (fail_department_state_rows > 0 and fail_department_state_rows < all_state_rows): wecom_department_sync_state = "partially" elif fail_department_state_rows == 0: wecom_department_sync_state = "completed" @@ -471,6 +432,8 @@ Synchronize Wecom tag results: wecom_user_sync_state = "partially" elif fail_user_state_rows == 0: wecom_user_sync_state = "completed" + else: + wecom_user_sync_state = '' if fail_tag_state_rows == all_state_rows: wecom_tag_sync_state = "fail" @@ -479,12 +442,11 @@ Synchronize Wecom tag results: elif fail_tag_state_rows == 0: wecom_tag_sync_state = "completed" - return ( - sync_state, - wecom_department_sync_state, - wecom_user_sync_state, - wecom_tag_sync_state, - ) + return (sync_state, + wecom_department_sync_state, + wecom_user_sync_state, + wecom_tag_sync_state, + ) def handle_sync_task_state(self, result, company): """ diff --git a/wecom_contacts_sync/models/wecom_user.py b/wecom_contacts_sync/models/wecom_user.py index 65eec13e..d7e69936 100644 --- a/wecom_contacts_sync/models/wecom_user.py +++ b/wecom_contacts_sync/models/wecom_user.py @@ -19,11 +19,10 @@ class WecomUser(models.Model): _order = "order_in_department" # 企微字段 - userid = fields.Char( - string="User ID", - readonly=True, - default="", - ) # 成员UserID。对应管理端的帐号 + userid = fields.Char(string="User ID", + readonly=True, + default="", + ) # 成员UserID。对应管理端的帐号 name = fields.Char(string="Name", readonly=True, default="") # 成员名称 english_name = fields.Char( string="English name", readonly=True, default="" @@ -78,70 +77,56 @@ class WecomUser(models.Model): ) # 开放用户Id,全局唯一,对于同一个服务商,不同应用获取到企业内同一个成员的open_userid是相同的,最多64个字节。仅第三方应用可获取 # odoo 字段 - company_id = fields.Many2one( - "res.company", - required=True, - domain="[('is_wecom_organization', '=', True)]", - copy=False, - store=True, - readonly=True, - ) - department_id = fields.Many2one( - "wecom.department", - "Department", - domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", - compute="_compute_department_id", - store=True, - ) - department_ids = fields.Many2many( - "wecom.department", - "wecom_user_department_rel", - "user_id", - "department_id", - string="Multiple Departments", - readonly=True, - compute="_compute_department_ids", - ) - tag_ids = fields.Many2many( - "wecom.tag", - "wecom_user_tag_rel", - "wecom_user_id", - "wecom_tag_id", - string="Tags", - ) - department_complete_name = fields.Char( - string="Department complete Name", related="department_id.complete_name" - ) - order_in_department = fields.Integer( - string="Sequence in department", - readonly=True, - default="0", - ) # 成员在对应部门中的排序值,默认为0。数量必须和department一致 - status_name = fields.Selection( - [ - ("1", _("Activated")), - ("2", _("Disabled")), - ("4", _("Not active")), - ("5", _("Exit the enterprise")), - ], - string="Status", - readonly=True, - # compute="_compute_status_name", - ) # 激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业。已激活代表已激活企业微信或已关注微信插件(原企业号)。未激活代表既未激活企业微信又未关注微信插件(原企业号)。 - - gender_name = fields.Selection( - [("1", _("Male")), ("2", _("Female")), ("0", _("Undefined"))], - string="Gender", - # compute="_compute_gender_name", - ) + company_id = fields.Many2one("res.company", + required=True, + domain="[('is_wecom_organization', '=', True)]", + copy=False, + store=True, + readonly=True, ) + department_id = fields.Many2one("wecom.department", + "Department", + domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", + compute="_compute_department_id", + store=True, ) + department_ids = fields.Many2many("wecom.department", + "wecom_user_department_rel", + "user_id", + "department_id", + string="Multiple Departments", + readonly=True, + compute="_compute_department_ids", ) + tag_ids = fields.Many2many("wecom.tag", + "wecom_user_tag_rel", + "wecom_user_id", + "wecom_tag_id", + string="Tags", + ) + department_complete_name = fields.Char(string="Department complete Name", related="department_id.complete_name") + order_in_department = fields.Integer(string="Sequence in department", + readonly=True, + default="0", + ) # 成员在对应部门中的排序值,默认为0。数量必须和department一致 + status_name = fields.Selection([("1", _("Activated")), + ("2", _("Disabled")), + ("4", _("Not active")), + ("5", _("Exit the enterprise")), ], + string="Status", readonly=True, + # compute="_compute_status_name", + ) # 激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业。已激活代表已激活企业微信或已关注微信插件(原企业号)。未激活代表既未激活企业微信又未关注微信插件(原企业号)。 + + gender_name = fields.Selection([("1", _("Male")), + ("2", _("Female")), + ("0", _("Undefined"))], + string="Gender", + # compute="_compute_gender_name", + ) color = fields.Integer("Color Index") - active = fields.Boolean( - "Active", - default=True, - store=True, - readonly=True, - # compute="_compute_active", - ) + active = fields.Boolean("Active", + default=True, + store=True, + readonly=True, + # compute="_compute_active", + ) @api.depends("status") def _compute_status_name(self): @@ -159,13 +144,8 @@ class WecomUser(models.Model): @api.depends("main_department") def _compute_department_id(self): for user in self: - department_id = self.env["wecom.department"].search( - [ - ("department_id", "=", user.main_department), - ("company_id", "=", user.company_id.id), - ], - limit=1, - ) + department_id = self.env["wecom.department"].search([("department_id", "=", user.main_department), + ("company_id", "=", user.company_id.id)], limit=1) if department_id: user.department_id = department_id @@ -181,9 +161,7 @@ class WecomUser(models.Model): """ for user in self: department_list = eval(user.department) - department_ids = self.get_parent_department( - user.company_id, department_list - ) + department_ids = self.get_parent_department(user.company_id, department_list) user.write({"department_ids": [(6, 0, department_ids)]}) @@ -193,62 +171,64 @@ class WecomUser(models.Model): """ department_ids = [] for department in departments: - department_id = self.env["wecom.department"].search( - [ - ("department_id", "=", department), - ("company_id", "=", company.id), - ], - limit=1, - ) + department_id = self.env["wecom.department"].search([("department_id", "=", department), + ("company_id", "=", company.id)], limit=1) if department_id: department_ids.append(department_id.id) return department_ids - def copy_as_system_user(self): """ 复制为系统用户 """ app_config = self.env["wecom.app_config"].sudo() - contacts_allow_add_system_users = app_config.get_param( - self.company_id.contacts_app_id.id, "contacts_allow_add_system_users" - ) # 允许创建用户 + contacts_allow_add_system_users = app_config.get_param(self.company_id.contacts_app_id.id, + "contacts_allow_add_system_users" + ) # 允许创建用户 if contacts_allow_add_system_users: # 允许 通讯录 生成系统用户 + # 首先 检查这个用户名称是否存在系统用户 + user_name = tools.ustr(self.name) login = tools.ustr(self.userid) - domain=[('wecom_userid','=',login),"|",("active", "=", True),("active", "=", False)] - user = self.env["res.users"].search(domain, limit=1) - if not user: - # 不存在系统用户,则创建系统用户 - - group_portal_id = self.env["ir.model.data"]._xmlid_to_res_id("base.group_portal") # 门户用户组 - user.create({ - "name": self.name if self.name else login, - "login": login, - "groups_id": [(6, 0, [group_portal_id])], - "share": False, - "active": True if self.status == 1 else False, - # "image_1920": self.avatar, - "company_id": self.company_id.id, - # 以下为企业微信字段 - "wecom_userid": login, - "wecom_openid": self.open_userid, - "is_wecom_user": True, - "qr_code": self.qr_code, - "wecom_user_order": self.order, - }) + user = self.env["res.users"].search( + [('name', '=', user_name), "|", ("active", "=", True), ("active", "=", False)], limit=1) + if user: + # 以下为企业微信字段 + user.write({"wecom_userid": login, + "wecom_openid": self.open_userid, + "is_wecom_user": True, + "qr_code": self.qr_code, + "wecom_user_order": self.order, + }) + else: + domain = [('wecom_userid', '=', login), "|", ("active", "=", True), ("active", "=", False)] + user = self.env["res.users"].search(domain, limit=1) + if not user: + # 不存在系统用户,则创建系统用户 + group_portal_id = self.env["ir.model.data"]._xmlid_to_res_id("base.group_portal") # 门户用户组 + user.create({"name": self.name if self.name else login, + "login": login, + "groups_id": [(6, 0, [group_portal_id])], + "share": False, + "active": True if self.status == 1 else False, + # "image_1920": self.avatar, + "company_id": self.company_id.id, + # 以下为企业微信字段 + "wecom_userid": login, + "wecom_openid": self.open_userid, + "is_wecom_user": True, + "qr_code": self.qr_code, + "wecom_user_order": self.order, + }) else: # 不允许 通讯录 生成系统用户 - msg = { - "title": _("Error!"), - "message": _("The current configuration does not allow replication as a system user!"), - "sticky": False - } + msg = {"title": _("Error!"), + "message": _("The current configuration does not allow replication as a system user!"), + "sticky": False + } return self.env["wecomapi.tools.action"].WecomWarningNotification(msg) - - # ------------------------------------------------------------ # 企微用户下载 # ------------------------------------------------------------ @@ -258,53 +238,36 @@ class WecomUser(models.Model): 下载用户列表 """ start_time = time.time() - company = self.env.context.get("company_id") if type(company) == int: company = self.env["res.company"].browse(company) - tasks = [] - try: - wxapi = self.env["wecom.service_api"].InitServiceApi( - company.corpid, company.contacts_app_id.secret - ) + wxapi = self.env["wecom.service_api"].InitServiceApi(company.corpid, company.contacts_app_id.secret) # 2020-8-27 按照官方API要求,进行重构 # json对象只有 userid 和 department 两个字段 # 参数:"cursor", 必须:否, 说明:用于分页查询的游标,字符串类型,由上一次调用返回,首次调用不填 # 参数:"limit", 必须:否, 说明:分页,预期请求的数据量,取值范围 1 ~ 10000 - response = wxapi.httpCall( - self.env["wecom.service_api_list"].get_server_api_call("USER_LIST_ID"), - {} - # {"department_id": "1", "fetch_child": "1",}, - ) + response = wxapi.httpCall(self.env["wecom.service_api_list"].get_server_api_call("USER_LIST_ID"), + {} + # {"department_id": "1", "fetch_child": "1",}, + ) except ApiException as ex: end_time = time.time() - - self.env["wecomapi.tools.action"].ApiExceptionDialog( - str(ex), raise_exception=False - ) - tasks = [ - { - "name": "download_user_data", - "state": False, - "time": end_time - start_time, - "msg": str(ex), - } - ] + self.env["wecomapi.tools.action"].ApiExceptionDialog(str(ex), raise_exception=False) + tasks = [{"name": "download_user_data", + "state": False, + "time": end_time - start_time, + "msg": str(ex), }] except Exception as e: end_time = time.time() - tasks = [ - { - "name": "download_user_data", - "state": False, - "time": end_time - start_time, - "msg": str(e), - } - ] + tasks = [{"name": "download_user_data", + "state": False, + "time": end_time - start_time, + "msg": str(e), }] else: # response["userlist"] 只含 'department', 'userid' 2个字段 if response["errcode"] == 0: @@ -336,12 +299,10 @@ class WecomUser(models.Model): # 3.完成下载 end_time = time.time() - task = { - "name": "download_user_data", - "state": True, - "time": end_time - start_time, - "msg": _("User list sync completed."), - } + task = {"name": "download_user_data", + "state": True, + "time": end_time - start_time, + "msg": _("User list sync completed."), } tasks.append(task) finally: return tasks # 返回结果 @@ -350,14 +311,7 @@ class WecomUser(models.Model): """ 下载用户 """ - user = self.sudo().search( - [ - ("userid", "=", wecom_user["userid"].lower()), - ("company_id", "=", company.id), - ], - limit=1, - ) - + user = self.sudo().search([("userid", "=", wecom_user["userid"]), ("company_id", "=", company.id)], limit=1) result = {} if not user: result = self.create_user(company, user, wecom_user) @@ -370,52 +324,40 @@ class WecomUser(models.Model): 创建用户 """ try: - user.create( - { - "userid": wecom_user["userid"], - "department": wecom_user["department"], - "company_id": company.id, - } - ) + user.create({"userid": wecom_user["userid"], + "department": wecom_user["department"], + "company_id": company.id, + }) + download_single_user = user.download_single_user() except Exception as e: - result = _("Error creating company [%s]'s user [%s], error reason: %s") % ( - company.userid, - wecom_user["userid"].lower(), - repr(e), - ) - + result = _("Error creating company [%s]'s user [%s], error reason: %s") % (company.userid, + wecom_user["userid"].lower(), + repr(e),) _logger.warning(result) - return { - "name": "add_user", - "state": False, - "time": 0, - "msg": result, - } + return {"name": "add_user", + "state": False, + "time": 0, + "msg": result, + } def update_user(self, company, user, wecom_user): """ 更新用户 """ try: - user.sudo().write( - { - "department": wecom_user["department"], - } - ) - + user.sudo().write({"department": wecom_user["department"]}) + if not user.name: + download_single_user = user.download_single_user() except Exception as e: - result = _("Error update company [%s]'s user [%s], error reason: %s") % ( - company.name, - wecom_user["userid"].lower(), - repr(e), - ) + result = _("Error update company [%s]'s user [%s], error reason: %s") % (company.name, + wecom_user["userid"].lower(), + repr(e),) _logger.warning(result) - return { - "name": "update_user", - "state": False, - "time": 0, - "msg": result, - } # 返回失败结果 + return {"name": "update_user", + "state": False, + "time": 0, + "msg": result, + } # 返回失败结果 def download_single_user(self): """ @@ -425,96 +367,76 @@ class WecomUser(models.Model): params = {} message = "" try: - wxapi = self.env["wecom.service_api"].InitServiceApi( - company.corpid, company.contacts_app_id.secret - ) - response = wxapi.httpCall( - self.env["wecom.service_api_list"].get_server_api_call("USER_GET"), - {"userid": self.userid}, - ) + # 改为只能使用默认的自建应用获取 + wecom_app = self.env['wecom.apps'].search([('company_id', '=', company.id), + ('type', '=', 'self')], order='sequence Desc', limit=1) + wxapi = self.env["wecom.service_api"].InitServiceApi(company.corpid, wecom_app.secret) + response = wxapi.httpCall(self.env["wecom.service_api_list"].get_server_api_call("USER_GET"), + {"userid": self.userid}, ) for key in response.keys(): if type(response[key]) in (list, dict) and response[key]: - json_str = json.dumps( - response[key], - sort_keys=False, - indent=2, - separators=(",", ":"), - ensure_ascii=False, - ) + json_str = json.dumps(response[key], + sort_keys=False, + indent=2, + separators=(",", ":"), + ensure_ascii=False, + ) response[key] = json_str - self.write( - { - "name": response["name"], - "english_name": self.env[ - "wecomapi.tools.dictionary" - ].check_dictionary_keywords(response, "english_name"), - "mobile": response["mobile"], - "department": response["department"], - "main_department": response["main_department"], - "order": response["order"], - "position": response["position"], - "gender": response["gender"], - "email": response["email"], - "biz_mail": response["biz_mail"], - "is_leader_in_dept": response["is_leader_in_dept"], - "direct_leader": response["direct_leader"], - "avatar": response["avatar"], - "thumb_avatar": response["thumb_avatar"], - "telephone": response["telephone"], - "alias": response["alias"], - "extattr": response["extattr"], - "external_profile": self.env[ - "wecomapi.tools.dictionary" - ].check_dictionary_keywords(response, "external_profile"), - "external_position": self.env[ - "wecomapi.tools.dictionary" - ].check_dictionary_keywords(response, "external_position"), - "status": response["status"], - "qr_code": response["qr_code"], - "address": self.env[ - "wecomapi.tools.dictionary" - ].check_dictionary_keywords(response, "address"), - "open_userid": self.env[ - "wecomapi.tools.dictionary" - ].check_dictionary_keywords(response, "open_userid"), - } - ) + self.write({"name": response["name"], + "english_name": self.env["wecomapi.tools.dictionary"].check_dictionary_keywords(response, + "english_name"), + # "mobile": response["mobile"], + "department": response["department"], + "main_department": response["main_department"], + "order": response["order"], + "position": response["position"], + # "gender": response["gender"], + # "email": response["email"], + # "biz_mail": response["biz_mail"], + "is_leader_in_dept": response["is_leader_in_dept"], + "direct_leader": response["direct_leader"], + # "avatar": response["avatar"], + # "thumb_avatar": response["thumb_avatar"], + "telephone": response["telephone"], + "alias": response["alias"], + "extattr": response["extattr"], + "external_profile": self.env["wecomapi.tools.dictionary"].check_dictionary_keywords(response, + "external_profile"), + "external_position": self.env["wecomapi.tools.dictionary"].check_dictionary_keywords(response, + "external_position"), + "status": response["status"], + # "qr_code": response["qr_code"], + "address": self.env["wecomapi.tools.dictionary"].check_dictionary_keywords(response, "address"), + "open_userid": self.env["wecomapi.tools.dictionary"].check_dictionary_keywords(response, + "open_userid")}) except ApiException as ex: - message = _("User [id:%s, name:%s] failed to download,Reason: %s") % ( - self.userid, - self.name, - str(ex), - ) + message = _("User [id:%s, name:%s] failed to download,Reason: %s") % (self.userid, + self.name, + str(ex), + ) _logger.warning(message) - params = { - "title": _("Download failed!"), - "message": message, - "sticky": True, # 延时关闭 - "className": "bg-danger", - "type": "danger", - } + params = {"title": _("Download failed!"), + "message": message, + "sticky": True, # 延时关闭 + "className": "bg-danger", + "type": "danger", } else: - message = _("User [id:%s, name:%s] downloaded successfully") % ( - self.userid, - self.name, - ) - params = { - "title": _("Download Success!"), - "message": message, - "sticky": False, # 延时关闭 - "className": "bg-success", - "type": "success", - "next": { - "type": "ir.actions.client", - "tag": "reload", - }, # 刷新窗体 - } + message = _("User [id:%s, name:%s] downloaded successfully") % (self.userid, + self.name,) + params = {"title": _("Download Success!"), + "message": message, + "sticky": False, # 延时关闭 + "className": "bg-success", + "type": "success", + "next": {"type": "ir.actions.client", + "tag": "reload", + }, # 刷新窗体 + } finally: - action = { - "type": "ir.actions.client", - "tag": "display_notification", - "params": params, - } + action = {"type": "ir.actions.client", + "tag": "display_notification", + "params": params, + } return action def get_open_userid(self): @@ -523,22 +445,12 @@ class WecomUser(models.Model): """ for user in self: try: - wxapi = self.env["wecom.service_api"].InitServiceApi( - user.company_id.corpid, - user.company_id.contacts_app_id.secret, - ) - response = wxapi.httpCall( - self.env["wecom.service_api_list"].get_server_api_call( - "USERID_TO_OPENID" - ), - { - "userid": user.userid, - }, - ) + wxapi = self.env["wecom.service_api"].InitServiceApi(user.company_id.corpid, + user.company_id.contacts_app_id.secret, ) + response = wxapi.httpCall(self.env["wecom.service_api_list"].get_server_api_call("USERID_TO_OPENID"), + {"userid": user.userid, }, ) except ApiException as ex: - self.env["wecomapi.tools.action"].ApiExceptionDialog( - ex, raise_exception=True - ) + self.env["wecomapi.tools.action"].ApiExceptionDialog(ex, raise_exception=True) else: user.open_userid = response["openid"] @@ -553,17 +465,9 @@ class WecomUser(models.Model): company_id = self.env.context.get("company_id") user_dict = xmltodict.parse(xml_tree)["xml"] # print("wecom_event_change_contact_user", user_dict) - domain = [ - "|", - ("active", "=", True), - ("active", "=", False), - ] - + domain = ["|", ("active", "=", True), ("active", "=", False)] users = self.sudo().search([("company_id", "=", company_id.id)] + domain) - callback_user = users.search( - [("userid", "=", user_dict["UserID"])], - limit=1, - ) + callback_user = users.search([("userid", "=", user_dict["UserID"])], limit=1, ) if callback_user: # 如果存在,则更新 @@ -595,8 +499,4 @@ class WecomUser(models.Model): del update_dict["userid"] callback_user.write(update_dict) elif cmd == "delete": - callback_user.write( - { - "active": False, - } - ) + callback_user.write({"active": False}) diff --git a/wecom_contacts_sync/views/wecom_user_views.xml b/wecom_contacts_sync/views/wecom_user_views.xml index 3b9edeaf..4d625c0f 100644 --- a/wecom_contacts_sync/views/wecom_user_views.xml +++ b/wecom_contacts_sync/views/wecom_user_views.xml @@ -7,11 +7,12 @@ wecom.user - - + + - + @@ -28,21 +29,23 @@ - + - - - + + + + -