Permalink
Browse files

[IMP] point_of_sale: Connect with Employee on PoS

=========
PURPOSE
=========

{Any employee can work on a PoS session, even if he has no user. This decreases the cost of PoS App.}

We are not competitive enough on the PoS Market because customers have to pay one user per cashier while they expect to pay one user per cash station.

===============
SPECIFICATIONS
===============

pos.config
- Add a checkbox : "Login with Employees"
- If set to TRUE, an "Allowed Employees" many2many appear
- If employees many2many is empty, all employees can log on this PoS
- If set to FALSE, can only log in with the currently logged user

hr.employee
- The barcode and pin fields were be moved to the hr.employee
- Set a barcode (not pin) by default at the creation of any employee
- No Employees module with PoS > dependency to hr

Open the PoS if "Log in with Employees" = FALSE
- Same behaviour than now, when I open the PoS, I'm logged in with the current user and can start working at once. The only difference is that it is not possible to switch users anymore.
- The button to close the session is visible

Open he PoS if "Log in with Employees" = TRUE
- When I open the PoS, the first screen I see is the login screen.
(a) I choose my employee and enter my pin code (if there is pin code on my employee, otherwise not required) (I can only see employees that have access to that PoS)
(b) I scan my barcode
(c) I scan my rfid card (future development)
- When I click on the employee name, I can log in with another employee
- If the employee is linked to a user that has the pos user/manager access right, I can see the button the close the session. Otherwise I cannot close the session (and thus cannot access backend).
- Add field employee on all records created by PoS (and replace the field user by that one) (on pos.orders)

Lock Screen
- Improve the PoS Interface
- Add a new feature to allow to "lock" the session. When a session has been locked, the user/employee has to re-log-in to access the PoS
- When I try to re-log-in, by default suggest the last employee logged in in the login screen

Migration
Plan a migration strategy > barcode and pin is now on the employee and not on the user anymore
  • Loading branch information...
pha-odoo authored and switch87 committed Oct 18, 2018
1 parent 47480a5 commit 34f934b0cf3d44241df38c0f9ab05dd40fbb8d15
Showing with 579 additions and 220 deletions.
  1. +13 −1 addons/hr/models/hr.py
  2. +4 −0 addons/hr/views/hr_views.xml
  3. +0 −47 addons/hr_attendance/models/hr_employee.py
  4. +2 −6 addons/hr_attendance/views/hr_employee_view.xml
  5. +0 −1 addons/point_of_sale/__manifest__.py
  6. +0 −3 addons/point_of_sale/data/point_of_sale_demo.xml
  7. +0 −1 addons/point_of_sale/models/__init__.py
  8. +1 −0 addons/point_of_sale/models/pos_config.py
  9. +1 −1 addons/point_of_sale/models/pos_order.py
  10. +0 −15 addons/point_of_sale/models/res_users.py
  11. +1 −1 addons/point_of_sale/report/pos_order_report.py
  12. BIN addons/point_of_sale/static/img/barcode.png
  13. +5 −0 addons/point_of_sale/static/img/login-bg-overlay.svg
  14. +79 −29 addons/point_of_sale/static/src/css/pos.css
  15. +75 −10 addons/point_of_sale/static/src/js/chrome.js
  16. +17 −20 addons/point_of_sale/static/src/js/gui.js
  17. +19 −41 addons/point_of_sale/static/src/js/models.js
  18. +56 −8 addons/point_of_sale/static/src/js/screens.js
  19. +1 −1 addons/point_of_sale/static/src/js/tests.js
  20. +27 −2 addons/point_of_sale/static/src/xml/pos.xml
  21. +21 −4 addons/point_of_sale/views/pos_config_view.xml
  22. +1 −3 addons/point_of_sale/views/pos_order_report_view.xml
  23. +7 −3 addons/point_of_sale/views/pos_order_view.xml
  24. +0 −18 addons/point_of_sale/views/res_users_views.xml
  25. +4 −0 addons/pos_hr/__init__.py
  26. +24 −0 addons/pos_hr/__manifest__.py
  27. +5 −0 addons/pos_hr/models/__init__.py
  28. +13 −0 addons/pos_hr/models/pos_config.py
  29. +34 −0 addons/pos_hr/models/pos_order.py
  30. +12 −0 addons/pos_hr/models/pos_session.py
  31. +3 −0 addons/pos_hr/report/__init__.py
  32. +17 −0 addons/pos_hr/report/pos_order_report.py
  33. +56 −0 addons/pos_hr/static/src/js/models.js
  34. +9 −0 addons/pos_hr/views/point_of_sale.xml
  35. +16 −0 addons/pos_hr/views/pos_config.xml
  36. +14 −0 addons/pos_hr/views/pos_order_report_view.xml
  37. +39 −0 addons/pos_hr/views/pos_order_view.xml
  38. +3 −5 addons/pos_sale/views/pos_config_views.xml
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from string import digits
import base64
import logging
from odoo import api, fields, models
from odoo import api, fields, models, SUPERUSER_ID
from odoo import tools, _
from odoo.exceptions import ValidationError, AccessError
from odoo.modules.module import get_module_resource
@@ -198,6 +199,16 @@ def _default_image(self):
# misc
notes = fields.Text('Notes')
color = fields.Integer('Color Index', default=0)
barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", copy=False)
pin = fields.Char(string="PIN", help="PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration).", copy=False)
_sql_constraints = [('barcode_uniq', 'unique (barcode)', "The Badge ID must be unique, this one is already assigned to another employee.")]
@api.constrains('pin')
def _verify_pin(self):
for employee in self:
if employee.pin and not employee.pin.isdigit():
raise ValidationError(_("The PIN must be a sequence of digits."))
@api.constrains('parent_id')
def _check_parent_id(self):
@@ -244,6 +255,7 @@ def _sync_user(self, user):
vals['tz'] = user.tz
return vals
@api.model
def create(self, vals):
if vals.get('user_id'):
@@ -139,6 +139,10 @@
<field name="company_id" groups="base.group_multi_company"/>
<field name="user_id" string="Related User"/>
</group>
<group string="Login Information" name="identification_group">
<field name="barcode"/>
<field name="pin"/>
</group>
</group>
</page>
</notebook>
@@ -1,36 +1,18 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from random import choice
from string import digits
from odoo import models, fields, api, exceptions, _, SUPERUSER_ID
class HrEmployee(models.Model):
_inherit = "hr.employee"
_description = "Employee"
def _default_random_pin(self):
return ("".join(choice(digits) for i in range(4)))
def _default_random_barcode(self):
barcode = None
while not barcode or self.env['hr.employee'].search([('barcode', '=', barcode)]):
barcode = "".join(choice(digits) for i in range(8))
return barcode
barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", default=_default_random_barcode, copy=False)
pin = fields.Char(string="PIN", default=_default_random_pin, help="PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration).", copy=False)
attendance_ids = fields.One2many('hr.attendance', 'employee_id', help='list of attendances for the employee')
last_attendance_id = fields.Many2one('hr.attendance', compute='_compute_last_attendance_id', store=True)
attendance_state = fields.Selection(string="Attendance Status", compute='_compute_attendance_state', selection=[('checked_out', "Checked out"), ('checked_in', "Checked in")])
manual_attendance = fields.Boolean(string='Manual Attendance', compute='_compute_manual_attendance', inverse='_inverse_manual_attendance',
help='The employee will have access to the "My Attendances" menu to check in and out from his session')
_sql_constraints = [('barcode_uniq', 'unique (barcode)', "The Badge ID must be unique, this one is already assigned to another employee.")]
@api.multi
def _compute_manual_attendance(self):
for employee in self:
@@ -59,12 +41,6 @@ def _compute_attendance_state(self):
att = employee.last_attendance_id.sudo()
employee.attendance_state = att and not att.check_out and 'checked_in' or 'checked_out'
@api.constrains('pin')
def _verify_pin(self):
for employee in self:
if employee.pin and not employee.pin.isdigit():
raise exceptions.ValidationError(_("The PIN must be a sequence of digits."))
@api.model
def attendance_scan(self, barcode):
""" Receive a barcode scanned from the Kiosk Mode and change the attendances of corresponding employee.
@@ -126,26 +102,3 @@ def attendance_action_change(self):
raise exceptions.UserError(_('Cannot perform check out on %(empl_name)s, could not find corresponding check in. '
'Your attendances have probably been modified manually by human resources.') % {'empl_name': self.name, })
return attendance
@api.model_cr_context
def _init_column(self, column_name):
""" Initialize the value of the given column for existing rows.
Overridden here because we need to have different default values
for barcode and pin for every employee.
"""
if column_name not in ["barcode", "pin"]:
super(HrEmployee, self)._init_column(column_name)
else:
default_compute = self._fields[column_name].default
query = 'SELECT id FROM "%s" WHERE "%s" is NULL' % (
self._table, column_name)
self.env.cr.execute(query)
employee_ids = self.env.cr.fetchall()
for employee_id in employee_ids:
default_value = default_compute(self)
query = 'UPDATE "%s" SET "%s"=%%s WHERE id = %s' % (
self._table, column_name, employee_id[0])
self.env.cr.execute(query, (default_value,))
@@ -19,12 +19,8 @@
</div>
</button>
</xpath>
<xpath expr="//group[@name='active_group']" position="after">
<group string='Attendance' groups="hr_attendance.group_hr_attendance_user">
<field name="barcode"/>
<field name="pin" groups="hr_attendance.group_hr_attendance_use_pin"/>
<field name="manual_attendance" attrs="{'invisible': [('user_id', '=', False)]}"/>
</group>
<xpath expr="//group[@name='identification_group']" position="inside">
<field name="manual_attendance" attrs="{'invisible': [('user_id', '=', False)]}"/>
</xpath>
</field>
</record>
@@ -37,7 +37,6 @@
'views/res_config_settings_views.xml',
'views/digest_views.xml',
'views/res_partner_view.xml',
'views/res_users_views.xml',
'views/report_statement.xml',
'views/report_userlabel.xml',
'views/report_saledetails.xml',
@@ -10,9 +10,6 @@
<record id='base.res_partner_12' model='res.partner'> <field name='barcode'>0420800000008</field> </record>
<record id='base.res_partner_18' model='res.partner'> <field name='barcode'>0421800000005</field> </record>
<record id="base.user_root" model="res.users">
<field name="barcode">0410100000006</field>
</record>
<record id="base.user_root" model="res.users">
<field name="barcode">0410100000006</field>
<field name="groups_id" eval="[(4,ref('group_pos_manager'))]"/>
@@ -12,5 +12,4 @@
from . import pos_session
from . import product
from . import res_partner
from . import res_users
from . import res_config_settings
@@ -174,6 +174,7 @@ def _compute_iface_start_categ_domain_ids(self):
module_pos_reprint = fields.Boolean(string="Reprint Receipt")
is_posbox = fields.Boolean("PosBox")
is_header_or_footer = fields.Boolean("Header & Footer")
module_pos_hr = fields.Boolean(help="If not enabled, only users in the 'Point of Sale' group can log in.")
def _compute_is_installed_account_accountant(self):
account_accountant = self.env['ir.module.module'].sudo().search([('name', '=', 'account_accountant'), ('state', '=', 'installed')])
@@ -524,7 +524,7 @@ def _default_pricelist(self):
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.user.company_id)
date_order = fields.Datetime(string='Order Date', readonly=True, index=True, default=fields.Datetime.now)
user_id = fields.Many2one(
comodel_name='res.users', string='Salesperson',
comodel_name='res.users', string='User',
help="Person who uses the cash register. It can be a reliever, a student or an interim employee.",
default=lambda self: self.env.uid,
states={'done': [('readonly', True)], 'invoiced': [('readonly', True)]},

This file was deleted.

Oops, something went wrong.
@@ -19,7 +19,7 @@ class PosOrderReport(models.Model):
[('draft', 'New'), ('paid', 'Paid'), ('done', 'Posted'),
('invoiced', 'Invoiced'), ('cancel', 'Cancelled')],
string='Status')
user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
user_id = fields.Many2one('res.users', string='User', readonly=True)
price_total = fields.Float(string='Total Price', readonly=True)
price_sub_total = fields.Float(string='Subtotal w/o discount', readonly=True)
total_discount = fields.Float(string='Total Discount', readonly=True)
Binary file not shown.
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="2000" height="1128" viewBox="0 0 2000 1128">
<polygon fill-opacity=".03" points="0 1077.844 392.627 778.443 1504.99 1127.745 0 1127.745"/>
<polygon fill-opacity=".02" points="392.216 778.443 283.294 0 0 0 0 666.504"/>
<polygon fill-opacity=".03" points="1000 0 2000 1009.98 2000 439.94 1749.817 0"/>
</svg>
@@ -2149,42 +2149,13 @@ td {
font-family: 'Lato';
font-family: Lato;
}
.pos .modal-dialog .popup-textinput{
display: flex;
flex-direction: column;
}
.pos .modal-dialog .popup-textinput .title,
.pos .modal-dialog .popup-textinput .footer{
flex: none;
}
.pos .modal-dialog .popup-textinput textarea{
resize: none;
flex: auto;
margin: 0 20px 80px;
}
.pos .modal-dialog .popup-confirm{
height: 250px;
}
.pos .popup-confirm .body {
overflow: auto;
height: 120px;
}
.pos .modal-dialog .popup-textinput{
display: flex;
flex-direction: column;
}
.pos .modal-dialog .popup-textinput .title,
.pos .modal-dialog .popup-textinput .footer{
flex: none;
}
.pos .modal-dialog .popup-textinput textarea{
resize: none;
flex: auto;
margin: 0 20px 80px;
}
.pos .popup .title {
background: rgba(255,255,255,0.5);
margin: 0;
@@ -2471,3 +2442,82 @@ td {
.pos .not-supported-browser img{
border-collapse: separate;
}
/* ********* Login Screen ********* */
.pos .login-overlay{
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
width: 100%;
height:100%;
z-index:1000;
background: linear-gradient(to right bottom, #77717e, #c9a8a9);
}
.pos .login-overlay:before {
content: '';
background-image: url(../../img/login-bg-overlay.svg);
background-color: rgba(0, 0, 0, 0.3);
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
width: 100%;
height:100%;
}
.pos .screen-login{
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
margin: auto;
width:550px;
height:300px;
text-align:center;
font-size:20px;
font-weight:bold;
background-color: #F0EEEE;
border-radius: 3px;
z-index:1200;
font-family: 'Lato';
}
.pos .login-title{
height: 55%;
vertical-align: middle;
line-height: 5;
font-size: larger;
}
.pos .login-body{
height:33%;
}
.pos .login-element{
float: left;
width: 45%;
height: 60%;
}
.pos .login-barcode-img{
width: 80px;
height: 55px;
background: white;
border: 0px;
}
.pos .login-barcode-text{
color: #999999;
font-size: 13px;
padding-top: 0.2em;
}
.pos .login-or{
font-size: 15px;
font-style: italic;
float: left;
width: 10%;
height: 100%;
line-height: 5;
}
.pos .login-button{
font-size: initial;
height: -webkit-fill-available;
color: #555555;
border-radius: 5px;
}
Oops, something went wrong.

0 comments on commit 34f934b

Please sign in to comment.