[IMP] point_of_sale: import / export of the point of sale offline state into / from text files.

This commit is contained in:
Frédéric van der Essen
2015-05-04 14:13:42 +02:00
parent ce114bebd8
commit d72d280a04
6 changed files with 250 additions and 71 deletions
+20 -1
View File
@@ -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{
+28 -8
View File
@@ -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'});
+24
View File
@@ -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
+3 -8
View File
@@ -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});
});
+46 -54
View File
@@ -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>