shelf_inventory.js 43.1 KB
Newer Older
Administrator committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
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"),
15 16 17
    add_product_form = $("#add_product_form"),
    change_shelf_form = $("#change_shelf_form"),
    change_shelf_btn = $('#change_shelf_btn');
Administrator committed
18

19
var shelf = null,
Administrator committed
20
    parent_location = '/shelfs',
21
    originView = "shelf", // or custom_list (create from order view)
Administrator committed
22
    list_to_process = [],
23 24
    table_to_process = null,
    table_processed = null,
25
    editing_item = null, // Store the item currently being edited
26
    editing_origin = "", // Keep track of where editing_item comes from
27
    processed_row_counter = 0, // Keep count of the order the item were added in processed list
Administrator committed
28 29
    search_chars = [],
    user_comments = '',
30
    adding_product = false, // True if modal to add a product is open.
31
    barcodes = null, // Barcodes stored locally
32
    // datetime for which shelf's ongoing_inv_start_datetime is considered null
33 34
    default_inventory_start_datetime = "0001-01-01 00:00:00",
    selected_products_for_shelf_change = [],
35
    all_shelfs = null, // Use get_all_shelfs to access it's value
Damien Moulard committed
36
    debounce_timeout = null;
Administrator committed
37

38
let lastKeypressTime = 0;
Administrator committed
39 40 41 42 43

/* UTILS */

// polyfill to check for safe integers: Number method not supported by all browsers
Number.isInteger = Number.isInteger || function(value) {
44
    return typeof value === 'number' &&
Administrator committed
45 46 47 48 49 50 51
    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) {
52
    return Number.isInteger(value) && Math.abs(value) <= Number.MAX_SAFE_INTEGER;
Administrator committed
53 54
};

55 56


Administrator committed
57
function back() {
Damien Moulard committed
58
    document.location.href = parent_location;
Administrator committed
59 60
}

61 62 63 64 65
function debounce(func, delay = 100) {
    clearTimeout(debounce_timeout);
    debounce_timeout = setTimeout(func, delay);
}

66 67
function get_added_qties_sum(item) {
    let total = null;
68

69 70 71 72 73
    function add(accumulator, a) { // for array sum
        result = 0;
        if (a) {
            if (item.uom_id[1] == "kg") {
                if (typeof a === "string") {
74
                    a = a.replace(',', '.');
75 76 77 78 79 80 81
                }
                result = parseFloat(accumulator) + parseFloat(a);
                result = result.toFixed(3);
            } else {
                result = parseInt(accumulator, 10) + parseInt(a, 10);
            }
        }
82

83 84 85 86 87
        return result;
    }
    if (typeof item.added_qties != "undefined" && item.added_qties.length > 0) {
        total = item.added_qties.reduce(add);
    }
88

89 90 91 92 93

    return total;
}
function barcode_analyzer(chars) {
    let barcode = chars;
94

95 96 97
    if (barcode && barcode.length >=13) {
        barcode = barcode.substring(barcode.length-13);
    } else if (barcode && barcode.length == 12 && barcode.indexOf('0') !== 0) {
98
        // User may use a scanner which remove leading 0
99
        barcode = '0' + barcode;
100 101 102
    } else if (barcode && barcode.length >= 8) {
        // For EAN8
        barcode = barcode.substring(barcode.length-8);
103
    } else {
104
        //manually submitted after correction
105
        var barcode_input = $('#search_input');
106

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
        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);
    }
122 123

    search_chars = [];
124
}
125

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
/**
 * 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();
//     }
// }

Félicie committed
143 144 145 146
function refresh() {
    location.reload();
}

Administrator committed
147 148
// Directly send a line to edition when barcode is read
function select_product_from_bc(barcode) {
Damien Moulard committed
149 150 151 152
    if (barcode === "" || barcode === null || barcode === undefined) {
        return -1;
    }

153 154
    var found = null,
        qty = null;
155

156
    if (isValidEAN13(barcode) || isValidEAN8(barcode)) {
157
        var scannedProduct = barcodes.get_corresponding_odoo_product(barcode);
158

159 160
        if (scannedProduct === null) {
            alert("Le code-barre " + barcode + " ne correspond à aucun article connu.");
161

162 163 164 165 166
            return -1;
        } else {
            barcode = scannedProduct.barcode;
            if (scannedProduct.rule.length > 0 && scannedProduct.rule != "product") {
                qty = scannedProduct.qty;
167
            }
168
        }
169
    } else {
170
        alert("Le code-barre " + barcode + " n'est pas reconnu comme un EAN13 ou EAN8 valide.");
171

172
        return -1;
173
    }
Administrator committed
174

175
    if (editing_item === null) {
176

177 178 179 180 181 182 183 184 185 186 187
        $.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;
188 189
                    if (qty) {
                        let message = "Attention, ce produit a déjà été compté.\n";
190

191 192
                        message += "La quantité " + qty + " n'a pas été ajoutée !\n";
                        // temporary add read qty and recorded one to added_qties to compute sum
193 194 195 196
                        found.added_qties = [
                            qty,
                            found.qty
                        ];
197 198 199 200
                        message += "Le total serait " + get_added_qties_sum(found);
                        alert(message);
                        qty = null;
                    }
201

202 203 204
                    editing_origin = 'processed';
                }
            });
Administrator committed
205 206
        }

207
        if (found !== null) {
208 209
            delete found.added_qties;
            setLineEdition(found, qty);
210
            if (editing_origin === 'to_process') {
211
                let row = table_to_process.row($('tr#'+found.id));
212

213 214
                remove_from_toProcess(row);
            } else {
215
                let row = table_processed.row($('tr#'+found.id));
216

217 218 219 220
                remove_from_processed(row);
            }
        } else {
            console.log('Code barre introuvable');
221
            alert("Le produit bippé n'est pas enregistré dans ce rayon.");
222
        }
223
    } else if (barcode == editing_item.barcode && qty) {
224 225
        // We scan the same product as the current one
        let edition_input = $('#edition_input');
226

227 228 229 230 231
        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
232 233
        editing_item.qty = get_added_qties_sum(editing_item);
        edition_input.val(editing_item.qty);
Administrator committed
234
    }
235 236

    return null;
Administrator committed
237 238 239 240 241 242 243 244
}

/*
  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) {
245 246
    element.addEventListener('animationend', onAnimationEnd);
    element.addEventListener('webkitAnimationEnd', onAnimationEnd);
Administrator committed
247

248
    function onAnimationEnd() {
249 250
        element.classList.remove('blink_me');
    }
Administrator committed
251 252 253 254 255 256 257
}


/* EDITION */

// When edition event is fired
function edit_event(clicked) {
258
    // Remove from origin table
259 260
    var row_data = null;

261
    if (editing_origin == 'to_process') {
262
        let row = table_to_process.row(clicked.parents('tr'));
263 264

        row_data = row.data();
265 266 267

        remove_from_toProcess(row);
    } else {
268
        let row = table_processed.row(clicked.parents('tr'));
269 270

        row_data = row.data();
271 272 273 274 275 276 277 278 279 280 281 282 283

        remove_from_processed(row);
    }

    // Product goes to editing
    setLineEdition(row_data);

    // Reset search
    $('#search_input').val('');
    $('table.dataTable').DataTable()
        .search('')
        .draw();
    search_chars = [];
Administrator committed
284 285 286
}

// Set edition area
287 288 289 290
/**
 * If qty is not null, it comes from barcode reading result
 * */
function setLineEdition(item, qty) {
291 292
    var edition_input = $('#edition_input'),
        set_focus = true;
293

294 295 296
    editing_item = item;
    $('#product_name').text(editing_item.name);
    $('#product_uom').text(editing_item.uom_id[1]);
Administrator committed
297

298 299 300 301 302 303 304
    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);
    }
305 306 307 308 309 310 311 312 313

    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);
314
        set_focus = false;
315
    }
316 317
    // If item is reprocessed, set input with value
    if (editing_origin == 'processed') {
318
        edition_input.val(editing_item.qty);
319
    }
320

321 322
    if (set_focus === true)
        edition_input.focus();
Administrator committed
323

324 325
    // Make edition area blink when edition button clicked
    container_edition.classList.add('blink_me');
Administrator committed
326 327 328 329
}

// Clear edition
function clearLineEdition() {
330
    editing_item = null;
Administrator committed
331

332 333 334
    $('#product_name').text('');
    $('#edition_input').val('');
    $('#search_input').focus();
335
    // $("#reset_to_previous_qty").hide();
Administrator committed
336 337
}

338 339 340 341
/**
 * Validate product edition.
 * Keep track of every qty change.
 */
342
function validateEdition() {
343
    if (editing_item != null) {
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
        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;

367 368 369
        if (editProductInfo(editing_item)) {
            clearLineEdition();
        }
Administrator committed
370 371 372
    }
}

373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
/**
 * 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 = [];
}
Administrator committed
392 393 394 395 396
/*
 * Update a product info and add it to processed items.
 * If 'value' is set, use it as new value.
*/
function editProductInfo (productToEdit, value = null) {
397 398 399 400
    // If 'value' parameter not set, get value from edition input
    var newValue = (value == null) ? parseFloat($('#edition_input').val()
        .replace(',', '.')) : value;

401 402 403 404 405 406
    if (isNaN(newValue)) {
        alert("Veuillez rentrer une valeur valide dans le champ d'édition !");

        return false;
    }

407 408
    // If uom is unit, prevent float
    if (productToEdit.uom_id[0] == 1 && !Number.isSafeInteger(newValue)) {
409
        alert('Vous ne pouvez pas rentrer de chiffre à virgule pour des produits à l\'unité.');
Administrator committed
410

411 412
        return false;
    }
Administrator committed
413

414
    productToEdit.qty = newValue;
Administrator committed
415

416
    add_to_processed(productToEdit);
Administrator committed
417

418
    // Update local storage
419
    localStorage.setItem(originView + "_" + shelf.id, JSON.stringify(shelf));
Administrator committed
420

421
    return true;
Administrator committed
422 423
}

424 425 426 427 428 429 430 431
/* 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({
432 433 434 435 436 437 438 439 440 441
            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
442
                    resolve(null);
443 444 445 446 447 448
            },
            error: function() {
                alert("Impossible de mettre à jour les données");
                resolve(null);
            }
        });
449 450 451 452 453 454
    });
}

// call on change_shelf_btn click action
async function open_change_shelf_modal() {
    selected_products_for_shelf_change = [];
455
    $('.select_product_cb:checked').each(function(idx, elt) {
456
        const row = $(elt).closest('tr');
457 458

        selected_products_for_shelf_change.push(table_to_process.row(row).data());
459 460 461 462 463 464
    });
    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
        */
465

466
        let shelfs = await get_all_shelfs();
467

468 469 470 471 472
        if (shelfs !== null) {
            let modal_content = $('#templates #change_shelf_form').clone(),
                shelf_selector = $('<select>').addClass('shelf_selection'),
                table = modal_content.find('table tbody').empty();

473 474
            /* construct shelfs selector */
            // first of all, sort by name
475
            shelfs.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
476 477
            // if ahead_shelfs_ids is not empty, put them ahead
            if (ahead_shelfs_ids.length > 0) {
478 479 480 481 482
                let to_move = {},
                    idx = 0;
                // find index of shelfs to move

                shelfs.forEach((shelf) => {
483 484 485 486
                    if (ahead_shelfs_ids.indexOf(shelf.id) > -1) {
                        to_move[shelf.id] = idx;
                    }
                    idx += 1;
487 488 489 490 491 492
                });
                // 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) => {
493
                    let shelf = shelfs[to_move[shelf_id]];
494 495 496 497 498 499 500 501 502

                    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);
503
            }
504 505 506 507 508 509 510

            shelfs.forEach((shelf) => {
                let option = $('<option>')
                    .val(shelf.id)
                    .text(shelf.name + ' (' + shelf.sort_order + ')');

                shelf_selector.append(option);
511
            });
512
            /* add product rows */
513 514 515 516
            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()));
517

518
                table.append(tr);
519 520 521 522 523 524 525 526 527
            });

            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 = [];
528 529

                            $('.overlay-content table tbody tr').each(function(idx, e) {
530
                                data.push({
531 532 533 534
                                    product_id : $(e).data('id'),
                                    shelf_id : $(e).find('select')
                                        .val()
                                });
535 536
                            });
                            const update_result = await record_products_shelf_on_server(data);
537

538
                            if (update_result !== null) {
539 540 541
                                update_result.forEach((product_id) => {
                                    remove_from_toProcess(table_to_process.row($('tr#'+product_id)));
                                });
542
                                let message = "L'opération a bien réussi.";
543

544 545 546 547
                                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
548
                                }
549 550 551 552 553 554 555
                                $.notify(
                                    message,
                                    {
                                        globalPosition:"top right",
                                        className: "info"
                                    }
                                );
556
                            }
557
                        };
558 559 560 561 562 563
                        make_change();
                    }
                },
                'Changer les produits de rayons'
            );
        } else {
564
            alert("Les informations des autres rayons n'ont pas pu être récupérées.");
565
        }
566

567 568
    }
}
Administrator committed
569 570 571 572

/* LISTS HANDLING */

// Init Data & listeners
573 574
function initLists() {
    if ('list_processed' in shelf) {
Administrator committed
575
    // Remove processed items from items to process
576 577
        for (processed_item of shelf.list_processed) {
            var index_in_toProcess = list_to_process.findIndex(x => x.id == processed_item.id);
Administrator committed
578

579 580 581 582 583 584
            if (index_in_toProcess > -1) {
                list_to_process.splice(index_in_toProcess, 1);
            }
        }
    } else {
        shelf.list_processed = [];
Administrator committed
585
    }
586 587

    // Init table for items to process
588 589 590 591 592 593 594

    var columns_to_process = [];

    if (shelf.inventory_status !== "") {
        columns_to_process.push({data:"id", title: "id", visible: false});
    } else {
        columns_to_process.push({
595
            title: `<div id="table_header_select_all" class="txtcenter">
596 597
                                            <input type="checkbox" id="select_all_products_cb" name="select_all_products_cb" value="all">
                                        </div>`,
598 599
            className: "dt-body-center",
            orderable: false,
600
            render: function () {
601 602 603
                return `<input type="checkbox" class="select_product_cb" />`;
            },
            width: "4%"});
604 605
    }
    columns_to_process = columns_to_process.concat([
606 607 608 609 610 611 612
        {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
        }
613
    ]);
614

615
    if (originView == 'custom_list') {
616
        columns_to_process.splice(1, 0, {data:"shelf_sortorder", title:"Rayon", className:"dt-body-center"});
Administrator committed
617 618
    }

619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
    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
        }
    ];
Administrator committed
650

651
    if (originView == 'custom_list') {
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
        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'}
    });
Administrator committed
671 672


673
    /* Listeners on tables & search input */
Administrator committed
674

675 676
    // Edit line from items to process
    $('#table_to_process tbody').on('click', 'a#process_item', function () {
Administrator committed
677
    // Prevent editing mutiple lines at a time
678 679 680 681 682
        if (editing_item == null) {
            editing_origin = "to_process";
            edit_event($(this));
        }
    });
Administrator committed
683

684 685
    // Edit line from items processed
    $('#table_processed tbody').on('click', 'a#reprocess_item', function () {
Administrator committed
686
    // Prevent editing mutiple lines at a time
687 688 689 690 691
        if (editing_item == null) {
            editing_origin = "processed";
            edit_event($(this));
        }
    });
Administrator committed
692

693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
    // 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();
        }
    });
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747

    // 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);
Administrator committed
748 749 750 751
}

// Add a line to the 'items to process' list
function add_to_toProcess(product) {
752 753
    // Add to list
    list_to_process.push(product);
Administrator committed
754

755 756 757
    // Add to table (no data binding...)
    var rowNode = table_to_process.row.add(product).draw(false)
        .node();
Administrator committed
758

759 760 761
    // Blinking effect on newly added row
    handle_blinking_effect(rowNode);
    $(rowNode).addClass('blink_me');
Administrator committed
762 763 764
}

// Remove a line from the 'items to process' list
765 766
function remove_from_toProcess(row) {
    item = row.data();
Administrator committed
767

768 769
    // Remove from list
    var index = list_to_process.indexOf(item);
Administrator committed
770

771 772 773 774 775 776
    if (index > -1) {
        list_to_process.splice(index, 1);
    }

    // Remove from table
    row.remove().draw();
Administrator committed
777 778 779
}

// Add a line to the 'items processed' list
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
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');
Administrator committed
797 798 799
}

// Remove a line from 'items processed'
800 801
function remove_from_processed(row) {
    let item = row.data();
Administrator committed
802

803 804
    // Remove from list
    let index = shelf.list_processed.indexOf(item);
Administrator committed
805

806 807 808 809 810 811
    if (index > -1) {
        shelf.list_processed.splice(index, 1);
    }

    //Remove from table
    row.remove().draw();
Administrator committed
812 813 814 815 816 817 818
}


/* ACTIONS */

// Set the quantity to 0 for all the remaining unprocessed items
function confirmProcessAllItems() {
819
    openModal();
Administrator committed
820

821
    // Iterate over all rows in table of items to process
822
    table_to_process.rows().every(function () {
823
        var data = this.data();
Administrator committed
824

825
        editProductInfo(data, 0);
826 827

        return 1;
828
    });
Administrator committed
829

830 831 832 833 834 835
    // Reset data
    list_to_process = [];
    table_to_process.rows().remove()
        .draw();

    closeModal();
Administrator committed
836 837 838 839
}

// Verifications before processing
function pre_send() {
840 841 842 843 844 845 846 847
    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);
    }
Administrator committed
848 849 850 851
}

// Proceed with inventory: send the request to the server
function send() {
852
    if (is_time_to('submit_inv_qties')) {
853
        // Loading on
854 855 856 857 858 859 860 861
        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;

862
        var url = "../do_" + originView + "_inventory";
863
        var call_begin_at = new Date().getTime();
864 865 866 867 868 869 870 871 872

        $.ajax({
            type: "PUT",
            url: url,
            dataType: "json",
            traditional: true,
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(shelf),
            success: function(data) {
873
                let next_step_call = back;
874

875 876 877
                // If step 1, display additionnal message in validation popup
                if (shelf.inventory_status == '') {
                    inventory_validated_msg.find('#step1_validated').show();
878 879
                    next_step_call = refresh;

880 881 882 883 884 885 886 887 888 889 890
                }

                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>');
                        }
                    }
                }

Félicie committed
891
                var msg = (originView == 'shelf') ? 'OK, je passe à la suite !' : 'Retour';
892

893 894 895 896
                // 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);
897 898

                // Clear local storage before leaving
899
                localStorage.removeItem(originView + '_' + shelf.id);
900
            },
901
            error: function(jqXHR) { // 500 error has been thrown or web server sent a timeout
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953
                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();
954
                }
Administrator committed
955
            }
956 957 958 959
        });
    } else {
        alert('Clic reçu il y a moins de 5 secondes. La demande est en cours de traitement.');
    }
Administrator committed
960 961 962 963

}

function exit_adding_product() {
964 965
    $('input.add_product_input').val('');
    adding_product = false;
Administrator committed
966 967
}

968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996
/**
 * 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");
            }
        });
    }
}

Administrator committed
997 998
// Add a product that's not in the list
function open_adding_product() {
999
    if (originView == 'shelf') {
1000
        adding_product = true;
Administrator committed
1001

1002 1003 1004
        openModal(add_product_form.html(), do_add_product, 'Valider', false, true, exit_adding_product);
        $('input.add_product_input').focus();
    }
Administrator committed
1005 1006 1007
}

function do_add_product() {
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
    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),
1020
        success: function() {
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
            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);
        }
    });
Administrator committed
1041 1042 1043
}


1044 1045
function openFAQ() {
    openModal(faq_content.html(), function() {}, 'Compris !', true, false);
Administrator committed
1046 1047
}

1048 1049 1050 1051
function openIssuesReport() {
    openModal(issues_reporting.html(), saveIssuesReport, 'Confirmer');

    var textarea = $("#issues_report");
Administrator committed
1052

1053 1054
    textarea.val(user_comments);
    textarea.focus();
Administrator committed
1055 1056 1057
}

function saveIssuesReport() {
1058 1059
    user_comments = $('#issues_report').val();
    $('#search_input').focus();
Administrator committed
1060 1061 1062 1063 1064
}


/* INIT */

1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095
// (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);
                }
            });
        }
    });
}

Administrator committed
1096 1097
// Get shelf data from server if not in local storage
function get_shelf_data() {
1098
    var url = (originView == 'shelf') ? '../' + shelf.id : '../get_custom_list_data?id=' + shelf.id;
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108

    $.ajax({
        type: 'GET',
        url: url,
        dataType:"json",
        traditional: true,
        contentType: "application/json; charset=utf-8",
        success: function(data) {
            shelf = data.res;
            init();
1109
            set_begin_inventory_datetime();
1110 1111 1112 1113 1114 1115 1116 1117
        },
        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.');
        }
    });
Administrator committed
1118 1119 1120 1121
}

// Init page : to be launched when shelf data is here
function init() {
1122 1123 1124 1125 1126 1127
    // 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();

1128 1129 1130 1131 1132 1133 1134 1135 1136 1137
    // 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++;

1138
    // Set DOM
1139
    if (originView == "shelf") {
1140 1141 1142 1143 1144 1145 1146 1147 1148
        $('#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
Administrator committed
1149
    // Header
1150 1151 1152 1153
        $('#header_step_one').addClass('step_one_active');

        // Container items to process
        $('#container_left').css('border', '3px solid #212529');
Administrator committed
1154

1155 1156
        // Container processed items
        $('#container_right').css('border', '3px solid #0275D8');
Administrator committed
1157

1158 1159
        // Edition
        $('#edition_header').text('Quantité en rayon');
Administrator committed
1160

1161 1162 1163 1164 1165 1166
        // 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');
Administrator committed
1167

1168
        var check_icon = document.createElement('i');
Administrator committed
1169

1170 1171
        check_icon.className = 'far fa-check-circle';
        $('#header_step_one_content').append(check_icon);
Administrator committed
1172

1173 1174 1175
        // Containers
        $('#container_left').css('border', '3px solid #0275D8');
        $('#container_right').css('border', '3px solid #5CB85C');
Administrator committed
1176

1177 1178 1179 1180 1181 1182 1183
        // Edition
        $('#edition_header').text('Quantité en réserve');

        // Validation button
        $('#validate_inventory').addClass('btn--success');
        $('#add_product_to_shelf').addClass('btn--primary');
    }
Administrator committed
1184 1185


1186 1187 1188 1189 1190
    // 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);
1191
    $(document).on('click', 'button#process_all_items', function () {
1192 1193
        openModal(process_all_items_msg.html(), confirmProcessAllItems, 'Confirmer', false);
    });
Administrator committed
1194

1195 1196
    // Action at modal closing
    $(document).on('click', 'a#modal_closebtn_top', exit_adding_product);
Administrator committed
1197 1198


1199 1200
    // Load FAQ modal content
    faq_content.load("/shelfs/shelf_inventory_FAQ");
Administrator committed
1201

1202 1203
    // Handle blinking effect on edition area
    var container_edition = document.querySelector('#container_edition');
Administrator committed
1204

1205
    handle_blinking_effect(container_edition);
Administrator committed
1206

1207
    // Disable mousewheel on an input number field when in focus
1208
    $('#edition_input').on('focus', function () {
1209 1210 1211
        $(this).on('wheel.disableScroll', function (e) {
            e.preventDefault();
            /*
Administrator committed
1212 1213 1214 1215 1216 1217 1218
        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);
      */
1219
        });
1220 1221 1222 1223 1224 1225 1226 1227 1228

        // 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();
        // }
Administrator committed
1229
    })
1230
        .on('blur', function () {
1231 1232 1233
            $(this).off('wheel.disableScroll');
        });

1234
    // Manual and textual input
1235
    $('#search_input').keypress(function(e) {
1236
        if (e.which >= 48 && e.which <= 57) { // figures [0-9]
1237
            search_chars.push(String.fromCharCode(e.which));
1238
        } else if (e.which == 13 || search_chars.length >= 13) {
1239
            e.stopPropagation(); // Prevent validation edition in this specific case
1240
            debounce(barcode_analyzer); // Avoid concurrent barcode analysing
1241 1242
        }
    });
1243 1244

    $(document).pos();
1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257
    $(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é.");
        }
    });
1258 1259 1260
    $(document).on('scan.pos.barcode', function(event) {
        //access `event.code` - barcode data
        var barcode = event.code;
1261

1262
        debounce(() => barcode_analyzer(barcode)); // Avoid concurrent barcode analysing
1263 1264
    });

Administrator committed
1265 1266
}

1267 1268 1269 1270
// Load barcodes at page loading, then barcodes are stored locally
var get_barcodes = async function() {
    if (barcodes == null) barcodes = await init_barcodes();
};
Administrator committed
1271 1272

$(document).ready(function() {
1273
    // Get Route parameter
1274 1275
    $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });

1276 1277 1278 1279 1280 1281
    var pathArray = window.location.pathname.split('/');

    shelf = {id: pathArray[pathArray.length-1]};

    // Working on a shelf
    if (pathArray.includes('shelf_inventory')) {
1282
        originView = 'shelf';
1283 1284
        parent_location = '/shelfs';
    } else {
1285
        originView = 'custom_list';
1286 1287 1288 1289 1290
        parent_location = '/inventory/custom_lists';
    }

    // Get shelf data from local storage
    if (Modernizr.localstorage) {
1291
        var stored_shelf = JSON.parse(localStorage.getItem(originView + '_' + shelf.id));
1292 1293 1294

        if (stored_shelf != null) {
            shelf = stored_shelf;
1295

1296
            init();
1297
            set_begin_inventory_datetime();
1298 1299 1300 1301
        } else {
            // Get shelf info if not coming from shelves list
            get_shelf_data();
        }
Administrator committed
1302
    } else {
1303
        get_shelf_data();
Administrator committed
1304
    }
1305
    get_barcodes();
1306

1307 1308 1309 1310 1311 1312 1313 1314 1315 1316
    /**
     * 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();
        }
    });

Administrator committed
1317
});