Commit a5b8f0f3 by Damien Moulard Committed by Alexis Aoun

reception: prevent concurrent access to same order

parent da6d534e
...@@ -35,6 +35,15 @@ input[type="number"] { ...@@ -35,6 +35,15 @@ input[type="number"] {
margin-bottom: 1em; margin-bottom: 1em;
} }
.order_last_update {
font-weight: bold;
}
.order_modified_msg {
font-size: 2rem;
color: #e62720;
}
/* PRODUITS */ /* PRODUITS */
.page_body { .page_body {
height: 100%; height: 100%;
......
...@@ -11,12 +11,38 @@ var orders = [], ...@@ -11,12 +11,38 @@ var orders = [],
groups: [] groups: []
}, },
dbc = null, dbc = null,
sync = null; sync = null,
fingerprint = null;
/* UTILS */ /* UTILS */
/** /**
* Difference between two dates
* @param {Date} date1
* @param {Date} date2
* @returns difference object
*/
function dates_diff(date1, date2) {
var diff = {}
var tmp = date2 - date1;
tmp = Math.floor(tmp/1000);
diff.sec = tmp % 60;
tmp = Math.floor((tmp-diff.sec)/60);
diff.min = tmp % 60;
tmp = Math.floor((tmp-diff.min)/60);
diff.hours = tmp % 24;
tmp = Math.floor((tmp-diff.hours)/24);
diff.days = tmp;
return diff;
}
/**
* Wait for both ajax callbacks for reloading to avoid a js error * Wait for both ajax callbacks for reloading to avoid a js error
* -> reloading when ajax call not answered causes a popup to appear, which can be confusing * -> reloading when ajax call not answered causes a popup to appear, which can be confusing
*/ */
...@@ -25,6 +51,48 @@ function reload() { ...@@ -25,6 +51,48 @@ function reload() {
document.location.reload(); document.location.reload();
} }
/**
* Check for concurent access to same order before going to reception page.
* @param {Int} id
*/
function check_before_goto(id) {
const order_doc_id = 'order_' + id;
dbc.get(order_doc_id).then((doc) => {
console.log(doc);
if (doc.last_update.fingerprint !== null && doc.last_update.fingerprint !== fingerprint) {
time_diff = dates_diff(new Date(doc.last_update.timestamp), new Date())
diff_str = ``
if (time_diff.days !== 0) {
diff_str += `${time_diff.days} jour(s), `
}
if (time_diff.hours !== 0) {
diff_str += `${time_diff.hours} heure(s), `
}
if (time_diff.min !== 0) {
diff_str += `${time_diff.min} min, `
}
diff_str += `${time_diff.sec}s`
let modal_order_access = $('#templates #modal_order_access');
modal_order_access.find(".order_last_update").text(diff_str);
openModal(
modal_order_access.html(),
() => {
goto(id);
},
'Valider'
);
} else {
goto(id);
}
})
.catch((err) => {
console.log(err);
})
}
function goto(id) { function goto(id) {
document.location.href = "produits/" + id; document.location.href = "produits/" + id;
} }
...@@ -49,7 +117,7 @@ function group_goto(group_index) { ...@@ -49,7 +117,7 @@ function group_goto(group_index) {
} }
// go to first order // go to first order
goto(order_groups.groups[group_index][0]); check_before_goto(order_groups.groups[group_index][0]);
} }
/** /**
...@@ -62,7 +130,7 @@ function create_order_doc(order_data, go_to_order = false) { ...@@ -62,7 +130,7 @@ function create_order_doc(order_data, go_to_order = false) {
dbc.get(order_doc_id).then(() => { dbc.get(order_doc_id).then(() => {
if (go_to_order === true) { if (go_to_order === true) {
goto(order_data.id); check_before_goto(order_data.id);
} }
}) })
.catch(function (err) { .catch(function (err) {
...@@ -71,13 +139,16 @@ function create_order_doc(order_data, go_to_order = false) { ...@@ -71,13 +139,16 @@ function create_order_doc(order_data, go_to_order = false) {
let order_doc = { ...order_data }; let order_doc = { ...order_data };
order_doc._id = order_doc_id; order_doc._id = order_doc_id;
order_doc.last_update = {
timestamp: Date.now(),
fingerprint: fingerprint,
};
dbc.put(order_doc, (err) => { dbc.put(order_doc).then(() => {
if (!err) {
if (go_to_order === true) { if (go_to_order === true) {
goto(order_data.id); goto(order_data.id);
} }
} else { }).catch((err) => {
error = { error = {
msg: 'Erreur dans la creation de la commande dans couchdb', msg: 'Erreur dans la creation de la commande dans couchdb',
ctx: 'validatePrices', ctx: 'validatePrices',
...@@ -85,7 +156,6 @@ function create_order_doc(order_data, go_to_order = false) { ...@@ -85,7 +156,6 @@ function create_order_doc(order_data, go_to_order = false) {
}; };
report_JS_error(error, 'reception'); report_JS_error(error, 'reception');
console.log(error); console.log(error);
}
}); });
} }
}); });
...@@ -184,6 +254,8 @@ function group_action() { ...@@ -184,6 +254,8 @@ function group_action() {
// Create doc for each group order if doesn't exist // Create doc for each group order if doesn't exist
create_order_doc(selected_data[i]); create_order_doc(selected_data[i]);
// TODO (en dernier): ask before grouping if at least one of the orders is being updated somewhere else
} }
group_ids.sort(); group_ids.sort();
...@@ -442,6 +514,8 @@ $(document).ready(function() { ...@@ -442,6 +514,8 @@ $(document).ready(function() {
openModal(); openModal();
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
fingerprint = new Fingerprint({canvas: true}).get();
// Init couchdb // Init couchdb
dbc = new PouchDB(couchdb_dbname), dbc = new PouchDB(couchdb_dbname),
sync = PouchDB.sync(couchdb_dbname, couchdb_server, { sync = PouchDB.sync(couchdb_dbname, couchdb_server, {
...@@ -493,8 +567,6 @@ $(document).ready(function() { ...@@ -493,8 +567,6 @@ $(document).ready(function() {
console.log(err); console.log(err);
}); });
// TODO on button click to access order: verif fingerprint & timestamp
// Get or create order groups doc // Get or create order groups doc
dbc.get("grouped_orders").then((doc) => { dbc.get("grouped_orders").then((doc) => {
order_groups = doc; order_groups = doc;
......
...@@ -33,7 +33,8 @@ var reception_status = null, ...@@ -33,7 +33,8 @@ var reception_status = null,
barcodes = null; // Barcodes stored locally barcodes = null; // Barcodes stored locally
var dbc = null, var dbc = null,
sync = null; sync = null,
fingerprint = null;
/* UTILS */ /* UTILS */
...@@ -115,7 +116,10 @@ function select_product_from_bc(barcode) { ...@@ -115,7 +116,10 @@ function select_product_from_bc(barcode) {
* @param {int} order_id * @param {int} order_id
*/ */
function update_distant_order(order_id) { function update_distant_order(order_id) {
// TODO insert fingerprint & timestamp orders[order_id].last_update = {
timestamp: Date.now(),
fingerprint: fingerprint,
};
dbc.put(orders[order_id], (err, result) => { dbc.put(orders[order_id], (err, result) => {
if (!err && result !== undefined) { if (!err && result !== undefined) {
...@@ -127,6 +131,30 @@ function update_distant_order(order_id) { ...@@ -127,6 +131,30 @@ function update_distant_order(order_id) {
}); });
} }
/**
* Update distant orders with local data
* @param {int} order_id
*/
function update_distant_orders() {
for (let order_id in orders) {
orders[order_id].last_update = {
timestamp: Date.now(),
fingerprint: fingerprint,
};
}
dbc.bulkDocs(Object.values(orders)).then((response) => {
// Update rev of current orders after their update
for (let doc of response) {
let order_id = doc.id.split('_')[1];
orders[order_id]._rev = doc.rev
}
})
.catch((err) => {
console.log(err);
})
}
/* INIT */ /* INIT */
// Get order(s) data from server // Get order(s) data from server
...@@ -1272,6 +1300,12 @@ function send() { ...@@ -1272,6 +1300,12 @@ function send() {
} }
orders[order_id].reception_status = updateType; orders[order_id].reception_status = updateType;
// Unlock order
orders[order_id].last_update = {
timestamp: null,
fingerprint: null,
}
// Delete temp data // Delete temp data
delete orders[order_id].valid_products; delete orders[order_id].valid_products;
delete orders[order_id].updated_products; delete orders[order_id].updated_products;
...@@ -1344,7 +1378,6 @@ function send() { ...@@ -1344,7 +1378,6 @@ function send() {
} }
}); });
// TODO : for step 1, instead of saving a temp report, save data in couchdb ?
/* Create error report */ /* Create error report */
$.ajax({ $.ajax({
type: "POST", type: "POST",
...@@ -1445,6 +1478,24 @@ var get_barcodes = async function() { ...@@ -1445,6 +1478,24 @@ var get_barcodes = async function() {
* @param {Array} partners_display_data * @param {Array} partners_display_data
*/ */
function init_dom(partners_display_data) { function init_dom(partners_display_data) {
// Back button
$('#back_button').on('click', function () {
// Liberate current orders
for (let order_id in orders) {
orders[order_id].last_update = {
timestamp: null,
fingerprint: null,
};
}
dbc.bulkDocs(Object.values(orders)).then((response) => {
back();
})
.catch((err) => {
console.log(err);
})
})
// Grouped orders // Grouped orders
if (Object.keys(orders).length > 1) { if (Object.keys(orders).length > 1) {
$('#partner_name').html(Object.keys(orders).length + " commandes"); $('#partner_name').html(Object.keys(orders).length + " commandes");
...@@ -1504,7 +1555,7 @@ function init_dom(partners_display_data) { ...@@ -1504,7 +1555,7 @@ function init_dom(partners_display_data) {
$("#modal_qtiesValidated").load("/reception/reception_qtiesValidated"); $("#modal_qtiesValidated").load("/reception/reception_qtiesValidated");
} else { } else {
// Extra security, shouldn't get in here // Extra security, shouldn't get in here: reception status not valid
back(); back();
} }
...@@ -1621,6 +1672,8 @@ function init_dom(partners_display_data) { ...@@ -1621,6 +1672,8 @@ function init_dom(partners_display_data) {
$(document).ready(function() { $(document).ready(function() {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
fingerprint = new Fingerprint({canvas: true}).get();
// Load barcodes // Load barcodes
get_barcodes(); get_barcodes();
...@@ -1636,15 +1689,25 @@ $(document).ready(function() { ...@@ -1636,15 +1689,25 @@ $(document).ready(function() {
auto_compaction: false auto_compaction: false
}); });
// TODO on sync change : redirect (cf order_helper)
sync.on('change', function (info) { sync.on('change', function (info) {
console.log(info); if (info.direction === "pull") {
for (const doc of info.change.docs) {
// Redirect if one of the current order is being modified somewhere else
if (String(doc.id) in orders && orders[doc.id]._rev !== doc._rev) {
alert("Un autre navigateur est en train de modifier cette commande ! Vous allez être redirigé.e.");
back();
}
}
}
}).on('error', function (err) { }).on('error', function (err) {
if (err.status === 409) {
alert("Une erreur de synchronisation s'est produite, la commande a sûrement été modifiée sur un autre navigateur. Vous allez être redirigé.e.");
back();
}
console.log('erreur sync');
console.log(err); console.log(err);
}); });
// TODO insert fingerprint & timestamp on access to order
// Disable alert errors from datatables // Disable alert errors from datatables
$.fn.dataTable.ext.errMode = 'none'; $.fn.dataTable.ext.errMode = 'none';
...@@ -1759,6 +1822,9 @@ $(document).ready(function() { ...@@ -1759,6 +1822,9 @@ $(document).ready(function() {
// Load saved user comments, get it from first order // Load saved user comments, get it from first order
user_comments = orders[Object.keys(orders)[0]].user_comments || ""; user_comments = orders[Object.keys(orders)[0]].user_comments || "";
// Indicate that these orders are used in this navigator
update_distant_orders()
// Fetch orders data // Fetch orders data
fetch_data(); fetch_data();
......
...@@ -46,6 +46,21 @@ ...@@ -46,6 +46,21 @@
<p>Êtez-vous sûr ?</p> <p>Êtez-vous sûr ?</p>
<hr /> <hr />
</div> </div>
<div id="modal_order_access">
<h3>Attention !</h3>
<br/>
<p class="order_modified_msg">
Un autre navigateur a commencé à réceptionner cette commande il y a <span class="order_last_update"></span>.
</p><br/>
<p>
Si quelqu'un d'autre que vous est à l'origine de cette opération et que celle-ci est récente,
nous conseillons fortement de ne pas accéder à la commande afin d'éviter les conflits.
</p><br/>
<p>Voulez-vous quand même y accéder ?</p>
<hr/>
</div>
</div> </div>
<br/> <br/>
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
{% endif %} {% endif %}
<div class="page_body"> <div class="page_body">
<header class="flex-container"> <header class="flex-container">
<button class="right btn--danger" onclick="back()">Retour</button> <button class="right btn--danger" id="back_button">Retour</button>
<div class="w33 arrow-block txtcenter" id="header_step_one"> <div class="w33 arrow-block txtcenter" id="header_step_one">
<h4 id="header_step_one_content">Produits à compter </h4> <h4 id="header_step_one_content">Produits à compter </h4>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment