Virtonomica: управление закупками на склад

Корректирует закупки на склад при условии что поставщиков у каждого товара может быть несколько. Учитывает доступные остатки/ограничения_количества и пропорционально увеличивает/уменьшает закупку.

// ==UserScript==
// @name           Virtonomica: управление закупками на склад
// @version        1.11
// @namespace      Virtonomica
// @description    Корректирует закупки на склад при условии что поставщиков у каждого товара может быть несколько. Учитывает доступные остатки/ограничения_количества и пропорционально увеличивает/уменьшает закупку.
// @include        http*://*virtonomic*.*/*/main/unit/view/*/supply
// ==/UserScript==

var run = function() {
  //ждем заполнения .bg-image скриптом
  $( document ).ready(function() {
	var win = (typeof(unsafeWindow) != 'undefined' ? unsafeWindow : top.window);
	$ = win.$;

    var src = $('.bg-image').attr('class'); 
	//если это не склад, выходим
	if(src.indexOf("warehouse") <= 0){
      return;
    }
	
	function toFloat(spNum){
		//console.log('toFloat = ' + spNum);
		if(spNum == null || spNum == '') {
			return 0;
		}
		return parseFloat(spNum.replace('$','').replace('"','').replace(/\s+/g,''),10);
	}
	
	function priceToFloat(spNum){
		//console.log('priceToFloat = ' + spNum);
		if(spNum == null || spNum == '') {
			return 0;
		}
		var nvSlashPos = spNum.indexOf("/");
		if(nvSlashPos > -1){
			return toFloat(spNum.substr(nvSlashPos + 1));
		} else {
			return toFloat(spNum);
		}
	}
	
	function remainToFloat(spNum){
		//console.log('remainToFloat = ' + spNum);
		if(spNum == null || spNum == '') {
			return 0;
		}
		
		var nvSpanCloseTagPos = spNum.indexOf(">");
		var nvBrPos = spNum.indexOf("<br>");
		if(nvBrPos > -1){
			var svNum = spNum.substr(nvSpanCloseTagPos+1, nvBrPos-nvSpanCloseTagPos-1);
			var nvInPos = svNum.indexOf("из");
			if(nvInPos > -1){
				return Math.min(toFloat(svNum.substr(0, nvInPos)), toFloat(svNum.substr(nvInPos+3)));
			} else {
				return toFloat(svNum);
			}
		} else {
			return toFloat(spNum);
		}
	}
	
	var data = [];
	function addProduct(spProductTitle, npQtyNeed, npQtyOrdered, opAddContras){
		//console.log('addProduct "'+spProductTitle+'"');
		data[spProductTitle] = {
			aContras : []
		   ,nQtyNeed : npQtyNeed
		   ,nQtyOrdered : npQtyOrdered
		   ,oAddContras : opAddContras
		};
	}
	
	function addContras(spProductTitle, opInputOrderQty, npOrderedQty, npPrice, npQuality, npRemain){
		//console.log('addContras "'+spProductTitle+'"');
		data[spProductTitle].aContras.push({
			oInputOrderQty : opInputOrderQty
		   ,nOrderedQty    : npOrderedQty
		   ,nPrice    	   : npPrice
		   ,nQuality       : npQuality
		   ,nRemain        : npRemain
		});
	}
	
	function collectData(){
		var svProductTitle = '';
		data = [];
		$("table.list > tbody > tr[class]").each( function() {
			var row = $(this);
			if(row.attr('class') == 'p_title'){
				//Отгрузки по контрактам
				// > td.p_title_l > div:nth-child(2) > table > tbody > tr > td:nth-child(2) > strong
				var nvQtyNeed = toFloat($('> td.p_title_l > div:nth-child(2) > table > tbody > tr:nth-child(3) > td:nth-child(2) > strong', row).first().text());
				console.log('nvQtyNeed = ' + nvQtyNeed);
				var nvQtyOrdered = toFloat($('> td:nth-child(2) > nobr > strong', row).first().text());
				console.log('nvQtyOrdered = ' + nvQtyOrdered);
				var ovProductImg = $('> td.p_title_l > div:nth-child(1) > div:nth-child(1) > div > img', row);
				var ovAddContras = $('> td.p_title_l > div:nth-child(1) > div:nth-child(2) > a:nth-child(2)', row);
				
				svProductTitle = ovProductImg.attr('title');
				//console.log('svProductTitle = ' + svProductTitle);
				addProduct(svProductTitle, nvQtyNeed, nvQtyOrdered, ovAddContras);
			} else if (row.attr('class') == 'odd' || row.attr('class') == 'even'){
				var ovInputOrderQty = $('> td:nth-child(2) > input[type="text"]:nth-child(1)', row);
				ovInputOrderQty.attr('old_val', ovInputOrderQty.val());
				var nvOrderedQty = toFloat($('> td:nth-child(3)', row).first().text());
				var nvPrice = priceToFloat($('> td:nth-child(4)', row).first().text());
				var nvQuality = toFloat($('> td:nth-child(6)', row).first().text());
				var nvRemain = remainToFloat($('> td:nth-child(9)', row).first().html());
				if ($(' > td:nth-child(1) > div:nth-child(3) > img[src="/img/unit_types/seaport.gif"]', row).length > 0){
				   nvRemain = Number.POSITIVE_INFINITY;
				}
				
				addContras(svProductTitle, ovInputOrderQty, nvOrderedQty, nvPrice, nvQuality, nvRemain);
			}
		});
	}
	
	function sortTable(){
		var filtered = true;
		for(var producTitle in data){
			//console.log(producTitle);
	  		var avContras = data[producTitle].aContras;
			var iterCnt = 0;
	  		do {
				filtered = true;
				for(var i = 0; i < avContras.length; i++) {
					if(i > 0 && avContras[i].nPrice / avContras[i].nQuality < avContras[i-1].nPrice / avContras[i-1].nQuality){
						var tbody = avContras[i].oInputOrderQty.closest('tbody');
						var prev = avContras[i-1].oInputOrderQty.closest('tr');
						var prevClass = prev.attr('class');
						var curr = avContras[i].oInputOrderQty.closest('tr');
						var currClass = curr.attr('class');
						
						prev.attr('class', currClass);
						curr.attr('class', prevClass);
						
						//console.log('move '+ (avContras[i].nPrice / avContras[i].nQuality) +' before '+(avContras[i-1].nPrice / avContras[i-1].nQuality));
						prev.before(curr);
						
						var tmp = avContras[i-1];
						avContras[i-1] = avContras[i];
						avContras[i] = tmp;
						//tbody.insertBefore(curr, prev);
						filtered = false;
					}
				}
				iterCnt++;
				if (iterCnt > 10000){
					console.log('зацикливание при сортировке или слишком много заказов продукта "'+producTitle+'"');
					break;
				}
			}
			while (!filtered);
		}
	}
	
	function changeOrderQty(opInput, npQty){
		var svOld = opInput.attr('old_val');
		var svNew = npQty;
		if(opInput.prev().prop("tagName") == 'B'){
			opInput.prev().remove();
		}
		opInput.before('<b>'+svOld+'->'+svNew+'</b>');
		opInput.val(npQty);
	}
	
	function scrollTo(element){
		element.focus();
		//var offset = element.offset().top;
		//$('html, body').animate({ scrollTop: offset }, 500);
	}
	
	function clearLog(){
		$('#autoCorrectMessages').html('');
	}
	function addToLog(opMsg){
		$('#autoCorrectMessages').append('<br>').append(opMsg);
	}
	
	function oneToOne(){
		clearLog();
		
		for(var producTitle in data){
	  		var avContras = data[producTitle].aContras;
			var nvQtyNeed = data[producTitle].nQtyNeed;
			var nvQtyOrdered = data[producTitle].nQtyOrdered;
			
			if(avContras.length == 0 && nvQtyNeed > 0){
				scrollTo(data[producTitle].oAddContras);
				addToLog('Нужно добавить поставщика товара "'+producTitle+'"');
				alert('Нужно добавить поставщика товара "'+producTitle+'"');
				return;
			} else if(avContras.length == 1){
				var nvOrderQty = toFloat(avContras[0].oInputOrderQty.val());
				console.log('nvQtyNeed = '+nvQtyNeed);
				console.log('nvOrderQty = '+nvOrderQty);
				console.log('avContras[0].nRemain = '+avContras[0].nRemain);
				if(nvQtyNeed > avContras[0].nRemain){
					scrollTo(data[producTitle].oAddContras);
					addToLog('Нужно добавить поставщика товара "'+producTitle+'"');
					alert('Нужно добавить поставщика товара "'+producTitle+'"');
					return;
				} else if (nvQtyNeed < avContras[0].nRemain && nvOrderQty != nvQtyNeed) {
					changeOrderQty(avContras[0].oInputOrderQty, nvQtyNeed);
				}
			} else {
				for(var i = 0; i < avContras.length; i++) {
					var nvOrderQty = toFloat(avContras[i].oInputOrderQty.val());
					if(nvOrderQty > 0){
						if(nvQtyNeed == 0){
							changeOrderQty(avContras[i].oInputOrderQty, 0);
						} else {
							//console.log('nvOrderQty = '+nvOrderQty);
							//console.log('nRemain = '+avContras[i].nRemain);
							if(avContras[i].nRemain == 0){
								changeOrderQty(avContras[i].oInputOrderQty, 0);
								nvQtyOrdered = nvQtyOrdered - nvOrderQty;
							} else if(avContras[i].nRemain < nvOrderQty){
								changeOrderQty(avContras[i].oInputOrderQty, avContras[i].nRemain);
								nvQtyOrdered = nvQtyOrdered - (nvOrderQty - avContras[i].nRemain);
							}
						}
					}
				}
				if(nvQtyNeed < nvQtyOrdered){
					var nvMissing = nvQtyNeed;
					for(var i = 0; i < avContras.length; i++) {
						var nvOrderQty = toFloat(avContras[i].oInputOrderQty.val());
						if(nvOrderQty > 0){
							var part = nvQtyOrdered / nvOrderQty;
							//console.log('nvQtyOrdered = '+nvQtyOrdered);
							//console.log('nvOrderQty = '+nvOrderQty);
							//console.log('part = '+part);
							var nvNew = Math.max(Math.round(nvQtyNeed / part), 0);
							//console.log('nvNew = '+nvNew);
							changeOrderQty(avContras[i].oInputOrderQty, nvNew);
							nvMissing = nvMissing - nvNew;
						}
					}
					if(nvMissing > 0){
						console.log('nvMissing = '+nvMissing);
						for(var i = avContras.length - 1; i >= 0; i--) {
							var nvOrderQty = toFloat(avContras[i].oInputOrderQty.val());
							if(nvOrderQty > 0){
								var nvNew = Math.max(nvOrderQty - nvMissing, 0);
								changeOrderQty(avContras[i].oInputOrderQty, nvNew);
								nvMissing = nvMissing - nvNew;
								if(nvMissing <= 0){
									break;
								}
							}
						}
						if(nvMissing > 0){
							console.log('nvMissing = '+nvMissing);
							scrollTo(data[producTitle].oAddContras);
							addToLog('Нужно уменьшить поставки товара "'+producTitle+'"');
							alert('Нужно уменьшить поставки товара "'+producTitle+'"');
							return;
						}
					}
				} else if(nvQtyNeed > nvQtyOrdered){
					var nvMissing = 0;
					for(var i = 0; i < avContras.length; i++) {
						var nvOrderQty = toFloat(avContras[i].oInputOrderQty.val());
						if(nvOrderQty > 0){
							var part = nvQtyOrdered / nvOrderQty;
							var nvNew = Math.min(Math.round(nvQtyNeed / part), avContras[i].nRemain);
							changeOrderQty(avContras[i].oInputOrderQty, nvNew);
							nvMissing = nvMissing + nvNew;
						}
					}
					if(nvMissing < nvQtyNeed){
						console.log('nvMissing = '+nvMissing);
						for(var i = 0; i < avContras.length; i++) {
							var nvOrderQty = toFloat(avContras[i].oInputOrderQty.val());
							if(nvOrderQty > 0){
								var nvNew = Math.min(nvOrderQty + nvMissing, avContras[i].nRemain);
								changeOrderQty(avContras[i].oInputOrderQty, nvNew);
								nvMissing = nvMissing + nvNew;
								if(nvMissing >= nvQtyNeed){
									break;
								}
							}
						}
						if(nvMissing < nvQtyNeed){
							console.log('nvMissing = '+nvMissing);
							scrollTo(data[producTitle].oAddContras);
							addToLog('Нужно добавить поставщика товара "'+producTitle+'"');
							alert('Нужно добавить поставщика товара "'+producTitle+'"');
							return;
						}
					}
				}
			}
		}
	}
	
	function maxAvailable(){
		clearLog();
		
		for(var producTitle in data){
	  		var avContras = data[producTitle].aContras;
			var nvQtyOrdered = data[producTitle].nQtyOrdered;
			
			for(var i = 0; i < avContras.length; i++) {
				changeOrderQty(avContras[i].oInputOrderQty, avContras[i].nRemain);
			}
		}
	}
	// кнопки
	 var oneToOneBtn = $('<button>1:1</button>').click(function() {
		 oneToOne();
	});
	 var maxAvailableBtn = $('<button>max</button>').click(function() {
		 maxAvailable();
	});
	$('#mainContent form').first().before(oneToOneBtn).before(maxAvailableBtn).before('<div id="autoCorrectMessages"></div>');
	//
	collectData();
	sortTable();
  });
}

// Хак, что бы получить полноценный доступ к DOM >:]
var script = document.createElement("script");
script.textContent = '(' + run.toString() + ')();';
document.documentElement.appendChild(script);