/* Cette page traite l'inventaire d'un rayon ou d'une liste personnalisée de produits. Un objet 'shelf' peut donc ici être un rayon, ou une liste personnalisée. Sémantiquement, ici : list_to_process représente la liste des produits à inventorier list_processed la liste des produit déjà inventoriés */ var validation_msg = $('#validation_msg'), inventory_validated_msg = $('#inventory_validated'), process_all_items_msg = $('#process_all_items_msg'), faq_content = $("#FAQ_modal_content"), issues_reporting = $("#issues_reporting"), add_product_form = $("#add_product_form"), change_shelf_form = $("#change_shelf_form"), change_shelf_btn = $('#change_shelf_btn'); var shelf = null, parent_location = '/shelfs', originView = "shelf", // or custom_list (create from order view) list_to_process = [], table_to_process = null, table_processed = null, editing_item = null, // Store the item currently being edited editing_origin = "", // Keep track of where editing_item comes from processed_row_counter = 0, // Keep count of the order the item were added in processed list search_chars = [], user_comments = '', adding_product = false, // True if modal to add a product is open. barcodes = null, // Barcodes stored locally // datetime for which shelf's ongoing_inv_start_datetime is considered null default_inventory_start_datetime = "0001-01-01 00:00:00", selected_products_for_shelf_change = [], all_shelfs = null, // Use get_all_shelfs to access it's value debounce_timeout = null; let lastKeypressTime = 0; /* UTILS */ // polyfill to check for safe integers: Number method not supported by all browsers Number.isInteger = Number.isInteger || function(value) { return typeof value === 'number' && isFinite(value) && Math.floor(value) === value; }; if (!Number.MAX_SAFE_INTEGER) { Number.MAX_SAFE_INTEGER = 9007199254740991; // Math.pow(2, 53) - 1; } Number.isSafeInteger = Number.isSafeInteger || function (value) { return Number.isInteger(value) && Math.abs(value) <= Number.MAX_SAFE_INTEGER; }; function back() { document.location.href = parent_location; } function debounce(func, delay = 100) { clearTimeout(debounce_timeout); debounce_timeout = setTimeout(func, delay); } function get_added_qties_sum(item) { let total = null; function add(accumulator, a) { // for array sum result = 0; if (a) { if (item.uom_id[1] == "kg") { if (typeof a === "string") { a = a.replace(',', '.'); } result = parseFloat(accumulator) + parseFloat(a); result = result.toFixed(3); } else { result = parseInt(accumulator, 10) + parseInt(a, 10); } } return result; } if (typeof item.added_qties != "undefined" && item.added_qties.length > 0) { total = item.added_qties.reduce(add); } return total; } function barcode_analyzer(chars) { let barcode = chars; if (barcode && barcode.length >=13) { barcode = barcode.substring(barcode.length-13); } else if (barcode && barcode.length == 12 && barcode.indexOf('0') !== 0) { // User may use a scanner which remove leading 0 barcode = '0' + barcode; } else if (barcode && barcode.length >= 8) { // For EAN8 barcode = barcode.substring(barcode.length-8); } else { //manually submitted after correction var barcode_input = $('#search_input'); barcode = barcode_input.val(); } document.getElementById('search_input').value = ''; $('table.dataTable').DataTable() .search('') .draw(); // If modal to add a product is open if (adding_product) { $('input.add_product_input').val(barcode); do_add_product(); } else { select_product_from_bc(barcode); } search_chars = []; } /** * Option to display an icon next to edition input to cancel last value. * Disabled for now. Look for #reset_to_previous_qty to restore. * * WARNING: if you restore this functionality, update validateEdition() so * canceling last value is ignored if edition_cancel is pressed. */ // function reset_previous_value() { // if (editing_item !== null) { // if (typeof editing_item.added_qties !== "undefined") { // editing_item.qty -= editing_item.added_qties.pop(); // } // $('#edition_input').val(editing_item.qty); // $("#reset_to_previous_qty").hide(); // } // } function refresh() { location.reload(); } // Directly send a line to edition when barcode is read function select_product_from_bc(barcode) { if (barcode === "" || barcode === null || barcode === undefined) { return -1; } var found = null, qty = null; if (isValidEAN13(barcode) || isValidEAN8(barcode)) { var scannedProduct = barcodes.get_corresponding_odoo_product(barcode); if (scannedProduct === null) { alert("Le code-barre " + barcode + " ne correspond à aucun article connu."); return -1; } else { barcode = scannedProduct.barcode; if (scannedProduct.rule.length > 0 && scannedProduct.rule != "product") { qty = scannedProduct.qty; } } } else { alert("Le code-barre " + barcode + " n'est pas reconnu comme un EAN13 ou EAN8 valide."); return -1; } if (editing_item === null) { $.each(list_to_process, function(i, e) { if (e.barcode == barcode) { found = e; editing_origin = 'to_process'; } }); if (typeof shelf != 'undefined' && typeof shelf.list_processed != 'undefined') { $.each(shelf.list_processed, function(i, e) { if (e.barcode == barcode) { found = e; if (qty) { let message = "Attention, ce produit a déjà été compté.\n"; message += "La quantité " + qty + " n'a pas été ajoutée !\n"; // temporary add read qty and recorded one to added_qties to compute sum found.added_qties = [ qty, found.qty ]; message += "Le total serait " + get_added_qties_sum(found); alert(message); qty = null; } editing_origin = 'processed'; } }); } if (found !== null) { delete found.added_qties; setLineEdition(found, qty); if (editing_origin === 'to_process') { let row = table_to_process.row($('tr#'+found.id)); remove_from_toProcess(row); } else { let row = table_processed.row($('tr#'+found.id)); remove_from_processed(row); } } else { console.log('Code barre introuvable'); alert("Le produit bippé n'est pas enregistré dans ce rayon."); } } else if (barcode == editing_item.barcode && qty) { // We scan the same product as the current one let edition_input = $('#edition_input'); if (typeof editing_item.added_qties == "undefined") { editing_item.added_qties = [edition_input.val()]; } editing_item.added_qties.push(qty); // TODO : add an action icon to view added quantities editing_item.qty = get_added_qties_sum(editing_item); edition_input.val(editing_item.qty); } return null; } /* To make an element blink: Call this function on an element so blinking is handled Then simply add 'blink_me' class on the element to make it blink */ function handle_blinking_effect(element) { element.addEventListener('animationend', onAnimationEnd); element.addEventListener('webkitAnimationEnd', onAnimationEnd); function onAnimationEnd() { element.classList.remove('blink_me'); } } /* EDITION */ // When edition event is fired function edit_event(clicked) { // Remove from origin table var row_data = null; if (editing_origin == 'to_process') { let row = table_to_process.row(clicked.parents('tr')); row_data = row.data(); remove_from_toProcess(row); } else { let row = table_processed.row(clicked.parents('tr')); row_data = row.data(); remove_from_processed(row); } // Product goes to editing setLineEdition(row_data); // Reset search $('#search_input').val(''); $('table.dataTable').DataTable() .search('') .draw(); search_chars = []; } // Set edition area /** * If qty is not null, it comes from barcode reading result * */ function setLineEdition(item, qty) { var edition_input = $('#edition_input'), set_focus = true; editing_item = item; $('#product_name').text(editing_item.name); $('#product_uom').text(editing_item.uom_id[1]); if (editing_item.uom_id[0] == 1) { // Unit edition_input.attr('type', 'number').attr('step', 1) .attr('max', 9999); } else { edition_input.attr('type', 'number').attr('step', 0.001) .attr('max', 9999); } if (qty) { /* To prevent futur data mess if someone scans barcode while focus is on edition input qty is stored in editing_item object */ editing_item.qty = qty; edition_input.val(qty); set_focus = false; } // If item is reprocessed, set input with value if (editing_origin == 'processed') { edition_input.val(editing_item.qty); } if (set_focus === true) edition_input.focus(); // Make edition area blink when edition button clicked container_edition.classList.add('blink_me'); } // Clear edition function clearLineEdition() { editing_item = null; $('#product_name').text(''); $('#edition_input').val(''); $('#search_input').focus(); // $("#reset_to_previous_qty").hide(); } /** * Validate product edition. * Keep track of every qty change. */ function validateEdition() { if (editing_item != null) { const current_val = $("#edition_input").val(); // Let's verify if quantity have been changed if (current_val != editing_item.qty) { if (typeof editing_item.added_qties !== "undefined") { // total may have been affected by manual typing const total = get_added_qties_sum(editing_item); if (current_val != total) { // add difference in added_qties array editing_item.added_qties.push(current_val - total); } } else { // Init added_qties to take change into account editing_item.added_qties = [ editing_item.qty, current_val - editing_item.qty ]; } } editing_item.qty = current_val; if (editProductInfo(editing_item)) { clearLineEdition(); } } } /** * Unselect all rows from datatable * Only for table_to_process */ function unselect_all_rows() { $("#select_all_products_cb").prop("checked", false); table_to_process.rows().every(function() { const node = $(this.node()); node.removeClass('selected'); node.find(".select_product_cb").first() .prop("checked", false); return 0; }); selected_rows = []; } /* * Update a product info and add it to processed items. * If 'value' is set, use it as new value. */ function editProductInfo (productToEdit, value = null) { // If 'value' parameter not set, get value from edition input var newValue = (value == null) ? parseFloat($('#edition_input').val() .replace(',', '.')) : value; if (isNaN(newValue)) { alert("Veuillez rentrer une valeur valide dans le champ d'édition !"); return false; } // If uom is unit, prevent float if (productToEdit.uom_id[0] == 1 && !Number.isSafeInteger(newValue)) { alert('Vous ne pouvez pas rentrer de chiffre à virgule pour des produits à l\'unité.'); return false; } productToEdit.qty = newValue; add_to_processed(productToEdit); // Update local storage localStorage.setItem(originView + "_" + shelf.id, JSON.stringify(shelf)); return true; } /* Change shelf process */ /* data should be an array of {product_id: xx, shelf_id: yy} */ function record_products_shelf_on_server(data) { return new Promise(resolve => { $.ajax({ type: "POST", url: "/shelfs/change_products_shelfs", dataType: "json", data: JSON.stringify(data), traditional: true, contentType: "application/json; charset=utf-8", success: function(data) { if (typeof data.res !== "undefined" && typeof data.res.done !== "undefined") resolve(data.res.done); else resolve(null); }, error: function() { alert("Impossible de mettre à jour les données"); resolve(null); } }); }); } // call on change_shelf_btn click action async function open_change_shelf_modal() { selected_products_for_shelf_change = []; $('.select_product_cb:checked').each(function(idx, elt) { const row = $(elt).closest('tr'); selected_products_for_shelf_change.push(table_to_process.row(row).data()); }); if (selected_products_for_shelf_change.length > 0) { /* As button is not shown if no product is selected, should be always true But, with CSS changes, it could happen that length == 0 */ let shelfs = await get_all_shelfs(); if (shelfs !== null) { let modal_content = $('#templates #change_shelf_form').clone(), shelf_selector = $('<select>').addClass('shelf_selection'), table = modal_content.find('table tbody').empty(); /* construct shelfs selector */ // first of all, sort by name shelfs.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); // if ahead_shelfs_ids is not empty, put them ahead if (ahead_shelfs_ids.length > 0) { let to_move = {}, idx = 0; // find index of shelfs to move shelfs.forEach((shelf) => { if (ahead_shelfs_ids.indexOf(shelf.id) > -1) { to_move[shelf.id] = idx; } idx += 1; }); // Respecting ahead_shelfs_ids order, move shelf ahead // splice can not be used, since more than 1 elt could be involved let ahead_elts = []; ahead_shelfs_ids.forEach((shelf_id) => { let shelf = shelfs[to_move[shelf_id]]; ahead_elts.push(shelf); }); //remove ahead elts shelfs = shelfs.filter((item) => { return !ahead_elts.includes(item.id); }); // put them ahead by concatenation shelfs = ahead_elts.concat(shelfs); } shelfs.forEach((shelf) => { let option = $('<option>') .val(shelf.id) .text(shelf.name + ' (' + shelf.sort_order + ')'); shelf_selector.append(option); }); /* add product rows */ selected_products_for_shelf_change.forEach((product) => { let tr = $('<tr>').attr('data-id', product.id) .append($('<td>').text(product.name)) .append($('<td>').append(shelf_selector.clone())); table.append(tr); }); openModal( modal_content.html(), () => { if (is_time_to('change_product_shelf', 500)) { make_change = async () => { // Prepare data to be transmitted to server to be recorded let data = []; $('.overlay-content table tbody tr').each(function(idx, e) { data.push({ product_id : $(e).data('id'), shelf_id : $(e).find('select') .val() }); }); const update_result = await record_products_shelf_on_server(data); if (update_result !== null) { update_result.forEach((product_id) => { remove_from_toProcess(table_to_process.row($('tr#'+product_id))); }); let message = "L'opération a bien réussi."; if (update_result.length !== data.length) { message = "L'opération a partiellement réussi.\n"; message += (data.length - update_result.length) + " produit(s) non déplacé(s)."; //TODO: display which products changes were in error } $.notify( message, { globalPosition:"top right", className: "info" } ); } }; make_change(); } }, 'Changer les produits de rayons' ); } else { alert("Les informations des autres rayons n'ont pas pu être récupérées."); } } } /* LISTS HANDLING */ // Init Data & listeners function initLists() { if ('list_processed' in shelf) { // Remove processed items from items to process for (processed_item of shelf.list_processed) { var index_in_toProcess = list_to_process.findIndex(x => x.id == processed_item.id); if (index_in_toProcess > -1) { list_to_process.splice(index_in_toProcess, 1); } } } else { shelf.list_processed = []; } // Init table for items to process var columns_to_process = []; if (shelf.inventory_status !== "") { columns_to_process.push({data:"id", title: "id", visible: false}); } else { columns_to_process.push({ title: `<div id="table_header_select_all" class="txtcenter"> <input type="checkbox" id="select_all_products_cb" name="select_all_products_cb" value="all"> </div>`, className: "dt-body-center", orderable: false, render: function () { return `<input type="checkbox" class="select_product_cb" />`; }, width: "4%"}); } columns_to_process = columns_to_process.concat([ {data:"name", title:"Produit", width: "60%"}, {data:"uom_id.1", title:"Unité de mesure", className:"dt-body-center"}, { title:"Renseigner qté", defaultContent: "<a class='btn' id='process_item' href='#'><i class='far fa-edit'></i></a>", "className":"dt-body-center", orderable: false } ]); if (originView == 'custom_list') { columns_to_process.splice(1, 0, {data:"shelf_sortorder", title:"Rayon", className:"dt-body-center"}); } table_to_process = $('#table_to_process').DataTable({ data: list_to_process, columns: columns_to_process, rowId : "id", order: [ [ 0, "asc" ] ], scrollY: "28vh", scrollCollapse: true, paging: false, dom: 'lrtip', // Remove the search input from that table language: {url : '/static/js/datatables/french.json'} }); // Init table for processed items var columns_processed = [ {data:"row_counter", title:"row_counter", "visible": false}, {data:"id", title: "id", visible: false}, {data:"name", title:"Produit", width: "60%"}, {data:"qty", title:"Qté", className:"dt-body-center"}, {data:"uom_id.1", title:"Unité de mesure", className:"dt-body-center"}, { title:"Modifier qté", defaultContent: "<a class='btn' id='reprocess_item' href='#'><i class='far fa-edit'></i></a>", "className":"dt-body-center", orderable: false } ]; if (originView == 'custom_list') { columns_processed.splice(2, 0, {data:"shelf_sortorder", title:"Rayon", className:"dt-body-center"}); } table_processed = $('#table_processed').DataTable({ data: shelf.list_processed, columns: columns_processed, rowId : "id", order: [ [ 0, "desc" ] ], scrollY: "28vh", scrollCollapse: true, paging: false, dom: 'lrtip', // Remove the search input from that table language: {url : '/static/js/datatables/french.json'} }); /* Listeners on tables & search input */ // Edit line from items to process $('#table_to_process tbody').on('click', 'a#process_item', function () { // Prevent editing mutiple lines at a time if (editing_item == null) { editing_origin = "to_process"; edit_event($(this)); } }); // Edit line from items processed $('#table_processed tbody').on('click', 'a#reprocess_item', function () { // Prevent editing mutiple lines at a time if (editing_item == null) { editing_origin = "processed"; edit_event($(this)); } }); // Search input for both tables $('#search_input').on('keyup', function () { $('table.dataTable') .DataTable() .search(jQuery.fn.DataTable.ext.type.search.string(this.value)) // search without accents (see DataTable plugin) .draw(); }); // Cancel line editing $('#edition_cancel').on('click', function () { if (editing_item != null) { if (editing_origin == "to_process") { add_to_toProcess(editing_item); } else if (editing_origin == "processed") { add_to_processed(editing_item, false); } clearLineEdition(); } }); // Select row(s) on checkbox change (copied from orders_helper.js -only table_to_process changed-) $(table_to_process.table().header()).on('click', 'th #select_all_products_cb', function () { if (this.checked) { selected_rows = []; table_to_process.rows().every(function() { const node = $(this.node()); node.addClass('selected'); node.find(".select_product_cb").first() .prop("checked", true); // Save selected rows in case the table is updated selected_rows.push(this.data().id); return 0; }); change_shelf_btn.show(); } else { unselect_all_rows(); change_shelf_btn.hide(); } }); $(table_to_process.table().body()).on('click', '.select_product_cb', function () { if (this.checked) { change_shelf_btn.show(); } else { // must hide change_shelf_btn only if no other product is selected if ($('.select_product_cb:checked').length === 0) { change_shelf_btn.hide(); } } }); change_shelf_btn.click(open_change_shelf_modal); } // Add a line to the 'items to process' list function add_to_toProcess(product) { // Add to list list_to_process.push(product); // Add to table (no data binding...) var rowNode = table_to_process.row.add(product).draw(false) .node(); // Blinking effect on newly added row handle_blinking_effect(rowNode); $(rowNode).addClass('blink_me'); } // Remove a line from the 'items to process' list function remove_from_toProcess(row) { item = row.data(); // Remove from list var index = list_to_process.indexOf(item); if (index > -1) { list_to_process.splice(index, 1); } // Remove from table row.remove().draw(); } // Add a line to the 'items processed' list function add_to_processed(product, withCounter = true) { // Add to list shelf.list_processed.push(product); // Add a counter to display first the last row added if (withCounter) { product.row_counter = processed_row_counter; processed_row_counter++; } // Add to table (no data binding...) var rowNode = table_processed.row.add(product).draw(false) .node(); // Handle blinking efect for newly added row handle_blinking_effect(rowNode); $(rowNode).addClass('blink_me'); } // Remove a line from 'items processed' function remove_from_processed(row) { let item = row.data(); // Remove from list let index = shelf.list_processed.indexOf(item); if (index > -1) { shelf.list_processed.splice(index, 1); } //Remove from table row.remove().draw(); } /* ACTIONS */ // Set the quantity to 0 for all the remaining unprocessed items function confirmProcessAllItems() { openModal(); // Iterate over all rows in table of items to process table_to_process.rows().every(function () { var data = this.data(); editProductInfo(data, 0); return 1; }); // Reset data list_to_process = []; table_to_process.rows().remove() .draw(); closeModal(); } // Verifications before processing function pre_send() { if (list_to_process.length > 0 || editing_item != null) { alert('Il reste des produits à compter. Si ces produits sont introuvables, cliquez sur "Il n\'y a plus de produits à compter".'); } else { if (shelf.inventory_status != '') validation_msg.find('.validation_msg_step2').show(); openModal(validation_msg.html(), send, 'Confirmer', false); } } // Proceed with inventory: send the request to the server function send() { if (is_time_to('submit_inv_qties')) { // Loading on var wz = $('#main-waiting-zone').clone(); wz.find('.msg').text("Patience, cela peut prendre de nombreuses minutes s'il y a une centaine de produits"); openModal(wz.html()); // Add user comments to data sent to server shelf.user_comments = user_comments; var url = "../do_" + originView + "_inventory"; var call_begin_at = new Date().getTime(); $.ajax({ type: "PUT", url: url, dataType: "json", traditional: true, contentType: "application/json; charset=utf-8", data: JSON.stringify(shelf), success: function(data) { let next_step_call = back; // If step 1, display additionnal message in validation popup if (shelf.inventory_status == '') { inventory_validated_msg.find('#step1_validated').show(); next_step_call = refresh; } if (typeof data.res.inventory != 'undefined') { if (typeof data.res.inventory.missed != 'undefined' && data.res.inventory.missed.length > 0) { $('#products_missed_container').show(); for (p of data.res.inventory.missed) { $('ul#products_missed_list').append('<li>'+ p.product.name +'</li>'); } } } var msg = (originView == 'shelf') ? 'OK, je passe à la suite !' : 'Retour'; // Go to next step if modal closed openModal(inventory_validated_msg.html(), next_step_call, msg, true, false); $('#modal_closebtn_top').on('click', next_step_call); $('#modal_closebtn_bottom').on('click', next_step_call); // Clear local storage before leaving localStorage.removeItem(originView + '_' + shelf.id); }, error: function(jqXHR) { // 500 error has been thrown or web server sent a timeout if (jqXHR.status == 504) { /* django is too long to respond. Let it the same time laps before asking if the process is well done */ var now = new Date().getTime(); setTimeout( function() { $.ajax({ type: 'GET', url: '../inventory_process_state/' + shelf.id, success: function(rData) { if ('res' in rData && 'state' in rData.res) { // Verification for step 2 only ; step 1 is always fast if (shelf.inventory_status == 'step1_done' && rData.res.state != 'step1_done') { // shelf inventory has been already done localStorage.removeItem(originView + '_' + shelf.id); closeModal(); back(); } else { console.log('Still in process : need to call recursively to make other calls'); } } else { console.log(rData); } } }); } , now - call_begin_at ); } else if (jqXHR.status == 500) { var message = "Erreur lors de la sauvegarde des données. " + "Pas de panique, les données de l'inventaire n'ont pas été perdues ! " + "Merci de contacter un salarié et de réessayer plus tard."; if (typeof jqXHR.responseJSON != 'undefined' && typeof jqXHR.responseJSON.error != 'undefined') { //console.log(jqXHR.responseJSON.error); if ('busy' in jqXHR.responseJSON) { message = "Inventaire en cours de traitement."; } else if (jqXHR.responseJSON.error == 'FileExistsError') { //step1 file has been found => previous request succeeded message = "Les données avaient déjà été transmises...."; // Clear local storage before leaving localStorage.removeItem(originView + '_' + shelf.id); } } closeModal(); alert(message); back(); } } }); } else { alert('Clic reçu il y a moins de 5 secondes. La demande est en cours de traitement.'); } } function exit_adding_product() { $('input.add_product_input').val(''); adding_product = false; } /** * Set the ongoing inventory start datetime. * This operation is invisible to the user. */ function set_begin_inventory_datetime() { if (originView === 'shelf' && ( shelf.ongoing_inv_start_datetime === default_inventory_start_datetime || shelf.ongoing_inv_start_datetime === undefined ) ) { $.ajax({ type: "POST", url: "/shelfs/"+shelf.id+"/set_begin_inventory_datetime", dataType: "json", traditional: true, contentType: "application/json; charset=utf-8", success: function(data) { shelf.ongoing_inv_start_datetime = data.res.inventory_begin_datetime; // Update local storage localStorage.setItem(originView + "_" + shelf.id, JSON.stringify(shelf)); }, error: function() { console.log("Impossible de mettre à jour la date de début d'inventaire"); } }); } } // Add a product that's not in the list function open_adding_product() { if (originView == 'shelf') { adding_product = true; openModal(add_product_form.html(), do_add_product, 'Valider', false, true, exit_adding_product); $('input.add_product_input').focus(); } } function do_add_product() { prod_data = { barcode: $('input.add_product_input').val() }; openModal(); $.ajax({ type: "POST", url: "../"+shelf.id+"/add_product", dataType: "json", traditional: true, contentType: "application/json; charset=utf-8", data: JSON.stringify(prod_data), success: function() { exit_adding_product(); closeModal(); alert('Produit ajouté !'); location.reload(); }, error: function(data) { if (typeof data.responseJSON != 'undefined') { console.log(data.responseJSON); } exit_adding_product(); closeModal(); msg = ""; if (typeof data.responseJSON.res != 'undefined' && typeof data.responseJSON.res.msg != 'undefined') { msg = " (" + data.responseJSON.res.msg + ")"; } alert("Impossible d'ajouter le produit au rayon." + msg); } }); } function openFAQ() { openModal(faq_content.html(), function() {}, 'Compris !', true, false); } function openIssuesReport() { openModal(issues_reporting.html(), saveIssuesReport, 'Confirmer'); var textarea = $("#issues_report"); textarea.val(user_comments); textarea.focus(); } function saveIssuesReport() { user_comments = $('#issues_report').val(); $('#search_input').focus(); } /* INIT */ // (for shelf change) function get_all_shelfs() { return new Promise(resolve => { if (all_shelfs !== null) { resolve(all_shelfs); } else { $.ajax({ type: 'GET', url: "/shelfs/all/simple", dataType:"json", traditional: true, contentType: "application/json; charset=utf-8", success: function(data) { shelfs = null; if (typeof data.res !== "undefined" && data.res.length > 0) shelfs = data.res; resolve(shelfs); }, error: function(data) { err = {msg: "erreur serveur lors de la récupération des rayons", ctx: 'get_all_shelfs'}; if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { err.msg += ' : ' + data.responseJSON.error; } report_JS_error(err, 'shelf_inventory'); resolve(null); } }); } }); } // Get shelf data from server if not in local storage function get_shelf_data() { var url = (originView == 'shelf') ? '../' + shelf.id : '../get_custom_list_data?id=' + shelf.id; $.ajax({ type: 'GET', url: url, dataType:"json", traditional: true, contentType: "application/json; charset=utf-8", success: function(data) { shelf = data.res; init(); set_begin_inventory_datetime(); }, error: function(data) { if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { console.log(data.responseJSON.error); } alert('Les données n\'ont pas pu être récupérées, réessayez plus tard.'); } }); } // Init page : to be launched when shelf data is here function init() { // Products passed at page loading // TODO: get products by ajax for better ui experience (? -> warning js at loading) // TODO : What happens if products are being put or removed from the self before the end of the inventory ? list_to_process = products; initLists(); // Set processed_row_counter to current value if ('list_processed' in shelf) { for (let processed_item of shelf['list_processed']) { if (processed_item.row_counter > processed_row_counter) { processed_row_counter = processed_item.row_counter; } } } processed_row_counter++; // Set DOM if (originView == "shelf") { $('#shelf_name').text(shelf.name + ' (numéro ' + shelf.sort_order + ')'); } else { $('#page_title').text("Inventaire du"); $('#shelf_name').text(shelf.datetime_created); $("#add_product_to_shelf").hide(); } if (shelf.inventory_status == "") { // Step 1 // Header $('#header_step_one').addClass('step_one_active'); // Container items to process $('#container_left').css('border', '3px solid #212529'); // Container processed items $('#container_right').css('border', '3px solid #0275D8'); // Edition $('#edition_header').text('Quantité en rayon'); // Validation button // $('#validation_button').html("<button class='btn--primary full_width_button' id='validate_inventory'>J'ai fini de compter</button>") $('#validate_inventory').addClass('btn--primary'); $('#add_product_to_shelf').addClass('btn--inverse'); } else { // Step 2 $('#header_step_two').addClass('step_two_active'); var check_icon = document.createElement('i'); check_icon.className = 'far fa-check-circle'; $('#header_step_one_content').append(check_icon); // Containers $('#container_left').css('border', '3px solid #0275D8'); $('#container_right').css('border', '3px solid #5CB85C'); // Edition $('#edition_header').text('Quantité en réserve'); // Validation button $('#validate_inventory').addClass('btn--success'); $('#add_product_to_shelf').addClass('btn--primary'); } // Buttons Listeners $(document).on('click', 'button#validate_inventory', pre_send); $(document).on('click', 'button#add_product_to_shelf', open_adding_product); $(document).on('click', 'button#open_issues_report', openIssuesReport); $(document).on('click', 'button#open_faq', openFAQ); $(document).on('click', 'button#process_all_items', function () { openModal(process_all_items_msg.html(), confirmProcessAllItems, 'Confirmer', false); }); // Action at modal closing $(document).on('click', 'a#modal_closebtn_top', exit_adding_product); // Load FAQ modal content faq_content.load("/shelfs/shelf_inventory_FAQ"); // Handle blinking effect on edition area var container_edition = document.querySelector('#container_edition'); handle_blinking_effect(container_edition); // Disable mousewheel on an input number field when in focus $('#edition_input').on('focus', function () { $(this).on('wheel.disableScroll', function (e) { e.preventDefault(); /* Option to possibly enable page scrolling when mouse over the input, but : - deltaY is not in pixels in Firefox - movement not fluid on other browsers var scrollTo = (e.originalEvent.deltaY) + $(document.documentElement).scrollTop(); $(document.documentElement).scrollTop(scrollTo); */ }); // if ($(this).val().length > 0) { // let reset_icon = $("#reset_to_previous_qty"); // reset_icon.show(); // reset_icon.off(); // reset_icon.on("click", reset_previous_value); // } else { // $("#reset_to_previous_qty").hide(); // } }) .on('blur', function () { $(this).off('wheel.disableScroll'); }); // Manual and textual input $('#search_input').keypress(function(e) { if (e.which >= 48 && e.which <= 57) { // figures [0-9] search_chars.push(String.fromCharCode(e.which)); } else if (e.which == 13 || search_chars.length >= 13) { e.stopPropagation(); // Prevent validation edition in this specific case debounce(barcode_analyzer); // Avoid concurrent barcode analysing } }); $(document).pos(); $(document).on('keydown','#edition_input',function(event) { const keypressTime = event.timeStamp; const timeDifference = keypressTime - lastKeypressTime; lastKeypressTime = keypressTime; // Assuming a scanner would input faster than 50ms between keystrokes if (timeDifference < 50) { // Looks like scanner input, ignore or handle differently event.preventDefault(); // You can display a message or handle the input differently alert("Vous ne pouvez pas scanner pour saisir une quantité."); } }); $(document).on('scan.pos.barcode', function(event) { //access `event.code` - barcode data var barcode = event.code; debounce(() => barcode_analyzer(barcode)); // Avoid concurrent barcode analysing }); } // Load barcodes at page loading, then barcodes are stored locally var get_barcodes = async function() { if (barcodes == null) barcodes = await init_barcodes(); }; $(document).ready(function() { // Get Route parameter $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); var pathArray = window.location.pathname.split('/'); shelf = {id: pathArray[pathArray.length-1]}; // Working on a shelf if (pathArray.includes('shelf_inventory')) { originView = 'shelf'; parent_location = '/shelfs'; } else { originView = 'custom_list'; parent_location = '/inventory/custom_lists'; } // Get shelf data from local storage if (Modernizr.localstorage) { var stored_shelf = JSON.parse(localStorage.getItem(originView + '_' + shelf.id)); if (stored_shelf != null) { shelf = stored_shelf; init(); set_begin_inventory_datetime(); } else { // Get shelf info if not coming from shelves list get_shelf_data(); } } else { get_shelf_data(); } get_barcodes(); /** * Validate edition if enter pressed, wherever the focus is. * validateEdition is triggered only if a product is in the edition area. */ $(document).keypress(function(event) { if (event.keyCode == 13 || event.which == 13) { validateEdition(); } }); });