martin-router-king / bushop-dettaglio-ordini

// ==UserScript==
// @name         bushop-dettaglio-ordini
// @namespace    bushop-dettaglio-ordini
// @version      1.5.0
// @description  Aggiunge dettagli alla pagina ordini di Bushop
// @match        https://bushop.org/cronologia-ordini
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    function init() {
        // -------------- Preparazione della struttura di base --------------

        // Rimozione colonna "Metodo di pagamento"
        removeColumn(4);

        // Rimozione colonna "Fattura"
        removeColumn(6);

        // L'ultima colonna viene allargate per recuperare spazio in cui inserire i contenuti
        resizeColumn(7, '50%');

        // -------------- Creazione foglio di stile per tabella --------------

        createCssStyle("table.order-detail-list { margin-bottom:10px; }");
        createCssStyle("table.order-detail-list td.product-detail { padding: 3px 10px !important; border-top:none !important}");
        createCssStyle("table.order-detail-list td.product-detail-name { font-weight:bold }");
        createCssStyle("table.order-detail-list td.product-detail-duration { font-style:italic }");

        // ----------- Gestione ordini --------------

        enhanceOrders();
    }

    /*
    * ################################################# CSS RESTYLE #################################################
    */

    /**
    * Elabora tutte le righe degli ordini
    */
    function enhanceOrders() {
        var ordersTable = document.querySelector("table#order-list");
        var rows = ordersTable.querySelectorAll("tbody tr:not(.ordini-enhanced)");

        rows.forEach(function(row) {
            // Imposto la riga come già elaborata
            row.classList.add("ordini-enhanced");

            // Elaboro al riga
            enhanceOrder(row);
        });
    }

    /**
    * Elabora la riga di un ordine
    */
    function enhanceOrder(row) {
        // [CSS] Il pulsante "Riordina" viene nascosto dalla tabella (disponibile nel dettaglio ordine)
        var historyDetails = row.querySelector("td.history_detail");
        historyDetails.querySelector("a[title='Riordina']").style.display = "none";

        // [CSS] Il pulsante "Dettaglio" diventa float:right
        historyDetails.querySelector("a.button").style.float = "right";

        // Estrazione ID univoco dell'ordine che servirà per recuperare i dettagli via XHR
        var detailsA = row.querySelector("td:first-child a.color-myaccount");
        var href = detailsA.getAttribute("href");
        var pattern = /javascript:showOrder\((\d+), (\d+)/g;
        var match = pattern.exec(href);
        var orderId = match[2];

        // Avvio XHR per recupero dettagli degli ordini, indicando la riga di appartenenza in cui inserire le informazioni una volta scaricate
        fetchOrderDetails(row, orderId);
    }

    /*
    * ################################################# ORDER ENGINE #################################################
    */

    /**
    * Scarica i dettagli di un ordine e inserisce nella riga i prodotti trovati
    */
    function fetchOrderDetails(orderListRow, orderId) {
        var xhrOrders = new XMLHttpRequest();

        // Scaricato il dettaglio degli ordini si inizia ad aggiungere le informazioni
        xhrOrders.onreadystatechange = function() {
            if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
                 try {
                     onFetchOrderDetailsCompleted(orderListRow, this.responseText);
                 } catch(e) {
                     // alert("Errore elaborazione ordine " + orderId + "\n" + e);
                     console.log(e);
                 }
            }
        };

        xhrOrders.open("GET", "/index.php?controller=order-detail&id_order=" + orderId + "&ajax=true", true);
        xhrOrders.send();
    }

    /**
    * Funziona invocata quando l'HTML XHR di un ordine è stato scaricato
    */
    function onFetchOrderDetailsCompleted(orderListRow, responseText) {
        // Estrazione metadati dall'html dell'ordine
        var parsed = parseOrderDetailsHtml(responseText);

        // Elaborazione riga
        enhanceOrderDetails(orderListRow, parsed);
    }

    /**
    * Riceve un contenuto HTML relativo al dettaglio dell'ordine, parse il DOM ed estrae le informazioni inserendole in un oggetto
    */
    function parseOrderDetailsHtml(input) {
        var html = new DOMParser().parseFromString(input, "text/html");

        // Oggetto in cui verranno inseriti tutti i metadati estratti
        var object = {};

        // ############### Riferimento Ordine ###############
        var pattern = /Riferimento Ordine ([A-Z]+) - effettuato il (\d{2,2}\/\d{2,2}\/\d{4,4})/g;
        var match = pattern.exec(html.querySelector("p.dark strong").innerText);
        if(match == null) {
            throw "Riferimento ordine non trovato";
        }
        object.codiceOrdine = match[1];
        object.dataOrdine = match[2];

        // ############### Stato ###############
        var statusTable = html.querySelectorAll("div.table_block table.detail_step_by_step")[0];
        var statusRows = statusTable.querySelectorAll("tbody tr");
        var statuses = [];
        statusRows.forEach(function(tr) {
            statuses.push({
                data : tr.querySelectorAll("td")[0].innerText,
                nome : tr.querySelectorAll("td")[1].innerText
            });
        });
        object.statuses = statuses;

        // ############### Prodotti ###############
        var productsRows = html.querySelectorAll("div#order-detail-content tbody tr");
        var products = [];
        productsRows.forEach(function(tr) {
            var tds = tr.querySelectorAll("td");

            var name = tds[1].innerText.trim();
            var pattern = /(.+)(?: - |, )Modalità Acquisto : Consegna a (\d+) giorni/g;
            var match = pattern.exec(name);
            if(match == null) {
                throw "Nome prodotto/Giorni attesa non trovati nel valore \"" + name + "\"";
            }

            products.push({
                nome : match[1],
                attesa : parseInt(match[2]),
                quantita : parseInt(tds[2].innerText.trim()),
                prezzoUnitario : parseFloat(tds[3].innerText.trim().replace(" €", "")),
                prezzoTotale : parseFloat(tds[4].innerText.trim().replace(" €", ""))
            });
        });
        object.products = products;

        // End of parse
        return object;
    }

    /**
    * Elabora la riga con i dettagli dell'ordine
    */
    function enhanceOrderDetails(orderListRow, json) {
        // Generazione dell'HTML da inserire
        var content = renderProducts(json);

        // Aggiunta HTML alla riga dell'ordine
        var detailsColumn = orderListRow.querySelector("td.history_detail");
        detailsColumn.innerHTML = content + detailsColumn.innerHTML;
    }

    /**
    * Genera l'HTML da inserire con l'elenco dei prodotti
    */
    function renderProducts(json) {
        var currentStatusName = getOrderCurrentStatus(json);

        // "In attesa di pagamento con bonifico bancario": il countdown non può ancora partire
        // "Pagamento accettato": il countdown è partito
        // "Preparazione in corso": il countdown ha superato il valore, sarà negativo
        // "Consegnato": il countdown non è più necessario

        var output = [];

        // ################ Dettagli dell'ordine ################
        output.push("<table class='order-detail-list'>");

        json.products.forEach(function(product) {
            // Potrebbero essere NULL se l'ordine non ha ancora raggiunto lo stato appropriato
            var pagamentoAccettatoDate = getOrderStatusDate(json, "Pagamento accettato");
            var consegnatoDate = getOrderStatusDate(json, "Consegnato");

            output.push("<tr>");
            output.push("<td class='product-detail product-detail-name'>" + product.quantita + "x " + product.nome + "</td>");
            output.push("<td class='product-detail product-detail-days'>" + product.attesa + " giorni</td>");

            // Data prevista di fine ordine
            output.push("<td class='product-detail product-detail-enddate'>");
            if(currentStatusName == "Pagamento accettato" || currentStatusName == "Preparazione in corso" || currentStatusName == "Consegnato") {
                var endDate = pagamentoAccettatoDate.add(product.attesa, 'days');
                output.push(endDate.format("DD-MM-YYYY"));
            }
            output.push("</td>");

            // Stato di avanzamento dell'ordine
            output.push("<td class='product-detail product-detail-datedetails'>");
            var duration;
            if(currentStatusName == "Pagamento accettato" || currentStatusName == "Preparazione in corso") {
                duration = moment.duration(moment().diff(pagamentoAccettatoDate));
                duration = (duration.get("months")*31 + duration.get("days"));
                output.push((duration > 0 ? "+":"") + duration + " giorni");
            } else if(currentStatusName == "Consegnato") {
                duration = moment.duration(consegnatoDate.diff(endDate));
                duration = (duration.get("months")*31 + duration.get("days"));
                output.push((duration > 0 ? "+":"") + duration + " giorni" + "<br />" + consegnatoDate.format("DD-MM-YYYY"));
            }
            output.push("</td>");

            output.push("</tr>");
        });


        // HTML viene montato
        output.push("</table>");
        return output.join("");
    }

    /*
    * ################################################# UTILS #################################################
    */

    /**
    * Fornisce il nome dello stato più recente dell'ordine
    */
    function getOrderCurrentStatus(json) {
        return json.statuses[0].nome;
    }

    /**
    * Fornisce la data relativo allo stato dell'ordine fornito
    */
    function getOrderStatusDate(json, statusName) {
        var returnValue = null;
        json.statuses.forEach(function(item){
            if(item.nome == statusName) {
                returnValue = moment(item.data, 'DD/MM/YYYY');
            }
        });

        return returnValue;
    }

    /*
    * ################################################# SETUP AMBIENTE #################################################
    */

    /**
    * Rimuove una colonna dalla tabella degli ordini con l'indice fornito
    */
    function removeColumn(index) {
        var ordersTable = document.querySelector("table#order-list");
        var columns = ordersTable.querySelectorAll("tr td:nth-child(" + index + "), tr th:nth-child(" + index + ")");
        columns.forEach(function(element) {
            element.style.display = "none";
        });
    }

    /**
    * Ridimensiona la colonna della tabella degli ordini
    */
    function resizeColumn(index, size) {
        var ordersTable = document.querySelector("table#order-list");
        var element = ordersTable.querySelector("tr th:nth-child(" + index + ")");
        element.style.width = size;
    }

    /**
    * Aggiunge uno stile di CSS al documento
    */
    function createCssStyle(content) {
        var style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = content;
        document.querySelector("head").appendChild(style);
    }

    // AVVIO DELLO SCRIPT
    init();
})();