[IMP] point_of_sale: import / export of the point of sale offline state into / from text files.
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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'});
|
||||
|
||||
@@ -299,6 +299,30 @@ var Gui = core.Class.extend({
|
||||
$('body').append('<audio src="'+src+'" autoplay="true"></audio>');
|
||||
},
|
||||
|
||||
/* ---- 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");
|
||||
|
||||
$("<a>",{
|
||||
download: name || 'document.txt',
|
||||
href: URL.createObjectURL(blob),
|
||||
}).get(0).dispatchEvent(evt);
|
||||
},
|
||||
|
||||
/* ---- Gui: KEYBOARD INPUT ---- */
|
||||
|
||||
// This is a helper to handle numpad keyboard input.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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});
|
||||
|
||||
});
|
||||
|
||||
@@ -920,52 +920,6 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="UnsentOrdersPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-unsent-orders">
|
||||
<p class="title">Unsent Orders</p>
|
||||
<t t-if='widget.pos.db.get_orders().length === 0'>
|
||||
<p class='body'>
|
||||
There are no unsent orders
|
||||
</p>
|
||||
</t>
|
||||
<t t-if='widget.pos.db.get_orders().length > 0'>
|
||||
<p class='body traceback'>
|
||||
<t t-esc='JSON.stringify(widget.pos.db.get_orders(),null,2)' />
|
||||
</p>
|
||||
</t>
|
||||
<div class="footer">
|
||||
<div class="button confirm">
|
||||
Ok
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="UnpaidOrdersPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-unpaid-orders">
|
||||
<p class="title">Unpaid Orders</p>
|
||||
<t t-if='widget.pos.db.get_unpaid_orders().length === 0'>
|
||||
<p class='body'>
|
||||
There are no unpaid orders
|
||||
</p>
|
||||
</t>
|
||||
<t t-if='widget.pos.db.get_unpaid_orders().length > 0'>
|
||||
<p class='body traceback'>
|
||||
<t t-esc='JSON.stringify(widget.pos.db.get_unpaid_orders(),null,2)' />
|
||||
</p>
|
||||
</t>
|
||||
<div class="footer">
|
||||
<div class="button confirm">
|
||||
Ok
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="SelectionPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-selection">
|
||||
@@ -1041,6 +995,42 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="OrderImportPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-import">
|
||||
<p class="title">Finished Importing Orders</p>
|
||||
<t t-if="widget.options and widget.options.report">
|
||||
<t t-set="report" t-value="widget.options.report" />
|
||||
<t t-set="unpaid_skipped" t-value="(report.unpaid_skipped_existing || 0) + (report.unpaid_skipped_session || 0)" />
|
||||
<ul class='body'>
|
||||
<li>Successfully imported <b><t t-esc='report.paid || 0'/></b> paid orders</li>
|
||||
<li>Successfully imported <b><t t-esc='report.unpaid || 0'/></b> unpaid orders</li>
|
||||
<t t-if="unpaid_skipped">
|
||||
<li><b><t t-esc="unpaid_skipped"/></b> unpaid orders could not be imported
|
||||
<ul>
|
||||
<li><b><t t-esc="report.unpaid_skipped_existing || 0" /></b> were duplicates of existing orders</li>
|
||||
<li><b><t t-esc="report.unpaid_skipped_session || 0" /></b> belong to another session:
|
||||
<t t-if="report.unpaid_skipped_sessions">
|
||||
<ul>
|
||||
<li>Session ids: <b><t t-esc="JSON.stringify(report.unpaid_skipped_sessions)" /></b></li>
|
||||
</ul>
|
||||
</t>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
<div class="footer">
|
||||
<div class="button cancel">
|
||||
Ok
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="Product">
|
||||
<span class='product' t-att-data-product-id="product.id">
|
||||
<div class="product-img">
|
||||
@@ -1143,15 +1133,17 @@
|
||||
<li class="button custom_ean">Scan EAN-13</li>
|
||||
</ul>
|
||||
|
||||
<p class="category">Unsent Orders</p>
|
||||
<p class="category">Orders</p>
|
||||
|
||||
<ul>
|
||||
<li class="button show_orders">Show All Unsent Orders</li>
|
||||
<li class="button delete_orders">Delete All Unsent Orders</li>
|
||||
</ul>
|
||||
<p class="category">Unpaid Orders</p>
|
||||
<ul>
|
||||
<li class="button show_unpaid_orders">Show All Unpaid Orders</li>
|
||||
<li class="button delete_unpaid_orders">Delete All Unpaid Orders</li>
|
||||
<li class="button delete_orders">Delete Paid Orders</li>
|
||||
<li class="button delete_unpaid_orders">Delete Unpaid Orders</li>
|
||||
<li class="button export_paid_orders">Export Paid Orders</li>
|
||||
<li class="button export_unpaid_orders">Export Unpaid Orders</li>
|
||||
<li class="button import_orders" style="position:relative">
|
||||
Import Orders
|
||||
<input type="file" style="opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;margin:0;cursor:pointer"/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="category">Hardware Status</p>
|
||||
|
||||
Reference in New Issue
Block a user