[WIP] point_of_sale: more progress on ipadification: the app now is in a separate controller, with proper meta tags for standalone apps, pixel ratio, etc

bzr revid: fva@openerp.com-20131127144427-odf37v9dthuw4yrm
This commit is contained in:
Frédéric van der Essen 2013-11-27 15:44:27 +01:00
parent 28c074dcb2
commit 165af24f49
11 changed files with 291 additions and 193 deletions

View File

@ -8,182 +8,73 @@ import random
from openerp import http
from openerp.http import request
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
from openerp.addons.web.controllers.main import manifest_list, module_boot
_logger = logging.getLogger(__name__)
class PointOfSaleController(http.Controller):
def __init__(self):
self.scale = 'closed'
self.scale_weight = 0.0
html_template = """<!DOCTYPE html>
<html>
<head>
<title>OpenERP Point of Sale</title>
@http.route('/pos/app', type='http', auth='admin')
def app(self):
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js',db=request.db))
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css',db=request.db))
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta http-equiv="content-type" content="text/html, charset=utf-8" />
cookie = request.httprequest.cookies.get("instance0|session_id")
session_id = cookie.replace("%22","")
template = html_template.replace('<html','<html manifest="/pos/manifest?session_id=%s"' % request.session_id)
<meta name="viewport" content=" width=1024, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
r = template % {
<link rel="apple-touch-icon" href="/point_of_sale/static/src/img/touch-icon-iphone.png">
<link rel="apple-touch-icon" sizes="76x76" href="/point_of_sale/static/src/img/touch-icon-ipad.png">
<link rel="apple-touch-icon" sizes="120x120" href="/point_of_sale/static/src/img/touch-icon-iphone-retina.png">
<link rel="apple-touch-icon" sizes="152x152" href="/point_of_sale/static/src/img/touch-icon-ipad-retina.png">
<link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" href="/web/static/src/css/full.css" />
%(css)s
%(js)s
<script type="text/javascript">
$(function() {
var s = new openerp.init(%(modules)s);
%(init)s
});
</script>
</head>
<body>
<!--[if lte IE 8]>
<script src="//ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
<script>CFInstall.check({mode: "overlay"});</script>
<![endif]-->
</body>
</html>
"""
class PosController(http.Controller):
@http.route('/pos/web', type='http', auth='none')
def a(self, debug=False, **k):
js_list = manifest_list('js',db=request.db, debug=debug)
css_list = manifest_list('css',db=request.db, debug=debug)
print css_list
print js_list
js = "\n".join('<script type="text/javascript" src="%s"></script>' % i for i in js_list)
css = "\n".join('<link rel="stylesheet" href="%s">' % i for i in css_list)
r = html_template % {
'js': js,
'css': css,
'modules': simplejson.dumps(module_boot(request)),
'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
'modules': simplejson.dumps(module_boot(request.db)),
'init': """
window.navigator.standalone = true;
var wc = new s.web.WebClient();
wc.appendTo($(document.body));
wc.show_application = function(){
wc.action_manager.do_action("pos.ui");
};
wc.show_login = function(){
window.location.href = '/';
}
"""
}
return r
@http.route('/pos/manifest',type='http', auth='admin')
def manifest(self):
""" This generates a HTML5 cache manifest files that preloads the categories and products thumbnails
and other ressources necessary for the point of sale to work offline """
ml = ["CACHE MANIFEST"]
# loading all the images in the static/src/img/* directories
def load_css_img(srcdir,dstdir):
for f in os.listdir(srcdir):
path = os.path.join(srcdir,f)
dstpath = os.path.join(dstdir,f)
if os.path.isdir(path) :
load_css_img(path,dstpath)
elif f.endswith(('.png','.PNG','.jpg','.JPG','.jpeg','.JPEG','.gif','.GIF')):
ml.append(dstpath)
imgdir = openerp.modules.get_module_resource('point_of_sale','static/src/img');
load_css_img(imgdir,'/point_of_sale/static/src/img')
products = request.registry.get('product.product')
for p in products.search_read(request.cr, request.uid, [('pos_categ_id','!=',False)], ['name']):
product_id = p['id']
url = "/web/binary/image?session_id=%s&model=product.product&field=image&id=%s" % (request.session_id, product_id)
ml.append(url)
categories = request.registry.get('pos.category')
for c in categories.search_read(request.cr, request.uid, [], ['name']):
category_id = c['id']
url = "/web/binary/image?session_id=%s&model=pos.category&field=image&id=%s" % (request.session_id, category_id)
ml.append(url)
ml += ["NETWORK:","*"]
m = "\n".join(ml)
return m
@http.route('/pos/test_connection', type='json', auth='admin')
def test_connection(self):
_logger.info('Received Connection Test from the Point of Sale');
@http.route('/pos/scan_item_success', type='json', auth='admin')
def scan_item_success(self, ean):
"""
A product has been scanned with success
"""
print 'scan_item_success: ' + str(ean)
@http.route('/pos/scan_item_error_unrecognized', type='json', auth='admin')
def scan_item_error_unrecognized(self, ean):
"""
A product has been scanned without success
"""
print 'scan_item_error_unrecognized: ' + str(ean)
@http.route('/pos/help_needed', type='json', auth='admin')
def help_needed(self):
"""
The user wants an help (ex: light is on)
"""
print "help_needed"
@http.route('/pos/help_canceled', type='json', auth='admin')
def help_canceled(self):
"""
The user stops the help request
"""
print "help_canceled"
@http.route('/pos/weighting_start', type='json', auth='admin')
def weighting_start(self):
if self.scale == 'closed':
print "Opening (Fake) Connection to Scale..."
self.scale = 'open'
self.scale_weight = 0.0
time.sleep(0.1)
print "... Scale Open."
else:
print "WARNING: Scale already Connected !!!"
@http.route('/pos/weighting_read_kg', type='json', auth='admin')
def weighting_read_kg(self):
if self.scale == 'open':
print "Reading Scale..."
time.sleep(0.025)
self.scale_weight += 0.01
print "... Done."
return self.scale_weight
else:
print "WARNING: Reading closed scale !!!"
return 0.0
@http.route('/pos/weighting_end', type='json', auth='admin')
def weighting_end(self):
if self.scale == 'open':
print "Closing Connection to Scale ..."
self.scale = 'closed'
self.scale_weight = 0.0
time.sleep(0.1)
print "... Scale Closed."
else:
print "WARNING: Scale already Closed !!!"
@http.route('/pos/payment_request', type='json', auth='admin')
def payment_request(self, price):
"""
The PoS will activate the method payment
"""
print "payment_request: price:"+str(price)
return 'ok'
@http.route('/pos/payment_status', type='json', auth='admin')
def payment_status(self):
print "payment_status"
return { 'status':'waiting' }
@http.route('/pos/payment_cancel', type='json', auth='admin')
def payment_cancel(self):
print "payment_cancel"
@http.route('/pos/transaction_start', type='json', auth='admin')
def transaction_start(self):
print 'transaction_start'
@http.route('/pos/transaction_end', type='json', auth='admin')
def transaction_end(self):
print 'transaction_end'
@http.route('/pos/cashier_mode_activated', type='json', auth='admin')
def cashier_mode_activated(self):
print 'cashier_mode_activated'
@http.route('/pos/cashier_mode_deactivated', type='json', auth='admin')
def cashier_mode_deactivated(self):
print 'cashier_mode_deactivated'
@http.route('/pos/open_cashbox', type='json', auth='admin')
def open_cashbox(self):
print 'open_cashbox'
@http.route('/pos/print_receipt', type='json', auth='admin')
def print_receipt(self, receipt):
print 'print_receipt' + str(receipt)
@http.route('/pos/log', type='json', auth='admin')
def log(self, arguments):
_logger.info(' '.join(str(v) for v in arguments))
@http.route('/pos/print_pdf_invoice', type='json', auth='admin')
def print_pdf_invoice(self, pdfinvoice):
print 'print_pdf_invoice' + str(pdfinvoice)

View File

@ -30,6 +30,12 @@
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.pos input::-webkit-outer-spin-button,
.pos input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.pos button{
box-shadow: none;
outline: none;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg3162"
version="1.1"
inkscape:version="0.48.3.1 r9886"
width="152"
height="152"
sodipodi:docname="ios7-icon.png"
inkscape:export-filename="/home/fva/Code/openerp/point_of_sale/touch-icon-ipad-retina.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<metadata
id="metadata3168">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3166">
<linearGradient
id="linearGradient3944">
<stop
style="stop-color:#483c98;stop-opacity:1;"
offset="0"
id="stop3946" />
<stop
style="stop-color:#8075c9;stop-opacity:1;"
offset="1"
id="stop3948" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3944"
id="linearGradient3950"
x1="116.83051"
y1="0.49999994"
x2="115.35169"
y2="227.45763"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.67555556,0,0,0.67555556,0,-0.67555559)" />
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1111"
id="namedview3164"
showgrid="true"
inkscape:zoom="1"
inkscape:cx="39.575132"
inkscape:cy="237.57664"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg3162">
<inkscape:grid
type="xygrid"
id="grid3942"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<rect
style="fill:url(#linearGradient3950);fill-opacity:1;fill-rule:evenodd;stroke:none"
id="rect3172"
width="152"
height="152"
x="0"
y="-3.9968029e-15"
ry="34.723557" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect3952"
width="5"
height="80"
x="25"
y="37"
ry="1" />
<rect
ry="1"
y="37"
x="35"
height="80"
width="3.0532093"
id="rect3954"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect3956"
width="5.0000033"
height="80"
x="45"
y="37"
ry="1" />
<rect
ry="1"
y="37"
x="54.999996"
height="80"
width="3.0000036"
id="rect3958"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect3960"
width="3.0000036"
height="80"
x="65"
y="37"
ry="1" />
<rect
ry="1"
y="37"
x="75"
height="80"
width="3.0000036"
id="rect3962"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect3964"
width="5.0000057"
height="80"
x="80"
y="37"
ry="1" />
<rect
ry="1"
y="37"
x="90"
height="80"
width="3.0000036"
id="rect3966"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
ry="1"
y="37"
x="100.02039"
height="80"
width="5.0000057"
id="rect3968"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect3970"
width="3.0000036"
height="80"
x="107"
y="37"
ry="1" />
<rect
ry="1"
y="37"
x="114.99999"
height="80"
width="5.0000057"
id="rect3972"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect3974"
width="3.0532093"
height="80"
x="122"
y="37"
ry="1" />
<rect
style="fill:#f80000;fill-opacity:1;stroke:none"
id="rect3976"
width="2.0000024"
height="110"
x="-103.85593"
y="20"
ry="1.375"
transform="matrix(0,-1,1,0,0,0)" />
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -378,7 +378,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
_flush_order: function(order_id, options){
var self = this;
options = options || {};
timeout = typeof options.timeout === 'number' ? options.timeout : 5000;
timeout = typeof options.timeout === 'number' ? options.timeout : 7500;
var order = this.db.get_order(order_id);
order.to_invoice = options.to_invoice || false;

View File

@ -812,6 +812,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
var self = this;
this.pos_widget.set_numpad_visible(false);
var print_button = this.add_action_button({
label: _t('Print'),
icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
@ -872,6 +874,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.currentOrder = this.pos.get('selectedOrder');
$('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this}));
},
close: function(){
this._super();
this.pos_widget.set_numpad_visible(true);
}
});
module.PaymentScreenWidget = module.ScreenWidget.extend({
@ -1006,6 +1012,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos_widget.screen_selector.set_current_screen(this.next_screen);
}
}
// hide onscreen (iOS) keyboard
setTimeout(function(){
document.activeElement.blur();
$("input").blur();
},250);
},
bindPaymentLineEvents: function() {
this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
@ -1068,8 +1079,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
isPaymentPaid: function(){
var currentOrder = this.pos.get('selectedOrder');
return (currentOrder.getTotalTaxIncluded < 0.000001
&& currentOrder.getPaidTotal() + 0.000001 < currentOrder.getTotalTaxIncluded());
return (currentOrder.getTotalTaxIncluded() >= 0.000001
&& currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
},
updatePaymentSummary: function() {
@ -1088,8 +1099,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}
if(this.pos_widget.action_bar){
this.pos_widget.action_bar.set_button_disabled('validation', this.isPaymentPaid());
this.pos_widget.action_bar.set_button_disabled('invoice', this.isPaymentPaid());
this.pos_widget.action_bar.set_button_disabled('validation', !this.isPaymentPaid());
this.pos_widget.action_bar.set_button_disabled('invoice', !this.isPaymentPaid());
}
},
set_numpad_state: function(numpadState) {

View File

@ -213,13 +213,13 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var scroller = this.$('.order-scroller')[0];
var scrollbottom = true;
var scrollTop = 0;
/*if(scroller){
if(scroller){
var overflow_bottom = scroller.scrollHeight - scroller.clientHeight;
scrollTop = scroller.scrollTop;
if( !goto_bottom && scrollTop < 0.9 * overflow_bottom){
scrollbottom = false;
}
}*/
}
this._super();
// freeing subwidgets
@ -244,14 +244,13 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.update_summary();
scroller = this.$('.order-scroller')[0];
if(scroller){
//scroller.scrollTop = 1000000;
/*
if(scrollbottom){
scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight;
}else{
scroller.scrollTop = scrollTop;
}*/
}
}
},
update_summary: function(){
@ -313,8 +312,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
focus: function(){
var val = this.$('input')[0].value;
this.$('input')[0].focus();
this.$('input')[0].value = val;
this.$('input')[0].select();
if(Number(val) === 0){
this.$('input')[0].value = '';
}else{
this.$('input')[0].value = val;
this.$('input')[0].select();
}
},
});
@ -863,16 +866,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.screen_selector.set_default_screen();
self.pos.barcode_reader.connect();
if(!$('#oe-fullscreenwidget-viewport').length){
$('head').append('<meta id="oe-pos-viewport" name="viewport" content=" width=1024px; user-scalable=no;">');
$('head').append('<meta id="oe-pos-apple-mobile" name="apple-mobile-web-capable" content="yes">');
}
$('.oe_leftbar').addClass('oe_hidden');
instance.webclient.set_content_full_screen(true);
if (!self.pos.get('pos_session')) {
@ -1104,7 +1099,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
var action = result;
action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'reload'}});
//self.destroy();
this.do_action(action);
}, this));
}, self));
@ -1124,9 +1118,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
destroy: function() {
this.pos.destroy();
instance.webclient.set_content_full_screen(false);
$('.oe_leftbar').removeClass('oe_hidden');
$('#oe-pos-viewport').remove();
$('#oe-pos-apple-mobile').remove();
this._super();
}
});

View File

@ -580,7 +580,7 @@
<t t-esc="widget.name"/>
</td>
<td class="paymentline-amount pos-right-align">
<input type="number" t-att-value="widget.payment_line.get_amount().toFixed(2)" />
<input type="number" step="0.01" t-att-value="widget.payment_line.get_amount().toFixed(2)" />
<a href='javascript:void(0)' class='delete-payment-line'><img src="/point_of_sale/static/src/img/search_reset.gif" /></a>
</td>
</tr>