François Kawala
5 years ago
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