/* Logique : Cette page peut avoir à traiter un groupe de commandes ou une unique commande. Pour garder une unique logique, une commande unique sera considérée comme un groupe de une commande. Sémantiquement, ici : list_to_process représente la liste des produits à réceptionner list_processed la liste des produit déjà réceptionnés */ /** * Associative array of current order(s) * If more than 1 element: group of orders * If 1 element: single order */ var orders = {}, group_ids = [], product_coeffs = []; var reception_status = null, list_to_process = [], list_processed = [], table_to_process = null, table_processed = null, editing_product = null, // Store the product currently being edited editing_origin = null, // Keep track of where editing_product comes from processed_row_counter = 0, // Order in which products were added in processed list user_comments = "", updatedProducts = [], // Keep record of updated products validProducts = [], // Keep record of directly validated products updateType = "", // step 1: qty_valid; step2: br_valid barcodes = null, // Barcodes stored locally priceToWeightIsCorrect = true, suppliers_products = [], // All products of current order(s) supplier(s) products_to_add = [], // Products to add to order re_editing_qty = false; // During prices edition, edit qty mode enabled var dbc = null, sync = null, fingerprint = null; let lastKeypressTime = 0; /* UTILS */ function back() { document.location.href = "/reception"; } /** * Dingle order or grouped orders? * @returns Boolean */ function is_grouped_order() { return Object.keys(orders).length > 1; } /** * Get distinct suppliers id of current orders * @returns Boolean */ function get_suppliers_id() { let suppliers_id = []; for (var order_id in orders) { if ('partner_id' in orders[order_id]) { // check for versions transition suppliers_id.push(orders[order_id].partner_id); } } return suppliers_id; } /** Search if the product being edited is already in the updated products. * Returns its index or -1. */ function searchUpdatedProduct() { try { if (editing_product != null) { for (var i=0; i < updatedProducts.length; i++) { if (updatedProducts[i].product_id[0] == editing_product.product_id[0]) { return i; } } } } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'searchUpdatedProduct'}; console.error(err); report_JS_error(err, 'reception'); } return -1; } // Directly send a line to edition when barcode is read function select_product_from_bc(barcode) { try { if (editing_product == null) { var scannedProduct = barcodes.get_corresponding_odoo_product(barcode); priceToWeightIsCorrect = true; if (scannedProduct == null) { alert("Le code-barre " + barcode + " ne correspond à aucun article connu."); return -1; } var foundProduct = {data: null, place: null}; // Does the product come from to_process ? $.each(list_to_process, function(i, e) { if (e.product_id[0] == scannedProduct.data[barcodes['keys']['id']]) { foundProduct.data = e; foundProduct.place = 'to_process'; } }); // Does the product come from processed ? if (foundProduct.data == null) { $.each(list_processed, function(i, e) { if (e.product_id[0] == scannedProduct.data[barcodes['keys']['id']]) { foundProduct.data = JSON.parse(JSON.stringify(e)); foundProduct.data.product_qty = null; // Set qty to null from product already scanned foundProduct.place = 'processed'; } }); } if (foundProduct.data !== null) { if (foundProduct.data.product_uom[0] == 21) { //if qty is in weight if (scannedProduct.rule === 'weight') { editing_product = foundProduct.data; foundProduct.weightAddition = true; // product weight is directly added editProductInfo(foundProduct.data, scannedProduct.qty); editing_product = null; } else if (scannedProduct.rule === 'price_to_weight') { openModal($('#templates #modal_confirm_price_to_weight').html(), price_to_weight_is_wrong, 'Non', false, true, price_to_weight_confirmed_callback(foundProduct, scannedProduct)); setupPopUpBtnStyle(scannedProduct); } } if (scannedProduct.rule !== 'price_to_weight') { if (foundProduct.data.product_uom[0] != 21) { setLineEdition(foundProduct.data); } if (foundProduct.place === 'to_process') { let row = table_to_process.row($('#'+foundProduct.data.product_id[0])); remove_from_toProcess(row, foundProduct.data); } // Don't remove product from processed list } } } } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'select_product_from_bc'}; console.error(err); report_JS_error(err, 'reception'); } return 0; } /** * Update couchdb order * @param {int} order_id */ function update_distant_order(order_id) { orders[order_id].last_update = { timestamp: Date.now(), fingerprint: fingerprint }; dbc.put(orders[order_id], (err, result) => { if (!err && result !== undefined) { orders[order_id]._rev = result.rev; } else { alert("Erreur lors de la sauvegarde de la commande... Si l'erreur persiste contactez un administrateur svp."); console.log(err); } }); } /** * 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); }); } function price_to_weight_confirmed_callback(foundProduct, scannedProduct) { return function() { let newQty = null; if (priceToWeightIsCorrect) { newQty = scannedProduct.qty; } else { let tmp = Number((scannedProduct.value/document.getElementById("new_price_to_weight").value).toFixed(3)); if (isFinite(tmp)) { newQty = tmp; } } if (foundProduct.data !== null && newQty != null) { if (foundProduct.place === 'to_process') { let row = table_to_process.row($('#'+foundProduct.data.product_id[0])); remove_from_toProcess(row, foundProduct.data); } editing_product = foundProduct.data; editProductInfo(foundProduct.data, newQty); editing_product = null; resetPopUpButtons(); } }; } function price_to_weight_is_wrong() { document.getElementById("new_price_to_weight").style.display = ""; document.getElementsByClassName("btn--success")[0].style.display = "none"; document.querySelector('#modal_closebtn_bottom').innerHTML = 'OK'; priceToWeightIsCorrect = false; } function setupPopUpBtnStyle(p) { //On inverse en quelque sorte les boutons succes et d'annulation en mettant "Oui" sur le btn d'annulation // et "Non" sur le bouton de reussite. //Cela nous permet de reecrire moins de code puisque si la reponse est Oui on ne veut //rien modifier et sortir du pop up, ce qui correspond au comportement du bouton annulation //(ou aussi appeler cancel button) document.querySelector('#modal_closebtn_bottom').innerHTML = 'Oui'; document.getElementById("modal_closebtn_bottom").style.backgroundColor = "green"; document.getElementsByClassName("btn--success")[0].style.backgroundColor = "red"; document.querySelector('#product_to_verify').innerHTML = p.data[0]; document.querySelector('#price_to_verify').innerHTML = p.data[6]; document.getElementById("new_price_to_weight").style.display = "none"; document.getElementsByClassName("btn--success")[0].style.display = ""; } function resetPopUpButtons() { document.getElementsByClassName("btn--success")[0].style.display = ""; document.getElementsByClassName("btn--success")[0].style.backgroundColor = ""; document.querySelector('#modal_closebtn_bottom').style.backgroundColor = ""; } /* FETCH SERVER DATA */ function store_received_product_coeffs(coeffs) { for (let i=0; i<coeffs.length; i++) { if (product_coeffs.indexOf(coeffs[i]) == -1) product_coeffs.push(coeffs[i]); } } /** * Get order(s) data from server * @param {Array} po_ids if set, fetch data for these po only */ function fetch_data(po_ids = null) { let po_to_fetch = (po_ids === null) ? group_ids : po_ids; try { $.ajax({ type: 'POST', url: '../get_orders_lines', dataType:"json", traditional: true, contentType: "application/json; charset=utf-8", data: JSON.stringify({'po_ids' : po_to_fetch}), success: function(data) { // for each order for (order_data of data.orders) { store_received_product_coeffs(order_data.used_coeffs); // for each product in order for (i in order_data.po) { // If in step 2, find old qty in previous step data if ( reception_status == 'qty_valid' && "previous_steps_data" in orders[order_data.id_po] && "False" in orders[order_data.id_po]["previous_steps_data"] && "updated_products" in orders[order_data.id_po]["previous_steps_data"]["False"] // extra + secturity ) { // For each updated product in step 1 for (let step1_updated_product of orders[order_data.id_po]["previous_steps_data"]["False"]["updated_products"]) { // If product found if (step1_updated_product["product_id"][0] === order_data.po[i]["product_id"][0]) { // Add old qty order_data.po[i].old_qty = step1_updated_product.old_qty; } } } // Does product already exists in list_to_process? var existing_index = null; for (var j = 0; j < list_to_process.length; j++) { if (order_data.po[i].product_id[0] == list_to_process[j].product_id[0]) { existing_index = j; break; } } // Products already exists: it is present in different orders if (existing_index != null) { // Add order id and product id to product list for other orders data if (!('other_orders_data' in list_to_process[existing_index])) { list_to_process[existing_index]['other_orders_data'] = []; } list_to_process[existing_index].other_orders_data.push({ id_po : order_data.id_po, id_product : order_data.po[i].id, initial_qty : order_data.po[i].product_qty }); // If in step 1, concatenate qty in list_to_process if (reception_status == 'False') { list_to_process[existing_index].product_qty += order_data.po[i].product_qty; list_to_process[existing_index].package_qty += order_data.po[i].package_qty; list_to_process[existing_index].product_qty_package += order_data.po[i].product_qty_package; } } else { // Add order key in products let order_full_data = orders[order_data.id_po]; order_data.po[i].order_key = order_full_data.key; // Add product to list_to_process list_to_process.push(order_data.po[i]); // Save order id to keep track of where product comes from list_to_process[list_to_process.length-1]['id_po'] = order_data.id_po; } } } initLists(); }, error: function() { alert('Les données n\'ont pas pu être récupérées, réessayez plus tard.'); } }); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'fetch_data'}; console.error(err); report_JS_error(err, 'reception'); } } // Load barcodes at page loading, then barcodes are stored locally var get_barcodes = async function() { if (barcodes == null) barcodes = await init_barcodes(); }; // Get labels to print for current orders from server function get_pdf_labels() { try { if (is_time_to('print_pdf_labels', 10000)) { // Concatenate orders id into a string, separated with comas, to retrieve oids = group_ids.join(','); // Send request & diret download pdf var filename = "codebarres_" + group_ids[0] + ".pdf"; $.ajax({ url: "../../orders/get_pdf_labels?oids=" + oids, success: download.bind(true, "pdf", filename) }); } else { alert("Vous avez cliqué il y a moins de 10s... Patience, la demande est en cours de traitement."); } } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'get_pdf_labels'}; console.error(err); report_JS_error(err, 'reception'); } } /** * Get products of order(s) supplier(s) if not already fetched */ function fetch_suppliers_products() { if (suppliers_products.length === 0) { openModal(); let suppliers_id = get_suppliers_id(); // Fetch supplier products $.ajax({ type: 'GET', url: "/orders/get_supplier_products", data: { sids: suppliers_id }, dataType:"json", traditional: true, contentType: "application/json; charset=utf-8", success: function(data) { suppliers_products = data.res.products; // Filter supplier products on products already in orders suppliers_products = suppliers_products.filter(p => list_to_process.findIndex(ptp => ptp.product_id[1] === p.name) === -1); suppliers_products = suppliers_products.filter(p => list_processed.findIndex(pp => pp.product_id[1] === p.name) === -1); closeModal(); set_add_products_modal(); }, error: function(data) { err = {msg: "erreur serveur lors de la récupération des produits du fournisseur", ctx: 'get_supplier_products'}; if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { err.msg += ' : ' + data.responseJSON.error; } report_JS_error(err, 'reception'); closeModal(); alert('Erreur lors de la récupération des produits, réessayer plus tard.'); } }); } else { set_add_products_modal(); } } /* LISTS HANDLING */ // Init Data & listeners function initLists() { try { // Set action buttons for remaining items if ( add_all_left_is_good_qties === "True" && reception_status == "False" || add_all_left_is_good_prices === "True" && reception_status == "qty_valid" ) { $("#remaining_lines_actions_area").addClass("connected_actions"); $("#all_left_is_good").show(); } // Enable validation buttons now the data's here if (reception_status == "False") { document.getElementById("valid_qty").disabled = false; document.getElementById("valid_all_qties").disabled = false; } else if (reception_status == "qty_valid") { document.getElementById("valid_uprice").disabled = false; document.getElementById("valid_all_uprices").disabled = false; } // Set processed and to_process lists based on saved data for (var i = 0; i < updatedProducts.length; i++) { let product = updatedProducts[i]; product['row_counter'] = -1; list_processed.push(product); let toProcess_index = list_to_process.findIndex(x => x.id == updatedProducts[i]['id']); if (toProcess_index > -1) { list_to_process.splice(toProcess_index, 1); } } for (var j = 0; j < validProducts.length; j++) { let toProcess_index = list_to_process.findIndex(x => x.id == validProducts[j]); if (toProcess_index > -1) { let product = list_to_process[toProcess_index]; product['row_counter'] = -1; list_processed.push(product); list_to_process.splice(toProcess_index, 1); } } let columns_to_process = []; let columns_processed = []; // In case of group orders, add "Order" as first column for ordering if (is_grouped_order()) { columns_to_process.push({ data:"order_key", title: "n°", className: "dt-body-center", width: "15px" }); } // Titles for Qty column const base_qty_title = "Qté"; const qty_title_tooltip = `<div class="tooltip tt_twolines"> Qté <span class="tooltiptext">Qté comptée / Qté commandée</span> </div>`; columns_to_process = columns_to_process.concat([ {data:"product_id.0", title: "id", visible: false}, {data:"shelf_sortorder", title: "Rayon", className: "dt-body-center", width: "4%"}, { data:"product_id.1", title:"Produit", render: function (data, type, full) { // Add tooltip with barcode over product name let display_barcode = "Aucun"; if ('barcode' in full) { display_barcode = full.barcode; } let supplier_code = "Aucune"; if ('supplier_code' in full && full.supplier_code) { supplier_code = full.supplier_code; } return '<div class="tooltip">' + data + ' <span class="tooltiptext tooltip-lg tt_twolines">Code barre : ' + display_barcode + ' Réf. fournisseur : ' + supplier_code + '</span> </div>'; } }, { data:"product_uom.1", title: "Unité vente", className:"dt-body-center", orderable: false, width: "5%", render: function (data) { if (display_autres === "True" && data.toLowerCase().indexOf('unit') === 0) { return "U"; } else { return data; } } }, { data:"product_qty", title: (reception_status == "qty_valid") ? qty_title_tooltip : base_qty_title, className: (reception_status == "qty_valid") ? "dt-body-center product_qty_cell" : "dt-body-center", width: "5%", render: function (data, type, full) { if (reception_status == "False") { return data; } else if ("old_qty" in full) { return `${data}/${full.old_qty}`; } else { return `${data}/${data}`; } } }, { data:"price_unit", title:"Prix unit.", className:"dt-body-center", visible: (reception_status == "qty_valid"), width: "5%" }, { title:"Editer", defaultContent: "<a class='btn toProcess_line_edit' href='#'><i class='far fa-edit'></i></a>", className:"dt-body-center", orderable: false, width: "5%" }, { title:"Valider", defaultContent: "<a class='btn toProcess_line_valid' href='#'><i class='far fa-check-square'></i></a>", className:"dt-body-center", orderable: false, width: "5%" }, { title:"", defaultContent: "<select class='select_product_action'><option value=''></option><option value='supplier_shortage'>Rupture fournisseur</option></select>", className:"dt-body-center", orderable: false, visible: display_autres === "True", width: "5%" } ]); columns_processed = [ {data:"row_counter", title:"row_counter", visible: false}, // Hidden counter to display last row first {data:"shelf_sortorder", title: "Rayon", className:"dt-body-center", width: "4%"}, { data:"product_id.1", title:"Produit", // width: "55%", render: function (data, type, full) { // Add tooltip with barcode over product name let display_barcode = "Aucun"; if ('barcode' in full) { display_barcode = full.barcode; } let supplier_code = "Aucune"; if ('supplier_code' in full && full.supplier_code) { supplier_code = full.supplier_code; } let display = '<div class="tooltip">' + data + ' <span class="tooltiptext tooltip-lg tt_twolines">Code barre : ' + display_barcode + ' Réf. fournisseur : ' + supplier_code + '</span> </div>'; if (full.supplier_shortage) { display += ' <div class="tooltip"><i class="fas fa-info-circle"></i>' + ' <span class="tooltiptext tt_twolines">Rupture fournisseur' + '</span> </div>'; } return display; } }, {data:"product_uom.1", title: "Unité vente", className:"dt-body-center", orderable: false, width: "5%"}, { data:"product_qty", title: qty_title_tooltip, className: (reception_status == "qty_valid") ? "dt-head-center dt-body-center product_qty_cell" : "dt-head-center dt-body-center", width: "5%", // visible: (reception_status == "False"), render: function (data, type, full) { let disp = [ data, (full.old_qty !== undefined) ? full.old_qty : data ].join("/"); return disp; }, orderable: false }, { data:"price_unit", title:"Prix unit", className:"dt-body-center", visible: (reception_status == "qty_valid"), width: "5%" }, { title:"Editer", defaultContent: "<a class='btn' id='processed_line_edit' href='#'><i class='far fa-edit'></i></a>", className:"dt-body-center", orderable: false, width: "5%" }, { title:"Autres", className:"dt-body-center", orderable: false, visible: display_autres === "True", render: function (data, type, full) { let disabled = (full.supplier_shortage) ? "disabled" : ''; return "<select class='select_product_action'>" + "<option value=''></option>" + "<option value='supplier_shortage' "+disabled+">Rupture fournisseur</option>" + "</select>"; } } ]; table_to_process_ordering = [ [ 0, "asc" ] ]; // For grouped orders, order first by number of order, then by product id if (is_grouped_order()) { table_to_process_ordering.push([ 1, "asc" ]); } // Init table for to_process content table_to_process = $('#table_to_process').DataTable({ data: list_to_process, columns: columns_to_process, rowId : "product_id.0", order: table_to_process_ordering, scrollY: "33vh", scrollCollapse: true, paging: false, dom: 'lrtip', // Remove the search input from that table language: {url : '/static/js/datatables/french.json'}, createdRow: function(row) { // Add class to rows with product with qty at 0 var row_data = $('#table_to_process').DataTable() .row(row) .data(); if (row_data !== undefined && row_data.product_qty === 0) { for (var i = 0; i < row.cells.length; i++) { const cell_node = row.cells[i]; $(cell_node).addClass('row_product_no_qty'); } } else if ( row_data !== undefined && row_data.product_qty !== 0 && 'old_qty' in row_data && row_data.old_qty != row_data.product_qty ) { for (var j = 0; j < row.cells.length; j++) { const cell_node = row.cells[j]; $(cell_node).addClass('row_product_qty_changed'); } } } }); // Init table for processed content table_processed = $('#table_processed').DataTable({ data: list_processed, columns: columns_processed, rowId : "product_id.0", 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'}, createdRow: function(row) { var row_data = $('#table_processed').DataTable() .row(row) .data(); if (row_data !== undefined && row_data.product_qty === 0) { for (var i = 0; i < row.cells.length; i++) { const cell_node = row.cells[i]; $(cell_node).addClass('row_product_no_qty'); } } else if ( row_data !== undefined && row_data.product_qty !== 0 && 'old_qty' in row_data && row_data.old_qty != row_data.product_qty ) { for (var j = 0; j < row.cells.length; j++) { const cell_node = row.cells[j]; $(cell_node).addClass('row_product_qty_changed'); } } } }); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'initLists: init tables'}; console.error(err); report_JS_error(err, 'reception'); } /* Listeners */ // Direct valid from to_process $('#table_to_process tbody').on('click', 'a.toProcess_line_valid', function () { if (is_time_to('reception_direct_valid_order_line', 500)) { try { let row = table_to_process.row($(this).parents('tr')); let data = row.data(); add_to_processed(data); remove_from_toProcess(row, data); // Update product's order if (!orders[data.id_po]['valid_products']) { orders[data.id_po]['valid_products'] = []; } orders[data.id_po]['valid_products'].push(data['id']); update_distant_order(data.id_po); // Reset search document.getElementById('search_input').value = ''; $('table.dataTable').DataTable() .search('') .draw(); // Re set focus on input document.getElementById('search_input').focus(); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'initLists: listener validate line'}; console.error(err); report_JS_error(err, 'reception'); } } }); // Edit to_process line $('#table_to_process tbody').on('click', 'a.toProcess_line_edit', function () { try { // Prevent editing mutiple lines at a time if (editing_product == null) { var row = table_to_process.row($(this).parents('tr')); var data = row.data(); // Product goes to editing editing_origin = "to_process"; setLineEdition(data); remove_from_toProcess(row, data); document.getElementById('search_input').value = ''; $('table.dataTable').DataTable() .search('') .draw(); } } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'initLists : listener edit line from list to process'}; console.error(err); report_JS_error(err, 'reception'); } }); $('#table_to_process tbody').on('change', '.select_product_action', function () { try { if ($(this).val() == 'supplier_shortage') { var row = table_to_process.row($(this).parents('tr')); var data = row.data(); var modal_shortage = $('#modal_set_supplier_shortage'); modal_shortage.find(".supplier_shortage_product").text(' ' + data.product_id[1]); modal_shortage.find(".supplier_shortage_supplier").text(' ' + data.partner_id[1]); openModal( modal_shortage.html(), function() { set_supplier_shortage(row, data); }, 'Valider', true, true, function() { $(".select_product_action").val(''); } ); } } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'initLists : listener set supplier shortage'}; console.error(err); report_JS_error(err, 'reception'); } }); // Edit processed line $('#table_processed tbody').on('click', 'a#processed_line_edit', function () { try { // Prevent editing mutiple lines at a time if (editing_product == null) { var row = table_processed.row($(this).parents('tr')); var data = row.data(); //Go to editing editing_origin = "processed"; setLineEdition(row.data()); remove_from_processed(row, data); document.getElementById('search_input').value = ''; $('table.dataTable').DataTable() .search('') .draw(); } } catch (e) { err = { msg: e.name + ' : ' + e.message, ctx: 'initLists: listener edit line from processed list' }; console.error(err); report_JS_error(err, 'reception'); } }); $('#table_processed tbody').on('change', '.select_product_action', function () { try { if ($(this).val() == 'supplier_shortage') { var row = table_processed.row($(this).parents('tr')); var data = row.data(); var modal_shortage = $('#modal_set_supplier_shortage'); modal_shortage.find(".supplier_shortage_product").text(' ' + data.product_id[1]); modal_shortage.find(".supplier_shortage_supplier").text(' ' + data.partner_id[1]); openModal( modal_shortage.html(), function() { set_supplier_shortage(row, data, true); }, 'Valider', true, true, function() { $(".select_product_action").val(''); } ); } } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'initLists : listener set supplier shortage'}; console.error(err); report_JS_error(err, 'reception'); } }); // Search input for both tables $('#search_input').on('keyup', function () { try { $('table.dataTable') .DataTable() .search(jQuery.fn.DataTable.ext.type.search.string(this.value)) // search without accents (see DataTable plugin) .draw(); } catch (e) { err = { msg: e.name + ' : ' + e.message, ctx: 'initLists: listener search_input ' }; console.error(err); report_JS_error(err, 'reception'); } }); // Cancel line editing $('#edition_cancel').on('click', function () { if (editing_product != null) { if (editing_origin == "to_process") { add_to_toProcess(editing_product); } else if (editing_origin == "processed") { add_to_processed(editing_product, false); } clearLineEdition(); } }); $('#table_to_process tbody').on('click', '.product_qty_cell', function () { // Prevent editing mutiple lines at a time if (editing_product == null) { let pswd = prompt('Mot de passe requis pour éditer la quantité de ce produit'); if (pswd == update_qty_pswd) { // Password ok, edit product qty let row = table_to_process.row($(this).parents('tr')); let data = row.data(); // Product goes to editing editing_origin = "to_process"; re_editing_qty = true; setLineEdition(data); remove_from_toProcess(row, data); document.getElementById('search_input').value = ''; $('table.dataTable').DataTable() .search('') .draw(); } else if (pswd == null) { return; } else { alert('Mauvais mot de passe !'); } } else { alert("Il y a déjà un produit dans la zone d'édition. Terminez d'abord d'éditer ce produit."); } }); $('#table_processed tbody').on('click', '.product_qty_cell', function () { // Prevent editing mutiple lines at a time if (editing_product == null) { let pswd = prompt('Mot de passe requis pour éditer la quantité de ce produit'); if (pswd == update_qty_pswd) { // Password ok, edit product qty let row = table_processed.row($(this).parents('tr')); let data = row.data(); // Product goes to editing editing_origin = "processed"; re_editing_qty = true; setLineEdition(data); remove_from_processed(row, data); document.getElementById('search_input').value = ''; $('table.dataTable').DataTable() .search('') .draw(); } else if (pswd == null) { return; } else { alert('Mauvais mot de passe !'); } } else { alert("Il y a déjà un produit dans la zone d'édition. Terminez d'abord d'éditer ce produit."); } }); } // Add a line to to_process function add_to_toProcess(product) { try { // 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(); // Handle blinking effect for newly added row var onAnimationEnd = function() { rowNode.classList.remove('blink_me'); }; $(rowNode).addClass('blink_me'); rowNode.addEventListener('animationend', onAnimationEnd); rowNode.addEventListener('webkitAnimationEnd', onAnimationEnd); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'add_to_toProcess'}; console.error(err); report_JS_error(err, 'reception'); } } // Add a line to processed function add_to_processed(product, withCounter = true) { try { // Add to list 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 var onAnimationEnd = function() { rowNode.classList.remove('blink_me'); }; $(rowNode).addClass('blink_me'); rowNode.addEventListener('animationend', onAnimationEnd); rowNode.addEventListener('webkitAnimationEnd', onAnimationEnd); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'add_to_processed'}; console.error(err); report_JS_error(err, 'reception'); } } // Remove a line from to_process function remove_from_toProcess(row, product) { try { // Remove from list var index = list_to_process.indexOf(product); if (index > -1) { list_to_process.splice(index, 1); } //Remove from table row.remove().draw(); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'remove_from_processed'}; console.error(err); report_JS_error(err, 'reception'); } } // Remove a line from processed function remove_from_processed(row, product) { try { // Remove from list var index = list_processed.indexOf(product); if (index > -1) { list_processed.splice(index, 1); } //Remove from table row.remove().draw(); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'remove_from_processed'}; console.error(err); report_JS_error(err, 'reception'); } } // Indicate the product is on supplier shortage. // Direct validation from to_process & set qty to 0 function set_supplier_shortage(row, product, from_processed = false) { try { product.supplier_shortage = true; // Step 1: set qty to 0 if (reception_status == 'False') { if (!from_processed) { product.old_qty = product.product_qty; } product.product_qty = 0; product.package_qty = 0; // Step 2: for consistency purposes, updated products need these fields to be set } else { if (!from_processed) { product.old_price_unit = product.price_unit; product.new_shelf_price = null; } } // Create 'updated products' list in order if doesn't exists if (!orders[product.id_po]['updated_products']) orders[product.id_po]['updated_products'] = []; if (from_processed) { // Look for product in order's updated products list let already_updated = false; for (i in orders[product.id_po]['updated_products']) { if (orders[product.id_po]['updated_products'][i]['id'] == product['id']) { orders[product.id_po]['updated_products'][i] = product; already_updated = true; } } // If not updated before, add product to updated list... if (!already_updated) { orders[product.id_po]['updated_products'].push(product); // ... and remove product from 'direct validated' products if was there if ('valid_products' in orders[product.id_po]) { for (i in orders[product.id_po]['valid_products']) { if (orders[product.id_po]['valid_products'][i] == product['id']) { orders[product.id_po]['valid_products'].splice(i, 1); } } } } } else { // Add the product to the updated products updatedProducts.push(product); orders[product.id_po]['updated_products'].push(product); } // Re-add product in table if (from_processed) { remove_from_processed(row, product); } else { remove_from_toProcess(row, product); } add_to_processed(product); // Update product's order update_distant_order(product.id_po); // Reset search document.getElementById('search_input').value = ''; $('table.dataTable').DataTable() .search('') .draw(); document.getElementById('search_input').focus(); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'set_supplier_shortage'}; console.error(err); report_JS_error(err, 'reception'); } } /* EDITION */ // Set edition area function setLineEdition(product) { editing_product = product; // name document.getElementById('product_name').innerHTML = editing_product.product_id[1]; // intput if (reception_status == 'False' || re_editing_qty === true) document.getElementById('edition_input').value = editing_product.product_qty; else document.getElementById('edition_input').value = editing_product.price_unit; document.getElementById("edition_input").focus(); // uom if (editing_product.product_uom[0] == 1) { // Unit if (reception_status == 'False' || re_editing_qty === true) { document.getElementById('product_uom').innerHTML = ' unité(s)'; $('#edition_input').attr('type', 'number') .attr('step', 1) .attr('max', 9999); } else { document.getElementById('product_uom').innerHTML = ' / unité'; $('#edition_input').attr('type', 'number') .attr('step', (allow_four_digits_in_reception_price == "True" ? 0.0001 : 0.01)) .attr('max', 9999); } } else if (editing_product.product_uom[0] == 21) { // kg if (reception_status == 'False' || re_editing_qty === true) { document.getElementById('product_uom').innerHTML = ' kg'; $('#edition_input').attr('type', 'number') .attr('step', 0.001) .attr('max', 9999); } else { document.getElementById('product_uom').innerHTML = ' / kg'; $('#edition_input').attr('type', 'number') .attr('step', (allow_four_digits_in_reception_price == "True" ? 0.0001 : 0.01)) .attr('max', 9999); } } // If editing qty during prices edition if (re_editing_qty === true) { document.getElementById('edition_header').innerHTML = "Ré-éditer la quantité"; document.getElementById('edition_input_label').innerHTML = "Qté"; } // Make edition area blink when edition button clicked container_edition.classList.add('blink_me'); } // Clear edition function clearLineEdition() { editing_product = null; // Reset DOM values document.getElementById('product_name').innerHTML = ''; document.getElementById('edition_input').value = null; document.getElementById('search_input').focus(); document.getElementById('product_uom').innerHTML = ''; if (re_editing_qty === true) { document.getElementById('edition_header').innerHTML = "Editer les prix"; document.getElementById('edition_input_label').innerHTML = "Prix unit."; re_editing_qty = false; } } /** * Update a product info : qty or unit price * @param {Object} productToEdit * @param {Float} value if set, use it as new value * @param {Boolean} batch if true, don't update couchdb data here * @returns */ function editProductInfo (productToEdit, value = null, batch = false) { try { // Check if the product is already in the 'updated' list var index = searchUpdatedProduct(); var firstUpdate = false; var isValid = false; // "valid" == no change from initial value let newValue = value; var addition = false; // If 'value' parameter not set, get value from edition input if (value == null) { newValue = parseFloat(document.getElementById('edition_input').value.replace(',', '.')); newValue = isFinite(newValue) ? newValue : 0; } // Particular process in case of qty reedition during prices update if (re_editing_qty === true) { // Look for product in product's order first step data let previous_step_index = -1; for (let i = 0; i < orders[productToEdit.id_po]["previous_steps_data"]["False"]["updated_products"].length; i++) { if ( orders[productToEdit.id_po]["previous_steps_data"]["False"]["updated_products"][i].id === productToEdit.id ) { previous_step_index = i; break; } } if (previous_step_index === -1) { // Product qty hasn't been updated yet: add to first step data productToEdit.old_qty = productToEdit.product_qty; productToEdit.product_qty = newValue; productToEdit.product_qty_package = 1; productToEdit.package_qty = productToEdit.product_qty; orders[productToEdit.id_po]["previous_steps_data"]["False"]["updated_products"].push(productToEdit); } else { productToEdit.product_qty = newValue; productToEdit.package_qty = productToEdit.product_qty; // Product qty has been updated before, update first step data orders[productToEdit.id_po]["previous_steps_data"]["False"]["updated_products"][previous_step_index].product_qty = newValue; orders[productToEdit.id_po]["previous_steps_data"]["False"]["updated_products"][previous_step_index].package_qty = newValue; } /* Send request to server to update a single product */ updateType = "qty_valid"; send([productToEdit]); // Update temp couchdb order update_distant_order(productToEdit.id_po); // Put back product in its original list if (editing_origin === "to_process") { add_to_toProcess(productToEdit); } else if (editing_origin === "processed") { add_to_processed(productToEdit); } return true; } // addition mode = weight is directly added from scanned product $.each(list_processed, function(i, e) { if ( e.product_id[0] == productToEdit.product_id[0] && "weightAddition" in productToEdit && productToEdit.weightAddition === true ) { addition = true; productToEdit = e; newValue = Number((newValue + productToEdit.product_qty).toFixed(3)); } }); // If qty edition & Check if qty changed if (reception_status == "False") { firstUpdate = (index == -1); //first update if (productToEdit.product_qty != newValue) { // If no old_qty in productToEdit, product qty wasn't edited before if (productToEdit.old_qty === undefined) { productToEdit.old_qty = productToEdit.product_qty; } else { //if it is not the first update AND newValue is equal to the validation qty then the product is valid (qty not changed) isValid = (newValue === productToEdit.old_qty); } // Edit product info productToEdit.product_qty = newValue; /* If qty has changed, we choose to set detailed values as follow: 1 package (product_qty_package) of X products (package_qty) */ productToEdit.product_qty_package = 1; productToEdit.package_qty = productToEdit.product_qty; } else if (firstUpdate) { // if the product is updated for the first time and productQty is equal to the newValue then the product is validated isValid = true; } } // Check if price changed if (reception_status == "qty_valid" && productToEdit.price_unit != newValue) { if (index == -1) { // First update productToEdit.old_price_unit = productToEdit.price_unit; productToEdit.new_shelf_price = parseFloat(newValue); try { // Let's compute product final price, using coeffs. let computing_shelf_price_details = {base_value: productToEdit.new_shelf_price, intermediate_values: []}; for (let k = 1; k <10; k++) { if (typeof productToEdit['coeff' + k + '_id'] !== "undefined") { product_coeffs.forEach((coeff) => { if (coeff.id == productToEdit['coeff' + k + '_id']) { if (coeff.operation_type == "fixed") { productToEdit.new_shelf_price += coeff.value; computing_shelf_price_details.intermediate_values.push({msg: "Found fixed coeff " + coeff.value, new_value: productToEdit.new_shelf_price}); } else if (coeff.operation_type == "multiplier") { productToEdit.new_shelf_price *= (1 + coeff.value); computing_shelf_price_details.intermediate_values.push({msg: "Found multiplier coeff " + coeff.value, new_value: productToEdit.new_shelf_price}); } } }); } } productToEdit.new_shelf_price *= productToEdit.tax_coeff; computing_shelf_price_details.intermediate_values.push({msg: "Applying tax coeff " + productToEdit.tax_coeff, new_value: productToEdit.new_shelf_price}); productToEdit.new_shelf_price = productToEdit.new_shelf_price.toFixed(2); computing_shelf_price_details.final_value = productToEdit.new_shelf_price; productToEdit.computing_shelf_price_details = computing_shelf_price_details; } catch (e) { productToEdit.new_shelf_price = null; err = {msg: e.name + ' : ' + e.message, ctx: 'computing new_shelf_price'}; console.error(err); report_JS_error(err, 'reception'); } firstUpdate = true; } else if (productToEdit.old_price_unit == newValue) { productToEdit.new_shelf_price = null; } productToEdit.price_unit = newValue; } // If the product info has been updated and for the first time if (firstUpdate) { //if product is validated thru edition without change -> add to valid_products if (isValid) { // Create 'valid_products' list in order if not exists if (!orders[productToEdit.id_po]['valid_products']) { orders[productToEdit.id_po]['valid_products'] = []; } orders[productToEdit.id_po]['valid_products'].push(productToEdit['id']); } else { updatedProducts.push(productToEdit); // Create 'updated_products' list in order if not exists if (!orders[productToEdit.id_po]['updated_products']) { orders[productToEdit.id_po]['updated_products'] = []; } // Add product to order's updated products if first update orders[productToEdit.id_po]['updated_products'].push(productToEdit); // May have been directly validated then updated from processed list // -> remove from 'valid_products' list for (i in orders[productToEdit.id_po]['valid_products']) { if (orders[productToEdit.id_po]['valid_products'][i] == productToEdit['id']) { orders[productToEdit.id_po]['valid_products'].splice(i, 1); } } } } else { if (isValid) { //if product is valid -> remove from updated_products list and add to valid_products list //removing from updated_products for (i in orders[productToEdit.id_po]['updated_products']) { if ( orders[productToEdit.id_po]['updated_products'][i]['product_id'][0] == productToEdit['product_id'][0] ) { orders[productToEdit.id_po]['updated_products'].splice(i, 1); } } //add to valid_products // Create 'valid_products' list in order if not exists if (!orders[productToEdit.id_po]['valid_products']) { orders[productToEdit.id_po]['valid_products'] = []; } orders[productToEdit.id_po]['valid_products'].push(productToEdit['id']); } else { // Look for product in order's updated products list for (i in orders[productToEdit.id_po]['updated_products']) { if (orders[productToEdit.id_po]['updated_products'][i]['product_id'][0] == productToEdit['product_id'][0]) { orders[productToEdit.id_po]['updated_products'][i] = productToEdit; } } } } if (batch === false) { // Update product order update_distant_order(productToEdit.id_po); } // Remove product from processed list if: // - we're adding directly weight from scanned product // - product comes from processed list if (addition === true || firstUpdate === false) { let row = table_processed.row($('#'+productToEdit.product_id[0])); remove_from_processed(row, productToEdit); } add_to_processed(productToEdit); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'edit product info'}; console.error(err); report_JS_error(err, 'reception'); } return true; } // Validate product edition function validateEdition(form = null) { if (editing_product != null) { if (editProductInfo(editing_product)) { clearLineEdition(); } } } // Set the quantity to 0 for all the products in to_process function setAllQties() { // Iterate over all rows in to_process table_to_process.rows().every(function () { var data = this.data(); editProductInfo(data, 0, true); return true; }); list_to_process = []; table_to_process.rows().remove() .draw(); // Batch update orders update_distant_orders(); } /* ACTIONS */ function print_product_labels() { try { if (is_time_to('print_pdt_labels', 10000)) { $.ajax("../../orders/print_product_labels?oids=" + group_ids.join(',')) .done(function(data) { let success = false; if (typeof data.res !== "undefined") { if (typeof data.res.error === "undefined") { success = true; } } if (success == true) { alert("l' impression des étiquettes à coller sur les articles vient d'être lancée."); $('#barcodesToPrint').hide(); } else { alert("Une erreur est survenue."); } }); } else { alert("Vous avez cliqué il y a moins de 10s... Patience, la demande est en cours de traitement."); } } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'print_product_labels'}; console.error(err); report_JS_error(err, 'reception'); } } /** DEPRECATED, printing is fired automaticaly now from server side. **/ // Send request to print a new shelf labels file for products with new price // function print_etiquettes() { // try { // // Additionnal security to be sure request isn't sent during step 1 // if (reception_status == 'qty_valid') { // // For all products with updated price // for (i in updatedProducts) { // // Send request // if (updatedProducts[i].new_shelf_price) { // $.ajax({ // url: tools_server + "/products/label_print/" // + updatedProducts[i].product_tmpl_id + "/" // + updatedProducts[i].new_shelf_price // }); // } // // } // // document.getElementById("etiquettesToPrint").innerHTML = "<br/><h5><b>Impression lancée !</b></h5>"; // } // } catch (e) { // err = {msg: e.name + ' : ' + e.message, ctx: 'print_etiquettes'} // console.error(err) // report_JS_error(err, 'reception') // } // } // Verifications before sending BC update function pre_send(type) { if (list_to_process.length > 0) { alert("Il reste des produits à traiter dans la commande."); } else { let modal_next_step = '#templates #modal_prices_validation'; updateType = type; if (type == 'qty_valid') { modal_next_step = '#templates #modal_qties_validation'; } openModal($(modal_next_step).html(), data_validation, 'Confirmer', false); } } function data_validation() { openModal(); $.ajax({ type: "POST", url: "../data_validation", dataType: "json", traditional: true, contentType: "application/json; charset=utf-8", data: JSON.stringify(group_ids), success: function(data) { if (data.unprocessable.length == 0) { // No product unprocessable, do process send(); } else { $("#modal_unprocessable_porducts #list_unprocessable_porducts").html(''); for (p of data.unprocessable) { $("#modal_unprocessable_porducts #list_unprocessable_porducts").append("<li>" + p[1] + "</li>"); } openModal($("#modal_unprocessable_porducts").html(), function() { return 0; }, 'Confirmer', true, false); } }, error: function(data) { // if error during validation, report error & go on so we don't block the process err = {msg: "erreur serveur lors de la validation des données", ctx: 'data_validation'}; if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { err.msg += ' : ' + data.responseJSON.error; } console.error(err); report_JS_error(err, 'reception'); send(); } }); } /** * Send the request to update order(s) data * * @param {Array} given_products If set, only update these products. * If no given products, we're in the regular process, ie the end of a reception. * Else, we're in the middle of a reception, so we'll skip some parts. */ function send(given_products = []) { try { // Loading on openModal(); /* Prepare data for orders update */ // Only send to server the updated lines var update_data = { update_type: updateType, orders: {} }; // Set orders in update data with empty list of updated products for (order_id in orders) { update_data.orders[order_id] = {'po' : []}; } has_given_products = given_products.length > 0; // If given products, update these only, else update global updatedProducts list products_to_update = has_given_products === true ? given_products : updatedProducts; // for each updated product, add it to its order list for (i in products_to_update) { /* ---> The following part concerns products found in different orders */ if ('other_orders_data' in products_to_update[i]) { // for each other order of product for (other_order_data of products_to_update[i].other_orders_data) { // Make a clone (deep copy) of the product object let product_copy = $.extend(true, {}, products_to_update[i]); // Set correct order line id for this product product_copy.id = other_order_data.id_product; // If in step 1, dispatch quantity in other orders if (reception_status == 'False') { // Reset initial qties in respective orders product_copy.old_qty = other_order_data.initial_qty; for (j in orders[products_to_update[i].id_po]['updated_products']) { if (orders[products_to_update[i].id_po]['updated_products'][j].product_id[0] == product_copy.product_id[0]) { orders[products_to_update[i].id_po]['updated_products'][j].old_qty -= other_order_data.initial_qty; break; } } if (product_copy.product_uom[0] == 21 && products_to_update[i].product_qty > 0.1) { // kg // Add minimum qty in other orders product_copy.product_qty_package = 1; product_copy.package_qty = 0.1; product_copy.product_qty = 0.1; // Remove this qty from first order products_to_update[i].package_qty -= 0.1; products_to_update[i].product_qty -= 0.1; } else if (product_copy.product_uom[0] == 1 && products_to_update[i].product_qty > 1) { // Unit product_copy.product_qty_package = 1; product_copy.package_qty = 1; product_copy.product_qty = 1; products_to_update[i].package_qty -= 1; products_to_update[i].product_qty -= 1; } else { // Not handled, all qty in one order product_copy.product_qty_package = 0; product_copy.package_qty = 0; product_copy.product_qty = 0; } } /* Add product to the other orders it belongs to */ // In update data update_data.orders[other_order_data.id_po]['po'].push(product_copy); // Add it to the 'updated products' of other orders (for error report) if (!('updated_products' in orders[other_order_data.id_po])) { orders[other_order_data.id_po]['updated_products'] = []; } orders[other_order_data.id_po]['updated_products'].push(product_copy); } } /* <--- */ // Add product to order's prod list prod_order_id = products_to_update[i].id_po; update_data.orders[prod_order_id]['po'].push(products_to_update[i]); } // Only send error report & no barcode list when no given products (ie normal process, end of reception) if (has_given_products === false) { /* Create the error report */ // Send changes between items to process and processed items var error_report_data = { 'group_amount_total' : 0, 'update_type' : updateType, 'updated_products' : products_to_update, 'user_comments': user_comments, 'orders' : [] }; for (let i in orders) { error_report_data.group_amount_total += orders[i].amount_total; error_report_data.orders.push(orders[i]); } //Create list of articl with no barcode no_barcode_list = []; for (var i = 0; i < list_processed.length; i++) { if (list_processed[i].product_qty != 0 && (list_processed[i].barcode == false || list_processed[i].barcode == null || list_processed[i].barcode == "")) { no_barcode_list.push([ list_processed[i]["product_id"][0], list_processed[i]["product_id"][1] ]); } } data_send_no_barcode={ "order" : orders[order_data['id_po']], "no_barcode_list" : no_barcode_list }; // Send of articl with no barcode to mail send if (no_barcode_list.length > 0) { $.ajax({ type: "POST", url: "../send_mail_no_barcode", dataType: "json", traditional: true, contentType: "application/json; charset=utf-8", data: JSON.stringify(data_send_no_barcode), success: function() {}, error: function() { alert('Erreur dans l\'envoi des produite sont barre code.'); } }); } // Send request for error report $.ajax({ type: "POST", url: "../save_error_report", dataType: "json", traditional: true, contentType: "application/json; charset=utf-8", data: JSON.stringify(error_report_data), success: function() {}, error: function() { closeModal(); alert('Erreur dans l\'envoi du rapport.'); } }); } /* Update orders */ $.ajax({ type: "PUT", url: "../update_orders", dataType: "json", traditional: true, contentType: "application/json; charset=utf-8", data: JSON.stringify(update_data), success: function() { closeModal(); if (has_given_products === false) { try { // If step 1 (counting) if (reception_status == "False") { /* Open pop-up with procedure explanation */ var barcodes_to_print = false; // Select products with local barcode and without barcode, when qty > 0 for (var i = 0; i < list_processed.length; i++) { if (list_processed[i].product_qty != 0) { // set DOM data if (typeof(list_processed[i].barcode) == "string" && list_processed[i].barcode.startsWith(fixed_barcode_prefix) && !barcodes_to_print) { // Products with barcode to print (local barcode) document.getElementById("barcodesToPrint").hidden = false; document.getElementById("nothingToDo").hidden = true; barcodes_to_print = true; } /* else if (list_processed[i].barcode == false || list_processed[i].barcode == null || list_processed[i].barcode == "") { // Products with no barcode var node = document.createElement('li'); let textNode = document.createTextNode(list_processed[i]["product_id"][1]); node.appendChild(textNode); document.getElementById('barcodesEmpty_list').appendChild(node); if (document.getElementById("barcodesEmpty").hidden) { document.getElementById("barcodesEmpty").hidden = false; document.getElementById("nothingToDo").hidden = true; } }*/ } } for (let i = 0; i < no_barcode_list.length; i++) { var node = document.createElement('li'); let textNode = document.createTextNode(no_barcode_list[i]); node.appendChild(textNode); document.getElementById('barcodesEmpty_list').appendChild(node); if (document.getElementById("barcodesEmpty").hidden) { document.getElementById("barcodesEmpty").hidden = false; document.getElementById("nothingToDo").hidden = true; } } // Set order(s) name in popup DOM if (is_grouped_order() === false) { // Single order document.getElementById("order_ref").innerHTML = orders[Object.keys(orders)[0]].name; } else { // group document.getElementById("success_order_name_container").hidden = true; document.getElementById("success_orders_name_container").hidden = false; for (order_id in orders) { var p_node = document.createElement('p'); var span_node = document.createElement('span'); span_node.className = 'order_ref_reminder'; let textNode = document.createTextNode(orders[order_id].name); span_node.appendChild(textNode); textNode = document.createTextNode(orders[order_id].partner + ' du ' + orders[order_id].date_order + ' : '); p_node.appendChild(textNode); p_node.appendChild(span_node); document.getElementById("orders_ref").appendChild(p_node); } } openModal( $('#modal_qtiesValidated').html(), back, 'Retour à la liste des commandes', true, false ); /* Not last step: update distant data */ for (let order_id in orders) { // Save current step updated data orders[order_id].previous_steps_data = {}; orders[order_id].previous_steps_data[reception_status] = { updated_products: orders[order_id].updated_products || [], user_comments: user_comments }; orders[order_id].reception_status = updateType; // Unlock order orders[order_id].last_update = { timestamp: null, fingerprint: null }; // Delete temp data delete orders[order_id].valid_products; delete orders[order_id].updated_products; } dbc.bulkDocs(Object.values(orders)).catch((err) => { console.log(err); }); } else { // Print etiquettes with new prices if (updatedProducts.length > 0) { document.getElementById("etiquettesToPrint").hidden = false; } openModal( $('#templates #modal_pricesValidated').html(), back, 'Retour à la liste des commandes', true, false ); /* Last step: Clear distant data */ // Delete orders doc for (let order_id in orders) { orders[order_id]._deleted = true; } // Remove orders group dbc.get("grouped_orders").then((doc) => { let couchdb_update_data = Object.values(orders); // We're in a group, remove it & update groups doc if (is_grouped_order()) { let groups_doc = doc; let first_order_id = parseInt(Object.keys(orders)[0]); for (let i in groups_doc.groups) { if (groups_doc.groups[i].includes(first_order_id)) { groups_doc.groups.splice(i, 1); break; } } couchdb_update_data.push(groups_doc); } return dbc.bulkDocs(couchdb_update_data); }) .catch(function (err) { console.log(err); }); } // Back if modal closed $('#modal_closebtn_top').on('click', back); $('#modal_closebtn_bottom').on('click', back); } catch (ee) { err = {msg: ee.name + ' : ' + ee.message, ctx: 'callback update_orders'}; console.error(err); report_JS_error(err, 'reception'); } } }, error: function() { closeModal(); alert('Erreur lors de la sauvegarde des données.'); } }); } catch (e) { err = {msg: e.name + ' : ' + e.message, ctx: 'send'}; console.error(err); report_JS_error(err, 'reception'); alert('Erreur : ' + err.msg); } } // Fired from verification modal for 'all prices' validation function confirmPricesAllValid() { updateType = 'br_valid'; send(); } // Fired from All left is good modal function confirm_all_left_is_good() { // all products left are to be considered as well filled // Iterate over all rows in to_process table_to_process.rows().every(function () { let data = this.data(); var value = null; if (reception_status == "False") { value = data.product_qty; } else { value = data.price_unit; } editProductInfo(data, value, true); return true; }); list_to_process = []; table_to_process.rows().remove() .draw(); // Batch update orders update_distant_orders(); closeModal(); } function saveErrorReport() { user_comments = document.getElementById("error_report").value; // Save comments in all orders for (order_id of Object.keys(orders)) { orders[order_id].user_comments = user_comments; update_distant_order(order_id); } document.getElementById("search_input").focus(); } /** * Check if all qty inputs are set first. * Adding products leads to creating a new order (for each supplier) that will be grouped with the current one(s) */ function add_products_action() { let qty_inputs = $("#modal .products_lines").find(".product_qty_input"); let has_empty_qty_input = false; for (let qty_input of qty_inputs) { if ($(qty_input).val() === "") { has_empty_qty_input = true; $(qty_input).closest(".product_qty") .find(".product_qty_input_alert") .show(); } else { $(qty_input).closest(".product_qty") .find(".product_qty_input_alert") .hide(); } } if (products_to_add.length > 0 && qty_inputs.length > 0 && has_empty_qty_input === false) { create_orders(); } } /** * Send request to create the new orders */ function create_orders() { let orders_data = { "suppliers_data": {} }; // Mock order date_planned : today let date_object = new Date(); formatted_date = date_object.toISOString().replace('T', ' ') .split('.')[0]; // Get ISO format bare string for (let supplier_id of get_suppliers_id()) { orders_data.suppliers_data[supplier_id] = { date_planned: formatted_date, lines: [] }; } // Prepare data: get products with their qty for (let p of products_to_add) { // Get product qty from input let product_qty = 0; let product_uom = ""; let add_products_lines = $("#modal .add_product_line"); for (let i = 0; i < add_products_lines.length; i++) { let line = add_products_lines[i]; if ($(line).find(".product_name") .text() === p.name) { product_uom = $(line).find(".product_uom") .text(); if (product_uom.includes("kg")) { product_qty = parseFloat($(line).find(".product_qty_input") .val()); } else { product_qty = parseInt($(line).find(".product_qty_input") .val(), 10); } break; } } let p_supplierinfo = p.suppliersinfo[0]; // product is ordered at its first supplier const supplier_id = p_supplierinfo.supplier_id; let item_qty_package = 0; // If package qty is > than input value, package qty will be used while creating order let package_qty = p_supplierinfo.package_qty; if (product_qty < package_qty) { package_qty = product_qty; } // Round differently for unit & kg products if (product_uom.includes("kg")) { item_qty_package = Math.round((product_qty / package_qty) * 1e2) / 1e2; } else { item_qty_package = Math.round(product_qty / package_qty); } orders_data.suppliers_data[supplier_id].lines.push({ 'package_qty': package_qty, 'product_id': p.id, 'name': p.name, 'product_qty_package': item_qty_package, 'product_qty': product_qty, 'product_uom': p.uom_id[0], 'price_unit': p_supplierinfo.price, 'supplier_taxes_id': p.supplier_taxes_id, 'product_variant_ids': p.product_variant_ids, 'product_code': p_supplierinfo.product_code }); } // Remove supplier from order data if no lines for (const supplier_id in orders_data.suppliers_data) { if (orders_data.suppliers_data[supplier_id].lines.length === 0) { delete(orders_data.suppliers_data[supplier_id]); } } openModal(); $("#modal em:contains('Chargement en cours...')").append("<br/>L'opération peut prendre un certain temps..."); $.ajax({ type: "POST", url: "/orders/create_orders", dataType: "json", traditional: true, contentType: "application/json; charset=utf-8", data: JSON.stringify(orders_data), success: (result) => { po_ids = []; for (let po of result.res.created) { po_ids.push(po.id_po); } // Get orders data as needed by the module with order lines $.ajax({ type: 'GET', url: "/reception/get_list_orders", dataType:"json", traditional: true, contentType: "application/json; charset=utf-8", data: { poids: po_ids, get_order_lines: true }, success: function(result2) { let current_orders_key = group_ids.length; for (let new_order of result2.data) { // Add key (position in orders list) to new orders data current_orders_key += 1; new_order.key = current_orders_key; // Consider new order lines as updated products new_order.updated_products = new_order.po; delete(new_order.po); // Add necessary data to order updated products for (let noup of new_order.updated_products) { noup.order_key = current_orders_key; noup.id_po = String(new_order.id); noup.old_qty = 0; // products weren't originally ordered } // Create couchdb doc for the new order create_order_doc(new_order); } dbc.get("grouped_orders").then((doc) => { // Not a group (yet) if (group_ids.length === 1) { group_ids = group_ids.concat(po_ids); doc.groups.push(group_ids); } else { for (let i in doc.groups) { // If group found in saved distatnt groups if (group_ids.findIndex(e => e == doc.groups[i][0]) !== -1) { doc.groups[i] = doc.groups[i].concat(po_ids); doc.groups[i].sort(); group_ids = doc.groups[i]; } } } dbc.put(doc, () => { // Update screen // The easy way: reload page now all data is correctly set. window.location.reload(); }); }); }, error: function(data) { err = {msg: "erreur serveur lors de la récupération des commandes", ctx: 'get_list_orders'}; if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { err.msg += ' : ' + data.responseJSON.error; } report_JS_error(err, 'orders'); closeModal(); alert('Erreur lors de la récupération des commandes, rechargez la page plus tard.'); } }); }, error: function(data) { let msg = "erreur serveur lors de la création des product orders"; err = {msg: msg, ctx: 'create_orders', data: orders_data}; if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { err.msg += ' : ' + data.responseJSON.error; } report_JS_error(err, 'reception'); closeModal(); alert('Erreur lors de la création des commandes. Veuillez ré-essayer plus tard.'); } }); } /** * Create a couchdb document for an order * * @param {Object} order_data */ function create_order_doc(order_data) { const order_doc_id = 'order_' + order_data.id; order_data._id = order_doc_id; order_data.last_update = { timestamp: Date.now(), fingerprint: fingerprint }; dbc.put(order_data).then(() => {}) .catch((err) => { error = { msg: 'Erreur dans la creation de la commande dans couchdb', ctx: 'create_order_doc', details: err }; report_JS_error(error, 'reception'); console.log(error); }); } /* DOM */ function openFAQ() { openModal($("div#modal_FAQ_content").html(), function() {}, 'Compris !', true, false); } function openErrorReport() { openModal($('#templates #modal_error_report').html(), saveErrorReport, 'Confirmer'); // listener for error report textarea // this is necessary because default behavior is overwritten by the listener defined in jquery.pos.js; $("#error_report").keypress(function(e) { var key = e.keyCode; if (key === 13) { this.value += "\n"; } }); var textarea = document.getElementById("error_report"); textarea.value = (user_comments != undefined) ? user_comments : ""; textarea.focus(); textarea.setSelectionRange(textarea.value.length, textarea.value.length); } /** * Set the autocomplete on add products modal, search product input. * If extists, destroys instance and recreate it. * Filter autocomplete data by removing products already selected. */ function set_products_autocomplete() { // Filter autocomplete products on products already selected let autocomplete_products = suppliers_products.filter(p => products_to_add.findIndex(pta => pta.name === p.name) === -1); try { $("#modal .search_product_input").autocomplete("destroy"); } catch (error) { // autocomplete not set yet, do nothing } $("#modal .search_product_input").autocomplete({ source: autocomplete_products.map(p => p.name), classes: { "ui-autocomplete": "autocomplete_dropdown" }, delay: 0, select: function(event, ui) { // Action called when an item is selected event.preventDefault(); let product_name = ui.item.label; // extra secutiry but shouldn't happen if (products_to_add.findIndex(p => p.name === product_name) === -1) { let product = suppliers_products.find(p => p.name === product_name); products_to_add.push(product); // Display let add_product_template = $("#add_product_line_template"); add_product_template.find(".product_name").text(product_name); add_product_template.find(".product_uom").text(product.uom_id[1]); $("#modal .products_lines").append(add_product_template.html()); if (products_to_add.length === 1) { $("#modal .products_lines").show(); } $(".remove_line_icon").off("click"); $(".remove_line_icon").on("click", remove_product_line); // Reset search elements $("#modal .search_product_input").val(''); set_products_autocomplete(); } } }); } /** * Remove product from list of products to add & remove line from DOM * @param {Event} e */ function remove_product_line(e) { let product_line = $(e.target).closest(".add_product_line"); let product_name = product_line.find(".product_name").text(); let product_to_add_index = products_to_add.findIndex(p => p.name === product_name); products_to_add.splice(product_to_add_index, 1); product_line.remove(); set_products_autocomplete(); } /** * Set & display the modal to search products. * If no products to add, display the according modal. */ function set_add_products_modal() { if (suppliers_products.length === 0) { let modal_no_product_to_add = $("#modal_no_product_to_add"); openModal( modal_no_product_to_add.html(), () => {}, 'OK' ); } else { let add_products_modal = $("#modal_add_products"); openModal( add_products_modal.html(), add_products_action, 'Ajouter les produits', false ); products_to_add = []; // Reset on modal opening set_products_autocomplete(); } } /** * Init the page according to order(s) data (texts, colors, events...) * * @param {Array} 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(() => { back(); }) .catch((err) => { console.log(err); }); }); // Grouped orders if (is_grouped_order()) { $('#partner_name').html(Object.keys(orders).length + " commandes"); // Display order data for each order var msg = ""; for (display_partner_data of partners_display_data) { if (msg != "") { msg += ", "; } msg += display_partner_data; } $('#container_multiple_partners').append('<h6> ' + msg + '</h6>'); } else { $('#partner_name').html(orders[Object.keys(orders)[0]].partner); } /* Set DOM according to reception status */ if (reception_status == "qty_valid") { // Step 2 // Header document.getElementById('header_step_two').classList.add('step_two_active'); var check_icon = document.createElement('i'); check_icon.className = 'far fa-check-circle'; document.getElementById('header_step_one_content').appendChild(check_icon); // Products lists containers document.getElementById('container_left').style.border = "3px solid #0275D8"; // container qty_checked document.getElementById('container_right').style.border = "3px solid #5CB85C"; // container processed items document.getElementById('header_container_left').innerHTML = "Prix à mettre à jour"; document.getElementById('header_container_right').innerHTML = "Prix mis à jour"; // Edition document.getElementById('edition_header').innerHTML = "Editer les prix"; document.getElementById('edition_input_label').innerHTML = "Prix unit."; // Validation buttons document.getElementById("valid_all").innerHTML = "<button class='btn--danger full_width_button' id='valid_all_uprices' onclick=\"openModal($('#templates #modal_no_prices').html(), confirmPricesAllValid, 'Confirmer', false);\" disabled>Pas de prix sur le bon de livraison</button>"; document.getElementById("validation_button").innerHTML = "<button class='btn--success full_width_button' id='valid_uprice' onclick=\"pre_send('br_valid')\" disabled>Valider la mise à jour des prix</button>"; // Modal content after validation $("#modal_pricesValidated").load("/reception/reception_pricesValidated"); } else if (reception_status == "False") { // Step 1 document.getElementById('header_step_one').classList.add('step_one_active'); document.getElementById('container_left').style.border = "3px solid #212529"; // container products to process document.getElementById('container_right').style.border = "3px solid #0275D8"; // container qty_checked document.getElementById('header_container_left').innerHTML = "Produits à compter"; document.getElementById('header_container_right').innerHTML = "Produits déjà comptés"; document.getElementById('edition_header').innerHTML = "Editer les quantités"; document.getElementById('edition_input_label').innerHTML = "Qté"; // Add products button document.getElementById('add_products_button').style.display = "block"; document.getElementById("valid_all").innerHTML = "<button class='btn--danger full_width_button' id='valid_all_qties' onclick=\"openModal($('#templates #modal_no_qties').html(), setAllQties, 'Confirmer');\" disabled>Il n'y a plus de produits à compter</button>"; document.getElementById("validation_button").innerHTML = "<button class='btn--primary full_width_button' id='valid_qty' onclick=\"pre_send('qty_valid')\" disabled>Valider le comptage des produits</button>"; $("#modal_qtiesValidated").load("/reception/reception_qtiesValidated"); } else { // Extra security, shouldn't get in here: reception status not valid back(); } // Load modals content $("#modal_FAQ_content").load("/reception/reception_FAQ"); $("#modal_qtiesValidated").load("/reception/reception_qtiesValidated"); $("#modal_pricesValidated").load("/reception/reception_pricesValidated"); // Handling blinking effect var container_edition = document.querySelector('#container_edition'); container_edition.addEventListener('animationend', onAnimationEnd); container_edition.addEventListener('webkitAnimationEnd', onAnimationEnd); function onAnimationEnd() { container_edition.classList.remove('blink_me'); } // Disable mousewheel on an input number field when in focus $('#edition_input').on('focus', function () { $(this).on('wheel.disableScroll', function (e) { e.preventDefault(); }); }) .on('blur', function () { $(this).off('wheel.disableScroll'); }); // client-side validation of numeric inputs, optionally replacing separator sign(s). $("input.number").on("keydown", function (e) { // allow function keys and decimal separators if ( // backspace, delete, tab, escape, enter, comma and . $.inArray(e.keyCode, [ 46, 8, 9, 27, 13, 110, 188, 190 ]) !== -1 || // Ctrl/cmd+A, Ctrl/cmd+C, Ctrl/cmd+X ($.inArray(e.keyCode, [ 65, 67, 88 ]) !== -1 && (e.ctrlKey === true || e.metaKey === true)) || // home, end, left, right (e.keyCode >= 35 && e.keyCode <= 39)) { /* // optional: replace commas with dots in real-time (for en-US locals) if (e.keyCode === 188) { e.preventDefault(); $(this).val($(this).val() + "."); } // optional: replace decimal points (num pad) and dots with commas in real-time (for EU locals) if (e.keyCode === 110 || e.keyCode === 190) { e.preventDefault(); $(this).val($(this).val() + ","); } */ return; } // block any non-number if ( //Figures entered with Shift + key (59 ==> .) (e.shiftKey && ((e.keyCode < 48 || e.keyCode > 57) && e.keyCode !== 59)) || //Numeric keyboard (!e.shiftKey && (e.keyCode < 96 || e.keyCode > 105)) ) { e.preventDefault(); } }); $("#edition_input").keypress(function(event) { // Force validation when enter pressed in edition if (event.keyCode == 13 || event.which == 13) { validateEdition(); } }); $("#add_products_button").on('click', () => { if (reception_status == "False") { let pswd = prompt('Merci de demander à un.e salarié.e le mot de passe pour ajouter des produits à la commande'); // Minimum security level if (pswd == add_products_pswd) { fetch_suppliers_products(); } else if (pswd == null) { return; } else { alert('Mauvais mot de passe !'); } } }); // Barcode reader $(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; if (barcode.length >=13) { barcode = barcode.substring(barcode.length-13); } else if (barcode.length == 12 && barcode.indexOf('0') !== 0) { // User may use a scanner which remove leading 0 barcode = '0' + barcode; } else if (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(); select_product_from_bc(barcode); }); } $(document).ready(function() { $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); fingerprint = new Fingerprint({canvas: true}).get(); // Load barcodes get_barcodes(); // Get Route parameter let pathArray = window.location.pathname.split('/'); let id = pathArray[pathArray.length-1]; // Init couchdb dbc = new PouchDB(couchdb_dbname), sync = PouchDB.sync(couchdb_dbname, couchdb_server, { live: true, retry: true, auto_compaction: false }); sync.on('change', function (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) { 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', err); }); // Disable alert errors from datatables $.fn.dataTable.ext.errMode = 'none'; // Listen for errors in tables with custom behavior $('#table_to_process').on('error.dt', function (e, settings, techNote, message) { var err_msg = message; try { var split = message.split(" "); var row_number = null; for (var i = 0; i < split.length; i++) { if (split[i] == "row") row_number = split[i+1]; } row_number = row_number.replace(',', ''); var row_data = $('#table_to_process').DataTable() .row(row_number) .data(); err_msg += " - Order id: " + row_data.id_po; err_msg += " - Product: " + row_data.product_id[1]; } catch (e) { console.log(e); } err = {msg: err_msg, ctx: 'datatable: table to_process'}; console.error(err); report_JS_error(err, 'reception'); }); $('#table_processed').on('error.dt', function (e, settings, techNote, message) { var err_msg = message; try { var split = message.split(" "); var row_number = null; for (var i = 0; i < split.length; i++) { if (split[i] == "row") row_number = split[i+1]; } row_number = row_number.replace(',', ''); var row_data = $('#table_processed').DataTable() .row(row_number) .data(); err_msg += " - Order id: " + row_data.id_po; err_msg += " - Product: " + row_data.product_id[1]; } catch (e) { console.log(e); } err = {msg: err_msg, ctx: 'datatable: table processed'}; console.error(err); report_JS_error(err, 'reception'); }); /* Get order info from couchdb */ // Get order groups let order_groups = []; dbc.get("grouped_orders").then((doc) => { order_groups = doc.groups; for (let group of order_groups) { for (group_order_id of group) { if (group_order_id == id) { // We're in a group! group_ids = group; } } } // if not in group, add current order to group (1 order = group of 1) if (group_ids.length == 0) { group_ids.push(parseInt(id)); } let partners_display_data = []; dbc.allDocs({ include_docs: true }).then(function (result) { // for each order in the group for (let i in group_ids) { // find order let order_id = group_ids[i]; let order = result.rows.find(el => el.id == 'order_' + order_id); order = order.doc; order.key = parseInt(i) + 1; orders[order_id] = order; // Add each order's already updated and validated products to common list if (order["updated_products"]) { updatedProducts = updatedProducts.concat(order["updated_products"]); } if (order["valid_products"]) { validProducts = validProducts.concat(order["valid_products"]); } // Prepare data to display in 'partner name' area partners_display_data.push(`<span class="title_partner_key">${order.key}.</span> ${order.partner} du ${order.date_order}`); } // Set current reception status: take first order's reception_status = orders[Object.keys(orders)[0]].reception_status; // Load saved user comments, get it from first order 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_data(); init_dom(partners_display_data); }) .catch(function (e) { let msg = ('message' in e && 'name' in e) ? e.name + ' : ' + e.message : ''; err = {msg, ctx: 'page init - get orders from couchdb', details: e}; console.error(err); report_JS_error(err, 'reception'); // Should be there, redirect alert("Erreur au chargement de cette commande. Vous allez être redirigé."); back(); }); }) .catch(function (e) { let msg = ('message' in e && 'name' in e) ? e.name + ' : ' + e.message : ''; err = {msg, ctx: 'page init - get grouped orders', details: e}; console.error(err); report_JS_error(err, 'reception'); // Should be there, redirect alert("Erreur au chargement de cette commande. Vous allez être redirigé."); back(); }); });