diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py
index 8b6a94c04ba..13ea11af7a6 100644
--- a/addons/hw_escpos/escpos/escpos.py
+++ b/addons/hw_escpos/escpos/escpos.py
@@ -682,7 +682,7 @@ class Escpos:
try:
stylestack = StyleStack()
serializer = XmlSerializer(self)
- root = ET.fromstring(xml)
+ root = ET.fromstring(xml.encode('utf-8'))
self._raw(stylestack.to_escpos())
diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py
index 79b4cbeebbf..4e378d0a494 100644
--- a/addons/point_of_sale/__openerp__.py
+++ b/addons/point_of_sale/__openerp__.py
@@ -60,6 +60,7 @@ Main Features
'wizard/pos_open_statement.xml',
'wizard/pos_payment.xml',
'wizard/pos_session_opening.xml',
+ 'views/templates.xml',
'point_of_sale_report.xml',
'point_of_sale_view.xml',
'point_of_sale_data.xml',
diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py
index 694f59f386c..bd9cce5a2f3 100644
--- a/addons/point_of_sale/controllers/main.py
+++ b/addons/point_of_sale/controllers/main.py
@@ -12,47 +12,6 @@ from openerp.addons.web.controllers.main import module_boot, login_redirect
_logger = logging.getLogger(__name__)
-html_template = """
-
-
- OpenERP POS
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"""
class PosController(http.Controller):
@@ -62,14 +21,19 @@ class PosController(http.Controller):
if not request.session.uid:
return login_redirect()
- r = html_template % {
- 'modules': simplejson.dumps(module_boot(request.db)),
- 'init': """
- var wc = new s.web.WebClient();
- wc.show_application = function(){
- wc.action_manager.do_action("pos.ui");
- };
- wc.appendTo($(document.body));
- """
- }
- return r
+ modules = simplejson.dumps(module_boot(request.db))
+ init = """
+ var wc = new s.web.WebClient();
+ wc.show_application = function(){
+ wc.action_manager.do_action("pos.ui");
+ };
+ wc.appendTo($(document.body));
+ """
+
+ html = request.registry.get('ir.ui.view').render(request.cr, request.session.uid,'point_of_sale.index',{
+ 'modules': modules,
+ 'init': init,
+ })
+
+ return html
+
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index c8cf81d062c..7d604bf6322 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -88,7 +88,14 @@ class pos_config(osv.osv):
'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
'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),
+ 'company_id': fields.many2one('res.company', 'Company', required=True),
+ 'barcode_product': fields.char('Product Barcodes', size=64, help='The pattern that identifies product barcodes'),
+ 'barcode_cashier': fields.char('Cashier Barcodes', size=64, help='The pattern that identifies cashier login barcodes'),
+ 'barcode_customer': fields.char('Customer Barcodes',size=64, help='The pattern that identifies customer\'s client card barcodes'),
+ 'barcode_price': fields.char('Price Barcodes', size=64, help='The pattern that identifies a product with a barcode encoded price'),
+ 'barcode_weight': fields.char('Weight Barcodes', size=64, help='The pattern that identifies a product with a barcode encoded weight'),
+ 'barcode_discount': fields.char('Discount Barcodes', size=64, help='The pattern that identifies a product with a barcode encoded discount'),
+ 'fidelity_id': fields.many2one('pos.fidelity','Fidelity Card', help='The type of fidelity card available for this point_of_sale'),
}
def _check_cash_control(self, cr, uid, ids, context=None):
@@ -156,6 +163,12 @@ class pos_config(osv.osv):
'iface_invoicing': True,
'stock_location_id': _get_default_location,
'company_id': _get_default_company,
+ 'barcode_product': '*',
+ 'barcode_cashier': '041*',
+ 'barcode_customer':'042*',
+ 'barcode_weight': '21xxxxxNNDDD',
+ 'barcode_discount':'22xxxxxxxxNN',
+ 'barcode_price': '23xxxxxNNNDD',
}
def onchange_picking_type_id(self, cr, uid, ids, picking_type_id, context=None):
@@ -191,6 +204,23 @@ class pos_config(osv.osv):
obj.sequence_id.unlink()
return super(pos_config, self).unlink(cr, uid, ids, context=context)
+class pos_fidelity(osv.osv):
+ _name = 'pos.fidelity'
+
+ _columns = {
+ 'name' : fields.char('Fidelity Card Name', size=32, select=1,
+ required=True, help="An internal identification for the fidelity card configuration"),
+ 'fidpoints_currency': fields.float('Points per paid currency',help="How many fidelity points are given to the customer by sold currency"),
+ 'fidpoints_product': fields.float('Points per sold product',help="How many fidelity points are given to the customer by product sold"),
+ 'fidpoints_sale': fields.float('Points per sale',help="How many fidelity points are given to the customer for each sale"),
+ }
+
+ _defaults = {
+ 'fidpoints_currency': 0,
+ 'fidpoints_product': 0,
+ 'fidpoints_sale': 0,
+ }
+
class pos_session(osv.osv):
_name = 'pos.session'
_order = 'id desc'
@@ -241,6 +271,8 @@ class pos_session(osv.osv):
'state' : fields.selection(POS_SESSION_STATE, 'Status',
required=True, readonly=True,
select=1),
+
+ 'sequence_number': fields.integer('Order Sequence Number'),
'cash_control' : fields.function(_compute_cash_all,
multi='cash',
@@ -303,6 +335,7 @@ class pos_session(osv.osv):
'name' : '/',
'user_id' : lambda obj, cr, uid, context: uid,
'state' : 'opening_control',
+ 'sequence_number': 1,
}
_sql_constraints = [
@@ -523,6 +556,26 @@ class pos_order(osv.osv):
_description = "Point of Sale"
_order = "id desc"
+ def _order_fields(self,ui_order):
+ return {
+ 'name': ui_order['name'],
+ 'user_id': ui_order['user_id'] or False,
+ 'session_id': ui_order['pos_session_id'],
+ 'lines': ui_order['lines'],
+ 'pos_reference':ui_order['name'],
+ 'partner_id': ui_order['partner_id'] or False,
+ 'fidpoints': ui_order['fidpoints'],
+ }
+
+ def _payment_fields(self,ui_paymentline):
+ return {
+ 'amount': ui_paymentline['amount'] or 0.0,
+ 'payment_date': ui_paymentline['name'],
+ 'statement_id': ui_paymentline['statement_id'],
+ 'payment_name': ui_paymentline.get('note',False),
+ 'journal': ui_paymentline['journal_id'],
+ }
+
def create_from_ui(self, cr, uid, orders, context=None):
# Keep only new orders
submitted_references = [o['data']['name'] for o in orders]
@@ -535,27 +588,21 @@ class pos_order(osv.osv):
for tmp_order in orders_to_save:
to_invoice = tmp_order['to_invoice']
order = tmp_order['data']
+ order_id = self.create(cr, uid, self._order_fields(order),context)
- 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'],
- 'partner_id': order['partner_id'] or False
- }, 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)
+ self.add_payment(cr, uid, order_id, self._payment_fields(payments[2]), context=context)
+
+ session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context)
+ if session.sequence_number <= order['sequence_number']:
+ session.write({'sequence_number': order['sequence_number'] + 1})
+ session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context)
+
+ if order['fidpoints'] and order['partner_id']:
+ partner = self.pool.get('res.partner').browse(cr, uid, order['partner_id'], context=context)
+ partner.write({'fidpoints': partner['fidpoints'] + order['fidpoints']})
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
if not cash_journal:
cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', session.statement_ids)
@@ -662,6 +709,8 @@ class pos_order(osv.osv):
'statement_ids': fields.one2many('account.bank.statement.line', 'pos_statement_id', 'Payments', states={'draft': [('readonly', False)]}, readonly=True),
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'draft': [('readonly', False)]}, readonly=True),
'partner_id': fields.many2one('res.partner', 'Customer', change_default=True, select=1, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]}),
+ 'sequence_number': fields.integer('Sequence Number', help='A session-unique sequence number for the order'),
+ 'fidpoints': fields.integer('Won Fidelity Points ', help='The Fidelity Points the client won with this order'),
'session_id' : fields.many2one('pos.session', 'Session',
#required=True,
@@ -710,6 +759,8 @@ class pos_order(osv.osv):
'name': '/',
'date_order': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'nb_print': 0,
+ 'sequence_number': 1,
+ 'fidpoints': 0,
'session_id': _default_session,
'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
'pricelist_id': _default_pricelist,
diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml
index 559fff5d7fc..3f699885ae2 100644
--- a/addons/point_of_sale/point_of_sale_view.xml
+++ b/addons/point_of_sale/point_of_sale_view.xml
@@ -674,6 +674,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -705,6 +716,60 @@
+
+
+
+ pos.fidelity.form.view
+ pos.fidelity
+
+
+
+
+
+
+ pos.fidelity.form.view
+ pos.fidelity
+
+
+
+
+
+
+
+
+ Fidelity Cards
+ ir.actions.act_window
+ pos.fidelity
+ form
+ tree,form
+
+
+ Click to add a Fidelity Card System
+
+ Fidelity card system allows you customer to earn points
+ and rewards when doing business at your shops.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 80515952317..30939636b23 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -303,24 +303,65 @@ td {
}
.pos .order-button{
- color: #273072;
- height:27px;
- margin:3px;
- margin-right:0px;
- border: 1px solid #353A7E;
- background: #7f82ac;
- text-shadow: none;
- border-radius: 3px;
+ color: #f0f0f0;
+ display: inline-block;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ height: 32px;
+ padding: 4px 8px;
+ margin: 3px;
+ margin-bottom: 0px;
+ margin-right: 2px;
+ padding-top: 0px;
+ background: #8b8b8b;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ vertical-align: top;
+ line-height: 26px;
+ text-align: center;
+ box-shadow: 0px -5px 10px -6px rgb(82,82,82) inset;
+ cursor: pointer;
+ min-width: 34px;
}
.pos .order-button.selected{
font-weight: 900;
- background: #666997;
- color: rgb(236, 237, 255);
+ background: #EEEEEE;
+ color: rgb(75,75,75);
+ height: 31px;
+ border-bottom: solid 1px rgb(196, 196, 196);
+ box-shadow: none;
+}
+
+.pos .order-button .order-sequence{
+ font-size: 16px;
+ font-weight: 800;
+ vertical-align: middle;
+}
+.pos .order-button.selected .order-sequence{
+ color: white;
+ background: black;
+ display: inline-block;
+ line-height: 24px;
+ min-width: 24px;
+ border-radius: 12px;
+ margin-right: 4px;
+ margin-left: -4px;
}
.pos .order-button.square{
- width: 32px;
- margin-left:4px;
+ width: 34px;
+ margin-left:1px;
+ background: #5c5c5c;
+ color: rgb(160,160,160);
+}
+.pos .order-button:not(.square) > .fa {
+ font-size: 16px;
+ vertical-align: middle;
+ margin-right: 4px;
+}
+.pos .order-button .order-sequence{
+ font-size: 16px;
+ font-weight: 800;
}
.pos .order-selector {
@@ -420,6 +461,46 @@ td {
background: #F0EEEE;
white-space: nowrap;
}
+/* ********* The control buttons ********* */
+
+.pos .control-buttons {
+ padding-top: 8px;
+}
+.pos .control-button {
+ background: #e2e2e2;
+ border: solid 1px #BEBEBE;
+ display: inline-block;
+ line-height: 38px;
+ min-width: 38px;
+ text-align: center;
+ border-radius: 3px;
+ padding: 0px 10px;
+ font-size: 18px;
+ margin-left: 6px;
+ cursor: pointer;
+}
+.pos .control-button .fa{
+ margin-right: 4px;
+}
+
+.pos .control-button.highlight{
+ background: #6EC89B;
+ border: solid 1px #6EC89B;
+ color: white;
+}
+.pos .control-button:active {
+ background: #7F82AC;
+ border: solid 1px #7F82AC;
+ color: white;
+}
+.pos .control-button.disabled,
+.pos .control-button.disabled:active{
+ background: #e2e2e2;
+ border: solid 1px #BEBEBE;
+ opacity: 0.5;
+ cursor: default;
+ color: inherit;
+}
/* ********* The paypad contains the payment buttons ********* */
@@ -782,6 +863,78 @@ td {
color: #5a5a5a;
}
+/* a) Generic Screen Layout Constructs */
+
+.screen .screen-content{
+ margin: 0px auto;
+ max-width: 1024px;
+ text-align: left;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+.screen .top-content{
+ position: absolute;
+ left: 0px; top: 0px; right: 0px;
+ height: 64px;
+ border-bottom: dashed 1px rgb(215,215,215);
+ text-align: center;
+}
+.screen .top-content .button {
+ position: absolute;
+ top: 0px;
+ line-height: 32px;
+ padding: 3px 13px;
+ font-size: 20px;
+ background: rgb(230, 230, 230);
+ margin: 12px;
+ border-radius: 3px;
+ border: solid 1px rgb(209, 209, 209);
+ cursor: pointer;
+}
+.screen .top-content .button.highlight{
+ background: rgb(110,200,155);
+ color: white;
+ border: solid 1px rgb(110,200,155);
+}
+.screen .top-content .button.back {
+ left: 0px;
+ margin-left: 16px;
+}
+.screen .top-content .button.next{
+ right: 0px;
+ margin-right: 16px;
+}
+.screen .left-content{
+ position: absolute;
+ left:0px; top: 64px; bottom: 0px;
+ right:50%;
+ overflow-x: hidden;
+ overflow-y: auto;
+ border-right: dashed 1px rgb(215,215,215);
+}
+.screen .right-content{
+ position: absolute;
+ right:0px; top: 64px; bottom: 0px;
+ left:50%;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+.screen .centered-content{
+ position: absolute;
+ right:25%; top: 64px; bottom: 0px;
+ left:25%;
+ border-right: dashed 1px rgb(215,215,215);
+ border-left: dashed 1px rgb(215,215,215);
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+.screen .full-content{
+ position: absolute;
+ right: 0%; top: 65px; bottom: 0px;
+ left: 0%;
+}
+
/* a) Layout for the Product Screen */
.pos .screen .layout-table {
@@ -995,117 +1148,145 @@ td {
/* d) The Scale screen */
-.pos .scale-screen .display{
- position:relative;
- width: 550px;
- height:190px;
- margin-top: 100px;
- margin-left: auto;
- margin-right: auto;
-}
-
-.pos .scale-screen .product-picture {
- position: relative;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- -ms-box-sizing: border-box;
- box-sizing: border-box;
- display: float;
- float: right;
- margin: 5px;
- width: 180px;
- height:180px;
- line-height:180px;
- cursor:pointer;
-
- background:#fff;
- border: 1px solid #fff;
- border-radius: 3px;
- box-shadow: 0px 2px rgb(228, 228, 228);
-}
-
-.pos .scale-screen .product-picture img{
- max-width: 178px;
- max-height: 178px;
- vertical-align: middle;
- cursor:pointer;
-}
-
-.pos .scale-screen .product-picture .product-price{
- position: absolute;
- top:8px;
- right:8px;
- width:auto;
- height:auto;
- line-height:1;
- color:white;
- background: #7f82ac;
- padding: 4px 5px;
- border-radius: 3px;
- cursor:pointer;
-}
-
-.pos .scale-screen .product-name {
- position: absolute;
- left: 0px;
- right: 210px;
- top: 10px;
- height: 50px;
- font-size: 36px;
- line-height: 50px;
- text-align: right;
- color: #8d8d8d;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-.pos .scale-screen .weight{
- position: absolute;
- left: 0;
- top: 80px;
- height: 90px;
- bottom: 55px;
- right: 210px;
- padding: 6px;
- padding-right: 33px;
- padding-top: 11px;
- background: white;
- border-radius: 3px;
- box-shadow: 0px 2px rgb(228, 228, 228) inset;
-}
-.pos .scale-screen .weight p{
+.pos .scale-screen .product-price{
+ font-size: 25px;
+ margin: 16px;
+ text-align: center;
display: inline-block;
- text-align:right;
- line-height: 90px;
- font-size: 56px;
- width: 100%;
- height: 100%;
- margin: 0;
- margin-right: 18px;
- font-family: "Inconsolata";
- color: #6c6c6c;
+ width: 40%;
+}
+.pos .scale-screen .computed-price{
+ font-size: 25px;
+ display: inline-block;
+ text-align: right;
+ margin: 16px;
+ margin-top: 0px;
+ padding: 16px;
+ background: white;
+ width: 40%;
+ border-radius: 3px;
+ font-family: Inconsolata;
+ font-weight: bold;
+ text-shadow: 0px 2px 0px rgb(210,210,210);
+ box-shadow: 0px 2px 0px rgb(225,225,225) inset;
+ float: right;
+}
+.pos .scale-screen .buy-product{
+ text-align: center;
+ font-size: 32px;
+ background: rgb(110,200,155);
+ color: white;
+ border-radius: 3px;
+ padding: 16px;
+ margin: 16px;
+ cursor: pointer;
}
-/* e) The Welcome Screen */
-.pos .goodbye-message{
- position: absolute;
- left:50%;
- top:40%;
- width:500px;
- height:400px;
- margin-left: -250px;
- margin-top: -200px;
- padding:10px;
- padding-top:20px;
- text-align:center;
- font-size:20px;
- font-weight:bold;
- background-color: #F0EEEE;
- border: 1px solid #E0DDDD;
+.pos .scale-screen .weight{
+ text-align: right;
+ margin: 16px;
+ background: white;
+ padding: 20px;
+ padding-right: 30px;
+ font-size: 56px;
border-radius: 3px;
- box-shadow: 0px 10px 20px rgba(0,0,0,0.4);
- z-index:1150;
+ font-family: Inconsolata;
+ text-shadow: 0px 2px 0px rgb(210, 210, 210);
+ box-shadow: 0px 2px 0px rgb(225,225,225) inset;
}
+
+/* e) The Client List Screen */
+
+.pos .clientlist-screen .client-list{
+ font-size: 16px;
+ width: 100%;
+ line-height: 40px;
+}
+.pos .clientlist-screen .client-list th,
+.pos .clientlist-screen .client-list td {
+ padding: 0px 8px;
+}
+.pos .clientlist-screen .client-list tr{
+ transition: all 150ms linear;
+ background: rgb(230,230,230);
+}
+.pos .clientlist-screen .client-list thead > tr,
+.pos .clientlist-screen .client-list tr:nth-child(even) {
+ background: rgb(247,247,247);
+}
+.pos .clientlist-screen .client-list tr.highlight{
+ transition: all 150ms linear;
+ background: rgb(110,200,155) !important;
+ color: white;
+}
+.pos .clientlist-screen .client-list tr.lowlight{
+ transition: all 150ms linear;
+ background: rgb(216, 238, 227);
+}
+.pos .clientlist-screen .client-list tr.lowlight:nth-child(even){
+ transition: all 150ms linear;
+ background: rgb(227, 246, 237);
+}
+.pos .clientlist-screen .client-details{
+ padding: 16px;
+ border-bottom: solid 5px rgb(110,200,155);
+}
+.pos .clientlist-screen .client-picture{
+ height: 64px;
+ width: 64px;
+ border-radius: 32px;
+ overflow: hidden;
+ text-align: center;
+ float: left;
+ margin-right: 16px;
+ background: white;
+}
+.pos .clientlist-screen .client-picture > img{
+ vertical-align: middle;
+ max-height: 64px;
+}
+.pos .clientlist-screen .client-name{
+ font-size: 32px;
+ line-height: 64px;
+ margin-bottom:16px;
+}
+.pos .clientlist-screen .client-details-box{
+ position: relative;
+ font-size: 16px;
+}
+.pos .clientlist-screen .client-details-left{
+ width: 50%;
+ float: left;
+}
+.pos .clientlist-screen .client-details-right{
+ width: 50%;
+ float: right;
+}
+.pos .clientlist-screen .client-detail{
+ line-height: 24px;
+}
+.pos .clientlist-screen .client-detail > .label{
+ font-weight: bold;
+ display: inline-block;
+ width: 64px;
+ text-align: right;
+ margin-right: 8px;
+}
+.pos .clientlist-screen .client-detail > .empty{
+ opacity: 0.3;
+}
+.pos .clientlist-screen .searchbox{
+ right: auto;
+ margin-left: -90px;
+ margin-top:8px;
+ left: 50%;
+}
+.pos .clientlist-screen .searchbox input{
+ width: 120px;
+}
+
+
/* ********* The OrderWidget ********* */
.pos .order-container{
@@ -1119,11 +1300,17 @@ td {
.pos .order-scroller{
width:100%;
height:100%;
- overflow:hidden;
+ overflow: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
+.pos .scrollable-y{
+ overflow: hidden !important;
+ overflow-y: auto !important;
+ -webkit-overflow-scrolling: touch !important;
+}
+
.pos .order{
background: white;
padding-bottom:15px;
@@ -1135,6 +1322,8 @@ td {
font-size:16px;
border-radius: 3px;
border: solid 1px rgb(220,220,220);
+ text-align: left;
+ max-width: 500px;
-webkit-transform: translate3d(0,0,0);
}
@@ -1172,6 +1361,25 @@ td {
color:#999;
}
+.pos .order .summary .fidpoints{
+ position: absolute;
+ left: 20px;
+ padding: 10px;
+ color: #6EC89B;
+ background: rgba(110, 200, 155, 0.17);
+ border-radius: 3px;
+}
+
+.submit-kitchen-button {
+ float: left;
+ background: rgb(61, 235, 82);
+ color: white;
+ padding: 12px 20px;
+ margin: 0px 15px;
+ border-radius: 3px;
+ cursor: pointer;
+}
+
/* ********* The OrderLineWidget ********* */
.pos .order .orderline{
@@ -1231,6 +1439,50 @@ td {
font-style:normal;
}
+/* ********* SplitBill ********* */
+
+.splitbill-screen .order-info {
+ text-align: center;
+ margin-bottom:20px;
+ padding: 20px 0px;
+ font-size: 64px;
+ color: #43996E;
+ text-shadow: 0px 2px white, 0px 2px 2px rgba(0, 0, 0, 0.27);
+ border-bottom: dashed 1px rgb(215,215,215);
+}
+.splitbill-screen .order .orderline.selected{
+ background: rgb(110,200,155);
+ color: white;
+ -webkit-transition: background 250ms ease-in-out;
+ -moz-transition: background 250ms ease-in-out;
+ transition: background 250ms ease-in-out;
+ cursor: default;
+}
+.splitbill-screen .order .orderline.partially.selected{
+ background: rgb(136, 214, 176);
+}
+.splitbill-screen .order .orderline.selected .info-list {
+ color: white;
+}
+.splitbill-screen .order .orderline.selected .info-list em{
+ color: white;
+ font-size: 24px;
+ vertical-align: top;
+}
+.splitbill-screen .paymentmethods {
+ margin: 16px;
+}
+.splitbill-screen .paymentmethod {
+ background: rgb(221, 221, 221);
+ line-height: 40px;
+ margin-bottom: 4px;
+ border-radius: 3px;
+ font-size: 16px;
+ border: solid 1px rgb(202, 202, 202);
+ cursor: pointer;
+ text-align: center;
+}
+
/* ********* The ActionBarWidget ********* */
.pos .pos-actionbar{
@@ -1408,6 +1660,11 @@ td {
box-shadow: 0px 10px 20px rgba(0,0,0,0.4);
z-index:1200;
}
+.pos .popup .comment{
+ font-weight: normal;
+ font-size: 18px;
+ margin: 0px 16px;
+}
.pos .popup .footer{
position:absolute;
bottom:0;
diff --git a/addons/point_of_sale/static/src/img/logo.png b/addons/point_of_sale/static/src/img/logo.png
index 9cf5eb05312..cf6daa51318 100644
Binary files a/addons/point_of_sale/static/src/img/logo.png and b/addons/point_of_sale/static/src/img/logo.png differ
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
index 1057d774806..38230fb0ca2 100644
--- a/addons/point_of_sale/static/src/js/db.js
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -21,6 +21,11 @@ function openerp_pos_db(instance, module){
this.product_by_category_id = {};
this.product_by_reference = {};
+ this.partners_sorted = [];
+ this.partner_by_id = {};
+ this.partner_by_ean13 = {};
+ this.partner_search_string = "";
+
this.category_by_id = {};
this.root_category_id = 0;
this.category_products = {};
@@ -196,6 +201,60 @@ function openerp_pos_db(instance, module){
}
}
},
+ _partner_search_string: function(partner){
+ var str = '' + partner.id + ':' + partner.name;
+ if(partner.ean13){
+ str += '|' + partner.ean13;
+ }
+ if(partner.address){
+ str += '|' + partner.address;
+ }
+ return str + '\n';
+ },
+ add_partners: function(partners){
+ for(var i = 0, len = partners.length; i < len; i++){
+ var partner = partners[i];
+ this.partner_by_id[partner.id] = partner;
+ if(partner.ean13){
+ this.partner_by_ean13[partner.ean13] = partner;
+ }
+ partner.address = (partner.street || '') +', '+
+ (partner.zip || '') +' '+
+ (partner.city || '') +', '+
+ (partner.country_id[1] || '');
+ this.partner_search_string += this._partner_search_string(partner);
+ this.partners_sorted.push(partner);
+ }
+ },
+ get_partner_by_id: function(id){
+ return this.partner_by_id[id];
+ },
+ get_partner_by_ean13: function(ean13){
+ return this.partner_by_ean13[ean13];
+ },
+ get_partners_sorted: function(){
+ return this.partners_sorted;
+ },
+ search_partner: function(query){
+ try {
+ query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.');
+ query = query.replace(' ','.+');
+ var re = RegExp("([0-9]+):.*?"+query,"gi");
+ }catch(e){
+ return [];
+ }
+ var results = [];
+ for(var i = 0; i < this.limit; i++){
+ r = re.exec(this.partner_search_string);
+ if(r){
+ var id = Number(r[1]);
+ results.push(this.get_partner_by_id(id));
+ }else{
+ break;
+ }
+ }
+ return results;
+ },
/* removes all the data from the database. TODO : being able to selectively remove data */
clear: function(stores){
for(var i = 0, len = arguments.length; i < len; i++){
@@ -243,7 +302,13 @@ function openerp_pos_db(instance, module){
* - a name, package or ean13 containing the query (case insensitive)
*/
search_product_in_category: function(category_id, query){
- var re = RegExp("([0-9]+):.*?"+query,"gi");
+ try {
+ query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.');
+ query = query.replace(' ','.+');
+ var re = RegExp("([0-9]+):.*?"+query,"gi");
+ }catch(e){
+ return [];
+ }
var results = [];
for(var i = 0; i < this.limit; i++){
r = re.exec(this.category_search_string[category_id]);
diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js
index 2b016445638..23dc8981866 100644
--- a/addons/point_of_sale/static/src/js/devices.js
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -77,6 +77,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
};
+
// this object interfaces with the local proxy to communicate to the various hardware devices
// connected to the Point of Sale. As the communication only goes from the POS to the proxy,
// methods are used both to signal an event, and to fetch information.
@@ -362,28 +363,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.notifications[name].push(callback);
},
- //a product has been scanned and recognized with success
- // ean is a parsed ean object
- scan_item_success: function(ean){
- return this.message('scan_item_success',{ean: ean});
- },
-
- // a product has been scanned but not recognized
- // ean is a parsed ean object
- scan_item_error_unrecognized: function(ean){
- return this.message('scan_item_error_unrecognized',{ean: ean});
- },
-
- //the client is asking for help
- help_needed: function(){
- return this.message('help_needed');
- },
-
- //the client does not need help anymore
- help_canceled: function(){
- return this.message('help_canceled');
- },
-
// returns the weight on the scale.
scale_read: function(){
var self = this;
@@ -411,71 +390,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.debug_weight = 0;
},
-
- // the pos asks the client to pay 'price' units
- payment_request: function(price){
- var ret = new $.Deferred();
- this.paying = true;
- this.custom_payment_status = this.default_payment_status;
- return this.message('payment_request',{'price':price});
- },
-
- payment_status: function(){
- if(this.bypass_proxy){
- this.bypass_proxy = false;
- return (new $.Deferred()).resolve(this.custom_payment_status);
- }else{
- return this.message('payment_status');
- }
- },
-
- // override what the proxy says and accept the payment
- debug_accept_payment: function(){
- this.bypass_proxy = true;
- this.custom_payment_status = {
- status: 'paid',
- message: 'Successfull Payment, have a nice day',
- payment_method: 'AMEX',
- receipt_client: 'bla',
- receipt_shop: 'bla',
- };
- },
-
- // override what the proxy says and reject the payment
- debug_reject_payment: function(){
- this.bypass_proxy = true;
- this.custom_payment_status = {
- status: 'error-rejected',
- message: 'Sorry you don\'t have enough money :(',
- };
- },
- // the client cancels his payment
- payment_cancel: function(){
- this.paying = false;
- this.custom_payment_status = 'waiting_for_payment';
- return this.message('payment_cancel');
- },
-
- // called when the client logs in or starts to scan product
- transaction_start: function(){
- return this.message('transaction_start');
- },
-
- // called when the clients has finished his interaction with the machine
- transaction_end: function(){
- return this.message('transaction_end');
- },
-
- // called when the POS turns to cashier mode
- cashier_mode_activated: function(){
- return this.message('cashier_mode_activated');
- },
-
- // called when the POS turns to client mode
- cashier_mode_deactivated: function(){
- return this.message('cashier_mode_deactivated');
- },
-
// ask for the cashbox (the physical box where you store the cash) to be opened
open_cashbox: function(){
return this.message('open_cashbox');
@@ -509,10 +423,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
return this.message('log',{'arguments': _.toArray(arguments)});
},
- // asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server )
- print_pdf_invoice: function(pdfinvoice){
- return this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice});
- },
});
// this module interfaces with the barcode reader. It assumes the barcode reader
@@ -524,8 +434,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
'product',
'cashier',
'client',
- 'discount',
],
+
init: function(attributes){
this.pos = attributes.pos;
this.action_callback = {};
@@ -535,11 +445,56 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.action_callback_stack = [];
- this.weight_prefix_set = attributes.weight_prefix_set || {'21':''};
- this.discount_prefix_set = attributes.discount_prefix_set || {'22':''};
- this.price_prefix_set = attributes.price_prefix_set || {'23':''};
- this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''};
- this.client_prefix_set = attributes.client_prefix_set || {'042':''};
+ this.add_barcode_patterns(attributes.patterns || {
+ 'product': ['xxxxxxxxxxxxx'],
+ 'cashier': ['041xxxxxxxxxx'],
+ 'client': ['042xxxxxxxxxx'],
+ 'weight': ['21xxxxxNNDDDx'],
+ 'discount': ['22xxxxxxxxNNx'],
+ 'price': ['23xxxxxNNNDDx'],
+ });
+
+ },
+
+ add_barcode_patterns: function(patterns){
+ this.patterns = this.patterns || {};
+ for(type in patterns){
+ this.patterns[type] = this.patterns[type] || [];
+
+ var patternlist = patterns[type];
+ if( typeof patternlist === 'string'){
+ patternlist = patternlist.split(',');
+ }
+ for(var i = 0; i < patternlist.length; i++){
+ var pattern = patternlist[i].trim().substring(0,12);
+ if(!pattern.length){
+ continue;
+ }
+ pattern = pattern.replace(/[x\*]/gi,'x');
+ while(pattern.length < 12){
+ pattern += 'x';
+ }
+ this.patterns[type].push(pattern);
+ }
+ }
+
+ this.sorted_patterns = [];
+ for (var type in this.patterns){
+ var patterns = this.patterns[type];
+ for(var i = 0; i < patterns.length; i++){
+ var pattern = patterns[i];
+ var score = 0;
+ for(var j = 0; j < pattern.length; j++){
+ if(pattern[j] != 'x'){
+ score++;
+ }
+ }
+ this.sorted_patterns.push({type:type, pattern:pattern,score:score});
+ }
+ }
+ this.sorted_patterns.sort(function(a,b){
+ return b.score - a.score;
+ });
},
@@ -625,67 +580,84 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
// most importantly :
// - code : the ean
// - type : the type of the ean:
- // 'price' | 'weight' | 'unit' | 'cashier' | 'client' | 'discount' | 'error'
+ // 'price' | 'weight' | 'product' | 'cashier' | 'client' | 'discount' | 'error'
//
- // - prefix : the prefix that has ben used to determine the type
- // - id : the part of the ean that identifies something
// - value : if the id encodes a numerical value, it will be put there
- // - unit : if the encoded value has a unit, it will be put there.
- // not to be confused with the 'unit' type, which represent an unit of a
- // unique product
// - base_code : the ean code with all the encoding parts set to zero; the one put on
// the product in the backend
parse_ean: function(ean){
+ var self = this;
var parse_result = {
encoding: 'ean13',
- type:'unknown',
- prefix:'',
+ type:'error',
code:ean,
base_code: ean,
- id:'',
value: 0,
- unit: 'none',
};
- function match_prefix(prefix_set, type){
- for(prefix in prefix_set){
- if(ean.substring(0,prefix.length) === prefix){
- parse_result.prefix = prefix;
- parse_result.type = type;
- return true;
- }
- }
- return false;
+ if (!this.check_ean(ean)){
+ return parse_result;
}
- if (!this.check_ean(ean)){
- parse_result.type = 'error';
- } else if( match_prefix(this.price_prefix_set,'price')){
- parse_result.id = ean.substring(0,7);
- parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
- parse_result.value = Number(ean.substring(7,12))/100.0;
- parse_result.unit = 'euro';
- } else if( match_prefix(this.weight_prefix_set,'weight')){
- parse_result.id = ean.substring(0,7);
- parse_result.value = Number(ean.substring(7,12))/1000.0;
- parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
- parse_result.unit = 'Kg';
- } else if( match_prefix(this.client_prefix_set,'client')){
- parse_result.id = ean.substring(0,7);
- parse_result.unit = 'Kg';
- } else if( match_prefix(this.cashier_prefix_set,'cashier')){
- parse_result.id = ean.substring(0,7);
- } else if( match_prefix(this.discount_prefix_set,'discount')){
- parse_result.id = ean.substring(0,7);
- parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
- parse_result.value = Number(ean.substring(7,12))/100.0;
- parse_result.unit = '%';
- } else {
- parse_result.type = 'unit';
- parse_result.prefix = '';
- parse_result.id = ean;
+ function is_number(char){
+ n = char.charCodeAt(0);
+ return n >= 48 && n <= 57;
}
+
+ function match_pattern(ean,pattern){
+ for(var i = 0; i < pattern.length; i++){
+ var p = pattern[i];
+ var e = ean[i];
+ if( is_number(p) && p !== e ){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function get_value(ean,pattern){
+ var value = 0;
+ var decimals = 0;
+ for(var i = 0; i < pattern.length; i++){
+ var p = pattern[i];
+ var v = parseInt(ean[i]);
+ if( p === 'N'){
+ value *= 10;
+ value += v;
+ }else if( p === 'D'){
+ decimals += 1;
+ value += v * Math.pow(10,-decimals);
+ }
+ }
+ return value;
+ }
+
+ function get_basecode(ean,pattern){
+ var base = '';
+ for(var i = 0; i < pattern.length; i++){
+ var p = pattern[i];
+ var v = ean[i];
+ if( p === 'x' || is_number(p)){
+ base += v;
+ }else{
+ base += '0';
+ }
+ }
+ return self.sanitize_ean(base);
+ }
+
+ var patterns = this.sorted_patterns;
+
+ for(var i = 0; i < patterns.length; i++){
+ if(match_pattern(ean,patterns[i].pattern)){
+ parse_result.type = patterns[i].type;
+ parse_result.value = get_value(ean,patterns[i].pattern);
+ parse_result.base_code = get_basecode(ean,patterns[i].pattern);
+ return parse_result;
+ }
+ }
+
return parse_result;
},
@@ -702,21 +674,18 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
}else if(this.pos.db.get_product_by_reference(code)){
var parse_result = {
encoding: 'reference',
- type: 'unit',
+ type: 'product',
code: code,
- prefix: '',
};
}else{
var parse_result = {
encoding: 'error',
type: 'error',
code: code,
- prefix: '',
};
- return;
}
- if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
+ if(parse_result.type in {'product':'', 'weight':'', 'price':'', 'discount':''}){ //ean is associated to a product
if(this.action_callback['product']){
this.action_callback['product'](parse_result);
}
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 94a23042df6..588c54f4be9 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -24,7 +24,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.pos_widget = attributes.pos_widget;
this.proxy = new module.ProxyDevice(this); // used to communicate to the hardware devices via a local proxy
- this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy}); // used to read barcodes
+ this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy, patterns: {}}); // used to read barcodes
this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy
this.db = new module.PosDB(); // a local database used to search trough products and categories & store pending orders
this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
@@ -34,6 +34,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.company_logo = null;
this.company_logo_base64 = '';
this.currency = null;
+ this.shop = null;
this.company = null;
this.user = null;
this.users = [];
@@ -47,6 +48,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.units = [];
this.units_by_id = {};
this.pricelist = null;
+ this.order_sequence = 1;
+ this.fidelity = null;
window.posmodel = this;
// these dynamic attributes can be watched for change by other models or widgets
@@ -118,6 +121,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.pos_widget.loading_message(_t('Loading')+' '+model,this._load_progress);
return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
},
+
// loads all the needed data on the sever. returns a deferred indicating when all the data has loaded.
load_server_data: function(){
var self = this;
@@ -148,6 +152,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
var units_by_id = {};
for(var i = 0, len = units.length; i < len; i++){
units_by_id[units[i].id] = units[i];
+ units[i].groupable = ( units[i].category_id[0] === 1 );
+ units[i].is_unit = ( units[i].id === 1 );
}
self.units_by_id = units_by_id;
@@ -155,9 +161,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}).then(function(users){
self.users = users;
- return self.fetch('res.partner', ['name','ean13'], [['ean13', '!=', false]]);
+ return self.fetch('res.partner', ['name','street','city','country_id','phone','zip','mobile','email','ean13','fidpoints']);
}).then(function(partners){
self.partners = partners;
+ self.db.add_partners(partners);
return self.fetch('account.tax', ['name','amount', 'price_include', 'type']);
}).then(function(taxes){
@@ -165,23 +172,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return self.fetch(
'pos.session',
- ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
+ ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number'],
[['state', '=', 'opened'], ['user_id', '=', self.session.uid]]
);
}).then(function(pos_sessions){
self.pos_session = pos_sessions[0];
- return self.fetch(
- 'pos.config',
- ['name','journal_ids','journal_id','pricelist_id',
- 'iface_self_checkout', 'iface_led', 'iface_cashdrawer',
- 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan',
- 'iface_vkeyboard','iface_print_via_proxy','iface_scan_via_proxy',
- 'iface_cashdrawer','iface_invoicing','iface_big_scrollbars',
- 'receipt_header','receipt_footer','proxy_ip',
- 'state','sequence_id','session_ids'],
- [['id','=', self.pos_session.config_id[0]]]
- );
+ return self.fetch('pos.config',[],[['id','=', self.pos_session.config_id[0]]]);
}).then(function(configs){
self.config = configs[0];
self.config.use_proxy = self.config.iface_payment_terminal ||
@@ -189,6 +186,18 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
self.config.iface_print_via_proxy ||
self.config.iface_scan_via_proxy ||
self.config.iface_cashdrawer;
+
+ self.barcode_reader.add_barcode_patterns({
+ 'product': self.config.barcode_product,
+ 'cashier': self.config.barcode_cashier,
+ 'client': self.config.barcode_customer,
+ 'weight': self.config.barcode_weight,
+ 'discount': self.config.barcode_discount,
+ 'price': self.config.barcode_price,
+ });
+ return self.fetch('stock.location',[],[['id','=', self.config.stock_location_id[0]]]);
+ }).then(function(shops){
+ self.shop = shops[0];
return self.fetch('product.pricelist',['currency_id'],[['id','=',self.config.pricelist_id[0]]]);
}).then(function(pricelists){
@@ -198,13 +207,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}).then(function(currencies){
self.currency = currencies[0];
- /*
- return (new instance.web.Model('decimal.precision')).call('get_precision',[['Account']]);
- }).then(function(precision){
- self.accounting_precision = precision;
- console.log("PRECISION",precision);
-*/
- return self.fetch('product.packaging',['ean','product_tmpl_id']);
+ return self.fetch('pos.fidelity',[],[['id','=',self.config.fidelity_id[0] || -1]]);
+ }).then(function(fidelity){
+ if(fidelity.length){
+ self.fidelity = fidelity[0];
+ }
+
+ return self.fetch('product.packaging',['ean','product_id']);
}).then(function(packagings){
self.db.add_packagings(packagings);
@@ -292,7 +301,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// this is called when an order is removed from the order collection. It ensures that there is always an existing
// order and a valid selected order
on_removed_order: function(removed_order,index,reason){
- if(reason === 'abandon' && this.get('orders').size() > 0){
+ if( (reason === 'abandon' || removed_order.temporary) && this.get('orders').size() > 0){
// when we intentionally remove an unfinished order, and there is another existing one
this.set({'selectedOrder' : this.get('orders').at(index) || this.get('orders').last()});
}else{
@@ -309,6 +318,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.set('selectedOrder', order);
},
+ get_order: function(){
+ return this.get('selectedOrder');
+ },
+
//removes the current order
delete_current_order: function(){
this.get('selectedOrder').destroy({'reason':'abandon'});
@@ -497,6 +510,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
selectedOrder.addProduct(product, {price:parsed_code.value});
}else if(parsed_code.type === 'weight'){
selectedOrder.addProduct(product, {quantity:parsed_code.value, merge:false});
+ }else if(parsed_code.type === 'discount'){
+ selectedOrder.addProduct(product, {discount:parsed_code.value, merge:false});
}else{
selectedOrder.addProduct(product);
}
@@ -504,6 +519,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
},
});
+ var orderline_id = 1;
+
// An orderline represent one element of the content of a client's shopping cart.
// An orderline contains a product, its quantity, its price, discount. etc.
// An Order contains zero or more Orderlines.
@@ -519,6 +536,21 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.discountStr = '0';
this.type = 'unit';
this.selected = false;
+ this.id = orderline_id++;
+ },
+ clone: function(){
+ var orderline = new module.Orderline({},{
+ pos: this.pos,
+ order: null,
+ product: this.product,
+ price: this.price,
+ });
+ orderline.quantity = this.quantity;
+ orderline.quantityStr = this.quantityStr;
+ orderline.discount = this.discount;
+ orderline.type = this.type;
+ orderline.selected = false;
+ return orderline;
},
// sets a discount [0,100]%
set_discount: function(discount){
@@ -566,7 +598,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
},
get_quantity_str_with_unit: function(){
var unit = this.get_unit();
- if(unit && unit.name !== 'Unit(s)'){
+ if(unit && !unit.is_unit){
return this.quantityStr + ' ' + unit.name;
}else{
return this.quantityStr;
@@ -602,6 +634,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
can_be_merged_with: function(orderline){
if( this.get_product().id !== orderline.get_product().id){ //only orderline of the same product can be merged
return false;
+ }else if(!this.get_unit() || !this.get_unit().groupable){
+ return false;
}else if(this.get_product_type() !== orderline.get_product_type()){
return false;
}else if(this.get_discount() > 0){ // we don't merge discounted orderlines
@@ -787,11 +821,33 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.selected_paymentline = undefined;
this.screen_data = {}; // see ScreenSelector
this.receipt_type = 'receipt'; // 'receipt' || 'invoice'
+ this.temporary = attributes.temporary || false;
+ this.sequence_number = this.pos.pos_session.sequence_number++;
return this;
},
+ is_empty: function(){
+ return (this.get('orderLines').models.length === 0);
+ },
generateUniqueId: function() {
return new Date().getTime();
},
+ addOrderline: function(line){
+ if(line.order){
+ order.removeOrderline(line);
+ }
+ line.order = this;
+ this.get('orderLines').add(line);
+ this.selectLine(this.getLastOrderline());
+ },
+ getWonFidpoints: function(){
+ if(!this.pos.fidelity){
+ return 0;
+ }else{
+ return round_di(this.getTotalTaxIncluded() * this.pos.fidelity.fidpoints_currency +
+ this.get('orderLines').models.length * this.pos.fidelity.fidpoints_product +
+ this.pos.fidelity.fidpoints_sale,0);
+ }
+ },
addProduct: function(product, options){
options = options || {};
var attr = JSON.parse(JSON.stringify(product));
@@ -805,6 +861,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
if(options.price !== undefined){
line.set_unit_price(options.price);
}
+ if(options.discount !== undefined){
+ line.set_discount(options.discount);
+ }
var last_orderline = this.getLastOrderline();
if( last_orderline && last_orderline.can_be_merged_with(line) && options.merge !== false){
@@ -818,6 +877,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.get('orderLines').remove(line);
this.selectLine(this.getLastOrderline());
},
+ getOrderline: function(id){
+ var orderlines = this.get('orderLines').models;
+ for(var i = 0; i < orderlines.length; i++){
+ if(orderlines[i].id === id){
+ return orderlines[i];
+ }
+ }
+ return null;
+ },
getLastOrderline: function(){
return this.get('orderLines').at(this.get('orderLines').length -1);
},
@@ -950,6 +1018,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
var client = this.get('client');
var cashier = this.pos.cashier || this.pos.user;
var company = this.pos.company;
+ var shop = this.pos.shop;
var date = new Date();
return {
@@ -969,6 +1038,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
cashier: cashier ? cashier.name : null,
header: this.pos.config.receipt_header || '',
footer: this.pos.config.receipt_footer || '',
+ fidpoints: this.getWonFidpoints(),
precision: {
price: 2,
money: 2,
@@ -994,6 +1064,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
phone: company.phone,
logo: this.pos.company_logo_base64,
},
+ shop:{
+ name: shop.name,
+ },
currency: this.pos.currency,
};
},
@@ -1019,6 +1092,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
partner_id: this.get_client() ? this.get_client().id : false,
user_id: this.pos.cashier ? this.pos.cashier.id : this.pos.user.id,
uid: this.uid,
+ sequence_number: this.sequence_number,
+ fidpoints: this.getWonFidpoints(),
};
},
getSelectedLine: function(){
@@ -1102,7 +1177,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}
},
switchSign: function() {
- console.log('switchsing');
var oldBuffer;
oldBuffer = this.get('buffer');
this.set({
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 71fdb3a5609..db41cd8988a 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -29,12 +29,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.popup_set = options.popup_set || {};
- this.default_client_screen = options.default_client_screen;
- this.default_cashier_screen = options.default_cashier_screen;
+ this.default_screen = options.default_screen;
this.current_popup = null;
- this.current_mode = options.default_mode || 'client';
+ this.current_mode = options.default_mode || 'cashier';
this.current_screen = null;
@@ -46,10 +45,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.popup_set[popup_name].hide();
}
- this.selected_order = this.pos.get('selectedOrder');
- this.selected_order.set_screen_data({
- client_screen: this.default_client_screen,
- cashier_screen: this.default_cashier_screen,
+ this.pos.get('selectedOrder').set_screen_data({
+ 'screen': this.default_screen,
});
this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
@@ -59,12 +56,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.screen_set[screen_name] = screen;
return this;
},
- show_popup: function(name){
+ show_popup: function(name,options){
if(this.current_popup){
this.close_popup();
}
this.current_popup = this.popup_set[name];
- this.current_popup.show();
+ this.current_popup.show(options);
},
close_popup: function(){
if(this.current_popup){
@@ -75,15 +72,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
load_saved_screen: function(){
this.close_popup();
-
var selectedOrder = this.pos.get('selectedOrder');
+ // this.set_current_screen(selectedOrder.get_screen_data('screen') || this.default_screen,null,'refresh');
+ this.set_current_screen(this.default_screen,null,'refresh');
- if(this.current_mode === 'client'){
- this.set_current_screen(selectedOrder.get_screen_data('client_screen') || this.default_client_screen,null,'refresh');
- }else if(this.current_mode === 'cashier'){
- this.set_current_screen(selectedOrder.get_screen_data('cashier_screen') || this.default_cashier_screen,null,'refresh');
- }
- this.selected_order = selectedOrder;
},
set_user_mode: function(user_mode){
if(user_mode !== this.current_mode){
@@ -102,20 +94,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}
this.close_popup();
- var selectedOrder = this.pos.get('selectedOrder');
- if(this.current_mode === 'client'){
- selectedOrder.set_screen_data('client_screen',screen_name);
- if(params){
- selectedOrder.set_screen_data('client_screen_params',params);
- }
- }else{
- selectedOrder.set_screen_data('cashier_screen',screen_name);
- if(params){
- selectedOrder.set_screen_data('cashier_screen_params',params);
- }
+
+ var order = this.pos.get('selectedOrder');
+ var old_screen_name = order.get_screen_data('screen');
+
+ order.set_screen_data('screen',screen_name);
+
+ if(params){
+ order.set_screen_data('params',params);
}
- if(screen && (refresh || screen !== this.current_screen)){
+ if( screen_name !== old_screen_name ){
+ order.set_screen_data('previous-screen',old_screen_name);
+ }
+
+ if ( refresh || screen !== this.current_screen){
if(this.current_screen){
this.current_screen.close();
this.current_screen.hide();
@@ -124,21 +117,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.current_screen.show();
}
},
- get_current_screen_param: function(param){
- var selected_order = this.pos.get('selectedOrder');
- if(this.current_mode === 'client'){
- var params = selected_order.get_screen_data('client_screen_params');
- }else{
- var params = selected_order.get_screen_data('cashier_screen_params');
- }
- if(params){
- return params[param];
- }else{
- return undefined;
+ get_current_screen: function(){
+ return this.pos.get('selectedOrder').get_screen_data('screen') || this.default_screen;
+ },
+ back: function(){
+ var previous = this.pos.get('selectedOrder').get_screen_data('previous-screen');
+ if(previous){
+ this.set_current_screen(previous);
}
},
+ get_current_screen_param: function(param){
+ var params = this.pos.get('selectedOrder').get_screen_data('params');
+ return params ? params[param] : undefined;
+ },
set_default_screen: function(){
- this.set_current_screen(this.current_mode === 'client' ? this.default_client_screen : this.default_cashier_screen);
+ this.set_current_screen(this.default_screen);
},
});
@@ -157,25 +150,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned
- barcode_product_error_popup: 'error-product', //if defined, this popup will be loaded when there's an error in the popup
hotkeys_handlers: {},
// what happens when a product is scanned :
- // it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if
- // there's an error.
+ // it will add the product to the order and go to barcode_product_screen.
barcode_product_action: function(code){
var self = this;
if(self.pos.scan_product(code)){
- self.pos.proxy.scan_item_success(code);
if(self.barcode_product_screen){
self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
}
}else{
- self.pos.proxy.scan_item_error_unrecognized(code);
- if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
- self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
- }
+ self.pos_widget.screen_selector.show_popup('error-barcode',code.code);
}
},
@@ -189,12 +176,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
if(users[i].ean13 === code.code){
this.pos.cashier = users[i];
this.pos_widget.username.refresh();
- this.pos.proxy.cashier_mode_activated();
- this.pos_widget.screen_selector.set_user_mode('cashier');
return true;
}
}
- this.pos.proxy.scan_item_error_unrecognized(code);
+ this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
return false;
},
@@ -203,30 +188,28 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// - if there's a user with a matching ean, put it as the active 'client' and return true
// - else : return false.
barcode_client_action: function(code){
- var partners = this.pos.partners;
- for(var i = 0, len = partners.length; i < len; i++){
- if(partners[i].ean13 === code.code){
- this.pos.get('selectedOrder').set_client(partners[i]);
- this.pos_widget.username.refresh();
- this.pos.proxy.scan_item_success(code);
- return true;
- }
+ var partner = this.pos.db.get_partner_by_ean13(code.code);
+ if(partner){
+ this.pos.get('selectedOrder').set_client(partner);
+ this.pos_widget.username.refresh();
+ return true;
}
- this.pos.proxy.scan_item_error_unrecognized(code);
+ this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
return false;
- //TODO start the transaction
},
// what happens when a discount barcode is scanned : the default behavior
// is to set the discount on the last order.
barcode_discount_action: function(code){
- this.pos.proxy.scan_item_success(code);
var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
if(last_orderline){
last_orderline.set_discount(code.value)
}
},
-
+ // What happens when an invalid barcode is scanned : shows an error popup.
+ barcode_error_action: function(code){
+ this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
+ },
// shows an action bar on the screen. The actionbar is automatically shown when you add a button
// with add_action_button()
show_action_bar: function(){
@@ -264,33 +247,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.hide_action_bar();
}
- // we add the help button by default. we do this because the buttons are cleared on each refresh so that
- // the button stay local to each screen
- this.pos_widget.left_action_bar.add_new_button({
- label: _t('Help'),
- icon: '/point_of_sale/static/src/img/icons/png48/help.png',
- click: function(){ self.help_button_action(); },
- });
-
var self = this;
- this.cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
- this.pos_widget.set_numpad_visible(this.show_numpad && this.cashier_mode);
+ this.pos_widget.set_numpad_visible(this.show_numpad);
this.pos_widget.set_leftpane_visible(this.show_leftpane);
- this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !this.cashier_mode);
- this.pos_widget.set_cashier_controls_visible(this.cashier_mode);
- if(this.cashier_mode && this.pos.config.iface_self_checkout){
- this.pos_widget.client_button.show();
- }else{
- this.pos_widget.client_button.hide();
- }
- if(this.cashier_mode){
- this.pos_widget.close_button.show();
- }else{
- this.pos_widget.close_button.hide();
- }
-
this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
this.pos.barcode_reader.set_action_callback({
@@ -298,6 +259,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
'product': self.barcode_product_action ? function(code){ self.barcode_product_action(code); } : undefined ,
'client' : self.barcode_client_action ? function(code){ self.barcode_client_action(code); } : undefined ,
'discount': self.barcode_discount_action ? function(code){ self.barcode_discount_action(code); } : undefined,
+ 'error' : self.barcode_error_action ? function(code){ self.barcode_error_action(code); } : undefined,
});
},
@@ -308,7 +270,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos.barcode_reader.reset_action_callbacks();
}
this.pos_widget.action_bar.destroy_buttons();
- this.pos_widget.left_action_bar.destroy_buttons();
},
// this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
@@ -352,21 +313,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
});
- module.HelpPopupWidget = module.PopUpWidget.extend({
- template:'HelpPopupWidget',
- show: function(){
- this._super();
- this.pos.proxy.help_needed();
- var self = this;
-
- this.$el.find('.button').off('click').click(function(){
- self.pos_widget.screen_selector.close_popup();
- });
- },
- close:function(){
- this.pos.proxy.help_canceled();
- },
- });
module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
template:'ChooseReceiptPopupWidget',
@@ -398,42 +344,64 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ErrorPopupWidget = module.PopUpWidget.extend({
template:'ErrorPopupWidget',
- show: function(){
+ show: function(text){
var self = this;
this._super();
- this.pos.proxy.help_needed();
- this.pos.proxy.scan_item_error_unrecognized();
+
+ if( text && (text.message || text.comment) ){
+ this.$('.message').text(text.message);
+ this.$('.comment').text(text.comment);
+ }
this.pos.barcode_reader.save_callbacks();
this.pos.barcode_reader.reset_action_callbacks();
- this.pos.barcode_reader.set_action_callback({
- 'cashier': function(code){
- clearInterval(this.intervalID);
- self.pos.proxy.cashier_mode_activated();
- self.pos_widget.screen_selector.set_user_mode('cashier');
- },
- });
this.$('.footer .button').off('click').click(function(){
self.pos_widget.screen_selector.close_popup();
});
},
close:function(){
this._super();
- this.pos.proxy.help_canceled();
this.pos.barcode_reader.restore_callbacks();
},
});
- module.ProductErrorPopupWidget = module.ErrorPopupWidget.extend({
- template:'ProductErrorPopupWidget',
- });
module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({
template:'ErrorSessionPopupWidget',
});
- module.ErrorNegativePricePopupWidget = module.ErrorPopupWidget.extend({
- template:'ErrorNegativePricePopupWidget',
+ module.ErrorBarcodePopupWidget = module.ErrorPopupWidget.extend({
+ template:'ErrorBarcodePopupWidget',
+ show: function(barcode){
+ this._super();
+ this.$('.barcode').text(barcode);
+ },
+ });
+
+ module.ConfirmPopupWidget = module.PopUpWidget.extend({
+ template: 'ConfirmPopupWidget',
+ show: function(options){
+ var self = this;
+ this._super();
+
+ this.message = options.message || '';
+ this.comment = options.comment || '';
+ this.renderElement();
+
+ this.$('.button.cancel').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ if( options.cancel ){
+ options.cancel.call(self);
+ }
+ });
+
+ this.$('.button.confirm').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ if( options.confirm ){
+ options.confirm.call(self);
+ }
+ });
+ },
});
module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
@@ -443,47 +411,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
template: 'ErrorInvoiceTransferPopupWidget',
});
-
- module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
- template:'ScaleInviteScreenWidget',
-
- next_screen:'scale',
- previous_screen:'products',
-
- show: function(){
- this._super();
- var self = this;
- var queue = this.pos.proxy_queue;
-
- queue.schedule(function(){
- return self.pos.proxy.weighting_start();
- },{ important: true });
-
- queue.schedule(function(){
- return self.pos.proxy.weighting_read_kg().then(function(weight){
- if(weight > 0.001){
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
- }
- });
- },{duration: 100, repeat: true});
-
- this.add_action_button({
- label: _t('Back'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
- }
- });
- },
- close: function(){
- this._super();
- var self = this;
- this.pos.proxy_queue.clear();
- this.pos.proxy_queue.schedule(function(){
- return self.pos.proxy.weighting_end();
- },{ important: true });
- },
- });
module.ScaleScreenWidget = module.ScreenWidget.extend({
template:'ScaleScreenWidget',
@@ -491,6 +418,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
next_screen: 'products',
previous_screen: 'products',
+ show_leftpane: false,
+
show: function(){
this._super();
var self = this;
@@ -510,23 +439,15 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
$('body').on('keyup',this.hotkey_handler);
- this.add_action_button({
- label: _t('Back'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
- }
- });
+ this.$('.back').click(function(){
+ self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
+ });
+
+ this.$('.next,.buy-product').click(function(){
+ self.order_product();
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
+ });
- this.validate_button = this.add_action_button({
- label: _t('Validate'),
- icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
- click: function(){
- self.order_product();
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
- },
- });
-
queue.schedule(function(){
return self.pos.proxy.scale_read().then(function(weight){
self.set_weight(weight.weight);
@@ -534,14 +455,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},{duration:50, repeat: true});
},
- renderElement: function(){
- var self = this;
- this._super();
- this.$('.product-picture').click(function(){
- self.order_product();
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
- });
- },
get_product: function(){
var ss = this.pos_widget.screen_selector;
if(ss){
@@ -563,7 +476,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
set_weight: function(weight){
this.weight = weight;
- this.$('.js-weight').text(this.get_product_weight_string());
+ this.$('.weight').text(this.get_product_weight_string());
+ this.$('.computed-price').text(this.get_computed_price_string());
},
get_product_weight_string: function(){
var product = this.get_product();
@@ -581,13 +495,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
weightstr += ' Kg';
return weightstr;
},
- get_product_image_url: function(){
- var product = this.get_product();
- if(product){
- return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+product.id;
- }else{
- return "";
- }
+ get_computed_price_string: function(){
+ return this.format_currency(this.get_product_price() * this.weight);
},
close: function(){
var self = this;
@@ -598,166 +507,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
});
-
- module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
- template:'ClientPaymentScreenWidget',
-
- next_screen: 'welcome',
- previous_screen: 'products',
-
- show: function(){
- this._super();
- var self = this;
-
- this.queue = new module.JobQueue();
- this.canceled = false;
- this.paid = false;
-
- // initiates the connection to the payment terminal and starts the update requests
- this.start = function(){
- var def = new $.Deferred();
- self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft())
- .done(function(ack){
- if(ack === 'ok'){
- self.queue.schedule(self.update);
- }else if(ack.indexOf('error') === 0){
- console.error('cannot make payment. TODO');
- }else{
- console.error('unknown payment request return value:',ack);
- }
- def.resolve();
- });
- return def;
- };
-
- // gets updated status from the payment terminal and performs the appropriate consequences
- this.update = function(){
- var def = new $.Deferred();
- if(self.canceled){
- return def.resolve();
- }
- self.pos.proxy.payment_status()
- .done(function(status){
- if(status.status === 'paid'){
-
- var currentOrder = self.pos.get('selectedOrder');
-
- //we get the first cashregister marked as self-checkout
- var selfCheckoutRegisters = [];
- for(var i = 0; i < self.pos.cashregisters.length; i++){
- var cashregister = self.pos.cashregisters[i];
- if(cashregister.self_checkout_payment_method){
- selfCheckoutRegisters.push(cashregister);
- }
- }
-
- var cashregister = selfCheckoutRegisters[0] || self.pos.cashregisters[0];
- currentOrder.addPaymentline(cashregister);
- self.pos.push_order(currentOrder)
- currentOrder.destroy();
- self.pos.proxy.transaction_end();
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
- self.paid = true;
- }else if(status.status.indexOf('error') === 0){
- console.error('error in payment request. TODO');
- }else if(status.status === 'waiting'){
- self.queue.schedule(self.update,200);
- }else{
- console.error('unknown status value:',status.status);
- }
- def.resolve();
- });
- return def;
- }
-
- // cancels a payment.
- this.cancel = function(){
- if(!self.paid && !self.canceled){
- self.canceled = true;
- self.pos.proxy.payment_cancel();
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
- self.queue.clear();
- }
- return (new $.Deferred()).resolve();
- }
-
- if(this.pos.get('selectedOrder').getDueLeft() <= 0){
- this.pos_widget.screen_selector.show_popup('error-negative-price');
- }else{
- this.queue.schedule(this.start);
- }
-
- this.add_action_button({
- label: _t('Back'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){
- self.queue.schedule(self.cancel);
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
- }
- });
- },
- close: function(){
- if(this.queue){
- this.queue.schedule(this.cancel);
- }
- //TODO CANCEL
- this._super();
- },
- });
-
- module.WelcomeScreenWidget = module.ScreenWidget.extend({
- template:'WelcomeScreenWidget',
-
- next_screen: 'products',
-
- show_numpad: false,
- show_leftpane: false,
- start: function(){
- this._super();
- $('.goodbye-message').click(function(){
- $(this).addClass('oe_hidden');
- });
- },
-
- barcode_product_action: function(code){
- this.pos.proxy.transaction_start();
- this._super(code);
- },
-
- barcode_client_action: function(code){
- this.pos.proxy.transaction_start();
- this._super(code);
- $('.goodbye-message').addClass('oe_hidden');
- this.pos_widget.screen_selector.show_popup('choose-receipt');
- },
-
- show: function(){
- this._super();
- var self = this;
-
- this.add_action_button({
- label: _t('Help'),
- icon: '/point_of_sale/static/src/img/icons/png48/help.png',
- click: function(){
- $('.goodbye-message').css({opacity:1}).addClass('oe_hidden');
- self.help_button_action();
- },
- });
-
- $('.goodbye-message').css({opacity:1}).removeClass('oe_hidden');
- setTimeout(function(){
- $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').addClass('oe_hidden');});
- },5000);
- },
- });
-
module.ProductScreenWidget = module.ScreenWidget.extend({
template:'ProductScreenWidget',
- scale_screen: 'scale',
- client_scale_screen : 'scale_invite',
- client_next_screen: 'client_payment',
-
show_numpad: true,
show_leftpane: true,
@@ -767,7 +519,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_list_widget = new module.ProductListWidget(this,{
click_product_action: function(product){
if(product.to_weight && self.pos.config.iface_electronic_scale){
- self.pos_widget.screen_selector.set_current_screen( self.cashier_mode ? self.scale_screen : self.client_scale_screen, {product: product});
+ self.pos_widget.screen_selector.set_current_screen('scale',{product: product});
}else{
self.pos.get('selectedOrder').addProduct(product);
}
@@ -789,16 +541,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_categories_widget.reset_category();
this.pos_widget.order_widget.set_editable(true);
-
- if(this.pos_widget.screen_selector.current_mode === 'client'){
- this.add_action_button({
- label: _t('Pay'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
- click: function(){
- self.pos_widget.screen_selector.set_current_screen(self.client_next_screen);
- }
- });
- }
},
close: function(){
@@ -812,6 +554,180 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
});
+ module.ClientListScreenWidget = module.ScreenWidget.extend({
+ template: 'ClientListScreenWidget',
+
+ show_leftpane: false,
+
+ auto_back: true,
+
+ show: function(){
+ var self = this;
+ this._super();
+
+ this.renderElement();
+ this.details_visible = false;
+ this.old_client = this.pos.get('selectedOrder').get('client');
+ this.new_client = this.old_client;
+
+ this.$('.back').click(function(){
+ self.pos_widget.screen_selector.back();
+ });
+
+ this.$('.next').click(function(){
+ self.save_changes();
+ self.pos_widget.screen_selector.back();
+ });
+
+ var partners = this.pos.db.get_partners_sorted();
+ this.render_list(partners);
+
+ if( this.old_client ){
+ this.display_client_details('show',this.old_client,0);
+ }
+
+ this.$('.client-list-contents').delegate('.client-line','click',function(event){
+ console.log('uh');
+ self.line_select(event,$(this),parseInt($(this).data('id')));
+ });
+
+ var search_timeout = null;
+ this.$('.searchbox input').on('keyup',function(event){
+ clearTimeout(search_timeout);
+
+ var query = this.value;
+
+ search_timeout = setTimeout(function(){
+ self.perform_search(query,event.which === 13);
+ },70);
+ });
+
+ this.$('.searchbox .search-clear').click(function(){
+ self.clear_search();
+ });
+ },
+ perform_search: function(query, associate_result){
+ if(query){
+ var customers = this.pos.db.search_partner(query);
+ this.display_client_details('hide');
+ if ( associate_result && customers.length === 1){
+ this.new_client = customers[0];
+ this.save_changes();
+ this.pos_widget.screen_selector.back();
+ }
+ this.render_list(customers);
+ }else{
+ var customers = this.pos.db.get_partners_sorted();
+ this.render_list(customers);
+ }
+ },
+ clear_search: function(){
+ var customers = this.pos.db.get_partners_sorted();
+ this.render_list(customers);
+ this.$('.searchbox input')[0].value = '';
+ this.$('.searchbox input').focus();
+ },
+ render_list: function(partners){
+ var contents = this.$('.client-list-contents');
+ contents.empty();
+ for(var i = 0, len = partners.length; i < len; i++){
+ var clientline = $(QWeb.render('ClientLine',{partner:partners[i]}));
+ if( partners[i] === this.new_client ){
+ clientline.addClass('highlight');
+ }
+ contents.append(clientline);
+ }
+ },
+ save_changes: function(){
+ if( this.has_client_changed() ){
+ this.pos.get('selectedOrder').set_client(this.new_client);
+ }
+ },
+ has_client_changed: function(){
+ if( this.old_client && this.new_client ){
+ return this.old_client.id !== this.new_client.id;
+ }else{
+ return !!this.old_client !== !!this.new_client;
+ }
+ },
+ toggle_save_button: function(){
+ var $button = this.$('.button.next');
+ if( this.new_client ){
+ if( !this.old_client){
+ $button.text(_t('Set Customer'));
+ }else{
+ $button.text(_t('Change Customer'));
+ }
+ }else{
+ $button.text(_t('Deselect Customer'));
+ }
+ $button.toggleClass('oe_hidden',!this.has_client_changed());
+ },
+ line_select: function(event,$line,id){
+ var partner = this.pos.db.get_partner_by_id(id);
+ this.$('.client-list .lowlight').removeClass('lowlight');
+ if ( $line.hasClass('highlight') ){
+ $line.removeClass('highlight');
+ $line.addClass('lowlight');
+ this.display_client_details('hide',partner);
+ this.new_client = null;
+ this.toggle_save_button();
+ }else{
+ this.$('.client-list .highlight').removeClass('highlight');
+ $line.addClass('highlight');
+ var y = event.pageY - $line.parent().offset().top
+ this.display_client_details('show',partner,y);
+ this.new_client = partner;
+ this.toggle_save_button();
+ }
+ },
+ partner_icon_url: function(id){
+ return '/web/binary/image?model=res.partner&id='+id+'&field=image_small';
+ },
+ display_client_details: function(visibility,partner,clickpos){
+ if(visibility === 'show'){
+ var contents = this.$('.client-details-contents');
+ var parent = this.$('.client-list').parent();
+ var old_scroll = parent.scrollTop();
+ var old_height = contents.height();
+ contents.empty();
+ contents.append($(QWeb.render('ClientDetails',{widget:this,partner:partner})));
+ var new_height = contents.height();
+
+ if(!this.details_visible){
+ if(clickpos < old_scroll + new_height + 20 ){
+ parent.scrollTop( clickpos - 20 );
+ }else{
+ parent.scrollTop(parent.scrollTop() + new_height);
+ }
+ }else{
+ parent.scrollTop(parent.scrollTop() - old_height + new_height);
+ }
+
+ this.details_visible = true;
+ }else if(visibility === 'hide'){
+ var contents = this.$('.client-details-contents');
+ var parent = this.$('.client-list').parent();
+ var scroll = parent.scrollTop();
+ var height = contents.height();
+ contents.empty();
+ if( height > scroll ){
+ contents.css({height:height+'px'});
+ contents.animate({height:0},400,function(){
+ contents.css({height:''});
+ });
+ //parent.scrollTop(0);
+ }else{
+ parent.scrollTop( parent.scrollTop() - height);
+ }
+ this.details_visible = false;
+ }
+ },
+ close: function(){
+ this._super();
+ },
+ });
+
module.ReceiptScreenWidget = module.ScreenWidget.extend({
template: 'ReceiptScreenWidget',
@@ -879,6 +795,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}
});
+
module.PaymentScreenWidget = module.ScreenWidget.extend({
template: 'PaymentScreenWidget',
back_screen: 'products',
@@ -913,7 +830,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
if(node){
node.line.set_amount(this.value);
}
-
};
this.line_click_handler = function(event){
@@ -943,8 +859,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.focus_selected_line();
document.body.addEventListener('keyup', this.hotkey_handler);
-
-
this.add_action_button({
label: _t('Back'),
@@ -1182,7 +1096,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
if(this.pos.config.iface_print_via_proxy){
var receipt = currentOrder.export_for_printing();
this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',{
- receipt: receipt
+ receipt: receipt, widget: self,
}));
this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
}else{
diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js
index 95e07a17c0a..490e46b2401 100644
--- a/addons/point_of_sale/static/src/js/widgets.js
+++ b/addons/point_of_sale/static/src/js/widgets.js
@@ -130,7 +130,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this._super(parent,options);
this.editable = false;
this.pos.bind('change:selectedOrder', this.change_selected_order, this);
- this.bind_orderline_events();
this.line_click_handler = function(event){
if(!self.editable){
return;
@@ -138,6 +137,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.pos.get('selectedOrder').selectLine(this.orderline);
self.pos_widget.numpad.state.reset();
};
+ this.client_change_handler = function(event){
+ self.update_summary();
+ }
+ this.bind_order_events();
},
enable_numpad: function(){
this.disable_numpad(); //ensure we don't register the callbacks twice
@@ -177,11 +180,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}
},
change_selected_order: function() {
- this.bind_orderline_events();
+ this.bind_order_events();
this.renderElement();
},
- bind_orderline_events: function() {
- var lines = this.pos.get('selectedOrder').get('orderLines');
+ bind_order_events: function() {
+
+ var order = this.pos.get('selectedOrder');
+ order.unbind('change:client', this.client_change_handler);
+ order.bind('change:client', this.client_change_handler);
+
+ var lines = order.get('orderLines');
lines.unbind();
lines.bind('add', function(){
this.numpad_state.reset();
@@ -233,10 +241,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var orderlines = order.get('orderLines').models;
var el_str = openerp.qweb.render('OrderWidget',{widget:this, order:order, orderlines:orderlines});
+
var el_node = document.createElement('div');
el_node.innerHTML = _.str.trim(el_str);
el_node = el_node.childNodes[0];
+
var list_container = el_node.querySelector('.orderlines');
for(var i = 0, len = orderlines.length; i < len; i++){
var orderline = this.render_orderline(orderlines[i]);
@@ -260,6 +270,22 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
+
+ var $fidpoints = $(this.el).find('.summary .fidpoints');
+
+ if(this.pos.fidelity && order.get_client()){
+ var points = order.getWonFidpoints();
+ var total = order.get_client().fidpoints + points;
+ if( points ){
+ points = points > 0 ? '+'+points : +points;
+ }
+ $fidpoints.replaceWith($(QWeb.render('FidelityPoints',{ widget:this, totalpoints:total, wonpoints: points })));
+ $fidpoints.removeClass('oe_hidden');
+ }else{
+ $fidpoints.empty();
+ $fidpoints.addClass('oe_hidden');
+ }
+
},
});
@@ -275,12 +301,22 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.pos.bind('change:selectedOrder', this.renderElement,this );
},
renderElement:function(){
+ this.selected = ( this.pos.get('selectedOrder') === this.order )
this._super();
var self = this;
this.$el.click(function(){
- self.selectOrder();
+ if( self.pos.get('selectedOrder') === self.order ){
+ var ss = self.pos.pos_widget.screen_selector;
+ if(ss.get_current_screen() === 'clientlist'){
+ ss.back();
+ }else{
+ ss.set_current_screen('clientlist');
+ }
+ }else{
+ self.selectOrder();
+ }
});
- if( this.order === this.pos.get('selectedOrder') ){
+ if( this.selected){
this.$el.addClass('selected');
}
},
@@ -687,14 +723,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
unknown_product: '9900000000004',
},
events:[
- 'scan_item_success',
- 'scan_item_error_unrecognized',
- 'payment_request',
'open_cashbox',
'print_receipt',
- 'print_pdf_invoice',
'scale_read',
- 'payment_status',
],
minimized: false,
init: function(parent,options){
@@ -763,12 +794,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}
self.minimized = !self.minimized;
});
- this.$('.button.accept_payment').click(function(){
- self.pos.proxy.debug_accept_payment();
- });
- this.$('.button.reject_payment').click(function(){
- self.pos.proxy.debug_reject_payment();
- });
this.$('.button.set_weight').click(function(){
var kg = Number(self.$('input.weight').val());
if(!isNaN(kg)){
@@ -782,15 +807,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.$('.button.custom_ean').click(function(){
var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0');
self.$('input.ean').val(ean);
- self.pos.barcode_reader.scan('ean13',ean);
+ self.pos.barcode_reader.scan(ean);
});
this.$('.button.reference').click(function(){
- self.pos.barcode_reader.scan('reference',self.$('input.ean').val());
+ self.pos.barcode_reader.scan(self.$('input.ean').val());
});
_.each(this.eans, function(ean, name){
self.$('.button.'+name).click(function(){
self.$('input.ean').val(ean);
- self.pos.barcode_reader.scan('ean13',ean);
+ self.pos.barcode_reader.scan(ean);
});
});
_.each(this.events, function(name){
@@ -799,18 +824,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.$('.event.'+name).animate({'background-color':'#1E1E1E'},2000);
});
});
- self.pos.proxy.add_notification('help_needed',function(){
- self.$('.status.help_needed').addClass('on');
- });
- self.pos.proxy.add_notification('help_canceled',function(){
- self.$('.status.help_needed').removeClass('on');
- });
- self.pos.proxy.add_notification('transaction_start',function(){
- self.$('.status.transaction').addClass('on');
- });
- self.pos.proxy.add_notification('transaction_end',function(){
- self.$('.status.transaction').removeClass('on');
- });
},
});
@@ -919,7 +932,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
this.numpad_visible = true;
- this.left_action_bar_visible = true;
this.leftpane_visible = true;
this.leftpane_width = '440px';
this.cashier_controls_visible = true;
@@ -963,7 +975,17 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
});
self.$('.deleteorder-button').click(function(){
- self.pos.delete_current_order();
+ if( !self.pos.get('selectedOrder').is_empty() ){
+ self.screen_selector.show_popup('confirm',{
+ message: _t('Destroy Current Order ?'),
+ comment: _t('You will lose any data associated with the current order'),
+ confirm: function(){
+ self.pos.delete_current_order();
+ },
+ });
+ }else{
+ self.pos.delete_current_order();
+ }
});
//when a new order is created, add an order button widget
@@ -1048,28 +1070,20 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.payment_screen = new module.PaymentScreenWidget(this, {});
this.payment_screen.appendTo(this.$('.screens'));
- this.welcome_screen = new module.WelcomeScreenWidget(this,{});
- this.welcome_screen.appendTo(this.$('.screens'));
-
- this.client_payment_screen = new module.ClientPaymentScreenWidget(this, {});
- this.client_payment_screen.appendTo(this.$('.screens'));
-
- this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, {});
- this.scale_invite_screen.appendTo(this.$('.screens'));
+ this.clientlist_screen = new module.ClientListScreenWidget(this, {});
+ this.clientlist_screen.appendTo(this.$('.screens'));
this.scale_screen = new module.ScaleScreenWidget(this,{});
this.scale_screen.appendTo(this.$('.screens'));
- // -------- Popups ---------
- this.help_popup = new module.HelpPopupWidget(this, {});
- this.help_popup.appendTo(this.$el);
+ // -------- Popups ---------
this.error_popup = new module.ErrorPopupWidget(this, {});
this.error_popup.appendTo(this.$el);
- this.error_product_popup = new module.ProductErrorPopupWidget(this, {});
- this.error_product_popup.appendTo(this.$el);
+ this.error_barcode_popup = new module.ErrorBarcodePopupWidget(this, {});
+ this.error_barcode_popup.appendTo(this.$el);
this.error_session_popup = new module.ErrorSessionPopupWidget(this, {});
this.error_session_popup.appendTo(this.$el);
@@ -1077,15 +1091,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {});
this.choose_receipt_popup.appendTo(this.$el);
- this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});
- this.error_negative_price_popup.appendTo(this.$el);
-
this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {});
this.error_no_client_popup.appendTo(this.$el);
this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {});
this.error_invoice_transfer_popup.appendTo(this.$el);
+ this.confirm_popup = new module.ConfirmPopupWidget(this,{});
+ this.confirm_popup.appendTo(this.$el);
+
// -------- Misc ---------
this.close_button = new module.HeaderButtonWidget(this,{
@@ -1108,9 +1122,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.action_bar = new module.ActionBarWidget(this);
this.action_bar.replace(this.$(".placeholder-RightActionBar"));
- this.left_action_bar = new module.ActionBarWidget(this);
- this.left_action_bar.replace(this.$('.placeholder-LeftActionBar'));
-
this.paypad = new module.PaypadWidget(this, {});
this.paypad.replace(this.$('.placeholder-PaypadWidget'));
@@ -1125,13 +1136,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
});
this.onscreen_keyboard.replace(this.$('.placeholder-OnscreenKeyboardWidget'));
- 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.$('.pos-rightheader'));
-
-
// -------- Screen Selector ---------
this.screen_selector = new module.ScreenSelector({
@@ -1139,25 +1143,21 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
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,
+ 'clientlist': this.clientlist_screen,
},
popup_set:{
- 'help': this.help_popup,
'error': this.error_popup,
- 'error-product': this.error_product_popup,
+ 'error-barcode': this.error_barcode_popup,
'error-session': this.error_session_popup,
- 'error-negative-price': this.error_negative_price_popup,
'choose-receipt': this.choose_receipt_popup,
'error-no-client': this.error_no_client_popup,
'error-invoice-transfer': this.error_invoice_transfer_popup,
+ 'confirm': this.confirm_popup,
},
- default_client_screen: 'welcome',
- default_cashier_screen: 'products',
- default_mode: this.pos.config.iface_self_checkout ? 'client' : 'cashier',
+ default_screen: 'products',
+ default_mode: 'cashier',
});
if(this.pos.debug){
@@ -1178,7 +1178,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(visible !== this.numpad_visible){
this.numpad_visible = visible;
if(visible){
- this.set_left_action_bar_visible(false);
this.numpad.show();
this.paypad.show();
}else{
@@ -1187,39 +1186,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}
}
},
- set_left_action_bar_visible: function(visible){
- if(visible !== this.left_action_bar_visible){
- this.left_action_bar_visible = visible;
- if(visible){
- this.set_numpad_visible(false);
- this.left_action_bar.show();
- }else{
- this.left_action_bar.hide();
- }
- }
- },
//shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
set_leftpane_visible: function(visible){
if(visible !== this.leftpane_visible){
this.leftpane_visible = visible;
if(visible){
- this.$('.pos-leftpane').removeClass('oe_hidden').animate({'width':this.leftpane_width},500,'swing');
- this.$('.pos-rightpane').animate({'left':this.leftpane_width},500,'swing');
+ this.$('.pos-leftpane').removeClass('oe_hidden');
+ this.$('.rightpane').css({'left':this.leftpane_width});
}else{
- var leftpane = this.$('.pos-leftpane');
- leftpane.animate({'width':'0px'},500,'swing', function(){ leftpane.addClass('oe_hidden'); });
- this.$('.pos-rightpane').animate({'left':'0px'},500,'swing');
- }
- }
- },
- //shows or hide the controls in the PosWidget that are specific to the cashier ( Orders, close button, etc. )
- set_cashier_controls_visible: function(visible){
- if(visible !== this.cashier_controls_visible){
- this.cashier_controls_visible = visible;
- if(visible){
- this.$('.pos-rightheader').removeClass('oe_hidden');
- }else{
- this.$('.pos-rightheader').addClass('oe_hidden');
+ this.$('.pos-leftpane').addClass('oe_hidden');
+ this.$('.rightpane').css({'left':'0px'});
}
}
},
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 55722d1e5d7..d00e1b4710f 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -12,9 +12,9 @@
@@ -35,6 +35,7 @@
@@ -258,29 +259,142 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Back
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+ 123.14 €
+
+
+ Order
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+ Address
+
+
+
+ email
+
+
+
+ phone
+
+
+
+
+ N/A
+
+
+
+
+
+ Points
+
+
+
+ ID
+
+
+
+
+ N/A
+
+
+
+ Tax ID
+ 123-45678-9101145
+
+
+
+
+
+
+
+
+
+
+
+