NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name CPQ Config Validator // @version 0.005 // @description Validates CPQ configurations in quotes or favorites // @author Obada Kadri // @match *://*.bigmachines.com/* // @grant GM_addStyle // @license MIT // @require https://code.jquery.com/jquery-3.4.1.min.js // ==/UserScript== // @require https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js (async function() { 'use strict'; const pages = ["commerce_list.jsp", "document.jsp", "model_configs.jsp"]; let configModels = {}; const isConfigValid = configState => { return configState.configData._state.actions._addToTxn.visible || configState.configData._state.actions._save.visible; } const saveConfiguration = async (e, callSettings) => { if ($(e.currentTarget).hasClass("saved")) return false; $(e.currentTarget).text("Saving..."); const configState = await $.ajax(callSettings) .catch(err => alert(`Status: ${err.status} - ${err.statusText}\nResponse: ${err.responseText}`)); $(e.currentTarget).text("Valid (Saved)"); $(e.currentTarget).removeClass("unsaved"); $(e.currentTarget).addClass("saved"); }; const doValidation = async (e, modelInfo, callSettings) => { if ($(e.currentTarget).hasClass("invalid")) return false; $(e.currentTarget).text("Validating..."); $(e.currentTarget).removeClass("valid invalid unsaved"); $(e.currentTarget).addClass("in-progress"); const configState = await $.ajax(callSettings) .catch(err => alert(`Status: ${err.status} - ${err.statusText}\nResponse: ${err.responseText}`)); console.log(configState); $(e.currentTarget).removeClass("in-progress"); if (configState.configData.searchMessages_HWSW && configState.configData.searchMessages_HWSW.length > 50) { var modalHtml = ` <!-- Modal --> <div class="modal fade" id="configHealthCheck" role="dialog"> <div class="modal-dialog modal-lg" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">${modelInfo.config_name} Messages</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div id="config-hc-body">${configState.configData.searchMessages_HWSW}</div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> </div> </div> </div> </div> `; $("body").append(modalHtml); $('#configHealthCheck').modal('show'); } if (isConfigValid(configState)) { $(e.currentTarget).text("Valid"); // Valid (unsaved) $(e.currentTarget).addClass("valid"); // valid unsaved $(e.currentTarget).off("click"); //const saveAction = configState.configData._state.actions._save.visible ? "_save" : "_addToTxn"; $(e.currentTarget).click(saveEvent => { saveEvent.preventDefault(); /*const saveCallSettings = { "url": `/rest/v8/config${modelInfo.pf_segment}.${modelInfo.product_line}.${modelInfo.configuration}/actions/${saveAction}`, "method": "POST", "headers": { "Content-Type": "application/json", }, "data": JSON.stringify({"cacheInstanceId":configState.cacheInstanceId}), }; saveConfiguration(saveEvent, saveCallSettings);*/ $('#configHealthCheck').modal('show'); }); } else { $(e.currentTarget).text("Invalid"); $(e.currentTarget).addClass("invalid"); $(e.currentTarget).click(clickEvent => { clickEvent.preventDefault(); $('#configHealthCheck').modal('show'); }); } }; const setConfigModels = async () => { console.info("CPQ Config Validator - Fetching configModelInfo"); // Populate the configModels from configModelInfo datatable const modelCallSettings = { "url": "/rest/v8/customConfigModelInfo", "method": "GET", "headers": { "Content-Type": "application/json", } }; configModels = await $.ajax(modelCallSettings); console.log(configModels); } // Only quote and favorites pages if (pages.indexOf(document.URL.split("/").pop().split("?").shift()) >= 0) { console.info("CPQ Config Validator"); await setConfigModels(); $("head").append( '<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">' ); // Add validate button next to favorite configs $("input[name=ids]:not(:disabled)").each((i, e) => { const modelTd = $(e).closest("td"); const validateBtn = $(`<button class="validate-btn" data-fav-id="${$(e).val()}">Validate</button>`); modelTd.append(validateBtn); $("button.validate-btn").click(e => { e.preventDefault(); // Scrape the model name from the 5th column const favId = $(e.currentTarget).data("favId"); const modelName = $(e.currentTarget).closest("tr").find("td:nth-child(5)").text().split('/').slice(1).join('/').trim(); const modelInfo = configModels.items.find(model => model.config_name.replace(/\s/g, " ") === modelName.replace(/\s/g, " ")); const favCallSettings = { "url": `/rest/v8/config${modelInfo.pf_segment}.${modelInfo.product_line}.${modelInfo.configuration}/actions/_reconfigureFav`, "method": "POST", "headers": { "Content-Type": "application/json" }, "data": JSON.stringify({"favoriteId":favId,"criteria":{"state":true}}), }; doValidation(e, modelInfo, favCallSettings); }); }); // Quote configs const modelPnTds = $("#line-item-grid tr.parent-line-item td[id*=__part_number]"); modelPnTds.each((i, modelTd) => { const validateBtn = $(`<button class="validate-btn">Validate</button>`); // Scrape the model name from the Item column const modelTr = $(modelTd).closest("tr"); const modelName = modelTr.find("td[id*=_displayedItemName_l]").text(); const modelInfo = configModels.items.find(model => model.config_name.replace(/\s/g, " ") === modelName.replace(/\s/g, " ")); const bsid = parseInt($("[name=id]").val()); const documentNumber = parseInt(modelTr.attr("documentNumber")); const documentId = parseInt(modelTr.attr("documentId")); if (modelInfo) { console.info(`Adding validate button for ${modelName}`); $(modelTd).append(validateBtn); $("button.validate-btn").click(e => { e.preventDefault(); const quoteCallSettings = { "url": `/rest/v8/config${modelInfo.pf_segment}.${modelInfo.product_line}.${modelInfo.configuration}/actions/_reconfigureTxn`, "method": "POST", "headers": { "Content-Type": "application/json" }, "data": JSON.stringify({"bsId":bsid,"documentNumber":documentNumber,"documentId":documentId,"legacyMode":true,"mainDocAction":true,"criteria":{"state":true}}), }; doValidation(e, modelInfo, quoteCallSettings); }); } }); } })(); // Styling Stuff var bm_css_src = ` button.validate-btn { width: 8em; background-color: #fff; border: 1px solid #08c; box-shadow: 1px 1px 4px 0px rgba(0, 0, 0, .25); color: #08c; opacity: 1; white-space: nowrap; cursor: pointer; } button.validate-btn.in-progress { border-color: #ffbf00; color: #ffbf00 } button.validate-btn.valid { border-color: #008900; color: #008900; } button.validate-btn.unsaved { border-color: #ffbf00; } button.validate-btn.invalid { border-color: #a00000; color: #a00000; } ul.ok-tool-bar { position: fixed; bottom: 0; right: 10%; margin: 0; } ul.ok-tool-bar li { display: inline-block; padding: 3px 6px; margin: 0 3px; font-size: 11px; white-space: nowrap; vertical-align: middle; -ms-touch-action: manipulation; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-image: none; border: 1px solid transparent; border-radius: 5px 5px 0 0; } li.btn-config-hc { color: #fff; background-color: #660000; border-color: #660000; } `; // eslint-disable-next-line no-undef GM_addStyle(bm_css_src);