add saas login
12
app_saas/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# please comment follow for produce
|
||||||
|
from . import models
|
||||||
|
from . import report
|
||||||
|
from . import controllers
|
||||||
|
|
||||||
|
# hook form install and uninstall
|
||||||
|
|
||||||
|
from .hooks import pre_init_hook
|
||||||
|
from .hooks import post_init_hook
|
||||||
|
from .hooks import uninstall_hook
|
||||||
85
app_saas/__manifest__.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Created on 2023-10-06
|
||||||
|
# author: 欧度智能,https://www.odooai.cn
|
||||||
|
# email: 300883@qq.com
|
||||||
|
# Copyright (C) 2009~2024 odooAi.cn
|
||||||
|
|
||||||
|
# Odoo16在线用户手册(长期更新)
|
||||||
|
# https://www.odooai.cn/documentation/16.0/zh_CN/index.html
|
||||||
|
|
||||||
|
# Odoo16在线开发者手册(长期更新)
|
||||||
|
# https://www.odooai.cn/documentation/16.0/zh_CN/developer.html
|
||||||
|
|
||||||
|
# 行业应用说明,应该是带 Industry 的就会放入
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Copyright (C) 2009-TODAY odooAi.cn Ltd. https://www.odooai.cn
|
||||||
|
# Author: Ivan Deng,300883@qq.com
|
||||||
|
# You can modify it under the terms of the GNU LESSER
|
||||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||||
|
# See <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies
|
||||||
|
# of the Software or modified copies of the Software.
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'odooapp.cn SaaS Client-Ai Passport.Odoo中文应用商店SaaS云服务客户端',
|
||||||
|
'version': '13.24.12.06',
|
||||||
|
'author': 'odooai.cn',
|
||||||
|
'category': 'Base',
|
||||||
|
'website': 'https://www.odooai.cn',
|
||||||
|
'live_test_url': 'https://demo.odooapp.cn',
|
||||||
|
'license': 'OPL-1',
|
||||||
|
'sequence': 2,
|
||||||
|
'price': 0.00,
|
||||||
|
'currency': 'EUR',
|
||||||
|
'images': ['static/description/banner.png'],
|
||||||
|
'summary': '''
|
||||||
|
Social Login SSO with Tiktok, Google, Azure, Facebook, Wechat, Taobao, Alipay, DingTalk, via odooapp.cn Ai Passport.
|
||||||
|
支持微信、抖音、QQ、淘宝、钉钉、支付宝、企业微信、Facebook、Google、微软Azure等整合登录。
|
||||||
|
''',
|
||||||
|
'description': '''
|
||||||
|
AiSaas云服务,使用Ai通行证,一键实现全社交媒体统一登录SSO。
|
||||||
|
支持微信、抖音、QQ、淘宝、钉钉、支付宝、企业微信、Facebook、Google、微软Azure等整合登录。
|
||||||
|
在Odoo中获取最新的中文翻译(会员专属),获取当前Odoo模块最新版本更新等。
|
||||||
|
支持Odoo中文版在线更新及 www.odooapp.cn 的信息推送等。
|
||||||
|
安装即代表同意我司云服务用户协议及隐私政策。 https://www.odooapp.cn/conditions
|
||||||
|
1. Ai通行证实现全社交媒体登录支持
|
||||||
|
2. 快速访问Odoo中文应用商店并获取独享Odoo中文通行码,可随时断开云服务连接
|
||||||
|
3. 一键获取最新Odoo中文应用模块与主题,最新行业服务包
|
||||||
|
4. 一键获取Odoo中文翻译更新(会员专属)
|
||||||
|
5. 快速获取Odoo服务,Odoo升级评估
|
||||||
|
6. 系统出错时一键提交,获取技术支持(会员专属)
|
||||||
|
11.多语言支持,多公司支持
|
||||||
|
12.仅从Odoo18-16企业版,社区版版本开始支持
|
||||||
|
13.代码完全开源
|
||||||
|
''',
|
||||||
|
'depends': [
|
||||||
|
'app_common',
|
||||||
|
'auth_oauth',
|
||||||
|
# 'website',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'data/auth_oauth_data.xml',
|
||||||
|
'views/auth_oauth_provider_views.xml',
|
||||||
|
'views/res_config_settings_views.xml',
|
||||||
|
],
|
||||||
|
'assets': {
|
||||||
|
'web.assets_frontend': [
|
||||||
|
# 'app_/static/src/scss/style.scss',
|
||||||
|
],
|
||||||
|
'web.assets_backend': [
|
||||||
|
# 'app_/static/src/js/*.js',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'demo': [
|
||||||
|
],
|
||||||
|
# 'pre_init_hook': 'pre_init_hook',
|
||||||
|
# 'post_init_hook': 'post_init_hook',
|
||||||
|
# 'uninstall_hook': 'uninstall_hook',
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
||||||
3
app_saas/controllers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# from . import main
|
||||||
25
app_saas/controllers/main.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# from odoo import http
|
||||||
|
# from odoo.addons.portal.controllers.portal import CustomerPortal
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class appCustomerPortal(CustomerPortal):
|
||||||
|
#
|
||||||
|
# # Controler sample
|
||||||
|
# @http.route('/my/webclient/locale/<string:lang>', type='http', auth="none")
|
||||||
|
# def index(self, **kw):
|
||||||
|
# return "Hello, world"
|
||||||
|
#
|
||||||
|
# @http.route('/default/default/objects/', auth='public')
|
||||||
|
# def list(self, **kw):
|
||||||
|
# return http.request.render('default.listing', {
|
||||||
|
# 'root': '/default/default',
|
||||||
|
# 'objects': http.request.env['default.default'].search([]),
|
||||||
|
# })
|
||||||
|
#
|
||||||
|
# @http.route('/default/default/objects/<model("default.default"):obj>/', auth='public')
|
||||||
|
# def object(self, obj, **kw):
|
||||||
|
# return http.request.render('default.object', {
|
||||||
|
# 'object': obj
|
||||||
|
# })
|
||||||
24
app_saas/data/auth_oauth_data.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="provider_app_saas" model="auth.oauth.provider">
|
||||||
|
<field name="name">Ai Passport of odooapp.cn</field>
|
||||||
|
<field name="body">Ai通行证-Odoo中文应用商店</field>
|
||||||
|
<field name="auth_endpoint">https://www.odooapp.cn/oauth/authorize</field>
|
||||||
|
<field name="code_endpoint">https://www.odooapp.cn/oauth/token</field>
|
||||||
|
<field name="scope">odoo,profile</field>
|
||||||
|
<field name="validation_endpoint">https://www.odooapp.cn/oauth/profile</field>
|
||||||
|
<field name="data_endpoint"></field>
|
||||||
|
<field name="user_template_id" ref=""/>
|
||||||
|
<field name="css_class">fa fa-2x fa-fw fa-sign-in text-primary</field>
|
||||||
|
<field name="enabled" eval="True"/>
|
||||||
|
</record>
|
||||||
|
<!-- Use database uuid as client_id for OpenERP oauth provider -->
|
||||||
|
<function model="auth.oauth.provider" name="write">
|
||||||
|
<value eval="[ref('app_saas.provider_app_saas')]"/>
|
||||||
|
<value model="ir.config_parameter" eval="{
|
||||||
|
'client_id': obj().env['ir.config_parameter'].get_param('database.uuid'),
|
||||||
|
}"/>
|
||||||
|
</function>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
29
app_saas/hooks.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Created on 2023-10-06
|
||||||
|
# author: 欧度智能,https://www.odooai.cn
|
||||||
|
# email: 300883@qq.com
|
||||||
|
# Copyright (C) 2009~2024 odooAi.cn
|
||||||
|
|
||||||
|
# Odoo16在线用户手册(长期更新)
|
||||||
|
# https://www.odooai.cn/documentation/16.0/zh_CN/index.html
|
||||||
|
|
||||||
|
# Odoo16在线开发者手册(长期更新)
|
||||||
|
# https://www.odooai.cn/documentation/16.0/zh_CN/developer.html
|
||||||
|
|
||||||
|
from odoo import api, SUPERUSER_ID, _
|
||||||
|
|
||||||
|
|
||||||
|
def pre_init_hook(env):
|
||||||
|
pass
|
||||||
|
# cr.execute("")
|
||||||
|
|
||||||
|
|
||||||
|
def post_init_hook(env):
|
||||||
|
pass
|
||||||
|
# cr.execute("")
|
||||||
|
|
||||||
|
|
||||||
|
def uninstall_hook(env):
|
||||||
|
pass
|
||||||
|
# cr.execute("")
|
||||||
2
app_saas/i18n/README.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Please follow this tutorial for more guides:
|
||||||
|
https://www.odoo.com/documentation/master/developer/misc/i18n/translations.html
|
||||||
48
app_saas/i18n/zh_CN.po
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * app_saas
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 18.0+e-20241031\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2024-11-08 10:21+0000\n"
|
||||||
|
"PO-Revision-Date: 2024-11-08 10:21+0000\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
|
#. module: app_saas
|
||||||
|
#: model_terms:ir.ui.view,arch_db:app_saas.app_res_config_settings_view_form
|
||||||
|
msgid "<strong>Save</strong> to Install Chinese PO App if downloaded."
|
||||||
|
msgstr "<strong>保存</strong> 后即可安装Odoo中文云翻译模块。本模块可至Odoo中文应用商店下载。"
|
||||||
|
|
||||||
|
#. module: app_saas
|
||||||
|
#: model:ir.model.fields,help:app_saas.field_res_config_settings__module_app_cn_po
|
||||||
|
#: model_terms:ir.ui.view,arch_db:app_saas.app_res_config_settings_view_form
|
||||||
|
msgid "Checked to Sync Odoo Chinese from www.odooapp.cn"
|
||||||
|
msgstr "勾选后即可启用Odoo中文云翻译,相关服务由 www.odooapp.cn 提供"
|
||||||
|
|
||||||
|
#. module: app_saas
|
||||||
|
#: model:ir.model,name:app_saas.model_res_config_settings
|
||||||
|
msgid "Config Settings"
|
||||||
|
msgstr "设置"
|
||||||
|
|
||||||
|
#. module: app_saas
|
||||||
|
#: model_terms:ir.ui.view,arch_db:app_saas.app_res_config_settings_view_form
|
||||||
|
msgid "Or get the app from"
|
||||||
|
msgstr "您可以在此获取本应用模块"
|
||||||
|
|
||||||
|
#. module: app_saas
|
||||||
|
#: model:ir.model.fields,field_description:app_saas.field_res_config_settings__module_app_cn_po
|
||||||
|
#: model_terms:ir.ui.view,arch_db:app_saas.app_res_config_settings_view_form
|
||||||
|
msgid "SaaS Chinese PO"
|
||||||
|
msgstr "中文云翻译PO"
|
||||||
|
|
||||||
|
#. module: app_saas
|
||||||
|
#: model_terms:ir.ui.view,arch_db:app_saas.app_res_config_settings_view_form
|
||||||
|
msgid "https://www.odooapp.cn/apps/modules/app_cn_po"
|
||||||
|
msgstr "https://www.odooapp.cn/apps/modules/app_cn_po"
|
||||||
8
app_saas/models/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of odooAi.cn. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import res_config_settings
|
||||||
|
from . import auth_oauth_provider
|
||||||
|
from . import res_users
|
||||||
|
|
||||||
|
|
||||||
16
app_saas/models/auth_oauth_provider.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo import api, fields, models, modules, tools, _
|
||||||
|
|
||||||
|
import base64
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
class AuthOAuthProvider(models.Model):
|
||||||
|
|
||||||
|
_inherit = 'auth.oauth.provider'
|
||||||
|
_order = 'sequence, name'
|
||||||
|
|
||||||
|
# 取code的url,主要针对国内的先取code,再拿Token的方式
|
||||||
|
code_endpoint = fields.Char(string='Token by Code Endpoint', help='Get Token from Code. Only for Code response type.')
|
||||||
|
user_template_id = fields.Many2one('res.users', string='New User Template',
|
||||||
|
domain=[('active', '=', False)])
|
||||||
14
app_saas/models/res_config_settings.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
_inherit = 'res.config.settings'
|
||||||
|
|
||||||
|
module_app_cn_po = fields.Boolean('SaaS Chinese PO', help="Checked to Sync Odoo Chinese from www.odooapp.cn")
|
||||||
143
app_saas/models/res_users.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
try:
|
||||||
|
import urlparse
|
||||||
|
except:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
try:
|
||||||
|
import urllib2
|
||||||
|
except:
|
||||||
|
from urllib import request as urllib2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import AccessDenied, UserError
|
||||||
|
from odoo.addons.auth_signup.models.res_users import SignupError
|
||||||
|
from odoo.http import request, Response
|
||||||
|
from odoo.tools.misc import ustr
|
||||||
|
|
||||||
|
from ast import literal_eval
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from datetime import timedelta
|
||||||
|
import random
|
||||||
|
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class OauthBindError(Exception):
|
||||||
|
# 增加一种错误类型
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ResUsers(models.Model):
|
||||||
|
_inherit = 'res.users'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_token_from_code(self, provider, params):
|
||||||
|
# 通过 code 取 token
|
||||||
|
# 这里原生是没处理code模式,此处将增加使用code取token,不在 controller 中处理
|
||||||
|
oauth_provider = self.env['auth.oauth.provider'].sudo().browse(provider)
|
||||||
|
|
||||||
|
# odoo 特殊处理,用code取token
|
||||||
|
params.update({
|
||||||
|
'scope': oauth_provider.scope or '',
|
||||||
|
'client_id': oauth_provider.client_id or '',
|
||||||
|
})
|
||||||
|
if hasattr(oauth_provider, 'client_secret') and oauth_provider.client_secret:
|
||||||
|
params.update({
|
||||||
|
'client_secret': oauth_provider.client_secret or '',
|
||||||
|
})
|
||||||
|
response = requests.get(oauth_provider.code_endpoint, params=params, timeout=30)
|
||||||
|
if response.ok:
|
||||||
|
ret = response.json()
|
||||||
|
# todo: 客户机首次连接时,取到的 server 端 key 写入 provider 的 client_secret
|
||||||
|
if ret.get('push_client_secret') and hasattr(oauth_provider, 'client_secret'):
|
||||||
|
oauth_provider.write({'client_secret': ret.get('push_client_secret')})
|
||||||
|
self._cr.commit()
|
||||||
|
return ret
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def auth_oauth(self, provider, params):
|
||||||
|
code = params.get('code', False)
|
||||||
|
access_token = params.get('access_token')
|
||||||
|
oauth_provider = self.env['auth.oauth.provider'].sudo().browse(provider)
|
||||||
|
# 额外code 处理
|
||||||
|
kw = params
|
||||||
|
if oauth_provider.code_endpoint and code and not access_token:
|
||||||
|
ret = self.get_token_from_code(provider, params)
|
||||||
|
kw.update(ret)
|
||||||
|
kw.pop('code', False)
|
||||||
|
|
||||||
|
self = self.with_context(auth_extra=kw)
|
||||||
|
res = super(ResUsers, self).auth_oauth(provider, kw)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _auth_oauth_signin(self, provider, validation, params):
|
||||||
|
# 用户绑定的额外处理,如果有同 login 用户则直接绑定
|
||||||
|
# todo: 当前不管多公司,在 social_login 里有更细节判断,后续优化
|
||||||
|
# todo: 当前同名就写 oauth 信息,不安全,要优化
|
||||||
|
oauth_provider = self.env['auth.oauth.provider'].sudo().browse(provider)
|
||||||
|
if oauth_provider and oauth_provider.scope.find('odoo') >= 0:
|
||||||
|
oauth_uid =validation.get('user_id')
|
||||||
|
if oauth_uid:
|
||||||
|
odoo_user = self.sudo().search([('login', '=', oauth_uid)], limit=1)
|
||||||
|
if odoo_user and not (odoo_user.oauth_access_token and odoo_user.oauth_provider_id and odoo_user.oauth_uid):
|
||||||
|
vals = {
|
||||||
|
'oauth_provider_id': provider,
|
||||||
|
'oauth_access_token': params.get('access_token'),
|
||||||
|
'oauth_uid': oauth_uid,
|
||||||
|
}
|
||||||
|
odoo_user.write(vals)
|
||||||
|
_logger.info('========= _auth_oauth_signin res.users write:%s' % vals)
|
||||||
|
self._cr.commit()
|
||||||
|
return odoo_user.user_id.login
|
||||||
|
res = super(ResUsers, self)._auth_oauth_signin(provider, validation, params)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _create_user_from_template(self, values):
|
||||||
|
# 处理odooapp.cn 为 server 时 默认为内部用户
|
||||||
|
oauth_provider_id = values.get('oauth_provider_id')
|
||||||
|
if oauth_provider_id:
|
||||||
|
provider = request.env['auth.oauth.provider'].sudo().browse(int(oauth_provider_id))
|
||||||
|
if provider:
|
||||||
|
template_user = provider.user_template_id
|
||||||
|
if not template_user and provider.scope.find('odoo') >= 0:
|
||||||
|
template_user = self.sudo().env.ref('base.default_user', False)
|
||||||
|
if not template_user:
|
||||||
|
template_user_id = literal_eval(self.env['ir.config_parameter'].sudo().get_param('base.template_portal_user_id', 'False'))
|
||||||
|
template_user = self.sudo().browse(template_user_id)
|
||||||
|
|
||||||
|
if not values.get('login'):
|
||||||
|
raise ValueError(_('Signup: no login given for new user'))
|
||||||
|
if not values.get('partner_id') and not values.get('name'):
|
||||||
|
raise ValueError(_('Signup: no name or partner given for new user'))
|
||||||
|
|
||||||
|
# create a copy of the template user (attached to a specific partner_id if given)
|
||||||
|
values['active'] = True
|
||||||
|
try:
|
||||||
|
with self.env.cr.savepoint():
|
||||||
|
return template_user.sudo().with_context(no_reset_password=True).copy(values)
|
||||||
|
except Exception as e:
|
||||||
|
# copy may failed if asked login is not available.
|
||||||
|
raise SignupError(ustr(e))
|
||||||
|
res = super(ResUsers, self)._create_user_from_template(values)
|
||||||
|
self._cr.commit()
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _generate_signup_values(self, provider, validation, params):
|
||||||
|
# 此处生成 创建 odoo user 的初始值,增加字段如头像
|
||||||
|
res = super()._generate_signup_values(provider, validation, params)
|
||||||
|
# 后置增加字段,包括 headimgurl
|
||||||
|
if validation.get('mobile'):
|
||||||
|
res['mobile'] = validation.get('mobile')
|
||||||
|
if validation.get('headimgurl'):
|
||||||
|
res['image_1920'] = self.sudo()._get_image_from_url(validation.get('headimgurl'))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _rpc_api_keys_only(self):
|
||||||
|
# 可直接使用 oauth_access_token 作为 password 登录
|
||||||
|
self.ensure_one()
|
||||||
|
return self.oauth_access_token or super()._rpc_api_keys_only()
|
||||||
5
app_saas/report/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
# from . import model_new_report
|
||||||
|
|
||||||
77
app_saas/report/model_new_report.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
# Copyright (C) 2009-TODAY odooai.cn Ltd. https://www.odooai.cn
|
||||||
|
# Author: Ivan Deng,300883@qq.com
|
||||||
|
# You can modify it under the terms of the GNU LESSER
|
||||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||||
|
# See <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies
|
||||||
|
# of the Software or modified copies of the Software.
|
||||||
|
|
||||||
|
# Create on 2023-10-06
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from odoo import fields, models, tools, api
|
||||||
|
|
||||||
|
|
||||||
|
class ModelNewReport(models.Model):
|
||||||
|
|
||||||
|
# Model New Analysis
|
||||||
|
_name = 'model.new.report'
|
||||||
|
_auto = False
|
||||||
|
_description = 'Model New Analysis'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
# Base field
|
||||||
|
name = fields.Char(string='Name', readonly=True)
|
||||||
|
ref = fields.Char(string='Reference', readonly=True)
|
||||||
|
amount = fields.Float(string="Amount", readonly=True)
|
||||||
|
date = fields.Datetime(string="Date", readonly=True)
|
||||||
|
user_id = fields.Many2one('res.users', string='User', readonly=True)
|
||||||
|
user_login = fields.Char(string='User Login', readonly=True)
|
||||||
|
company_id = fields.Many2one('res.company', 'Company', readonly=True)
|
||||||
|
active = fields.Boolean(string="Active", readonly=True)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _select(self):
|
||||||
|
return '''
|
||||||
|
SELECT
|
||||||
|
m.id,
|
||||||
|
m.name,
|
||||||
|
m.ref,
|
||||||
|
m.amount,
|
||||||
|
m.date,
|
||||||
|
m.user_id,
|
||||||
|
u.login AS user_login,
|
||||||
|
m.company_id,
|
||||||
|
m.active
|
||||||
|
'''
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _from(self):
|
||||||
|
return '''
|
||||||
|
FROM model_new AS m
|
||||||
|
'''
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _join(self):
|
||||||
|
return '''
|
||||||
|
JOIN res_users AS u ON m.user_id = u.id
|
||||||
|
'''
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _where(self):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
tools.drop_view_if_exists(self._cr, self._table)
|
||||||
|
sql = '''
|
||||||
|
CREATE OR REPLACE VIEW %s AS (
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
)
|
||||||
|
''' % (self._table, self._select(), self._from(), self._join(), self._where())
|
||||||
|
self._cr.execute(sql)
|
||||||
88
app_saas/report/model_new_report_views.xml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Graph view, 图表-->
|
||||||
|
<record id="app_model_new_report_view_graph" model="ir.ui.view">
|
||||||
|
<field name="name">app.model.new.report.graph</field>
|
||||||
|
<field name="model">model.new.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<graph string="Model New Analysis" sample="1">
|
||||||
|
<field name="amount"/>
|
||||||
|
<field name="date" interval="month"/>
|
||||||
|
</graph>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Pivot view, 透视表-->
|
||||||
|
<record id="app_model_new_report_view_pivot" model="ir.ui.view">
|
||||||
|
<field name="name">app.model.new.report.pivot</field>
|
||||||
|
<field name="model">model.new.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<pivot string="Model new pivot" sample="1">
|
||||||
|
<field name="name" type="col"/>
|
||||||
|
<field name="user_id" type="col"/>
|
||||||
|
<field name="date" interval="month" type="row"/>
|
||||||
|
</pivot>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Tree view, 列表-->
|
||||||
|
<record id="app_model_new_report_view_tree" model="ir.ui.view">
|
||||||
|
<field name="name">app.model.new.report.tree</field>
|
||||||
|
<field name="model">model.new.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree default_order="date desc">
|
||||||
|
<field name="date"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="amount"/>
|
||||||
|
<field name="user_id" groups="base.group_multi_company"/>
|
||||||
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="app_model_new_report_view_search" model="ir.ui.view">
|
||||||
|
<field name="name">app.model.new.report.search</field>
|
||||||
|
<field name="model">model.new.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Model New Search">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<filter string="Trailing 12 months" name="completion_date" domain="[
|
||||||
|
('date', '>=', (datetime.datetime.combine(context_today() + relativedelta(days=-365), datetime.time(0,0,0)).to_utc()).strftime('%Y-%m-%d %H:%M:%S')),
|
||||||
|
('date', '>=', (datetime.datetime.combine(context_today(), datetime.time(0,0,0)).to_utc()).strftime('%Y-%m-%d %H:%M:%S'))]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter name="filter_date" date="date"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||||
|
<group expand="1" string="Group By">
|
||||||
|
<filter string="Date" name="group_by_date" context="{'group_by': 'date:month'}"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="User" name="group_by_user_id" context="{'group_by': 'user_id'}"/>
|
||||||
|
<filter string="Company" name="company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_model_new_report" model="ir.actions.act_window">
|
||||||
|
<field name="name">Model New Report</field>
|
||||||
|
<field name="res_model">model.new.report</field>
|
||||||
|
<field name="view_mode">graph,pivot,tree</field>
|
||||||
|
<field name="context">{
|
||||||
|
'pivot_column_groupby': ['user_id'],
|
||||||
|
'pivot_row_groupby': ['date:month'],
|
||||||
|
'graph_mode': 'bar',
|
||||||
|
'graph_groupbys': ['date:month', 'user_id'],
|
||||||
|
}
|
||||||
|
</field>
|
||||||
|
<field name="domain">[]</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
No data yet!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Start Analysis
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
41
app_saas/security/app_security.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<!-- Create new App security category, 创建应用 -->
|
||||||
|
<record model="ir.module.category" id="app_module_category_1">
|
||||||
|
<field name="name">App...</field>
|
||||||
|
<field name="description">Helps you manage your ...</field>
|
||||||
|
<field name="sequence">8</field>
|
||||||
|
</record>
|
||||||
|
<!-- Cretate new App security group, 权限组 -->
|
||||||
|
<!-- User group, 普通用记 -->
|
||||||
|
<record id="group_app_user" model="res.groups">
|
||||||
|
<field name="name">App User</field>
|
||||||
|
<field name="category_id" ref="app_module_category_1"/>
|
||||||
|
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||||
|
<field name="comment">The user will be able to ...</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Admin group,管理员,可以配置 -->
|
||||||
|
<record id="group_app_admin" model="res.groups">
|
||||||
|
<field name="name">App Admin</field>
|
||||||
|
<field name="category_id" ref="app_module_category_1"/>
|
||||||
|
<field name="implied_ids" eval="[(4, ref('group_app_user'))]"/>
|
||||||
|
<field name="comment">The user will be able to config ...</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Auto set erp admin full access,group_erp_manager自动有完整权限 -->
|
||||||
|
<record id="base.group_erp_manager" model="res.groups">
|
||||||
|
<field name="implied_ids" eval="[(4, ref('group_app_admin'))]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- IR Rules, user can only see my record,记录集权限,用户只能见到自已的记录 -->
|
||||||
|
<!-- <record id="rule_user" model="ir.rule">-->
|
||||||
|
<!-- <field name="name">Users are allowed to access their own record</field>-->
|
||||||
|
<!-- <field name="model_id" ref="model_app_order"/>-->
|
||||||
|
<!-- <field name="domain_force">['|', ('partner_id', 'in', [user.partner_id.id]), ('user_id.id', '=', user.id)]</field>-->
|
||||||
|
<!-- <field name="groups" eval="[(4, ref('base.group_user'))]"/>-->
|
||||||
|
<!-- </record>-->
|
||||||
|
<!--End -->
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
3
app_saas/security/ir.model.access.csv
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_model_new,access_model_new,model_model_new,base.group_user,1,1,1,1
|
||||||
|
access_model_new_report,access_model_new_report,model_model_new_report,base.group_user,1,1,1,1
|
||||||
|
BIN
app_saas/static/description/banner.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
app_saas/static/description/banner1.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
BIN
app_saas/static/description/banner2.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
app_saas/static/description/banner3.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
app_saas/static/description/cnreadme.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
app_saas/static/description/demo1.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
app_saas/static/description/demo2.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
app_saas/static/description/demo3.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
app_saas/static/description/demo4.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
app_saas/static/description/demo5.jpg
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
app_saas/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
136
app_saas/static/description/index.html
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
# Copyright (C) 2009~2024 odooAi.cn
|
||||||
|
##############################################################################
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<!-- begin title-->
|
||||||
|
<section class="oe_container container o_cc o_cc2">
|
||||||
|
<h2 class="text-center bg-warning text-white pt24 pb24">Ai通行证.Odoo中文应用商店SaaS云服务客户端,一键微信QQ淘宝抖音全媒体登录SSO</h2>
|
||||||
|
<h3 class="text-center">odooapp.cn SaaS Client-Ai Passport.SSO with </h3>
|
||||||
|
<h4 class="text-center">一键获取最新应用信息,最新行业服务包,一键获取Odoo中文翻译更新,快速获取Odoo服务,Odoo升级评估</h4>
|
||||||
|
</section>
|
||||||
|
<!-- end title-->
|
||||||
|
|
||||||
|
<!-- quick demo-->
|
||||||
|
<section class="oe_container container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h2 class="bg-warning text-center pt8 pb8 mt16 mb16">1.启用后即可使用 odooapp.cn 提供的全网社交媒体整合登录</h2>
|
||||||
|
<h4 class="oe_slogan">支持微信、抖音、QQ、淘宝、钉钉、支付宝、企业微信、Facebook、Google、微软Azure等整合登录。</h4>
|
||||||
|
<p>未经用户明确同意,本应用不会获取任何用户系统数据.</p>
|
||||||
|
<p class="">相关内容,请参考用户协议及我们的隐私政策:</p>
|
||||||
|
<p> https://www.odooapp.cn/conditions</p>
|
||||||
|
<p> https://www.odooapp.cn/privacy-policy</p>
|
||||||
|
<div class="oe_demo oe_screenshot img img-fluid">
|
||||||
|
<img src="banner1.png"/>
|
||||||
|
</div>
|
||||||
|
<p>用户可直接调用SSO一键登录</p>
|
||||||
|
<div class="oe_demo oe_screenshot img img-fluid">
|
||||||
|
<img src="demo1.jpg"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="oe_container container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h2 class="bg-warning text-center pt8 pb8 mt16 mb16">2. 快速访问Odoo中文应用商店并获取独享Odoo中文通行码,可随时断开云服务连接</h2>
|
||||||
|
<h4 class="oe_slogan">启用云服务后,即可获取最新功能推送,版本比对信息,提供版本升级建议</h4>
|
||||||
|
<div class="oe_demo oe_screenshot img img-fluid">
|
||||||
|
<img src="demo2.jpg"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="oe_container container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h2 class="bg-warning text-center pt8 pb8 mt16 mb16">标准的Oauth2登录支持,可方便配置</h2>
|
||||||
|
<h4 class="oe_slogan">安装后,用户即可使用 odooapp.cn 提供的 Ai通行证进行登录</h4>
|
||||||
|
<div class="oe_demo oe_screenshot img img-fluid">
|
||||||
|
<img src="demo3.jpg"/>
|
||||||
|
</div>
|
||||||
|
<h4 class="oe_slogan">可以使用微信抖音QQ等在Odoo中文应用商店登录,同意并继续后即可登入自有Odoo</h4>
|
||||||
|
<div class="oe_demo oe_screenshot img img-fluid">
|
||||||
|
<img src="demo4.jpg"/>
|
||||||
|
</div>
|
||||||
|
<h4 class="oe_slogan">自定义默认用户模板,为用户启用不同权限</h4>
|
||||||
|
<div class="oe_demo oe_screenshot img img-fluid">
|
||||||
|
<img src="demo5.jpg"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="oe_container container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h2 class="bg-warning text-center pt8 pb8 mt16 mb16">相关服务正在紧张开发中,后续可享用更多服务</h2>
|
||||||
|
<div class="oe_demo oe_screenshot img img-fluid">
|
||||||
|
<img src="cnreadme.jpg"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- end quick demo-->
|
||||||
|
|
||||||
|
<!-- begin howto-->
|
||||||
|
<section class="oe_container container s_text_block o_colored_level pt16 pb16">
|
||||||
|
<h2 class="text-center bg-info text-white pt16 pb16">- How to setup and use -</h2>
|
||||||
|
<h3>This app need no extra module. The price already included</h3>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<h4 class="pt16">
|
||||||
|
1. Buy and Install
|
||||||
|
</h4>
|
||||||
|
<h4 class="pt16">
|
||||||
|
2. Read the app description for user guide
|
||||||
|
</h4>
|
||||||
|
<h4 class="pt16">
|
||||||
|
3. Enjoy and easy use
|
||||||
|
</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="oe_demo oe_screenshot img img-fluid">
|
||||||
|
<img src="banner.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4 class="pt16">4. More information in our FAQ</h4>
|
||||||
|
<div class="row">
|
||||||
|
https://www.odooai.cn/faq
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- end howto-->
|
||||||
|
|
||||||
|
<!-- begin upsell-->
|
||||||
|
<!-- end upsell-->
|
||||||
|
|
||||||
|
<!-- begin support-->
|
||||||
|
<section class="container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced text-center">
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="oe_slogan">Technical Help & Support</h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12 pad0">
|
||||||
|
<div class="oe_mt16">
|
||||||
|
<p><h4 class="pt16">
|
||||||
|
For any type of technical help & support requests, Feel free to contact us</h4></p>
|
||||||
|
<a style="background: #002e5a none repeat scroll 0% 0%; color: rgb(255, 255, 255);position: relative; overflow: hidden;"
|
||||||
|
class="btn btn-warning btn-lg" rel="nofollow" href="mailto:odoo@china.com"><span
|
||||||
|
style="height: 354px; width: 354px; top: -147.433px; left: -6.93335px;" class="o_ripple"></span>
|
||||||
|
<i class="fa fa-envelope"></i> odoo@china.com</a>
|
||||||
|
<p><h4 class="pt16">
|
||||||
|
Via QQ: 300883 (App user would not get QQ or any other IM support. Only for odoo project customize.)</h4></p>
|
||||||
|
<a style="background: #002e5a none repeat scroll 0% 0%; color: rgb(255, 255, 255);position: relative; overflow: hidden;"
|
||||||
|
class="btn btn-warning btn-lg" rel="nofollow" href="mailto:300883@qq.com"><span
|
||||||
|
style="height: 354px; width: 354px; top: -147.433px; left: -6.93335px;" class="o_ripple"></span>
|
||||||
|
<i class="fa fa-envelope"></i> 300883@qq.com</a>
|
||||||
|
</div>
|
||||||
|
<div class="oe_mt16">
|
||||||
|
<h4 class="pt16">Visit our website for more support.</h4>
|
||||||
|
<h4 class="pt16">https://www.odooai.cn</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- end support-->
|
||||||
|
</html>
|
||||||
BIN
app_saas/static/description/qr.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app_saas/static/img/icon_odooai.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app_saas/static/img/icon_odooapp.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
app_saas/static/img/logo_odooai.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
0
app_saas/static/src/js/odooai.cn
Normal file
0
app_saas/static/src/scss/odooai.cn
Normal file
3
app_saas/static/src/scss/style.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// scss sample
|
||||||
|
|
||||||
|
|
||||||
0
app_saas/static/src/xml/odooai.cn
Normal file
16
app_saas/views/auth_oauth_provider_views.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="app_view_oauth_provider_form" model="ir.ui.view">
|
||||||
|
<field name="name">app.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="//field[@name='auth_endpoint']" position="after">
|
||||||
|
<field name="code_endpoint"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='data_endpoint']" position="before">
|
||||||
|
<field name="user_template_id"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
28
app_saas/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!-- <record id="app_res_config_settings_view_form" model="ir.ui.view">-->
|
||||||
|
<!-- <field name="name">res.config.settings.view.form.inherit.app_common</field>-->
|
||||||
|
<!-- <field name="model">res.config.settings</field>-->
|
||||||
|
<!-- <field name="inherit_id" ref="base.res_config_settings_view_form"/>-->
|
||||||
|
<!-- <field name="arch" type="xml">-->
|
||||||
|
<!-- <xpath expr="//setting[@id='odooai_cloud_saas_ok']" position="after">-->
|
||||||
|
<!-- <setting string="SaaS Chinese PO" help="Checked to Sync Odoo Chinese from www.odooapp.cn" id="module_app_cn_po">-->
|
||||||
|
<!-- <field name="module_app_cn_po"/>-->
|
||||||
|
<!-- <div class="content-group mt16" invisible="not module_app_cn_po" id="msg_module_app_cn_po">-->
|
||||||
|
<!-- <div class="mt16 text-warning">-->
|
||||||
|
<!-- <strong>Save</strong> to Install Chinese PO App if downloaded.-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="text-warning">-->
|
||||||
|
<!-- Or get the app from-->
|
||||||
|
<!-- <a href="https://www.odooapp.cn/apps/modules/app_cn_po" class="o_doc_link ml8"-->
|
||||||
|
<!-- target="_blank">https://www.odooapp.cn/apps/modules/app_cn_po-->
|
||||||
|
<!-- </a>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </setting>-->
|
||||||
|
<!-- </xpath>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </record>-->
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||