OpenERP Tutorial: Module creation and modification of the Point Of Sale
I. Introduction▲
WARNING
My native language is french. I have some basic knowledge of english language, thus I translated the tutorial by myself and sometimes by the use of a famous translation service, so I hope the whole text is easily understandable.
Thank you for reading
OpenERP programming is a rather complicated task. It is necessary to know several programming languages and you must also have a good idea of how OpenERP does work.My native language is french. I have some basic knowledge of english language, thus I translated the tutorial by myself and sometimes by the use of a famous translation service, so I hope the whole text is easily understandable.
Thank you for reading
The documentation is quite limited and very brief. There are very few forums or specialized blogs about OpenERP, and almost all are in English.
The few tutorials found on the Web are limited to specific problems.
They do not fully achieve a complex module, they just allow you to add a button or function somewhere. It's not enough.
The tutorial that I propose is the result of hundreds of hours of learning everything I could find in the official documentation and on the Web, as well as number of lines of code, of countless tests, bugs and other gaieties. You know what I mean...
This is due to a request from a customer that I had the mission to make a module that will allow the creation of cashiers for the Point Of Sale.
The context
OpenERP is installed on a Debian server. The client sells products through franchises. Each society can access to OpenERP, which is configured with Multi-Company feature.
Each company has a PC in its location that is connected to the Point of Sale. And each company has several cashiers as employees.
Customer demand
How could we make purchases with the Point Of Sale on the same computer (PC) without having to create a session each time a cashier wants to make a sale?
Point Of Sale actually allows to make sales with several seller. To do this, simply create users in the company, put them in the Point Of Sale/User Group and assign them a Point Of Sale and voila.
But each seller must then log in to make the sale.
These functions, however, native, did not suit the customer needs.
So I made a module that allows to create a different kind of users, the cashiers. You'll see later that this is not very complicated.
Where it gets complicated is the part of the module, (The Web module), which operates in the Point Of Sale, and which allows to open only one session in the morning, make sales by several cashiers without leaving the Point Of Sale, and thus prevent to close close the previous session to open a new one.
Finally, the last statement of the customer: the cashier is required. The sale must not be possible if the Point Of Sale has no cashiers.
Here is what it is.
I will show you the steps that will help you for the creation of your own modules.
The source code certainly needs some improvements and optimizations, do not hesitate to share your comments, it will also help me to move further into the intricacies of the OpenERP programming.
II. Requirements▲
To go with this tutorial, you need a good knowledge of programming languages used in OpenERP :
- Python ;
- XML ;
- JavaScript ;
- Qweb/JQuery ;
- Linux command line
III. Basics▲
To make it short, we can say that the making of a conventional module is done in three steps.:
This is a special case which will require additional programming steps. The POS is not integrated with OpenERP, so its entire Web interface will keep our attention.
- The creation of the initialization file of the module.
- The creation of the Python Object.
- The creation of the XML view.
This is a special case which will require additional programming steps. The POS is not integrated with OpenERP, so its entire Web interface will keep our attention.
IV. Necessary tools▲
Here are the softwares I use, feel free to use them or find similar softwares.
Configure WinSCP
We will configure WinSCP for it opens directly with your favorite code editor when you double-click a file.
- WinSCPWinSCP : SFTP/FTP client, very pleasant to use, and allows to easily navigate through the file tree.
- PuTTYPuTTY : Telnet / SSH client that will run Linux commands on the server (WinSCP also allows, but with some limitations)
- SublimeSublime : a very well made text editor. It is aptly named.
- FirefoxFirefox with FirebugFirebug extension. Essential for JavaScript debugging.
Configure WinSCP
We will configure WinSCP for it opens directly with your favorite code editor when you double-click a file.
- Launch WinSCP.
- Go to See menu, then Settings.
- In the dialog form, click on Editor in the left column.
- In the frame Editors preferences, click Add.
- In the dialog box, clik External editor then select the exe file of your text editor.
- In Use this editor for following files, select filter *.* (all files).
- Validate.
In this tutorial, we assume that OpenERP is installed on a Linux/Debian server and that we are working on a Windows PC.
I have installed OpenERP Version 7.0-20130703-231023 (version therefore dated 03 July 2013).
If you are working directly on the server, you will adapt the guidelines relating to the server administration (files, rights, etc..). The construction of the module remains the same.
I have installed OpenERP Version 7.0-20130703-231023 (version therefore dated 03 July 2013).
If you are working directly on the server, you will adapt the guidelines relating to the server administration (files, rights, etc..). The construction of the module remains the same.
V. Stages of completion▲
Creation of OpenERP internal module
- Creation of the module initialization file .
- Creation of the file that contains Python Object
- creation of the views (Form view, Tree view).
- Creation of a menu for the POS/Manager users group
- Creation of the access rights for the module
- Creation of the recording rules
- Creation of the icon
- Creation of the JavaScript file that contains module's actions and functions
- Creation of the GUI elements (drop-down list, labels, etc.) in a XML file
- Creation of the style file *.css for the design of the elements
- Creation of the language template (*.pot)
- Creation of the french tanslated file (*.po)
Warning :
In this tutorial, Point Of Sale may be called POS
Source code will be written in english, but texts and labels will be translated later
The module is called pos_cashier.
Once completed, the POS Cashiers Module will appear in the list of installed modules, like in the image below In this tutorial, Point Of Sale may be called POS
Source code will be written in english, but texts and labels will be translated later
The module is called pos_cashier.
Warning
The tutorial may seem very long, but I tried to dissect all stages of the implementation of a module and I also tried to explain as best as I could all the source code and the various files that compose the module
The tutorial may seem very long, but I tried to dissect all stages of the implementation of a module and I also tried to explain as best as I could all the source code and the various files that compose the module
VI. Module structure▲
Here is the file-tree of the pos_cashier module
i18n directory :
Contains the internationalization files of the module.
security directory :
Contains the access control file and recording rules file.
static directory :
Contains the « Web » part of the module.
It contains the css directory that will host the stylesheet, the img directory that will host the icon of the module and the requiered pictures, the js directory that will host the JavaScript file and the xml directory that will host the views of the module.
Also , at the root of the module, there is the Python module and the XML views.
i18n directory :
Contains the internationalization files of the module.
security directory :
Contains the access control file and recording rules file.
static directory :
Contains the « Web » part of the module.
It contains the css directory that will host the stylesheet, the img directory that will host the icon of the module and the requiered pictures, the js directory that will host the JavaScript file and the xml directory that will host the views of the module.
Also , at the root of the module, there is the Python module and the XML views.
VII. Creation of a basic OpenERP module▲
First, we will achieve the module that will allow the creation of the cashiers within OpenERP. The Web part of the module (which is in POS) will be discussed later.
VII-A. Creation of the directories▲
The OpenERP modules are usually placed in the « addons » directory, but I recommend that you create a special directory (outside OpenERP) where you will place your own modules.
For the tutorial, we will create a modules-openerp directory in the /opt directory on your server.
Log in with WinSCP (as root) and connect to your dev server.
Navigate to the /opt directory and create a new folder. Name it modules-openerp.
Log in using PuTTY (from WinSCP) and enter the following commands:
We assigned the /opt/modules-openerp directory to the user openerp and the group openerp, then we changed the rights.
For our future modules to be taken into account by OpenERP, we have to modify the server configuration file and add the path to our directory.
Modify openerp-server.conf file
If this line does not exist, add it by putting only the path to your directory.
There may be many paths to directories. They must be separated by a comma.
Save and close the file.
Restart the server using the following command
Make sure the server is started by opening a Web page with the corresponding URL
For the tutorial, we will create a modules-openerp directory in the /opt directory on your server.
Log in with WinSCP (as root) and connect to your dev server.
Navigate to the /opt directory and create a new folder. Name it modules-openerp.
Log in using PuTTY (from WinSCP) and enter the following commands:
Modify owner and rights
Sélectionnez
cd /opt [+Enter]
chown openerp:openerp ./modules-openerp [+Enter]
chmod 0755
./modules-openerp [+Enter]
For our future modules to be taken into account by OpenERP, we have to modify the server configuration file and add the path to our directory.
Modify openerp-server.conf file
- In WinSCP, go to /etc.
- Depending on the version, the openerp-server.conf file can be found in /etc or /etc /openerp.
- Double-click the file to edit.
- Change the line below by adding the full path to the modules directory that we have just created.
openerp-server.conf
Sélectionnez
addons_path = /opt/openerp/addons,/opt/openerp/server/openerp/addons,/opt/openerp/web/addons,/opt/modules-openerp
There may be many paths to directories. They must be separated by a comma.
Save and close the file.
Restart the server using the following command
Restart OpenERP
Sélectionnez
/etc/init.d/openerp-server restart [+Enter]
Then create different directories within the modules-openerp directoryhttp://IP_OF_YOUR_SERVER:8069
- pos_cashier
- i18n
- security
- static
- src
- css
- img
- js
- xml
- src
Modify owner and rights
Sélectionnez
cd /opt [+Enter]
chown openerp:openerp ./modules-openerp -R [+Enter]
chmod 0755
./modules-openerp -R [+Enter]
VII-B. Required Python files▲
There are three mandatory files when you create a module.
This is the file that contains all the information about the module : name, version, category, description, files to load, etc.
The data are noted as key:value separated by a comma (end of line).
The value can also contain an array like in the case of depends, data, js parameters , etc..
Parameters :
The « description » parameter
To insert a text on multiple lines, you must enclose with three double quotes
(""" text here """)
Put the fullest possible description.
To return to the line, you actually have to jump one more, otherwise the new line will not be visible in OpenERP.
As we stated in the __ init__.py file, OpenERP will attempt to load the pos_cashier module. We must now create the pos_cashier.py file (the module itself).
- __init__.py
- __openerp__.py
- our_famous_module.py
VII-B-1. The __init__.py file▲
This is the file that will invite OpenERP to load our module.
The contents of this file is very simple:
Put the name of the module. It is also the name of the directory.
The contents of this file is very simple:
__init__.py
Sélectionnez
import
pos_cashier
VII-B-2. The __openerp__.py file▲
This is the file that contains all the information about the module : name, version, category, description, files to load, etc.
__openerp__.py
Sélectionnez
#
-*-
coding:
utf-8
-*-
{
'
name
'
: '
POS
Cashiers
'
,
'
version
'
: '
1
.
0
.
0
'
,
'
category
'
: '
Point
Of
Sale
'
,
'
sequence
'
: 3
,
'
author
'
: '
Thierry
Godin
'
,
'
summary
'
: '
Manage
cashiers
for
Point
Of
Sale
'
,
'
description
'
: """
Manage
several
cashiers
for
each
Point
Of
Sale
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
This
could
be
handy
in
case
of
using
the
same
POS
at
the
same
cash
register
while
it
is
used
by
several
cashiers
.
Cashier
'
s
name
is
displayed
on
the
payement
receipt
and
on
the
order
.
Cashiers
are
allowed
to
change
the
current
cashier
(
by
choosing
their
name
in
the
drop
-
down
list
)
and
can
make
a
sell
without
creating
a
new
session
.
Cashier
'
s
name
is
mandatory
.
You
cannot
perform
a
sell
if
no
cashier
has
been
created
or
is
active
in
your
POS
.
The
shop
manager
will
know
who
made
the
sell
.
"""
,
'
depends
'
: ["
point_of_sale
"
],
'
data
'
: [
'
security
/
pos_cashier_security
.
xml
'
,
'
security
/
ir
.
model
.
access
.
csv
'
,
'
cashier_view
.
xml
'
,
'
order_cashier_view
.
xml
'
,
],
'
js
'
: [
'
static
/
src
/
js
/
pos_cashier
.
js
'
,
],
'
css
'
: [
'
static
/
src
/
css
/
pos_cashier
.
css
'
,
],
'
qweb
'
: [
'
static
/
src
/
xml
/
pos_cashier
.
xml
'
,
],
'
installable
'
: True
,
'
application
'
: False
,
'
auto_install
'
: False
,
}
If you copy the code from this page
Beware of Python code indentation. This can be estimated in this article. This is due to the editor I used to write this article.
The source code of the module can be downloaded at the bottom of the page.
The file is to fill like this
Beware of Python code indentation. This can be estimated in this article. This is due to the editor I used to write this article.
The source code of the module can be downloaded at the bottom of the page.
Sélectionnez
'
parameter
'
: '
value
'
,
'
parameter
'
: ['
value1
'
,'
value2
'
,'
value3
'
],
The value can also contain an array like in the case of depends, data, js parameters , etc..
Parameters :
- name : the module name;
- version : the module version;
- category : the category in which you put the module;
- sequence : This is a number that will show your module in the list of modules. 1, it will be up, 100 it will be down ;
- author : author of the module ;
- summary : a summary that explains what is your module. A very short text, it appears under the name of the module in the module list ;
- description : the full description of the module ;
- depends : modules that your module depends;
- data : files to load ;
- js : in case of a Web module like this one, the JavaScript files;
- css : the stylesheet;
- qweb : View of the Web Part (Template);
- installable : if your module is installable or not;
- application : set it to False. Your module will not be recognized as an application. It's OpenERP that issues certificates that qualify your module as an application;
- auto_install : set it to False, we will install the module by hand. (With a button, anyway ...)
The « description » parameter
To insert a text on multiple lines, you must enclose with three double quotes
(""" text here """)
Put the fullest possible description.
To return to the line, you actually have to jump one more, otherwise the new line will not be visible in OpenERP.
Underline text with the = sign will appear as the <h1></h1> web tag.
We created two files (which still bear the same names) for module initialization. But this is not enough!As we stated in the __ init__.py file, OpenERP will attempt to load the pos_cashier module. We must now create the pos_cashier.py file (the module itself).
VII-B-3. The pos_cashier.py file▲
I put the contents of the entire file.
Do not worry, we'll dissect it all quietly.
At the very beginning of the file
We will import the libraries we need for the module.
This is necessary because we will use native OpenERP and Python functions.
Then we create the Python object
Do not worry, we'll dissect it all quietly.
pos_cashier.py
Sélectionnez
#
-*-
coding:
utf-8
-*-
#
#############################################################################
#
#
Module
:
pos_cashier
#
Créé
le
:
2013-06-06
par
Thierry
Godin
#
#
Module
permettant
la
création
de
vendeurs
pour
les
points
de
vente
#
#
#############################################################################
import
openerp
from
openerp import
netsvc, tools, pooler
from
openerp.osv import
fields, osv
from
openerp.tools.translate import
_
import
time
class
pos_cashier
(osv.osv):
_name =
'
pos
.
cashier
'
_order =
'
cashier_name
asc
'
_columns =
{
'
pos_config_id
'
: fields.many2one
('
pos
.
config
'
, '
Point
Of
Sale
'
, required=
True
),
'
cashier_name
'
: fields.char
('
Cashier
'
, size=
128
, required=
True
),
'
active
'
: fields.boolean
('
Active
'
, help=
"
If
a
cashier
is
not
active
,
it
will
not
be
displayed
in
POS
"
),
}
_defaults =
{
'
cashier_name
'
: '
'
,
'
active
'
: True
,
'
pos_config_id
'
: lambda
self,cr,uid,c: self.pool.get
('
res
.
users
'
).browse
(cr, uid, uid, c).pos_config.id
,
}
_sql_constraints =
[
('
uniq_name
'
, '
unique
(
cashier_name
,
pos_config_id
)
'
, "
A
cashier
already
exists
with
this
name
in
this
Point
Of
sale
.
Cashier
'
s
name
must
be
unique
!
"
),
]
class
inherit_pos_order_for_cashiers
(osv.osv):
_name=
'
pos
.
order
'
_inherit=
'
pos
.
order
'
def
create_from_ui
(self, cr, uid, orders, context=
None
):
#
_logger.info("orders:
%r",
orders)
order_ids =
[]
for
tmp_order in
orders:
order =
tmp_order['
data
'
]
order_id =
self.create
(cr, uid, {
'
name
'
: order['
name
'
],
'
user_id
'
: order['
user_id
'
] or
False
,
'
session_id
'
: order['
pos_session_id
'
],
'
lines
'
: order['
lines
'
],
'
pos_reference
'
:order['
name
'
],
'
cashier_name
'
: order['
cashier_name
'
]
}, context)
for
payments in
order['
statement_ids
'
]:
payment =
payments[2
]
self.add_payment
(cr, uid, order_id, {
'
amount
'
: payment['
amount
'
] or
0
.0
,
'
payment_date
'
: payment['
name
'
],
'
statement_id
'
: payment['
statement_id
'
],
'
payment_name
'
: payment.get
('
note
'
, False
),
'
journal
'
: payment['
journal_id
'
]
}, context=
context)
if
order['
amount_return
'
]:
session =
self.pool.get
('
pos
.
session
'
).browse
(cr, uid, order['
pos_session_id
'
], context=
context)
cash_journal =
session.cash_journal_id
cash_statement =
False
if
not
cash_journal:
cash_journal_ids =
filter
(lambda
st: st.journal_id.type
=
=
'
cash
'
, session.statement_ids)
if
not
len
(cash_journal_ids):
raise
osv.except_osv
( _
('
error
!
'
),
_
("
No
cash
statement
found
for
this
session
.
Unable
to
record
returned
cash
.
"
))
cash_journal =
cash_journal_ids[0
].journal_id
self.add_payment
(cr, uid, order_id, {
'
amount
'
: -
order['
amount_return
'
],
'
payment_date
'
: time.strftime
('
%
Y
-
%
m
-
%d
%
H
:
%
M
:
%S
'
),
'
payment_name
'
: _
('
return
'
),
'
journal
'
: cash_journal.id
,
}, context=
context)
order_ids.append
(order_id)
wf_service =
netsvc.LocalService
("
workflow
"
)
wf_service.trg_validate
(uid, '
pos
.
order
'
, order_id, '
paid
'
, cr)
return
order_ids
_columns =
{
'
cashier_name
'
: fields.char
('
Cashier
'
, size=
128
),
}
inherit_pos_order_for_cashiers
()
We will import the libraries we need for the module.
Sélectionnez
import
openerp
from
openerp import
netsvc, tools, pooler
from
openerp.osv import
fields, osv
from
openerp.tools.translate import
_
import
time
Then we create the Python object
Object is stated like this
Sélectionnez
class
pos_cashier
(osv.osv):
From now on, we must be very careful with the code indentation. You will notice that there is no signal of the end of the object.
This is why we must pay attention to the code editor we use, it must be able to handle Python for apropriate indentation.
If in doubt, do not hesitate to read this page : FAQ PythonFAQ Python and this one : Cours PythonCours Python.
This is why we must pay attention to the code editor we use, it must be able to handle Python for apropriate indentation.
If in doubt, do not hesitate to read this page : FAQ PythonFAQ Python and this one : Cours PythonCours Python.
VII-B-3-a. Statements▲
_name : is the name of the table in OpenERP database. In fact, the table is really named « pos_cashier » in the database.
_order : as you may understand , this is like SQL statement « ORDER BY ». Here, cashiers will be sorted by their names
_columns : these are the fields that will be created in the pos_cashier table.
pos_config_id field
Here we record the ID of the Point of Sale of the user. This field relationship with the pos_config table wich contains settings of each Point Of Sale.
cashier_name field
It is in this field that will record the name of the cashier.
active field
This field will allow us to enable or disable a cashier (when on leave due to illness or travel, for example).
By default, the active check box in the form will be checked and we automatically retrieve the ID of the Point of Sale of the user. That means, that when we create a new cashier, the Point of Sale of the user will be selected in (pos_config_id) drop-down list in the form. The name field (cashier_name) will be empty.
_sql_constraints : that are record rules , which is in SQL is called CONSTRAINT.
The rules are to be recorded as follows:
The rule unique(cashier_name, pos_config_id) means that there can be only one cashier with the same name in the same Point Of Sale.
_sql_constraints is an array. You can set multiple rules separated by a comma.
_name
Sélectionnez
_name =
'
pos
.
cashier
'
_order
Sélectionnez
_order =
'
cashier_name
asc
'
_columns
Sélectionnez
_columns =
{
'
pos_config_id
'
: fields.many2one
('
pos
.
config
'
, '
Point
Of
Sale
'
, required=
True
),
'
cashier_name
'
: fields.char
('
Cashier
'
, size=
128
, required=
True
),
'
active
'
: fields.boolean
('
Active
'
, help=
"
If
a
cashier
is
not
active
,
it
will
not
be
displayed
in
POS
"
),
}
Here we record the ID of the Point of Sale of the user. This field relationship with the pos_config table wich contains settings of each Point Of Sale.
cashier_name field
It is in this field that will record the name of the cashier.
active field
This field will allow us to enable or disable a cashier (when on leave due to illness or travel, for example).
In OpenERP, the active field is a special field. When active = False, the record is automatically not visible.
_defaults : these are the default values for records .
_columns
Sélectionnez
_defaults =
{
'
cashier_name
'
: '
'
,
'
active
'
: True
,
'
pos_config_id
'
: lambda
self,cr,uid,c: self.pool.get
('
res
.
users
'
).browse
(cr, uid, uid, c).pos_config.id
,
}
_sql_constraints : that are record rules , which is in SQL is called CONSTRAINT.
_sql_constraints
Sélectionnez
_sql_constraints =
[
('
uniq_name
'
, '
unique
(
cashier_name
,
pos_config_id
)
'
, "
A
cashier
already
exists
with
this
name
in
this
Point
Of
sale
.
Cashier
'
s
name
must
be
unique
!
"
),
]
Sélectionnez
('
NAME
OF
THE
RULE
'
, '
RULE
'
, "
MESSAGE
IN
CASE
OF
CONSTRAINT
VIOLATION
"
)
_sql_constraints is an array. You can set multiple rules separated by a comma.
VII-B-3-b. Overload pos.order object of Point Of Sale▲
In addition to creating the pos_cashier object that allows us to manage the cashiers, we need to override the original pos_order object of the Point Of sale
This module is in the original directory of Point Of Sale. You find it in the file point_of_sale.py around line 479.
For this, we will create a new object that inherits from the original parent class.
For our module inherits pos_order module, we will give it the same name
And we will add the _inherit statement specifying the name of the parent module
Once done, we will add a field to the orders.
We can then send the cashier's name in the cashier_name field of the pos_order table
This is all we add to this function.
The last thing to do is to add the cashier_name field to the pos_order table.
As in the previous module, simply add the field in the _columns statement
We're almost there. It remains the call to the object for OpenERP to take it into account..
We achieved pos_cashier Python module.
Save the file to the root of the module.
This module is in the original directory of Point Of Sale. You find it in the file point_of_sale.py around line 479.
We need to change the create_from_ui () function and add a cashier_name field in the orders table called pos_order.openerp_path/addons/point_of_sale/point_of_sale.py
For this, we will create a new object that inherits from the original parent class.
This is why we stated that our module depended of point_of_sale module in the __ openerp__.py file.
inherit_pos_order_for_cashiers
Sélectionnez
class
inherit_pos_order_for_cashiers
(osv.osv):
_name=
'
pos
.
order
'
_inherit=
'
pos
.
order
'
_name
Sélectionnez
_name=
'
pos
.
order
'
_inherit
Sélectionnez
_inherit=
'
pos
.
order
'
About OpenERP objects inheritance I suggest you to read this page on the publisher's website :
OpenERP Object InheritanceOpenERP Object Inheritance.
Attention, this is the documentation for the version 6.x, but the instructions are still valid for version 7.x of OpenERP.
We will then just copy the entire original create_from_ui() function in our file.OpenERP Object InheritanceOpenERP Object Inheritance.
Attention, this is the documentation for the version 6.x, but the instructions are still valid for version 7.x of OpenERP.
Once done, we will add a field to the orders.
A little explanation
Point Of Sale works with JavaScript scripts.
Orders are stored in the browser (LocalStorage).
See : Principe de fonctionnement du Point De VentePrincipe de fonctionnement du Point De Vente.
As long as you make an order, it is stored in the browser. When you confirm the order, the create_from_ui() function is called. It will send the order to the database of OpenERP.
In fact, it will send all valid orders that are stored in the browser.
This allows the Point Of Sale to work in offline mode
In the loop for tmp_order in orders:, we add the cashier_name field din self.create() function like belowPoint Of Sale works with JavaScript scripts.
Orders are stored in the browser (LocalStorage).
See : Principe de fonctionnement du Point De VentePrincipe de fonctionnement du Point De Vente.
As long as you make an order, it is stored in the browser. When you confirm the order, the create_from_ui() function is called. It will send the order to the database of OpenERP.
In fact, it will send all valid orders that are stored in the browser.
This allows the Point Of Sale to work in offline mode
for tmp_order in orders loop:
Sélectionnez
for
tmp_order in
orders:
order =
tmp_order['
data
'
]
order_id =
self.create
(cr, uid, {
'
name
'
: order['
name
'
],
'
user_id
'
: order['
user_id
'
] or
False
,
'
session_id
'
: order['
pos_session_id
'
],
'
lines
'
: order['
lines
'
],
'
pos_reference
'
:order['
name
'
], #
<----------
COMMA
'
cashier_name
'
: order['
cashier_name
'
] #
<----------
ADD
THE
FIELD
HERE
}, context)
Do not forget to add a comma at the end of the previous line.
The cashier's name will be recorded in order[] array.We can then send the cashier's name in the cashier_name field of the pos_order table
This is all we add to this function.
The last thing to do is to add the cashier_name field to the pos_order table.
As in the previous module, simply add the field in the _columns statement
_columns
Sélectionnez
_columns =
{
'
cashier_name
'
: fields.char
('
Cashier
'
, size=
128
),
}
Sélectionnez
inherit_pos_order_for_cashiers
()
Save the file to the root of the module.
VII-B-4. cashier_view.xml file▲
This is the file of the views of pos_cashier module. More exact, there are the views of the cashiers. We will put the views of the orders in another file.
In this file we will create the tree_view, the form_view, menus, search_view, and the action of the « Cashiers » menu.
As before, I put the complete code, and we will see that quietly.
OpenERP view file is always built this way
The first line contains the identifier of the view and the model used.
id="pos_cashier_form"
To make it easier to debug later, I recommend you put the module name, followed by the type of view.
model="ir.ui.view"
Since this is a « View », the model will always be ir.ui.view (the view is stored in the ir_ui_view table of OpenERP).
The following three fields (mandatory)
name="name" field
This is the name of the view. Return the identifier of the view, but replace underscores with a dot so that there is no confusion.
name="model" field
This is the name of the table. Here we use the pos.cashier table.
name="arch" field
It is inside this tag that will put the view itself.
So we will insert a form
name="cashier_name"
This is the text field that allows you to enter the name of the cashier.
name="pos_config_id"
This field will display the dropdown list of available Points Of Sale through the widget="selection" attribute
With the « eval » attribute, we ask OpenERP to display the name of the Point Of Sale
name="active"
This is the checkbox that enables / disable a cashier.
You will notice that the fields are inside a <group> tag.
This tells OpenERP how to display the fields in the form page
Here we specify, with the col attribute, the number of columns to use.
Form fields appear like this
Here's how the cashiers creation form will be displayed
Instead of <form> tag, we will insert a <tree> tag.
These columns appear in the cashiers table.
In this file we will create the tree_view, the form_view, menus, search_view, and the action of the « Cashiers » menu.
As before, I put the complete code, and we will see that quietly.
cashier_view.xml
Sélectionnez
<?
xml
version="1.0"
encoding="utf-8"?
>
<
openerp
>
<
data
>
<
record
id
=
"
pos_cashier_form
"
model
=
"
ir.ui.view
"
>
<
field
name
=
"
name
"
>
pos.cashier.form<
/
field
>
<
field
name
=
"
model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
form
string
=
"
Cashiers
"
version
=
"
7.0
"
>
<
group
col
=
"
4
"
>
<
field
name
=
"
cashier_name
"
/
>
<
field
name
=
"
pos_config_id
"
widget
=
"
selection
"
eval
=
"
ref('pos.config.name')
"
/
>
<
field
name
=
"
active
"
/
>
<
/
group
>
<
/
form
>
<
/
field
>
<
/
record
>
<
record
id
=
"
pos_cashier_tree
"
model
=
"
ir.ui.view
"
>
<
field
name
=
"
name
"
>
pos.cashier.tree<
/
field
>
<
field
name
=
"
model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
tree
string
=
"
Cashiers
"
>
<
field
name
=
"
cashier_name
"
/
>
<
field
name
=
"
pos_config_id
"
ref
=
"
pos.config.name
"
/
>
<
field
name
=
"
active
"
/
>
<
/
tree
>
<
/
field
>
<
/
record
>
<
record
model
=
"
ir.ui.view
"
id
=
"
pos_cashier_search
"
>
<
field
name
=
"
name
"
>
pos.cashier.search<
/
field
>
<
field
name
=
"
model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
search
string
=
"
Point
of
Sale
Cashier
"
>
<
field
name
=
"
cashier_name
"
/
>
<
filter
name
=
"
filter_see_all
"
string
=
"
All
"
domain
=
"
['|',
('active',
'=',True),
('active',
'=',False)]
"
/
>
<
filter
name
=
"
filter_see_active
"
string
=
"
Active
"
domain
=
"
[('active',
'=',True)]
"
/
>
<
filter
name
=
"
filter_see_inactive
"
string
=
"
Inactive
"
domain
=
"
[('active',
'=',False)]
"
/
>
<
/
search
>
<
/
field
>
<
/
record
>
<!--
L'action
du
menu
-->
<
record
model
=
"
ir.actions.act_window
"
id
=
"
action_pos_cashier
"
>
<
field
name
=
"
name
"
>
Cashiers<
/
field
>
<
field
name
=
"
type
"
>
ir.actions.act_window<
/
field
>
<
field
name
=
"
res_model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
view_type
"
>
form<
/
field
>
<
field
name
=
"
view_mode
"
>
tree,form<
/
field
>
<
field
name
=
"
view_id
"
ref
=
"
pos_cashier_tree
"
/
>
<
field
name
=
"
context
"
>
{"search_default_filter_see_all":1}<
/
field
>
<
field
name
=
"
help
"
type
=
"
html
"
>
<
p
class
=
"
oe_view_nocontent_create
"
>
Click here to create a cashier for the Point Of Sale.
<
/
p
>
<
/
field
>
<
/
record
>
<!--
Menu
gauche
Vendeurs
-->
<
menuitem
name
=
"
Cashiers
"
id
=
"
menu_point_of_sale_cashiers
"
parent
=
"
point_of_sale.menu_point_root
"
sequence
=
"
16
"
groups
=
"
point_of_sale.group_pos_manager
"
/
>
<
menuitem
id
=
"
menu_action_pos_cashier
"
parent
=
"
menu_point_of_sale_cashiers
"
action
=
"
action_pos_cashier
"
/
>
<!--
#
-->
<
/
data
>
<
/
openerp
>
Structure of an OpenERP view
Sélectionnez
<?
xml
version="1.0"
encoding="utf-8"?
>
<
openerp
>
<
data
>
<
record
>
<!--
Here
the
various
fields
of
the
view
-->
<
/
record
>
<
menuitem
/
>
<!--
Etc.
-->
<
/
data
>
<
/
openerp
>
VII-B-4-a. The form view▲
pos_cashier_form View
Sélectionnez
<
record
id
=
"
pos_cashier_form
"
model
=
"
ir.ui.view
"
>
<
field
name
=
"
name
"
>
pos.cashier.form<
/
field
>
<
field
name
=
"
model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
form
string
=
"
Cashiers
"
version
=
"
7.0
"
>
<
group
col
=
"
4
"
>
<
field
name
=
"
cashier_name
"
/
>
<
field
name
=
"
pos_config_id
"
widget
=
"
selection
"
eval
=
"
ref('pos.config.name')
"
/
>
<
field
name
=
"
active
"
/
>
<
/
group
>
<
/
form
>
<
/
field
>
<
/
record
>
id="pos_cashier_form"
To make it easier to debug later, I recommend you put the module name, followed by the type of view.
model="ir.ui.view"
Since this is a « View », the model will always be ir.ui.view (the view is stored in the ir_ui_view table of OpenERP).
The following three fields (mandatory)
Sélectionnez
<
field
name
=
"
name
"
>
pos.cashier.form<
/
field
>
<
field
name
=
"
model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<!--
other
objects
inside
"arch"
field
-->
<
/
field
>
This is the name of the view. Return the identifier of the view, but replace underscores with a dot so that there is no confusion.
name="model" field
This is the name of the table. Here we use the pos.cashier table.
name="arch" field
It is inside this tag that will put the view itself.
So we will insert a form
Sélectionnez
<
form
string
=
"
Cashiers
"
version
=
"
7.0
"
>
<
group
col
=
"
4
"
>
<
field
name
=
"
cashier_name
"
/
>
<
field
name
=
"
pos_config_id
"
widget
=
"
selection
"
eval
=
"
ref('pos.config.name')
"
/
>
<
field
name
=
"
active
"
/
>
<
/
group
>
<
/
form
>
When you add the string attribute in a field, this is the text that will be displayed (instead of the name of the field in the database, if that is the case).
So we add the nedded form fields.name="cashier_name"
This is the text field that allows you to enter the name of the cashier.
name="pos_config_id"
This field will display the dropdown list of available Points Of Sale through the widget="selection" attribute
With the « eval » attribute, we ask OpenERP to display the name of the Point Of Sale
name="active"
This is the checkbox that enables / disable a cashier.
You will notice that the fields are inside a <group> tag.
This tells OpenERP how to display the fields in the form page
Here we specify, with the col attribute, the number of columns to use.
Form fields appear like this
Here's how the cashiers creation form will be displayed
VII-B-4-b. The Tree view▲
pos_cashier_tree View
Sélectionnez
<
record
id
=
"
pos_cashier_tree
"
model
=
"
ir.ui.view
"
>
<
field
name
=
"
name
"
>
pos.cashier.tree<
/
field
>
<
field
name
=
"
model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
tree
string
=
"
Cashiers
"
>
<
field
name
=
"
cashier_name
"
/
>
<
field
name
=
"
pos_config_id
"
ref
=
"
pos.config.name
"
/
>
<
field
name
=
"
active
"
/
>
<
/
tree
>
<
/
field
>
<
/
record
>
These columns appear in the cashiers table.
VII-B-4-c. The search view▲
This is a special view. It will create search filters that appear by clicking the arrow in the search form in the upper right of the page.
This time, instead of <tree> tag, we will insert a <search> tag in which we specify a search field and several filters.
So we add the following field
We will now add a search filter with the <filter> tag
name="filter_see_all"
Here we call the filter : filter_see_all.
string="All"
This is the word that will be displayed in the search form
domain="['|', ('active', '=',True), ('active', '=',False)]"
This is the area of research.
Here we search for active or non-active cashiers. We want to see all cashiers.
The domain attribute is an array in which you put the search parameters
here, the « | » (or) operator indicates that at least one condition must be met.
pos_cashier_search View
Sélectionnez
<
record
model
=
"
ir.ui.view
"
id
=
"
pos_cashier_search
"
>
<
field
name
=
"
name
"
>
pos.cashier.search<
/
field
>
<
field
name
=
"
model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
search
string
=
"
Point
of
Sale
Cashier
"
>
<
field
name
=
"
cashier_name
"
/
>
<
filter
name
=
"
filter_see_all
"
string
=
"
All
"
domain
=
"
['|',
('active',
'=',True),
('active',
'=',False)]
"
/
>
<
filter
name
=
"
filter_see_active
"
string
=
"
Active
"
domain
=
"
[('active',
'=',True)]
"
/
>
<
filter
name
=
"
filter_see_inactive
"
string
=
"
Inactive
"
domain
=
"
[('active',
'=',False)]
"
/
>
<
/
search
>
<
/
field
>
<
/
record
>
So we add the following field
Sélectionnez
<
field
name
=
"
cashier_name
"
/
>
Sélectionnez
<
filter
name
=
"
filter_see_all
"
string
=
"
All
"
domain
=
"
['|',
('active',
'=',True),
('active',
'=',False)]
"
/
>
Here we call the filter : filter_see_all.
string="All"
This is the word that will be displayed in the search form
domain="['|', ('active', '=',True), ('active', '=',False)]"
This is the area of research.
Here we search for active or non-active cashiers. We want to see all cashiers.
The domain attribute is an array in which you put the search parameters
here, the « | » (or) operator indicates that at least one condition must be met.
Inactive objects are not visible in tree view nor form view by default. We must create a filter to display inactive cashiers in order to activate them when needed.
The other two filters will display the active or inactive cashiersVII-B-4-d. The Cashiers menu▲
We will now create a Cashiers menu that will appear in Cashiers heading in the Point Of Sale left menu
A menu is written in <menuitem/> tag.
name="Cashiers"
This is the name of the heading.
id
As usual, it specifies an identifier for the heading.
parent
This is what allows us to insert the item in an existing menu.
Here we insert our menu item in the Point of Sale menu, we must retrieve the identifier of the menu in the original file of the Point Of Sale. As this menu does not belong to our module, we refer to it by using the usual dot syntax.
sequence
This is the number that is used to sort the heading. The lower the number, the higher the heading is.
groups
We want to restrict access to this menu for POS/Manager user group.
We use here also dot syntax to refer to the group. We could allow multiple groups, by adding them separated by a comma.
This time, we add the action attribute that refers to the action that we will define later.
You will also notice that the parent menu is the heading we created earlier. Clearly, the menu will be within this heading.
The menu appears in the Menu tab of the POS/Manager group (from Configuration/Groups menu) as shown in the image below.
We could have simply to add only Cashiers menu without adding a Cashiers heading, but it will show you how to create a menu inside a heading.
Moreover, you will see that this heading will be visible only by one user group. If in the future you decide to add a menu in this heading, only authorized users in the group can see it..
Moreover, you will see that this heading will be visible only by one user group. If in the future you decide to add a menu in this heading, only authorized users in the group can see it..
I present to you the creation of the menu before the creation of the action for a better understanding, but in fact, the code will require that the menu is written after the action, because as referring to the action, if the menu is written before, Python will return the error action_pos_cashier does not exist.
The « Cashiers » heading
Cashiers heading
Sélectionnez
<
menuitem
name
=
"
Cashiers
"
id
=
"
menu_point_of_sale_cashiers
"
parent
=
"
point_of_sale.menu_point_root
"
sequence
=
"
16
"
groups
=
"
point_of_sale.group_pos_manager
"
/
>
name="Cashiers"
This is the name of the heading.
id
As usual, it specifies an identifier for the heading.
parent
This is what allows us to insert the item in an existing menu.
Here we insert our menu item in the Point of Sale menu, we must retrieve the identifier of the menu in the original file of the Point Of Sale. As this menu does not belong to our module, we refer to it by using the usual dot syntax.
sequence
This is the number that is used to sort the heading. The lower the number, the higher the heading is.
groups
We want to restrict access to this menu for POS/Manager user group.
We use here also dot syntax to refer to the group. We could allow multiple groups, by adding them separated by a comma.
A menu which has no action attribute becomes a heading
The « Cashiers » menu
Cashiers menu
Sélectionnez
<
menuitem
id
=
"
menu_action_pos_cashier
"
parent
=
"
menu_point_of_sale_cashiers
"
action
=
"
action_pos_cashier
"
/
>
You will also notice that the parent menu is the heading we created earlier. Clearly, the menu will be within this heading.
The menu appears in the Menu tab of the POS/Manager group (from Configuration/Groups menu) as shown in the image below.
Here we can see the sorting of the menus according to their sequence.
VII-B-4-e. The action of the menu▲
When we click on the Cashiers menu, the following action will be executed.
When it comes to action, then we use ir.actions.act_window model (the action will be recorded in the ir_act_window table of OpenERP).
name="type"
This is the type of the action
name="res_model"
This is the name of the table used
name="view_type"
This is the type of the view
name="view_mode"
This is the type of available views. Here we will allow form view and tree view. There is another type of view, the Kaban view.
name="view_id"
This is the ID of the view that this action will apply. Here, the tree view.
name="context"
The context of the view that apply to the tree view. Here, a default filter is applied, the filter_see_all filter we created earlier. So we will see all cashiers.
name="help" type="html"
This field will be used to display the content in HTML format if the table is empty.
Using oe_view_nocontent_create special class, a text will be displayed with an arrow to the Create button.
action_pos_cashier
Sélectionnez
<
record
model
=
"
ir.actions.act_window
"
id
=
"
action_pos_cashier
"
>
<
field
name
=
"
name
"
>
Cashiers<
/
field
>
<
field
name
=
"
type
"
>
ir.actions.act_window<
/
field
>
<
field
name
=
"
res_model
"
>
pos.cashier<
/
field
>
<
field
name
=
"
view_type
"
>
form<
/
field
>
<
field
name
=
"
view_mode
"
>
tree,form<
/
field
>
<
field
name
=
"
view_id
"
ref
=
"
pos_cashier_tree
"
/
>
<
field
name
=
"
context
"
>
{"search_default_filter_see_all":1}<
/
field
>
<
field
name
=
"
help
"
type
=
"
html
"
>
<
p
class
=
"
oe_view_nocontent_create
"
>
Click here to create a cashier for the Point Of Sale.
<
/
p
>
<
/
field
>
<
/
record
>
name="type"
This is the type of the action
name="res_model"
This is the name of the table used
name="view_type"
This is the type of the view
name="view_mode"
This is the type of available views. Here we will allow form view and tree view. There is another type of view, the Kaban view.
name="view_id"
This is the ID of the view that this action will apply. Here, the tree view.
name="context"
The context of the view that apply to the tree view. Here, a default filter is applied, the filter_see_all filter we created earlier. So we will see all cashiers.
name="help" type="html"
This field will be used to display the content in HTML format if the table is empty.
Using oe_view_nocontent_create special class, a text will be displayed with an arrow to the Create button.
In the Point Of Sale, when we will click on the « Cashiers » menu, the tee view will appear with the « All » filter that will display all cashiers. A « Create » button will be displayed on top of the table.
VII-B-5. The order_cashier_view.xml file▲
We will now create the view for orders to the cashier's name appears.
As in the previous file, we will create a « form » view and a « tree » view.
Warning !
If you remember, the inherit_pos_order_for_cashiers object inherits from the pos_order object, the original object of the Point Of Sale.
We must therefore take the header of the original view, change the name field and add the inherit_id field.
You will notice that we put the identifier of the original view, and do not forget to precede with the name of the original module (dot syntax), since this view does not belong to our module, but to its parent.
We will insert the cashier_name field in the form.
For this, we use a field that already exists and add the position attribute.
Availble positions
In the tree view, we will replace the user_id field (User of the Point Of Sale) by cashier_name field.
order_cashier_view.xml
Sélectionnez
<?
xml
version="1.0"
encoding="utf-8"?
>
<
openerp
>
<
data
>
<!--
Vue
formulaire
-->
<
record
model
=
"
ir.ui.view
"
id
=
"
view_pos_cashier_form
"
>
<
field
name
=
"
model
"
>
pos.order<
/
field
>
<
field
name
=
"
name
"
>
view.inherit.pos.order.form<
/
field
>
<
field
name
=
"
view_type
"
>
form<
/
field
>
<
field
name
=
"
inherit_id
"
ref
=
"
point_of_sale.view_pos_pos_form
"
/
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
field
name
=
"
partner_id
"
position
=
"
after
"
>
<
field
name
=
"
cashier_name
"
/
>
<
/
field
>
<
/
field
>
<
/
record
>
<!--
Vue
Tree
-->
<
record
model
=
"
ir.ui.view
"
id
=
"
view_pos_cashier_tree
"
>
<
field
name
=
"
model
"
>
pos.order<
/
field
>
<
field
name
=
"
name
"
>
view.inherit.pos.order.tree<
/
field
>
<
field
name
=
"
view_type
"
>
tree<
/
field
>
<
field
name
=
"
inherit_id
"
ref
=
"
point_of_sale.view_pos_order_tree
"
/
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
field
name
=
"
user_id
"
position
=
"
replace
"
>
<
field
name
=
"
cashier_name
"
/
>
<
/
field
>
<
/
field
>
<
/
record
>
<
/
data
>
<
/
openerp
>
Warning !
If you remember, the inherit_pos_order_for_cashiers object inherits from the pos_order object, the original object of the Point Of Sale.
We must therefore take the header of the original view, change the name field and add the inherit_id field.
inherit_id
Sélectionnez
<
field
name
=
"
inherit_id
"
ref
=
"
point_of_sale.view_pos_order_tree
"
/
>
VII-B-5-a. The form view▲
Vue formulaire
Sélectionnez
<
record
model
=
"
ir.ui.view
"
id
=
"
view_pos_cashier_form
"
>
<
field
name
=
"
model
"
>
pos.order<
/
field
>
<
field
name
=
"
name
"
>
view.inherit.pos.order.form<
/
field
>
<
field
name
=
"
view_type
"
>
form<
/
field
>
<
field
name
=
"
inherit_id
"
ref
=
"
point_of_sale.view_pos_pos_form
"
/
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
field
name
=
"
partner_id
"
position
=
"
after
"
>
<
field
name
=
"
cashier_name
"
/
>
<
/
field
>
<
/
field
>
<
/
record
>
For this, we use a field that already exists and add the position attribute.
Availble positions
- after : the field will be inserted after the one that contains the position attribute ;
- before : the field will be inserted before the one that contains the position attribute;
- replace : replace the field that contains the position attribute ;
VII-B-5-b. The Tree View▲
Tree view
Sélectionnez
<
record
model
=
"
ir.ui.view
"
id
=
"
view_pos_cashier_tree
"
>
<
field
name
=
"
model
"
>
pos.order<
/
field
>
<
field
name
=
"
name
"
>
view.inherit.pos.order.tree<
/
field
>
<
field
name
=
"
view_type
"
>
tree<
/
field
>
<
field
name
=
"
inherit_id
"
ref
=
"
point_of_sale.view_pos_order_tree
"
/
>
<
field
name
=
"
arch
"
type
=
"
xml
"
>
<
field
name
=
"
user_id
"
position
=
"
replace
"
>
<
field
name
=
"
cashier_name
"
/
>
<
/
field
>
<
/
field
>
<
/
record
>
Remember that we create this module for several cashiers can place orders without having to log in each time the cashier changes. This is why we do not want the user name of the Point Of Sale, which will be the same for all cashiers, appears in the orders view. However, reveal the name of the cashier will allow immediately the manager to see wich cashier has made such sale.
VII-C. Security Settings of the module▲
As we have defined the menu access to a particular group, we will now apply some security policies on the module to restrict access and set a rule on the records.
VII-C-1. Access Rights▲
We will create a special file in the security directory of the module.
It has always the same name : ir.model.access.csv.
The first line contains the names of the fields separated by a comma.
id
A unique identifier for the access rule
name
The name of the rule. It will appear in the configuration pages of OpenERP.
model_id:id
The table to which this rule applies. The table name should always be prefixed with model_.
group_id:id
The user group to which this rule applies.
perm_read
Permission to read the data (1 or 0).
perm_write
Permission to modify the data (1 or 0).
perm_create
Permission to create data (1 or 0).
perm_unlink
Permission to delete the data (1 or 0).
We will now add two additional lines.
A line for the rights for the users of the POS (POS/User group) with read rights only.
And a line for the rights of the managers of the POS (POS/Manager group) with all rights.
About POS/User group
The file that defines the access rights to the database records is a CSV file./opt/modules-openerp/pos_cashier/security
It has always the same name : ir.model.access.csv.
The first line contains the names of the fields separated by a comma.
CSV fields
Sélectionnez
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
A unique identifier for the access rule
name
The name of the rule. It will appear in the configuration pages of OpenERP.
model_id:id
The table to which this rule applies. The table name should always be prefixed with model_.
group_id:id
The user group to which this rule applies.
perm_read
Permission to read the data (1 or 0).
perm_write
Permission to modify the data (1 or 0).
perm_create
Permission to create data (1 or 0).
perm_unlink
Permission to delete the data (1 or 0).
We will now add two additional lines.
A line for the rights for the users of the POS (POS/User group) with read rights only.
And a line for the rights of the managers of the POS (POS/Manager group) with all rights.
CSV fields
Sélectionnez
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_pos_cashier_u,pos.cashier user,model_pos_cashier,point_of_sale.group_pos_user,1,0,0,0
access_pos_cashier_m,pos.cashier manager,model_pos_cashier,point_of_sale.group_pos_manager,1,1,1,1
- id = access_pos_cashier_u
- name = pos.cashier user
- model_id:id = model_pos_cashier
- group_id:id = point_of_sale.group_pos_user
- perm_read = 1
- perm_write = 0
- perm_create = 0
- perm_unlink = 0
- id = access_pos_cashier_m
- name = pos.cashier manager
- model_id:id = model_pos_cashier
- group_id:id = point_of_sale.group_pos_manager
- perm_read = 1
- perm_write = 1
- perm_create = 1
- perm_unlink = 1
VII-C-2. Record rules▲
Now let's define a record rule to ensure that the manager of a Point Of Sale can create a cashier for its Point Of Sale only.
When creating a cashier, if the manager selects a Point Of Sale in the list which does not belong to him, an error message will be displayed.
Here, as it comes to security rules, the model will be always ir.rule (the rule will be recorded in the ir_rule table of OpenERP).
As in previous XML file we will put an identifier, then we will add some fields.
name="model_id"
This is the name of the affected table (prefixed with model_).
name="global" eval="True"
If this rule is global, it applies to everyone. If it is not global, it must then specify the user groups to which it applies.
name="domain_force"
This is the rule itself,
Here, a recording can be done only if the selected Point Of Sale belongs to the user.
Here is how this rule will appear in the Configuration/Security/Records rules menu of OpenERP
If you click on this rule, it appears in the form below.
When creating a cashier, if the manager selects a Point Of Sale in the list which does not belong to him, an error message will be displayed.
The administrator of OpenERP (Admin user) can create cashiers in any Point Of Sale.
The administrator has full rights to the database. It can therefore configure any application. Security rules do not apply to him.
To do this, still in the security directory of the module, we will create an XML file that we'll call pos_cashier_security.xml.The administrator has full rights to the database. It can therefore configure any application. Security rules do not apply to him.
pos_cashier_security.xml
Sélectionnez
<?
xml
version="1.0"
encoding="utf-8"?
>
<
openerp
>
<
data
noupdate
=
"
0
"
>
<
record
id
=
"
rule_pos_cashier
"
model
=
"
ir.rule
"
>
<
field
name
=
"
name
"
>
Point Of Sale Cashiers<
/
field
>
<
field
name
=
"
model_id
"
ref
=
"
model_pos_cashier
"
/
>
<
field
name
=
"
global
"
eval
=
"
True
"
/
>
<
field
name
=
"
domain_force
"
>
[('pos_config_id', '=', user.pos_config.id)]<
/
field
>
<
/
record
>
<
/
data
>
<
/
openerp
>
As in previous XML file we will put an identifier, then we will add some fields.
name="model_id"
This is the name of the affected table (prefixed with model_).
name="global" eval="True"
If this rule is global, it applies to everyone. If it is not global, it must then specify the user groups to which it applies.
name="domain_force"
This is the rule itself,
domain_force
Sélectionnez
[('pos_config_id', '=', user.pos_config.id)]
Here is how this rule will appear in the Configuration/Security/Records rules menu of OpenERP
If you click on this rule, it appears in the form below.
VII-D. Add an icon to the module▲
For your module displays an icon in the Configuration/Modules menu of OpenERP, we'll just create a PNG image of 64 pixels by 64 pixels we call icon.png.
This icon is put in the img subdirectory of the static directory of the module.
This icon is put in the img subdirectory of the static directory of the module.
At the loading of OpenERP, and at the loading of the modules, the application looks for icon.png file into this directory to display next to the name of the module./opt/modules-openerp/pos_cashier/static/src/img
VII-E. End of the basic OpenERP module▲
We finished the «basic» module for OpenERP
All files that we have created are added to the data[] array in the __ openerp__.py file
See here.
We still can not install our module, because as we stated other files in the __ openerp__.py file, if they are not created, OpenERP will return an error.
We'll have to wait a little…
All files that we have created are added to the data[] array in the __ openerp__.py file
See here.
We still can not install our module, because as we stated other files in the __ openerp__.py file, if they are not created, OpenERP will return an error.
We'll have to wait a little…
VIII. Creation of the Web module for the Point Of Sale▲
Now that we have created the module to create cashiers in the Point Of Sale, we will create the necessary files that will allow to use the cashiers in the Point Of Sale.
I remind you that the Point Of Sale is not, strictly speaking, integrated into OpenERP. This is a completely different web interface.
When we are finished, you will see the cashiers list at the bottom left of the Point Of Sale, under the keypad, as shown in the image below.
I remind you that the Point Of Sale is not, strictly speaking, integrated into OpenERP. This is a completely different web interface.
When we are finished, you will see the cashiers list at the bottom left of the Point Of Sale, under the keypad, as shown in the image below.
VIII-A. The pos_cashier.js file▲
Well, here is a little hot. I put you though all the script then I will explain to you the different functions.
This file is created in the js directory of the module.
There are two main functions in this file.
openerp_pos_cashier() function
It will allow us to add the necessary functions to the module and it will also allow us to modify some original features.
openerp.point_of_sale() function
We will change that for our module is taken into account in the Point Of Sale.
This file is created in the js directory of the module.
/opt/modules-openerp/pos_cashier/static/src/js
pos_cashier.js
Sélectionnez
function
openerp_pos_cashier
(instance,
module){
//
module
is
instance.point_of_sale
var
module =
instance.
point_of_sale;
var
QWeb =
instance.
web.
qweb;
_t =
instance.
web.
_t;
globalCashier =
null
;
module.
CashierWidget =
module.
PosWidget.
include
({
template
: '
PosWidget
'
,
init
: function
(parent,
options) {
this
.
_super
(parent);
var
self =
this
;
}
,
//
recuperation
de
l'ID
du
POS
get_cur_pos_config_id
: function
(){
var
self =
this
;
var
config =
self.
pos.
get
('
pos_config
'
);
var
config_id =
null
;
if
(config){
config_id =
config.
id;
return
config_id;
}
return
'
'
;
}
,
fetch
: function
(model,
fields,
domain,
ctx){
return
new
instance.
web.
Model
(model).
query
(fields).
filter
(domain).
context
(ctx).
all
()
}
,
cashier_change
: function
(name){
globalCashier =
name;
$('
#pay-screen-cashier-name
'
).
html
(name);
console.
log
('
cashier_change
:
'
+
name);
if
(name !
=
'
'
){
$('
.gotopay-button
'
).
removeAttr
('
disabled
'
);
}
else
{
$('
.gotopay-button
'
).
attr
('
disabled
'
,
'
disabled
'
);
}
}
,
get_cashiers
: function
(config_id){
var
self =
this
;
var
cashier_list =
[
]
;
var
loaded =
self.
fetch
('
pos.cashier
'
,
[
'
cashier_name
'
]
,
[
[
'
pos_config_id
'
,
'
=
'
,
config_id]
,
[
'
active
'
,
'
=
'
,
'
true
'
]
]
)
.
then
(function
(cashiers){
for
(var
i =
0
,
len =
cashiers.
length;
i <
len;
i+
+
){
cashier_list.
push
(cashiers[
i]
.
cashier_name);
}
if
(cashier_list.
length >
0
){
for
(var
i =
0
,
len =
cashier_list.
length;
i <
len;
i+
+
){
var
content =
self.
$('
#cashier-select
'
).
html
();
var
new_option =
'
<option
value="
'
+
cashier_list[
i]
+
'
">
'
+
cashier_list[
i]
+
'
</option>\n
'
;
self.
$('
#cashier-select
'
).
html
(content +
new_option);
}
self.
$('
#AlertNoCashier
'
).
css
('
display
'
,
'
none
'
);
self.
$('
#cashier-select
'
).
selectedIndex =
0
;
globalCashier =
cashier_list[
0
]
;
self.
cashier_change
(globalCashier);
}
else
{
//
if
there
are
no
cashier
self.
$('
#AlertNoCashier
'
).
css
('
display
'
,
'
block
'
);
self.
$('
.gotopay-button
'
).
attr
('
disabled
'
,
'
disabled
'
);
}
}
);
}
,
renderElement
: function
() {
var
self =
this
;
this
.
_super
();
self.
$('
#cashier-select
'
).
change
(function
(){
var
name =
this
.
value;
self.
cashier_change
(name);
}
);
}
,
build_widgets
: function
() {
var
self =
this
;
//
--------
Screens
---------
this
.
product_screen =
new
module.
ProductScreenWidget
(this
,
{
}
);
this
.
product_screen.
appendTo
($('
#rightpane
'
));
this
.
receipt_screen =
new
module.
ReceiptScreenWidget
(this
,
{
}
);
this
.
receipt_screen.
appendTo
($('
#rightpane
'
));
this
.
payment_screen =
new
module.
PaymentScreenWidget
(this
,
{
}
);
this
.
payment_screen.
appendTo
($('
#rightpane
'
));
this
.
welcome_screen =
new
module.
WelcomeScreenWidget
(this
,
{
}
);
this
.
welcome_screen.
appendTo
($('
#rightpane
'
));
this
.
client_payment_screen =
new
module.
ClientPaymentScreenWidget
(this
,
{
}
);
this
.
client_payment_screen.
appendTo
($('
#rightpane
'
));
this
.
scale_invite_screen =
new
module.
ScaleInviteScreenWidget
(this
,
{
}
);
this
.
scale_invite_screen.
appendTo
($('
#rightpane
'
));
this
.
scale_screen =
new
module.
ScaleScreenWidget
(this
,
{
}
);
this
.
scale_screen.
appendTo
($('
#rightpane
'
));
//
--------
Popups
---------
this
.
help_popup =
new
module.
HelpPopupWidget
(this
,
{
}
);
this
.
help_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_popup =
new
module.
ErrorPopupWidget
(this
,
{
}
);
this
.
error_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_product_popup =
new
module.
ProductErrorPopupWidget
(this
,
{
}
);
this
.
error_product_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_session_popup =
new
module.
ErrorSessionPopupWidget
(this
,
{
}
);
this
.
error_session_popup.
appendTo
($('
.point-of-sale
'
));
this
.
choose_receipt_popup =
new
module.
ChooseReceiptPopupWidget
(this
,
{
}
);
this
.
choose_receipt_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_negative_price_popup =
new
module.
ErrorNegativePricePopupWidget
(this
,
{
}
);
this
.
error_negative_price_popup.
appendTo
($('
.point-of-sale
'
));
//
--------
Misc
---------
this
.
notification =
new
module.
SynchNotificationWidget
(this
,
{
}
);
this
.
notification.
appendTo
(this
.
$('
#rightheader
'
));
this
.
username =
new
module.
UsernameWidget
(this
,
{
}
);
this
.
username.
replace
(this
.
$('
.placeholder-UsernameWidget
'
));
this
.
action_bar =
new
module.
ActionBarWidget
(this
);
this
.
action_bar.
appendTo
($("
.point-of-sale
#rightpane
"
));
this
.
left_action_bar =
new
module.
ActionBarWidget
(this
);
this
.
left_action_bar.
appendTo
($("
.point-of-sale
#leftpane
"
));
this
.
gotopay =
new
module.
GoToPayWidget
(this
,
{
}
);
this
.
gotopay.
replace
($('
#placeholder-GoToPayWidget
'
));
this
.
paypad =
new
module.
PaypadWidget
(this
,
{
}
);
this
.
paypad.
replace
($('
#placeholder-PaypadWidget
'
));
this
.
numpad =
new
module.
NumpadWidget
(this
);
this
.
numpad.
replace
($('
#placeholder-NumpadWidget
'
));
this
.
order_widget =
new
module.
OrderWidget
(this
,
{
}
);
this
.
order_widget.
replace
($('
#placeholder-OrderWidget
'
));
this
.
onscreen_keyboard =
new
module.
OnscreenKeyboardWidget
(this
,
{
'
keyboard_model
'
:
'
simple
'
}
);
this
.
onscreen_keyboard.
appendTo
($("
.point-of-sale
#content
"
));
this
.
close_button =
new
module.
HeaderButtonWidget
(this
,
{
label
: _t
('
Close
'
),
action
: function
(){
self.
try_close
();
}
,
}
);
this
.
close_button.
appendTo
(this
.
$('
#rightheader
'
));
this
.
client_button =
new
module.
HeaderButtonWidget
(this
,
{
label
: _t
('
Self-Checkout
'
),
action
: function
(){
self.
screen_selector.
set_user_mode
('
client
'
);
}
,
}
);
this
.
client_button.
appendTo
(this
.
$('
#rightheader
'
));
//
--------
Screen
Selector
---------
this
.
screen_selector =
new
module.
ScreenSelector
({
pos
: this
.
pos,
screen_set
:{
'
products
'
:
this
.
product_screen,
'
payment
'
:
this
.
payment_screen,
'
client_payment
'
:
this
.
client_payment_screen,
'
scale_invite
'
:
this
.
scale_invite_screen,
'
scale
'
:
this
.
scale_screen,
'
receipt
'
:
this
.
receipt_screen,
'
welcome
'
:
this
.
welcome_screen,
}
,
popup_set
:{
'
help
'
:
this
.
help_popup,
'
error
'
:
this
.
error_popup,
'
error-product
'
:
this
.
error_product_popup,
'
error-session
'
:
this
.
error_session_popup,
'
error-negative-price
'
:
this
.
error_negative_price_popup,
'
choose-receipt
'
:
this
.
choose_receipt_popup,
}
,
default_client_screen
: '
welcome
'
,
default_cashier_screen
: '
products
'
,
default_mode
: this
.
pos.
iface_self_checkout ?
'
client
'
:
'
cashier
'
,
}
);
if
(this
.
pos.
debug){
this
.
debug_widget =
new
module.
DebugWidget
(this
);
this
.
debug_widget.
appendTo
(this
.
$('
#content
'
));
}
}
,
}
);
module.
CashierPayScreenWidget =
module.
PaymentScreenWidget.
include
({
template
: '
PaymentScreenWidget
'
,
show
: function
(){
this
.
_super
();
var
self =
this
;
this
.
$('
#pay-screen-cashier-name
'
).
html
(globalCashier);
this
.
$('
#ticket-screen-cashier-name
'
).
html
(globalCashier);
this
.
pos.
get
('
selectedOrder
'
).
set_cashier_name
(globalCashier);
this
.
paypad =
new
module.
PaypadWidget
(this
,
{
}
);
this
.
paypad.
replace
($('
#placeholder-PaypadWidget
'
));
}
,
}
);
module.
CashierReceiptScreenWidget =
module.
ReceiptScreenWidget.
include
({
refresh
: function
() {
this
.
_super
();
$('
.pos-receipt-container
'
,
this
.
$el).
html
(QWeb.
render
('
PosTicket
'
,
{
widget:
this
}
));
if
(globalCashier !
=
'
'
){
this
.
$('
#ticket-screen-cashier-name
'
).
html
(globalCashier);
}
}
,
}
);
module.
GoToPayWidget =
module.
PosBaseWidget.
extend
({
template
: '
GoToPayWidget
'
,
init
: function
(parent,
options) {
this
.
_super
(parent);
}
,
renderElement
: function
() {
var
self =
this
;
this
.
_super
();
var
button =
new
module.
GoToPayButtonWidget
(self);
button.
appendTo
(self.
$el);
}
,
}
);
module.
GoToPayButtonWidget =
module.
PosBaseWidget.
extend
({
template
: '
GoToPayButtonWidget
'
,
init
: function
(parent,
options) {
this
.
_super
(parent);
}
,
renderElement
: function
() {
var
self =
this
;
this
.
_super
();
this
.
$el.
click
(function
(){
self.
pos_widget.
screen_selector.
set_current_screen
('
payment
'
);
}
);
}
,
}
);
module.
Order =
Backbone.
Model.
extend
({
initialize
: function
(attributes){
Backbone.
Model.
prototype.
initialize.
apply
(this
,
arguments);
this
.
set
({
creationDate
: new
Date
(),
orderLines
: new
module.
OrderlineCollection
(),
paymentLines
: new
module.
PaymentlineCollection
(),
name
: "
Order
"
+
this
.
generateUniqueId
(),
client
: null
,
cashier_name
: null
,
}
);
this
.
pos =
attributes.
pos;
this
.
selected_orderline =
undefined;
this
.
screen_data =
{
}
;
//
see
ScreenSelector
this
.
receipt_type =
'
receipt
'
;
//
'receipt'
||
'invoice'
return
this
;
}
,
generateUniqueId
: function
() {
return
new
Date
().
getTime
();
}
,
addProduct
: function
(product,
options){
options =
options |
|
{
}
;
var
attr =
product.
toJSON
();
attr.
pos =
this
.
pos;
attr.
order =
this
;
var
line =
new
module.
Orderline
({
}
,
{
pos:
this
.
pos,
order:
this
,
product:
product}
);
if
(options.
quantity !
=
=
undefined){
line.
set_quantity
(options.
quantity);
}
if
(options.
price !
=
=
undefined){
line.
set_unit_price
(options.
price);
}
var
last_orderline =
this
.
getLastOrderline
();
if
( last_orderline &
&
last_orderline.
can_be_merged_with
(line) &
&
options.
merge !
=
=
false
){
last_orderline.
merge
(line);
}
else
{
this
.
get
('
orderLines
'
).
add
(line);
}
this
.
selectLine
(this
.
getLastOrderline
());
}
,
removeOrderline
: function
( line ){
this
.
get
('
orderLines
'
).
remove
(line);
this
.
selectLine
(this
.
getLastOrderline
());
}
,
getLastOrderline
: function
(){
return
this
.
get
('
orderLines
'
).
at
(this
.
get
('
orderLines
'
).
length -
1
);
}
,
addPaymentLine
: function
(cashRegister) {
var
paymentLines =
this
.
get
('
paymentLines
'
);
var
newPaymentline =
new
module.
Paymentline
({
}
,
{
cashRegister:
cashRegister}
);
if
(cashRegister.
get
('
journal
'
).
type !
=
=
'
cash
'
){
newPaymentline.
set_amount
( this
.
getDueLeft
() );
}
paymentLines.
add
(newPaymentline);
}
,
getName
: function
() {
return
this
.
get
('
name
'
);
}
,
getSubtotal :
function
(){
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine){
return
sum +
orderLine.
get_display_price
();
}
),
0
);
}
,
getTotalTaxIncluded
: function
() {
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine) {
return
sum +
orderLine.
get_price_with_tax
();
}
),
0
);
}
,
getDiscountTotal
: function
() {
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine) {
return
sum +
(orderLine.
get_unit_price
() *
(orderLine.
get_discount
()/
100
) *
orderLine.
get_quantity
());
}
),
0
);
}
,
getTotalTaxExcluded
: function
() {
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine) {
return
sum +
orderLine.
get_price_without_tax
();
}
),
0
);
}
,
getTax
: function
() {
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine) {
return
sum +
orderLine.
get_tax
();
}
),
0
);
}
,
getPaidTotal
: function
() {
return
(this
.
get
('
paymentLines
'
)).
reduce
((function
(sum,
paymentLine) {
return
sum +
paymentLine.
get_amount
();
}
),
0
);
}
,
getChange
: function
() {
return
this
.
getPaidTotal
() -
this
.
getTotalTaxIncluded
();
}
,
getDueLeft
: function
() {
return
this
.
getTotalTaxIncluded
() -
this
.
getPaidTotal
();
}
,
set_cashier_name
: function
(name){
this
.
set
('
cashier_name
'
,
name);
}
,
//
sets
the
type
of
receipt
'receipt'(default)
or
'invoice'
set_receipt_type
: function
(type){
this
.
receipt_type =
type;
}
,
get_receipt_type
: function
(){
return
this
.
receipt_type;
}
,
//
the
client
related
to
the
current
order.
set_client
: function
(client){
this
.
set
('
client
'
,
client);
}
,
get_client
: function
(){
return
this
.
get
('
client
'
);
}
,
get_client_name
: function
(){
var
client =
this
.
get
('
client
'
);
return
client ?
client.
name :
"
"
;
}
,
//
the
order
also
stores
the
screen
status,
as
the
PoS
supports
//
different
active
screens
per
order.
This
method
is
used
to
//
store
the
screen
status.
set_screen_data
: function
(key,
value){
if
(arguments.
length =
=
=
2
){
this
.
screen_data[
key]
=
value;
}
else
if
(arguments.
length =
=
=
1
){
for
(key in
arguments[
0
]
){
this
.
screen_data[
key]
=
arguments[
0
]
[
key]
;
}
}
}
,
//
see
set_screen_data
get_screen_data
: function
(key){
return
this
.
screen_data[
key]
;
}
,
//
exports
a
JSON
for
receipt
printing
export_for_printing
: function
(){
var
orderlines =
[
]
;
this
.
get
('
orderLines
'
).
each
(function
(orderline){
orderlines.
push
(orderline.
export_for_printing
());
}
);
var
paymentlines =
[
]
;
this
.
get
('
paymentLines
'
).
each
(function
(paymentline){
paymentlines.
push
(paymentline.
export_for_printing
());
}
);
var
client =
this
.
get
('
client
'
);
var
cashier =
this
.
pos.
get
('
cashier
'
) |
|
this
.
pos.
get
('
user
'
);
var
company =
this
.
pos.
get
('
company
'
);
var
shop =
this
.
pos.
get
('
shop
'
);
var
date =
new
Date
();
return
{
orderlines
: orderlines,
paymentlines
: paymentlines,
subtotal
: this
.
getSubtotal
(),
total_with_tax
: this
.
getTotalTaxIncluded
(),
total_without_tax
: this
.
getTotalTaxExcluded
(),
total_tax
: this
.
getTax
(),
total_paid
: this
.
getPaidTotal
(),
total_discount
: this
.
getDiscountTotal
(),
change
: this
.
getChange
(),
name :
this
.
getName
(),
client
: client ?
client.
name :
null
,
invoice_id
: null
,
//
TODO
cashier
: cashier ?
cashier.
name :
null
,
date
: {
year
: date.
getFullYear
(),
month
: date.
getMonth
(),
date
: date.
getDate
(),
//
day
of
the
month
day
: date.
getDay
(),
//
day
of
the
week
hour
: date.
getHours
(),
minute
: date.
getMinutes
()
}
,
company
:{
email
: company.
email,
website
: company.
website,
company_registry
: company.
company_registry,
contact_address
: company.
contact_address,
vat
: company.
vat,
name
: company.
name,
phone
: company.
phone,
}
,
shop
:{
name
: shop.
name,
}
,
currency
: this
.
pos.
get
('
currency
'
),
}
;
}
,
exportAsJSON
: function
() {
var
orderLines,
paymentLines;
orderLines =
[
]
;
(this
.
get
('
orderLines
'
)).
each
(_.
bind
( function
(item) {
return
orderLines.
push
([
0
,
0
,
item.
export_as_JSON
()]
);
}
,
this
));
paymentLines =
[
]
;
(this
.
get
('
paymentLines
'
)).
each
(_.
bind
( function
(item) {
return
paymentLines.
push
([
0
,
0
,
item.
export_as_JSON
()]
);
}
,
this
));
return
{
name
: this
.
getName
(),
amount_paid
: this
.
getPaidTotal
(),
amount_total
: this
.
getTotalTaxIncluded
(),
amount_tax
: this
.
getTax
(),
amount_return
: this
.
getChange
(),
lines
: orderLines,
statement_ids
: paymentLines,
pos_session_id
: this
.
pos.
get
('
pos_session
'
).
id,
partner_id
: this
.
pos.
get
('
client
'
) ?
this
.
pos.
get
('
client
'
).
id :
undefined,
user_id
: this
.
pos.
get
('
cashier
'
) ?
this
.
pos.
get
('
cashier
'
).
id :
this
.
pos.
get
('
user
'
).
id,
cashier_name
: this
.
pos.
get
('
selectedOrder
'
).
get
('
cashier_name
'
),
}
;
}
,
getSelectedLine
: function
(){
return
this
.
selected_orderline;
}
,
selectLine
: function
(line){
if
(line){
if
(line !
=
=
this
.
selected_orderline){
if
(this
.
selected_orderline){
this
.
selected_orderline.
set_selected
(false
);
}
this
.
selected_orderline =
line;
this
.
selected_orderline.
set_selected
(true
);
}
}
else
{
this
.
selected_orderline =
undefined;
}
}
,
}
);
}
;
openerp.
point_of_sale =
function
(instance) {
instance.
point_of_sale =
{
}
;
var
module =
instance.
point_of_sale;
openerp_pos_db
(instance,
module);
//
import
db.js
openerp_pos_models
(instance,
module);
//
import
pos_models.js
openerp_pos_basewidget
(instance,
module);
//
import
pos_basewidget.js
openerp_pos_keyboard
(instance,
module);
//
import
pos_keyboard_widget.js
openerp_pos_scrollbar
(instance,
module);
//
import
pos_scrollbar_widget.js
openerp_pos_screens
(instance,
module);
//
import
pos_screens.js
openerp_pos_widgets
(instance,
module);
//
import
pos_widgets.js
openerp_pos_devices
(instance,
module);
//
import
pos_devices.js
//
cashiers
openerp_pos_cashier
(instance,
module);
//
import
openerp_pos_cashier
instance.
web.
client_actions.
add
('
pos.ui
'
,
'
instance.point_of_sale.PosWidget
'
);
}
;
openerp_pos_cashier() function
It will allow us to add the necessary functions to the module and it will also allow us to modify some original features.
openerp.point_of_sale() function
We will change that for our module is taken into account in the Point Of Sale.
VIII-A-1. openerp_pos_cashier() function▲
This function is declared as follows:
This function will be called later in the function that creates the Point Of Sale.
The module will be an instance of the Point Of Sale.
QWeb is the template rendering engine. I invite you to read the documentation on the OpenERP website: Documentation QWebDocummentation QWeb
_t is an instance of _t (?!??). To be honest, I do not know what it is. When I created the module for the first time, this instance does not exist in OpenERP version I had. It was been added later. It is used for the action of the close button is at the top of the Point Of Sale. As you will see later, we import the close button of Point Of Sale, this instance is required.
We then declare a global variable. We will use it further to store the name of the selected cashier.
openerp_pos_cashier()function declaration
Sélectionnez
function
openerp_pos_cashier
(instance,
module){
}
I'm not going to be able to explain all, because I'm not an OpenERP Guru. I proceeded by studying the scripts of OpenERP modules magnifying glass to try to deduce how to do it.
In fact, if at any time you find an error or an approximation in my explanations, or even in the code, do not hesitate to contact me to let me know.
Then we will instantiate some objects:In fact, if at any time you find an error or an approximation in my explanations, or even in the code, do not hesitate to contact me to let me know.
Sélectionnez
var
module =
instance.
point_of_sale;
var
QWeb =
instance.
web.
qweb;
_t =
instance.
web.
_t;
QWeb is the template rendering engine. I invite you to read the documentation on the OpenERP website: Documentation QWebDocummentation QWeb
_t is an instance of _t (?!??). To be honest, I do not know what it is. When I created the module for the first time, this instance does not exist in OpenERP version I had. It was been added later. It is used for the action of the close button is at the top of the Point Of Sale. As you will see later, we import the close button of Point Of Sale, this instance is required.
We then declare a global variable. We will use it further to store the name of the selected cashier.
Global variable
Sélectionnez
globalCashier =
null
;
For a variable to be global, it is stated without the var keyword.
A local variable is declared like this: var myvar = 'something';
A local variable is declared like this: var myvar = 'something';
VIII-A-2. The CashierWidget module▲
Now we will override the original PosWidget module.
This is in fact the module that contains the entire Point Of Sale.
We are not really going to « overload » the module, we will include additional functions when possible, with the include() function.
This means that when the module is loaded, it is also made to the PosWidget view, which we will study further.
We will then create a function that will allow to retrieve the ID of the Point Of Sale and then get cashiers who belong to this Point Of Sale.
This function uses the pos.get() function that was defined in the original module.
When a function belongs to the original Point Of Sale Module, it is writen like this : pos.the_function(). If a record is found, it returns the ID of the POS currently used.
We will then add a function that I grabbed from another module. This function allows to make a query to a table of the database.
With the correct settings, you can get the data from the table. We will see this later.
We will then create a function that will be called from the POS interface (when onchange() event of the cashiers list, for example).
Then the cashier's name will also be sent to <div id="pay-screen-cashier-name"></div> tag which appears on the payment page.
Also, if the cashier's name is empty (so there is no cashier in the Point Of Sale), it will disable the Pay button of the Point Of Sale. No sale must be performed.
We will now add the get_cashiers() function that will retrieve the cashiers from the database and build options of the cashiers drop down list.
We first create an empty array
Then we make a query with the fetch() function that we seen earlier.
Here, we will perform a query (SELECT) on pos_cashier table, we will retrieve the field cashier_name of the cashiers who belong to the Point Of Sale which pos_config_id is equal to config_id we have passed as a parameter AND that are active!
Then we will create options of the drop down list with the function that is performed after the query.
I let you dissect the function above.
Note that if there is no cashier, we display an error message in the <div id="AlertNoCashier"></div> tag and we disable the Pay button.
You also see that once the list of the cashiers is built, it stores the name of the first cashier in the globalCashier global variable then we call cashier_change() function, passing it the name of the first cashier of the list.
Then we will add a function to be called when loading the module and call the cashier_change() function to initialize the Point Of Sale.
And finally, we'll copy/paste and modify the original build_widgets() function, which is in widgets.js file of point_of_sale module.
We add the widget that will display the Pay button we will see later to the list of widgets to build .
Here it says that the widget will be placed in the <div id="placeholder-GoToPayWidget"></ div> tag that we put in the view later.
This is in fact the module that contains the entire Point Of Sale.
We are not really going to « overload » the module, we will include additional functions when possible, with the include() function.
module.CashierWidget
Sélectionnez
module.
CashierWidget =
module.
PosWidget.
include
({
template
: '
PosWidget
'
,
//
recuperation
de
l'ID
du
POS
get_cur_pos_config_id
: function
(){
var
self =
this
;
var
config =
self.
pos.
get
('
pos_config
'
);
var
config_id =
null
;
if
(config){
config_id =
config.
id;
return
config_id;
}
return
'
'
;
}
,
fetch
: function
(model,
fields,
domain,
ctx){
return
new
instance.
web.
Model
(model).
query
(fields).
filter
(domain).
context
(ctx).
all
()
}
,
cashier_change
: function
(name){
globalCashier =
name;
$('
#pay-screen-cashier-name
'
).
html
(name);
//
console.log('cashier_change
:
'
+
name);
if
(name !
=
'
'
){
$('
.gotopay-button
'
).
removeAttr
('
disabled
'
);
}
else
{
$('
.gotopay-button
'
).
attr
('
disabled
'
,
'
disabled
'
);
}
}
,
get_cashiers
: function
(config_id){
var
self =
this
;
var
cashier_list =
[
]
;
var
loaded =
self.
fetch
('
pos.cashier
'
,
[
'
cashier_name
'
]
,
[
[
'
pos_config_id
'
,
'
=
'
,
config_id]
,
[
'
active
'
,
'
=
'
,
'
true
'
]
]
)
.
then
(function
(cashiers){
for
(var
i =
0
,
len =
cashiers.
length;
i <
len;
i+
+
){
cashier_list.
push
(cashiers[
i]
.
cashier_name);
}
if
(cashier_list.
length >
0
){
for
(var
i =
0
,
len =
cashier_list.
length;
i <
len;
i+
+
){
var
content =
self.
$('
#cashier-select
'
).
html
();
var
new_option =
'
<option
value="
'
+
cashier_list[
i]
+
'
">
'
+
cashier_list[
i]
+
'
</option>\n
'
;
self.
$('
#cashier-select
'
).
html
(content +
new_option);
}
self.
$('
#AlertNoCashier
'
).
css
('
display
'
,
'
none
'
);
self.
$('
#cashier-select
'
).
selectedIndex =
0
;
globalCashier =
cashier_list[
0
]
;
self.
cashier_change
(globalCashier);
}
else
{
//
if
there
are
no
cashier
self.
$('
#AlertNoCashier
'
).
css
('
display
'
,
'
block
'
);
self.
$('
.gotopay-button
'
).
attr
('
disabled
'
,
'
disabled
'
);
}
}
);
}
,
renderElement
: function
() {
var
self =
this
;
this
.
_super
();
self.
$('
#cashier-select
'
).
change
(function
(){
var
name =
this
.
value;
self.
cashier_change
(name);
}
);
}
,
build_widgets
: function
() {
var
self =
this
;
//
--------
Screens
---------
this
.
product_screen =
new
module.
ProductScreenWidget
(this
,
{
}
);
this
.
product_screen.
appendTo
($('
#rightpane
'
));
this
.
receipt_screen =
new
module.
ReceiptScreenWidget
(this
,
{
}
);
this
.
receipt_screen.
appendTo
($('
#rightpane
'
));
this
.
payment_screen =
new
module.
PaymentScreenWidget
(this
,
{
}
);
this
.
payment_screen.
appendTo
($('
#rightpane
'
));
this
.
welcome_screen =
new
module.
WelcomeScreenWidget
(this
,
{
}
);
this
.
welcome_screen.
appendTo
($('
#rightpane
'
));
this
.
client_payment_screen =
new
module.
ClientPaymentScreenWidget
(this
,
{
}
);
this
.
client_payment_screen.
appendTo
($('
#rightpane
'
));
this
.
scale_invite_screen =
new
module.
ScaleInviteScreenWidget
(this
,
{
}
);
this
.
scale_invite_screen.
appendTo
($('
#rightpane
'
));
this
.
scale_screen =
new
module.
ScaleScreenWidget
(this
,
{
}
);
this
.
scale_screen.
appendTo
($('
#rightpane
'
));
//
--------
Popups
---------
this
.
help_popup =
new
module.
HelpPopupWidget
(this
,
{
}
);
this
.
help_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_popup =
new
module.
ErrorPopupWidget
(this
,
{
}
);
this
.
error_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_product_popup =
new
module.
ProductErrorPopupWidget
(this
,
{
}
);
this
.
error_product_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_session_popup =
new
module.
ErrorSessionPopupWidget
(this
,
{
}
);
this
.
error_session_popup.
appendTo
($('
.point-of-sale
'
));
this
.
choose_receipt_popup =
new
module.
ChooseReceiptPopupWidget
(this
,
{
}
);
this
.
choose_receipt_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_negative_price_popup =
new
module.
ErrorNegativePricePopupWidget
(this
,
{
}
);
this
.
error_negative_price_popup.
appendTo
($('
.point-of-sale
'
));
//
--------
Misc
---------
this
.
notification =
new
module.
SynchNotificationWidget
(this
,
{
}
);
this
.
notification.
appendTo
(this
.
$('
#rightheader
'
));
this
.
username =
new
module.
UsernameWidget
(this
,
{
}
);
this
.
username.
replace
(this
.
$('
.placeholder-UsernameWidget
'
));
this
.
action_bar =
new
module.
ActionBarWidget
(this
);
this
.
action_bar.
appendTo
($("
.point-of-sale
#rightpane
"
));
this
.
left_action_bar =
new
module.
ActionBarWidget
(this
);
this
.
left_action_bar.
appendTo
($("
.point-of-sale
#leftpane
"
));
this
.
gotopay =
new
module.
GoToPayWidget
(this
,
{
}
);
this
.
gotopay.
replace
($('
#placeholder-GoToPayWidget
'
));
this
.
paypad =
new
module.
PaypadWidget
(this
,
{
}
);
this
.
paypad.
replace
($('
#placeholder-PaypadWidget
'
));
this
.
numpad =
new
module.
NumpadWidget
(this
);
this
.
numpad.
replace
($('
#placeholder-NumpadWidget
'
));
this
.
order_widget =
new
module.
OrderWidget
(this
,
{
}
);
this
.
order_widget.
replace
($('
#placeholder-OrderWidget
'
));
this
.
onscreen_keyboard =
new
module.
OnscreenKeyboardWidget
(this
,
{
'
keyboard_model
'
:
'
simple
'
}
);
this
.
onscreen_keyboard.
appendTo
($("
.point-of-sale
#content
"
));
this
.
close_button =
new
module.
HeaderButtonWidget
(this
,
{
label
: _t
('
Close
'
),
action
: function
(){
self.
try_close
();
}
,
}
);
this
.
close_button.
appendTo
(this
.
$('
#rightheader
'
));
this
.
client_button =
new
module.
HeaderButtonWidget
(this
,
{
label
: _t
('
Self-Checkout
'
),
action
: function
(){
self.
screen_selector.
set_user_mode
('
client
'
);
}
,
}
);
this
.
client_button.
appendTo
(this
.
$('
#rightheader
'
));
//
--------
Screen
Selector
---------
this
.
screen_selector =
new
module.
ScreenSelector
({
pos
: this
.
pos,
screen_set
:{
'
products
'
:
this
.
product_screen,
'
payment
'
:
this
.
payment_screen,
'
client_payment
'
:
this
.
client_payment_screen,
'
scale_invite
'
:
this
.
scale_invite_screen,
'
scale
'
:
this
.
scale_screen,
'
receipt
'
:
this
.
receipt_screen,
'
welcome
'
:
this
.
welcome_screen,
}
,
popup_set
:{
'
help
'
:
this
.
help_popup,
'
error
'
:
this
.
error_popup,
'
error-product
'
:
this
.
error_product_popup,
'
error-session
'
:
this
.
error_session_popup,
'
error-negative-price
'
:
this
.
error_negative_price_popup,
'
choose-receipt
'
:
this
.
choose_receipt_popup,
}
,
default_client_screen
: '
welcome
'
,
default_cashier_screen
: '
products
'
,
default_mode
: this
.
pos.
iface_self_checkout ?
'
client
'
:
'
cashier
'
,
}
);
if
(this
.
pos.
debug){
this
.
debug_widget =
new
module.
DebugWidget
(this
);
this
.
debug_widget.
appendTo
(this
.
$('
#content
'
));
}
}
,
}
);
You will notice that the statements or functions within the module are separated by a comma.
First, we declare the template that will be used for the module.
template
Sélectionnez
template
: '
PosWidget
'
,
We will then create a function that will allow to retrieve the ID of the Point Of Sale and then get cashiers who belong to this Point Of Sale.
get_cur_pos_config_id()
Sélectionnez
//
recuperation
de
l'ID
du
POS
get_cur_pos_config_id
: function
(){
var
self =
this
;
var
config =
self.
pos.
get
('
pos_config
'
);
var
config_id =
null
;
if
(config){
config_id =
config.
id;
return
config_id;
}
return
'
'
;
}
,
When a function belongs to the original Point Of Sale Module, it is writen like this : pos.the_function(). If a record is found, it returns the ID of the POS currently used.
We will then add a function that I grabbed from another module. This function allows to make a query to a table of the database.
fetch()
Sélectionnez
fetch
: function
(model,
fields,
domain,
ctx){
return
new
instance.
web.
Model
(model).
query
(fields).
filter
(domain).
context
(ctx).
all
()
}
,
We will then create a function that will be called from the POS interface (when onchange() event of the cashiers list, for example).
cashier_change()
Sélectionnez
cashier_change
: function
(name){
globalCashier =
name;
$('
#pay-screen-cashier-name
'
).
html
(name);
//
console.log('cashier_change
:
'
+
name);
if
(name !
=
'
'
){
$('
.gotopay-button
'
).
removeAttr
('
disabled
'
);
}
else
{
$('
.gotopay-button
'
).
attr
('
disabled
'
,
'
disabled
'
);
}
}
,
I left you for a special comment:
//console.log('cashier_change : ' + name);
If you use Firebug extension, which is very effective for JavaScript debugging, and if you uncomment this line, you will see the message in the Firebug console each time the cashier_change() function will be called .
When you build your personal unit, do not hesitate to use this trick in your JavaScripts, in particular to verify that the functions are executed and your variables do contain the values you expect.
Obviously, you do not forget to remove your console.log() commands or comment them before going into production.
Finally, the last little tip, if you use console.log() by passing an object, you will not forget to use this: console.log(JSON.stringify (myObject));
When the function is called, the name of the cashier will be stored in the globalCashier global variable.//console.log('cashier_change : ' + name);
If you use Firebug extension, which is very effective for JavaScript debugging, and if you uncomment this line, you will see the message in the Firebug console each time the cashier_change() function will be called .
When you build your personal unit, do not hesitate to use this trick in your JavaScripts, in particular to verify that the functions are executed and your variables do contain the values you expect.
Obviously, you do not forget to remove your console.log() commands or comment them before going into production.
Finally, the last little tip, if you use console.log() by passing an object, you will not forget to use this: console.log(JSON.stringify (myObject));
Then the cashier's name will also be sent to <div id="pay-screen-cashier-name"></div> tag which appears on the payment page.
Also, if the cashier's name is empty (so there is no cashier in the Point Of Sale), it will disable the Pay button of the Point Of Sale. No sale must be performed.
A little explanation !
The left side of the Point Of Sale normally displays the keypad and some payment buttons.
There can be as many buttons as you set up payment methods for the Point Of Sale.
To prohibit the sale if no cashier has been created in the Point Of Sale, I moved the payment buttons on the payment page, and instead I put a Pay button.
It is easier to disable one button that is « hard coded » in the view, rather than make a function that disables many payment buttons that are dynamically generated.
The left side of the Point Of Sale normally displays the keypad and some payment buttons.
There can be as many buttons as you set up payment methods for the Point Of Sale.
To prohibit the sale if no cashier has been created in the Point Of Sale, I moved the payment buttons on the payment page, and instead I put a Pay button.
It is easier to disable one button that is « hard coded » in the view, rather than make a function that disables many payment buttons that are dynamically generated.
The standart payment interface with several payment buttons |
The payment interface with only one Pay button + the dropdown list of the cashiers |
get_cashiers()
Sélectionnez
get_cashiers
: function
(config_id){
var
self =
this
;
var
cashier_list =
[
]
;
var
loaded =
self.
fetch
('
pos.cashier
'
,
[
'
cashier_name
'
]
,
[
[
'
pos_config_id
'
,
'
=
'
,
config_id]
,
[
'
active
'
,
'
=
'
,
'
true
'
]
]
)
.
then
(function
(cashiers){
for
(var
i =
0
,
len =
cashiers.
length;
i <
len;
i+
+
){
cashier_list.
push
(cashiers[
i]
.
cashier_name);
}
if
(cashier_list.
length >
0
){
for
(var
i =
0
,
len =
cashier_list.
length;
i <
len;
i+
+
){
var
content =
self.
$('
#cashier-select
'
).
html
();
var
new_option =
'
<option
value="
'
+
cashier_list[
i]
+
'
">
'
+
cashier_list[
i]
+
'
</option>\n
'
;
self.
$('
#cashier-select
'
).
html
(content +
new_option);
}
self.
$('
#AlertNoCashier
'
).
css
('
display
'
,
'
none
'
);
self.
$('
#cashier-select
'
).
selectedIndex =
0
;
globalCashier =
cashier_list[
0
]
;
self.
cashier_change
(globalCashier);
}
else
{
//
if
there
are
no
cashier
self.
$('
#AlertNoCashier
'
).
css
('
display
'
,
'
block
'
);
self.
$('
.gotopay-button
'
).
attr
('
disabled
'
,
'
disabled
'
);
}
}
);
}
,
Sélectionnez
var
cashier_list =
[
]
;
Sélectionnez
var
loaded =
self.
fetch
('
pos.cashier
'
,
[
'
cashier_name
'
]
,
[
[
'
pos_config_id
'
,
'
=
'
,
config_id]
,
[
'
active
'
,
'
=
'
,
'
true
'
]
]
)
Then we will create options of the drop down list with the function that is performed after the query.
Sélectionnez
.
then
(function
(cashiers){
for
(var
i =
0
,
len =
cashiers.
length;
i <
len;
i+
+
){
cashier_list.
push
(cashiers[
i]
.
cashier_name);
}
if
(cashier_list.
length >
0
){
for
(var
i =
0
,
len =
cashier_list.
length;
i <
len;
i+
+
){
var
content =
self.
$('
#cashier-select
'
).
html
();
var
new_option =
'
<option
value="
'
+
cashier_list[
i]
+
'
">
'
+
cashier_list[
i]
+
'
</option>\n
'
;
self.
$('
#cashier-select
'
).
html
(content +
new_option);
}
self.
$('
#AlertNoCashier
'
).
css
('
display
'
,
'
none
'
);
self.
$('
#cashier-select
'
).
selectedIndex =
0
;
globalCashier =
cashier_list[
0
]
;
self.
cashier_change
(globalCashier);
}
else
{
//
if
there
are
no
cashier
self.
$('
#AlertNoCashier
'
).
css
('
display
'
,
'
block
'
);
self.
$('
.gotopay-button
'
).
attr
('
disabled
'
,
'
disabled
'
);
}
}
);
Note that if there is no cashier, we display an error message in the <div id="AlertNoCashier"></div> tag and we disable the Pay button.
You also see that once the list of the cashiers is built, it stores the name of the first cashier in the globalCashier global variable then we call cashier_change() function, passing it the name of the first cashier of the list.
Then we will add a function to be called when loading the module and call the cashier_change() function to initialize the Point Of Sale.
renderElement()
Sélectionnez
renderElement
: function
() {
var
self =
this
;
this
.
_super
();
self.
$('
#cashier-select
'
).
change
(function
(){
var
name =
this
.
value;
self.
cashier_change
(name);
}
);
}
,
build_widgets()
Sélectionnez
build_widgets
: function
() {
var
self =
this
;
//
--------
Screens
---------
this
.
product_screen =
new
module.
ProductScreenWidget
(this
,
{
}
);
this
.
product_screen.
appendTo
($('
#rightpane
'
));
this
.
receipt_screen =
new
module.
ReceiptScreenWidget
(this
,
{
}
);
this
.
receipt_screen.
appendTo
($('
#rightpane
'
));
this
.
payment_screen =
new
module.
PaymentScreenWidget
(this
,
{
}
);
this
.
payment_screen.
appendTo
($('
#rightpane
'
));
this
.
welcome_screen =
new
module.
WelcomeScreenWidget
(this
,
{
}
);
this
.
welcome_screen.
appendTo
($('
#rightpane
'
));
this
.
client_payment_screen =
new
module.
ClientPaymentScreenWidget
(this
,
{
}
);
this
.
client_payment_screen.
appendTo
($('
#rightpane
'
));
this
.
scale_invite_screen =
new
module.
ScaleInviteScreenWidget
(this
,
{
}
);
this
.
scale_invite_screen.
appendTo
($('
#rightpane
'
));
this
.
scale_screen =
new
module.
ScaleScreenWidget
(this
,
{
}
);
this
.
scale_screen.
appendTo
($('
#rightpane
'
));
//
--------
Popups
---------
this
.
help_popup =
new
module.
HelpPopupWidget
(this
,
{
}
);
this
.
help_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_popup =
new
module.
ErrorPopupWidget
(this
,
{
}
);
this
.
error_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_product_popup =
new
module.
ProductErrorPopupWidget
(this
,
{
}
);
this
.
error_product_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_session_popup =
new
module.
ErrorSessionPopupWidget
(this
,
{
}
);
this
.
error_session_popup.
appendTo
($('
.point-of-sale
'
));
this
.
choose_receipt_popup =
new
module.
ChooseReceiptPopupWidget
(this
,
{
}
);
this
.
choose_receipt_popup.
appendTo
($('
.point-of-sale
'
));
this
.
error_negative_price_popup =
new
module.
ErrorNegativePricePopupWidget
(this
,
{
}
);
this
.
error_negative_price_popup.
appendTo
($('
.point-of-sale
'
));
//
--------
Misc
---------
this
.
notification =
new
module.
SynchNotificationWidget
(this
,
{
}
);
this
.
notification.
appendTo
(this
.
$('
#rightheader
'
));
this
.
username =
new
module.
UsernameWidget
(this
,
{
}
);
this
.
username.
replace
(this
.
$('
.placeholder-UsernameWidget
'
));
this
.
action_bar =
new
module.
ActionBarWidget
(this
);
this
.
action_bar.
appendTo
($("
.point-of-sale
#rightpane
"
));
this
.
left_action_bar =
new
module.
ActionBarWidget
(this
);
this
.
left_action_bar.
appendTo
($("
.point-of-sale
#leftpane
"
));
this
.
gotopay =
new
module.
GoToPayWidget
(this
,
{
}
);
//
We
add
here
the
creation
this
.
gotopay.
replace
($('
#placeholder-GoToPayWidget
'
));
//
of
the
widget
that
displays
the
Pay
button
this
.
paypad =
new
module.
PaypadWidget
(this
,
{
}
);
this
.
paypad.
replace
($('
#placeholder-PaypadWidget
'
));
this
.
numpad =
new
module.
NumpadWidget
(this
);
this
.
numpad.
replace
($('
#placeholder-NumpadWidget
'
));
this
.
order_widget =
new
module.
OrderWidget
(this
,
{
}
);
this
.
order_widget.
replace
($('
#placeholder-OrderWidget
'
));
this
.
onscreen_keyboard =
new
module.
OnscreenKeyboardWidget
(this
,
{
'
keyboard_model
'
:
'
simple
'
}
);
this
.
onscreen_keyboard.
appendTo
($("
.point-of-sale
#content
"
));
this
.
close_button =
new
module.
HeaderButtonWidget
(this
,
{
label
: _t
('
Close
'
),
action
: function
(){
self.
try_close
();
}
,
}
);
this
.
close_button.
appendTo
(this
.
$('
#rightheader
'
));
this
.
client_button =
new
module.
HeaderButtonWidget
(this
,
{
label
: _t
('
Self-Checkout
'
),
action
: function
(){
self.
screen_selector.
set_user_mode
('
client
'
);
}
,
}
);
this
.
client_button.
appendTo
(this
.
$('
#rightheader
'
));
//
--------
Screen
Selector
---------
this
.
screen_selector =
new
module.
ScreenSelector
({
pos
: this
.
pos,
screen_set
:{
'
products
'
:
this
.
product_screen,
'
payment
'
:
this
.
payment_screen,
'
client_payment
'
:
this
.
client_payment_screen,
'
scale_invite
'
:
this
.
scale_invite_screen,
'
scale
'
:
this
.
scale_screen,
'
receipt
'
:
this
.
receipt_screen,
'
welcome
'
:
this
.
welcome_screen,
}
,
popup_set
:{
'
help
'
:
this
.
help_popup,
'
error
'
:
this
.
error_popup,
'
error-product
'
:
this
.
error_product_popup,
'
error-session
'
:
this
.
error_session_popup,
'
error-negative-price
'
:
this
.
error_negative_price_popup,
'
choose-receipt
'
:
this
.
choose_receipt_popup,
}
,
default_client_screen
: '
welcome
'
,
default_cashier_screen
: '
products
'
,
default_mode
: this
.
pos.
iface_self_checkout ?
'
client
'
:
'
cashier
'
,
}
);
if
(this
.
pos.
debug){
this
.
debug_widget =
new
module.
DebugWidget
(this
);
this
.
debug_widget.
appendTo
(this
.
$('
#content
'
));
}
}
,
Sélectionnez
this
.
gotopay =
new
module.
GoToPayWidget
(this
,
{
}
);
this
.
gotopay.
replace
($('
#placeholder-GoToPayWidget
'
));
VIII-A-3. The CashierPayScreenWidget module▲
This module will allow us to add a function to the original module.PaymentScreenWidget module (which is in screen.js file of point_of_sale module), again with the include() function
The module will be attached to the PaymentScreenWidget template view .
We will add some instructions in the show() function of the module.
We will first of all get the name of the cashier who is in the globalCashier global variable that will be displayed on the payment page and on the receipt.
It will then record the name of the cashier in the current order with the following function
This function does not exist yet, we'll add it later.
Finally, we will recreate the payment buttons that I removed,. In addition, they will be destroyed at the end of each order.
PaymentScreenWidget
Sélectionnez
module.
CashierPayScreenWidget =
module.
PaymentScreenWidget.
include
({
template
: '
PaymentScreenWidget
'
,
show
: function
(){
this
.
_super
();
var
self =
this
;
this
.
$('
#pay-screen-cashier-name
'
).
html
(globalCashier);
this
.
$('
#ticket-screen-cashier-name
'
).
html
(globalCashier);
this
.
pos.
get
('
selectedOrder
'
).
set_cashier_name
(globalCashier);
this
.
paypad =
new
module.
PaypadWidget
(this
,
{
}
);
this
.
paypad.
replace
($('
#placeholder-PaypadWidget
'
));
}
,
}
);
We will add some instructions in the show() function of the module.
We will first of all get the name of the cashier who is in the globalCashier global variable that will be displayed on the payment page and on the receipt.
It will then record the name of the cashier in the current order with the following function
set_cashier_name
Sélectionnez
this
.
pos.
get
('
selectedOrder
'
).
set_cashier_name
(globalCashier);
Finally, we will recreate the payment buttons that I removed,. In addition, they will be destroyed at the end of each order.
PaypadWidget
Sélectionnez
this
.
paypad =
new
module.
PaypadWidget
(this
,
{
}
);
this
.
paypad.
replace
($('
#placeholder-PaypadWidget
'
));
The PaypadWidget module is the original one created for the Point Of Sale. This is the one that was removed before it was next to the keypad, which was replaced with the Pay button.
VIII-A-4. The CashierReceiptScreenWidget module▲
Here we will add instructions in the original function refresh() of ReceiptScreenWidget module that is in screen.js file of point_of_sale module. This is the module that will display the name of the cashier on the receipt.
In fact, it will copy the original function and add the last three lines (the if() statement).
You see that we get the name of the cashier who is in globalCashier variable to send it to <div id="ticket-screen-cashier-name"></ div> tag that is diplayed on the receipt. We'll see that later when we take care of the views (XML).
In fact, it will copy the original function and add the last three lines (the if() statement).
CashierReceiptScreenWidget
Sélectionnez
module.
CashierReceiptScreenWidget =
module.
ReceiptScreenWidget.
include
({
refresh
: function
() {
this
.
_super
();
$('
.pos-receipt-container
'
,
this
.
$el).
html
(QWeb.
render
('
PosTicket
'
,
{
widget:
this
}
));
if
(globalCashier !
=
'
'
){
this
.
$('
#ticket-screen-cashier-name
'
).
html
(globalCashier);
}
}
,
}
);
VIII-A-5. GoToPayWidget module▲
Now we will create the module that will host the Pay button.
Warning, this is not the button itself, it is simply the « container » that will host the button.
As for the other original Point Of Sale modules, these are extensions of PosBaseWidget module.
We assign the template view with the following statement:
Then adding the init() function like the original modules.
We will then add a function that will command QWeb (the rendering engine templates) to add the Pay button, we will see just after this module (in its container, somehow).
That's all for this module
Warning, this is not the button itself, it is simply the « container » that will host the button.
Sélectionnez
module.
GoToPayWidget =
module.
PosBaseWidget.
extend
({
template
: '
GoToPayWidget
'
,
init
: function
(parent,
options) {
this
.
_super
(parent);
}
,
renderElement
: function
() {
var
self =
this
;
this
.
_super
();
var
button =
new
module.
GoToPayButtonWidget
(self);
button.
appendTo
(self.
$el);
}
,
}
);
We assign the template view with the following statement:
Sélectionnez
template
: '
GoToPayWidget
'
,
We will then add a function that will command QWeb (the rendering engine templates) to add the Pay button, we will see just after this module (in its container, somehow).
renderElement
Sélectionnez
renderElement
: function
() {
var
self =
this
;
this
.
_super
();
var
button =
new
module.
GoToPayButtonWidget
(self);
button.
appendTo
(self.
$el);
}
,
VIII-A-6. GoToPayButtonWidget module (Pay button)▲
This time, we will create the module of the Pay button.
As with the previous module, it will be an extension of the basic PosBaseWidget module, we assign the GoToPayButtonWidget template view , we add the init() function that goes well, then we also add the renderElement() function.
We add click() function in renderElement().
This function is performed, you guessed it, when clicking on the button (onclick() event).
This function itself will call the function to display different views of Point Of Sale.
In this case, it will display the payment page.
As with the previous module, it will be an extension of the basic PosBaseWidget module, we assign the GoToPayButtonWidget template view , we add the init() function that goes well, then we also add the renderElement() function.
GoToPayButtonWidget
Sélectionnez
module.
GoToPayButtonWidget =
module.
PosBaseWidget.
extend
({
template
: '
GoToPayButtonWidget
'
,
init
: function
(parent,
options) {
this
.
_super
(parent);
}
,
renderElement
: function
() {
var
self =
this
;
this
.
_super
();
this
.
$el.
click
(function
(){
self.
pos_widget.
screen_selector.
set_current_screen
('
payment
'
);
}
);
}
,
}
);
This function is performed, you guessed it, when clicking on the button (onclick() event).
This function itself will call the function to display different views of Point Of Sale.
In this case, it will display the payment page.
set_current_screen()
Sélectionnez
this
.
$el.
click
(function
(){
self.
pos_widget.
screen_selector.
set_current_screen
('
payment
'
);
}
);
VIII-A-7. Order module▲
This is the module that creates the orders, which stores them in the browser (LocalStorage), and which then sends the orders to the database.
It is not possible to overload the original module, because when initializing, the module returns an object (the order) to the Point Of Sale, and we need to modify it to take into account the name of the cashier.
We'll copy/paste the entire module you will find in model.js file of point_of_sale module and we will add two or three things.
I put the code of the module and I'll explain only the functions or objects I added.
At the beginning of the module, we will add a cashier_name field to the order.
As you can see, the order contains multiple fields. We simply add the cashier_name field.
Now that the field is created, we can send the name of the cashier to the order when needed.
Then we will add a function in the list of functions that already exist.
When set_cashier_name() function is called, it will send the name of the cashier in the field we added earlier.
We added the cashier_name field in the return of the function.
This time, we get the name of the cashier who was sent to the order earlier.
The openerp_pos_cashier module() is finished!
It is not possible to overload the original module, because when initializing, the module returns an object (the order) to the Point Of Sale, and we need to modify it to take into account the name of the cashier.
We'll copy/paste the entire module you will find in model.js file of point_of_sale module and we will add two or three things.
I put the code of the module and I'll explain only the functions or objects I added.
module.Order
Sélectionnez
module.
Order =
Backbone.
Model.
extend
({
initialize
: function
(attributes){
Backbone.
Model.
prototype.
initialize.
apply
(this
,
arguments);
this
.
set
({
creationDate
: new
Date
(),
orderLines
: new
module.
OrderlineCollection
(),
paymentLines
: new
module.
PaymentlineCollection
(),
name
: "
Order
"
+
this
.
generateUniqueId
(),
client
: null
,
cashier_name
: null
,
}
);
this
.
pos =
attributes.
pos;
this
.
selected_orderline =
undefined;
this
.
screen_data =
{
}
;
//
see
ScreenSelector
this
.
receipt_type =
'
receipt
'
;
//
'receipt'
||
'invoice'
return
this
;
}
,
generateUniqueId
: function
() {
return
new
Date
().
getTime
();
}
,
addProduct
: function
(product,
options){
options =
options |
|
{
}
;
var
attr =
product.
toJSON
();
attr.
pos =
this
.
pos;
attr.
order =
this
;
var
line =
new
module.
Orderline
({
}
,
{
pos:
this
.
pos,
order:
this
,
product:
product}
);
if
(options.
quantity !
=
=
undefined){
line.
set_quantity
(options.
quantity);
}
if
(options.
price !
=
=
undefined){
line.
set_unit_price
(options.
price);
}
var
last_orderline =
this
.
getLastOrderline
();
if
( last_orderline &
&
last_orderline.
can_be_merged_with
(line) &
&
options.
merge !
=
=
false
){
last_orderline.
merge
(line);
}
else
{
this
.
get
('
orderLines
'
).
add
(line);
}
this
.
selectLine
(this
.
getLastOrderline
());
}
,
removeOrderline
: function
( line ){
this
.
get
('
orderLines
'
).
remove
(line);
this
.
selectLine
(this
.
getLastOrderline
());
}
,
getLastOrderline
: function
(){
return
this
.
get
('
orderLines
'
).
at
(this
.
get
('
orderLines
'
).
length -
1
);
}
,
addPaymentLine
: function
(cashRegister) {
var
paymentLines =
this
.
get
('
paymentLines
'
);
var
newPaymentline =
new
module.
Paymentline
({
}
,
{
cashRegister:
cashRegister}
);
if
(cashRegister.
get
('
journal
'
).
type !
=
=
'
cash
'
){
newPaymentline.
set_amount
( this
.
getDueLeft
() );
}
paymentLines.
add
(newPaymentline);
}
,
getName
: function
() {
return
this
.
get
('
name
'
);
}
,
getSubtotal :
function
(){
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine){
return
sum +
orderLine.
get_display_price
();
}
),
0
);
}
,
getTotalTaxIncluded
: function
() {
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine) {
return
sum +
orderLine.
get_price_with_tax
();
}
),
0
);
}
,
getDiscountTotal
: function
() {
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine) {
return
sum +
(orderLine.
get_unit_price
() *
(orderLine.
get_discount
()/
100
) *
orderLine.
get_quantity
());
}
),
0
);
}
,
getTotalTaxExcluded
: function
() {
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine) {
return
sum +
orderLine.
get_price_without_tax
();
}
),
0
);
}
,
getTax
: function
() {
return
(this
.
get
('
orderLines
'
)).
reduce
((function
(sum,
orderLine) {
return
sum +
orderLine.
get_tax
();
}
),
0
);
}
,
getPaidTotal
: function
() {
return
(this
.
get
('
paymentLines
'
)).
reduce
((function
(sum,
paymentLine) {
return
sum +
paymentLine.
get_amount
();
}
),
0
);
}
,
getChange
: function
() {
return
this
.
getPaidTotal
() -
this
.
getTotalTaxIncluded
();
}
,
getDueLeft
: function
() {
return
this
.
getTotalTaxIncluded
() -
this
.
getPaidTotal
();
}
,
set_cashier_name
: function
(name){
this
.
set
('
cashier_name
'
,
name);
}
,
//
sets
the
type
of
receipt
'receipt'(default)
or
'invoice'
set_receipt_type
: function
(type){
this
.
receipt_type =
type;
}
,
get_receipt_type
: function
(){
return
this
.
receipt_type;
}
,
//
the
client
related
to
the
current
order.
set_client
: function
(client){
this
.
set
('
client
'
,
client);
}
,
get_client
: function
(){
return
this
.
get
('
client
'
);
}
,
get_client_name
: function
(){
var
client =
this
.
get
('
client
'
);
return
client ?
client.
name :
"
"
;
}
,
//
the
order
also
stores
the
screen
status,
as
the
PoS
supports
//
different
active
screens
per
order.
This
method
is
used
to
//
store
the
screen
status.
set_screen_data
: function
(key,
value){
if
(arguments.
length =
=
=
2
){
this
.
screen_data[
key]
=
value;
}
else
if
(arguments.
length =
=
=
1
){
for
(key in
arguments[
0
]
){
this
.
screen_data[
key]
=
arguments[
0
]
[
key]
;
}
}
}
,
//
see
set_screen_data
get_screen_data
: function
(key){
return
this
.
screen_data[
key]
;
}
,
//
exports
a
JSON
for
receipt
printing
export_for_printing
: function
(){
var
orderlines =
[
]
;
this
.
get
('
orderLines
'
).
each
(function
(orderline){
orderlines.
push
(orderline.
export_for_printing
());
}
);
var
paymentlines =
[
]
;
this
.
get
('
paymentLines
'
).
each
(function
(paymentline){
paymentlines.
push
(paymentline.
export_for_printing
());
}
);
var
client =
this
.
get
('
client
'
);
var
cashier =
this
.
pos.
get
('
cashier
'
) |
|
this
.
pos.
get
('
user
'
);
var
company =
this
.
pos.
get
('
company
'
);
var
shop =
this
.
pos.
get
('
shop
'
);
var
date =
new
Date
();
return
{
orderlines
: orderlines,
paymentlines
: paymentlines,
subtotal
: this
.
getSubtotal
(),
total_with_tax
: this
.
getTotalTaxIncluded
(),
total_without_tax
: this
.
getTotalTaxExcluded
(),
total_tax
: this
.
getTax
(),
total_paid
: this
.
getPaidTotal
(),
total_discount
: this
.
getDiscountTotal
(),
change
: this
.
getChange
(),
name :
this
.
getName
(),
client
: client ?
client.
name :
null
,
invoice_id
: null
,
//
TODO
cashier
: cashier ?
cashier.
name :
null
,
date
: {
year
: date.
getFullYear
(),
month
: date.
getMonth
(),
date
: date.
getDate
(),
//
day
of
the
month
day
: date.
getDay
(),
//
day
of
the
week
hour
: date.
getHours
(),
minute
: date.
getMinutes
()
}
,
company
:{
email
: company.
email,
website
: company.
website,
company_registry
: company.
company_registry,
contact_address
: company.
contact_address,
vat
: company.
vat,
name
: company.
name,
phone
: company.
phone,
}
,
shop
:{
name
: shop.
name,
}
,
currency
: this
.
pos.
get
('
currency
'
),
}
;
}
,
exportAsJSON
: function
() {
var
orderLines,
paymentLines;
orderLines =
[
]
;
(this
.
get
('
orderLines
'
)).
each
(_.
bind
( function
(item) {
return
orderLines.
push
([
0
,
0
,
item.
export_as_JSON
()]
);
}
,
this
));
paymentLines =
[
]
;
(this
.
get
('
paymentLines
'
)).
each
(_.
bind
( function
(item) {
return
paymentLines.
push
([
0
,
0
,
item.
export_as_JSON
()]
);
}
,
this
));
return
{
name
: this
.
getName
(),
amount_paid
: this
.
getPaidTotal
(),
amount_total
: this
.
getTotalTaxIncluded
(),
amount_tax
: this
.
getTax
(),
amount_return
: this
.
getChange
(),
lines
: orderLines,
statement_ids
: paymentLines,
pos_session_id
: this
.
pos.
get
('
pos_session
'
).
id,
partner_id
: this
.
pos.
get
('
client
'
) ?
this
.
pos.
get
('
client
'
).
id :
undefined,
user_id
: this
.
pos.
get
('
cashier
'
) ?
this
.
pos.
get
('
cashier
'
).
id :
this
.
pos.
get
('
user
'
).
id,
cashier_name
: this
.
pos.
get
('
selectedOrder
'
).
get
('
cashier_name
'
),
}
;
}
,
getSelectedLine
: function
(){
return
this
.
selected_orderline;
}
,
selectLine
: function
(line){
if
(line){
if
(line !
=
=
this
.
selected_orderline){
if
(this
.
selected_orderline){
this
.
selected_orderline.
set_selected
(false
);
}
this
.
selected_orderline =
line;
this
.
selected_orderline.
set_selected
(true
);
}
}
else
{
this
.
selected_orderline =
undefined;
}
}
,
}
);
initialize()
Sélectionnez
initialize
: function
(attributes){
Backbone.
Model.
prototype.
initialize.
apply
(this
,
arguments);
this
.
set
({
creationDate
: new
Date
(),
orderLines
: new
module.
OrderlineCollection
(),
paymentLines
: new
module.
PaymentlineCollection
(),
name
: "
Order
"
+
this
.
generateUniqueId
(),
client
: null
,
<!--
DO NOT FORGET COMMA -
-
>
cashier_name
: null
,
<!--
ADD cashier_name FIELD HERE -
-
>
}
);
this
.
pos =
attributes.
pos;
this
.
selected_orderline =
undefined;
this
.
screen_data =
{
}
;
//
see
ScreenSelector
this
.
receipt_type =
'
receipt
'
;
//
'receipt'
||
'invoice'
return
this
;
}
,
Now that the field is created, we can send the name of the cashier to the order when needed.
Then we will add a function in the list of functions that already exist.
set_cashier_name()
Sélectionnez
set_cashier_name
: function
(name){
this
.
set
('
cashier_name
'
,
name);
}
,
If you well remember, this function is called in the show() function of CashierPayScreenWidget module.
So when the payment page will be displayed, the name of the cashier will be sent to the order.
Now we will modify the function that sends the order to the database.So when the payment page will be displayed, the name of the cashier will be sent to the order.
exportAsJSON()
Sélectionnez
exportAsJSON
: function
() {
var
orderLines,
paymentLines;
orderLines =
[
]
;
(this
.
get
('
orderLines
'
)).
each
(_.
bind
( function
(item) {
return
orderLines.
push
([
0
,
0
,
item.
export_as_JSON
()]
);
}
,
this
));
paymentLines =
[
]
;
(this
.
get
('
paymentLines
'
)).
each
(_.
bind
( function
(item) {
return
paymentLines.
push
([
0
,
0
,
item.
export_as_JSON
()]
);
}
,
this
));
return
{
name
: this
.
getName
(),
amount_paid
: this
.
getPaidTotal
(),
amount_total
: this
.
getTotalTaxIncluded
(),
amount_tax
: this
.
getTax
(),
amount_return
: this
.
getChange
(),
lines
: orderLines,
statement_ids
: paymentLines,
pos_session_id
: this
.
pos.
get
('
pos_session
'
).
id,
partner_id
: this
.
pos.
get
('
client
'
) ?
this
.
pos.
get
('
client
'
).
id :
undefined,
user_id
: this
.
pos.
get
('
cashier
'
) ?
this
.
pos.
get
('
cashier
'
).
id :
this
.
pos.
get
('
user
'
).
id,
cashier_name
: this
.
pos.
get
('
selectedOrder
'
).
get
('
cashier_name
'
),
}
;
}
,
cashier_name
Sélectionnez
cashier_name
: this
.
pos.
get
('
selectedOrder
'
).
get
('
cashier_name
'
),
The openerp_pos_cashier module() is finished!
VIII-A-8. The best for last▲
The JavaScript file is almost complete.
We now include our module inside the Point Of Sale.
For this, there is no other way than to take the function that creates the Point Of Sale and add our module.
So we add, following the module, the openerp.point_of_sale() function that is in the main.js file of point_of_sale module.
We add the openerp_pos_cashier line.
And that's it for the pos_cashier.js file !!!
We now include our module inside the Point Of Sale.
For this, there is no other way than to take the function that creates the Point Of Sale and add our module.
So we add, following the module, the openerp.point_of_sale() function that is in the main.js file of point_of_sale module.
openerp.point_of_sale()
Sélectionnez
openerp.
point_of_sale =
function
(instance) {
instance.
point_of_sale =
{
}
;
var
module =
instance.
point_of_sale;
openerp_pos_db
(instance,
module);
//
import
db.js
openerp_pos_models
(instance,
module);
//
import
pos_models.js
openerp_pos_basewidget
(instance,
module);
//
import
pos_basewidget.js
openerp_pos_keyboard
(instance,
module);
//
import
pos_keyboard_widget.js
openerp_pos_scrollbar
(instance,
module);
//
import
pos_scrollbar_widget.js
openerp_pos_screens
(instance,
module);
//
import
pos_screens.js
openerp_pos_widgets
(instance,
module);
//
import
pos_widgets.js
openerp_pos_devices
(instance,
module);
//
import
pos_devices.js
//
cashiers
openerp_pos_cashier
(instance,
module);
//
import
openerp_pos_cashier
instance.
web.
client_actions.
add
('
pos.ui
'
,
'
instance.point_of_sale.PosWidget
'
);
}
;
And that's it for the pos_cashier.js file !!!
VIII-B. the pos_cashier.xml file▲
This is the file of views needed to display the data in the Point Of Sale.
This file is placed in the xml directory of the module.
Here we will use special tags that will allow Qweb rendering engine to insert objects in the page.
This file is placed in the xml directory of the module.
/opt/modules-openerp/pos_cashiers/static/src/xml
pos_cashier.xml
Sélectionnez
<?
xml
version="1.0"
encoding="UTF-8"?
>
<!--
vim:fdl=1:
-->
<
templates
id
=
"
template
"
xml
:
space
=
"
preserve
"
>
<!--
Cashiers
drop-down
list
under
NumPad
-->
<
t
t-extend
=
"
PosWidget
"
>
<
t
t-jquery
=
"
footer
"
t-operation
=
"
append
"
>
<
div
id
=
"
AlertNoCashier
"
>
You must create at least one cashier!<
/
div
>
<
div
id
=
"
cashier-footer
"
>
<
div
id
=
"
cashier-title
"
>
Select a cashier :
<
/
div
>
<
div
id
=
"
cashier-frame
"
>
<
t
t-esc
=
"
widget.get_cashiers(widget.get_cur_pos_config_id())
"
/
>
<
select
id
=
"
cashier-select
"
>
<
/
select
>
<
/
div
>
<
/
div
>
<
/
t
>
<
/
t
>
<!--
Name
of
the
cashier
on
Payement
Page
-->
<
t
t-extend
=
"
PaymentScreenWidget
"
>
<
t
t-jquery
=
"
.pos-step-container
"
t-operation
=
"
prepend
"
>
<
div
id
=
"
pay-screen-cashier
"
>
Cashier :
<
span
id
=
"
pay-screen-cashier-name
"
>
<
/
span
>
<
/
div
>
<
/
t
>
<
/
t
>
<!--
Name
of
the
cashier
on
Ticket
-->
<
t
t-extend
=
"
PosTicket
"
>
<
t
t-jquery
=
"
#header-ticket
"
t-operation
=
"
append
"
>
Cashier : <
span
id
=
"
ticket-screen-cashier-name
"
>
<
/
span
>
<
/
t
>
<
/
t
>
<
/
templates
>
VIII-B-1. The dropdown cashiers▲
We will place the dropdown cashiers under the keypad.
To modify the original view, we use the t-extend attribute.
As you see above, a template is written inside <t></t> tags.
Here, we wish to add objects to <footer></ footer> tag , following those of the original PosWidget template.
We can see the tag which contains the error message in case of no cashier, followed by the drop-down list of cashiers.
The first tag uses the t-esc attribute that can insert standard JavaScript commands.
At the loading of the Point Of Sale, we get the ID of the Point Of Sale with get_cur_pos_config_id() function.
Then, just below, we insert the dropdown list of cashiers who belong to this Point Of Sale.
To display the name of the cashier on the payment page, we will extend the original PaymentScreenWidget module.
Finally, the cashier's name must also appear on the receipt, so we will also extend the original PosTicket module.
That's it for the XML file.
PosWidget
Sélectionnez
<!--
Cashiers
drop-down
list
under
NumPad
-->
<
t
t-extend
=
"
PosWidget
"
>
<
t
t-jquery
=
"
footer
"
t-operation
=
"
append
"
>
<
div
id
=
"
AlertNoCashier
"
>
You must create at least one cashier!<
/
div
>
<
div
id
=
"
cashier-footer
"
>
<
div
id
=
"
cashier-title
"
>
Select a cashier :
<
/
div
>
<
div
id
=
"
cashier-frame
"
>
<
t
t-esc
=
"
widget.get_cashiers(widget.get_cur_pos_config_id())
"
/
>
<
select
id
=
"
cashier-select
"
>
<
/
select
>
<
/
div
>
<
/
div
>
<
/
t
>
<
/
t
>
t-extend
Sélectionnez
<!--
Cashiers
drop-down
list
under
NumPad
-->
<
t
t-extend
=
"
PosWidget
"
>
-
-
-
<
/
t
>
To see the different attributes and functions, I invite you to read this page on the publisher's website:
Documentation QWebDocumentation QWeb
Then, just as in the XML views of Python module where we used the position attribute to place objects before, after or in place of the objects of the original template, we will use here the t-operation attribute preceded with the t-jquery attribute to specify the object of original template.Documentation QWebDocumentation QWeb
t-jquery
Sélectionnez
<
t
t-jquery
=
"
footer
"
t-operation
=
"
append
"
>
<
div
id
=
"
AlertNoCashier
"
>
You must create at least one cashier!<
/
div
>
<
div
id
=
"
cashier-footer
"
>
<
div
id
=
"
cashier-title
"
>
Select a cashier :
<
/
div
>
<
div
id
=
"
cashier-frame
"
>
<
t
t-esc
=
"
widget.get_cashiers(widget.get_cur_pos_config_id())
"
/
>
<
select
id
=
"
cashier-select
"
>
<
/
select
>
<
/
div
>
<
/
div
>
<
/
t
>
We can see the tag which contains the error message in case of no cashier, followed by the drop-down list of cashiers.
Cashiers dorpdown list
Sélectionnez
<
t
t-esc
=
"
widget.get_cashiers(widget.get_cur_pos_config_id())
"
/
>
<
select
id
=
"
cashier-select
"
>
<
/
select
>
At the loading of the Point Of Sale, we get the ID of the Point Of Sale with get_cur_pos_config_id() function.
Then, just below, we insert the dropdown list of cashiers who belong to this Point Of Sale.
To display the name of the cashier on the payment page, we will extend the original PaymentScreenWidget module.
PaymentScreenWidget
Sélectionnez
<!--
Name
of
the
cashier
on
Payement
Page
-->
<
t
t-extend
=
"
PaymentScreenWidget
"
>
<
t
t-jquery
=
"
.pos-step-container
"
t-operation
=
"
prepend
"
>
<
div
id
=
"
pay-screen-cashier
"
>
Cashier :
<
span
id
=
"
pay-screen-cashier-name
"
>
<
/
span
>
<
/
div
>
<
/
t
>
<
/
t
>
Finally, the cashier's name must also appear on the receipt, so we will also extend the original PosTicket module.
PosTicket
Sélectionnez
<!--
Name
of
the
cashier
on
Ticket
-->
<
t
t-extend
=
"
PosTicket
"
>
<
t
t-jquery
=
"
#header-ticket
"
t-operation
=
"
append
"
>
Cashier : <
span
id
=
"
ticket-screen-cashier-name
"
>
<
/
span
>
<
/
t
>
<
/
t
>
That's it for the XML file.
VIII-C. pos_cashier.css file▲
A simple *.CSS file to put in css directory of the module :
Here you can put your additional styles for your module, and you can also edit the original ones, if needed,
/opt/modules-openerp/pos_cashiers/static/src/css
pos_cashier.css
Sélectionnez
#
cashier-title
{
vertical-align:
middle
;
display:
inline-block;
text-align:
left;
font-size:
16px
;
font-weight:
normal
;
font-style:
italic
;
width:
45%
;
}
#
cashier-frame
{
text-align:
center
;
vertical-align:
middle
;
display:
inline-block;
border:
1px
solid
#
000000
;
width:
55%
;
padding:
5px
0px
5px
0px
;
}
#
cashier-select
{
width:
95%
;
}
#
cashier-footer
{
background:
linear-gradient(#
7B7979
,
#
393939
) repeat
scroll
0
0
transparent
;
display:
block
;
color:
#
ffcc00
;
padding:
10px
5px
10px
5px
;
}
#
pay-screen-cashier
{
color:
black
;
border-bottom:
1px
dashed
#
666666
;
padding:
2px
2px
2px
2px
;
text-align:
left;
font-size:
14px
;
font-weight:
normal
;
font-style:
italic
;
}
#
ticket-screen-cashier
{
font-style:
italic
;
border-bottom :
1px
solid
gray
;
padding-bottom:
2px
;
}
#
AlertNoCashier
{
background:
red
url
("
.
.
/
img
/
error
.
png
"
) no-repeat
4px
;
color:
white
;
font-size:
14px
;
font-weight:
bold
;
padding:
12px
4px
4px
30px
;
height:
24px
;
text-transform:
uppercase
;
}
IX. Installation of the module▲
This time, we can install our module.
Connect to OpenERP as an administrator, and then click the Configuration menu.
Click on « Update list of modules » link
Then click « Installed Modules » and delete the « Installed » filter in the search bar.
Our module will appear.
Click the Install button and wait until the end of the installation.
Connect to OpenERP as an administrator, and then click the Configuration menu.
Click on « Update list of modules » link
Then click « Installed Modules » and delete the « Installed » filter in the search bar.
Our module will appear.
Click the Install button and wait until the end of the installation.
X. Internationalisation▲
You have noticed that the text, labels and titles were all in English?
There are two reasons for this.
Know that we need to create a pos_cashier.pot file, we put in the i18n directory.
A *.pot file is a translation template file. It contains only the original terms, it does not contain the translated words.
But creating a *.pot from A to Z is a bit complicated.
Fortunately, our beloved OpenERP Dev Team have thought of everything.
Make a *.pot file
Here's what looks like a *.pot.
Before the copy, we will add some instructions.
You may have noticed that the words that are in this file are the names of the fields, constraints or comments that we have created in the tables.
We also want to translate words that we « hard coded » in the XML files of some views.
In particular, we want to translate the error message that appears in the Point Of Sale when there is no cashier, we also want to translate the word « cashier », etc..
We will add portions of code as below.
Again, since there is no documentation on this, I poked around in the translation files of other modules.
It can translate a word or phrase in an XML file specifying the source of the file (from the root module) followed by the line number.
In the example above, the sentence to be translated is line 9 of static/src/xml/pos_cashier.xml file.
Then we add the following two translations.
Please note that the string to be translated is in front of the msgid keyword. This is the identifier of the chain. On the next line, we have the msgstr keyword followed by an empty string.
Save the file, then copy the file renaming, this time to fr.po. This will be our translation file for the French language. Obviously, you'll understand it then just add the translations of the strings in the corresponding msgstr.
Here is an excerpt of fr.po file:
If you want to translate into several languages, simply copy and rename the pos_cashier.pot file to a xx.po file . For different languages taken into account, just look in the i18n directory of the other modules.
While you're there, then duplicate the fr.po file for Belgium and Switzerland.
When you have completed the translation files, you will need to restart OpenERP sure everything loads properly.
Then you will be able to go into the POS, tickle the drop-down list after creating two or three cashiers!
There are two reasons for this.
- The first one , is that the module is fully functional, it may therefore be useful to other people. It will just make them translated into their language. I could do it, but I speak not very well Hungarian nor Korean ...
- The second reason is that I wanted to explain how to do the translation of a module. This is the best reason, finally.
Know that we need to create a pos_cashier.pot file, we put in the i18n directory.
From this pos_cashier.pot file, we can create files for different languages./opt/modules-openerp/pos_cashier/i18n
A *.pot file is a translation template file. It contains only the original terms, it does not contain the translated words.
But creating a *.pot from A to Z is a bit complicated.
Fortunately, our beloved OpenERP Dev Team have thought of everything.
Make a *.pot file
- Connect to OpenERP as administrator
- click Configuration in top menu.
- In Translation heading, click Export translation.
- The form below appears
- In Langage field, select New Language.
- In File format, select PO File.
- In Modules to export, select POS Cashiers.
- Click Export.
- A second window is displayed.
- Download the file by clicking the download link.
- Rename the file to pos_cashier.pot.
- And open it with your text editor.
pos_cashier.pot
Sélectionnez
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * pos_cashier
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0-20130703-231023\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-07-13 00:04+0000\n"
"PO-Revision-Date: 2013-07-13 00:04+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: pos_cashier
#: view:pos.cashier:0
msgid "All"
msgstr ""
#. module: pos_cashier
#: model:ir.model,name:pos_cashier.model_pos_order
msgid "Point of Sale"
msgstr ""
#. module: pos_cashier
#: model:ir.actions.act_window,help:pos_cashier.action_pos_cashier
msgid "<p class=\"oe_view_nocontent_create\">\n"
" Click here to create a cashier for the Point Of Sale.\n"
" </p>\n"
" "
msgstr ""
#. module: pos_cashier
#: view:pos.cashier:0
msgid "Point of Sale Cashier"
msgstr ""
#. module: pos_cashier
#: field:pos.cashier,cashier_name:0
#: field:pos.order,cashier_name:0
msgid "Cashier"
msgstr ""
#. module: pos_cashier
#: view:pos.cashier:0
msgid "Inactive"
msgstr ""
#. module: pos_cashier
#: help:pos.cashier,active:0
msgid "If a cashier is not active, it will not be displayed in POS"
msgstr ""
#. module: pos_cashier
#: sql_constraint:pos.cashier:0
msgid "A cashier already exists with this name in this Point Of sale. Cashier's name must be unique!"
msgstr ""
#. module: pos_cashier
#: view:pos.cashier:0
#: field:pos.cashier,active:0
msgid "Active"
msgstr ""
#. module: pos_cashier
#: model:ir.model,name:pos_cashier.model_pos_cashier
msgid "pos.cashier"
msgstr ""
#. module: pos_cashier
#: model:ir.actions.act_window,name:pos_cashier.action_pos_cashier
#: model:ir.ui.menu,name:pos_cashier.menu_action_pos_cashier
#: model:ir.ui.menu,name:pos_cashier.menu_point_of_sale_cashiers
#: view:pos.cashier:0
msgid "Cashiers"
msgstr ""
#. module: pos_cashier
#: field:pos.cashier,pos_config_id:0
msgid "Point Of Sale"
msgstr ""
Before the copy, we will add some instructions.
You may have noticed that the words that are in this file are the names of the fields, constraints or comments that we have created in the tables.
We also want to translate words that we « hard coded » in the XML files of some views.
In particular, we want to translate the error message that appears in the Point Of Sale when there is no cashier, we also want to translate the word « cashier », etc..
We will add portions of code as below.
pos_cashier.pot
Sélectionnez
#. module: pos_cashier
#. openerp-web
#: code:static/src/xml/pos_cashier.xml:9
#, python-format
msgid "You must create at least one cashier!"
msgstr ""
It can translate a word or phrase in an XML file specifying the source of the file (from the root module) followed by the line number.
In the example above, the sentence to be translated is line 9 of static/src/xml/pos_cashier.xml file.
Then we add the following two translations.
pos_cashier.pot
Sélectionnez
#. module: pos_cashier
#. openerp-web
#: code:static/src/xml/pos_cashier.xml:12
#, python-format
msgid "Select a cashier :"
msgstr ""
#. module: pos_cashier
#. openerp-web
#: code:static/src/xml/pos_cashier.xml:26
#: code:static/src/xml/pos_cashier.xml:36
#, python-format
msgid "Cashier :"
msgstr ""
Save the file, then copy the file renaming, this time to fr.po. This will be our translation file for the French language. Obviously, you'll understand it then just add the translations of the strings in the corresponding msgstr.
Here is an excerpt of fr.po file:
fr.po
Sélectionnez
#. module: pos_cashier
#: help:pos.cashier,active:0
msgid "If a cashier is not active, it will not be displayed in POS"
msgstr "Un caissier désactivé ne sera pas visible dans le Point De Vente"
#. module: pos_cashier
#: sql_constraint:pos.cashier:0
msgid ""
"A cashier already exists with this name in this Point Of sale. Cashier's "
"name must be unique!"
msgstr ""
"Un caissier existe déjà avec le même nom dans ce Point De Vente. Le nom du "
"caissier doit être unique!"
While you're there, then duplicate the fr.po file for Belgium and Switzerland.
When you have completed the translation files, you will need to restart OpenERP sure everything loads properly.
Then you will be able to go into the POS, tickle the drop-down list after creating two or three cashiers!
XI. Conclusion▲
In this tutorial we have seen a lot of things, finally:
Thank you.
- the creation of a Python object;
- the creation of XML views (form view and tree view) ;
- the creation of search filters;
- the creation of a menu ;
- the module access rights;
- the records rules ;
- add an icon to the module ;
- modify the Point Of sale (add a dropdown list and a button) ;
- QWeb and templates;
- the installation of a module ;
- the translation of a module.
- OpenERP Server Developers DocumentationOpenERP Server Developers Documentation ;
- OpenERP Web's documentationOpenERP Web's documentation ;
- QWebQWeb ;
- OpenERP technical MementoOpenERP technical Memento.
Thank you.
No comments:
Post a Comment