企业微信 模块
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
@@ -1,43 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
{
|
||||
"name": "iErp Web Widgets",
|
||||
"author": "RStudio",
|
||||
"category": "iErp Suites/Widget",
|
||||
"summary": "iErp Widgets",
|
||||
"website": "https://eis-solution.coding.net/public/odoo/oec/git",
|
||||
"version": "17.0.0.1",
|
||||
"description": """
|
||||
|
||||
""",
|
||||
"depends": [
|
||||
"web",
|
||||
"attachment_indexation",
|
||||
],
|
||||
"external_dependencies": {
|
||||
"python": [
|
||||
# "pyzbar",
|
||||
],
|
||||
},
|
||||
"data": [],
|
||||
"assets": {
|
||||
# "web.assets_common": [
|
||||
# "web_widgets/static/fonts/fonts.scss",
|
||||
# ],
|
||||
"web.assets_backend": [
|
||||
"web_widgets/static/fonts/fonts.scss",
|
||||
"web_widgets/static/src/views/**/*.js",
|
||||
"web_widgets/static/src/views/**/*.xml",
|
||||
"web_widgets/static/src/views/**/*.scss",
|
||||
"web_widgets/static/src/webclient/**/*",
|
||||
],
|
||||
"web.assets_frontend": [
|
||||
# "web_widgets/static/lib/officetohtml/**/*",
|
||||
],
|
||||
},
|
||||
"sequence": 500,
|
||||
"installable": True,
|
||||
"auto_install": True,
|
||||
"application": False,
|
||||
"license": "AGPL-3",
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import binary
|
||||
@@ -1,61 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from odoo import SUPERUSER_ID, _, http
|
||||
from odoo.exceptions import AccessError, MissingError, ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.http import request
|
||||
|
||||
from odoo.addons.web.controllers.binary import Binary
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.tools import file_open, file_path, replace_exceptions
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebWidgetBinary(Binary):
|
||||
|
||||
@http.route(
|
||||
["/<string:model>/<string:field>/<int:id>.<string:filetype>"],
|
||||
type="http",
|
||||
# auth="public",
|
||||
auth="user",
|
||||
)
|
||||
def get_office_url(
|
||||
self,
|
||||
xmlid=None,
|
||||
model="ir.attachment",
|
||||
id=None,
|
||||
field="raw",
|
||||
filetype=None,
|
||||
filename=None,
|
||||
filename_field="name",
|
||||
mimetype=None,
|
||||
unique=False,
|
||||
download=False,
|
||||
access_token=None,
|
||||
nocache=False,
|
||||
):
|
||||
print(model, field, id, filename, filename_field, filetype)
|
||||
|
||||
with replace_exceptions(UserError, by=request.not_found()):
|
||||
record = request.env["ir.binary"]._find_record(
|
||||
xmlid, model, id and int(id), access_token
|
||||
)
|
||||
stream = request.env["ir.binary"]._get_stream_from(
|
||||
record, field, filename, filename_field, mimetype
|
||||
)
|
||||
send_file_kwargs = {"as_attachment": False}
|
||||
if unique:
|
||||
send_file_kwargs["immutable"] = True
|
||||
send_file_kwargs["max_age"] = http.STATIC_CACHE_LONG
|
||||
if nocache:
|
||||
send_file_kwargs["max_age"] = None
|
||||
|
||||
res = stream.get_response(**send_file_kwargs)
|
||||
res.headers["Content-Security-Policy"] = "default-src 'none'"
|
||||
return "https://wecom.rstudio.xyz/web_widgets/static/files/office/sale_order_outsourcing/finalized_office_file_4.docx"
|
||||
return res
|
||||
@@ -1,93 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * web_widgets
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 17.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-03-10 04:17+0000\n"
|
||||
"PO-Revision-Date: 2024-03-10 12:19+0800\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.4.2\n"
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_widgets/static/src/webclient/actions/api_error_dialog/api_error_dialog.js:0
|
||||
#, python-format
|
||||
msgid "API Error"
|
||||
msgstr "API错误"
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_widgets/static/src/webclient/actions/api_error_dialog/api_error_dialog.xml:0
|
||||
#, python-format
|
||||
msgid "Error Details:"
|
||||
msgstr "错误详细信息:"
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_widgets/static/src/webclient/actions/api_error_dialog/api_error_dialog.xml:0
|
||||
#, python-format
|
||||
msgid "Error code:"
|
||||
msgstr "错误代码:"
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_widgets/static/src/webclient/actions/api_error_dialog/api_error_dialog.xml:0
|
||||
#, python-format
|
||||
msgid "Error description:"
|
||||
msgstr "错误描述:"
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_widgets/static/src/webclient/actions/api_error_dialog/api_error_dialog.xml:0
|
||||
#, python-format
|
||||
msgid "Error troubleshooting method:"
|
||||
msgstr "错误排除方法:"
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-python
|
||||
#: code:addons/web_widgets/models/ir_module.py:0
|
||||
#, python-format
|
||||
msgid "Failed to upload file %s, reason:%s"
|
||||
msgstr "无法上载文件%s,原因:%s"
|
||||
|
||||
#. module: web_widgets
|
||||
#: model:ir.model,name:web_widgets.model_ir_module_module
|
||||
msgid "Module"
|
||||
msgstr "模块"
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_widgets/static/src/views/fields/password_char/password_char.js:0
|
||||
#, python-format
|
||||
msgid "String decrypted"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_widgets/static/src/views/fields/password_char/password_char.js:0
|
||||
#, python-format
|
||||
msgid "String encrypted"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-python
|
||||
#: code:addons/web_widgets/models/ir_module.py:0
|
||||
#, python-format
|
||||
msgid "Successfully uploaded %s"
|
||||
msgstr "已成功上传 %s"
|
||||
|
||||
#. module: web_widgets
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_widgets/static/src/views/fields/password_char/password_char.js:0
|
||||
#, python-format
|
||||
msgid "Text is displayed in password characters"
|
||||
msgstr "文本以密码字符显示"
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import ir_module
|
||||
@@ -1,37 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import platform
|
||||
import base64
|
||||
import pathlib
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.tools.misc import file_path
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Module(models.Model):
|
||||
_inherit = "ir.module.module"
|
||||
|
||||
@api.model
|
||||
def get_module_installation_status(self, addon_name=None):
|
||||
"""
|
||||
获取 addon 是否安装
|
||||
"""
|
||||
addon = self.sudo().search([("name", "=", addon_name)])
|
||||
if not addon:
|
||||
# return {
|
||||
# "exist": False,
|
||||
# "moduleId": 0,
|
||||
# }
|
||||
return False
|
||||
else:
|
||||
# addon.button_install()
|
||||
# return {
|
||||
# "exist": True,
|
||||
# "moduleId": addon.id,
|
||||
# }
|
||||
return True
|
||||
@@ -1,106 +0,0 @@
|
||||
# 企微 Widget
|
||||
|
||||
## Json编辑器
|
||||
|
||||
创建一个 `fields.Json` 的字段,然后在视图中使用 `json_editor` 的 `widget`,即可使用 `json` 编辑器
|
||||
```python
|
||||
report_data_set = fields.Json(string="Report Data Set", translate=False, help="Report Data Set.")
|
||||
```
|
||||
```xml
|
||||
<field name="report_data_set" widget="json_editor" options="{'mode': 'code', 'modes':['code', 'form', 'text', 'tree', 'view', 'preview']}" force_save="1" readonly="1"/>
|
||||
```
|
||||
> `options` 参数说明:
|
||||
|
||||
| 选项| 说明 |
|
||||
|----------|-----:|
|
||||
|mode| 默认模式 |
|
||||
|modes| 模式清单,可选有: `code`, `form`, `text`, `tree`, `view`, `preview`|
|
||||
|
||||
|
||||
## Markdown 编辑器
|
||||
|
||||
### 使用
|
||||
```xml
|
||||
<field name="description" widget="markdown_editor" options="{'height': 'auto','theme': 'dark', 'previewStyle':'vertical', }"/>
|
||||
```
|
||||
|
||||
> `options` 参数说明:
|
||||
|
||||
| 选项| 类型 |说明 |
|
||||
|----------|-----:|-----:|
|
||||
|height|字符串 |高度 例如 `300px` 或者 `auto`|
|
||||
|theme|字符串 |主题 `dark` 或者为 空 |
|
||||
|previewStyle| 字符串|预览样式,可选有: 选项卡 `tab`, 垂直 `vertical`|
|
||||
|userPlugin| 布尔值|使用组件,默认为`false`|
|
||||
|
||||
|
||||
> 组件列表说明:
|
||||
|
||||
| 名称| 说明 |链接 |
|
||||
|----------|-----:|-----:|
|
||||
|chart|图表插件,`测试无法正常使用`| <a href="https://nhn.github.io/tui.editor/latest/tutorial-example07-editor-with-chart-plugin" target="_blank">打开链接</a>|
|
||||
|code-syntax-highlight|代码语法突出显示插件,`可以正常使用`| <a href="https://nhn.github.io/tui.editor/latest/tutorial-example08-editor-with-code-syntax-highlight-plugin" target="_blank">打开链接</a>|
|
||||
|colorSyntax|颜色语法插件,`测试无法正常使用`| <a href="https://nhn.github.io/tui.editor/latest/tutorial-example09-editor-with-color-syntax-plugin" target="_blank">打开链接</a>|
|
||||
|tableMergedCell|表格合并单元格插件,`测试无法正常使用`| <a href="https://nhn.github.io/tui.editor/latest/tutorial-example10-editor-with-table-merged-cell-plugin" target="_blank">打开链接</a>|
|
||||
|uml|UML 插件,`测试无法正常使用`| <a href="https://nhn.github.io/tui.editor/latest/tutorial-example11-editor-with-uml-plugin" target="_blank">打开链接</a>|
|
||||
|
||||
|
||||
## 扫码
|
||||
1. 支持条码及二维码
|
||||
2. 支持同时扫码多个条码及二维码
|
||||
3. 调用 `企业微信扫一扫` 仅支持单个条码及二维码
|
||||
|
||||
### `ScanCodeChar`
|
||||
```xml
|
||||
<field name="barcode" widget="ScanCodeChar" options="{'need_confirm': true, 'autoplay':true}" />
|
||||
```
|
||||
|
||||
### `ScanCodeText`
|
||||
```xml
|
||||
<field name="barcode" widget="ScanCodeText" options="{'need_confirm': true, 'autoplay':true}" />
|
||||
```
|
||||
|
||||
### `ScanCodeURL`
|
||||
```xml
|
||||
<field name="barcode" widget="ScanCodeURL" options="{'need_confirm': true, 'autoplay':true}" />
|
||||
```
|
||||
|
||||
## 密码显示 `display_password`
|
||||
```xml
|
||||
<field name="contacts_secret" widget="password_char" require_encryption="True" />
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 一对多配置 one2many_config
|
||||
```xml
|
||||
<field name="contacts_app_config_ids" widget="one2many_config" format="value" type="ttype" help="description" class="w-100 pl-3" t-translation="off">
|
||||
<tree create="0" edit="true" delete="0" editable="bottom" sample="1">
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="key" readonly="1"/>
|
||||
<field name="value" need_format="true" force_save="1" required="1"/>
|
||||
<field name="description" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
```
|
||||
|
||||
## 从 form 视图 返回 list 视图,模拟 alt+b 的组合键
|
||||
|
||||
> 应一个客户的需求开发
|
||||
|
||||
### 在form 视图添加一个按钮,如下:
|
||||
|
||||
```html
|
||||
<button name="action_confirm" id="action_confirm" string="Confirm" class="btn-primary" type="object" icon="fa-check" invisible="state not in ['draft','error']"/>
|
||||
```
|
||||
|
||||
### 此按钮python 代码如下:
|
||||
```python
|
||||
def action_confirm(self):
|
||||
# 省略一万字的代码字符串
|
||||
action = {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "open_list_action",
|
||||
}
|
||||
return action
|
||||
```
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 9.9 KiB |
@@ -1,4 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'password';
|
||||
src: url('/web_widgets/static/fonts/password.ttf');
|
||||
}
|
||||
Binary file not shown.
@@ -1,13 +0,0 @@
|
||||
.o_field_widget {
|
||||
|
||||
// MS Office Viewer
|
||||
&.o_field_ms_office_viewer {
|
||||
.o_msoffice_view_iframe {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
border: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.PasswordCharField" owl="1">
|
||||
|
||||
<t t-if="props.readonly">
|
||||
<span t-esc="props.record.data[props.name] or ''" />
|
||||
<!-- <span t-esc="formattedValue" /> -->
|
||||
</t>
|
||||
<t t-else="">
|
||||
<!-- <input type="text" class="o_input" autocomplete="off" t-att-id="props.id" t-att-placeholder="props.placeholder" t-att-data-encrypted="`${state.encrypted}`" t-att-required="props.required" t-ref="input" t-on-input="onInput"/> -->
|
||||
|
||||
<input type="text" class="o_input" autocomplete="off" t-att-id="props.id" t-att-placeholder="props.placeholder" data-encrypted="true" t-att-required="props.required" t-ref="input" />
|
||||
|
||||
<!-- <button type="button" t-ref="button" t-attf-class="btn btn-sm btn-primary o_display_password_button" t-on-click.stop="(event) => onClick(event.target,state.encrypted)">
|
||||
<i t-attf-class="fa fa-eye{{state.encrypted ? '-slash':''}}"/>
|
||||
</button> -->
|
||||
|
||||
<button type="button" t-ref="button" t-attf-class="btn btn-sm btn-primary o_display_password_button" t-on-click.stop="() => this.onClick()">
|
||||
<i t-attf-class="fa fa-eye{{state.encrypted ? '-slash':''}}"/>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,103 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useInputField } from "@web/views/fields/input_field_hook";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
|
||||
const { Component, useState, useRef, onMounted, onRendered, onPatched } = owl;
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// DisplayPasswordChar
|
||||
// --------------------------------------------------------------------------------
|
||||
export class PasswordCharField extends Component {
|
||||
|
||||
static template = "web.PasswordCharField";
|
||||
|
||||
static props = {
|
||||
...standardFieldProps,
|
||||
string: { type: String, optional: true },
|
||||
className: { type: String, optional: true },
|
||||
placeholder: { type: String, optional: true },
|
||||
shouldTrim: { type: Boolean, optional: true },
|
||||
encrypted: { type: Boolean, optional: true },
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.state = useState({
|
||||
...this.state,
|
||||
encrypted: true,
|
||||
});
|
||||
|
||||
// this.encrypted = true; // 是否加密
|
||||
this.input = useRef("input");
|
||||
this.button = useRef("button");
|
||||
this.popover = useService("popover");
|
||||
|
||||
useInputField({
|
||||
getValue: () => this.props.record.data[this.props.name] || "",
|
||||
refName: "input",
|
||||
// parse: (v) => this.parse(v),
|
||||
});
|
||||
onMounted(() => {
|
||||
// console.log("-------onMounted", this.input.el)
|
||||
if (this.props.record.data[this.props.name].trim() != "" && this.input.el) {
|
||||
this.input.el.dataset.encrypted = "true";
|
||||
} else if (this.props.record.data[this.props.name].trim() == "" && this.input.el) {
|
||||
this.input.el.dataset.encrypted = "false";
|
||||
}
|
||||
});
|
||||
|
||||
// onRendered(() => {
|
||||
// // console.log("-------2", this.formattedValue)
|
||||
// // if (this.props.record.data[this.props.name].trim() != "") {
|
||||
// // // this.state.encrypted = true;
|
||||
// // this.input.el.dataset.encrypted = "true";
|
||||
// // } else {
|
||||
// // // this.state.encrypted = false;
|
||||
// // this.input.el.dataset.encrypted = "false";
|
||||
// // }
|
||||
// });
|
||||
}
|
||||
|
||||
parse(value) {
|
||||
if (this.shouldTrim) {
|
||||
return value.trim();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// onClick(event, value) {
|
||||
onClick() {
|
||||
let icon = this.button.el.firstChild;
|
||||
|
||||
if (this.state.encrypted) {
|
||||
// 当前状态为加密状态,点击后变为解密状态
|
||||
icon.classList.remove("fa-eye");
|
||||
icon.classList.add("fa-eye-slash");
|
||||
this.input.el.dataset.encrypted = "false";
|
||||
this.state.encrypted = false;
|
||||
} else {
|
||||
// 当前状态为解密状态,点击后变为加密状态
|
||||
icon.classList.remove("fa-eye-slash");
|
||||
icon.classList.add("fa-eye");
|
||||
this.input.el.dataset.encrypted = "true";
|
||||
this.state.encrypted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const passwordCharField = {
|
||||
component: PasswordCharField,
|
||||
displayName: _t("Text is displayed in password characters"),
|
||||
supportedTypes: ["char"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
placeholder: attrs.placeholder,
|
||||
}),
|
||||
};
|
||||
|
||||
registry.category("fields").add("password_char", passwordCharField);
|
||||
@@ -1,51 +0,0 @@
|
||||
.o_field_password_char {
|
||||
width: 100% !important;
|
||||
border: 1px solid $primary;
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
|
||||
&[data-encrypted="true"] {
|
||||
font-family: "password";
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
span {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
span,
|
||||
input {
|
||||
&[data-encrypted="true"] {
|
||||
font-family: "password";
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
&[data-encrypted="false"] {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
// input:empty {
|
||||
// font-family: inherit;
|
||||
// }
|
||||
|
||||
input::placeholde {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
&:focus {
|
||||
// @include print-variable(o-input-border-color, $o-brand-primary);
|
||||
box-shadow: none;
|
||||
// background-color: #FFFFFF;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.PasswordCharField" owl="1">
|
||||
|
||||
<input type="text" class="o_input" autocomplete="off" t-att-id="props.id" t-att-placeholder="props.placeholder" data-encrypted="true" t-att-required="props.required" t-ref="input" t-att-readonly="props.readonly" />
|
||||
|
||||
<button type="button" t-ref="button" t-attf-class="btn btn-sm btn-primary o_display_password_button" t-on-click.stop="() => this.onClick()">
|
||||
<i t-attf-class="fa fa-eye{{state.encrypted ? '-slash':''}}"/>
|
||||
</button>
|
||||
|
||||
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,26 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { markup } from "@odoo/owl";
|
||||
const { Component, } = owl;
|
||||
|
||||
|
||||
export class ApiErrorDialog extends Component {
|
||||
setup() {
|
||||
this.title = this.props.title;
|
||||
this.size = this.props.size;
|
||||
this.code = this.props.code;
|
||||
this.description = this.props.description;
|
||||
this.method = markup(this.props.method);
|
||||
this.details = this.props.details;
|
||||
|
||||
this.bodyClass = "bg-warning";
|
||||
this.contentClass = "bg-warning";
|
||||
}
|
||||
}
|
||||
ApiErrorDialog.components = {
|
||||
Dialog
|
||||
};
|
||||
ApiErrorDialog.template = "web.ApiErrorDialog";
|
||||
ApiErrorDialog.title = _lt("API Error");
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="web.ApiErrorDialog" owl="1">
|
||||
<Dialog size="size" contentClass="contentClass" bodyClass="bodyClass" title="this.constructor.title">
|
||||
<p>
|
||||
<b>Error code: </b>
|
||||
<b t-esc="code"/>
|
||||
</p>
|
||||
<p>
|
||||
<b>Error description:</b>
|
||||
<b t-esc="description"/>
|
||||
</p>
|
||||
<p>
|
||||
<b>Error troubleshooting method:</b>
|
||||
<t t-out="method"/>
|
||||
</p>
|
||||
<p>
|
||||
<b>Error Details:</b>
|
||||
<t t-esc="details"/>
|
||||
</p>
|
||||
</Dialog>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
@@ -1,12 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ApiErrorDialog } from "./api_error_dialog";
|
||||
|
||||
|
||||
|
||||
export function apiDialogAction(env, action) {
|
||||
const params = action.params || {};
|
||||
env.services.dialog.add(ApiErrorDialog, params, {});
|
||||
}
|
||||
registry.category("actions").add("api_error_dialog", apiDialogAction);
|
||||
@@ -1,25 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
export function openListAction(env, action) {
|
||||
|
||||
if (Object.keys(action).length > 0) {
|
||||
window.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'b',
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
altKey: true,
|
||||
metaKey: false,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
registry.category('actions').add('open_list_action', openListAction);
|
||||
@@ -1,19 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo.tools import sql
|
||||
from . import api
|
||||
from . import tools
|
||||
from . import models
|
||||
from . import controllers
|
||||
|
||||
|
||||
# from odoo import api, SUPERUSER_ID, _
|
||||
|
||||
|
||||
# def post_init_hook(cr, registry):
|
||||
# # 安装模块后清理 清除所有的记录,并且索引号从0开始
|
||||
# env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
# query = """
|
||||
# TRUNCATE TABLE wecom_app_config RESTART IDENTITY CASCADE;
|
||||
# """
|
||||
# env.cr.execute(query)
|
||||
@@ -1,46 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
"name": "WeCom API",
|
||||
"author": "RStudio",
|
||||
"website": "https://gitee.com/rainbowstudio/wecom",
|
||||
"sequence": 600,
|
||||
"installable": True,
|
||||
"application": False,
|
||||
"auto_install": False,
|
||||
"category": "WeCom Suites/Settings",
|
||||
"version": "17.0.0.1",
|
||||
"summary": """
|
||||
WeCom Service-side API and Client-side API
|
||||
""",
|
||||
"description": """
|
||||
WeCom Service-side API and Client-side API
|
||||
Reconstruction based on project "https://github.com/sbzhu/weworkapi_python"
|
||||
""",
|
||||
"depends": [],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"data/ir_config_parameter.xml",
|
||||
"data/wecom_server_api_error_data.xml",
|
||||
"data/service_api_list_data.xml",
|
||||
],
|
||||
"assets": {
|
||||
"web.assets_common": [
|
||||
# JS
|
||||
# "wecom_api/static/src/js/*.js",
|
||||
],
|
||||
"web.assets_backend": [
|
||||
# JS
|
||||
],
|
||||
},
|
||||
"external_dependencies": {
|
||||
"python": [
|
||||
"requests_toolbelt",
|
||||
# "pandas",
|
||||
"xmltodict",
|
||||
"pycryptodome",
|
||||
"html2text",
|
||||
],
|
||||
},
|
||||
"license": "AGPL-3",
|
||||
# "post_init_hook": "post_init_hook",
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import wecom_abstract_api
|
||||
from . import wecom_server_api
|
||||
from . import wecom_message_api
|
||||
from . import wecom_client_api
|
||||
@@ -1,198 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import requests
|
||||
from odoo import api, fields, models, _, SUPERUSER_ID
|
||||
from odoo.exceptions import UserError
|
||||
import warnings
|
||||
|
||||
|
||||
class ApiException(Exception):
|
||||
def __init__(self, errCode, errMsg):
|
||||
super().__init__(errCode, errMsg)
|
||||
self.errCode = errCode
|
||||
self.errMsg = errMsg
|
||||
|
||||
|
||||
class WecomAbstractApi(models.AbstractModel):
|
||||
_name = "wecom.abstract_api"
|
||||
_description = "Wecom Abstract API"
|
||||
|
||||
code = fields.Integer("API error code", copy=False)
|
||||
message = fields.Char("API error message", copy=False)
|
||||
|
||||
def get_api_debug(self):
|
||||
"""
|
||||
获取 API 调试模式
|
||||
"""
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
return True if ir_config.get_param("wecom.debug_enabled") == "True" else False
|
||||
|
||||
def getAccessToken(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def refreshAccessToken(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def getSuiteAccessToken(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def refreshSuiteAccessToken(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def getProviderAccessToken(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def refreshProviderAccessToken(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def httpCall(self, urlType, args=None, include_agentid=False):
|
||||
"""
|
||||
调用API
|
||||
:param urlType : 服务端API类型和请求方式("GET" or "POST")
|
||||
:param args : 请求参数
|
||||
:param include_agentid : 标识args含有 "agentid" 关键字,需要进行处理
|
||||
:returns 返回结果
|
||||
"""
|
||||
shortUrl = urlType[0]
|
||||
method = urlType[1]
|
||||
response = {}
|
||||
for retryCnt in range(0, 3):
|
||||
try:
|
||||
if "POST" == method:
|
||||
url = self.__makeUrl(shortUrl)
|
||||
if "agentid" in args and include_agentid: # type: ignore
|
||||
url = self.__appendArgs(url, {"agentid": args["agentid"]}) # type: ignore
|
||||
del args["agentid"] # type: ignore
|
||||
response = self.__httpPost(url, args)
|
||||
elif "GET" == method:
|
||||
url = self.__makeUrl(shortUrl)
|
||||
url = self.__appendArgs(url, args)
|
||||
response = self.__httpGet(url)
|
||||
else:
|
||||
raise ApiException(-1, _("unknown method type"))
|
||||
except Exception as e:
|
||||
raise ApiException(-2, e) # 其他错误
|
||||
|
||||
# 检查令牌是否过期
|
||||
if self.__tokenExpired(response.get("errcode")):
|
||||
self.__refreshToken(shortUrl)
|
||||
retryCnt += 1
|
||||
continue
|
||||
else:
|
||||
break
|
||||
# 检测响应
|
||||
return self.__checkResponse(response)
|
||||
|
||||
def httpPostFile(self, urlType, args=None, data=None, headers=None):
|
||||
shortUrl = urlType[0]
|
||||
response = {}
|
||||
for retryCnt in range(0, 3):
|
||||
url = self.__makeUrl(shortUrl)
|
||||
url = self.__appendArgs(url, args)
|
||||
response = self.__httpPostFile(url, data, headers)
|
||||
|
||||
# 检查令牌是否过期
|
||||
if self.__tokenExpired(response.get("errcode")):
|
||||
self.__refreshToken(shortUrl)
|
||||
retryCnt += 1
|
||||
continue
|
||||
else:
|
||||
break
|
||||
return self.__checkResponse(response)
|
||||
|
||||
@staticmethod
|
||||
def __appendArgs(url, args):
|
||||
if args is None:
|
||||
return url
|
||||
|
||||
for key, value in args.items():
|
||||
if "?" in url:
|
||||
url += "&" + key + "=" + value
|
||||
else:
|
||||
url += "?" + key + "=" + value
|
||||
return url
|
||||
|
||||
@staticmethod
|
||||
def __makeUrl(shortUrl):
|
||||
base = "https://qyapi.weixin.qq.com"
|
||||
if shortUrl[0] == "/":
|
||||
return base + shortUrl
|
||||
else:
|
||||
return base + "/" + shortUrl
|
||||
|
||||
def __appendToken(self, url):
|
||||
if "SUITE_ACCESS_TOKEN" in url:
|
||||
return url.replace("SUITE_ACCESS_TOKEN", self.getSuiteAccessToken())
|
||||
elif "PROVIDER_ACCESS_TOKEN" in url:
|
||||
return url.replace("PROVIDER_ACCESS_TOKEN", self.getProviderAccessToken())
|
||||
elif "ACCESS_TOKEN" in url:
|
||||
return url.replace("ACCESS_TOKEN", self.getAccessToken())
|
||||
else:
|
||||
return url
|
||||
|
||||
def __httpPost(self, url, args):
|
||||
realUrl = self.__appendToken(url)
|
||||
|
||||
if self.get_api_debug() is True:
|
||||
print("Wecom API POST", realUrl, args)
|
||||
|
||||
return requests.post(
|
||||
realUrl, data=json.dumps(args, ensure_ascii=False).encode("utf-8")
|
||||
).json()
|
||||
|
||||
def __httpPostFile(self, url, data, headers):
|
||||
realUrl = self.__appendToken(url)
|
||||
|
||||
if self.get_api_debug() is True:
|
||||
print("Wecom API POST FILE", realUrl, data, headers)
|
||||
|
||||
# response = requests.post(url=realUrl, data=data, headers=headers).json()
|
||||
return requests.post(url=realUrl, data=data, headers=headers).json()
|
||||
|
||||
def __post_file(self, url, media_file):
|
||||
if self.get_api_debug() is True:
|
||||
print("Wecom API POST FILE", url, media_file)
|
||||
return requests.post(url, file=media_file).json() # type: ignore
|
||||
|
||||
def __httpGet(self, url):
|
||||
realUrl = self.__appendToken(url)
|
||||
|
||||
if self.get_api_debug() is True:
|
||||
print("Wecom API GET", realUrl)
|
||||
|
||||
return requests.get(realUrl).json()
|
||||
|
||||
@staticmethod
|
||||
def __checkResponse(response):
|
||||
"""
|
||||
检查 返回 值是否合法,
|
||||
"""
|
||||
errCode = response.get("errcode")
|
||||
errMsg = response.get("errmsg")
|
||||
|
||||
if errCode == 0:
|
||||
return response
|
||||
else:
|
||||
raise ApiException(errCode, errMsg)
|
||||
|
||||
@staticmethod
|
||||
def __tokenExpired(errCode):
|
||||
"""
|
||||
检查 令牌 是否过期
|
||||
"""
|
||||
if errCode == 40014 or errCode == 42001 or errCode == 42007 or errCode == 42009:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __refreshToken(self, url):
|
||||
"""
|
||||
刷新令牌
|
||||
"""
|
||||
if "SUITE_ACCESS_TOKEN" in url:
|
||||
self.refreshSuiteAccessToken()
|
||||
elif "PROVIDER_ACCESS_TOKEN" in url:
|
||||
self.refreshProviderAccessToken()
|
||||
elif "ACCESS_TOKEN" in url:
|
||||
self.refreshAccessToken()
|
||||
@@ -1,96 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import requests
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class WecomClientApi(models.TransientModel):
|
||||
_name = "wecom.client_api"
|
||||
_description = "Wecom Client API"
|
||||
|
||||
@api.model
|
||||
def geContactsDisplayAgentConfig(self, postDatas):
|
||||
"""
|
||||
获取企业微信通讯录展示组件的应用配置
|
||||
"""
|
||||
configs = []
|
||||
result = {}
|
||||
for data in postDatas:
|
||||
config = {}
|
||||
company = self.env["res.company"].search([("id", "=", data["company_id"])])
|
||||
self_built_app = company.self_built_app_id
|
||||
|
||||
timestamp = int(time.time())
|
||||
nonceStr = self.env["wecomapi.tools.security"].random_str(8)
|
||||
jsapi_ticket = company.contacts_app_id.jsapi_ticket
|
||||
signature = self.env["wecomapi.tools.security"].generate_jsapi_signature(
|
||||
jsapi_ticket, nonceStr, timestamp
|
||||
)
|
||||
|
||||
config.update(
|
||||
{
|
||||
"company_id": company.id,
|
||||
"corpid": company.corpid,
|
||||
"agentid": self_built_app.agentid,
|
||||
"timestamp": timestamp,
|
||||
"nonceStr": nonceStr,
|
||||
"signature": signature,
|
||||
"jsApiList": ["selectExternalContact"],
|
||||
}
|
||||
)
|
||||
configs.append(config)
|
||||
result.update(
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "ok",
|
||||
"configs": configs,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@api.model
|
||||
def getBarcodeWXConfig(self, postDatas):
|
||||
"""
|
||||
获取企业微信扫码组件的应用配置
|
||||
"""
|
||||
configs = []
|
||||
result = {}
|
||||
for data in postDatas:
|
||||
config = {}
|
||||
company = (
|
||||
self.env["res.company"].sudo().search([("id", "=", data["company_id"])])
|
||||
)
|
||||
# wecom_apps = self.env['wecom.apps'].sudo().search([('type','=','self'), ('subtype_ids.code','=','auth')], limit=1)
|
||||
timestamp = int(time.time())
|
||||
nonceStr = self.env["wecomapi.tools.security"].sudo().random_str(8)
|
||||
jsapi_ticket = company.corp_jsapi_ticket
|
||||
signature = (
|
||||
self.env["wecomapi.tools.security"]
|
||||
.sudo()
|
||||
.generate_jsapi_signature(jsapi_ticket, nonceStr, timestamp)
|
||||
)
|
||||
|
||||
config.update(
|
||||
{
|
||||
"company_id": company.id,
|
||||
"corpid": company.corpid,
|
||||
# "agentid":wecom_apps.agentid,
|
||||
"timestamp": timestamp,
|
||||
"nonceStr": nonceStr,
|
||||
"signature": signature,
|
||||
"jsApiList": ["scanQRCode"],
|
||||
}
|
||||
)
|
||||
configs.append(config)
|
||||
result.update(
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "ok",
|
||||
"configs": configs,
|
||||
}
|
||||
)
|
||||
return result
|
||||
@@ -1,260 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from odoo import api, models, _
|
||||
|
||||
|
||||
from odoo.addons.wecom_api.api.wecom_abstract_api import ApiException # type: ignore
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
import json
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WeComMessageApi(models.AbstractModel):
|
||||
_name = "wecom.message.api"
|
||||
_description = "WeCom Message API"
|
||||
|
||||
def get_message_api(self, company):
|
||||
""""""
|
||||
wxapi = self.env["wecom.service_api"].InitServiceApi(
|
||||
company, "message_secret", "message"
|
||||
)
|
||||
return wxapi
|
||||
|
||||
def send_by_api(self, message, api):
|
||||
params = self.env["ir.config_parameter"].sudo()
|
||||
debug = params.get_param("wecom.debug_enabled")
|
||||
|
||||
try:
|
||||
del message["company"] # 删除message中的 company
|
||||
|
||||
response = api.httpCall(
|
||||
self.env["wecom.service_api_list"].get_server_api_call("MESSAGE_SEND"),
|
||||
message,
|
||||
)
|
||||
return response
|
||||
|
||||
except ApiException as ex:
|
||||
if debug:
|
||||
_logger.warning(_("Error sending message: %s") % (ex))
|
||||
|
||||
def build_message(
|
||||
self,
|
||||
msgtype,
|
||||
toall=None,
|
||||
touser=None,
|
||||
toparty=None,
|
||||
totag=None,
|
||||
subject=None,
|
||||
media_id=None,
|
||||
description=None,
|
||||
author_id=None,
|
||||
body_html=None,
|
||||
body_json=None,
|
||||
body_markdown=None,
|
||||
safe=None,
|
||||
enable_id_trans=None,
|
||||
enable_duplicate_check=None,
|
||||
duplicate_check_interval=None,
|
||||
company=None,
|
||||
):
|
||||
"""
|
||||
构建消息
|
||||
:msgtype: 消息类型,根据消息类型来构建消息内容
|
||||
:toall: 发送给全体人员
|
||||
:touser: 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。
|
||||
特殊情况:指定为”@all”,则向该企业应用的全部成员发送
|
||||
:toparty: 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。
|
||||
当touser为”@all”时忽略本参数
|
||||
:totag: 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。
|
||||
当touser为”@all”时忽略本参数
|
||||
:subject: 标题 ,不同消息类型,长度限制不一样
|
||||
:media_id: media_id, 可以通过素材管理接口获得
|
||||
:body_html: 图文消息的内容,支持html标签,不超过666 K个字节,仅用于图文消息(mpnews)
|
||||
:body_json: 非图文消息的内容,不同消息类型,长度限制不一样
|
||||
:safe: 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0
|
||||
:enable_id_trans: 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。
|
||||
:enable_duplicate_check: Indicates whether to enable duplicate message checking. 0 indicates no, 1 indicates yes. The default is 0
|
||||
:duplicate_check_interval: Indicates whether the message check is repeated. The default is 1800s and the maximum is no more than 4 hours
|
||||
|
||||
"""
|
||||
if not company:
|
||||
company = self.env.company
|
||||
|
||||
messages_content = self.get_messages_content(
|
||||
msgtype,
|
||||
description,
|
||||
author_id,
|
||||
body_html,
|
||||
body_json,
|
||||
body_markdown,
|
||||
subject,
|
||||
media_id,
|
||||
)
|
||||
messages_options = self.get_messages_options(
|
||||
msgtype,
|
||||
safe,
|
||||
enable_id_trans,
|
||||
enable_duplicate_check,
|
||||
duplicate_check_interval,
|
||||
)
|
||||
|
||||
messages = {
|
||||
"touser": "@all" if toall else touser,
|
||||
"toparty": "" if toall else toparty,
|
||||
"totag": "" if toall else totag,
|
||||
"msgtype": msgtype,
|
||||
"agentid": company.message_agentid,
|
||||
"company": company,
|
||||
}
|
||||
messages.update(messages_content)
|
||||
messages.update(messages_options)
|
||||
return messages
|
||||
|
||||
@api.model
|
||||
def send_message(self, message):
|
||||
"""
|
||||
发送一条企业微信消息 到多个人员
|
||||
"""
|
||||
return self.send_by_api(message)
|
||||
|
||||
@api.model
|
||||
def _send_message_batch(self, messages):
|
||||
"""
|
||||
批量模式发送企业微信消息
|
||||
"""
|
||||
params = {
|
||||
"messages": messages,
|
||||
}
|
||||
return self._wecom_message_send_api(params)
|
||||
|
||||
def get_messages_content(
|
||||
self,
|
||||
msgtype,
|
||||
description=None,
|
||||
author_id=None,
|
||||
body_html=None,
|
||||
body_json=None,
|
||||
body_markdown=None,
|
||||
subject=None,
|
||||
media_id=None,
|
||||
):
|
||||
# material_info = (
|
||||
# self.env["wecom.material"].sudo().browse(int(media_id)).read(["name"])
|
||||
# )
|
||||
|
||||
messages_content = {}
|
||||
if msgtype == "text":
|
||||
# 文本消息
|
||||
messages_content = {
|
||||
"text": body_json,
|
||||
}
|
||||
elif msgtype == "mpnews":
|
||||
# 图文消息(mpnews)
|
||||
material = (
|
||||
self.sudo()
|
||||
.env["wecom.material"]
|
||||
.search(
|
||||
[
|
||||
("id", "=", media_id),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
)
|
||||
# material_media_id = self.check_material_file_expiration(material)
|
||||
messages_content = {
|
||||
"mpnews": {
|
||||
"articles": [
|
||||
{
|
||||
"title": subject,
|
||||
"thumb_media_id": material._check_material_file_expiration(),
|
||||
"author": author_id.display_name,
|
||||
"content": body_html,
|
||||
"digest": description,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
elif msgtype == "markdown":
|
||||
# markdown消息
|
||||
messages_content = {
|
||||
"markdown": {
|
||||
"content": body_markdown,
|
||||
}
|
||||
}
|
||||
|
||||
elif msgtype == "template_card":
|
||||
# 模板卡片消息
|
||||
messages_content = {
|
||||
"template_card": json.loads(body_json, strict=False),
|
||||
}
|
||||
return messages_content
|
||||
|
||||
def get_messages_options(
|
||||
self,
|
||||
msgtype,
|
||||
safe=None,
|
||||
enable_id_trans=None,
|
||||
enable_duplicate_check=None,
|
||||
duplicate_check_interval=None,
|
||||
):
|
||||
"""[summary]
|
||||
获取企业微信消息的选项
|
||||
Args:
|
||||
msgtype ([type]): [description]
|
||||
safe ([type], optional): [description]. Defaults to None.
|
||||
enable_id_trans ([type], optional): [description]. Defaults to None.
|
||||
enable_duplicate_check ([type], optional): [description]. Defaults to None.
|
||||
duplicate_check_interval ([type], optional): [description]. Defaults to None.
|
||||
|
||||
Returns:
|
||||
[type]: [description]
|
||||
"""
|
||||
messages_options = {
|
||||
"safe": int(safe),
|
||||
"enable_id_trans": int(enable_id_trans),
|
||||
"enable_duplicate_check": int(enable_duplicate_check),
|
||||
"duplicate_check_interval": duplicate_check_interval,
|
||||
}
|
||||
|
||||
if msgtype == "markdown":
|
||||
# markdown消息
|
||||
del messages_options["safe"]
|
||||
del messages_options["enable_id_trans"]
|
||||
if msgtype == "template_card":
|
||||
# 模板卡片消息
|
||||
del messages_options["safe"]
|
||||
|
||||
return messages_options
|
||||
|
||||
# def check_material_file_expiration(self, material):
|
||||
# """[summary]
|
||||
# 检查素材文件是否过期
|
||||
# Args:
|
||||
# material ([type]): [description]
|
||||
|
||||
# Returns:
|
||||
# [type]: [description]
|
||||
# """
|
||||
# media_id = ""
|
||||
# if material.created_at:
|
||||
# # 有创建日期
|
||||
# created_time = material.created_at
|
||||
# MAX_FAIL_TIME = 3
|
||||
|
||||
# # 检查是否超过3天
|
||||
# overdue = self.env["wecom.tools"].cheeck_days_overdue(
|
||||
# created_time, MAX_FAIL_TIME
|
||||
# )
|
||||
# if overdue:
|
||||
# # 临时素材超期,重新上传
|
||||
# material.upload_media()
|
||||
# media_id = material.media_id
|
||||
# else:
|
||||
# # 无创建日期,执行第一次上传临时素材
|
||||
# material.upload_media()
|
||||
# media_id = material.media_id
|
||||
# return media_id
|
||||
@@ -1,301 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
import logging
|
||||
import base64
|
||||
import random
|
||||
import hashlib
|
||||
import time
|
||||
import struct
|
||||
from Crypto.Cipher import AES
|
||||
import xml.etree.cElementTree as ET
|
||||
import socket
|
||||
import hashlib
|
||||
import werkzeug.urls
|
||||
import werkzeug.utils
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
#########################################################################
|
||||
# Description:定义错误码含义
|
||||
#########################################################################
|
||||
WXBizMsgCrypt_OK = 0
|
||||
WXBizMsgCrypt_ValidateSignature_Error = -40001
|
||||
WXBizMsgCrypt_ParseXml_Error = -40002
|
||||
WXBizMsgCrypt_ComputeSignature_Error = -40003
|
||||
WXBizMsgCrypt_IllegalAesKey = -40004
|
||||
WXBizMsgCrypt_ValidateCorpid_Error = -40005
|
||||
WXBizMsgCrypt_EncryptAES_Error = -40006
|
||||
WXBizMsgCrypt_DecryptAES_Error = -40007
|
||||
WXBizMsgCrypt_IllegalBuffer = -40008
|
||||
WXBizMsgCrypt_EncodeBase64_Error = -40009
|
||||
WXBizMsgCrypt_DecodeBase64_Error = -40010
|
||||
WXBizMsgCrypt_GenReturnXml_Error = -40011
|
||||
|
||||
|
||||
class FormatException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def throw_exception(message, exception_class=FormatException):
|
||||
"""自定义引发异常函数"""
|
||||
raise exception_class(message)
|
||||
|
||||
|
||||
class SHA1:
|
||||
"""计算企业微信的消息签名接口"""
|
||||
|
||||
def getSHA1(self, token, timestamp, nonce, encrypt):
|
||||
"""用SHA1算法生成安全签名
|
||||
@param token: 票据
|
||||
@param timestamp: 时间戳
|
||||
@param encrypt: 密文
|
||||
@param nonce: 随机字符串
|
||||
@return: 安全签名
|
||||
"""
|
||||
try:
|
||||
sortlist = [token, timestamp, nonce, encrypt]
|
||||
sortlist.sort()
|
||||
sha = hashlib.sha1()
|
||||
sha.update("".join(sortlist).encode())
|
||||
return WXBizMsgCrypt_OK, sha.hexdigest()
|
||||
except Exception as e:
|
||||
logger = logging.getLogger()
|
||||
logger.error(e)
|
||||
return WXBizMsgCrypt_ComputeSignature_Error, None
|
||||
|
||||
|
||||
class XMLParse:
|
||||
"""提供提取消息格式中的密文及生成回复消息格式的接口"""
|
||||
|
||||
# xml消息模板
|
||||
AES_TEXT_RESPONSE_TEMPLATE = """<xml>
|
||||
<Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
|
||||
<MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature>
|
||||
<TimeStamp>%(timestamp)s</TimeStamp>
|
||||
<Nonce><![CDATA[%(nonce)s]]></Nonce>
|
||||
</xml>"""
|
||||
|
||||
def extract(self, xmltext):
|
||||
"""提取出xml数据包中的加密消息
|
||||
@param xmltext: 待提取的xml字符串
|
||||
@return: 提取出的加密消息字符串
|
||||
"""
|
||||
try:
|
||||
xml_tree = ET.fromstring(xmltext)
|
||||
encrypt = xml_tree.find("Encrypt")
|
||||
return WXBizMsgCrypt_OK, encrypt.text # type: ignore
|
||||
except Exception as e:
|
||||
logger = logging.getLogger()
|
||||
logger.error(e)
|
||||
return WXBizMsgCrypt_ParseXml_Error, None, None
|
||||
|
||||
def generate(self, encrypt, signature, timestamp, nonce):
|
||||
"""生成xml消息
|
||||
@param encrypt: 加密后的消息密文
|
||||
@param signature: 安全签名
|
||||
@param timestamp: 时间戳
|
||||
@param nonce: 随机字符串
|
||||
@return: 生成的xml字符串
|
||||
"""
|
||||
resp_dict = {
|
||||
"msg_encrypt": encrypt,
|
||||
"msg_signaturet": signature,
|
||||
"timestamp": timestamp,
|
||||
"nonce": nonce,
|
||||
}
|
||||
resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict
|
||||
return resp_xml
|
||||
|
||||
|
||||
class PKCS7Encoder:
|
||||
"""提供基于PKCS7算法的加解密接口"""
|
||||
|
||||
block_size = 32
|
||||
|
||||
def encode(self, text):
|
||||
""" 对需要加密的明文进行填充补位
|
||||
@param text: 需要进行填充补位操作的明文
|
||||
@return: 补齐明文字符串
|
||||
"""
|
||||
text_length = len(text)
|
||||
# 计算需要填充的位数
|
||||
amount_to_pad = self.block_size - (text_length % self.block_size)
|
||||
if amount_to_pad == 0:
|
||||
amount_to_pad = self.block_size
|
||||
# 获得补位所用的字符
|
||||
pad = chr(amount_to_pad)
|
||||
return text + (pad * amount_to_pad).encode()
|
||||
|
||||
def decode(self, decrypted):
|
||||
"""删除解密后明文的补位字符
|
||||
@param decrypted: 解密后的明文
|
||||
@return: 删除补位字符后的明文
|
||||
"""
|
||||
pad = ord(decrypted[-1])
|
||||
if pad < 1 or pad > 32:
|
||||
pad = 0
|
||||
return decrypted[:-pad]
|
||||
|
||||
|
||||
class Prpcrypt(object):
|
||||
"""提供接收和推送给企业微信消息的加解密接口"""
|
||||
|
||||
def __init__(self, key):
|
||||
|
||||
# self.key = base64.b64decode(key+"=")
|
||||
self.key = key
|
||||
# 设置加解密模式为AES的CBC模式
|
||||
self.mode = AES.MODE_CBC
|
||||
|
||||
def encrypt(self, text, receiveid):
|
||||
"""对明文进行加密
|
||||
@param text: 需要加密的明文
|
||||
@return: 加密得到的字符串
|
||||
"""
|
||||
# 16位随机字符串添加到明文开头
|
||||
text = text.encode()
|
||||
text = (
|
||||
self.get_random_str()
|
||||
+ struct.pack("I", socket.htonl(len(text)))
|
||||
+ text
|
||||
+ receiveid.encode()
|
||||
)
|
||||
|
||||
# 使用自定义的填充方式对明文进行补位填充
|
||||
pkcs7 = PKCS7Encoder()
|
||||
text = pkcs7.encode(text)
|
||||
# 加密
|
||||
cryptor = AES.new(self.key, self.mode, self.key[:16]) # type: ignore
|
||||
try:
|
||||
ciphertext = cryptor.encrypt(text)
|
||||
# 使用BASE64对加密后的字符串进行编码
|
||||
return WXBizMsgCrypt_OK, base64.b64encode(ciphertext)
|
||||
except Exception as e:
|
||||
logger = logging.getLogger()
|
||||
logger.error(e)
|
||||
return WXBizMsgCrypt_EncryptAES_Error, None
|
||||
|
||||
def decrypt(self, text, receiveid):
|
||||
"""对解密后的明文进行补位删除
|
||||
@param text: 密文
|
||||
@return: 删除填充补位后的明文
|
||||
"""
|
||||
try:
|
||||
cryptor = AES.new(self.key, self.mode, self.key[:16]) # type: ignore
|
||||
# 使用BASE64对密文进行解码,然后AES-CBC解密
|
||||
plain_text = cryptor.decrypt(base64.b64decode(text))
|
||||
except Exception as e:
|
||||
logger = logging.getLogger()
|
||||
logger.error(e)
|
||||
return WXBizMsgCrypt_DecryptAES_Error, None
|
||||
try:
|
||||
pad = plain_text[-1]
|
||||
# 去掉补位字符串
|
||||
# pkcs7 = PKCS7Encoder()
|
||||
# plain_text = pkcs7.encode(plain_text)
|
||||
# 去除16位随机字符串
|
||||
content = plain_text[16:-pad]
|
||||
xml_len = socket.ntohl(struct.unpack("I", content[:4])[0])
|
||||
xml_content = content[4 : xml_len + 4]
|
||||
from_receiveid = content[xml_len + 4 :]
|
||||
except Exception as e:
|
||||
logger = logging.getLogger()
|
||||
logger.error(e)
|
||||
return WXBizMsgCrypt_IllegalBuffer, None
|
||||
|
||||
if from_receiveid.decode("utf8") != receiveid:
|
||||
return WXBizMsgCrypt_ValidateCorpid_Error, None
|
||||
return 0, xml_content
|
||||
|
||||
def get_random_str(self):
|
||||
""" 随机生成16位字符串
|
||||
@return: 16位字符串
|
||||
"""
|
||||
return str(random.randint(1000000000000000, 9999999999999999)).encode()
|
||||
|
||||
|
||||
class WecomMsgCrypt(object):
|
||||
# 消息加解密
|
||||
# _name = "wecom.msg_crypt"
|
||||
# _description = "Wecom Message encryption and decryption"
|
||||
|
||||
def __init__(self, sToken, sEncodingAESKey, sReceiveId):
|
||||
"""
|
||||
初始化消息加解密对象
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
self.key = base64.b64decode(sEncodingAESKey + "=")
|
||||
assert len(self.key) == 32
|
||||
except:
|
||||
throw_exception("[error]: EncodingAESKey unvalid !", FormatException)
|
||||
# return WXBizMsgCrypt_IllegalAesKey,None
|
||||
self.m_sToken = sToken
|
||||
self.m_sReceiveId = sReceiveId
|
||||
|
||||
def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr):
|
||||
"""验证URL
|
||||
@param sMsgSignature: 签名串,对应URL参数的msg_signature
|
||||
@param sTimeStamp: 时间戳,对应URL参数的timestamp
|
||||
@param sNonce: 随机串,对应URL参数的nonce
|
||||
@param sEchoStr: 随机串,对应URL参数的echostr
|
||||
@param sReplyEchoStr: 解密之后的echostr,当return返回0时有效
|
||||
@return: 成功0,失败返回对应的错误码
|
||||
"""
|
||||
sha1 = SHA1()
|
||||
ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, sEchoStr)
|
||||
if ret != 0:
|
||||
return ret, None
|
||||
if not signature == sMsgSignature:
|
||||
return WXBizMsgCrypt_ValidateSignature_Error, None
|
||||
pc = Prpcrypt(self.key)
|
||||
ret, sReplyEchoStr = pc.decrypt(sEchoStr, self.m_sReceiveId)
|
||||
return ret, sReplyEchoStr
|
||||
|
||||
def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
|
||||
"""将企业回复用户的消息加密打包
|
||||
@param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
|
||||
@param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间
|
||||
@param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
|
||||
sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
|
||||
return:成功0,sEncryptMsg,失败返回对应的错误码None
|
||||
"""
|
||||
pc = Prpcrypt(self.key)
|
||||
ret, encrypt = pc.encrypt(sReplyMsg, self.m_sReceiveId)
|
||||
encrypt = encrypt.decode("utf8") # type: ignore
|
||||
if ret != 0:
|
||||
return ret, None
|
||||
if timestamp is None:
|
||||
timestamp = str(int(time.time()))
|
||||
# 生成安全签名
|
||||
sha1 = SHA1()
|
||||
ret, signature = sha1.getSHA1(self.m_sToken, timestamp, sNonce, encrypt)
|
||||
if ret != 0:
|
||||
return ret, None
|
||||
xmlParse = XMLParse()
|
||||
return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)
|
||||
|
||||
def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):
|
||||
"""检验消息的真实性,并且获取解密后的明文
|
||||
# @param sMsgSignature: 签名串,对应URL参数的msg_signature
|
||||
# @param sTimeStamp: 时间戳,对应URL参数的timestamp
|
||||
# @param sNonce: 随机串,对应URL参数的nonce
|
||||
# @param sPostData: 密文,对应POST请求的数据
|
||||
# xml_content: 解密后的原文,当return返回0时有效
|
||||
# @return: 成功0,失败返回对应的错误码
|
||||
# 验证安全签名
|
||||
"""
|
||||
xmlParse = XMLParse()
|
||||
ret, encrypt = xmlParse.extract(sPostData) # type: ignore
|
||||
if ret != 0:
|
||||
return ret, None
|
||||
sha1 = SHA1()
|
||||
ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, encrypt)
|
||||
if ret != 0:
|
||||
return ret, None
|
||||
if not signature == sMsgSignature:
|
||||
return WXBizMsgCrypt_ValidateSignature_Error, None
|
||||
pc = Prpcrypt(self.key)
|
||||
ret, xml_content = pc.decrypt(encrypt, self.m_sReceiveId)
|
||||
return ret, xml_content
|
||||
@@ -1,100 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import requests
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
|
||||
# from .wecom_abstract_api import ApiException
|
||||
|
||||
|
||||
class WecomServerApi(models.TransientModel):
|
||||
_inherit = "wecom.abstract_api"
|
||||
_name = "wecom.service_api"
|
||||
_description = "Wecom Server API"
|
||||
_table = "wecom_service_api"
|
||||
|
||||
corpid = fields.Char(string="Corp Id", readonly=True)
|
||||
secret = fields.Char(string="Secret", readonly=True)
|
||||
access_token = fields.Char(string="Access Token", readonly=True, default=None)
|
||||
token_expiration_time = fields.Datetime(
|
||||
string="Token Expiration Time", readonly=True
|
||||
)
|
||||
|
||||
@api.model
|
||||
def InitServiceApi(self, corpid, secret):
|
||||
"""
|
||||
初始化企业微信API 对象
|
||||
:param corpid : 企业ID
|
||||
:param secret : 应用密钥
|
||||
:returns 模型"wecom.service_api"对象
|
||||
"""
|
||||
api = self.search(
|
||||
[
|
||||
("corpid", "=", corpid),
|
||||
("secret", "=", secret),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
if not api:
|
||||
# 创建API令牌记录
|
||||
api = self.sudo().create(
|
||||
{
|
||||
"corpid": corpid,
|
||||
"secret": secret,
|
||||
}
|
||||
)
|
||||
if api["access_token"] is False or api["access_token"] == "": # type: ignore
|
||||
# token为空,刷新API令牌记录
|
||||
api.refreshAccessToken() # type: ignore
|
||||
if (
|
||||
api["token_expiration_time"] is False # type: ignore
|
||||
or api["token_expiration_time"] < datetime.now() # type: ignore
|
||||
):
|
||||
# token过期,刷新API令牌记录
|
||||
api.refreshAccessToken() # type: ignore
|
||||
|
||||
return api
|
||||
|
||||
def getAccessToken(self):
|
||||
"""
|
||||
获取令牌
|
||||
"""
|
||||
if self.access_token is None:
|
||||
self.refreshAccessToken()
|
||||
return self.access_token
|
||||
|
||||
def refreshAccessToken(self):
|
||||
"""
|
||||
刷新模型 'wecom.service_api' 的令牌
|
||||
同时刷新发起请求的模型 'wecom.apps' 的令牌
|
||||
"""
|
||||
response = self.httpCall( # type: ignore
|
||||
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 = {
|
||||
"access_token": response.get("access_token"),
|
||||
"token_expiration_time": datetime.now() + expiration_second,
|
||||
}
|
||||
|
||||
self.sudo().write(dict)
|
||||
|
||||
# 写对应的应用的值
|
||||
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.sudo().write(dict)
|
||||
|
||||
def get_api_debug(self):
|
||||
"""
|
||||
获取 API 调试模式
|
||||
"""
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
return True if ir_config.get_param("wecom.debug_enabled") == "True" else False
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import main
|
||||
@@ -1,131 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import logging
|
||||
import xml.etree.cElementTree as ET
|
||||
from lxml import etree
|
||||
import sys
|
||||
from odoo.addons.wecom_api.api.wecom_msg_crtpt import WecomMsgCrypt # type: ignore
|
||||
from odoo import http, models, fields, _
|
||||
from odoo.http import request
|
||||
from odoo.http import Response
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StripeController(http.Controller):
|
||||
""" """
|
||||
|
||||
@http.route(
|
||||
["/wecom_callback", "/wecom_callback/"],
|
||||
type="http",
|
||||
auth="public",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
def WecomCallbackService(self): # type: ignore
|
||||
"""
|
||||
企业微信回调服务
|
||||
"""
|
||||
|
||||
@http.route(
|
||||
["/wecom_callback/<int:id>/<string:service>", "/wecom_callback/contacts"],
|
||||
type="http",
|
||||
auth="public",
|
||||
methods=["GET", "POST"],
|
||||
csrf=False,
|
||||
)
|
||||
def WecomCallbackService(self, service, id, **kw):
|
||||
"""
|
||||
企业微信回调服务
|
||||
:param id: 公司id
|
||||
:param service: 回调服务名称 code
|
||||
文档URL: https://work.weixin.qq.com/api/doc/90000/90135/90930#3.2%20%E6%94%AF%E6%8C%81Http%20Post%E8%AF%B7%E6%B1%82%E6%8E%A5%E6%94%B6%E4%B8%9A%E5%8A%A1%E6%95%B0%E6%8D%AE
|
||||
"""
|
||||
company_id = request.env["res.company"].sudo().search([("id", "=", id)]) # type: ignore
|
||||
sCorpID = company_id.corpid
|
||||
|
||||
callback_service = (
|
||||
company_id.contacts_app_id.app_callback_service_ids.sudo().search(
|
||||
[
|
||||
("app_id", "=", company_id.contacts_app_id.id),
|
||||
("code", "=", service),
|
||||
"|",
|
||||
("active", "=", True),
|
||||
("active", "=", False),
|
||||
]
|
||||
)
|
||||
)
|
||||
if callback_service.active is False:
|
||||
_logger.info(
|
||||
_("App [%s] does not have service [%s] enabled")
|
||||
% (company_id.contacts_app_id.name, callback_service.name)
|
||||
)
|
||||
return Response("success", status=200)
|
||||
else:
|
||||
wxcpt = WecomMsgCrypt(
|
||||
callback_service.callback_url_token,
|
||||
callback_service.callback_aeskey,
|
||||
sCorpID,
|
||||
)
|
||||
|
||||
# 获取企业微信发送的相关参数
|
||||
sVerifyMsgSig = kw["msg_signature"]
|
||||
sVerifyTimeStamp = kw["timestamp"]
|
||||
sVerifyNonce = kw["nonce"]
|
||||
|
||||
if request.httprequest.method == "GET": # type: ignore
|
||||
# ^ 假设企业的接收消息的URL设置为http://api.3dept.com。
|
||||
# ^ 企业管理员在保存回调配置信息时,企业微信会发送一条验证消息到填写的URL,请求内容如下:
|
||||
# ^ 请求方式:GET
|
||||
# ^ http://api.3dept.com/?msg_signature=ASDFQWEXZCVAQFASDFASDFSS×tamp=13500001234&nonce=123412323&echostr=ENCRYPT_STR
|
||||
# ^ 请求参数:
|
||||
# ^ msg_signature: 企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。
|
||||
# ^ timestamp: 时间戳。与nonce结合使用,用于防止请求重放攻击。
|
||||
# ^ nonce: 随机数。用于保证签名不可预测。与timestamp结合使用,用于防止请求重放攻击。
|
||||
# ^ echostr: 加密的随机字符串。用于保证签名不可预测。
|
||||
sVerifyEchoStr = kw["echostr"]
|
||||
ret, msg = wxcpt.VerifyURL(
|
||||
sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, sVerifyEchoStr
|
||||
)
|
||||
if ret != 0:
|
||||
logging.error("ERR: VerifyURL ret: " + str(ret))
|
||||
sys.exit(1)
|
||||
return msg
|
||||
|
||||
if request.httprequest.method == "POST": # type: ignore
|
||||
# ^ 假设企业的接收消息的URL设置为http://api.3dept.com。
|
||||
# ^ 当用户触发回调行为时,企业微信会发送回调消息到填写的URL,请求内容如下:
|
||||
# ^ 请求方式:POST
|
||||
# ^ 请求地址 :http://api.3dept.com/?msg_signature=ASDFQWEXZCVAQFASDFASDFSS×tamp=13500001234&nonce=123412323
|
||||
# ^ 请求参数:
|
||||
# ^ msg_signature: 企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。
|
||||
# ^ timestamp: 时间戳。与nonce结合使用,用于防止请求重放攻击。
|
||||
# ^ nonce: 随机数。用于保证签名不可预测。与timestamp结合使用,用于防止请求重放攻击。
|
||||
# ^ ToUserName: 企业微信的CorpID,当为第三方应用回调事件时,CorpID的内容为suiteid
|
||||
# ^ AgentID: 接收的应用id,可在应用的设置页面获取。仅应用相关的回调会带该字段。
|
||||
# ^ Encrypt: 消息结构体加密后的字符串
|
||||
sReqData = request.httprequest.data # type: ignore
|
||||
ret, msg = wxcpt.DecryptMsg(
|
||||
sReqData, sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce
|
||||
)
|
||||
if ret != 0:
|
||||
logging.error("ERR: DecryptMsg ret: " + str(ret))
|
||||
sys.exit(1)
|
||||
# 解密成功,msg即明文的xml消息结构体
|
||||
# xml_tree = etree.fromstring(msg) # xml解析为一个xml元素
|
||||
# print("解密成功", msg)
|
||||
try:
|
||||
return (
|
||||
request.env["wecom.app.event_type"] # type: ignore
|
||||
.sudo()
|
||||
.with_context(xml_tree=msg, company_id=company_id)
|
||||
.handle_event()
|
||||
) # 传递xml元素和公司
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
# ^ 正确响应企业微信本次的POST请求,企业微信将不会再次发送请求
|
||||
# ^ ·企业微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
|
||||
# ^ ·当接收成功后,http头部返回200表示接收ok,其他错误码企业微信后台会一律当做失败并发起重试
|
||||
# return Response("success", status=200)
|
||||
pass
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
</data>
|
||||
<data noupdate="1">
|
||||
|
||||
<record model="ir.config_parameter" id="wecom_global_error_code_url">
|
||||
<field name="key">wecom.global_error_code_url</field>
|
||||
<field name="value">https://developer.work.weixin.qq.com/document/path/90313</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- !2022-10-03更新 -->
|
||||
<!-- ! 用于定位 错误代码 的路径表达式-->
|
||||
<record model="ir.config_parameter" id="global_error_code_item_selection_code">
|
||||
<field name="key">wecom.global_error_code_item_selection_code</field>
|
||||
<field name="value">//h5</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,98 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ----------------------------------------
|
||||
# 使用爬虫爬取微信错误码 生成 XML 数据文件
|
||||
# ----------------------------------------
|
||||
import os
|
||||
from io import BytesIO
|
||||
import requests
|
||||
from lxml import etree
|
||||
|
||||
|
||||
nodes = "//table/tbody/tr"
|
||||
url = "https://developer.work.weixin.qq.com/document/path/90313"
|
||||
page_text = requests.get(url=url).text
|
||||
tree = etree.HTML(page_text) # type: ignore
|
||||
lines = tree.xpath(nodes)
|
||||
|
||||
|
||||
# 输出一个错误代码列表
|
||||
codes = []
|
||||
for line in lines:
|
||||
code = line.xpath("./td[1]/text()")[0]
|
||||
try:
|
||||
name = line.xpath("./td[2]/text()")[0]
|
||||
except:
|
||||
name = line.xpath("./td[3]/text()")[0]
|
||||
# print(name)
|
||||
|
||||
# xml 转义
|
||||
if '"' in name:
|
||||
name.replace('"', """)
|
||||
if "&" in name:
|
||||
name.replace('"', "&")
|
||||
if "<" in name:
|
||||
name.replace('"', "<")
|
||||
if "<" in name:
|
||||
name.replace(">", ">")
|
||||
|
||||
codes.append(
|
||||
{
|
||||
"code": int(code),
|
||||
"name": name,
|
||||
}
|
||||
)
|
||||
|
||||
# 排序
|
||||
new_codes = sorted(codes, key=lambda c: int(c["code"]))
|
||||
|
||||
# ----------------------------
|
||||
# https://lxml.de/tutorial.html
|
||||
# ----------------------------
|
||||
# 生成生成根节点
|
||||
root = etree.Element("odoo") # type: ignore
|
||||
|
||||
# 生成 data 节点
|
||||
data_node = etree.Element("data", attrib={"noupdate": "0"}) # type: ignore
|
||||
root.append(data_node)
|
||||
|
||||
for code in new_codes:
|
||||
id = "wechat_error_codes_"
|
||||
if code == -1:
|
||||
id += "minus_1"
|
||||
else:
|
||||
id += str(code["code"])
|
||||
|
||||
record_attrs = {
|
||||
"id": id,
|
||||
"model": "wechat.error_codes",
|
||||
}
|
||||
record_node = etree.Element("record", attrib=record_attrs) # type: ignore
|
||||
|
||||
code_field_node = etree.Element("field", attrib={"name": "code"}) # type: ignore
|
||||
code_field_node.text = str(code["code"])
|
||||
name_field_node = etree.Element("field", attrib={"name": "name"}) # type: ignore
|
||||
name_field_node.text = code["name"]
|
||||
record_node.append(code_field_node)
|
||||
record_node.append(name_field_node)
|
||||
data_node.append(record_node)
|
||||
|
||||
# 保存xml
|
||||
xml = (
|
||||
"""<?xml version="1.0" encoding="utf-8"?>
|
||||
%s
|
||||
"""
|
||||
% etree.tostring(root, pretty_print=True).decode()
|
||||
)
|
||||
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
xml_file_name = "reptile.xml"
|
||||
xml_file_path = os.path.join(current_dir, xml_file_name)
|
||||
|
||||
if not os.path.exists(xml_file_path):
|
||||
os.system(r"touch {}".format(xml_file_path)) # 调用系统命令行来创建文件
|
||||
|
||||
with open(xml_file_path, "w", encoding="gb2312") as f:
|
||||
f.write(xml)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<data noupdate="0">
|
||||
|
||||
<record id="wecom_server_api_error_python_code" model="wecom.service_api_error">
|
||||
<field name="name">Python Code Error</field>
|
||||
<field name="code">-2</field>
|
||||
<field name="method">Python Code Error,Please check your code for errors. Python代码错误,请检查您的代码是否有错误</field>
|
||||
<field name="sequence">0</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
</odoo>
|
||||
@@ -1,371 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * wecom_api
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0beta\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-10-04 07:34+0000\n"
|
||||
"PO-Revision-Date: 2022-10-04 15:35+0800\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: \n"
|
||||
"X-Generator: Poedit 3.1.1\n"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_abstract_api__code
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__code
|
||||
msgid "API error code"
|
||||
msgstr "API错误代码"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_abstract_api__message
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__message
|
||||
msgid "API error message"
|
||||
msgstr "API错误消息"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__access_token
|
||||
msgid "Access Token"
|
||||
msgstr "访问令牌"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__request_type
|
||||
msgid "Api Request Type"
|
||||
msgstr "Api请求类型"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__type
|
||||
msgid "Api Type"
|
||||
msgstr "Api类型"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "App [%s] does not have service [%s] enabled"
|
||||
msgstr "应用 [%s] 未启用服务 [%s]"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__6
|
||||
msgid "Application management"
|
||||
msgstr "应用管理"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__1
|
||||
msgid "Base"
|
||||
msgstr "基础"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__2
|
||||
msgid "Contacts"
|
||||
msgstr "通讯录管理"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__13
|
||||
msgid "Corp Chain"
|
||||
msgstr "上下游(公司链)"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__12
|
||||
msgid "Corp Group"
|
||||
msgstr "企业互联"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__corpid
|
||||
msgid "Corp Id"
|
||||
msgstr "企业ID"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__create_uid
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__create_uid
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__create_uid
|
||||
msgid "Created by"
|
||||
msgstr "创建者"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__create_date
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__create_date
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__create_date
|
||||
msgid "Created on"
|
||||
msgstr "创建于"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__3
|
||||
msgid "Customer contact"
|
||||
msgstr "客户联系"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__description
|
||||
msgid "Description"
|
||||
msgstr "说明"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__display_name
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__display_name
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "显示名称"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__10
|
||||
msgid "Efficiency tools"
|
||||
msgstr "效率工具"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__15
|
||||
msgid "Electronic invoice"
|
||||
msgstr "电子发票"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__code
|
||||
msgid "Error code"
|
||||
msgstr "错误代码"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/tools/action.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Error code: %s,Error description: %s,Error troubleshooting method: %s,Error "
|
||||
"Details:%s"
|
||||
msgstr "错误代码:%s,错误描述:%s,故障排除方法:%s,出错详细信息:%s"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__name
|
||||
msgid "Error description"
|
||||
msgstr "错误说明"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/api/wecom_message_api.py:0
|
||||
#, python-format
|
||||
msgid "Error sending message: %s"
|
||||
msgstr "发送消息时出错:%s"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__token_expiration_time
|
||||
msgid "Token Expiration Time"
|
||||
msgstr "Token过期时间"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/tools/action.py:0
|
||||
#, python-format
|
||||
msgid "Failed"
|
||||
msgstr "失败"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/models/wecom_server_api_error.py:0
|
||||
#, python-format
|
||||
msgid "Failed to pull WeCom global error code, reason:%s"
|
||||
msgstr "无法拉取企微全局错误代码,原因:%s"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__request_type__get
|
||||
msgid "GET"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__id
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__id
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__5
|
||||
msgid "Identity authentication"
|
||||
msgstr "身份验证"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api____last_update
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error____last_update
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "最后修改"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__write_uid
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__write_uid
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "最后更新者"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__write_date
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__write_date
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "最后更新于"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__8
|
||||
msgid "Media material"
|
||||
msgstr "素材管理"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__7
|
||||
msgid "Message push"
|
||||
msgstr "消息推送"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__9
|
||||
msgid "OA"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__request_type__post
|
||||
msgid "POST"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__18
|
||||
msgid "Report"
|
||||
msgstr "政民沟通"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__function_name
|
||||
msgid "Request Function Name"
|
||||
msgstr "请求函数名称"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__name
|
||||
msgid "Request Name"
|
||||
msgstr "请求名称"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__short_url
|
||||
msgid "Request Short Url"
|
||||
msgstr "请求短Url"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__16
|
||||
msgid "School"
|
||||
msgstr "家校沟通"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__17
|
||||
msgid "School Apps"
|
||||
msgstr "家校应用"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api__secret
|
||||
msgid "Secret"
|
||||
msgstr "密钥"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__sequence
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_list__sequence
|
||||
msgid "Sequence"
|
||||
msgstr "序列"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__14
|
||||
msgid "Session content archiving"
|
||||
msgstr "会话内容存档"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/models/wecom_server_api_error.py:0
|
||||
#, python-format
|
||||
msgid "Start pulling the global error code of WeCom."
|
||||
msgstr "开始拉取企微全局错误代码。"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/models/wecom_server_api_error.py:0
|
||||
#, python-format
|
||||
msgid "Successfully pulled the WeCom global error code!"
|
||||
msgstr "已成功拉取企微全局错误代码!"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.constraint,message:wecom_api.constraint_wecom_service_api_list_function_name_uniq
|
||||
msgid "The function is unique !"
|
||||
msgstr "功能是唯一的!"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields,field_description:wecom_api.field_wecom_service_api_error__method
|
||||
msgid "Treatment method"
|
||||
msgstr "排查方法"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecom_message_api
|
||||
msgid "WeCom Message API"
|
||||
msgstr "企微消息API"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__4
|
||||
msgid "Wechat customer service"
|
||||
msgstr "微信客服"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecomapi_tools_action
|
||||
msgid "Wecom API Tools - Action"
|
||||
msgstr "企微API工具-动作"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecomapi_tools_data
|
||||
msgid "Wecom API Tools - Data"
|
||||
msgstr "企微API工具-数据"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecomapi_tools_datetime
|
||||
msgid "Wecom API Tools - Datetime"
|
||||
msgstr "企微API工具-日期时间"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecomapi_tools_file
|
||||
msgid "Wecom API Tools - File"
|
||||
msgstr "企微API工具-文件"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecomapi_tools_message
|
||||
msgid "Wecom API Tools - Message"
|
||||
msgstr "企微API工具-消息"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecomapi_tools_security
|
||||
msgid "Wecom API Tools - Security"
|
||||
msgstr "企微API工具-安全"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/tools/action.py:0
|
||||
#, python-format
|
||||
msgid "Wecom API error: %s, error name: %s, error message: %s"
|
||||
msgstr "企微API错误: %s, 错误名称: %s, 错误消息: %s"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecom_abstract_api
|
||||
msgid "Wecom Abstract API"
|
||||
msgstr "企微抽象API"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecom_service_api
|
||||
msgid "Wecom Server API"
|
||||
msgstr "企微服务端API"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecom_service_api_error
|
||||
msgid "Wecom Server API Error"
|
||||
msgstr "企微服务端API错误"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecom_service_api_list
|
||||
msgid "Wecom Server API List"
|
||||
msgstr "企微服务端API列表"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model,name:wecom_api.model_wecom_tools
|
||||
msgid "Wecom Tools"
|
||||
msgstr "企微工具"
|
||||
|
||||
#. module: wecom_api
|
||||
#: model:ir.model.fields.selection,name:wecom_api.selection__wecom_service_api_list__type__11
|
||||
msgid "externalpay"
|
||||
msgstr "企业支付"
|
||||
|
||||
#. module: wecom_api
|
||||
#: code:addons/wecom_api/api/wecom_abstract_api.py:0
|
||||
#, python-format
|
||||
msgid "unknown method type"
|
||||
msgstr "未知方法类型"
|
||||
@@ -1,5 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from . import wecom_server_api_error
|
||||
from . import wecom_server_api_list
|
||||
@@ -1,8 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import requests
|
||||
import pandas as pd
|
||||
pd.set_option('display.max_columns', 10)
|
||||
pd.set_option("max_colwidth", 200)
|
||||
pd.set_option("display.width", 200)
|
||||
pd.set_option('display.max_rows', 200)
|
||||
from lxml import etree
|
||||
|
||||
|
||||
url = "https://developer.work.weixin.qq.com/document/path/90313"
|
||||
anchor = "//h2[@data-sign='12309549435022fd54b0549f5968b4c2']"
|
||||
codes = "//h5"
|
||||
methods ="//p[@data-type='p']"
|
||||
|
||||
page_text = requests.get(url=url).text
|
||||
tree = etree.HTML(page_text) # type: ignore
|
||||
|
||||
anchor_elemen = tree.xpath(anchor)
|
||||
codes_elements = tree.xpath(codes)
|
||||
methods_elements = tree.xpath(methods)
|
||||
|
||||
methods = []
|
||||
for code_element in codes_elements:
|
||||
code_element_str = code_element.xpath("text()")[0]
|
||||
error_code = code_element_str.split(":", 1)[1:][0]
|
||||
method_element = code_element.getnext()
|
||||
method = etree.tostring(method_element, encoding="utf-8", pretty_print=True ).decode() # type: ignore
|
||||
|
||||
if " " in error_code:
|
||||
# 一个元素存在多个错误码
|
||||
multiple_codes = error_code.split(" ", 1)
|
||||
for multiple_code in multiple_codes:
|
||||
multiple_dic = {}
|
||||
multiple_dic["code"] = multiple_code
|
||||
multiple_dic["method"] = method
|
||||
methods.append(multiple_dic)
|
||||
else:
|
||||
dic = {}
|
||||
dic["code"] = error_code
|
||||
dic["method"] = method
|
||||
methods.append(dic)
|
||||
|
||||
table = tree.xpath("//div[@class='cherry-table-container']/table") # 取出表格
|
||||
table = etree.tostring(
|
||||
table[0], encoding="utf-8" # type: ignore
|
||||
).decode() # 将第一个表格转成string格式
|
||||
table = table.replace("<th>错误码</th>", "<th>code</th>")
|
||||
table = table.replace("<th>错误说明</th>", "<th>name</th>")
|
||||
table = table.replace("<th>排查方法</th>", "<th>method</th>")
|
||||
|
||||
df = pd.read_html(table, encoding="utf-8", header=0)[0] # pandas读取table
|
||||
if 'Unnamed: 3' in df.columns:
|
||||
del df['Unnamed: 3']
|
||||
error_results = list(df.T.to_dict().values()) # 转换成列表嵌套字典的格式
|
||||
|
||||
errors = []
|
||||
for index, error in enumerate(error_results):
|
||||
error["sequence"] = index
|
||||
|
||||
errors.append(error)
|
||||
df = pd.DataFrame(errors)
|
||||
print(df)
|
||||
@@ -1,175 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import operator
|
||||
import requests
|
||||
import logging
|
||||
from urllib.parse import quote, unquote
|
||||
import pandas as pd
|
||||
|
||||
# pd.set_option("max_colwidth", 4096)
|
||||
# pd.set_option("max_colwidth", 10)
|
||||
|
||||
from lxml import etree
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WecomServerApiError(models.Model):
|
||||
_name = "wecom.service_api_error"
|
||||
_description = "Wecom Server API Error"
|
||||
_order = "sequence"
|
||||
|
||||
name = fields.Char(
|
||||
"Error description",
|
||||
required=True,
|
||||
readonly=True,
|
||||
)
|
||||
code = fields.Integer(
|
||||
"Error code",
|
||||
required=True,
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
method = fields.Html(
|
||||
"Treatment method",
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
sequence = fields.Integer(default=0)
|
||||
|
||||
def get_error_by_code(self, code):
|
||||
res = self.search(
|
||||
[("code", "=", code)],
|
||||
limit=1,
|
||||
)
|
||||
return {
|
||||
"code": res.code, # type: ignore
|
||||
"name": res.name, # type: ignore
|
||||
"method": res.method, # type: ignore
|
||||
}
|
||||
|
||||
def cron_pull_global_error_code(self):
|
||||
self.get_api_error_eode()
|
||||
|
||||
@api.model
|
||||
def get_api_error_eode(self):
|
||||
"""
|
||||
使用爬虫爬取 全局错误码
|
||||
URL的一般格式为: protocol://hostname[:port]/path/[;parameters][?query]#fragment
|
||||
"""
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
url = ir_config.get_param("wecom.global_error_code_url")
|
||||
|
||||
codes = ir_config.get_param("wecom.global_error_code_item_selection_code")
|
||||
|
||||
state = False
|
||||
msg= ""
|
||||
try:
|
||||
_logger.info(_("Start pulling the global error code of WeCom."))
|
||||
page_text = requests.get(url=url).text
|
||||
tree = etree.HTML(page_text) # type: ignore
|
||||
|
||||
codes_elements = tree.xpath(codes)
|
||||
|
||||
methods = []
|
||||
|
||||
for code_element in codes_elements:
|
||||
code_element_str = code_element.xpath("text()")[0]
|
||||
error_code = code_element_str.split(":", 1)[1:][0]
|
||||
|
||||
method_element = code_element.getnext()
|
||||
method = etree.tostring(method_element, encoding="utf-8", pretty_print=True ).decode() # type: ignore
|
||||
|
||||
if " " in error_code:
|
||||
# 一个元素存在多个错误码
|
||||
multiple_codes = error_code.split(" ", 1)
|
||||
for multiple_code in multiple_codes:
|
||||
multiple_dic = {}
|
||||
multiple_dic["code"] = multiple_code
|
||||
multiple_dic["method"] = method
|
||||
methods.append(multiple_dic)
|
||||
else:
|
||||
dic = {}
|
||||
dic["code"] = error_code
|
||||
dic["method"] = method
|
||||
methods.append(dic)
|
||||
|
||||
table = tree.xpath("//div[@class='cherry-table-container']/table") # 取出表格
|
||||
table = etree.tostring(
|
||||
table[0], encoding="utf-8" # type: ignore
|
||||
).decode() # 将第一个表格转成string格式
|
||||
table = table.replace("<th>错误码</th>", "<th>code</th>")
|
||||
table = table.replace("<th>错误说明</th>", "<th>name</th>")
|
||||
table = table.replace("<th>排查方法</th>", "<th>method</th>")
|
||||
|
||||
df = pd.read_html(table, encoding="utf-8", header=0)[0] # pandas读取table
|
||||
if 'Unnamed: 3' in df.columns:
|
||||
del df['Unnamed: 3']
|
||||
|
||||
error_results = list(df.T.to_dict().values()) # 转换成列表嵌套字典的格式
|
||||
|
||||
errors = []
|
||||
for index, error in enumerate(error_results):
|
||||
if (error["code"] == 40054) or (error["code"] == 40055):
|
||||
error["method"] = self.replaceMethod(str(error["code"]), methods)
|
||||
elif error["method"] == "查看帮助":
|
||||
error["method"] = self.replaceMethod(str(error["code"]), methods)
|
||||
error["sequence"] = index
|
||||
errors.append(error)
|
||||
|
||||
except Exception as e:
|
||||
msg = _("Failed to pull WeCom global error code, reason:%s") % str(e)
|
||||
_logger.warning(msg)
|
||||
state = False
|
||||
else:
|
||||
# 写入到odoo
|
||||
for error in errors:
|
||||
res = self.search(
|
||||
[("code", "=", error["code"])],
|
||||
limit=1,
|
||||
)
|
||||
if not res:
|
||||
self.sudo().create(
|
||||
{
|
||||
"code": error["code"],
|
||||
"name": error["name"],
|
||||
"method": error["method"],
|
||||
"sequence": error["sequence"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
res.sudo().write( # type: ignore
|
||||
{
|
||||
"name": error["name"],
|
||||
"method": error["method"],
|
||||
"sequence": error["sequence"],
|
||||
}
|
||||
)
|
||||
state = True
|
||||
msg = _("Successfully pulled the WeCom global error code!")
|
||||
_logger.info(msg)
|
||||
|
||||
finally:
|
||||
return {"state": state, "msg": msg}
|
||||
|
||||
def replaceMethod(self, code, methods):
|
||||
"""
|
||||
替换 排查方法
|
||||
"""
|
||||
df = pd.DataFrame(methods)
|
||||
method = df["method"][df["code"] == code].to_string(
|
||||
index=False
|
||||
) # 取 包含指定code 值的 "method"列
|
||||
|
||||
if method[-2:] == "\\n":
|
||||
method = method[:-2] # 去掉最后的换行符
|
||||
|
||||
return method
|
||||
|
||||
def getMiddleStr(self, content, startStr, endStr):
|
||||
startIndex = content.index(startStr)
|
||||
if startIndex >= 0:
|
||||
startIndex += len(startStr)
|
||||
endIndex = content.index(endStr)
|
||||
return content[startIndex:endIndex]
|
||||
@@ -1,67 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
|
||||
|
||||
class WecomServerApiList(models.Model):
|
||||
_name = "wecom.service_api_list"
|
||||
_description = "Wecom Server API List"
|
||||
_order = "sequence,type"
|
||||
|
||||
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"), # 家校应用
|
||||
("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,)
|
||||
|
||||
short_url = fields.Char("Request Short Url", required=True,)
|
||||
|
||||
request_type = fields.Selection(
|
||||
[("GET", "GET"), ("POST", "POST"),],
|
||||
string="Api Request Type",
|
||||
required=True,
|
||||
default="GET",
|
||||
)
|
||||
description = fields.Html(string="Description")
|
||||
sequence = fields.Integer(default=0)
|
||||
|
||||
_sql_constraints = [
|
||||
("function_name_uniq", "unique (function_name)", "The function is unique !",),
|
||||
]
|
||||
|
||||
def get_server_api_call(self, function_name):
|
||||
"""
|
||||
根据函数名称获取 企业微信API的路由和请求方式
|
||||
:param function_name : 函数名称
|
||||
:returns 企业微信API的路由和请求方式的集合
|
||||
"""
|
||||
# ['/cgi-bin/gettoken', 'GET']
|
||||
data = []
|
||||
res = self.search([("function_name", "=", function_name)], limit=1,)
|
||||
data.append(res.short_url) # type: ignore
|
||||
data.append(res.request_type) # type: ignore
|
||||
return data
|
||||
@@ -1,7 +0,0 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
|
||||
wecom_service_api_access_right,access.wecom.service_api,model_wecom_service_api,base.group_erp_manager,1,1,1,1
|
||||
wecom_service_api_access_right_user,access.wecom.service_api,model_wecom_service_api,base.group_user,1,0,0,0
|
||||
|
||||
wecom_service_api_error_access_right,access.wecom.service_api_error,model_wecom_service_api_error,base.group_erp_manager,1,1,1,1
|
||||
wecom_service_api_error_access_right_user,access.wecom.service_api_error,model_wecom_service_api_error,base.group_user,1,0,0,0
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 9.9 KiB |
@@ -1,15 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from . import action
|
||||
from . import file
|
||||
from . import string
|
||||
from . import datetime
|
||||
from . import message
|
||||
from . import security
|
||||
from . import data
|
||||
from . import convert
|
||||
from . import dictionary
|
||||
|
||||
|
||||
from . import wx_tools
|
||||
@@ -1,125 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, _
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WecomApiToolsAction(models.AbstractModel):
|
||||
_name = "wecomapi.tools.action"
|
||||
_description = "Wecom API Tools - Action"
|
||||
|
||||
def ApiExceptionDialog(
|
||||
self,
|
||||
ex,
|
||||
raise_exception=False,
|
||||
):
|
||||
"""
|
||||
API 错误弹框
|
||||
"""
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
debug = ir_config.get_param("wecom.debug_enabled")
|
||||
error = self.env["wecom.service_api_error"].get_error_by_code(ex.errCode)
|
||||
|
||||
if debug:
|
||||
_logger.warning(
|
||||
_("Wecom API error: %s, error name: %s, error message: %s")
|
||||
% (str(ex.errCode), error["name"], ex.errMsg)
|
||||
)
|
||||
if raise_exception:
|
||||
params = {
|
||||
"title": _("Failed"),
|
||||
"message": _(
|
||||
"Error code: %s,Error description: %s,Error troubleshooting method: %s,Error Details:%s"
|
||||
)
|
||||
% (
|
||||
str(ex.errCode),
|
||||
str(error["name"]),
|
||||
error["method"],
|
||||
ex.errMsg,
|
||||
),
|
||||
"size": "md", # medium
|
||||
}
|
||||
action = {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "api_error_dialog",
|
||||
"params": {
|
||||
"title": params["title"],
|
||||
"size": params["size"],
|
||||
"code": str(ex.errCode),
|
||||
"description": str(error["name"]),
|
||||
"method": str(error["method"]),
|
||||
"details": ex.errMsg,
|
||||
},
|
||||
}
|
||||
return action
|
||||
|
||||
def WecomInfoNotification(self, msg):
|
||||
"""
|
||||
API 提示信息
|
||||
"""
|
||||
action = {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "display_notification",
|
||||
"params": {
|
||||
"title": msg["title"],
|
||||
"type": "info",
|
||||
"message": msg["message"],
|
||||
"sticky": msg["sticky"],
|
||||
},
|
||||
}
|
||||
return action
|
||||
|
||||
def WecomSuccessNotification(self, msg):
|
||||
"""
|
||||
API 成功提示信息
|
||||
"""
|
||||
params = {
|
||||
"title": msg["title"],
|
||||
"type": "success",
|
||||
"message": msg["message"],
|
||||
"sticky": msg["sticky"],
|
||||
}
|
||||
if "next" in msg:
|
||||
params.update({"next": msg["next"]})
|
||||
|
||||
action = {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "display_notification",
|
||||
"params": params,
|
||||
}
|
||||
return action
|
||||
|
||||
def WecomWarningNotification(self, msg):
|
||||
"""
|
||||
API 警告提示信息
|
||||
"""
|
||||
action = {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "display_notification",
|
||||
"params": {
|
||||
"title": msg["title"],
|
||||
"type": "warning",
|
||||
"message": msg["message"],
|
||||
"sticky": msg["sticky"],
|
||||
},
|
||||
}
|
||||
return action
|
||||
|
||||
def WecomErrorNotification(self, msg):
|
||||
"""
|
||||
API 错误提示信息
|
||||
"""
|
||||
|
||||
action = {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "display_notification",
|
||||
"params": {
|
||||
"title": msg["title"],
|
||||
"type": "error",
|
||||
"message": msg["message"],
|
||||
"sticky": msg["sticky"],
|
||||
},
|
||||
}
|
||||
return action
|
||||
@@ -1,47 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import html2text
|
||||
from odoo import api, models, tools, _
|
||||
|
||||
class WecomApiToolsTypeConvert(models.AbstractModel):
|
||||
_name = "wecomapi.tools.convert"
|
||||
_description = "Wecom API Tools - Type convert"
|
||||
|
||||
def html2text_handle(self, body_html):
|
||||
# 转换markdown格式
|
||||
if bool(body_html):
|
||||
return html2text.html2text(body_html)
|
||||
else:
|
||||
return None
|
||||
|
||||
def str2bool(self):
|
||||
"""
|
||||
字符串转布尔值
|
||||
:param val: 字符串
|
||||
:return: 布尔值
|
||||
"""
|
||||
# return self.value.lower() in ("yes", "true", "t", "1")
|
||||
|
||||
if self.value.lower() in ["true", "t", "1"]: # type: ignore
|
||||
return True
|
||||
elif self.value.lower() in ["false", "f", "0"]: # type: ignore
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def sex2gender(self, sex):
|
||||
"""
|
||||
性别转换
|
||||
"""
|
||||
if sex == "1":
|
||||
return "male"
|
||||
elif sex == "2":
|
||||
return "female"
|
||||
else:
|
||||
return "other"
|
||||
|
||||
def gendge2sex(self, gender):
|
||||
if gender == "male":
|
||||
return "1"
|
||||
elif gender == "female":
|
||||
return "2"
|
||||
@@ -1,42 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, models, tools, _
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class WecomApiToolsData(models.AbstractModel):
|
||||
_name = "wecomapi.tools.data"
|
||||
_description = "Wecom API Tools - Data"
|
||||
|
||||
|
||||
def union_data_set(self, set1, set2):
|
||||
"""
|
||||
2个数据集的并集
|
||||
:param set1: 数据集1
|
||||
:param set2: 数据集2
|
||||
:returns: 并集 的
|
||||
"""
|
||||
# set1.union(set2)
|
||||
return set1 | set2
|
||||
|
||||
def difference_data_set(self, set1, set2):
|
||||
"""
|
||||
2个数据集的差集
|
||||
:param set1: 数据集1
|
||||
:param set2: 数据集2
|
||||
:returns: 差集
|
||||
"""
|
||||
# set1.difference(set2)
|
||||
return set1 - set2
|
||||
|
||||
def intersection_data_set(self, set1, set2):
|
||||
"""
|
||||
2个数据集的交集
|
||||
:param set1: 数据集1
|
||||
:param set2: 数据集2
|
||||
:returns: 交集
|
||||
"""
|
||||
# set1.intersection(set2)
|
||||
return set1 & set2
|
||||
@@ -1,82 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
from odoo import api, models, tools, _
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WecomApiToolsDatetime(models.AbstractModel):
|
||||
_name = "wecomapi.tools.datetime"
|
||||
_description = "Wecom API Tools - Datetime"
|
||||
|
||||
def timestamp2datetime(self, time_stamp):
|
||||
"""
|
||||
时间戳转日期时间
|
||||
"""
|
||||
if len(str(time_stamp)) > 10:
|
||||
# 一般爬取下来的时间戳长度都是13位的数字,而time.localtime的参数要的长度是10位,所以我们需要将其/1000并取整即可
|
||||
time_stamp = int(time_stamp / 1000)
|
||||
loc_time = time.localtime(time_stamp)
|
||||
return time.strftime("%Y-%m-%d %H:%M:%S", loc_time)
|
||||
|
||||
def cheeck_days_overdue(self, datetime_start, maxday):
|
||||
"""[summary]
|
||||
检查天数是否超期
|
||||
Args:
|
||||
datetime_start_str ([type]): [description]
|
||||
datetime_end_str ([type]): [description]
|
||||
maxday ([type]): [description] 天数
|
||||
|
||||
Returns:
|
||||
True: 超期
|
||||
False:未超期
|
||||
"""
|
||||
|
||||
now = datetime.now()
|
||||
# print(now - datetime_start)
|
||||
if datetime_start > (now - timedelta(days=maxday)):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def cheeck_hours_overdue(self, datetime_start, maxhour):
|
||||
"""[summary]
|
||||
检查小时是否超期
|
||||
Args:
|
||||
datetime_start_str ([type]): [description]
|
||||
datetime_end_str ([type]): [description]
|
||||
maxhour ([type]): [description] 小时
|
||||
|
||||
Returns:
|
||||
True: 超期
|
||||
False:未超期
|
||||
"""
|
||||
|
||||
now = datetime.now()
|
||||
if datetime_start > (now - timedelta(hours=maxhour)):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def cheeck_minutes_overdue(self, datetime_start, maxminute):
|
||||
"""[summary]
|
||||
检查分钟是否超期
|
||||
Args:
|
||||
datetime_start_str ([type]): [description]
|
||||
datetime_end_str ([type]): [description]
|
||||
maxminute ([type]): [description] 分钟
|
||||
|
||||
Returns:
|
||||
True: 超期
|
||||
False:未超期
|
||||
"""
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
if datetime_start > (now - timedelta(minutes=maxminute)):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -1,18 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, models, tools, _
|
||||
|
||||
class WecomApiToolsDictionary(models.AbstractModel):
|
||||
_name = "wecomapi.tools.dictionary"
|
||||
_description = "Wecom API Tools - Dictionary"
|
||||
|
||||
|
||||
def check_dictionary_keywords(self, dictionary, key):
|
||||
"""
|
||||
检查字典中是否存在key
|
||||
"""
|
||||
# dictionary, key = (self.value[0], self.value[1])
|
||||
if key in dictionary.keys():
|
||||
return dictionary[key]
|
||||
else:
|
||||
return None
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, models, tools, _
|
||||
import base64
|
||||
import requests
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import logging
|
||||
from odoo.modules.module import get_module_resource
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WecomApiToolsFile(models.AbstractModel):
|
||||
_name = "wecomapi.tools.file"
|
||||
_description = "Wecom API Tools - File"
|
||||
|
||||
def path_is_exists(self, path, subpath):
|
||||
"""
|
||||
检文件夹路径是否存在,不存在则创建路径
|
||||
return:返回路径
|
||||
"""
|
||||
if platform.system() == "Windows":
|
||||
filepath = path.replace("\\", "/") + subpath + "/"
|
||||
else:
|
||||
filepath = path + subpath + "/"
|
||||
# print(filepath)
|
||||
if not os.path.exists(filepath):
|
||||
os.makedirs(filepath)
|
||||
return filepath
|
||||
|
||||
def get_avatar_base64(self, use_default_avatar, gender, avatar_url):
|
||||
"""
|
||||
获取企业微信用户头像的base64编码
|
||||
return:返回base64
|
||||
"""
|
||||
imgbase64 = ""
|
||||
if use_default_avatar or avatar_url == "":
|
||||
image_name = "default_image.png"
|
||||
if gender == "1":
|
||||
image_name = "default_male_image.png"
|
||||
elif gender == "2":
|
||||
image_name = "default_female_image.png"
|
||||
|
||||
default_image = get_module_resource("hrms_base", "static/src/img", image_name)
|
||||
# print(default_image)
|
||||
with open(default_image, "rb") as f:
|
||||
imgbase64 = base64.b64encode(f.read())
|
||||
else:
|
||||
imgbase64 = base64.b64encode(requests.get(avatar_url).content)
|
||||
return imgbase64
|
||||
@@ -1,14 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, models, tools, _
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WecomApiToolsMessage(models.AbstractModel):
|
||||
_name = "wecomapi.tools.message"
|
||||
_description = "Wecom API Tools - Message"
|
||||
|
||||
def message_split(self,text):
|
||||
""" """
|
||||
@@ -1,83 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
import logging
|
||||
import hashlib
|
||||
import random
|
||||
import hashlib
|
||||
from Crypto.Cipher import AES
|
||||
from passlib.context import CryptContext
|
||||
import xml.etree.cElementTree as ET
|
||||
import hashlib
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
NUMLIST = ["0","1","2","3","4","5","6","7","8","9","q","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","W","R","S","T","U","V","W","X","Y","Z",]
|
||||
|
||||
|
||||
class WecomApiToolsSecurity(models.AbstractModel):
|
||||
_name = "wecomapi.tools.security"
|
||||
_description = "Wecom API Tools - Security"
|
||||
|
||||
def random_str(self, num):
|
||||
"""
|
||||
生成随机字符串
|
||||
:return:
|
||||
"""
|
||||
rang = num
|
||||
if rang == None:
|
||||
random_str = "".join(random.choice(NUMLIST) for i in range(8))
|
||||
else:
|
||||
random_str = "".join(random.choice(NUMLIST) for i in range(int(rang)))
|
||||
return random_str
|
||||
|
||||
def random_passwd(self, num):
|
||||
"""
|
||||
生成随机密码
|
||||
:return:
|
||||
"""
|
||||
rang = num
|
||||
if rang == None:
|
||||
passwd = "".join(random.choice(NUMLIST) for i in range(8))
|
||||
else:
|
||||
passwd = "".join(random.choice(NUMLIST) for i in range(int(rang)))
|
||||
|
||||
crypt_context = CryptContext(
|
||||
schemes=["pbkdf2_sha512", "plaintext"], deprecated=["plaintext"]
|
||||
)
|
||||
hash_password = (
|
||||
crypt_context.hash
|
||||
if hasattr(crypt_context, "hash")
|
||||
else crypt_context.encrypt
|
||||
)
|
||||
return hash_password(passwd)
|
||||
|
||||
def generate_jsapi_signature(self, jsapi_ticket, nonceStr, timestamp):
|
||||
"""
|
||||
使用sha1加密算法,生成JSAPI的签名
|
||||
------------------------------
|
||||
company: 公司
|
||||
nonceStr: 生成签名的随机串
|
||||
timestamp: 生成签名的时间戳
|
||||
url: 当前网页的URL, 不包含#及其后面部分
|
||||
"""
|
||||
|
||||
# 生成签名前,刷新 ticke .search([(("is_wecom_organization", "=", True))])
|
||||
# company.sudo().get_jsapi_ticket()
|
||||
|
||||
ICP = self.env["ir.config_parameter"].sudo()
|
||||
web_base_url = ICP.get_param("web.base.url", default="http://localhost:8069")
|
||||
if web_base_url[-1] == "/":
|
||||
web_base_url = web_base_url + "web"
|
||||
else:
|
||||
web_base_url = web_base_url + "/web"
|
||||
|
||||
jsapi_ticket_str = ("jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s") % (
|
||||
jsapi_ticket,
|
||||
nonceStr,
|
||||
timestamp,
|
||||
web_base_url,
|
||||
)
|
||||
encrypts = hashlib.sha1(jsapi_ticket_str.encode("utf-8")).hexdigest()
|
||||
return encrypts
|
||||
@@ -1,42 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import random
|
||||
from odoo import api, models, tools, _
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WecomApiToolsString(models.AbstractModel):
|
||||
_name = "wecomapi.tools.string"
|
||||
_description = "Wecom API Tools - Fields"
|
||||
|
||||
def str2bool(self):
|
||||
"""
|
||||
字符串转布尔值
|
||||
:param val: 字符串
|
||||
:return: 布尔值
|
||||
"""
|
||||
# return self.value.lower() in ("yes", "true", "t", "1")
|
||||
|
||||
if self.value.lower() in ["true", "t", "1"]: # type: ignore
|
||||
return True
|
||||
elif self.value.lower() in ["false", "f", "0"]: # type: ignore
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
# def int2bool(self):
|
||||
# """
|
||||
# 整形转布尔值
|
||||
# :param val: 整形
|
||||
# :return: 布尔值
|
||||
# """
|
||||
# if self.value.lower() in ["true", "t", "1"]:
|
||||
# return True
|
||||
# elif self.value.lower() in ["false", "f", "0"]:
|
||||
# return False
|
||||
# else:
|
||||
# return False
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from PIL import Image
|
||||
import os
|
||||
import base64
|
||||
import random
|
||||
import html2text
|
||||
import platform
|
||||
import hashlib
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from odoo import api, models, tools, _
|
||||
from odoo.modules.module import get_module_resource
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WxTools(models.AbstractModel):
|
||||
_name = "wecom.tools"
|
||||
_description = "Wecom Tools"
|
||||
|
||||
def recipients_split(text): # type: ignore
|
||||
"""
|
||||
使用 | 拆分企业微信消息的接收对象
|
||||
"""
|
||||
if not text:
|
||||
return []
|
||||
|
||||
def wecom_user_enable(self):
|
||||
"""
|
||||
企业微信用户是否启用
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
if self.value == "0": # type: ignore
|
||||
self.result = False
|
||||
if self.value == "1": # type: ignore
|
||||
self.result = True
|
||||
return self.result
|
||||
|
||||
def encode_avatar_image_as_base64(self, gender):
|
||||
if gender == "1":
|
||||
default_image = get_module_resource(
|
||||
"wecom_api", "static/src/img", "default_male_image.png"
|
||||
)
|
||||
elif gender == "2":
|
||||
default_image = get_module_resource(
|
||||
"wecom_api",
|
||||
"static/src/img",
|
||||
"default_female_image.png",
|
||||
)
|
||||
else:
|
||||
default_image = get_module_resource(
|
||||
"wecom_api", "static/src/img", "default_image.png"
|
||||
)
|
||||
|
||||
with open(default_image, "rb") as f:
|
||||
return base64.b64encode(f.read())
|
||||
|
||||
def get_default_avatar_url(self, gender):
|
||||
params = self.env["ir.config_parameter"].sudo()
|
||||
base_url = params.get_param("web.base.url")
|
||||
if gender == "1":
|
||||
default_image_url = (
|
||||
base_url + "/wecom_api/static/src/img/default_male_image.png"
|
||||
)
|
||||
elif gender == "2":
|
||||
default_image_url = (
|
||||
base_url + "/wecom_api/static/src/img/default_female_image.png"
|
||||
)
|
||||
|
||||
return default_image_url # type: ignore
|
||||
|
||||
def encode_image_as_base64(self):
|
||||
if not self.value: # type: ignore
|
||||
pass
|
||||
else:
|
||||
with open(self.value, "rb") as f: # type: ignore
|
||||
encoded_string = base64.b64encode(f.read())
|
||||
return encoded_string
|
||||
|
||||
def is_exists(self):
|
||||
"""
|
||||
判断是否存在值
|
||||
:return:
|
||||
"""
|
||||
if not self.value: # type: ignore
|
||||
self.result = False
|
||||
else:
|
||||
self.result = True
|
||||
return self.result
|
||||
|
||||
def mail_is_exists(self):
|
||||
"""
|
||||
判断是否存在值
|
||||
:return:
|
||||
"""
|
||||
if not self.value: # type: ignore
|
||||
self.result = ""
|
||||
else:
|
||||
self.result = self.value # type: ignore
|
||||
return self.result
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
@@ -1,42 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
"name": "WeCom Authentication",
|
||||
"author": "RStudio",
|
||||
"website": "https://gitee.com/rainbowstudio/wecom",
|
||||
"sequence": 605,
|
||||
"installable": True,
|
||||
"application": True,
|
||||
"auto_install": False,
|
||||
"category": "WeCom Suites/Authentication",
|
||||
"version": "17.0.0.1",
|
||||
"summary": """
|
||||
One click login, code scanning login.
|
||||
""",
|
||||
"description": """
|
||||
|
||||
""",
|
||||
"depends": [
|
||||
"portal",
|
||||
"auth_oauth",
|
||||
"wecom_material",
|
||||
],
|
||||
"data": [
|
||||
"data/wecom_apps_data.xml",
|
||||
"data/wecom_app_config_data.xml",
|
||||
"data/wecom_oauth_data.xml",
|
||||
"views/res_config_settings_views.xml",
|
||||
"views/auth_oauth_views.xml",
|
||||
# "views/wecom_apps_views.xml",
|
||||
# "views/auth_signup_login_templates.xml",
|
||||
],
|
||||
"assets": {
|
||||
"web.assets_backend": [
|
||||
# "wecom_auth_oauth/static/src/webclient/**/*",
|
||||
],
|
||||
"web.assets_frontend": [
|
||||
"wecom_auth_oauth/static/src/js/wecom_login/*",
|
||||
],
|
||||
},
|
||||
"bootstrap": True, # 加载登录屏幕的翻译,
|
||||
"license": "Other proprietary",
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import main
|
||||
@@ -1,110 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# !参考 \addons\auth_oauth\controllers\main.py
|
||||
|
||||
import base64
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import werkzeug.urls
|
||||
import werkzeug.utils
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from odoo import api, http, SUPERUSER_ID, _
|
||||
from odoo.exceptions import AccessDenied
|
||||
from odoo.http import request, Response
|
||||
from odoo import registry as registry_get
|
||||
from odoo.tools.misc import clean_context
|
||||
|
||||
from odoo.addons.auth_signup.controllers.main import AuthSignupHome as Home
|
||||
from odoo.addons.web.controllers.utils import ensure_db, _get_login_redirect_url
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
WECOM_BROWSER_MESSAGES = {
|
||||
"not_wecom_browser": _(
|
||||
"The current browser is not an WeCom built-in browser, so the one-click login function cannot be used."
|
||||
),
|
||||
"is_wecom_browser": _(
|
||||
"It is detected that the page is opened in the built-in browser of WeCom, please select company."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class WeComOAuthLogin(Home):
|
||||
def list_providers(self):
|
||||
providers = super(WeComOAuthLogin, self).list_providers()
|
||||
|
||||
for provider in providers:
|
||||
if (
|
||||
"login.work.weixin.qq.com/wwlogin/sso/login"
|
||||
in provider["auth_endpoint"]
|
||||
):
|
||||
# 链接格式
|
||||
# https://login.work.weixin.qq.com/wwlogin/sso/login?login_type=LOGIN_TYPE&appid=APPID&redirect_uri=REDIRECT_URI&state=STATE
|
||||
return_url = request.httprequest.url_root + "wecom/authorize"
|
||||
state = self.get_state(provider)
|
||||
params = dict(
|
||||
appid=False,
|
||||
redirect_uri=return_url,
|
||||
response_type="code",
|
||||
scope=provider["scope"],
|
||||
state=json.dumps(state),
|
||||
)
|
||||
provider["auth_link"] = "%s?%s%s" % (
|
||||
provider["auth_endpoint"],
|
||||
werkzeug.urls.url_encode(params), # type: ignore
|
||||
"#wechat_redirect",
|
||||
)
|
||||
|
||||
return providers
|
||||
|
||||
|
||||
class OAuthController(http.Controller):
|
||||
@http.route("/wwcom_login_info", type="json", auth="none")
|
||||
def wecom_get_login_info(self, **kwargs):
|
||||
data = {}
|
||||
if "is_wecom_browser" in kwargs:
|
||||
if kwargs["is_wecom_browser"]:
|
||||
data = {
|
||||
"is_wecom_browser": True,
|
||||
"msg": WECOM_BROWSER_MESSAGES["is_wecom_browser"],
|
||||
"companies": [],
|
||||
}
|
||||
else:
|
||||
data = {
|
||||
"is_wecom_browser": False,
|
||||
"msg": WECOM_BROWSER_MESSAGES["not_wecom_browser"],
|
||||
"companies": [],
|
||||
}
|
||||
else:
|
||||
data = {
|
||||
"join_button_name": _("Join WeCom,Become our employee."),
|
||||
"companies": [],
|
||||
}
|
||||
|
||||
# 获取 标记为 企业微信组织 的公司
|
||||
companies = request.env["res.company"].search( # type: ignore
|
||||
[(("is_wecom_organization", "=", True))]
|
||||
)
|
||||
|
||||
if len(companies) > 0:
|
||||
for company in companies:
|
||||
# app_config = request.env["wecom.app_config"].sudo()
|
||||
# contacts_app = company.contacts_app_id.sudo() # 通讯录应用
|
||||
auth_app = company.auth_app_id.sudo() # 验证登录应用
|
||||
data["companies"].append(
|
||||
{
|
||||
"id": company["id"],
|
||||
"name": company["abbreviated_name"],
|
||||
"fullname": company["name"],
|
||||
"appid": company["corpid"],
|
||||
"agentid": auth_app.agentid if auth_app else 0,
|
||||
"enabled_join": company["wecom_contacts_join_qrcode_enabled"],
|
||||
"join_qrcode": company["wecom_contacts_join_qrcode"],
|
||||
}
|
||||
)
|
||||
return data
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- 1 -->
|
||||
<record id="wecom_app_config_authentication_auth_redirect_uri" model="wecom.app_config">
|
||||
<field name="app_id" ref="wecom_contacts.wecom_app_first_self_app"/>
|
||||
<field name="name">Callback link address redirected after authorization</field>
|
||||
<field name="key">auth_redirect_uri</field>
|
||||
<field name="ttype">char</field>
|
||||
<field name="value">http://xxx.xxx.com:port/wecom_auth_oauth/authorize</field>
|
||||
<field name="description" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Fill in the "Corp ID", "AgentId" and "Secret" that require authentication and authorization, and use the domain name to access,
|
||||
<span style="color:orange;">
|
||||
Click the button "One-click to set up WeCom OAuth"
|
||||
</span>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 2 -->
|
||||
<record id="wecom_app_config_authentication_qr_redirect_uri" model="wecom.app_config">
|
||||
<field name="app_id" ref="wecom_contacts.wecom_app_first_self_app"/>
|
||||
<field name="name">Scan the QR code to log in and call back the link address</field>
|
||||
<field name="key">auth_qr_redirect_uri</field>
|
||||
<field name="ttype">char</field>
|
||||
<field name="value">http://xxx.xxx.com:port/wecom_auth_oauth/qr</field>
|
||||
<field name="description" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Fill in the "Corp ID", "AgentId" and "Secret" that require authentication and authorization, and use the domain name to access,
|
||||
<span style="color:orange;">
|
||||
Click the button "One-click to set up WeCom OAuth"
|
||||
</span>
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- 应用子类型 -->
|
||||
<!-- <record id="wecom_app_subtype_auth" model="wecom.app.subtype">
|
||||
<field name="name">Authentication</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_self"/>
|
||||
<field name="code">auth</field>
|
||||
<field name="sequence">1</field>
|
||||
</record> -->
|
||||
|
||||
<!-- 应用 -->
|
||||
<record id="wecom_contacts.wecom_app_first_self_app" model="wecom.apps">
|
||||
<field name="subtype_ids" eval="[(4,ref('wecom_base.wecom_app_subtype_auth'))]"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<function model="auth.oauth.provider" name="write">
|
||||
<value eval="[ref('auth_oauth.provider_openerp')]"/>
|
||||
<value eval="{'enabled': False}"/>
|
||||
</function>
|
||||
|
||||
<record id="provider_wecom_quick_login" model="auth.oauth.provider">
|
||||
<field name="name">WeCom Quick Login</field>
|
||||
<!--身份验证网址-->
|
||||
<field name="auth_endpoint">https://login.work.weixin.qq.com/wwlogin/sso/login</field>
|
||||
<!--作用域-->
|
||||
<field name="scope">snsapi_base</field>
|
||||
<!--验证网址-->
|
||||
<field name="validation_endpoint">https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo</field>
|
||||
<!--数据网址-->
|
||||
<field name="data_endpoint">https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail</field>
|
||||
<field name="css_class">fa fa-fw fa-wechat wecom_auth_quick</field>
|
||||
<field name="body">WeCom quick login</field>
|
||||
<field name="enabled" eval="False"/>
|
||||
<field name="is_woecom" eval="False"/>
|
||||
<field name="description"></field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="provider_wecom_qrconnect" model="auth.oauth.provider">
|
||||
<field name="name">WeCom OAuth2 Scan</field>
|
||||
<field name="auth_endpoint">https://open.work.weixin.qq.com/wwopen/sso/qrConnect</field>
|
||||
<field name="scope"></field>
|
||||
<field name="validation_endpoint"></field>
|
||||
<field name="data_endpoint"></field>
|
||||
<field name="css_class">fa fa-fw fa-qrcode wecom_auth_scancode</field>
|
||||
<field name="body">WeCom scan code login</field>
|
||||
<field name="enabled" eval="False"/>
|
||||
<field name="description"></field>
|
||||
</record> -->
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,287 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * wecom_auth_oauth
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 17.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-03-12 09:11+0000\n"
|
||||
"PO-Revision-Date: 2024-03-12 17:20+0800\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.4.2\n"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "<i class=\"fa fa-cogs fa-lg\" aria-label=\"Authentication Configuration\"/>"
|
||||
msgstr "<i class=\"fa fa-cogs fa-lg\" aria-label=\"身份认证配置\"/>"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "<i class=\"fa fa-key fa-lg\" aria-label=\"Auth Secret\"/>"
|
||||
msgstr "<i class=\"fa fa-key fa-lg\" aria-label=\"身份认证密钥\"/>"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "<i class=\"fa fa-list-ol fa-lg\" aria-label=\"Auth AgentId\"/>"
|
||||
msgstr "<i class=\"fa fa-list-ol fa-lg\" aria-label=\"身份认证应用Id\"/>"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid ""
|
||||
"<i class=\"fa fa-puzzle-piece fa-lg\" aria-label=\"Authentication Application\"/>"
|
||||
msgstr "<i class=\"fa fa-puzzle-piece fa-lg\" aria-label=\"身份认证应用\"/>"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "<i class=\"fa fa-ticket fa-lg\" role=\"img\" aria-label=\"JSAPI Ticket\"/>"
|
||||
msgstr ""
|
||||
"<i class=\"fa fa-ticket fa-lg\" role=\"img\" aria-label=\"JSAPI临时凭证\"/>"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "<strong>Save</strong> this page and come back here to set up other feature."
|
||||
msgstr "<strong>保存</strong> 此页面并返回此处以设置其它功能。"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_res_config_settings__auth_agentid
|
||||
msgid "Agent ID"
|
||||
msgstr "应用ID"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_res_company__auth_app_id
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_res_config_settings__auth_app_id
|
||||
msgid "Application"
|
||||
msgstr "应用"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_res_config_settings__auth_app_config_ids
|
||||
msgid "Application Configuration"
|
||||
msgstr "应用配置"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid ""
|
||||
"Application Management→Application→Open the application that needs to be "
|
||||
"verified and authorized, copy 'AgentId'"
|
||||
msgstr "应用程序管理→应用→打开需要验证和授权的应用程序,复制“AgentId”"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid ""
|
||||
"Application Management→Application→Open the application that needs to be "
|
||||
"verified and authorized, copy 'Secret'"
|
||||
msgstr "应用程序管理→应用→打开需要验证和授权的应用程序,复制“Secret”"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#. odoo-python
|
||||
#: code:addons/wecom_auth_oauth/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "Auth application ID and secret cannot be empty!"
|
||||
msgstr "身份验证应用ID和Secret 不能为空!"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "Binding Auth Application"
|
||||
msgstr "绑定验证应用"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "Button group"
|
||||
msgstr "按钮组"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:wecom.app_config,name:wecom_auth_oauth.wecom_app_config_authentication_auth_redirect_uri
|
||||
msgid "Callback link address redirected after authorization"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model,name:wecom_auth_oauth.model_res_company
|
||||
msgid "Companies"
|
||||
msgstr "公司"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_res_config_settings__company_id
|
||||
msgid "Company"
|
||||
msgstr "公司"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model,name:wecom_auth_oauth.model_res_config_settings
|
||||
msgid "Config Settings"
|
||||
msgstr "配置设置"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_auth_oauth_provider__description
|
||||
msgid "Description"
|
||||
msgstr "说明"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "Enable WeCom quick login"
|
||||
msgstr "启用企微快速登录"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "Expiration time"
|
||||
msgstr "过期时间"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:wecom.app_config,description:wecom_auth_oauth.wecom_app_config_authentication_auth_redirect_uri
|
||||
#: model_terms:wecom.app_config,description:wecom_auth_oauth.wecom_app_config_authentication_qr_redirect_uri
|
||||
msgid ""
|
||||
"Fill in the \"Corp ID\", \"AgentId\" and \"Secret\" that require authentication "
|
||||
"and authorization, and use the domain name to access,\n"
|
||||
" <span style=\"color:orange;\">\n"
|
||||
" Click the button \"One-click to set up WeCom OAuth\"\n"
|
||||
" </span>"
|
||||
msgstr ""
|
||||
"在需要认证和授权的“企业ID”、“AgentId”和“Secret”中,使用域名进行访问,\n"
|
||||
" <span style=\"color:orange;\">\n"
|
||||
" Click the button \"One-click to set up WeCom OAuth\"\n"
|
||||
" </span>"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "Get JSAPI Ticket"
|
||||
msgstr "获取JSAPI临时凭证"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "Get application info"
|
||||
msgstr "获取应用信息"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid ""
|
||||
"Is a temporary ticket for the H5 application to call the Wecom JS interface. "
|
||||
"Under normal circumstances, <b>the validity period of jsapi_ticket is 7200 "
|
||||
"seconds</b>, obtained by access_token. Since the number of API calls to the "
|
||||
"jsapi_ticket is very limited (an enterprise can get up to 400 times in an hour, "
|
||||
"and a single application cannot get more than 100 times), frequent refresh "
|
||||
"jsapi_ticket will cause the API call to be limited, affecting their own "
|
||||
"business, <b>and developers must cache jsapi_ticket in their own service global "
|
||||
"cache</b>."
|
||||
msgstr ""
|
||||
"jsapi_ticket是H5应用调用企业微信JS接口的临时票据。正常情况下,<b>jsapi_ticket的有"
|
||||
"效期为7200秒</b>,通过access_token获得。由于API调用jsapi_ticket的次数非常有限(一"
|
||||
"小时内,一个企业最多可获取400次,且单个应用不能超过100次),频繁刷新jsapi_ticket"
|
||||
"会导致api调用受限,影响自身业务,<b>开发者必须在自己的服务全局缓存应用的"
|
||||
"jsapi_ticket</b>中。"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_res_config_settings__auth_app_jsapi_ticket
|
||||
msgid "JSAPI Ticket"
|
||||
msgstr "JSAPI临时凭证"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_res_config_settings__auth_app_jsapi_ticket_expiration_time
|
||||
msgid "JSAPI Ticket Expiration Time"
|
||||
msgstr "JSAPI临时凭证有效时间"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "Management application"
|
||||
msgstr "管理应用"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model,name:wecom_auth_oauth.model_auth_oauth_provider
|
||||
msgid "OAuth2 provider"
|
||||
msgstr "OAuth2服务商"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#. odoo-python
|
||||
#: code:addons/wecom_auth_oauth/models/wecom_apps.py:0
|
||||
#, python-format
|
||||
msgid "Please click on the 'Generate Parameters' button!"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:wecom.app_config,name:wecom_auth_oauth.wecom_app_config_authentication_qr_redirect_uri
|
||||
msgid "Scan the QR code to log in and call back the link address"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_res_config_settings__auth_secret
|
||||
msgid "Secret"
|
||||
msgstr "密钥"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#. odoo-python
|
||||
#: code:addons/wecom_auth_oauth/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "Start getting ticket for company [%s]"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#. odoo-python
|
||||
#: code:addons/wecom_auth_oauth/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "Successfully obtained ticket!"
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#. odoo-python
|
||||
#: code:addons/wecom_auth_oauth/models/wecom_apps.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The parameter \"Callback link address redirected after authorization\" does not "
|
||||
"exist!\n"
|
||||
"Please click on the 'Generate Parameters' button!\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#. odoo-python
|
||||
#: code:addons/wecom_auth_oauth/models/wecom_apps.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The parameter \"Scan the QR code to log in and call back the link address\" does "
|
||||
"not exist!\n"
|
||||
"Please click on the 'Generate Parameters' button!\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#. odoo-python
|
||||
#: code:addons/wecom_auth_oauth/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "Ticket is still valid, and no update is required!"
|
||||
msgstr "临时凭证仍然有效,不需要更新!"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#. odoo-python
|
||||
#: code:addons/wecom_auth_oauth/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "Tips"
|
||||
msgstr "提示"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model,name:wecom_auth_oauth.model_res_users
|
||||
msgid "User"
|
||||
msgstr "用户"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model_terms:ir.ui.view,arch_db:wecom_auth_oauth.res_config_settings_view_form
|
||||
msgid "View OAuth providers"
|
||||
msgstr "查看OAuth提供程序"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model.fields,field_description:wecom_auth_oauth.field_auth_oauth_provider__is_woecom
|
||||
msgid "WeCom OAuth"
|
||||
msgstr "企业微信OAuth"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:auth.oauth.provider,body:wecom_auth_oauth.provider_wecom_quick_login
|
||||
msgid "WeCom quick login"
|
||||
msgstr "企微快速登录"
|
||||
|
||||
#. module: wecom_auth_oauth
|
||||
#: model:ir.model,name:wecom_auth_oauth.model_wecom_apps
|
||||
msgid "Wecom Application"
|
||||
msgstr "企微应用"
|
||||
@@ -1,8 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import auth_oauth
|
||||
from . import wecom_apps
|
||||
from . import res_users
|
||||
@@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AuthOAuthProvider(models.Model):
|
||||
_inherit = "auth.oauth.provider"
|
||||
|
||||
is_woecom = fields.Boolean(string="WeCom OAuth", default=False)
|
||||
description = fields.Html(string="Description", translate=True)
|
||||
@@ -1,82 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.addons.wecom_api.api.wecom_abstract_api import ApiException # type: ignore
|
||||
|
||||
|
||||
import werkzeug.urls
|
||||
import werkzeug.utils
|
||||
import urllib
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
auth_app_id = fields.Many2one(
|
||||
"wecom.apps",
|
||||
string="Application",
|
||||
# required=True,
|
||||
# default=lambda self: self.env.company,
|
||||
# domain="[('company_id', '=', current_company_id)]",
|
||||
domain="[('company_id', '=', current_company_id)]",
|
||||
)
|
||||
|
||||
def set_oauth_provider_wxwork(self):
|
||||
web_base_url = self.env["ir.config_parameter"].get_param("web.base.url")
|
||||
|
||||
new_auth_redirect_uri = (
|
||||
urllib.parse.urlparse(web_base_url).scheme # type: ignore
|
||||
+ "://"
|
||||
+ urllib.parse.urlparse(web_base_url).netloc # type: ignore
|
||||
+ urllib.parse.urlparse(self.auth_redirect_uri).path # type: ignore
|
||||
)
|
||||
new_qr_redirect_uri = (
|
||||
urllib.parse.urlparse(web_base_url).scheme # type: ignore
|
||||
+ "://"
|
||||
+ urllib.parse.urlparse(web_base_url).netloc # type: ignore
|
||||
+ urllib.parse.urlparse(self.qr_redirect_uri).path # type: ignore
|
||||
)
|
||||
|
||||
# 设置回调链接地址
|
||||
self.auth_redirect_uri = new_auth_redirect_uri
|
||||
self.qr_redirect_uri = new_qr_redirect_uri
|
||||
|
||||
auth_endpoint = "https://open.weixin.qq.com/connect/oauth2/authorize"
|
||||
qr_auth_endpoint = "https://open.work.weixin.qq.com/wwopen/sso/qrConnect"
|
||||
|
||||
try:
|
||||
providers = (
|
||||
self.env["auth.oauth.provider"]
|
||||
.sudo()
|
||||
.search(
|
||||
[
|
||||
"|",
|
||||
("enabled", "=", True),
|
||||
("enabled", "=", False),
|
||||
]
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
providers = []
|
||||
|
||||
for provider in providers:
|
||||
if auth_endpoint in provider["auth_endpoint"]:
|
||||
provider.write(
|
||||
{
|
||||
# "client_id": client_id,
|
||||
"validation_endpoint": self.auth_redirect_uri,
|
||||
"enabled": True,
|
||||
}
|
||||
)
|
||||
if qr_auth_endpoint in provider["auth_endpoint"]:
|
||||
provider.write(
|
||||
{
|
||||
# "client_id": client_id,
|
||||
"validation_endpoint": self.qr_redirect_uri,
|
||||
"enabled": True,
|
||||
}
|
||||
)
|
||||
@@ -1,149 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
from odoo.addons.wecom_api.api.wecom_abstract_api import ApiException # type: ignore
|
||||
|
||||
|
||||
import werkzeug.urls
|
||||
import werkzeug.utils
|
||||
import urllib
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
company_id = fields.Many2one(
|
||||
"res.company",
|
||||
string="Company",
|
||||
required=True,
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
|
||||
auth_app_id = fields.Many2one(
|
||||
related="company_id.auth_app_id",
|
||||
readonly=False,
|
||||
)
|
||||
auth_agentid = fields.Integer(related="auth_app_id.agentid", readonly=False)
|
||||
auth_secret = fields.Char(related="auth_app_id.secret", readonly=False)
|
||||
|
||||
auth_app_jsapi_ticket = fields.Char(
|
||||
related="auth_app_id.jsapi_ticket", readonly=False
|
||||
)
|
||||
auth_app_jsapi_ticket_expiration_time = fields.Datetime(
|
||||
related="auth_app_id.jsapi_ticket_expiration_time", readonly=False
|
||||
)
|
||||
|
||||
auth_app_config_ids = fields.One2many(
|
||||
# related="company_id.contacts_auto_sync_hr_enabled", readonly=False
|
||||
related="auth_app_id.app_config_ids",
|
||||
# domain="[('company_id', '=', company_id),('app_id', '=', contacts_app_id)]",
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
def enable_wecom_quick_login(self):
|
||||
ir_model_data = self.env["ir.model.data"]
|
||||
provider_wecom_quick_login = ir_model_data._xmlid_lookup(
|
||||
"wecom_auth_oauth.provider_wecom_quick_login",
|
||||
)[1]
|
||||
providers = [provider_wecom_quick_login]
|
||||
for provider in providers:
|
||||
p = self.env["auth.oauth.provider"].search([("id", "=", provider)], limit=1)
|
||||
if p:
|
||||
p.enabled = True
|
||||
|
||||
def set_oauth_provider_wecom(self):
|
||||
self.auth_app_id.set_oauth_provider_wecom() # type: ignore
|
||||
|
||||
def generate_parameters(self):
|
||||
"""
|
||||
生成参数
|
||||
:return:
|
||||
"""
|
||||
code = self.env.context.get("code")
|
||||
if bool(code) and code == "auth":
|
||||
for record in self:
|
||||
# if not record.contacts_app_id:
|
||||
# raise ValidationError(_("Please bind contact app!"))
|
||||
# else:
|
||||
record.auth_app_id.with_context(code=code).generate_parameters() # type: ignore
|
||||
super(ResConfigSettings, self).generate_parameters() # type: ignore
|
||||
|
||||
def get_app_info(self):
|
||||
"""
|
||||
获取应用信息
|
||||
:return:
|
||||
"""
|
||||
app = self.env.context.get("app")
|
||||
for record in self:
|
||||
if app == "auth" and (
|
||||
record.auth_app_id.agentid == 0 or record.auth_app_id.secret == "" # type: ignore
|
||||
):
|
||||
raise UserError(_("Auth application ID and secret cannot be empty!"))
|
||||
else:
|
||||
record.auth_app_id.get_app_info() # type: ignore
|
||||
super(ResConfigSettings, self).get_app_info() # type: ignore
|
||||
|
||||
def get_auth_app_jsapi_ticket(self):
|
||||
"""
|
||||
获取身份认证应用的jsapi_ticket
|
||||
:return:
|
||||
"""
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
debug = ir_config.get_param("wecom.debug_enabled")
|
||||
if debug:
|
||||
_logger.info(
|
||||
_("Start getting ticket for company [%s]") % (self.company_id.name) # type: ignore
|
||||
)
|
||||
try:
|
||||
if (
|
||||
self.auth_app_jsapi_ticket_expiration_time
|
||||
and self.auth_app_jsapi_ticket_expiration_time > datetime.now()
|
||||
):
|
||||
# 未过期,
|
||||
# print("未过期")
|
||||
msg = {
|
||||
"title": _("Tips"),
|
||||
"message": _("Ticket is still valid, and no update is required!"),
|
||||
"sticky": False,
|
||||
}
|
||||
return self.env["wecomapi.tools.action"].WecomInfoNotification(msg)
|
||||
else:
|
||||
# print("过期")
|
||||
wecom_api = self.env["wecom.service_api"].InitServiceApi(self.company_id.corpid, self.auth_app_id.secret) # type: ignore
|
||||
response = wecom_api.httpCall(
|
||||
self.env["wecom.service_api_list"].get_server_api_call(
|
||||
"AGENT_GET_TICKET"
|
||||
),
|
||||
{"type": "agent_config"},
|
||||
)
|
||||
# print(response)
|
||||
except ApiException as ex:
|
||||
return self.env["wecomapi.tools.action"].ApiExceptionDialog(
|
||||
ex, raise_exception=True
|
||||
)
|
||||
else:
|
||||
if response["errcode"] == 0:
|
||||
self.write(
|
||||
{
|
||||
"auth_app_jsapi_ticket": response["ticket"],
|
||||
"auth_app_jsapi_ticket_expiration_time": datetime.now()
|
||||
+ timedelta(seconds=response["expires_in"]),
|
||||
}
|
||||
)
|
||||
msg = {
|
||||
"title": _("Tips"),
|
||||
"message": _("Successfully obtained ticket!"),
|
||||
"sticky": False,
|
||||
"next": {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "reload",
|
||||
},
|
||||
}
|
||||
return self.env["wecomapi.tools.action"].WecomSuccessNotification(msg)
|
||||
@@ -1,59 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, api, _
|
||||
from odoo.exceptions import AccessDenied
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = "res.users"
|
||||
|
||||
# ---------------------
|
||||
# 验证
|
||||
# ---------------------
|
||||
@api.model
|
||||
def wecom_auth_oauth(self, provider, params):
|
||||
"""
|
||||
允许一键登录和扫码登录且标记了企业微信的用户登录系统
|
||||
:param provider:
|
||||
:param params:
|
||||
:return:
|
||||
"""
|
||||
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),])
|
||||
)
|
||||
|
||||
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:
|
||||
return AccessDenied
|
||||
return (self.env.cr.dbname, oauth_user.login, oauth_userid) # type: ignore
|
||||
else:
|
||||
return AccessDenied
|
||||
|
||||
|
||||
def _check_credentials(self, password, env):
|
||||
# password为企业微信的用户ID
|
||||
try:
|
||||
return super(ResUsers, self)._check_credentials(password, env) # type: ignore
|
||||
except AccessDenied:
|
||||
res = self.sudo().search(
|
||||
[("id", "=", self.env.uid), ("wecom_userid", "=", password)]
|
||||
)
|
||||
if not res:
|
||||
raise
|
||||
@@ -1,152 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import datetime
|
||||
import werkzeug.utils
|
||||
import urllib
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WeComApps(models.Model):
|
||||
_inherit = "wecom.apps"
|
||||
|
||||
def generate_parameters_by_code(self, code):
|
||||
"""
|
||||
根据code生成参数
|
||||
:param code:
|
||||
:return:
|
||||
"""
|
||||
if code == "auth":
|
||||
ir_model_data = self.env["ir.model.data"]
|
||||
auth_redirect_uri = ir_model_data.get_object_reference(
|
||||
"wecom_auth_oauth", "wecom_app_config_authentication_auth_redirect_uri"
|
||||
)[1]
|
||||
qr_redirect_uri = ir_model_data.get_object_reference(
|
||||
"wecom_auth_oauth", "wecom_app_config_authentication_qr_redirect_uri"
|
||||
)[1]
|
||||
vals_list = [
|
||||
auth_redirect_uri,
|
||||
qr_redirect_uri,
|
||||
]
|
||||
|
||||
for id in vals_list:
|
||||
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)]) # type: ignore
|
||||
)
|
||||
if not app_config:
|
||||
app_config = (
|
||||
self.env["wecom.app_config"]
|
||||
.sudo()
|
||||
.create(
|
||||
{
|
||||
"name": app_config_id.name,
|
||||
"app_id": self.id, # type: ignore
|
||||
"key": app_config_id.key,
|
||||
"ttype": app_config_id.ttype,
|
||||
"value": app_config_id.value,
|
||||
"description": app_config_id.description,
|
||||
}
|
||||
)
|
||||
)
|
||||
else:
|
||||
app_config.sudo().write(
|
||||
{
|
||||
"name": app_config_id.name,
|
||||
"value": app_config_id.value,
|
||||
"description": app_config_id.description,
|
||||
}
|
||||
)
|
||||
super(WeComApps, self).generate_parameters_by_code(code) # type: ignore
|
||||
|
||||
# ————————————————————————————————————
|
||||
# 设置认证应用配置
|
||||
# ————————————————————————————————————
|
||||
def set_oauth_provider_wecom(self):
|
||||
web_base_url = self.env["ir.config_parameter"].get_param("web.base.url")
|
||||
auth_redirect_uri = None
|
||||
qr_redirect_uri = None
|
||||
if len(self.app_config_ids) > 0: # type: ignore
|
||||
for record in self.app_config_ids: # type: ignore
|
||||
if record["key"] == "auth_redirect_uri":
|
||||
auth_redirect_uri = record
|
||||
else:
|
||||
raise UserError(
|
||||
_(
|
||||
"""The parameter "Callback link address redirected after authorization" does not exist!
|
||||
Please click on the 'Generate Parameters' button!
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
if record["key"] == "qr_redirect_uri":
|
||||
qr_redirect_uri = record
|
||||
else:
|
||||
raise UserError(
|
||||
_(
|
||||
"""The parameter "Scan the QR code to log in and call back the link address" does not exist!
|
||||
Please click on the 'Generate Parameters' button!
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
new_auth_redirect_uri = (
|
||||
urllib.parse.urlparse(web_base_url).scheme # type: ignore
|
||||
+ "://"
|
||||
+ urllib.parse.urlparse(web_base_url).netloc # type: ignore
|
||||
+ urllib.parse.urlparse(auth_redirect_uri.value).path # type: ignore
|
||||
)
|
||||
new_qr_redirect_uri = (
|
||||
urllib.parse.urlparse(web_base_url).scheme # type: ignore
|
||||
+ "://"
|
||||
+ urllib.parse.urlparse(web_base_url).netloc # type: ignore
|
||||
+ urllib.parse.urlparse(qr_redirect_uri.value).path # type: ignore
|
||||
)
|
||||
|
||||
# 设置应用参数中的回调链接地址
|
||||
auth_redirect_uri.write({"value": new_auth_redirect_uri}) # type: ignore
|
||||
qr_redirect_uri.write({"value": new_qr_redirect_uri}) # type: ignore
|
||||
|
||||
auth_endpoint = "https://open.weixin.qq.com/connect/oauth2/authorize"
|
||||
qr_auth_endpoint = "https://open.work.weixin.qq.com/wwopen/sso/qrConnect"
|
||||
|
||||
try:
|
||||
providers = (
|
||||
self.env["auth.oauth.provider"]
|
||||
.sudo()
|
||||
.search(
|
||||
[
|
||||
"|",
|
||||
("enabled", "=", True),
|
||||
("enabled", "=", False),
|
||||
]
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
providers = []
|
||||
|
||||
for provider in providers:
|
||||
if auth_endpoint in provider["auth_endpoint"]:
|
||||
provider.write(
|
||||
{
|
||||
# "client_id": client_id,
|
||||
"validation_endpoint": auth_redirect_uri,
|
||||
"enabled": True,
|
||||
}
|
||||
)
|
||||
if qr_auth_endpoint in provider["auth_endpoint"]:
|
||||
provider.write(
|
||||
{
|
||||
# "client_id": client_id,
|
||||
"validation_endpoint": qr_redirect_uri,
|
||||
"enabled": True,
|
||||
}
|
||||
)
|
||||
else:
|
||||
raise UserError(_("Please click on the 'Generate Parameters' button!"))
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,21 +0,0 @@
|
||||
/** @odoo-module **//** @odoo-module **/
|
||||
|
||||
import publicWidget from "@web/legacy/js/public/public_widget";
|
||||
// import * as ww from '@wecom/jssdk'
|
||||
|
||||
publicWidget.registry.WecomJion = publicWidget.Widget.extend({
|
||||
selector: 'form.oe_login_form',
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.rpc = this.bindService("rpc");
|
||||
},
|
||||
start: function () {
|
||||
this.companies = this.rpc({
|
||||
route: "/wecom_login_info",
|
||||
params: {
|
||||
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
</templates>
|
||||
@@ -1,20 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import publicWidget from "@web/legacy/js/public/public_widget";
|
||||
// import * as ww from '@wecom/jssdk'
|
||||
|
||||
publicWidget.registry.WecomLogin = publicWidget.Widget.extend({
|
||||
selector: '.o_login_auth',
|
||||
|
||||
events: {
|
||||
'click a': '_onClick',
|
||||
},
|
||||
jsLibs: [
|
||||
document.location.protocol + "//wwcdn.weixin.qq.com/node/open/js/wecom-jssdk-1.3.1.js",
|
||||
],
|
||||
_onClick: async function (ev) {
|
||||
ev.preventDefault(); //阻止默认行为
|
||||
let auth_url = $(ev.currentTarget).attr('href');
|
||||
console.log(auth_url);
|
||||
},
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
</templates>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_oauth_provider_tree" model="ir.ui.view">
|
||||
<field name="name">auth.oauth.provider.tree</field>
|
||||
<field name="model">auth.oauth.provider</field>
|
||||
<field name="inherit_id" ref="auth_oauth.view_oauth_provider_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//field[last()]" position="after">
|
||||
<field name="is_woecom" />
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_oauth_provider_form" model="ir.ui.view">
|
||||
<field name="name">auth.oauth.provider.form</field>
|
||||
<field name="model">auth.oauth.provider</field>
|
||||
<field name="inherit_id" ref="auth_oauth.view_oauth_provider_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//group[last()]" position="after">
|
||||
<group>
|
||||
<field name="is_woecom" />
|
||||
<field name="description" />
|
||||
</group>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,130 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">Settings</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="inherit_id" ref="wecom_contacts.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<div id="wecom_auth_oauth_warning" position="replace"/>
|
||||
|
||||
<div id="wecom_auth_oauth_install" position="after">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" invisible="not is_wecom_organization">
|
||||
<div class="o_setting_left_pane">
|
||||
<i class="fa fa-puzzle-piece fa-lg" aria-label="Authentication Application"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="auth_app_id" string="Binding Auth Application"/>
|
||||
<div class="content-group">
|
||||
<div class="mt8">
|
||||
<field name="auth_app_id" class="w-100" widget="selection" domain="['&',('type_code', 'like', '%auth%'),('company_id', '=', company_id)]"/>
|
||||
</div>
|
||||
<button name="%(wecom_base.action_view_wecom_apps_list)d" icon="fa-arrow-right" type="action" string="Management application" class="btn-link" invisible="not auth_app_id"/>
|
||||
<button class="btn btn-primary" name="get_app_info" string="Get application info" type="object" icon="fa-cloud-download" invisible="not auth_app_id" context="{'app': 'auth'}"/>
|
||||
<div class="mt4" invisible="not auth_app_id">
|
||||
<div class="text-warning mt8">
|
||||
<strong>Save</strong> this page and come back here to set up other feature.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" invisible="not auth_app_id">
|
||||
<div class="o_setting_left_pane">
|
||||
<i class="fa fa-list-ol fa-lg" aria-label="Auth AgentId"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="auth_agentid"/>
|
||||
<div class="row">
|
||||
<div class="text-muted col-md-12">
|
||||
Application Management→Application→Open the application that needs to be verified and authorized, copy 'AgentId'
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="mt8">
|
||||
<field name="auth_agentid" widget="integer" options="{'format': false}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" invisible="not auth_app_id">
|
||||
<div class="o_setting_left_pane">
|
||||
<i class="fa fa-key fa-lg" aria-label="Auth Secret"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="auth_secret"/>
|
||||
<div class="row">
|
||||
<div class="text-muted col-md-12">
|
||||
Application Management→Application→Open the application that needs to be verified and authorized, copy 'Secret'
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="mt8">
|
||||
<field name="auth_secret" widget="password_char"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 身份认证应用的JSAPI临时凭证 -->
|
||||
<div class="col-12 col-lg-12 o_setting_box" id="auth_app_jsapi_ticket_container" invisible="not auth_app_id">
|
||||
<div class="o_setting_left_pane">
|
||||
<i class="fa fa-ticket fa-lg" role="img" aria-label="JSAPI Ticket"></i>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="auth_app_jsapi_ticket" />
|
||||
<div class="text-muted">
|
||||
Is a temporary ticket for the H5 application to call the Wecom JS interface. Under normal circumstances, <b>the validity period of jsapi_ticket is 7200 seconds</b>, obtained by access_token. Since the number of API calls to the jsapi_ticket is very limited (an enterprise can get up to 400 times in an hour, and a single application cannot get more than 100 times), frequent refresh jsapi_ticket will cause the API call to be limited, affecting their own business, <b>and developers must cache jsapi_ticket in their own service global cache</b>.
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="mt8">
|
||||
<field name="auth_app_jsapi_ticket" readonly="1" force_save="1" widget="CopyClipboardText" class="w-100"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="mt8 d-flex justify-content-between">
|
||||
<label for="auth_app_jsapi_ticket_expiration_time" string="Expiration time"/>
|
||||
<field name="auth_app_jsapi_ticket_expiration_time" readonly="1" force_save="1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-group mt8">
|
||||
<button class="oe_highlight" name="get_auth_app_jsapi_ticket" type="object" string="Get JSAPI Ticket" icon="fa-ticket" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-12 o_setting_box" invisible="not auth_app_id">
|
||||
<div class="o_setting_left_pane">
|
||||
<i class="fa fa-cogs fa-lg" aria-label="Authentication Configuration"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<!-- <label for="auth_app_config_ids"/> -->
|
||||
<div class="row">
|
||||
<div class="content-group">
|
||||
<!-- <div class="mt4">
|
||||
<field name="auth_app_config_ids" widget="one2many" class="w-100" t-translation="off">
|
||||
<tree create="0" edit="true" delete="0" editable="bottom">
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="key" readonly="1"/>
|
||||
<field name="value" required="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div> -->
|
||||
<div class="btn-group float-right" role="group" aria-label="Button group">
|
||||
<!-- <button class="btn btn-outline-primary" name="generate_parameters" context="{'code': 'auth'}" string="Generate parameters" type="object" icon="fa-cogs" /> -->
|
||||
<button class="btn btn-outline-primary" name="enable_wecom_quick_login" type="object" string="Enable WeCom quick login" icon="fa-sign-in"/>
|
||||
<button class="btn btn-outline-primary" type="action" name="%(auth_oauth.action_oauth_provider)d" string="View OAuth providers" icon="fa-arrow-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
def post_init_hook(env):
|
||||
# env["ir.http"].clear_caches()
|
||||
# env["ir.ui.view"].clear_caches()
|
||||
env["ir.http"]._invalidate_cache()
|
||||
env["ir.ui.view"]._invalidate_cache()
|
||||
@@ -1,49 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
{
|
||||
"name": "WeCom Base",
|
||||
"author": "RStudio",
|
||||
"sequence": 601,
|
||||
"installable": True,
|
||||
"application": True,
|
||||
"auto_install": False,
|
||||
"category": "WeCom Suites/Settings",
|
||||
"website": "https://gitee.com/rainbowstudio/wecom",
|
||||
"version": "17.0.0.1",
|
||||
"summary": """
|
||||
|
||||
""",
|
||||
"description": """
|
||||
|
||||
""",
|
||||
"depends": ["base_setup", "wecom_widget", "wecom_api"],
|
||||
"data": [
|
||||
"security/wecom_security.xml",
|
||||
"security/ir.model.access.csv",
|
||||
"data/ir_module_category_data.xml",
|
||||
"data/ir_config_parameter.xml",
|
||||
"data/ir_cron_data.xml",
|
||||
"data/wecom_app_type_data.xml",
|
||||
"views/ir_cron_views.xml",
|
||||
"views/res_config_settings_views.xml",
|
||||
"views/res_company_views.xml",
|
||||
"views/wecom_service_api_list_views.xml",
|
||||
"views/wecom_service_api_error_views.xml",
|
||||
"views/wecom_apps_views.xml",
|
||||
"views/wecom_app_config_views.xml",
|
||||
"views/wecom_app_callback_service_views.xml",
|
||||
"views/wecom_app_event_type_views.xml",
|
||||
"views/wecom_app_type_views.xml",
|
||||
"views/wecom_app_subtype_views.xml",
|
||||
"views/wecom_base_views.xml",
|
||||
"views/menu_views.xml",
|
||||
],
|
||||
"assets": {
|
||||
"web.assets_backend": [
|
||||
"wecom_base/static/src/views/**/*",
|
||||
],
|
||||
},
|
||||
"license": "AGPL-3",
|
||||
"bootstrap": True,
|
||||
"post_init_hook": "post_init_hook",
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
</data>
|
||||
<data noupdate="1">
|
||||
|
||||
<function model="ir.config_parameter" name="set_param" eval="('wecom.debug_enabled', 'True')"/>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record forcecreate="True" id="ir_cron_pull_wecom_service_api_error" model="ir.cron">
|
||||
<field name="name">WeCom: Get the global error code of WeCom.</field>
|
||||
<field name="model_id" ref="model_wecom_service_api_error"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.cron_pull_global_error_code()</field>
|
||||
<field name="user_id" ref="base.user_root" />
|
||||
<field name="interval_number">7</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="active" eval="True"/>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="True" id="ir_cron_get_app_token" model="ir.cron">
|
||||
<field name="name">WeCom: Get app token</field>
|
||||
<field name="model_id" ref="model_wecom_apps"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.cron_get_app_token()</field>
|
||||
<field name="user_id" ref="base.user_root" />
|
||||
<field name="interval_number">2</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="active" eval="True"/>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="ir.module.category" id="module_category_wecom_settings">
|
||||
<field name="name">WeCom Settings</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="visible" eval="1" />
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="base.main_company" model="res.company">
|
||||
<field name="short_name">My Company</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,186 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<data noupdate="0">
|
||||
<!-- 类型 -->
|
||||
|
||||
<!-- 类型:管理工具 -->
|
||||
<record id="wecom_app_type_manage" model="wecom.app.type">
|
||||
<field name="name">Manage Tools</field>
|
||||
<field name="code">manage</field>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
|
||||
<!-- 类型:基础应用 -->
|
||||
<record id="wecom_app_type_base" model="wecom.app.type">
|
||||
<field name="name">Base application</field>
|
||||
<field name="code">base</field>
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
|
||||
<!-- 类型:外部联系-->
|
||||
<record id="wecom_app_type_external_contact" model="wecom.app.type">
|
||||
<field name="name">External contact</field>
|
||||
<field name="code">external_contact</field>
|
||||
<field name="sequence">3</field>
|
||||
</record>
|
||||
|
||||
<!-- 类型:自建应用 -->
|
||||
<record id="wecom_app_type_self" model="wecom.app.type">
|
||||
<field name="name">Self built application</field>
|
||||
<field name="code">self</field>
|
||||
<field name="sequence">4</field>
|
||||
</record>
|
||||
|
||||
<!-- 类型:第三方应用 -->
|
||||
<record id="wecom_app_type_third" model="wecom.app.type">
|
||||
<field name="name">Third party application</field>
|
||||
<field name="code">third</field>
|
||||
<field name="sequence">5</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 -->
|
||||
|
||||
<!-- 子类型 :通讯录管理-->
|
||||
<record id="wecom_app_subtype_contacts_management" model="wecom.app.subtype">
|
||||
<field name="name">Contacts management</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_manage"/>
|
||||
<field name="code">contacts</field>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 :客户联系 -->
|
||||
<record id="wecom_app_subtype_customer_contact" model="wecom.app.subtype">
|
||||
<field name="name">Customer contact</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_external_contact"/>
|
||||
<field name="code">customer</field>
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 :微信客服 -->
|
||||
<record id="wecom_app_subtype_kf" model="wecom.app.subtype">
|
||||
<field name="name">Wechat customer service</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">kf</field>
|
||||
<field name="sequence">3</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 身份验证 -->
|
||||
<record id="wecom_app_subtype_auth" model="wecom.app.subtype">
|
||||
<field name="name">Authentication</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_self"/>
|
||||
<field name="code">auth</field>
|
||||
<field name="sequence">4</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 应用管理 -->
|
||||
<record id="wecom_app_subtype_app" model="wecom.app.subtype">
|
||||
<field name="name">Application management</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_self"/>
|
||||
<field name="code">app</field>
|
||||
<field name="sequence">5</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 消息推送 -->
|
||||
<record id="wecom_app_subtype_message_push" model="wecom.app.subtype">
|
||||
<field name="name">Message push</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_self"/>
|
||||
<field name="code">message</field>
|
||||
<field name="sequence">6</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 素材管理 -->
|
||||
<record id="wecom_app_subtype_material_management" model="wecom.app.subtype">
|
||||
<field name="name">Material management</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_self"/>
|
||||
<field name="code">material</field>
|
||||
<field name="sequence">7</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : OA-打卡 -->
|
||||
<record id="wecom_app_subtype_oa_checkin" model="wecom.app.subtype">
|
||||
<field name="name">OA:Checkin</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">checkin</field>
|
||||
<field name="sequence">8</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : OA-审批 -->
|
||||
<record id="wecom_app_subtype_oa_approval" model="wecom.app.subtype">
|
||||
<field name="name">OA:Approval</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">approval</field>
|
||||
<field name="sequence">9</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : OA-汇报 -->
|
||||
<record id="wecom_app_subtype_oa_journal" model="wecom.app.subtype">
|
||||
<field name="name">OA:Journal</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">journal</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : OA-会议室 -->
|
||||
<record id="wecom_app_subtype_oa_meetingroom" model="wecom.app.subtype">
|
||||
<field name="name">OA:Meeting room</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">meetingroom</field>
|
||||
<field name="sequence">11</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 效率工具-企业邮箱 -->
|
||||
<record id="wecom_app_subtype_efficiency_tools_exmail" model="wecom.app.subtype">
|
||||
<field name="name">Efficiency tools:Corporate email</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">exmail</field>
|
||||
<field name="sequence">12</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 效率工具-日程 -->
|
||||
<record id="wecom_app_subtype_efficiency_tools_calendar" model="wecom.app.subtype">
|
||||
<field name="name">Efficiency tools:Calendar</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">calendar</field>
|
||||
<field name="sequence">13</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 效率工具-直播 -->
|
||||
<record id="wecom_app_subtype_efficiency_tools_living" model="wecom.app.subtype">
|
||||
<field name="name">Efficiency tools:Living</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">living</field>
|
||||
<field name="sequence">14</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 效率工具-微盘 -->
|
||||
<record id="wecom_app_subtype_efficiency_tools_wedrive" model="wecom.app.subtype">
|
||||
<field name="name">Efficiency tools:We Drive</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">wedrive</field>
|
||||
<field name="sequence">15</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 效率工具-公费电话 -->
|
||||
<record id="wecom_app_subtype_efficiency_tools_dial" model="wecom.app.subtype">
|
||||
<field name="name">Efficiency tools:Public telephone</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_base"/>
|
||||
<field name="code">dial</field>
|
||||
<field name="sequence">16</field>
|
||||
</record>
|
||||
|
||||
<!-- 子类型 : 会话内容存档 -->
|
||||
<record id="wecom_app_subtype_msgaudit" model="wecom.app.subtype">
|
||||
<field name="name">Session content archiving</field>
|
||||
<field name="parent_id" ref="wecom_base.wecom_app_type_manage"/>
|
||||
<field name="code">msgaudit</field>
|
||||
<field name="sequence">17</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
|
||||
</data>
|
||||
|
||||
|
||||
</odoo>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import ir_http
|
||||
|
||||
from . import wecom_server_api
|
||||
from . import wecom_server_api_error
|
||||
from . import wecom_server_api_list
|
||||
|
||||
from . import ir_module
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import res_partner
|
||||
|
||||
from . import wecom_app_type
|
||||
from . import wecom_app_subtype
|
||||
from . import wecom_apps
|
||||
from . import wecom_app_callback_service
|
||||
from . import wecom_app_config
|
||||
from . import wecom_app_event_type
|
||||
@@ -1,25 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models
|
||||
from odoo.http import request
|
||||
|
||||
class Http(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def session_info(self):
|
||||
session_info = super(Http, self).session_info() # type: ignore
|
||||
user = request.env.user # type: ignore
|
||||
if self.env.user.has_group('base.group_user'):
|
||||
allowed_companies = session_info["user_companies"]["allowed_companies"]
|
||||
|
||||
allowed_companies = {
|
||||
comp.id: {
|
||||
'id': comp.id,
|
||||
'name': comp.name,
|
||||
'is_wecom_organization': comp.is_wecom_organization,
|
||||
'sequence': comp.sequence,
|
||||
} for comp in user.company_ids
|
||||
}
|
||||
session_info["user_companies"]["allowed_companies"] = allowed_companies
|
||||
|
||||
return session_info
|
||||
@@ -1,15 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
|
||||
|
||||
class Module(models.Model):
|
||||
_inherit = "ir.module.module"
|
||||
|
||||
def wecom_check_module_installed(self, modules_name):
|
||||
# modules 输入参数是个 list,如 ['base', 'sale']
|
||||
module = self.sudo().search([("name", "=", modules_name)])
|
||||
if not module or module.state != "installed": # type: ignore
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -1,23 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
# 基础
|
||||
short_name = fields.Char("Short Name", translate=True)
|
||||
is_wecom_organization = fields.Boolean("WeCom organization", default=False)
|
||||
corpid = fields.Char("Corp ID")
|
||||
|
||||
# 企业的jsapi_ticket
|
||||
corp_jsapi_ticket = fields.Char(string="Corp JSAPI Ticket", copy=False)
|
||||
corp_jsapi_ticket_expiration_time = fields.Datetime(
|
||||
string="Expiration time of Corp JSAPI ticket", copy=False
|
||||
)
|
||||
@@ -1,167 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from random import weibullvariate
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.addons.wecom_api.api.wecom_abstract_api import ApiException # type: ignore
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
# 基础
|
||||
is_wecom_organization = fields.Boolean(
|
||||
related="company_id.is_wecom_organization", readonly=False
|
||||
)
|
||||
company_name = fields.Char(
|
||||
related="company_id.name", string="Company Name", readonly=False
|
||||
)
|
||||
short_name = fields.Char(related="company_id.short_name", readonly=False)
|
||||
corpid = fields.Char(string="Corp ID", related="company_id.corpid", readonly=False)
|
||||
|
||||
corp_jsapi_ticket = fields.Char(
|
||||
related="company_id.corp_jsapi_ticket",
|
||||
readonly=False,
|
||||
)
|
||||
corp_jsapi_ticket_expiration_time = fields.Datetime(
|
||||
related="company_id.corp_jsapi_ticket_expiration_time",
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
debug_enabled = fields.Boolean("Turn on debug mode", default=True)
|
||||
|
||||
resources_path = fields.Char(
|
||||
"WeCom resources storage path",
|
||||
config_parameter="wecom.resources_path",
|
||||
)
|
||||
|
||||
global_error_code_url = fields.Char(
|
||||
"Global error code page URL",
|
||||
config_parameter="wecom.global_error_code_url",
|
||||
)
|
||||
|
||||
global_error_code_item_selection_code = fields.Char(
|
||||
"Global error code item selection code",
|
||||
config_parameter="wecom.global_error_code_item_selection_code",
|
||||
) # 错误码排查方法的选取属性。
|
||||
|
||||
module_web_theme = fields.Boolean("Rainbow Theme")
|
||||
module_wecom_contacts = fields.Boolean("WeCom Contacts")
|
||||
module_wecom_contacts_sync = fields.Boolean("WeCom Contacts Synchronized")
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
res = super(ResConfigSettings, self).get_values() # type: ignore
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
|
||||
debug_enabled = (
|
||||
True if ir_config.get_param("wecom.debug_enabled") == "True" else False
|
||||
)
|
||||
|
||||
res.update(
|
||||
debug_enabled=debug_enabled,
|
||||
)
|
||||
return res
|
||||
|
||||
def set_values(self):
|
||||
super(ResConfigSettings, self).set_values() # type: ignore
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
ir_config.set_param("wecom.debug_enabled", self.debug_enabled or "False")
|
||||
|
||||
def open_wecom_company(self):
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"name": "My Company",
|
||||
"view_mode": "form",
|
||||
"res_model": "res.company",
|
||||
"res_id": self.env.company.id,
|
||||
"target": "current",
|
||||
"context": {
|
||||
"form_view_initial_mode": "edit",
|
||||
},
|
||||
}
|
||||
|
||||
@api.depends("company_id")
|
||||
def _compute_wecom_company_corpid(self):
|
||||
company_corpid = self.company_id.corpid if self.company_id.corpid else "" # type: ignore
|
||||
|
||||
for record in self:
|
||||
record.wecom_company_corpid = company_corpid # type: ignore
|
||||
|
||||
@api.model
|
||||
def open_wecom_settings(self):
|
||||
action = self.env["ir.actions.actions"]._for_xml_id(
|
||||
"wecom_base.res_config_settings_view_form"
|
||||
)
|
||||
action["target"] = "new"
|
||||
|
||||
return action
|
||||
|
||||
def get_app_info(self):
|
||||
"""
|
||||
获取企业应用信息
|
||||
:param agentid:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def get_corp_jsapi_ticket(self):
|
||||
"""
|
||||
获取企业的jsapi_ticket
|
||||
:return:
|
||||
"""
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
debug = ir_config.get_param("wecom.debug_enabled")
|
||||
if debug:
|
||||
_logger.info(
|
||||
_("Start getting ticket for company [%s]") % (self.company_id.name) # type: ignore
|
||||
)
|
||||
try:
|
||||
if (
|
||||
self.corp_jsapi_ticket_expiration_time
|
||||
and self.corp_jsapi_ticket_expiration_time > datetime.now()
|
||||
):
|
||||
# 未过期,
|
||||
# print("未过期")
|
||||
msg = {
|
||||
"title": _("Tips"),
|
||||
"message": _("Ticket is still valid, and no update is required!"),
|
||||
"sticky": False,
|
||||
}
|
||||
return self.env["wecomapi.tools.action"].WecomInfoNotification(msg)
|
||||
else:
|
||||
# print("过期")
|
||||
wecom_api = self.env["wecom.service_api"].InitServiceApi(self.company_id.corpid, self.contacts_app_id.secret) # type: ignore
|
||||
response = wecom_api.httpCall(
|
||||
self.env["wecom.service_api_list"].get_server_api_call(
|
||||
"GET_JSAPI_TICKET"
|
||||
),
|
||||
{},
|
||||
)
|
||||
# print(response)
|
||||
except ApiException as ex:
|
||||
return self.env["wecomapi.tools.action"].ApiExceptionDialog(
|
||||
ex, raise_exception=True
|
||||
)
|
||||
else:
|
||||
if response["errcode"] == 0:
|
||||
self.write(
|
||||
{
|
||||
"corp_jsapi_ticket": response["ticket"],
|
||||
"corp_jsapi_ticket_expiration_time": datetime.now()
|
||||
+ timedelta(seconds=response["expires_in"]),
|
||||
}
|
||||
)
|
||||
msg = {
|
||||
"title": _("Tips"),
|
||||
"message": _("Successfully obtained ticket!"),
|
||||
"sticky": False,
|
||||
"next": {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "reload",
|
||||
},
|
||||
}
|
||||
return self.env["wecomapi.tools.action"].WecomSuccessNotification(msg)
|
||||
@@ -1,7 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class Partner(models.Model):
|
||||
_inherit = "res.partner"
|
||||
@@ -1,103 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class WeComAppCallbackService(models.Model):
|
||||
"""
|
||||
接收事件服务器配置
|
||||
https://work.weixin.qq.com/api/doc/90000/90135/90930
|
||||
"""
|
||||
|
||||
_name = "wecom.app_callback_service"
|
||||
_description = "Wecom Application receive event service"
|
||||
|
||||
app_id = fields.Many2one(
|
||||
"wecom.apps",
|
||||
string="Application",
|
||||
copy=False,
|
||||
ondelete="cascade",
|
||||
default=lambda self: self.env["wecom.apps"].id,
|
||||
# domain="[('company_id', '=', company_id)]",
|
||||
required=True,
|
||||
)
|
||||
|
||||
name = fields.Char(string="Service Name", required=True, translate=True)
|
||||
code = fields.Char(
|
||||
string="Service Code",
|
||||
copy=False,
|
||||
required=True,
|
||||
)
|
||||
|
||||
callback_url = fields.Char(
|
||||
string="URL",
|
||||
store=True,
|
||||
readonly=True,
|
||||
compute="_default_callback_url",
|
||||
copy=False,
|
||||
) # 回调服务地址
|
||||
callback_url_token = fields.Char(string="Token", copy=False) # Token用于计算签名
|
||||
callback_aeskey = fields.Char(string="AES Key", copy=False) # 用于消息内容加密
|
||||
|
||||
description = fields.Text(string="Description", translate=True, copy=True)
|
||||
active = fields.Boolean("Active", default=False)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"app_code_uniq",
|
||||
"unique (app_id,code)",
|
||||
_("The code of each application must be unique !"),
|
||||
)
|
||||
]
|
||||
|
||||
@api.depends("app_id", "code")
|
||||
def _default_callback_url(self):
|
||||
"""
|
||||
默认回调地址
|
||||
:return:"""
|
||||
params = self.env["ir.config_parameter"].sudo()
|
||||
base_url = params.get_param("web.base.url")
|
||||
callback_url = ""
|
||||
for server in self:
|
||||
if server.app_id.company_id and server.code: # type: ignore
|
||||
callback_url = base_url + "/wecom_callback/%s/%s" % ( # type: ignore
|
||||
server.app_id.company_id.id, # type: ignore
|
||||
server.code, # type: ignore
|
||||
)
|
||||
|
||||
server.callback_url = callback_url # type: ignore
|
||||
|
||||
@api.onchange("app_id", "code")
|
||||
def _onchange_callback_url(self):
|
||||
"""
|
||||
当应用发生变化时,更新回调服务地址
|
||||
:return:
|
||||
"""
|
||||
params = self.env["ir.config_parameter"].sudo()
|
||||
base_url = params.get_param("web.base.url")
|
||||
|
||||
if self.app_id:
|
||||
self.callback_url = base_url + "/wecom_callback/%s/%s" % (
|
||||
self.app_id.company_id.id, # type: ignore
|
||||
self.code,
|
||||
)
|
||||
else:
|
||||
self.callback_url = ""
|
||||
|
||||
def generate_contact_service(self):
|
||||
"""
|
||||
生成通讯录回调服务
|
||||
:return:
|
||||
"""
|
||||
params = self.env["ir.config_parameter"].sudo()
|
||||
base_url = params.get_param("web.base.url")
|
||||
if not self.app_id:
|
||||
raise ValidationError(_("Please bind contact app!"))
|
||||
else:
|
||||
callback_url = base_url + "/wecom_callback/%s/%s" % (
|
||||
self.app_id.company_id.id, # type: ignore
|
||||
self.code,
|
||||
)
|
||||
return callback_url
|
||||
@@ -1,161 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models, api, _
|
||||
from odoo.tools import config, ormcache, mute_logger
|
||||
|
||||
FIELD_TYPES = [(key, key) for key in sorted(fields.Field.by_type)]
|
||||
|
||||
|
||||
class WeComAppConfig(models.Model):
|
||||
_name = "wecom.app_config"
|
||||
_description = "Wecom Application Configuration"
|
||||
_table = "wecom_app_config"
|
||||
# _rec_name = "key"
|
||||
_order = "id"
|
||||
|
||||
app_id = fields.Many2one( # type: ignore
|
||||
"wecom.apps",
|
||||
string="Application",
|
||||
copy=False,
|
||||
ondelete="cascade",
|
||||
default=lambda self: self.env["wecom.apps"].id,
|
||||
# domain="[('company_id', '=', company_id)]",
|
||||
required=True,
|
||||
)
|
||||
company_id = fields.Many2one(related="app_id.company_id", store=True)
|
||||
name = fields.Char(string="Name", translate=True, required=True, copy=True)
|
||||
key = fields.Char(
|
||||
required=True,
|
||||
)
|
||||
ttype = fields.Selection(
|
||||
selection=FIELD_TYPES, string="Field Type", required=True, copy=True
|
||||
)
|
||||
value = fields.Text(required=True)
|
||||
description = fields.Html(string="Description", translate=True, copy=True)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"app_id_key_uniq",
|
||||
"unique (app_id,key)",
|
||||
_("The key of each application must be unique !"),
|
||||
)
|
||||
]
|
||||
|
||||
def get_format_field_value_and_type(self, res_id=False, field_name=""):
|
||||
"""
|
||||
JS 获取需要格式化的字段的类型和值
|
||||
:return:
|
||||
"""
|
||||
res = self.browse(res_id)
|
||||
return res.ttype # type: ignore
|
||||
# return {
|
||||
# "id": res_id,
|
||||
# "value_type": res.ttype,
|
||||
# "value": res.value,
|
||||
# }
|
||||
|
||||
def update_config(self, res_id=False, value=""):
|
||||
"""
|
||||
更新参数
|
||||
"""
|
||||
# res = self.browse(res_id)
|
||||
app_config = (
|
||||
self.env["wecom.app_config"]
|
||||
.sudo()
|
||||
.search(
|
||||
[
|
||||
("id", "=", res_id),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
app_config.sudo().write({"value": value})
|
||||
print(app_config.value)
|
||||
return app_config.value
|
||||
|
||||
@api.model
|
||||
def get_param(self, app_id, key, default=False):
|
||||
"""检索给定key的value
|
||||
|
||||
:param string key: 要检索的参数值的键。
|
||||
:param string default: 如果缺少参数,则为默认值。
|
||||
:return: 参数的值, 如果不存在,则为 ``default``.
|
||||
:rtype: string
|
||||
"""
|
||||
return self._get_param(app_id, key) or default
|
||||
|
||||
def _get_param(self, app_id, key):
|
||||
params = self.search_read(
|
||||
[("app_id", "=", app_id), ("key", "=", key)],
|
||||
fields=["ttype", "value"],
|
||||
limit=1,
|
||||
)
|
||||
value = None
|
||||
ttype = None
|
||||
if not params:
|
||||
# 如果没有找到,搜索其他公司相同应用配置参数 进行复制
|
||||
copy_app_config = self.search([("key", "=", key)], limit=1)
|
||||
if copy_app_config:
|
||||
new_app_config = self.create(
|
||||
{
|
||||
"app_id": app_id,
|
||||
"name": copy_app_config.name, # type: ignore
|
||||
"key": copy_app_config.key, # type: ignore
|
||||
"ttype": copy_app_config.ttype, # type: ignore
|
||||
"value": copy_app_config.value, # type: ignore
|
||||
"description": copy_app_config.description, # type: ignore
|
||||
}
|
||||
)
|
||||
value = new_app_config.value
|
||||
ttype = new_app_config.ttype
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
value = params[0]["value"]
|
||||
ttype = params[0]["ttype"]
|
||||
|
||||
if ttype == "boolean":
|
||||
boolean_value = str(value).lower()
|
||||
if boolean_value in ["true", "yes", "t", "1"]:
|
||||
return True
|
||||
elif boolean_value in ["false", "no", "f", "0"]:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
return value if params else None
|
||||
|
||||
@api.model
|
||||
def set_param(self, app_id, key, value):
|
||||
"""设置参数的值。
|
||||
|
||||
:param string key: 要设置的参数值的键。
|
||||
:param string value: 要设置的值。
|
||||
:return: 参数的上一个值,如果不存在,则为False。
|
||||
:rtype: string
|
||||
"""
|
||||
param = self.search([("app_id", "=", app_id), ("key", "=", key)])
|
||||
if param:
|
||||
old = param.value # type: ignore
|
||||
if value is not False and value is not None:
|
||||
if str(value) != old:
|
||||
param.write({"value": value}) # type: ignore
|
||||
else:
|
||||
param.unlink() # type: ignore
|
||||
return old
|
||||
else:
|
||||
if value is not False and value is not None:
|
||||
self.create({"app_id": app_id, "key": key, "value": value})
|
||||
return False
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
self._invalidate_cache()
|
||||
return super(WeComAppConfig, self).create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
self._invalidate_cache()
|
||||
return super(WeComAppConfig, self).write(vals)
|
||||
|
||||
def unlink(self):
|
||||
self._invalidate_cache()
|
||||
return super(WeComAppConfig, self).unlink()
|
||||
@@ -1,152 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import base64
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import MissingError, UserError, ValidationError, AccessError
|
||||
from odoo.tools.safe_eval import safe_eval, test_python_expr
|
||||
from lxml import etree
|
||||
from lxml import etree as ET
|
||||
from odoo.http import Response
|
||||
|
||||
# from lxml_to_dict import lxml_to_dict
|
||||
# from xmltodict import lxml_to_dict
|
||||
import xmltodict, json
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WeComAppEventType(models.Model):
|
||||
|
||||
_name = "wecom.app.event_type"
|
||||
_description = "Wecom Application Event"
|
||||
_order = "id"
|
||||
|
||||
DEFAULT_PYTHON_CODE = """# Available variables:
|
||||
# - env: Odoo Environment on which the action is triggered
|
||||
# - model: Odoo Model of the record on which the action is triggered; is a void recordset
|
||||
# - record: record on which the action is triggered; may be void
|
||||
# - records: recordset of all records on which the action is triggered in multi-mode; may be void
|
||||
# - time, datetime, dateutil, timezone: useful Python libraries
|
||||
# - float_compare: Odoo function to compare floats based on specific precisions
|
||||
# - log: log(message, level='info'): logging function to record debug information in ir.logging table
|
||||
# - UserError: Warning Exception to use with raise
|
||||
# To return an action, assign: action = {...}\n\n\n\n"""
|
||||
|
||||
name = fields.Char(
|
||||
string="Name",
|
||||
translate=True,
|
||||
copy=False,
|
||||
required=True,
|
||||
)
|
||||
model_ids = fields.Many2many(
|
||||
"ir.model",
|
||||
string="Related Model",
|
||||
)
|
||||
msg_type = fields.Char(
|
||||
string="Message Type", copy=False, required=True, default="event"
|
||||
)
|
||||
event = fields.Char(
|
||||
string="Event Code",
|
||||
copy=False,
|
||||
required=True,
|
||||
)
|
||||
change_type = fields.Char(string="Change Type", copy=False)
|
||||
code = fields.Char(
|
||||
string="Python Code",
|
||||
default="",
|
||||
)
|
||||
command = fields.Char(string="Command", copy=False)
|
||||
|
||||
def handle_event(self):
|
||||
"""
|
||||
处理事件
|
||||
"""
|
||||
xml_tree = self.env.context.get("xml_tree")
|
||||
company_id = self.env.context.get("company_id")
|
||||
event_str = etree.fromstring(xml_tree).find("Event").text # type: ignore
|
||||
changetype_str = etree.fromstring(xml_tree).find("ChangeType").text # type: ignore
|
||||
|
||||
_logger.info(
|
||||
_(
|
||||
"Received the callback notification of Wecom, event [%s], change type [%s]."
|
||||
)
|
||||
% (
|
||||
event_str,
|
||||
changetype_str,
|
||||
)
|
||||
)
|
||||
event = (
|
||||
self.env["wecom.app.event_type"]
|
||||
.sudo()
|
||||
.search(
|
||||
[
|
||||
("event", "=", event_str),
|
||||
("change_type", "=", changetype_str),
|
||||
]
|
||||
)
|
||||
)
|
||||
if not event:
|
||||
_logger.warning(
|
||||
_(
|
||||
"Cannot find [%s] change type for executing company [%s], ignoring it."
|
||||
)
|
||||
% (
|
||||
event.name,
|
||||
company_id.name,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if event.code:
|
||||
# ^ 正确响应企业微信本次的POST请求,企业微信将不会再次发送请求
|
||||
# ^ ·企业微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
|
||||
# ^ ·当接收成功后,http头部返回200表示接收ok,其他错误码企业微信后台会一律当做失败并发起重试
|
||||
try:
|
||||
event.with_context(
|
||||
xml_tree=xml_tree, company_id=company_id
|
||||
).sudo().run()
|
||||
except Exception as e:
|
||||
_logger.warning(
|
||||
_(
|
||||
"Unable to execute [%s] change type for company [%s], ignoring it. reason: %s"
|
||||
)
|
||||
% (event.name, company_id.name, str(e))
|
||||
)
|
||||
return Response("success", status=200)
|
||||
else:
|
||||
return Response("success", status=200)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
执行事件
|
||||
"""
|
||||
xml_tree = self.env.context.get("xml_tree")
|
||||
company_id = self.env.context.get("company_id")
|
||||
func_name = self.code.split("model.")[1]
|
||||
cmd = self.command
|
||||
|
||||
for m in self.model_ids:
|
||||
model_obj = self.env.get(m.model) # type: ignore
|
||||
if hasattr(model_obj, func_name):
|
||||
# 存在函数
|
||||
func = getattr(
|
||||
model_obj.with_context(xml_tree=xml_tree, company_id=company_id),
|
||||
func_name,
|
||||
)
|
||||
func(cmd)
|
||||
_logger.info(
|
||||
_("Method [%s] to execute model [%s]")
|
||||
% (
|
||||
func_name,
|
||||
model_obj,
|
||||
)
|
||||
)
|
||||
else:
|
||||
_logger.warning(
|
||||
_("Function [%s] does not exist in model [%s]")
|
||||
% (
|
||||
func_name,
|
||||
model_obj,
|
||||
)
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class WeComAppType(models.Model):
|
||||
_name = "wecom.app.subtype"
|
||||
_description = "Wecom Application Subtype"
|
||||
_order = "parent_id,sequence"
|
||||
|
||||
name = fields.Char(string="Name", translate=True, copy=False, required=True,)
|
||||
|
||||
parent_id = fields.Many2one(
|
||||
"wecom.app.type",
|
||||
ondelete="cascade",
|
||||
string="Parent",
|
||||
index=True,
|
||||
copy=False,
|
||||
required=True,
|
||||
)
|
||||
code = fields.Char(string="Code", copy=False, required=True,)
|
||||
sequence = fields.Integer(default=0, copy=True)
|
||||
|
||||
_sql_constraints = [("code_uniq", "unique (code)", _("Code must be unique !"),)]
|
||||
@@ -1,35 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class WeComAppType(models.Model):
|
||||
_name = "wecom.app.type"
|
||||
_description = "Wecom Application Type"
|
||||
_order = "sequence"
|
||||
|
||||
name = fields.Char(
|
||||
string="Name",
|
||||
translate=True,
|
||||
copy=False,
|
||||
required=True,
|
||||
)
|
||||
code = fields.Char(
|
||||
string="Code",
|
||||
copy=False,
|
||||
required=True,
|
||||
)
|
||||
subtype_ids = fields.One2many(
|
||||
"wecom.app.subtype",
|
||||
"parent_id",
|
||||
string="Application Subtype",
|
||||
)
|
||||
sequence = fields.Integer(default=0, copy=True)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"code_uniq",
|
||||
"unique (code)",
|
||||
_("Code must be unique !"),
|
||||
)
|
||||
]
|
||||
@@ -1,432 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.addons.wecom_api.api.wecom_abstract_api import ApiException # type: ignore
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WeComApps(models.Model):
|
||||
_name = "wecom.apps"
|
||||
_description = "Wecom Application"
|
||||
_order = "sequence"
|
||||
|
||||
name = fields.Char(
|
||||
string="Name",
|
||||
copy=False,
|
||||
compute="_compute_name",
|
||||
store=True,
|
||||
index=True,
|
||||
) # 企业应用名称
|
||||
app_name = fields.Char(
|
||||
string="Application Name",
|
||||
translate=True,
|
||||
copy=True,
|
||||
) # 应用名称
|
||||
|
||||
company_id = fields.Many2one(
|
||||
"res.company",
|
||||
string="Company",
|
||||
domain="[('is_wecom_organization', '=', True)]",
|
||||
copy=False,
|
||||
store=True,
|
||||
)
|
||||
# model_ids = fields.Many2many("ir.model", string="Related Model",)
|
||||
# 应用类型 required=True
|
||||
type = fields.Selection(
|
||||
selection=lambda self: self._type_selection_values(),
|
||||
string="Application Type",
|
||||
required=True,
|
||||
copy=True,
|
||||
default="manage",
|
||||
)
|
||||
type_id = fields.Many2one("wecom.app.type", string="Application Types", store=True)
|
||||
|
||||
subtype_ids = fields.Many2many(
|
||||
"wecom.app.subtype",
|
||||
string="Application Subtype",
|
||||
)
|
||||
type_code = fields.Char(
|
||||
string="Application type code",
|
||||
store=True,
|
||||
readonly=True,
|
||||
compute="_computet_type_code",
|
||||
)
|
||||
|
||||
def write(self, vals):
|
||||
for app in self:
|
||||
labels = dict(self.fields_get(allfields=["type"])["type"]["selection"])[app.type] # type: ignore
|
||||
if "company_id" in vals:
|
||||
company = app.company_id # type: ignore
|
||||
vals["name"] = "%s/%s/%s" % (
|
||||
company.short_name,
|
||||
labels,
|
||||
app.app_name, # type: ignore
|
||||
)
|
||||
else:
|
||||
vals["name"] = "%s/%s" % (labels, app.app_name) # type: ignore
|
||||
result = super(WeComApps, self).write(vals)
|
||||
|
||||
return result
|
||||
|
||||
@api.depends("subtype_ids")
|
||||
def _computet_type_code(self):
|
||||
"""
|
||||
计算应用类型代码
|
||||
"""
|
||||
if self.subtype_ids:
|
||||
self.type_code = str(self.subtype_ids.mapped("code")) # type: ignore
|
||||
else:
|
||||
self.type_code = "[]"
|
||||
|
||||
def get_type_code(self):
|
||||
"""
|
||||
测试代码
|
||||
"""
|
||||
type_code = str(self.subtype_ids.mapped("code")) # type: ignore
|
||||
print(type_code, type(type_code))
|
||||
|
||||
@api.onchange("subtype_ids")
|
||||
def _onchange_subtype_ids(self):
|
||||
"""
|
||||
变更子类型
|
||||
:return:
|
||||
"""
|
||||
if self.type_id.code == "manage" or self.type_id.code == "base": # type: ignore
|
||||
if len(self.subtype_ids) > 1:
|
||||
raise UserError(
|
||||
_("Only one subtype can be selected for the current app type!")
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _type_selection_values(self):
|
||||
models = self.env["wecom.app.type"].sudo().search([]).sorted("sequence")
|
||||
return [(model.code, model.name) for model in models]
|
||||
|
||||
@api.onchange("type")
|
||||
def _onchange_type(self):
|
||||
# self.subtype_ids = False
|
||||
# self.type_code = ""
|
||||
# if self.type:
|
||||
# type = self.env["wecom.app.type"].sudo().search([("code", "=", self.type)])
|
||||
# self.type_id = type
|
||||
# return {"domain": {"subtype_ids": [("parent_id", "=", type.id)]}}
|
||||
# else:
|
||||
# self.type_id = False
|
||||
# return {"domain": {"subtype": []}}
|
||||
if self.subtype_ids:
|
||||
self.type_code = str(self.subtype_ids.mapped("code")) # type: ignore
|
||||
else:
|
||||
self.type_code = "[]"
|
||||
|
||||
agentid = fields.Integer(string="Agent ID", copy=False) # 企业应用id
|
||||
secret = fields.Char("Secret", default="", copy=False)
|
||||
square_logo_url = fields.Char(string="Square Logo", copy=True) # 企业应用方形头像
|
||||
description = fields.Text(string="Description", translate=True, copy=True) # 企业应用详情
|
||||
allow_userinfos = fields.Char(
|
||||
string="Visible range (personnel)", copy=False
|
||||
) # 企业应用可见范围(人员),其中包括userid
|
||||
allow_partys = fields.Char(
|
||||
string="Visible range (Department)", copy=False
|
||||
) # 企业应用可见范围(部门)
|
||||
allow_tags = fields.Char(string="Visible range (Tag))", 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
|
||||
) # 企业应用是否打开地理位置上报 0:不上报;1:进入会话上报;
|
||||
isreportenter = fields.Boolean(
|
||||
string="Report user entry event", copy=False
|
||||
) # 企业应用是否打开进入会话上报 0:不上报;1:进入会话上报;
|
||||
home_url = fields.Char(string="Home page", copy=True) # 企业应用主页url
|
||||
|
||||
sequence = fields.Integer(default=0, copy=True)
|
||||
menu_body = fields.Text(
|
||||
"Application menu data",
|
||||
translate=True,
|
||||
default="{}",
|
||||
copy=False,
|
||||
)
|
||||
|
||||
# 访问令牌
|
||||
access_token = fields.Char(string="Access Token", readonly=True, copy=False)
|
||||
token_expiration_time = fields.Datetime(
|
||||
string="Token Expiration Time", readonly=True, copy=False
|
||||
)
|
||||
|
||||
# 应用的jsapi_ticket
|
||||
jsapi_ticket = fields.Char(string="JSAPI Ticket", readonly=True, copy=False)
|
||||
jsapi_ticket_expiration_time = fields.Datetime(
|
||||
string="JSAPI Ticket Expiration Time", readonly=True, copy=False
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"company_id_key_uniq",
|
||||
"unique (company_id,app_name)",
|
||||
_("The application name of each company must be unique !"),
|
||||
)
|
||||
]
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values):
|
||||
res = super(WeComApps, self).create(values)
|
||||
if res.subtype_ids:
|
||||
res.type_code = str(res.subtype_ids.mapped("code"))
|
||||
else:
|
||||
res.type_code = "[]"
|
||||
return res
|
||||
|
||||
@api.depends("company_id", "app_name", "type")
|
||||
def _compute_name(self):
|
||||
for app in self:
|
||||
labels = dict(self.fields_get(allfields=["type"])["type"]["selection"])[app.type] # type: ignore
|
||||
if app.company_id: # type: ignore
|
||||
app.name = "%s/%s/%s" % (app.company_id.short_name,labels,app.app_name,) # type: ignore
|
||||
else:
|
||||
app.name = "%s/%s" % (labels, app.app_name) # type: ignore
|
||||
|
||||
# 回调服务
|
||||
app_callback_service_ids = fields.One2many(
|
||||
"wecom.app_callback_service",
|
||||
"app_id",
|
||||
string="Receive event service",
|
||||
domain="['|', ('active', '=', True), ('active', '=', False)]",
|
||||
context={"active_test": False},
|
||||
)
|
||||
|
||||
# 应用参数配置
|
||||
app_config_ids = fields.One2many(
|
||||
"wecom.app_config",
|
||||
"app_id",
|
||||
string="Application Configuration",
|
||||
# context={
|
||||
# "default_company_id": lambda self: self.company_id,
|
||||
# },
|
||||
) # 应用参数配置
|
||||
|
||||
# ————————————————————————————————————
|
||||
# 应用回调服务
|
||||
# ————————————————————————————————————
|
||||
def generate_service(self):
|
||||
"""
|
||||
生成回调服务
|
||||
:return:
|
||||
"""
|
||||
code = self.env.context.get("code") # 按钮的传递值
|
||||
if bool(code):
|
||||
# 存在按钮的传递值,通过按钮的传递值生成回调服务
|
||||
self.generate_service_by_code(code)
|
||||
else:
|
||||
# 不存在按钮的传递值,通过子类型生成生成回调服务
|
||||
self.generate_service_by_subtype()
|
||||
|
||||
def generate_service_by_subtype(self):
|
||||
"""
|
||||
通过子类型生成生成回调服务
|
||||
"""
|
||||
for record in self.subtype_ids:
|
||||
self.generate_service_by_code(record.code) # type: ignore
|
||||
|
||||
def generate_service_by_code(self, code):
|
||||
"""
|
||||
根据code生成回调服务
|
||||
:param code:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# ————————————————————————————————————
|
||||
# 应用参数配置
|
||||
# ————————————————————————————————————
|
||||
def generate_parameters(self):
|
||||
"""
|
||||
生成参数
|
||||
:return:
|
||||
"""
|
||||
code = self.env.context.get("code") # 按钮的传递值
|
||||
if bool(code):
|
||||
# 存在按钮的传递值,通过按钮的传递值生成回调服务
|
||||
self.generate_parameters_by_code(code)
|
||||
else:
|
||||
# 不存在按钮的传递值,通过子类型生成生成回调服务
|
||||
self.generate_parameters_by_subtype()
|
||||
|
||||
def generate_parameters_by_subtype(self):
|
||||
"""
|
||||
通过子类型生成生成参数
|
||||
"""
|
||||
for record in self.subtype_ids:
|
||||
self.generate_parameters_by_code(record.code) # type: ignore
|
||||
|
||||
def generate_parameters_by_code(self, code):
|
||||
"""
|
||||
根据code生成参数
|
||||
:param code:
|
||||
:retur
|
||||
"""
|
||||
|
||||
# ————————————————————————————————————
|
||||
# 应用信息
|
||||
# ————————————————————————————————————
|
||||
def get_app_info(self):
|
||||
"""
|
||||
获取企业应用信息
|
||||
:param agentid:
|
||||
:return:
|
||||
"""
|
||||
for record in self:
|
||||
try:
|
||||
wecomapi = self.env["wecom.service_api"].InitServiceApi(record.company_id.corpid, record.secret) # type: ignore
|
||||
response = wecomapi.httpCall(
|
||||
self.env["wecom.service_api_list"].get_server_api_call("AGENT_GET"),
|
||||
{"agentid": str(record.agentid)}, # type: ignore
|
||||
)
|
||||
except ApiException as e:
|
||||
return self.env["wecomapi.tools.action"].ApiExceptionDialog(
|
||||
e, raise_exception=True
|
||||
)
|
||||
else:
|
||||
if response["errcode"] == 0:
|
||||
record.write(
|
||||
{
|
||||
"app_name": response["name"],
|
||||
"square_logo_url": response["square_logo_url"],
|
||||
"description": response["description"],
|
||||
"allow_userinfos": response["allow_userinfos"]
|
||||
if "allow_userinfos" in response
|
||||
else "{}",
|
||||
"allow_partys": response["allow_partys"]
|
||||
if "allow_partys" in response
|
||||
else "{}",
|
||||
"allow_tags": response["allow_tags"]
|
||||
if "allow_tags" in response
|
||||
else "{}",
|
||||
"close": response["close"],
|
||||
"redirect_domain": response["redirect_domain"],
|
||||
"report_location_flag": response["report_location_flag"],
|
||||
"isreportenter": response["isreportenter"],
|
||||
"home_url": response["home_url"],
|
||||
}
|
||||
)
|
||||
# msg = {
|
||||
# "title": _("Tips"),
|
||||
# "message": _("Successfully obtained application information!"),
|
||||
# "sticky": False,
|
||||
# }
|
||||
# return self.env["wecomapi.tools.action"].WecomSuccessNotification(msg)
|
||||
|
||||
def set_app_info(self):
|
||||
"""
|
||||
设置企业应用信息
|
||||
:param agentid:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# ————————————————————————————————————
|
||||
# 应用令牌
|
||||
# ————————————————————————————————————
|
||||
def get_access_token(self):
|
||||
"""获取企业应用接口调用凭据(令牌)
|
||||
:return:
|
||||
"""
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
debug = ir_config.get_param("wecom.debug_enabled")
|
||||
if debug:
|
||||
_logger.info(
|
||||
_("Start getting app [%s] token for company [%s]")% (self.name, self.company_id.name) # type: ignore
|
||||
)
|
||||
try:
|
||||
wecom_api = self.env["wecom.service_api"].InitServiceApi(self.company_id.corpid, self.secret) # type: ignore
|
||||
except ApiException as ex:
|
||||
return self.env["wecomapi.tools.action"].ApiExceptionDialog(
|
||||
ex, raise_exception=True
|
||||
)
|
||||
# finally:
|
||||
# if self.token_expiration_time and self.token_expiration_time > datetime.now():
|
||||
# # 令牌未过期,则直接返回 提示信息
|
||||
# msg = {
|
||||
# "title": _("Tips"),
|
||||
# "message": _("Token is still valid, and no update is required!"),
|
||||
# "sticky": False,
|
||||
# }
|
||||
# return self.env["wecomapi.tools.action"].WecomInfoNotification(msg)
|
||||
|
||||
def cron_get_app_token(self):
|
||||
"""
|
||||
自动任务定时获取应用token
|
||||
"""
|
||||
for app in self.search([("company_id", "!=", False)]): # type: ignore
|
||||
_logger.info(
|
||||
_(
|
||||
"Automatic task: start to get the application [%s] token of company [%s]"
|
||||
)
|
||||
% (app.name, app.company_id.name)
|
||||
)
|
||||
app.get_access_token()
|
||||
|
||||
# ————————————————————————————————————
|
||||
# 应用 jsapi_ticket
|
||||
# ————————————————————————————————————
|
||||
def get_app_jsapi_ticket(self):
|
||||
"""
|
||||
获取应用的jsapi_ticket
|
||||
"""
|
||||
ir_config = self.env["ir.config_parameter"].sudo()
|
||||
debug = ir_config.get_param("wecom.debug_enabled")
|
||||
if debug:
|
||||
_logger.info(
|
||||
_("Start getting app [%s] ticket for company [%s]")
|
||||
% (self.name, self.company_id.name) # type: ignore
|
||||
)
|
||||
try:
|
||||
if (
|
||||
self.jsapi_ticket_expiration_time
|
||||
and self.jsapi_ticket_expiration_time > datetime.now()
|
||||
):
|
||||
# 未过期,
|
||||
# print("未过期")
|
||||
msg = {
|
||||
"title": _("Tips"),
|
||||
"message": _("Ticket is still valid, and no update is required!"),
|
||||
"sticky": False,
|
||||
}
|
||||
return self.env["wecomapi.tools.action"].WecomInfoNotification(msg)
|
||||
else:
|
||||
# print("过期")
|
||||
wecom_api = self.env["wecom.service_api"].InitServiceApi(
|
||||
self.company_id.corpid, self.secret # type: ignore
|
||||
)
|
||||
response = wecom_api.httpCall(
|
||||
self.env["wecom.service_api_list"].get_server_api_call(
|
||||
"AGENT_GET_TICKET"
|
||||
),
|
||||
{"type": "agent_config"},
|
||||
)
|
||||
except ApiException as ex:
|
||||
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"]),
|
||||
}
|
||||
)
|
||||
msg = {
|
||||
"title": _("Tips"),
|
||||
"message": _("Successfully obtained application ticket!"),
|
||||
"sticky": False,
|
||||
"next": {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "reload",
|
||||
},
|
||||
}
|
||||
finally:
|
||||
return self.env["wecomapi.tools.action"].WecomSuccessNotification(msg) # type: ignore
|
||||
@@ -1,7 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
|
||||
class WecomServerApi(models.TransientModel):
|
||||
_inherit = "wecom.service_api"
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import requests
|
||||
import logging
|
||||
from urllib.parse import quote, unquote
|
||||
import pandas as pd
|
||||
|
||||
pd.set_option("max_colwidth", 4096)
|
||||
|
||||
from lxml import etree
|
||||
import requests
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WecomServerApiError(models.Model):
|
||||
_inherit = "wecom.service_api_error"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user