diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 4daab2ffad9..9926beb09ba 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -819,7 +819,7 @@ td {
background: url("../img/search.png") no-repeat 15px;
background-color: white;
border-radius: 20px;
- font-family: Lato;
+ font-family: "Lato","Lucida Grande", Helvetica, Verdana, Arial;
font-size: 13px;
}
.pos .searchbox input:focus {
@@ -2062,6 +2062,8 @@ td {
border-radius: 3px;
box-shadow: 0px 10px 20px rgba(0,0,0,0.4);
z-index:1200;
+ font-family: 'Lato';
+ font-family: Lato;
}
.pos .popup .body {
font-weight: normal;
@@ -2240,6 +2242,23 @@ td {
display: inline-block;
}
+.pos .popup .body ul,
+.pos .popup ul.body {
+ text-align: left;
+ margin-left: 1em;
+}
+.pos .popup .body li {
+ text-indent: 1em;
+}
+.pos .popup .body li:before {
+ content: '—';
+ position: relative;
+ font-size: 0.6em;
+ left: -1em;
+ bottom: 0.2em;
+}
+
+
/* ********* The Webkit Scrollbar ********* */
.pos *::-webkit-scrollbar{
diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js
index 0be0953fbd6..76f86879812 100644
--- a/addons/point_of_sale/static/src/js/chrome.js
+++ b/addons/point_of_sale/static/src/js/chrome.js
@@ -254,22 +254,16 @@ var DebugWidget = PosBaseWidget.extend({
this.$('.button.barcode').click(function(){
self.pos.barcode_reader.scan(self.$('input.ean').val());
});
- this.$('.button.show_orders').click(function(){
- self.gui.show_popup('unsent-orders');
- });
this.$('.button.delete_orders').click(function(){
self.gui.show_popup('confirm',{
- 'title': _t('Delete Unsent Orders ?'),
- 'body': _t('This operation will permanently destroy all unsent orders from the local storage. You will lose all the data. This operation cannot be undone.'),
+ 'title': _t('Delete Paid Orders ?'),
+ 'body': _t('This operation will permanently destroy all paid orders from the local storage. You will lose all the data. This operation cannot be undone.'),
confirm: function(){
self.pos.db.remove_all_orders();
self.pos.set({synch: { state:'connected', pending: 0 }});
},
});
});
- this.$('.button.show_unpaid_orders').click(function(){
- self.gui.show_popup('unpaid-orders');
- });
this.$('.button.delete_unpaid_orders').click(function(){
self.gui.show_popup('confirm',{
'title': _t('Delete Unpaid Orders ?'),
@@ -280,6 +274,32 @@ var DebugWidget = PosBaseWidget.extend({
},
});
});
+
+ this.$('.button.export_unpaid_orders').click(function(){
+ self.gui.download_file(self.pos.export_unpaid_orders(),
+ "unpaid_orders_" + (new Date()).toUTCString().replace(/\ /g,'_') + '.json');
+ });
+
+ this.$('.button.export_paid_orders').click(function() {
+ self.gui.download_file(self.pos.export_paid_orders(),
+ "paid_orders_" + (new Date()).toUTCString().replace(/\ /g,'_') + '.json');
+ });
+
+ this.$('.button.import_orders input').on('change', function(event) {
+ var file = event.target.files[0];
+
+ if (file) {
+ var reader = new FileReader();
+
+ reader.onload = function(event) {
+ var report = self.pos.import_orders(event.target.result);
+ self.gui.show_popup('orderimport',{report:report});
+ };
+
+ reader.readAsText(file);
+ }
+ });
+
_.each(this.events, function(name){
self.pos.proxy.add_notification(name,function(){
self.$('.event.'+name).stop().clearQueue().css({'background-color':'#6CD11D'});
diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js
index c37ed951e5c..48021560642 100644
--- a/addons/point_of_sale/static/src/js/gui.js
+++ b/addons/point_of_sale/static/src/js/gui.js
@@ -299,6 +299,30 @@ var Gui = core.Class.extend({
$('body').append('');
},
+ /* ---- Gui: FILE I/O ---- */
+
+ // This will make the browser download 'contents' as a
+ // file named 'name'
+ // if 'contents' is not a string, it is converted into
+ // a JSON representation of the contents.
+ download_file: function(contents, name) {
+ var URL = window.URL || window.webkitURL;
+
+ if (typeof contents !== 'string') {
+ contents = JSON.stringify(contents,null,2);
+ }
+
+ var blob = new Blob([contents]);
+
+ var evt = document.createEvent("HTMLEvents");
+ evt.initEvent("click");
+
+ $("",{
+ download: name || 'document.txt',
+ href: URL.createObjectURL(blob),
+ }).get(0).dispatchEvent(evt);
+ },
+
/* ---- Gui: KEYBOARD INPUT ---- */
// This is a helper to handle numpad keyboard input.
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index a7a9d0cf9f6..ad2edf147ee 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -140,6 +140,14 @@ exports.PosModel = Backbone.Model.extend({
// used during loading such as object ids, etc.
models: [
{
+ label: 'version',
+ loaded: function(self){
+ return session.rpc('/web/webclient/version_info',{}).done(function(version) {
+ self.version = version;
+ });
+ },
+
+ },{
model: 'res.users',
fields: ['name','company_id'],
ids: function(self){ return [session.uid]; },
@@ -773,6 +781,127 @@ exports.PosModel = Backbone.Model.extend({
}
return true;
},
+
+ // Exports the paid orders (the ones waiting for internet connection)
+ export_paid_orders: function() {
+ return JSON.stringify({
+ 'paid_orders': this.db.get_orders(),
+ 'session': this.pos_session.name,
+ 'session_id': this.pos_session.id,
+ 'date': (new Date()).toUTCString(),
+ 'version': this.version.server_version_info,
+ },null,2);
+ },
+
+ // Exports the unpaid orders (the tabs)
+ export_unpaid_orders: function() {
+ return JSON.stringify({
+ 'unpaid_orders': this.db.get_unpaid_orders(),
+ 'session': this.pos_session.name,
+ 'session_id': this.pos_session.id,
+ 'date': (new Date()).toUTCString(),
+ 'version': this.version.server_version_info,
+ },null,2);
+ },
+
+ // This imports paid or unpaid orders from a json file whose
+ // contents are provided as the string str.
+ // It returns a report of what could and what could not be
+ // imported.
+ import_orders: function(str) {
+ var json = JSON.parse(str);
+ var report = {
+ // Number of paid orders that were imported
+ paid: 0,
+ // Number of unpaid orders that were imported
+ unpaid: 0,
+ // Orders that were not imported because they already exist (uid conflict)
+ unpaid_skipped_existing: 0,
+ // Orders that were not imported because they belong to another session
+ unpaid_skipped_session: 0,
+ // The list of session ids to which skipped orders belong.
+ unpaid_skipped_sessions: [],
+ };
+
+ if (json.paid_orders) {
+ for (var i = 0; i < json.paid_orders.length; i++) {
+ this.db.add_order(json.paid_orders[i].data);
+ }
+ report.paid = json.paid_orders.length;
+ this.push_order();
+ }
+
+ if (json.unpaid_orders) {
+
+ var orders = [];
+ var existing = this.get_order_list();
+ var existing_uids = {};
+ var skipped_sessions = {};
+
+ for (var i = 0; i < existing.length; i++) {
+ existing_uids[existing[i].uid] = true;
+ }
+
+ for (var i = 0; i < json.unpaid_orders.length; i++) {
+ var order = json.unpaid_orders[i];
+ if (order.pos_session_id !== this.pos_session.id) {
+ report.unpaid_skipped_session += 1;
+ skipped_sessions[order.pos_session_id] = true;
+ } else if (existing_uids[order.uid]) {
+ report.unpaid_skipped_existing += 1;
+ } else {
+ orders.push(new exports.Order({},{
+ pos: this,
+ json: order,
+ }));
+ }
+ }
+
+ orders = orders.sort(function(a,b){
+ return a.sequence_number - b.sequence_number;
+ });
+
+ if (orders.length) {
+ report.unpaid = orders.length;
+ this.get('orders').add(orders);
+ }
+
+ report.unpaid_skipped_sessions = _.keys(skipped_sessions);
+ }
+
+ return report;
+ },
+
+ _load_orders: function(){
+ var jsons = this.db.get_unpaid_orders();
+ var orders = [];
+ var not_loaded_count = 0;
+
+ for (var i = 0; i < jsons.length; i++) {
+ var json = jsons[i];
+ if (json.pos_session_id === this.pos_session.id) {
+ orders.push(new exports.Order({},{
+ pos: this,
+ json: json,
+ }));
+ } else {
+ not_loaded_count += 1;
+ }
+ }
+
+ if (not_loaded_count) {
+ console.info('There are '+not_loaded_count+' locally saved unpaid orders belonging to another session');
+ }
+
+ orders = orders.sort(function(a,b){
+ return a.sequence_number - b.sequence_number;
+ });
+
+ if (orders.length) {
+ this.get('orders').add(orders);
+ }
+ },
+
});
// Add fields to the list of read fields when a model is loaded
diff --git a/addons/point_of_sale/static/src/js/popups.js b/addons/point_of_sale/static/src/js/popups.js
index 1b22d46fe3d..3fc7b089795 100644
--- a/addons/point_of_sale/static/src/js/popups.js
+++ b/addons/point_of_sale/static/src/js/popups.js
@@ -221,14 +221,9 @@ var PasswordPopupWidget = NumberPopupWidget.extend({
});
gui.define_popup({name:'password', widget: PasswordPopupWidget});
-var UnsentOrdersPopupWidget = ConfirmPopupWidget.extend({
- template: 'UnsentOrdersPopupWidget',
+var OrderImportPopupWidget = PopupWidget.extend({
+ template: 'OrderImportPopupWidget',
});
-gui.define_popup({name:'unsent-orders', widget: UnsentOrdersPopupWidget});
-
-var UnpaidOrdersPopupWidget = ConfirmPopupWidget.extend({
- template: 'UnpaidOrdersPopupWidget',
-});
-gui.define_popup({name:'unpaid-orders', widget: UnpaidOrdersPopupWidget});
+gui.define_popup({name:'orderimport', widget: OrderImportPopupWidget});
});
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 98dc3e464d5..06a5bb74545 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -920,52 +920,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
@@ -1143,15 +1133,17 @@
Scan EAN-13
-
Unsent Orders
+
Orders
+
- - Show All Unsent Orders
- - Delete All Unsent Orders
-
-
Unpaid Orders
-
Hardware Status