diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..a14f254b8
--- /dev/null
+++ b/.gitignore
@@ -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/
diff --git a/odex25_mobile/odex_mobile/controllers/authentication.py b/odex25_mobile/odex_mobile/controllers/authentication.py
index b1964bcfc..a69d9d6fd 100644
--- a/odex25_mobile/odex_mobile/controllers/authentication.py
+++ b/odex25_mobile/odex_mobile/controllers/authentication.py
@@ -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)
diff --git a/odex25_mobile/odex_mobile/controllers/rest_api_v2/attendance.py b/odex25_mobile/odex_mobile/controllers/rest_api_v2/attendance.py
index 70375b037..9ad2f66f0 100644
--- a/odex25_mobile/odex_mobile/controllers/rest_api_v2/attendance.py
+++ b/odex25_mobile/odex_mobile/controllers/rest_api_v2/attendance.py
@@ -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)
diff --git a/odex25_mobile/odex_mobile/controllers/rest_api_v2/authentication.py b/odex25_mobile/odex_mobile/controllers/rest_api_v2/authentication.py
index 8aa5f6f30..d2e5f3c84 100644
--- a/odex25_mobile/odex_mobile/controllers/rest_api_v2/authentication.py
+++ b/odex25_mobile/odex_mobile/controllers/rest_api_v2/authentication.py
@@ -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)
diff --git a/odex25_mobile/odex_mobile/models/access_token.py b/odex25_mobile/odex_mobile/models/access_token.py
index dfff1df61..8ed97b058 100644
--- a/odex25_mobile/odex_mobile/models/access_token.py
+++ b/odex25_mobile/odex_mobile/models/access_token.py
@@ -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):
diff --git a/odex25_mobile/odex_mobile/models/attendence_zone_config.py b/odex25_mobile/odex_mobile/models/attendence_zone_config.py
index 4feae0abd..eeddb14ec 100644
--- a/odex25_mobile/odex_mobile/models/attendence_zone_config.py
+++ b/odex25_mobile/odex_mobile/models/attendence_zone_config.py
@@ -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:
diff --git a/odex25_mobile/odex_mobile/models/hr_employee.py b/odex25_mobile/odex_mobile/models/hr_employee.py
index 03bc31389..bfb7ec31e 100644
--- a/odex25_mobile/odex_mobile/models/hr_employee.py
+++ b/odex25_mobile/odex_mobile/models/hr_employee.py
@@ -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 = {
diff --git a/odex25_mobile/odex_mobile/models/res_users.py b/odex25_mobile/odex_mobile/models/res_users.py
index 7b514f227..fde67073e 100644
--- a/odex25_mobile/odex_mobile/models/res_users.py
+++ b/odex25_mobile/odex_mobile/models/res_users.py
@@ -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()
diff --git a/odex25_mobile/odex_mobile/validator.py b/odex25_mobile/odex_mobile/validator.py
index 5afba63db..179427f5a 100644
--- a/odex25_mobile/odex_mobile/validator.py
+++ b/odex25_mobile/odex_mobile/validator.py
@@ -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
diff --git a/odex25_mobile/odex_mobile/views/attendance_zone_config_view.xml b/odex25_mobile/odex_mobile/views/attendance_zone_config_view.xml
index 5ce8b9065..4b41912a7 100644
--- a/odex25_mobile/odex_mobile/views/attendance_zone_config_view.xml
+++ b/odex25_mobile/odex_mobile/views/attendance_zone_config_view.xml
@@ -15,10 +15,10 @@
-
+
-
-
+
+
@@ -29,8 +29,9 @@
-
-
+
+
+
@@ -70,11 +71,7 @@
tree,form
-
+
diff --git a/odex25_mobile/odex_web_app/__init__.py b/odex25_mobile/odex_web_app/__init__.py
new file mode 100644
index 000000000..f7209b171
--- /dev/null
+++ b/odex25_mobile/odex_web_app/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import controllers
diff --git a/odex25_mobile/odex_web_app/__manifest__.py b/odex25_mobile/odex_web_app/__manifest__.py
new file mode 100644
index 000000000..1f175239b
--- /dev/null
+++ b/odex25_mobile/odex_web_app/__manifest__.py
@@ -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,
+}
diff --git a/odex25_mobile/odex_web_app/__pycache__/__init__.cpython-38.pyc b/odex25_mobile/odex_web_app/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 000000000..30dc47f3d
Binary files /dev/null and b/odex25_mobile/odex_web_app/__pycache__/__init__.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/__pycache__/http_helper.cpython-38.pyc b/odex25_mobile/odex_web_app/__pycache__/http_helper.cpython-38.pyc
new file mode 100644
index 000000000..09f0ddcdf
Binary files /dev/null and b/odex25_mobile/odex_web_app/__pycache__/http_helper.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/__pycache__/validator.cpython-38.pyc b/odex25_mobile/odex_web_app/__pycache__/validator.cpython-38.pyc
new file mode 100644
index 000000000..9f94059b9
Binary files /dev/null and b/odex25_mobile/odex_web_app/__pycache__/validator.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/controllers/__init__.py b/odex25_mobile/odex_web_app/controllers/__init__.py
new file mode 100644
index 000000000..f9417ca5b
--- /dev/null
+++ b/odex25_mobile/odex_web_app/controllers/__init__.py
@@ -0,0 +1,3 @@
+from . import authentication
+from . import web
+
diff --git a/odex25_mobile/odex_web_app/controllers/__pycache__/__init__.cpython-38.pyc b/odex25_mobile/odex_web_app/controllers/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 000000000..4374fcef9
Binary files /dev/null and b/odex25_mobile/odex_web_app/controllers/__pycache__/__init__.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/controllers/__pycache__/authentication.cpython-38.pyc b/odex25_mobile/odex_web_app/controllers/__pycache__/authentication.cpython-38.pyc
new file mode 100644
index 000000000..d5f1a2bd9
Binary files /dev/null and b/odex25_mobile/odex_web_app/controllers/__pycache__/authentication.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/controllers/__pycache__/web.cpython-38.pyc b/odex25_mobile/odex_web_app/controllers/__pycache__/web.cpython-38.pyc
new file mode 100644
index 000000000..5ddbe49fd
Binary files /dev/null and b/odex25_mobile/odex_web_app/controllers/__pycache__/web.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/controllers/authentication.py b/odex25_mobile/odex_web_app/controllers/authentication.py
new file mode 100644
index 000000000..9a69130e9
--- /dev/null
+++ b/odex25_mobile/odex_web_app/controllers/authentication.py
@@ -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))
diff --git a/odex25_mobile/odex_web_app/controllers/web.py b/odex25_mobile/odex_web_app/controllers/web.py
new file mode 100644
index 000000000..2c893e3dc
--- /dev/null
+++ b/odex25_mobile/odex_web_app/controllers/web.py
@@ -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()
diff --git a/odex25_mobile/odex_web_app/http_helper.py b/odex25_mobile/odex_web_app/http_helper.py
new file mode 100644
index 000000000..f744e3df7
--- /dev/null
+++ b/odex25_mobile/odex_web_app/http_helper.py
@@ -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()
diff --git a/odex25_mobile/odex_web_app/models/__init__.py b/odex25_mobile/odex_web_app/models/__init__.py
new file mode 100644
index 000000000..87567c3ab
--- /dev/null
+++ b/odex25_mobile/odex_web_app/models/__init__.py
@@ -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
diff --git a/odex25_mobile/odex_web_app/models/__pycache__/__init__.cpython-38.pyc b/odex25_mobile/odex_web_app/models/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 000000000..d300be7ae
Binary files /dev/null and b/odex25_mobile/odex_web_app/models/__pycache__/__init__.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/models/__pycache__/attendence_zone_config.cpython-38.pyc b/odex25_mobile/odex_web_app/models/__pycache__/attendence_zone_config.cpython-38.pyc
new file mode 100644
index 000000000..564d9601c
Binary files /dev/null and b/odex25_mobile/odex_web_app/models/__pycache__/attendence_zone_config.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/models/__pycache__/hr_employee.cpython-38.pyc b/odex25_mobile/odex_web_app/models/__pycache__/hr_employee.cpython-38.pyc
new file mode 100644
index 000000000..e3a21c38a
Binary files /dev/null and b/odex25_mobile/odex_web_app/models/__pycache__/hr_employee.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/models/__pycache__/mail_thread.cpython-38.pyc b/odex25_mobile/odex_web_app/models/__pycache__/mail_thread.cpython-38.pyc
new file mode 100644
index 000000000..87a915c26
Binary files /dev/null and b/odex25_mobile/odex_web_app/models/__pycache__/mail_thread.cpython-38.pyc differ
diff --git a/odex25_mobile/odex_web_app/models/access_token.py b/odex25_mobile/odex_web_app/models/access_token.py
new file mode 100644
index 000000000..8ed97b058
--- /dev/null
+++ b/odex25_mobile/odex_web_app/models/access_token.py
@@ -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
diff --git a/odex25_mobile/odex_web_app/models/attendence_zone_config.py b/odex25_mobile/odex_web_app/models/attendence_zone_config.py
new file mode 100644
index 000000000..19a610396
--- /dev/null
+++ b/odex25_mobile/odex_web_app/models/attendence_zone_config.py
@@ -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')
diff --git a/odex25_mobile/odex_web_app/models/hr_employee.py b/odex25_mobile/odex_web_app/models/hr_employee.py
new file mode 100644
index 000000000..863dc1cbe
--- /dev/null
+++ b/odex25_mobile/odex_web_app/models/hr_employee.py
@@ -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
+
+
diff --git a/odex25_mobile/odex_web_app/models/mail_thread.py b/odex25_mobile/odex_web_app/models/mail_thread.py
new file mode 100644
index 000000000..2f36083da
--- /dev/null
+++ b/odex25_mobile/odex_web_app/models/mail_thread.py
@@ -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)
\ No newline at end of file
diff --git a/odex25_mobile/odex_web_app/models/res_users.py b/odex25_mobile/odex_web_app/models/res_users.py
new file mode 100644
index 000000000..973f79683
--- /dev/null
+++ b/odex25_mobile/odex_web_app/models/res_users.py
@@ -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
diff --git a/odex25_mobile/odex_web_app/security/ir.model.access.csv b/odex25_mobile/odex_web_app/security/ir.model.access.csv
new file mode 100644
index 000000000..4a82f77dc
--- /dev/null
+++ b/odex25_mobile/odex_web_app/security/ir.model.access.csv
@@ -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
diff --git a/odex25_mobile/odex_web_app/validator.py b/odex25_mobile/odex_web_app/validator.py
new file mode 100644
index 000000000..5afba63db
--- /dev/null
+++ b/odex25_mobile/odex_web_app/validator.py
@@ -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 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()
diff --git a/odex25_mobile/odex_web_app/views/attendance_zone_config_view.xml b/odex25_mobile/odex_web_app/views/attendance_zone_config_view.xml
new file mode 100644
index 000000000..5d64aff2f
--- /dev/null
+++ b/odex25_mobile/odex_web_app/views/attendance_zone_config_view.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+ mobile.res.config.settings.view.form
+ res.config.settings
+
+
+
+
+
WebMobile Configuration
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/odex25_mobile/odex_web_app/views/hr_employee_view.xml b/odex25_mobile/odex_web_app/views/hr_employee_view.xml
new file mode 100644
index 000000000..f0442f300
--- /dev/null
+++ b/odex25_mobile/odex_web_app/views/hr_employee_view.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ employee.web.mobile.inherited.form
+ hr.employee
+
+ 200
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/odex25_mobile/odoo_dynamic_workflow_mobile/controllers/controllers.py b/odex25_mobile/odoo_dynamic_workflow_mobile/controllers/controllers.py
index c559fd1cb..4cdcc0b88 100644
--- a/odex25_mobile/odoo_dynamic_workflow_mobile/controllers/controllers.py
+++ b/odex25_mobile/odoo_dynamic_workflow_mobile/controllers/controllers.py
@@ -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 = (