commit
ebb9e183cf
|
|
@ -0,0 +1,49 @@
|
|||
# sphinx build directories
|
||||
_build/
|
||||
|
||||
# dotfiles
|
||||
.*
|
||||
!.gitignore
|
||||
!.github
|
||||
!.mailmap
|
||||
# compiled python files
|
||||
*.py[co]
|
||||
__pycache__/
|
||||
# setup.py egg_info
|
||||
*.egg-info
|
||||
# emacs backup files
|
||||
*~
|
||||
# hg stuff
|
||||
*.orig
|
||||
status
|
||||
# odoo filestore
|
||||
odoo/filestore
|
||||
# maintenance migration scripts
|
||||
odoo/addons/base/maintenance
|
||||
|
||||
# generated for windows installer?
|
||||
install/win32/*.bat
|
||||
install/win32/meta.py
|
||||
|
||||
# needed only when building for win32
|
||||
setup/win32/static/less/
|
||||
setup/win32/static/wkhtmltopdf/
|
||||
setup/win32/static/postgresql*.exe
|
||||
|
||||
# js tooling
|
||||
node_modules
|
||||
jsconfig.json
|
||||
tsconfig.json
|
||||
package-lock.json
|
||||
package.json
|
||||
.husky
|
||||
|
||||
# various virtualenv
|
||||
/bin/
|
||||
/build/
|
||||
/dist/
|
||||
/include/
|
||||
/lib/
|
||||
/man/
|
||||
/share/
|
||||
/src/
|
||||
|
|
@ -185,7 +185,6 @@ class AuthenticationController(http.Controller):
|
|||
return http_helper.response(code=400,message=_('User account with login {} not found').format(login),success=False)
|
||||
|
||||
uid = http_helper.is_authentic(login,password)
|
||||
|
||||
if not uid:
|
||||
return http_helper.errcode(code=400, message=_('Unable to Sign In. invalid user password'))
|
||||
token = validator.create_token(request.env.user)
|
||||
|
|
|
|||
|
|
@ -314,10 +314,12 @@ class AttendanceController(http.Controller):
|
|||
"You are not allowed to perform this operation. please check with one of your team admins"),
|
||||
success=False)
|
||||
try:
|
||||
print("******************employee.message_sent:",employee.message_sent)
|
||||
if json.loads(body['in_zone']):
|
||||
records = employee.attendance_log_ids.sudo().filtered(lambda r: str(r.date) == str(datetime.today().date()) and r.old == False)
|
||||
for r in records:
|
||||
r.old = True
|
||||
employee.message_sent = False
|
||||
return http_helper.response(message="Old Record Done", data={'status': True})
|
||||
|
||||
else:
|
||||
|
|
@ -326,7 +328,6 @@ class AttendanceController(http.Controller):
|
|||
limit=1)
|
||||
if attendance.action == 'sign_in':
|
||||
records = employee.attendance_log_ids.sudo().filtered(lambda r: r.old == False and str(r.date) == str(datetime.today().date()))
|
||||
# records = employee.attendance_log_ids.sudo().filtered(lambda r: r.old == False and r.date == str(datetime.today().date()))
|
||||
if records:
|
||||
n = len(records)
|
||||
last = records[n - 1]
|
||||
|
|
@ -335,37 +336,43 @@ class AttendanceController(http.Controller):
|
|||
if now > last:
|
||||
diff = now - last
|
||||
diff = diff.seconds / 60
|
||||
auto = request.env.user.company_id.auto_checkout if request.env.user.company_id.auto_checkout > 0 else 5
|
||||
zone = http.request.env['attendance.zone'].search([('employee_ids', 'in', employee.id)],limit=1)
|
||||
zone_general = http.request.env['attendance.zone'].search([('general', '=', True)],limit=1)
|
||||
auto = zone.auto_checkout or zone_general.auto_checkout or request.env.user.company_id.auto_checkout or 20
|
||||
if diff >= auto:
|
||||
attendance = http.request.env['attendance.attendance'].create({
|
||||
'employee_id': employee.id,
|
||||
'action':'sign_out',
|
||||
'action': 'sign_out',
|
||||
'action_type': 'auto',
|
||||
'name': fields.datetime.now(),
|
||||
# 'device_id': body.get('device_id'),
|
||||
'zone': "%s,%s" % (body.get('longitude'), body.get('latitude')),
|
||||
'longitude': body.get('longitude'),
|
||||
'latitude': body.get('latitude'),
|
||||
})
|
||||
msg = (_("Auto Checkout successfully"))
|
||||
subject = (_("Auto Checkout"))
|
||||
msg = _("Auto Checkout successfully")
|
||||
subject = _("Auto Checkout")
|
||||
self.send_msg(employee, msg, subject)
|
||||
records = employee.attendance_log_ids.sudo().filtered(
|
||||
lambda r: str(r.date) == str(datetime.today().date()) and r.old == False)
|
||||
for r in records:
|
||||
r.old = True
|
||||
return http_helper.response(message="Auto Checkout successfully", data={'status': True})
|
||||
employee.message_sent = False
|
||||
return http_helper.response(message="Auto Checkout successfully", data={'status': True})
|
||||
else:
|
||||
msg = (_("You are out of attendance zone you will be auto sin out "))
|
||||
subject = (_("Auto Sign out"))
|
||||
self.send_msg(employee, msg, subject)
|
||||
return http_helper.response(message="Auto Checkout Fail and Send", data={'status': False})
|
||||
if not employee.message_sent:
|
||||
msg = _("You are out of attendance zone you will be auto sign out")
|
||||
subject = _("Auto Sign out")
|
||||
self.send_msg(employee, msg, subject)
|
||||
employee.message_sent = True
|
||||
return http_helper.response(message="Auto Checkout Fail and Send", data={'status': False})
|
||||
else:
|
||||
self.create_log(employee, body.get('longitude'), body.get('latitude'))
|
||||
msg = (_("You are out of attendance zone you will be auto sin out "))
|
||||
subject = (_("Auto Sign out"))
|
||||
self.send_msg(employee, msg, subject)
|
||||
return http_helper.response(message="Auto Checkout Fail and Send", data={'status': False})
|
||||
if not employee.message_sent:
|
||||
msg = _("You are out of attendance zone you will be auto sign out")
|
||||
subject = _("Auto Sign out")
|
||||
self.send_msg(employee, msg, subject)
|
||||
employee.message_sent = True
|
||||
return http_helper.response(message="Auto Checkout Fail and Send", data={'status': False})
|
||||
else:
|
||||
return http_helper.response(message="You are not Checked in yet", data={'status': True})
|
||||
except Exception as e:
|
||||
|
|
@ -375,6 +382,7 @@ class AttendanceController(http.Controller):
|
|||
return http_helper.errcode(code=403, message=message)
|
||||
|
||||
def send_msg(self, emp, msg, subject):
|
||||
print("*****************************send")
|
||||
if emp.user_id.partner_id:
|
||||
partner_id = emp.user_id.partner_id
|
||||
partner_id.send_notification(subject, msg, data=None, all_device=True)
|
||||
|
|
|
|||
|
|
@ -186,7 +186,6 @@ class AuthenticationController(http.Controller):
|
|||
return http_helper.response(code=400,message=_('User account with login {} not found').format(login),success=False)
|
||||
|
||||
uid = http_helper.is_authentic(login,password)
|
||||
|
||||
if not uid:
|
||||
return http_helper.errcode(code=400, message=_('Unable to Sign In. invalid user password'))
|
||||
token = validator.create_token(request.env.user)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ class JwtAccessToken(models.Model):
|
|||
token.is_expired = datetime.now() > token.expires
|
||||
|
||||
def access_token_cron(self):
|
||||
self.search([("is_expired", "=", True)]).unlink()
|
||||
# self.search([("is_expired", "=", True)]).unlink()
|
||||
current_time = datetime.now()
|
||||
expired_tokens = self.search([('expires', '<', current_time)])
|
||||
expired_tokens.unlink()
|
||||
return True
|
||||
|
||||
def set_env(self,env):
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ class AttendanceZone(models.Model):
|
|||
loc_ch_dist = fields.Integer('Location Change Distance - Meter', default=100)
|
||||
srv_ch_tmout = fields.Integer('Services Change Timeout - Minutes', default=5)
|
||||
|
||||
auto_checkout = fields.Integer(string="Auto Checkout After" ,default=10)
|
||||
|
||||
@api.constrains('start','end')
|
||||
def start_end(self):
|
||||
for rec in self:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class HrEmployee(models.Model):
|
|||
device_id = fields.Char(string="Employee Device ")
|
||||
fcm_token = fields.Char(string='FCM Token')
|
||||
attendance_log_ids = fields.One2many('attendance.log','employee_id',string="Attendance Log")
|
||||
|
||||
message_sent = fields.Boolean(string="Message Sent", default=False)
|
||||
def user_push_notification(self, notification):
|
||||
url = "https://fcm.googleapis.com/fcm/send"
|
||||
header = {
|
||||
|
|
|
|||
|
|
@ -97,3 +97,22 @@ class Users(models.Model):
|
|||
groups.append("group_department_manager")
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super(Users, self).create(vals)
|
||||
if 'password' in vals or vals.get('active') is False:
|
||||
self._invalidate_tokens(res)
|
||||
return res
|
||||
|
||||
def write(self, vals):
|
||||
result = super(Users, self).write(vals)
|
||||
if 'password' in vals or 'active' in vals and not vals['active']:
|
||||
self._invalidate_tokens(self)
|
||||
return result
|
||||
|
||||
def _invalidate_tokens(self, users):
|
||||
token_model = self.env['jwt_provider.access_token']
|
||||
for user in users:
|
||||
token_model.search([('user_id', '=', user.id)]).unlink()
|
||||
|
|
|
|||
|
|
@ -192,9 +192,17 @@ class Validator:
|
|||
_logger.error(traceback.format_exc())
|
||||
|
||||
except (jwt.InvalidTokenError, Exception) as e:
|
||||
result['code'] = 497
|
||||
result['message'] = 'Token invalid or expired'
|
||||
_logger.error(traceback.format_exc())
|
||||
record = request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
])
|
||||
if not record:
|
||||
result['message'] = 'Token not found'
|
||||
result['code'] = 497
|
||||
return result
|
||||
else:
|
||||
result['code'] = 498
|
||||
result['message'] = 'Token invalid'
|
||||
_logger.error(traceback.format_exc())
|
||||
return result
|
||||
|
||||
def refresh_token(self, token):
|
||||
|
|
@ -237,9 +245,17 @@ class Validator:
|
|||
_logger.error(traceback.format_exc())
|
||||
|
||||
except (jwt.InvalidTokenError, Exception) as e:
|
||||
result['code'] = 497
|
||||
result['message'] = 'Token invalid'
|
||||
_logger.error(traceback.format_exc())
|
||||
record = request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
])
|
||||
if not record:
|
||||
result['message'] = 'Token not found'
|
||||
result['code'] = 497
|
||||
return result
|
||||
else:
|
||||
result['code'] = 498
|
||||
result['message'] = 'Token invalid'
|
||||
_logger.error(traceback.format_exc())
|
||||
return result
|
||||
|
||||
|
||||
|
|
@ -284,9 +300,17 @@ class Validator:
|
|||
return result
|
||||
|
||||
except (jwt.InvalidTokenError, Exception) as e:
|
||||
result['code'] = 497
|
||||
result['message'] = 'Token invalid'
|
||||
_logger.error(traceback.format_exc())
|
||||
record = request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
])
|
||||
if not record:
|
||||
result['message'] = 'Token not found'
|
||||
result['code'] = 497
|
||||
return result
|
||||
else:
|
||||
result['code'] = 498
|
||||
result['message'] = 'Token invalid'
|
||||
_logger.error(traceback.format_exc())
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@
|
|||
</h2>
|
||||
</div>
|
||||
<group col="4" colspan="2">
|
||||
<field name="latitude" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="latitude" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="longitude" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="zone" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="allowed_range" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="zone" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="allowed_range" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="active_check" readonly="1" force_save="1" />
|
||||
</group>
|
||||
<group>
|
||||
|
|
@ -29,8 +29,9 @@
|
|||
|
||||
<group>
|
||||
<field name="loc_ch_intv" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}" class="oe_inline"/>
|
||||
<field name="loc_ch_dist" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="srv_ch_tmout" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="loc_ch_dist" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="srv_ch_tmout" attrs="{'required':[('specific','=',True)],'invisible':[('specific','=',False)]}"/>
|
||||
<field name="auto_checkout" />
|
||||
</group>
|
||||
|
||||
</group>
|
||||
|
|
@ -70,11 +71,7 @@
|
|||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<!-- <menuitem-->
|
||||
<menuitem id="menu_attendance_zone"
|
||||
name="Attendance Zone"
|
||||
parent="attendances.menu_hr_attendance_configurations"
|
||||
sequence="3"
|
||||
action="attendance_zone_action"/>
|
||||
<menuitem id="menu_attendance_zone" name="Attendance Zone" parent="attendances.menu_hr_attendance_configurations" sequence="3" action="attendance_zone_action"/>
|
||||
|
||||
|
||||
<!-- config-->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
from . import models
|
||||
from . import controllers
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
'name': 'Odex Web App API',
|
||||
'version': '1.0',
|
||||
'license': 'AGPL-3',
|
||||
'category': 'Odex25-Mobile/Odex25-Mobile',
|
||||
'author': 'Expert Co. Ltd.',
|
||||
'website': 'http://exp-sa.com',
|
||||
'summary': "All Mopile Web App Api and Configurations",
|
||||
'depends': ['hr'],
|
||||
'external_dependencies': {
|
||||
'python': ['jwt', ],
|
||||
},
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/attendance_zone_config_view.xml',
|
||||
'views/hr_employee_view.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
|||
from . import authentication
|
||||
from . import web
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,142 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import werkzeug
|
||||
from odoo import http, tools
|
||||
from odoo.http import request, Response
|
||||
from odoo.addons.auth_signup.models.res_users import SignupError
|
||||
from odoo.exceptions import UserError
|
||||
import base64
|
||||
from ..validator import validator
|
||||
from ..http_helper import http_helper
|
||||
import json
|
||||
import logging
|
||||
from odoo.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
SENSITIVE_FIELDS = ['password', 'password_crypt', 'new_password', 'create_uid', 'write_uid']
|
||||
|
||||
|
||||
class AuthenticationController(http.Controller):
|
||||
|
||||
@http.route('/rest_api/web/login', type='http', auth='none', csrf=False, cors='*', methods=['POST'])
|
||||
def login_phone(self, **kw):
|
||||
login = kw.get('login')
|
||||
password = kw.get('password')
|
||||
if not login:
|
||||
return http_helper.response(code=400, message=_('username or email is missing'), success=False)
|
||||
|
||||
if not password:
|
||||
return http_helper.response(code=400, message=_('Password is missing'), success=False)
|
||||
if not kw.get('device_id'):
|
||||
return http_helper.response(code=400, message=_('Device id is missing'), success=False)
|
||||
|
||||
# check fcm_token
|
||||
if not kw.get('fcm_token_web'):
|
||||
return http_helper.response(code=400, message=_('FCM Token is missing'), success=False)
|
||||
|
||||
user = request.env['res.users'].sudo().search([('login', '=', login)], limit=1)
|
||||
|
||||
if not user or not user.login:
|
||||
return http_helper.response(code=400, message=_('User account with login {} not found').format(login),
|
||||
success=False)
|
||||
|
||||
uid = http_helper.is_authentic(login, password)
|
||||
|
||||
if not uid:
|
||||
return http_helper.errcode(code=400, message=_('Unable to Sign In. invalid user password'))
|
||||
token = validator.create_token(request.env.user)
|
||||
dic = request.env.user.to_dict(True)
|
||||
employee = http.request.env['hr.employee'].sudo().search([('user_id', '=', user.id)], limit=1)
|
||||
if employee and kw.get('device_id') and not employee.device_id:
|
||||
employee.sudo().write({'device_id': kw.get('device_id')})
|
||||
|
||||
# write fcm_token and fcm_token_web in employee
|
||||
fcm_token_web = kw.get('fcm_token_web')
|
||||
if employee and fcm_token_web:
|
||||
employee.sudo().write({'fcm_token_web': fcm_token_web})
|
||||
|
||||
dic['token'] = token
|
||||
http_helper.cleanup();
|
||||
return http_helper.response(data=dic, message=_("User log in successfully"))
|
||||
|
||||
|
||||
@http.route('/rest_api/web/validate',type='http', auth='none', csrf=False, cors='*',methods=['POST'])
|
||||
def validate_token(self, **kw):
|
||||
http_method, body, headers, token = http_helper.parse_request()
|
||||
|
||||
result = validator.validate_token(token)
|
||||
if result['code'] == 497 or result['code'] == 498:
|
||||
return http_helper.errcode(code=result['code'], message=result['message'])
|
||||
|
||||
return http_helper.response(message="uploaded success",data=result['data'])
|
||||
|
||||
@http.route('/rest_api/web/refresh',type='http', auth='none', csrf=False, cors='*',methods=['POST'])
|
||||
def refresh_token(self, **kw):
|
||||
http_method, body, headers, token = http_helper.parse_request()
|
||||
|
||||
result = validator.refresh_token(token)
|
||||
if result['code'] == 497:
|
||||
return http_helper.errcode(code=result['code'], message=result['message'])
|
||||
|
||||
return http_helper.response(message="uploaded success",data=result['data'])
|
||||
|
||||
# Reet password with email
|
||||
@http.route(['/rest_api/web/reset'], type='http', auth='none', csrf=False, methods=['POST'])
|
||||
def reset_email(self, **kw):
|
||||
http_method, body, headers, token = http_helper.parse_request()
|
||||
if not body.get('email'):
|
||||
return http_helper.response(code=400, message="Email must not be empty", success=False)
|
||||
user = http.request.env['res.users'].sudo().search([('login', '=', kw.get('email'))])
|
||||
if user:
|
||||
user.sudo().action_reset_password()
|
||||
return http_helper.response(message=_("A verification link has been sent to you email account"), data={})
|
||||
else:
|
||||
return http_helper.errcode(code=403, message="Password reset failed")
|
||||
|
||||
@http.route('/rest_api/web/users/password',type='http', auth='none', csrf=False, cors='*',methods=['PUT'])
|
||||
def change_password(self, **kw):
|
||||
http_method, body, headers, token = http_helper.parse_request()
|
||||
if not body.get('old_password') or not body.get('new_password'):
|
||||
return http_helper.errcode(code=400, message='Password must not be empty')
|
||||
|
||||
result = validator.verify_token(token)
|
||||
|
||||
if not result['status']:
|
||||
return http_helper.errcode(code=400, message='Invalid passwords')
|
||||
|
||||
user = validator.verify(token)
|
||||
if not user:
|
||||
return http_helper.errcode(code=400, message=_("You are not allowed to perform this operation. please check with one of your team admins"))
|
||||
|
||||
if not http_helper.is_authentic(user.login, body.get('old_password')):
|
||||
return http_helper.errcode(code=400, message='Invalid passwords')
|
||||
|
||||
request.env.user.write({
|
||||
'password':str(body.get('new_password')).strip()
|
||||
})
|
||||
request.session.logout()
|
||||
|
||||
|
||||
return http_helper.response(message=_("password changed successfully"),data={'id':user.id})
|
||||
|
||||
@http.route('/rest_api/web/logout', type='http', auth='none', csrf=False, cors='*', methods=['POST'])
|
||||
def logout(self, **kw):
|
||||
http_method, body, headers, token = http_helper.parse_request()
|
||||
result = validator.verify_token(token)
|
||||
if not result['status']:
|
||||
return http_helper.errcode(code=result['code'], message=result['message'])
|
||||
|
||||
http_helper.do_logout(token)
|
||||
return http_helper.response()
|
||||
|
||||
@http.route('/rest_api/web/users', type='http', auth='none', csrf=False, cors='*', methods=['GET'])
|
||||
def info(self, **kw):
|
||||
http_method, body, headers, token = http_helper.parse_request()
|
||||
result = validator.verify_token(token)
|
||||
if not result['status']:
|
||||
return http_helper.errcode(code=result['code'], message=result['message'])
|
||||
user = validator.verify(token)
|
||||
if not user:
|
||||
return http_helper.response(code=400, message=_("You are not allowed to perform this operation. please check with one of your team admins"), success=False)
|
||||
|
||||
return http_helper.response(data=user.to_dict(True))
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import odoo
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class WebController(http.Controller):
|
||||
@http.route('/web/session/authenticate', type='json', auth="none")
|
||||
def authenticate(self, login, password, base_location=None):
|
||||
db = odoo.tools.config.get('db_name')
|
||||
if not db:
|
||||
response_data = {
|
||||
"error": "Database name should be specified in Conf File",
|
||||
"status": 400
|
||||
}
|
||||
return response_data
|
||||
|
||||
request.session.authenticate(db, login, password)
|
||||
return request.env['ir.http'].session_info()
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
from odoo import http
|
||||
from odoo.http import request, Response
|
||||
from .validator import validator
|
||||
import simplejson as json
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
return_fields = ['id', 'login', 'name', 'company_id','state']
|
||||
|
||||
|
||||
class HttpHelper:
|
||||
|
||||
def get_state(self):
|
||||
return {
|
||||
'd': request.session.db
|
||||
}
|
||||
|
||||
def parse_request(self):
|
||||
http_method = request.httprequest.method
|
||||
try:
|
||||
body = http.request.params
|
||||
except Exception:
|
||||
body = {}
|
||||
|
||||
headers = dict(list(request.httprequest.headers.items()))
|
||||
if 'wsgi.input' in headers:
|
||||
del headers['wsgi.input']
|
||||
if 'wsgi.errors' in headers:
|
||||
del headers['wsgi.errors']
|
||||
if 'HTTP_AUTHORIZATION' in headers:
|
||||
headers['Authorization'] = headers['HTTP_AUTHORIZATION']
|
||||
|
||||
# extract token
|
||||
token = ''
|
||||
if 'Authorization' in headers:
|
||||
try:
|
||||
# Bearer token_string
|
||||
token = headers['Authorization'].split(' ')[1]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return http_method, body, headers, token
|
||||
|
||||
def date2str(self, d, f='%Y-%m-%d %H:%M:%S'):
|
||||
"""
|
||||
Convert datetime to string
|
||||
:param self:
|
||||
:param d: datetime object
|
||||
:param f='%Y-%m-%d%H:%M:%S': string format
|
||||
"""
|
||||
try:
|
||||
s = d.strftime(f)
|
||||
except:
|
||||
s = None
|
||||
finally:
|
||||
return s
|
||||
|
||||
def response(self, success=True, message=None, data=None, code=200,errors=None):
|
||||
"""
|
||||
Create a HTTP Response for controller
|
||||
:param success=True indicate this response is successful or not
|
||||
:param message=None message string
|
||||
:param data=None data to return
|
||||
:param code=200 http status code
|
||||
"""
|
||||
payload = json.dumps({
|
||||
'success': success,
|
||||
'message': message,
|
||||
'data': data,
|
||||
'code':code
|
||||
})
|
||||
|
||||
return Response(payload, status=code, headers=[
|
||||
('Content-Type', 'application/json'),
|
||||
])
|
||||
|
||||
def response_500(self, message='Internal Server Error', data=None):
|
||||
return self.response(success=False, message=message, data=data, code=500)
|
||||
|
||||
def response_404(self, message='404 Not Found', data=None):
|
||||
return self.response(success=False, message=message, data=data, code=404)
|
||||
|
||||
def response_403(self, message='403 Forbidden', data=None):
|
||||
return self.response(success=False, message=message, data=data, code=403)
|
||||
|
||||
def errcode(self, code, message=None):
|
||||
return self.response(success=False, code=code, message=message)
|
||||
|
||||
def is_authentic(self, login, password):
|
||||
state = self.get_state()
|
||||
name = login.strip()
|
||||
pwd = password.strip()
|
||||
return request.session.authenticate(state['d'], name, pwd)
|
||||
|
||||
|
||||
|
||||
def do_login(self, login, password):
|
||||
# get current db
|
||||
state = self.get_state()
|
||||
name = login.strip()
|
||||
pwd = password.strip()
|
||||
|
||||
uid = request.session.authenticate(state['d'], name, pwd)
|
||||
if not uid:
|
||||
return self.errcode(code=400, message='incorrect login')
|
||||
# login success, generate token
|
||||
token = validator.create_token(request.env.user)
|
||||
dic = request.env.user.to_dict(True)
|
||||
dic['token'] = token
|
||||
|
||||
return self.response(data=dic)
|
||||
|
||||
def do_logout(self, token):
|
||||
request.session.logout()
|
||||
request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
]).unlink()
|
||||
|
||||
def cleanup(self):
|
||||
# Clean up things after success request
|
||||
# use logout here to make request as stateless as possible
|
||||
|
||||
|
||||
|
||||
request.session.logout()
|
||||
|
||||
|
||||
http_helper = HttpHelper()
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from . import hr_employee
|
||||
from . import attendence_zone_config
|
||||
from . import mail_thread
|
||||
from . import access_token
|
||||
from . import res_users
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,35 @@
|
|||
from odoo import models, fields, api
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil import parser
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
str_fmt = '%d/%m/%Y %H:%M:%S'
|
||||
|
||||
class JwtAccessToken(models.Model):
|
||||
_name = 'jwt_provider.access_token'
|
||||
_description = 'Store user access token for one-time-login'
|
||||
|
||||
token = fields.Char('Access Token', required=True)
|
||||
user_id = fields.Many2one('res.users', string='User', required=True, ondelete='cascade')
|
||||
expires = fields.Datetime('Expires', required=True)
|
||||
|
||||
is_expired = fields.Boolean(compute='_compute_is_expired')
|
||||
|
||||
@api.depends('expires')
|
||||
def _compute_is_expired(self):
|
||||
ctr = datetime.now().strftime(str_fmt)
|
||||
_logger.info(ctr)
|
||||
for token in self:
|
||||
token.is_expired = datetime.now() > token.expires
|
||||
|
||||
def access_token_cron(self):
|
||||
# self.search([("is_expired", "=", True)]).unlink()
|
||||
current_time = datetime.now()
|
||||
expired_tokens = self.search([('expires', '<', current_time)])
|
||||
expired_tokens.unlink()
|
||||
return True
|
||||
|
||||
def set_env(self,env):
|
||||
self.env = env
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
import datetime
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
web_fcm_server_key = fields.Char(string='Server Key:', related="company_id.web_fcm_server_key", readonly=False)
|
||||
web_sender_id = fields.Char(string='Sender ID:', related="company_id.web_sender_id", readonly=False)
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
web_fcm_server_key = fields.Char(string='Server Key')
|
||||
web_sender_id = fields.Char(string='Sender ID')
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import models,fields,api,_
|
||||
from odoo.exceptions import ValidationError
|
||||
import random
|
||||
import json
|
||||
import json, requests
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
device_id = fields.Char(string="Employee Device ")
|
||||
fcm_token_web = fields.Char(string='FCM Web Token')
|
||||
|
||||
|
||||
def user_push_notification_web(self, notification):
|
||||
url = "https://fcm.googleapis.com/fcm/send"
|
||||
header = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'key=%s' % (self.env.user.company_id.web_fcm_server_key)
|
||||
}
|
||||
body = json.dumps({
|
||||
"to": self.fcm_token_web,
|
||||
"direct_boot_ok": True,
|
||||
"notification": {
|
||||
"title": "Message",
|
||||
"body": notification
|
||||
}
|
||||
})
|
||||
try:
|
||||
respons = requests.post(url=url, data=body, headers=header)
|
||||
return True
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class MailThread(models.AbstractModel):
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
def message_post(self, *, message_type='notification', **kwargs):
|
||||
if self._name in ['mail.channel']:
|
||||
notification_body = kwargs.get('body', '')
|
||||
attachments = len(kwargs.get('attachment_ids', []))
|
||||
if notification_body and attachments:
|
||||
notification_body += '\n{} File(s)'.format(attachments)
|
||||
elif attachments:
|
||||
notification_body = '{} File(s)'.format(attachments)
|
||||
|
||||
partners_to_notify = self.channel_partner_ids.filtered(lambda r: r.id != self.env.user.partner_id.id)
|
||||
for employee_id in self.env['hr.employee'].sudo().search([('user_id', 'in', partners_to_notify.user_ids.ids)]):
|
||||
push_notify = employee_id.user_push_notification_web(notification_body)
|
||||
|
||||
return super(MailThread, self).message_post(message_type=message_type, **kwargs)
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import werkzeug
|
||||
|
||||
from odoo.exceptions import AccessDenied
|
||||
from odoo import api, models, fields, SUPERUSER_ID
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
from ..validator import validator
|
||||
|
||||
|
||||
class Users(models.Model):
|
||||
_inherit = "res.users"
|
||||
|
||||
access_token_ids = fields.One2many(
|
||||
string="Access Tokens",
|
||||
comodel_name="jwt_provider.access_token",
|
||||
inverse_name="user_id",
|
||||
)
|
||||
|
||||
avatar = fields.Char(compute="_compute_avatar")
|
||||
# is_verified = fields.Boolean("Verified" , default=False)
|
||||
|
||||
@classmethod
|
||||
def _login(cls, db, login, password, user_agent_env):
|
||||
user_id = super(Users, cls)._login(
|
||||
db, login, password, user_agent_env=user_agent_env
|
||||
)
|
||||
if user_id:
|
||||
return user_id
|
||||
uid = validator.verify(password)
|
||||
return uid
|
||||
|
||||
@api.model
|
||||
def check_credentials(self, password):
|
||||
try:
|
||||
super(Users, self).check_credentials(password)
|
||||
except AccessDenied:
|
||||
# verify password as token
|
||||
if not validator.verify(password):
|
||||
raise
|
||||
|
||||
@api.depends("image_1024")
|
||||
def _compute_avatar(self):
|
||||
base = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
|
||||
for u in self:
|
||||
u.avatar = werkzeug.urls.url_join(base, "rest_api/web/avatar/%d" % u.id)
|
||||
|
||||
# @api.multi
|
||||
def to_dict(self, single=False):
|
||||
res = []
|
||||
for u in self:
|
||||
d = u.read(["email", "name", "avatar", "mobile", "phone", "partner_id"])[0]
|
||||
d["user_id"] = self.id
|
||||
d["partner_id"] = self.partner_id.id
|
||||
d["lang"] = self.partner_id.lang
|
||||
groups = self.user_groups()
|
||||
d["groups"] = groups
|
||||
employee = (
|
||||
self.env["hr.employee"]
|
||||
.sudo()
|
||||
.search([("user_id", "=", self.id)], limit=1)
|
||||
)
|
||||
# attendance_status = validator.get_attendance_check(employee)
|
||||
d["job"] = employee.job_id.name if employee and employee.job_id else None
|
||||
d["employe_id"] = employee.id if employee and employee.id else None
|
||||
# d["attendance_status"] = attendance_status if attendance_status else None
|
||||
|
||||
res.append(d)
|
||||
|
||||
return res[0] if single else res
|
||||
|
||||
def user_groups(self):
|
||||
groups = []
|
||||
if self.has_group("base.group_user"):
|
||||
groups.append("group_user")
|
||||
if self.has_group("hr_base.group_division_manager"):
|
||||
groups.append("group_division_manager")
|
||||
if self.has_group("hr.group_hr_manager"):
|
||||
groups.append("group_hr_manager")
|
||||
if self.has_group("hr_base.group_executive_manager"):
|
||||
groups.append("group_executive_manager")
|
||||
if self.has_group("hr_loans_salary_advance.group_loan_user"):
|
||||
groups.append("group_loan_user")
|
||||
if self.has_group("hr_base.group_general_manager"):
|
||||
groups.append("group_general_manager")
|
||||
if self.has_group("hr_base.group_account_manager"):
|
||||
groups.append("group_account_manager")
|
||||
if self.has_group("hr.group_hr_user"):
|
||||
groups.append("group_hr_user")
|
||||
if self.has_group("hr_timesheet.group_timesheet_manager"):
|
||||
groups.append("group_timesheet_manager")
|
||||
if self.has_group("hr_holidays.group_hr_holidays_user"):
|
||||
groups.append("group_hr_holidays_user")
|
||||
if self.has_group("hr_base.group_department_manager"):
|
||||
groups.append("group_department_manager")
|
||||
|
||||
return groups
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_jwt_access_token,Read.jwt.access.token,model_jwt_provider_access_token,,1,0,0,0
|
||||
|
|
|
@ -0,0 +1,293 @@
|
|||
import logging
|
||||
import jwt
|
||||
import re
|
||||
import datetime
|
||||
import traceback
|
||||
from odoo import http, service, registry, SUPERUSER_ID,_
|
||||
from odoo.http import request
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
import phonenumbers
|
||||
import math
|
||||
from math import ceil
|
||||
from odoo.service import security
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
SECRET_KEY = "skjdfe48ueq739rihesdio*($U*WIO$u8"
|
||||
|
||||
regex = r"^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
|
||||
|
||||
|
||||
class Validator:
|
||||
def get_page_pagination(self,page):
|
||||
limit = 20
|
||||
page = int(page)
|
||||
page = 1 if page<1 else page
|
||||
offset = (page - 1) * limit
|
||||
prev = False if page -1<=0 else page -1
|
||||
return page,offset,limit,prev
|
||||
|
||||
def get_page_pagination_next(self,page,count):
|
||||
page = int(page)
|
||||
page = page+1
|
||||
next = math.ceil(count / 20)
|
||||
if next <page:
|
||||
return False
|
||||
else:
|
||||
return page
|
||||
|
||||
def get_attendance_check(self,employee):
|
||||
last = http.request.env['attendance.attendance'].sudo().search([('employee_id', '=', employee.id), ], order='name desc',
|
||||
limit=1)
|
||||
if last:
|
||||
return last.action
|
||||
else:
|
||||
return 'sign_out'
|
||||
|
||||
|
||||
def get_state_name(self,obj,state):
|
||||
state_name = dict(obj.fields_get(["state"],['selection'])['state']["selection"]).get(obj.state)
|
||||
return state_name
|
||||
|
||||
def is_valid_name(self, name):
|
||||
return len (name) > 6
|
||||
|
||||
def is_valid_password(self, password):
|
||||
return len (password) > 5
|
||||
|
||||
def is_valid_location(self, location):
|
||||
try:
|
||||
lat , lat = [float(x) for x in location.split(',') if x.replace('.','',1).isdigit()]
|
||||
if lat and lat:
|
||||
return True
|
||||
except :
|
||||
pass
|
||||
return False
|
||||
|
||||
def is_valid_email(self, email):
|
||||
return re.search(regex, email)
|
||||
|
||||
def is_valid_phone(self,phone):
|
||||
try:
|
||||
if not phone.startswith('+'):
|
||||
phone = '+'+phone
|
||||
number = phonenumbers.parse(phone, None)
|
||||
isvalid = phonenumbers.is_valid_number(number)
|
||||
return isvalid
|
||||
except :
|
||||
pass
|
||||
return False
|
||||
|
||||
def get_server_error(self,e,user):
|
||||
x= False
|
||||
if len(e.args) == 2:
|
||||
x, y = e.args
|
||||
x = re.sub('\n', '', x)
|
||||
else:
|
||||
x = e.args
|
||||
text = ""
|
||||
if user.lang == 'en_US':
|
||||
text =(_("contact admin or edit it Manually"))
|
||||
else:
|
||||
text = "الرجاء التواصل مع مدير النظام او استخدام الموقع الالكترونى"
|
||||
message = "%s, %s" % (x,text)
|
||||
return message
|
||||
|
||||
|
||||
def create_token(self, user):
|
||||
try:
|
||||
exp = datetime.datetime.utcnow() + datetime.timedelta(days=7)
|
||||
payload = {
|
||||
'exp': exp,
|
||||
'iat': datetime.datetime.utcnow(),
|
||||
'sub': user['id'],
|
||||
'lgn': user['login'],
|
||||
}
|
||||
token = jwt.encode(
|
||||
payload,
|
||||
SECRET_KEY,
|
||||
algorithm='HS256'
|
||||
)
|
||||
# payload = jwt.decode(token, SECRET_KEY ,algorithms='HS256')
|
||||
print(token)
|
||||
self.save_token(token, user['id'], exp)
|
||||
|
||||
return token
|
||||
except Exception as ex:
|
||||
_logger.error(ex)
|
||||
raise
|
||||
|
||||
def save_token(self, token, uid, exp):
|
||||
request.env['jwt_provider.access_token'].sudo().create({
|
||||
'user_id': uid,
|
||||
'expires': exp.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'token': token,
|
||||
})
|
||||
|
||||
def verify(self, token):
|
||||
record = request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
])
|
||||
|
||||
if len(record) != 1:
|
||||
_logger.info('not found %s' % token)
|
||||
return False
|
||||
|
||||
if record.is_expired:
|
||||
return False
|
||||
|
||||
record.set_env(request.env)
|
||||
return record.user_id
|
||||
|
||||
def verify_token(self, token):
|
||||
try:
|
||||
result = {
|
||||
'status': False,
|
||||
'message': None,
|
||||
}
|
||||
|
||||
record = request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
])
|
||||
|
||||
if len(record) != 1:
|
||||
result['message'] = 'Token not found'
|
||||
result['code'] = 497
|
||||
return result
|
||||
|
||||
if record.is_expired:
|
||||
result['message'] = 'Token has expired'
|
||||
result['code'] = 498
|
||||
return result
|
||||
|
||||
payload = jwt.decode(token, SECRET_KEY ,algorithms='HS256')
|
||||
|
||||
uid =self.verify(token)
|
||||
user = uid
|
||||
request.session.uid = user.id
|
||||
request.session.login = user.login
|
||||
request.session.session_token = user.id and security.compute_session_token(
|
||||
request.session, request.env
|
||||
)
|
||||
request.uid = user.id
|
||||
request.disable_db = False
|
||||
request.session.get_context()
|
||||
|
||||
# Set user's context
|
||||
user_context = request.env(request.cr, request.session.uid)['res.users'].context_get().copy()
|
||||
request.session.context = request.context = user_context
|
||||
request.env.user = uid
|
||||
# uid = request.session.authenticate(request.session.db, uid=payload['sub'], password=token)
|
||||
if not uid:
|
||||
result['message'] = 'Token invalid or expired'
|
||||
result['code'] = 498
|
||||
return result
|
||||
|
||||
result['status'] = True
|
||||
return result
|
||||
except jwt.ExpiredSignatureError as ex :
|
||||
result['code'] = 498
|
||||
result['message'] = 'Signature has expired'
|
||||
_logger.error(traceback.format_exc())
|
||||
|
||||
except (jwt.InvalidTokenError, Exception) as e:
|
||||
result['code'] = 497
|
||||
result['message'] = 'Token invalid or expired'
|
||||
_logger.error(traceback.format_exc())
|
||||
return result
|
||||
|
||||
def refresh_token(self, token):
|
||||
try:
|
||||
result = {
|
||||
'status': False,
|
||||
'message': None,
|
||||
'code':200
|
||||
}
|
||||
record = request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
])
|
||||
|
||||
if len(record) != 1 or not record:
|
||||
result['message'] = 'Token not found'
|
||||
result['code'] = 497
|
||||
return result
|
||||
|
||||
payload = jwt.decode(token, SECRET_KEY ,algorithms='HS256')
|
||||
user = request.env['res.users'].sudo().search([('id', '=',payload['sub'])], limit=1)
|
||||
if not user:
|
||||
result['message'] = 'Token for user not found'
|
||||
result['code'] = 497
|
||||
return result
|
||||
|
||||
request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
]).unlink()
|
||||
result['status'] = True
|
||||
result['code'] == 200
|
||||
result['data'] = {'token':self.create_token(user)}
|
||||
return result
|
||||
except jwt.ExpiredSignatureError as ex :
|
||||
request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
]).unlink()
|
||||
result['status'] = True
|
||||
result['code'] == 200
|
||||
result['data'] = {'token':self.create_token(user)}
|
||||
_logger.error(traceback.format_exc())
|
||||
|
||||
except (jwt.InvalidTokenError, Exception) as e:
|
||||
result['code'] = 497
|
||||
result['message'] = 'Token invalid'
|
||||
_logger.error(traceback.format_exc())
|
||||
return result
|
||||
|
||||
|
||||
def validate_token(self, token):
|
||||
try:
|
||||
result = {
|
||||
'status': False,
|
||||
'message': None,
|
||||
'code':200
|
||||
}
|
||||
record = request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
])
|
||||
|
||||
if len(record) != 1 or not record:
|
||||
result['message'] = 'Token not found'
|
||||
result['code'] = 497
|
||||
return result
|
||||
|
||||
payload = jwt.decode(token, SECRET_KEY ,algorithms='HS256')
|
||||
user = request.env['res.users'].sudo().search([('id', '=',payload['sub'])], limit=1)
|
||||
if not user:
|
||||
result['message'] = 'Token for user not found'
|
||||
result['code'] = 497
|
||||
return result
|
||||
|
||||
uid =self.verify(token) #request.session.finalize()
|
||||
# uid = request.session.authenticate(request.session.db, uid=payload['sub'], password=token)
|
||||
if not uid:
|
||||
result['message'] = 'Token invalid or expired'
|
||||
result['code'] = 498
|
||||
return result
|
||||
result['data'] = {}
|
||||
return result
|
||||
except jwt.ExpiredSignatureError as ex :
|
||||
request.env['jwt_provider.access_token'].sudo().search([
|
||||
('token', '=', token)
|
||||
]).unlink()
|
||||
result['message'] = 'Token invalid or expired'
|
||||
result['code'] = 498
|
||||
_logger.error(traceback.format_exc())
|
||||
return result
|
||||
|
||||
except (jwt.InvalidTokenError, Exception) as e:
|
||||
result['code'] = 497
|
||||
result['message'] = 'Token invalid'
|
||||
_logger.error(traceback.format_exc())
|
||||
return result
|
||||
|
||||
|
||||
validator = Validator()
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
|
||||
|
||||
<!-- config-->
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">mobile.res.config.settings.view.form</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="//div[hasclass('settings')]" position="inside">
|
||||
<div class="app_settings_block" data-string="Web App Configuration" string="Web App" data-key="spl">
|
||||
<h2>WebMobile Configuration</h2>
|
||||
<div id="web_fcm_settings">
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="web_fcm_server_key"/>
|
||||
<field name="web_fcm_server_key"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="web_sender_id"/>
|
||||
<field name="web_sender_id"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!--hr-->
|
||||
|
||||
<record id="employee_web_mobile_inherited_form" model="ir.ui.view">
|
||||
<field name="name">employee.web.mobile.inherited.form</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="priority">200</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<field name="fcm_token_web"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -245,9 +245,12 @@ class RestApi(Controller):
|
|||
|
||||
context = request.env.context.copy()
|
||||
context.update({"active_model": btn.model})
|
||||
reject_reason = kw.get('reason_msg')
|
||||
if reject_reason:
|
||||
context.update({"reject_reason": reject_reason})
|
||||
context.update({"active_id": int(active_id)})
|
||||
request.env.context = context
|
||||
btn._run_code(active_id, btn.model, request.env)
|
||||
btn.with_context(context)._run_code(active_id, btn.model, request.env)
|
||||
res = obj.read(["id", "state"])[0]
|
||||
state = res["state"]
|
||||
btn_new = (
|
||||
|
|
|
|||
Loading…
Reference in New Issue