François Kawala
5 years ago
committed by
Sylvain LE GAL
10 changed files with 396 additions and 0 deletions
-
1pos_barcode_tare/__init__.py
-
36pos_barcode_tare/__openerp__.py
-
BINpos_barcode_tare/static/description/icon.png
-
105pos_barcode_tare/static/src/css/tare_screen.css
-
36pos_barcode_tare/static/src/js/barcode.js
-
20pos_barcode_tare/static/src/js/open_tare_screen_button.js
-
139pos_barcode_tare/static/src/js/tare_screen.js
-
9pos_barcode_tare/static/src/xml/open_tare_screen_button.xml
-
38pos_barcode_tare/static/src/xml/tare_screen.xml
-
12pos_barcode_tare/views/templates.xml
@ -0,0 +1 @@ |
|||
# -*- coding: utf-8 -*- |
@ -0,0 +1,36 @@ |
|||
# -*- coding: utf-8 -*- |
|||
{ |
|||
'name': "pos_barecode_tare", |
|||
|
|||
'summary': """ |
|||
Allows to scan a barcode to tare the latest product. |
|||
""", |
|||
|
|||
'description': """ |
|||
This add-on enable POS to read and print tare bar codes. A tare bar code is used to sell unpackaged goods in a |
|||
BYOC (bring your own container) scheme. This scheme has four steps: |
|||
1. The cashier weights the container and sticks the tare bar code onto the customer's container. |
|||
2. The customer takes the desired quantity of whatever good s-he wants. |
|||
3. The cashier weights the filled container and good, POS gives the corresponding price. |
|||
4. The cashier scans the tare bar code, POS removes the container's weight from the latest product of the order. |
|||
""", |
|||
|
|||
'author': "Le Nid", |
|||
'website': "http://www.lenid.ch", |
|||
|
|||
'category': 'Point of Sale', |
|||
'version': '0.1', |
|||
|
|||
# any module necessary for this one to work correctly |
|||
'depends': ['point_of_sale'], |
|||
|
|||
# always loaded |
|||
'data': [ |
|||
'views/templates.xml', |
|||
], |
|||
'qweb': [ |
|||
'static/src/xml/tare_screen.xml', |
|||
'static/src/xml/open_tare_screen_button.xml', |
|||
|
|||
], |
|||
} |
After Width: 1571 | Height: 1690 | Size: 90 KiB |
@ -0,0 +1,105 @@ |
|||
.pos .print-label.disabled { |
|||
background: #e2e2e2; |
|||
border: solid 1px #BEBEBE; |
|||
opacity: 0.5; |
|||
cursor: default; |
|||
color: inherit; |
|||
} |
|||
|
|||
.pos .pos-tare-label { |
|||
width: 300px; |
|||
background-color: white; |
|||
margin: 20px; |
|||
padding: 15px; |
|||
font-size: 21px; |
|||
padding-bottom:30px; |
|||
display: inline-block; |
|||
font-family: "Inconsolata"; |
|||
border: solid 1px rgb(220,220,220); |
|||
border-radius: 3px; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.pos .tare-screen .pos-directions-for-user { |
|||
font-size: 25px; |
|||
margin: 8px; |
|||
text-align: center; |
|||
line-height: 2; |
|||
} |
|||
|
|||
.pos .tare-screen .pos-directions-for-user span { |
|||
width:100px; |
|||
height: 50px; |
|||
background-color: rgb(49,174,218); |
|||
color: white !important; |
|||
font-weight: bold; |
|||
border: solid 1px black; |
|||
border-radius: 90% 30%; |
|||
display: flex; |
|||
align-items: center; |
|||
overflow: hidden; |
|||
vertical-align:middle; |
|||
justify-content: center; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
.pos .pos-tare-label img { |
|||
width: 50mm; |
|||
height: 45mm; |
|||
} |
|||
|
|||
.pos .tare-screen .print-label { |
|||
text-align: center; |
|||
font-size: 32px; |
|||
background: rgb(110,200,155); |
|||
color: white; |
|||
border-radius: 3px; |
|||
padding: 16px; |
|||
margin: 16px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
@media print { |
|||
.pos .tare-screen header, |
|||
.pos .tare-screen .top-content, |
|||
.pos .tare-screen .centered-content .print-label, |
|||
.pos .tare-screen .pos-directions-for-user { |
|||
display: none !important; |
|||
} |
|||
|
|||
.pos .tare-screen .centered-content { |
|||
position: static; |
|||
border: none; |
|||
} |
|||
|
|||
.pos .pos-tare-paper { |
|||
margin: 0; |
|||
margin-left: 0 !important; |
|||
margin-right: 0 !important; |
|||
width: 42mm !important; |
|||
height: 29mm !important; |
|||
background: white; |
|||
display: block; |
|||
position: fixed; |
|||
display: flex !important; |
|||
justify-content: center !important; |
|||
align-items: center !important; |
|||
} |
|||
|
|||
|
|||
.pos-tare-label img { |
|||
width: 30mm !important; |
|||
height: 25mm !important; |
|||
} |
|||
|
|||
.pos .pos-tare-label { |
|||
margin: 0; |
|||
margin-left: 0 !important; |
|||
margin-right: 0 !important; |
|||
position: fixed !important; |
|||
border: none !important; |
|||
font-size: 10px !important; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
odoo.define('barcode_tare',function(require) { |
|||
"use strict"; |
|||
var screens = require('point_of_sale.screens'); |
|||
var gui = require('point_of_sale.gui'); |
|||
var core = require('web.core'); |
|||
var _t = core._t; |
|||
|
|||
screens.ScreenWidget.include( |
|||
{ |
|||
barcode_weight_action: function(code){ |
|||
var self = this; |
|||
var order = this.pos.get_order(); |
|||
var last_order_line = order.get_last_orderline(); |
|||
var total_weight = last_order_line.get_quantity(); |
|||
var tare = code.value; |
|||
var paid_weight = total_weight - tare; |
|||
|
|||
if (paid_weight <= 0) { |
|||
this.gui.show_popup('confirm', { |
|||
'title': _t('Poids négatif'), |
|||
'body': _t('Le poids à payer est négatif. Avez-vous scanné le bon code bare ?'), |
|||
confirm: function(){ |
|||
last_order_line.set_quantity(paid_weight) |
|||
}}); |
|||
} else { |
|||
last_order_line.set_quantity(paid_weight) |
|||
} |
|||
}, |
|||
|
|||
show: function(){ |
|||
var self = this; |
|||
this._super() |
|||
this.pos.barcode_reader.set_action_callback('weight', _.bind(self.barcode_weight_action, self)) |
|||
}, |
|||
}); |
|||
}); |
@ -0,0 +1,20 @@ |
|||
odoo.define('tare-screen-button.button', function (require) { |
|||
"use strict"; |
|||
var core = require('web.core'); |
|||
var screens = require('point_of_sale.screens'); |
|||
var gui = require('point_of_sale.gui'); |
|||
|
|||
var TareScreenButton = screens.ActionButtonWidget.extend({ |
|||
template: 'TareScreenButton', |
|||
|
|||
button_click: function(){ |
|||
var self = this; |
|||
this.gui.show_screen('tare'); |
|||
} |
|||
}); |
|||
|
|||
screens.define_action_button({ |
|||
'name': 'tareScreenButton', |
|||
'widget': TareScreenButton, |
|||
}); |
|||
}); |
@ -0,0 +1,139 @@ |
|||
odoo.define('tare-screen.screen', function (require) { |
|||
"use strict"; |
|||
var chrome = require('point_of_sale.chrome'); |
|||
var core = require('web.core'); |
|||
var devices = require('point_of_sale.devices'); |
|||
var gui = require('point_of_sale.gui'); |
|||
var models = require('point_of_sale.models'); |
|||
var screens = require('point_of_sale.screens'); |
|||
var QWeb = core.qweb; |
|||
|
|||
var TareScreenWidget = screens.ScreenWidget.extend({ |
|||
template: 'TareScreenWidget', |
|||
next_screen: 'products', |
|||
previous_screen: 'products', |
|||
default_tare_value_kg: 0.0, |
|||
|
|||
show: function(){ |
|||
this._super(); |
|||
var self = this; |
|||
var queue = this.pos.proxy_queue; |
|||
|
|||
queue.schedule(function(){ |
|||
return self.pos.proxy.scale_read().then(function(weight){ |
|||
self.set_weight(weight.weight); |
|||
}); |
|||
},{duration:150, repeat: true}); |
|||
|
|||
this.render_receipt(); |
|||
this.lock_screen(true); |
|||
}, |
|||
set_weight: function(weight){ |
|||
if (weight > 0){ |
|||
this.weight = weight; |
|||
this.render_receipt(); |
|||
this.lock_screen(false); |
|||
} |
|||
}, |
|||
get_weight: function(){ |
|||
if (typeof this.weight === 'undefined') { |
|||
return this.default_tare_value_kg; |
|||
} |
|||
return this.weight; |
|||
}, |
|||
ean13_checksum: function(s){ |
|||
var result = 0; |
|||
for (let counter = s.length-1; counter >=0; counter--){ |
|||
result = result + parseInt(s.charAt(counter)) * (1+(2*(counter % 2))); |
|||
} |
|||
return (10 - (result % 10)) % 10; |
|||
}, |
|||
barcode_data: function(weight, weight_prefix_id=21){ |
|||
var padding_size = 5; |
|||
var void_product_id = '0'.repeat(padding_size); |
|||
var weight_in_gram = weight * 10e2; |
|||
var weight_with_padding = '0'.repeat(padding_size) + weight_in_gram; |
|||
var padded_weight = weight_with_padding.substr(weight_with_padding.length - padding_size); |
|||
var barcode_data = `${weight_prefix_id}${void_product_id}${padded_weight}`; |
|||
var checksum = this.ean13_checksum(barcode_data); |
|||
console.log(`${barcode_data}${checksum}`); |
|||
return `${barcode_data}${checksum}`; |
|||
}, |
|||
get_barcode_data: function(){ |
|||
return this.barcode_data(this.get_weight()); |
|||
}, |
|||
should_auto_print: function() { |
|||
return this.pos.config.iface_print_auto && !this.pos.get_order()._printed; |
|||
}, |
|||
should_close_immediately: function() { |
|||
return this.pos.config.iface_print_via_proxy && this.pos.config.iface_print_skip_screen; |
|||
}, |
|||
lock_screen: function(locked) { |
|||
this._locked = locked; |
|||
if (locked) { |
|||
this.$('.print-label').addClass('disabled'); |
|||
} else { |
|||
this.$('.print-label').removeClass('disabled'); |
|||
} |
|||
}, |
|||
print_web: function() { |
|||
window.print(); |
|||
this.pos.get_order()._printed = true; |
|||
}, |
|||
print: function() { |
|||
var self = this; |
|||
|
|||
// The problem is that in chrome the print() is asynchronous and doesn't
|
|||
// execute until all rpc are finished. So it conflicts with the rpc used
|
|||
// to send the orders to the backend, and the user is able to go to the next
|
|||
// screen before the printing dialog is opened. The problem is that what's
|
|||
// printed is whatever is in the page when the dialog is opened and not when it's called,
|
|||
// and so you end up printing the product list instead of the receipt...
|
|||
//
|
|||
// Fixing this would need a re-architecturing
|
|||
// of the code to postpone sending of orders after printing.
|
|||
//
|
|||
// But since the print dialog also blocks the other asynchronous calls, the
|
|||
// button enabling in the setTimeout() is blocked until the printing dialog is
|
|||
// closed. But the timeout has to be big enough or else it doesn't work
|
|||
// 1 seconds is the same as the default timeout for sending orders and so the dialog
|
|||
// should have appeared before the timeout... so yeah that's not ultra reliable.
|
|||
|
|||
this.lock_screen(true); |
|||
|
|||
setTimeout(function(){ |
|||
self.lock_screen(false); |
|||
}, 1000); |
|||
|
|||
this.print_web(); |
|||
this.click_back(); |
|||
}, |
|||
click_back: function() { |
|||
this.close() |
|||
this.gui.show_screen(this.previous_screen); |
|||
}, |
|||
renderElement: function() { |
|||
var self = this; |
|||
this._super(); |
|||
this.$('.back').click(function(){ |
|||
self.click_back(); |
|||
}); |
|||
this.$('.print-label').click(function(){ |
|||
if (!self._locked) { |
|||
self.print(); |
|||
} |
|||
}); |
|||
}, |
|||
render_receipt: function() { |
|||
this.$('.pos-tare-label-container').html(QWeb.render('PosTareLabel',{widget:this})); |
|||
}, |
|||
close: function(){ |
|||
this._super(); |
|||
delete this.weight; |
|||
this.pos.proxy_queue.clear(); |
|||
}, |
|||
}); |
|||
|
|||
gui.define_screen({name:'tare', widget: TareScreenWidget}); |
|||
|
|||
}); |
@ -0,0 +1,9 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<templates id="template" xml:space="preserve"> |
|||
<t t-name="TareScreenButton"> |
|||
<span class="control-button"> |
|||
<i class="fa fa-print"></i> |
|||
Créer une étiquette de tare |
|||
</span> |
|||
</t> |
|||
</templates> |
@ -0,0 +1,38 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<templates id="template" xml:space="preserve"> |
|||
<t t-name="TareScreenWidget"> |
|||
<div class='tare-screen screen'> |
|||
<div class='screen-content'> |
|||
<div class='top-content'> |
|||
<span class='button back'> |
|||
<i class='fa fa-angle-double-left'></i> |
|||
Back |
|||
</span> |
|||
<h1>Création d'une étiquette de tare</h1> |
|||
</div> |
|||
<div class="centered-content"> |
|||
<div class="pos-tare-label-container"></div> |
|||
<div class="pos-directions-for-user"> |
|||
Appuyez sur la touche <span>print</span> puis vérifiez le poids ci-dessus. |
|||
</div> |
|||
<div class='print-label'> |
|||
Imprimer |
|||
<i class='fa fa-angle-double-right'></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
|
|||
<t t-name="PosTareLabel"> |
|||
<div class="pos-center-align"> |
|||
<div class="pos-tare-paper"> |
|||
<div class="pos-tare-label"> |
|||
<img t-att-src="'/report/barcode/EAN13/' + widget.get_barcode_data()" /> |
|||
<br /> |
|||
tare = <t t-esc="widget.get_weight()" />kg |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
</templates> |
@ -0,0 +1,12 @@ |
|||
<odoo> |
|||
<data> |
|||
<template id="assets_backend" name="pos_barcode_tare" inherit_id="point_of_sale.assets"> |
|||
<xpath expr="." position="inside"> |
|||
<link rel="stylesheet" href="/pos_barcode_tare/static/src/css/tare_screen.css"/> |
|||
<script type="text/javascript" src="/pos_barcode_tare/static/src/js/tare_screen.js"></script> |
|||
<script type="text/javascript" src="/pos_barcode_tare/static/src/js/open_tare_screen_button.js"></script> |
|||
<script type="text/javascript" src="/pos_barcode_tare/static/src/js/barcode.js"></script> |
|||
</xpath> |
|||
</template> |
|||
</data> |
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue