[IMP] point_of_sale: new pos.config dashboard

This dashboard has implied some refactoring:
 - the wizard pos_session_opening has been deleted and all the methods have been put inside pos_config
 - new fields in pos_config to display some useful informations (closing balance, closing date, etc.)
This commit is contained in:
Martin Geubelle
2015-08-14 10:22:39 +02:00
committed by David Monjoie
parent 67a74ebe2b
commit 48ab50ed0d
12 changed files with 292 additions and 207 deletions
+2 -1
View File
@@ -41,8 +41,8 @@ Main Features
'wizard/pos_discount.xml',
'wizard/pos_open_statement.xml',
'wizard/pos_payment.xml',
'wizard/pos_session_opening.xml',
'views/templates.xml',
'point_of_sale.xml',
'point_of_sale_report.xml',
'point_of_sale_view.xml',
'point_of_sale_sequence.xml',
@@ -63,6 +63,7 @@ Main Features
'views/report_sessionsummary.xml',
'views/report_userlabel.xml',
'views/point_of_sale.xml',
'point_of_sale_dashboard.xml',
],
'demo': [
'point_of_sale_demo.xml',
+101 -1
View File
@@ -39,6 +39,45 @@ class pos_config(osv.osv):
result[pos_config.id] = currency_id
return result
def _get_current_session(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
session_id = record.session_ids.filtered(lambda r: r.user_id.id == uid and not r.state == 'closed')
result[record.id] = {
'current_session_id': session_id,
'current_session_state': session_id.state,
}
return result
def _get_last_session(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
session_ids = self.pool['pos.session'].search_read(
cr, uid,
[('config_id', '=', record.id), ('state', '=', 'closed')],
['cash_register_balance_end_real', 'stop_at'],
order="stop_at desc", limit=1, context=context)
if session_ids:
result[record.id] = {
'last_session_closing_cash': session_ids[0]['cash_register_balance_end_real'],
'last_session_closing_date': session_ids[0]['stop_at'],
}
else:
result[record.id] = {
'last_session_closing_cash': 0,
'last_session_closing_date': None,
}
return result
def _get_current_session_user(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
result[record.id] = record.session_ids.filtered(lambda r: r.state == 'opened').user_id.name
return result
_columns = {
'name' : fields.char('Point of Sale Name', select=1,
required=True, help="An internal identification of the point of sale"),
@@ -76,6 +115,11 @@ class pos_config(osv.osv):
help="This sequence is automatically created by Odoo but you can change it "\
"to customize the reference numbers of your orders.", copy=False),
'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
'current_session_id': fields.function(_get_current_session, multi="session", type="many2one", relation="pos.session", string="Current Session"),
'current_session_state': fields.function(_get_current_session, multi="session", type='char'),
'last_session_closing_cash': fields.function(_get_last_session, multi="last_session", type='float'),
'last_session_closing_date': fields.function(_get_last_session, multi="last_session", type='date'),
'pos_session_username': fields.function(_get_current_session_user, type='char'),
'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
'pricelist_id': fields.many2one('product.pricelist','Pricelist', required=True),
'company_id': fields.many2one('res.company', 'Company', required=True),
@@ -231,6 +275,62 @@ class pos_config(osv.osv):
obj.sequence_id.unlink()
return super(pos_config, self).unlink(cr, uid, ids, context=context)
# Methods to open the POS
def open_ui(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
context = dict(context or {})
context['active_id'] = record.current_session_id.id
return {
'type': 'ir.actions.act_url',
'url': '/pos/web/',
'target': 'self',
}
def open_existing_session_cb_close(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
record.current_session_id.signal_workflow('cashbox_control')
return self.open_session_cb(cr, uid, ids, context)
def open_session_cb(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
proxy = self.pool.get('pos.session')
record = self.browse(cr, uid, ids[0], context=context)
current_session_id = record.current_session_id
if not current_session_id:
values = {
'user_id': uid,
'config_id': record.id,
}
session_id = proxy.create(cr, uid, values, context=context)
record.current_session_id = proxy.browse(cr, uid, session_id, context=context)
if record.current_session_id.state == 'opened':
return self.open_ui(cr, uid, ids, context=context)
return self._open_session(session_id)
return self._open_session(current_session_id.id)
def open_existing_session_cb(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
return self._open_session(record.current_session_id.id)
def _open_session(self, session_id):
return {
'name': _('Session'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'pos.session',
'res_id': session_id,
'view_id': False,
'type': 'ir.actions.act_window',
}
class pos_session(osv.osv):
_name = 'pos.session'
_order = 'id desc'
@@ -739,7 +839,7 @@ class pos_order(osv.osv):
domain="[('state', '=', 'opened')]",
states={'draft' : [('readonly', False)]},
readonly=True),
'config_id': fields.related('session_id', 'config_id', string="Point of Sale", type='many2one' , relation='pos.config'),
'state': fields.selection([('draft', 'New'),
('cancel', 'Cancelled'),
('paid', 'Paid'),
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="hr assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/point_of_sale/static/src/less/pos_dashboard.less"/>
</xpath>
</template>
</odoo>
@@ -0,0 +1,140 @@
<?xml version="1.0"?>
<odoo>
<record id="action_pos_session_filtered" model="ir.actions.act_window">
<field name="name">Sessions</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_pos_session_search" />
<field name="context">{
'search_default_config_id': [active_id],
'default_config_id': active_id}
</field>
</record>
<record id="action_pos_order_filtered" model="ir.actions.act_window">
<field name="name">Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_pos_order_search"/>
<field name="context">{
'search_default_config_id': [active_id],
'default_config_id': active_id}
</field>
</record>
<record id="action_report_pos_order_all_filtered" model="ir.actions.act_window">
<field name="name">Orders Analysis</field>
<field name="res_model">report.pos.order</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
<field name="search_view_id" ref="view_report_pos_order_search"/>
<field name="context">{
'search_default_config_id': [active_id],
'default_config_id': active_id}
</field>
</record>
<!-- Dashboard -->
<record id="view_pos_config_kanban" model="ir.ui.view">
<field name="name">pos.config.kanban.view</field>
<field name="model">pos.config</field>
<field name="arch" type="xml">
<kanban create="false" class="o_kanban_dashboard o_pos_kanban">
<field name="state"/>
<field name="name"/>
<field name="session_ids"/>
<field name="current_session_id"/>
<field name="current_session_state"/>
<field name="last_session_closing_cash"/>
<field name="last_session_closing_date"/>
<field name="pos_session_username"/>
<templates>
<t t-name="kanban-box">
<div>
<div class="o_kanban_card_header">
<div class="o_kanban_card_header_title">
<div class="o_primary"><t t-esc="record.name.value"/></div>
<div class="o_secondary"><t t-esc="record.pos_session_username.value || 'Unused'"/></div>
</div>
<div class="o_kanban_manage_button_section">
<a class="o_kanban_manage_toggle_button" href="#">More <i class="fa fa-caret-down"/></a>
</div>
</div>
<div class="container o_kanban_card_content o_visible">
<div class="row">
<div class="col-xs-6 o_kanban_primary_left">
<button t-if="record.current_session_state.raw_value === 'opened'" class="btn btn-primary" name="open_ui" type="object">Resume</button>
<button t-if="record.current_session_state.raw_value === 'opening_control'" class="btn btn-primary" name="open_existing_session_cb" type="object">Open Session</button>
<button t-if="record.current_session_state.raw_value === 'opened'" class="btn btn-default" name="open_existing_session_cb_close" type="object">Close</button>
<button t-if="record.current_session_state.raw_value === 'closing_control'" class="btn btn-default" name="open_existing_session_cb" type="object">Close</button>
<button t-if="!record.current_session_id.raw_value" class="btn btn-primary" name="open_session_cb" type="object">New Session</button>
</div>
<div class="col-xs-6 o_kanban_primary_right">
<div t-if="record.last_session_closing_date.value" class="row">
<div class="col-xs-6">
<span>Last Closing Date</span>
</div>
<div class="col-xs-6">
<span><t t-esc="record.last_session_closing_date.value"/></span>
</div>
</div>
<div t-if="record.last_session_closing_date.value" class="row">
<div class="col-xs-6">
<span>Cash Balance</span>
</div>
<div class="col-xs-6">
<span><t t-esc="record.last_session_closing_cash.value"/></span>
</div>
</div>
</div>
</div>
</div><div class="container o_kanban_card_manage_pane o_invisible">
<div class="row">
<div class="col-xs-6 o_kanban_card_manage_section o_kanban_manage_view">
<div class="o_kanban_card_manage_title">
<span>View</span>
</div>
<div>
<a name="%(action_pos_session_filtered)d" type="action">Sessions</a>
</div>
<div>
<a name="%(action_pos_order_filtered)d" type="action">Sales Orders</a>
</div>
</div>
<div class="col-xs-6 o_kanban_card_manage_section o_kanban_manage_new">
<div class="o_kanban_card_manage_title">
<span>Reports</span>
</div>
<div>
<a name="%(action_report_pos_order_all_filtered)d" type="action">Orders</a>
</div>
</div>
</div>
<div t-if="widget.editable" class="o_kanban_card_manage_settings row">
<div class="col-xs-12 text-right">
<a type="edit">Settings</a>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
</odoo>
+28 -1
View File
@@ -160,6 +160,17 @@
</field>
</record>
<record model="ir.ui.view" id="view_pos_order_search">
<field name="name">pos.order.search.view</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<search string="Point of Sale Orders">
<field name="name"/>
<field name="config_id"/>
</search>
</field>
</record>
<menuitem name="Orders" id="menu_point_of_sale" parent="menu_point_root" sequence="10"/>
<menuitem parent="menu_point_of_sale" id="menu_point_ofsale" action="action_pos_pos_form" sequence="1" groups="group_pos_manager,group_pos_user"/>
@@ -943,7 +954,23 @@
</field>
</record>
<menuitem action="action_pos_session_opening"
<record model="ir.actions.act_window" id="action_pos_config_kanban">
<field name="name">Point of Sale</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.config</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="search_view_id" ref="view_pos_config_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new PoS config.
</p><p>
Configure at least one Point of Sale to be able to sell through the PoS interface.
</p>
</field>
</record>
<menuitem action="action_pos_config_kanban"
parent="menu_point_root" name="Dashboard"
id="menu_pos_dashboard" sequence="1"/>
@@ -37,6 +37,7 @@
<filter string="Year" name="year" domain="[('date','&lt;=', time.strftime('%%Y-12-31')),('date','&gt;=',time.strftime('%%Y-01-01'))]" help="POS ordered created during current year"/>
<separator/>
<filter string="My Sales" help="My Sales" domain="[('user_id','=',uid)]"/>
<field name="config_id"/>
<field name="partner_id"/>
<field name="user_id"/>
<field name="partner_id"/>
+1 -1
View File
@@ -6,7 +6,7 @@ var Tour = require('web.Tour');
Tour.register({
id: 'pos_basic_order',
name: 'Complete a basic order trough the Front-End',
path: '/web#model=pos.session.opening&action=point_of_sale.action_pos_session_opening',
path: '/web#model=pos.config&action=point_of_sale.action_pos_config_kanban',
mode: 'test',
steps: [
{
@@ -0,0 +1,9 @@
.o_kanban_view.o_kanban_dashboard.o_pos_kanban.o_kanban_ungrouped {
.o_kanban_record {
min-width: 500px;
@media (max-width: @grid-float-breakpoint) {
min-width: inherit;
}
}
}
@@ -38,26 +38,11 @@
-
!record {model: product.product, id: product.product_product_4, view: False}:
taxes_id: [account_tax_05_incl, account_tax_05_incl_chicago]
-
I create a new session
-
!record {model: pos.session, id: pos_order_session0}:
user_id: 1
config_id: point_of_sale.pos_config_main
-
I open a new PoS Session wizard
-
!record {model: pos.session.opening, id: pos_order_session_wizard0}:
pos_config_id: point_of_sale.pos_config_main
-
I click on create a new session button
-
!python {model: pos.session.opening}: |
self.open_existing_session_cb(cr, uid, [ref('pos_order_session_wizard0')])
-
I open the session after having counted the money
-
!workflow {model: pos.session, action: open, ref: pos_order_session0}
!python {model: pos.config}: |
self.open_session_cb(cr, uid, [ref('pos_config_main')])
-
I create a PoS order with 2 units of PCSC234 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
-
-1
View File
@@ -7,4 +7,3 @@ import pos_details
import pos_discount
import pos_open_statement
import pos_payment
import pos_session_opening
@@ -1,116 +0,0 @@
from openerp.osv import osv, fields
from openerp.tools.translate import _
from openerp.addons.point_of_sale.point_of_sale import pos_session
class pos_session_opening(osv.osv_memory):
_name = 'pos.session.opening'
_columns = {
'pos_config_id' : fields.many2one('pos.config', string='Point of Sale', required=True),
'pos_session_id' : fields.many2one('pos.session', string='PoS Session'),
'pos_state' : fields.related('pos_session_id', 'state',
type='selection',
selection=pos_session.POS_SESSION_STATE,
string='Session Status', readonly=True),
'pos_state_str' : fields.char('Status', readonly=True),
'show_config' : fields.boolean('Show Config', readonly=True),
'pos_session_name' : fields.related('pos_session_id', 'name', string="Session Name",
type='char', size=64, readonly=True),
'pos_session_username' : fields.related('pos_session_id', 'user_id', 'name',
type='char', size=64, readonly=True)
}
def open_ui(self, cr, uid, ids, context=None):
data = self.browse(cr, uid, ids[0], context=context)
context = dict(context or {})
context['active_id'] = data.pos_session_id.id
return {
'type' : 'ir.actions.act_url',
'url': '/pos/web/',
'target': 'self',
}
def open_existing_session_cb_close(self, cr, uid, ids, context=None):
wizard = self.browse(cr, uid, ids[0], context=context)
wizard.pos_session_id.signal_workflow('cashbox_control')
return self.open_session_cb(cr, uid, ids, context)
def open_session_cb(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
proxy = self.pool.get('pos.session')
wizard = self.browse(cr, uid, ids[0], context=context)
if not wizard.pos_session_id:
values = {
'user_id' : uid,
'config_id' : wizard.pos_config_id.id,
}
session_id = proxy.create(cr, uid, values, context=context)
s = proxy.browse(cr, uid, session_id, context=context)
if s.state=='opened':
return self.open_ui(cr, uid, ids, context=context)
return self._open_session(session_id)
return self._open_session(wizard.pos_session_id.id)
def open_existing_session_cb(self, cr, uid, ids, context=None):
assert len(ids) == 1
wizard = self.browse(cr, uid, ids[0], context=context)
return self._open_session(wizard.pos_session_id.id)
def _open_session(self, session_id):
return {
'name': _('Session'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'pos.session',
'res_id': session_id,
'view_id': False,
'type': 'ir.actions.act_window',
}
def on_change_config(self, cr, uid, ids, config_id, context=None):
result = {
'pos_session_id': False,
'pos_state': False,
'pos_state_str' : '',
'pos_session_username' : False,
'pos_session_name' : False,
}
if not config_id:
return {'value' : result}
proxy = self.pool.get('pos.session')
session_ids = proxy.search(cr, uid, [
('state', '!=', 'closed'),
('config_id', '=', config_id),
('user_id', '=', uid),
], context=context)
if session_ids:
session = proxy.browse(cr, uid, session_ids[0], context=context)
result['pos_state'] = str(session.state)
result['pos_state_str'] = dict(pos_session.POS_SESSION_STATE).get(session.state, '')
result['pos_session_id'] = session.id
result['pos_session_name'] = session.name
result['pos_session_username'] = session.user_id.name
return {'value' : result}
def default_get(self, cr, uid, fieldnames, context=None):
so = self.pool.get('pos.session')
session_ids = so.search(cr, uid, [('state','<>','closed'), ('user_id','=',uid)], context=context)
if session_ids:
result = so.browse(cr, uid, session_ids[0], context=context).config_id.id
else:
current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
result = current_user.pos_config and current_user.pos_config.id or False
if not result:
r = self.pool.get('pos.config').search(cr, uid, [], context=context)
result = r and r[0] or False
count = self.pool.get('pos.config').search_count(cr, uid, [('state', '=', 'active')], context=context)
show_config = bool(count > 1)
return {
'pos_config_id' : result,
'show_config' : show_config,
}
@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="pos_session_opening_form_view">
<field name="name">pos.session.opening.form.view</field>
<field name="model">pos.session.opening</field>
<field name="arch" type="xml">
<form string="PoS Session Opening">
<field name="show_config" invisible="1" />
<separator string="Select your Point of Sale" colspan="4" attrs="{'invisible' : [('show_config', '=', False)]}" />
<group attrs="{'invisible' : [('show_config', '=', False)]}">
<field name="pos_config_id" on_change="on_change_config(pos_config_id)"
options="{'no_create': True}" domain="[('state','=','active')]"
class="oe_inline"/>
<field name="pos_state" invisible="1" />
</group>
<field name="pos_session_id" invisible="1"/>
<button name="open_ui" type="object" string="Resume Session"
attrs="{'invisible' : [('pos_state', 'not in', ('opened',))]}"
class="oe_highlight"
/>
<button name="open_existing_session_cb" type="object" string="Open Session"
attrs="{'invisible' : [('pos_state', 'not in', ('opening_control',))]}"
class="oe_highlight"
/>
<button name="open_existing_session_cb_close" type="object" string="Close Session"
attrs="{'invisible' : [('pos_state', 'not in', ('opened',))]}"
/>
<button name="open_existing_session_cb" type="object" string="Close Session"
attrs="{'invisible' : [('pos_state', 'not in', ('closing_control',))]}"
class="oe_highlight"
/>
<button name="open_session_cb" type="object" string="New Session"
attrs="{'invisible' : ['|', ('pos_state', '!=', False),('pos_config_id','=',False)]}"
class="oe_highlight"
/>
<div attrs="{'invisible' : ['|', ('pos_state', '!=', False),('pos_config_id','=',False)]}" class="oe_view_nocontent">
<p class="oe_view_nocontent_create">
Click to start a session.
</p><p>
You may have to control your cash amount in your cash register, before
being able to start selling through the touchscreen interface.
</p>
</div>
<div attrs="{'invisible' : [('pos_state', '!=', 'opened')]}" class="oe_view_nocontent">
<p class="oe_view_nocontent_create">
Click to continue the session.
</p>
<p>
The session <field name="pos_session_name" class="oe_inline" /> (<field name="pos_session_username" class="oe_inline" />) is "<b><field name="pos_state_str" class="oe_inline" /></b>".<br/>
You can continue sales from the touchscreen interface by clicking on "Start Selling" or close the cash register session.
</p>
</div>
</form>
</field>
</record>
<record id="action_pos_session_opening" model="ir.actions.act_window">
<field name="name">Your Session</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.session.opening</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
</record>
</data>
</openerp>