Преглед на файлове

Merge branch 'master' into olym

caiaolin преди 8 години
родител
ревизия
48200e2573
променени са 58 файла, в които са добавени 5985 реда и са изтрити 301 реда
  1. 337 0
      lib/JSExpressionEval_src/Date.js
  2. 1050 0
      lib/JSExpressionEval_src/Evaluator.js
  3. 129 0
      lib/JSExpressionEval_src/ExpressionTester.htm
  4. 141 0
      lib/JSExpressionEval_src/JsHashMap.js
  5. 133 0
      lib/JSExpressionEval_src/License.txt
  6. 105 0
      lib/JSExpressionEval_src/Stack.js
  7. 108 0
      lib/JSExpressionEval_src/StackImpl.htm
  8. 119 0
      lib/JSExpressionEval_src/StackTester.htm
  9. 54 0
      lib/JSExpressionEval_src/TokanTester.htm
  10. 493 0
      lib/JSExpressionEval_src/Tokanizer.js
  11. 47 0
      lib/JSExpressionEval_src/VBEval.htm
  12. 123 0
      lib/JSExpressionEval_src/postfix.txt
  13. 1 1
      lib/ztree/jquery.ztree.exedit.js
  14. 1 0
      modules/main/models/bills.js
  15. 2 1
      modules/main/models/proj_counter.js
  16. 4 0
      modules/main/models/project.js
  17. 3 2
      modules/main/models/project_consts.js
  18. 1 0
      modules/main/models/ration.js
  19. 2 0
      modules/pm/controllers/copy_proj_controller.js
  20. 1 1
      modules/pm/models/project_schema.js
  21. 16 3
      modules/ration_glj/facade/glj_calculate_facade.js
  22. 633 0
      modules/ration_glj/facade/quantity_detail_facade.js
  23. 9 3
      modules/ration_glj/facade/ration_glj_facade.js
  24. 21 0
      modules/ration_glj/models/quantity_detail.js
  25. 2 2
      modules/ration_glj/models/ration_glj.js
  26. 42 2
      modules/ration_glj/models/ration_glj_temp.js
  27. 2 1
      modules/ration_repository/models/glj_repository.js
  28. 1 1
      modules/users/controllers/login_controller.js
  29. 66 0
      modules/volume_price/models/volume_price_model.js
  30. 33 0
      modules/volume_price/models/volume_price_schema.js
  31. 7 6
      public/calc_util.js
  32. 2 0
      public/web/sheet/sheet_common.js
  33. 10 1
      public/web/tree_sheet/tree_sheet_helper.js
  34. 4 4
      test/calculation/test_ration_calc.js
  35. 9 67
      test/tmp_data/bills_grid_setting.js
  36. 209 0
      test/tmp_data/test_bills_calc/bills_calc_base.js
  37. 86 0
      test/tmp_data/test_ration_calc/ration_calc_base.js
  38. 2 0
      web/building_saas/fee_rates/fee_rate.html
  39. 18 1
      web/building_saas/main/html/main.html
  40. 45 77
      web/building_saas/main/js/models/bills_calc.js
  41. 61 0
      web/building_saas/main/js/calc/calc_fees.js
  42. 330 0
      web/building_saas/main/js/calc/ration_calc.js
  43. 29 1
      web/building_saas/main/js/controllers/project_controller.js
  44. 1 1
      web/building_saas/main/js/main_ajax.js
  45. 21 1
      web/building_saas/main/js/models/bills.js
  46. 3 1
      web/building_saas/main/js/models/main_consts.js
  47. 13 1
      web/building_saas/main/js/models/project.js
  48. 450 0
      web/building_saas/main/js/models/quantity_detail.js
  49. 22 17
      web/building_saas/main/js/models/ration.js
  50. 385 0
      web/building_saas/main/js/models/ration_calc.js
  51. 52 8
      web/building_saas/main/js/models/ration_glj.js
  52. 93 0
      web/building_saas/main/js/models/volume_price.js
  53. 83 56
      web/building_saas/main/js/views/glj_view.js
  54. 130 0
      web/building_saas/main/js/views/glj_view_contextMenu.js
  55. 40 0
      web/building_saas/main/js/views/main_tree_col.js
  56. 79 18
      web/building_saas/main/js/views/project_view.js
  57. 92 17
      web/building_saas/main/js/views/ration_calc_view.js
  58. 30 7
      web/building_saas/main/js/views/sub_view.js

+ 337 - 0
lib/JSExpressionEval_src/Date.js

@@ -0,0 +1,337 @@
+// ===================================================================
+// Author: Matt Kruse <matt@mattkruse.com>
+// WWW: http://www.mattkruse.com/
+//
+// NOTICE: You may use this code for any purpose, commercial or
+// private, without any further permission from the author. You may
+// remove this notice from your final code if you wish, however it is
+// appreciated by the author if at least my web site address is kept.
+//
+// You may *NOT* re-distribute this code in any way except through its
+// use. That means, you can include it in your product, or your web
+// site, or any other form where the code is actually being used. You
+// may not put the plain javascript up on your site for download or
+// include it in your javascript libraries for download. 
+// If you wish to share this code with others, please just point them
+// to the URL instead.
+// Please DO NOT link directly to my .js files from your site. Copy
+// the files to your server and use them there. Thank you.
+// ===================================================================
+
+// HISTORY
+// ------------------------------------------------------------------
+// May 17, 2003: Fixed bug in parseDate() for dates <1970
+// March 11, 2003: Added parseDate() function
+// March 11, 2003: Added "NNN" formatting option. Doesn't match up
+//                 perfectly with SimpleDateFormat formats, but 
+//                 backwards-compatability was required.
+
+// ------------------------------------------------------------------
+// These functions use the same 'format' strings as the 
+// java.text.SimpleDateFormat class, with minor exceptions.
+// The format string consists of the following abbreviations:
+// 
+// Field        | Full Form          | Short Form
+// -------------+--------------------+-----------------------
+// Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
+// Month        | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
+//              | NNN (abbr.)        |
+// Day of Month | dd (2 digits)      | d (1 or 2 digits)
+// Day of Week  | EE (name)          | E (abbr)
+// Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
+// Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
+// Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
+// Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
+// Minute       | mm (2 digits)      | m (1 or 2 digits)
+// Second       | ss (2 digits)      | s (1 or 2 digits)
+// AM/PM        | a                  |
+//
+// NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
+// Examples:
+//  "MMM d, y" matches: January 01, 2000
+//                      Dec 1, 1900
+//                      Nov 20, 00
+//  "M/d/yy"   matches: 01/20/00
+//                      9/2/00
+//  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
+// ------------------------------------------------------------------
+
+var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
+var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
+function LZ(x) {return(x<0||x>9?"":"0")+x}
+
+// ------------------------------------------------------------------
+// isDate ( date_string, format_string )
+// Returns true if date string matches format of format string and
+// is a valid date. Else returns false.
+// It is recommended that you trim whitespace around the value before
+// passing it to this function, as whitespace is NOT ignored!
+// ------------------------------------------------------------------
+function isDate(val,format) {
+	var date=getDateFromFormat(val,format);
+	if (date==0) { return false; }
+	return true;
+	}
+
+// -------------------------------------------------------------------
+// compareDates(date1,date1format,date2,date2format)
+//   Compare two date strings to see which is greater.
+//   Returns:
+//   1 if date1 is greater than date2
+//   0 if date2 is greater than date1 of if they are the same
+//  -1 if either of the dates is in an invalid format
+// -------------------------------------------------------------------
+function compareDates(date1,dateformat1,date2,dateformat2) {
+	var d1=getDateFromFormat(date1,dateformat1);
+	var d2=getDateFromFormat(date2,dateformat2);
+	if (d1==0 || d2==0) {
+		return -1;
+		}
+	else if (d1 > d2) {
+		return 1;
+		}
+	return 0;
+	}
+
+// ------------------------------------------------------------------
+// formatDate (date_object, format)
+// Returns a date in the output format specified.
+// The format string uses the same abbreviations as in getDateFromFormat()
+// ------------------------------------------------------------------
+function formatDate(date,format) {
+	format=format+"";
+	var result="";
+	var i_format=0;
+	var c="";
+	var token="";
+	var y=date.getYear()+"";
+	var M=date.getMonth()+1;
+	var d=date.getDate();
+	var E=date.getDay();
+	var H=date.getHours();
+	var m=date.getMinutes();
+	var s=date.getSeconds();
+	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
+	// Convert real date parts into formatted versions
+	var value=new Object();
+	if (y.length < 4) {y=""+(y-0+1900);}
+	value["y"]=""+y;
+	value["yyyy"]=y;
+	value["yy"]=y.substring(2,4);
+	value["M"]=M;
+	value["MM"]=LZ(M);
+	value["MMM"]=MONTH_NAMES[M-1];
+	value["NNN"]=MONTH_NAMES[M+11];
+	value["d"]=d;
+	value["dd"]=LZ(d);
+	value["E"]=DAY_NAMES[E+7];
+	value["EE"]=DAY_NAMES[E];
+	value["H"]=H;
+	value["HH"]=LZ(H);
+	if (H==0){value["h"]=12;}
+	else if (H>12){value["h"]=H-12;}
+	else {value["h"]=H;}
+	value["hh"]=LZ(value["h"]);
+	if (H>11){value["K"]=H-12;} else {value["K"]=H;}
+	value["k"]=H+1;
+	value["KK"]=LZ(value["K"]);
+	value["kk"]=LZ(value["k"]);
+	if (H > 11) { value["a"]="PM"; }
+	else { value["a"]="AM"; }
+	value["m"]=m;
+	value["mm"]=LZ(m);
+	value["s"]=s;
+	value["ss"]=LZ(s);
+	while (i_format < format.length) {
+		c=format.charAt(i_format);
+		token="";
+		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
+			token += format.charAt(i_format++);
+			}
+		if (value[token] != null) { result=result + value[token]; }
+		else { result=result + token; }
+		}
+	return result;
+	}
+	
+// ------------------------------------------------------------------
+// Utility functions for parsing in getDateFromFormat()
+// ------------------------------------------------------------------
+function _isInteger(val) {
+	var digits="1234567890";
+	for (var i=0; i < val.length; i++) {
+		if (digits.indexOf(val.charAt(i))==-1) { return false; }
+		}
+	return true;
+	}
+function _getInt(str,i,minlength,maxlength) {
+	for (var x=maxlength; x>=minlength; x--) {
+		var token=str.substring(i,i+x);
+		if (token.length < minlength) { return null; }
+		if (_isInteger(token)) { return token; }
+		}
+	return null;
+	}
+	
+// ------------------------------------------------------------------
+// getDateFromFormat( date_string , format_string )
+//
+// This function takes a date string and a format string. It matches
+// If the date string matches the format string, it returns the 
+// getTime() of the date. If it does not match, it returns 0.
+// ------------------------------------------------------------------
+function getDateFromFormat(val,format) {
+	val=val+"";
+	format=format+"";
+	var i_val=0;
+	var i_format=0;
+	var c="";
+	var token="";
+	var token2="";
+	var x,y;
+	var now=new Date();
+	var year=now.getYear();
+	var month=now.getMonth()+1;
+	var date=1;
+	var hh=now.getHours();
+	var mm=now.getMinutes();
+	var ss=now.getSeconds();
+	var ampm="";
+	
+	while (i_format < format.length) {
+		// Get next token from format string
+		c=format.charAt(i_format);
+		token="";
+		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
+			token += format.charAt(i_format++);
+			}
+
+		// Extract contents of value based on format token
+		if (token=="yyyy" || token=="yy" || token=="y") {
+			if (token=="yyyy") { x=4;y=4; }
+			if (token=="yy")   { x=2;y=2; }
+			if (token=="y")    { x=2;y=4; }
+			year=_getInt(val,i_val,x,y);
+
+			if (year==null) { return 0; }
+			i_val += year.length;
+			if (year.length==2) {
+				if (year > 70) { year=1900+(year-0); }
+				else { year=2000+(year-0); }
+				}
+			}
+		else if (token=="MMM"||token=="NNN"){
+			month=0;
+			for (var i=0; i<MONTH_NAMES.length; i++) {
+				var month_name=MONTH_NAMES[i];
+				if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) {
+					if (token=="MMM"||(token=="NNN"&&i>11)) {
+						month=i+1;
+						if (month>12) { month -= 12; }
+						i_val += month_name.length;
+						break;
+						}
+					}
+				}
+			if ((month < 1)||(month>12)){return 0;}
+			}
+		else if (token=="EE"||token=="E"){
+			for (var i=0; i<DAY_NAMES.length; i++) {
+				var day_name=DAY_NAMES[i];
+				if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) {
+					i_val += day_name.length;
+					break;
+					}
+				}
+			}
+		else if (token=="MM"||token=="M") {
+			month=_getInt(val,i_val,token.length,2);
+			if(month==null||(month<1)||(month>12)){return 0;}
+			i_val+=month.length;}
+		else if (token=="dd"||token=="d") {
+			date=_getInt(val,i_val,token.length,2);
+			if(date==null||(date<1)||(date>31)){return 0;}
+			i_val+=date.length;}
+		else if (token=="hh"||token=="h") {
+			hh=_getInt(val,i_val,token.length,2);
+			if(hh==null||(hh<1)||(hh>12)){return 0;}
+			i_val+=hh.length;}
+		else if (token=="HH"||token=="H") {
+			hh=_getInt(val,i_val,token.length,2);
+			if(hh==null||(hh<0)||(hh>23)){return 0;}
+			i_val+=hh.length;}
+		else if (token=="KK"||token=="K") {
+			hh=_getInt(val,i_val,token.length,2);
+			if(hh==null||(hh<0)||(hh>11)){return 0;}
+			i_val+=hh.length;}
+		else if (token=="kk"||token=="k") {
+			hh=_getInt(val,i_val,token.length,2);
+			if(hh==null||(hh<1)||(hh>24)){return 0;}
+			i_val+=hh.length;hh--;}
+		else if (token=="mm"||token=="m") {
+			mm=_getInt(val,i_val,token.length,2);
+			if(mm==null||(mm<0)||(mm>59)){return 0;}
+			i_val+=mm.length;}
+		else if (token=="ss"||token=="s") {
+			ss=_getInt(val,i_val,token.length,2);
+			if(ss==null||(ss<0)||(ss>59)){return 0;}
+			i_val+=ss.length;}
+		else if (token=="a") {
+			if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
+			else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
+			else {return 0;}
+			i_val+=2;}
+		else {
+			if (val.substring(i_val,i_val+token.length)!=token) {return 0;}
+			else {i_val+=token.length;}
+			}
+		}
+	// If there are any trailing characters left in the value, it doesn't match
+	if (i_val != val.length) { return 0; }
+	// Is date valid for month?
+	if (month==2) {
+		// Check for leap year
+		if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
+			if (date > 29){ return 0; }
+			}
+		else { if (date > 28) { return 0; } }
+		}
+	if ((month==4)||(month==6)||(month==9)||(month==11)) {
+		if (date > 30) { return 0; }
+		}
+	// Correct hours value
+	if (hh<12 && ampm=="PM") { hh=hh-0+12; }
+	else if (hh>11 && ampm=="AM") { hh-=12; }
+	var newdate=new Date(year,month-1,date,hh,mm,ss);
+	return newdate.getTime();
+	}
+
+// ------------------------------------------------------------------
+// parseDate( date_string [, prefer_euro_format] )
+//
+// This function takes a date string and tries to match it to a
+// number of possible date formats to get the value. It will try to
+// match against the following international formats, in this order:
+// y-M-d   MMM d, y   MMM d,y   y-MMM-d   d-MMM-y  MMM d
+// M/d/y   M-d-y      M.d.y     MMM-d     M/d      M-d
+// d/M/y   d-M-y      d.M.y     d-MMM     d/M      d-M
+// A second argument may be passed to instruct the method to search
+// for formats like d/M/y (european format) before M/d/y (American).
+// Returns a Date object or null if no patterns match.
+// ------------------------------------------------------------------
+function parseDate(val) {
+	var preferEuro=(arguments.length==2)?arguments[1]:false;
+	generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');
+	monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
+	dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
+	var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
+	var d=null;
+	for (var i=0; i<checkList.length; i++) {
+		var l=window[checkList[i]];
+		for (var j=0; j<l.length; j++) {
+			d=getDateFromFormat(val,l[j]);
+			if (d!=0) { return new Date(d); }
+			}
+		}
+	return null;
+	}

Файловите разлики са ограничени, защото са твърде много
+ 1050 - 0
lib/JSExpressionEval_src/Evaluator.js


+ 129 - 0
lib/JSExpressionEval_src/ExpressionTester.htm

@@ -0,0 +1,129 @@
+   <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1250">
+<meta name="generator" content="PSPad editor, www.pspad.com">
+<title>Expression Converter</title>
+<style>
+body     {font-family: Verdana,Tahoma; font-size: 8px; font-weight: normal;}
+input    {font-family: Verdana,Tahoma; font-size: 8px; font-weight: normal;}
+textarea {font-family: Verdana,Tahoma; font-size: 8px; font-weight: normal;}
+button   {font-family: Verdana,Tahoma; font-size: 9px; font-weight: normal; height: 24px;}
+table, td {font-family: Verdana,Tahoma; font-size: 8px; font-weight: normal;}
+
+</style>
+<script src="Date.js" type="text/javascript"></script>
+<script src="Stack.js" type="text/javascript"></script>
+<script src="Tokanizer.js" type="text/javascript"></script>
+<script src="Evaluator.js" type="text/javascript"></script>
+<script language="vbscript">
+'5*log(1000)*-2/3+MAX(4,6 MOD 7) 
+'5*log(1000)*-2/3+6 
+'5*6.90775527898214*-2/3+6 
+'34.5387763949107*-2/3+6 
+'-69.0775527898214/3+6 
+'-23.0258509299405+6 
+'-17.0258509299405
+'-17.025850929940457
+'InputBox "6 Mod 7", "Result", (6 Mod 7)
+</script>
+<script language="javascript">
+//alert (6 % 7);
+
+var exp = new Expression("");
+
+function Convert()
+{
+    var frm = document.frmMain;
+    var arrToks;
+    var arrPFix;
+    var strExp;
+    var intCntr;
+
+    strExp = frm.txtExp.value;
+    if (strExp == null || strExp == undefined)
+    {
+        alert ("No expression is specified!");
+        return false;
+    }
+
+    exp.Expression(strExp);
+    frm.txtResult.value = exp.Parse();
+    return false;
+}
+
+function Compute()
+{
+    var frm;
+    var strExp;
+
+    frm = document.frmMain;
+    strExp = frm.txtExp.value;
+    exp.Expression(strExp);
+    frm.txtResult.value = exp.Evaluate();
+}
+
+function AddVar()
+{
+    var frm;
+
+    frm = document.frmMain;
+    exp.AddVariable(frm.txtVarName.value, frm.txtVarValue.value);
+    return true;
+}
+
+function Reset()
+{
+	exp.Reset();
+}
+</script>
+</head>
+<body>
+<form id="frmMain" name="frmMain">
+	<table>
+    	<tr>
+        	<td>
+        		<table>
+            		<tr>
+                		<td>Expression : </td>
+                		<td><input type="text" size="50" name="txtExp" id="txtExp" /></td>
+            		</tr>
+            		<tr>
+                		<td>&nbsp;</td>
+                		<td><textarea cols="58" rows="5" name="txtResult" id="txtResult"></textarea></td>
+            		</tr>
+            		<tr>
+                		<td colspan="2" align="right">
+                    		<a href="#" name="btnConvert" id="btnConvert" onClick="javascript:Convert();">PostFix</a>
+                    		&nbsp;
+                    		<a href="#" name="btnCompute" id="btnCompute" onClick="javascript:Compute();">Evaluate</a>
+                    		&nbsp;
+                    		<a href="#" name="btnReset" id="btnReset" onClick="javascript:Reset();">Reset</a>
+                		</td>
+            		</tr>
+        		</table>
+			</td>
+		</tr>
+		<tr>
+			<td>
+        		<table>
+            		<tr>
+                		<td>Variable Name : </td>
+                		<td><input type="text" size="50" name="txtVarName" id="txtVarName" /></td>
+            		</tr>
+            		<tr>
+                		<td>Variable Value : </td>
+                		<td><input type="text" size="50" name="txtVarValue" id="txtVarVal" /></td>
+            		</tr>
+            		<tr>
+                		<td colspan="2" align="right">
+                    		<a href="#" name="btnAdd" id="btnAdd" onClick="AddVar();">Add Variable</a>
+                		</td>
+            		</tr>
+        		</table>
+			</td>
+		</tr>
+	</table>
+</form>
+</body>
+</html>

+ 141 - 0
lib/JSExpressionEval_src/JsHashMap.js

@@ -0,0 +1,141 @@
+/////////////////////////////////////////////
+/////       Js Object                 //////
+///////////////////////////////////////////
+// Author: NHM TAnveer Hossain Khan (Hasan)
+// http://hasan.we4tech.com
+// mail:admin at we4tech.com
+
+// hashmap internal data object
+JsObject=function(key, value) {
+  this._key=key;
+  this._value=value;
+}
+
+// set some methods for JsObject
+JsObject.prototype.getKey=function() {
+  return this._key;
+}
+
+// get value
+JsObject.prototype.getValue=function() {
+  return this._value;
+}
+
+
+/////////////////////////////////////////////
+////        Iterator                 ///////
+///////////////////////////////////////////
+JsIterator=function(array) {
+  // set internal array
+  this._array=array;
+
+  // create inernal index counter
+  this._counter=0;
+  
+  // set _hasNext value
+  if(array.length>0)
+    this._hasNext=true;
+  else
+    this._hasNext=false;
+}
+
+// return boolean value
+JsIterator.prototype.hasNext=function() {
+  return this._hasNext;
+}
+
+// return object in next method
+JsIterator.prototype.next=function() {
+    if(this._array.length>this._counter)
+    {
+        // get object
+        var rtnObj=this._array[this._counter];
+        // increment counter value;
+        this._counter++;
+        // check is has next true of flase
+        if(this._array.length>this._counter)
+            this._hasNext=true;
+        else
+            this._hasNext=false;
+
+        // return data
+        return rtnObj;
+    }
+    else
+    {
+        this._hasNext=false;
+    }
+}
+
+// remove object
+JsIterator.prototype.remove=function() {
+    this._array.splice(this._counter,1);
+    if(this._array.length > this._counter)
+        this._hasNext=false;
+}
+
+
+/////////////////////////////////////////////
+////        HashMap Object           ///////
+///////////////////////////////////////////
+
+// create JsHashMap class object
+JsHashMap=function() {
+    // init. internal array
+    this._array=new Array();
+    // set internal counter value as 0
+    // this counter will keep track the current index
+    // of array
+    this._counter=0;
+}
+
+// create add method
+// put key and value
+JsHashMap.prototype.put=function(key, value) {
+    // add new value
+    var newJsObj=new JsObject(key, value);
+    // add in internal array
+    this._array[this._counter]=newJsObj;
+    // increment the internal index counter
+    this._counter++;
+}
+
+// retrive data based on iterator
+JsHashMap.prototype.iterator=function() {
+    // create iterator
+    var it=new JsIterator(this._array);
+    // return iterator
+    return it;
+}
+
+// retrive data based on keyword
+JsHashMap.prototype.get=function(key) {
+    // create iterator object
+    var it=this.iterator();
+  
+    // iterate untile get success
+    while(it.hasNext())
+    {
+        // fetch object
+        var getObj=it.next();
+        // check is found or not
+        if(getObj.getKey()==key)
+            return getObj.getValue();
+    }
+}
+
+// remove key and object
+JsHashMap.prototype.remove=function(key) {
+    // create iterator object
+    var it=this.iterator();
+  
+    // iterate untile get success
+    while(it.hasNext())
+    {
+        // fetch object
+        var getObj=it.next();
+        // check is found or not
+        if(getObj.getKey()==key)
+            it.remove();
+    }
+}

+ 133 - 0
lib/JSExpressionEval_src/License.txt

@@ -0,0 +1,133 @@
+KIS Public License 1.0
+Terms & Conditions Of Use
+February, 2005
+Copyright (c) 2005. Khan Information Systems. All Rights Reserved
+
+
+PLEASE READ THIS DOCUMENT CAREFULLY. BY DOWNLOADING OR USING THE CODE BASE AND
+DOCUMENTATION ACCOMPANYING THIS LICENSE (THE "License"), YOU AGREE TO THE
+FOLLOWING TERMS AND CONDITIONS OF THIS LICENSE.
+
+Section One: Your Rights
+Subject to these terms and conditions of this License, Khan Information
+Systems (KIS) (the "Original Contributor") and each subsequent contributor
+(collectively with the Original Contributor, the "Contributors") hereby grants
+you a non-exclusive, worldwide, no-charge, transferable, copyright license to
+execute, prepare derivative works of, and distribute (internally and externally),
+for commercial and noncommercial purposes, the original code contributed by
+Original Contributor and all Modifications (collectively called the "Program").
+
+This non-exclusive license (with respect to the grant from a particular
+Contributor) automatically terminates for any entity that initiates legal action
+for intellectual property infringement against such Contributor as of the
+initiation of such action.
+
+Section Two: Your License Grant
+If you make a Modification and distribute it externally you are a Contributor,
+and you must provide such Modification in source code form to the Original
+Contributor within thirty (30) days of such distribution under the terms of the
+license in Section 1 above in accordance with the instructions below.
+A "Modification" to the Program is any addition to or deletion from the contents
+of any file of the Program or of any Modifications and any new file that contains
+any part of the Program or of any Modifications.
+
+As a Contributor you represent that your contributions are your original
+creation(s) and, to the best of your knowledge, no third party has any claim
+(including but not limited to intellectual property claims) relating to your
+Modification. You represent that your contribution submission includes complete
+details of any license or other restriction associated with any part of your
+Modification (including a copy of any applicable license agreement).
+
+If, after submitting your contribution, you learn of a third party claim or other
+restriction relating to your contribution, you shall promptly modify your
+contribution submission and take all reasonable steps to inform those who may
+have received the Program containing such Modification.
+
+Section Three: Redistribution
+If you distribute the Program or any derivative work of the Program in a form to
+which the recipient can make Modifications, you must ensure that the recipient
+of the Program or derivative work of the Program accepts the terms of this
+License with respect to the Program and any Modifications included your
+distribution. In addition, in each source and data file of the Program and any
+Modification you distribute must contain the following:
+
+	"The contents of this file, as updated from time to time by Khan Information
+	Systems (KIS), are subject to the KIS Public License Version 1.0
+	(the "License"); you may not use this file except in compliance with the
+	License.
+
+	Software distributed under the License is distributed on an "AS IS" basis,
+	WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
+	the specific language governing rights and limitations under the License.
+
+	This software consists of voluntary contributions made by many individuals
+	on behalf of the Khan Information Systems.
+
+	The Original Code is ______________________________________________________.
+	The Initial Developer of the Original Code is _____________________________.
+	Portions created by _______________________________________________________
+	are Copyright (C) ______ _______________________. All Rights Reserved.
+	Contributor(s): __________________________________________________________."
+
+If you redistribute the Program or any derivative work of the Program in a form
+to which the recipient can not make Modifications, you must include the
+following in appropriate and conspicuous locations:
+
+	"Includes software, which is Copyright (c) 2005 - (insert then current year)
+	Khan Information Systems (KIS) and others. All rights reserved."
+
+Any redistribution must be made without any further restriction on the
+recipient's exercise of the rights granted herein.
+
+Section Four: Termination
+If you fail to comply with this License, your rights (but not your obligations)
+under this License shall terminate automatically unless you cure such breach
+within thirty days of becoming aware of the noncompliance. All sublicenses
+granted by you which preexist such termination and are properly granted shall
+survive such termination.
+
+Section Five: Other Terms
+Except for the copyright notices required above or as otherwise agreed in writing,
+you may not use any trademark of any of the Contributors.
+
+All transfers of the Program or any part thereof shall be made in compliance
+with U.S. Trade regulations or other restrictions of the U.S. Department of
+Commerce, as well as other similar trade or commerce restrictions which might
+apply.
+
+Any patent obtained by any party covering the Program or any part thereof must
+include a provision providing for the free, perpetual and unrestricted commercial
+and noncommercial use by any third party.
+
+If, as a consequence of a court judgment or allegation of patent infringement or
+for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent license would not
+permit royalty-free redistribution of the Program by all those who receive copies
+directly or indirectly through you, then the only way you could satisfy both it
+and this License would be to refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and the
+section as a whole is intended to apply in other circumstances.
+
+YOU AGREE THAT THE PROGRAM IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND
+(EITHER EXPRESS OR IMPLIED) INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY 
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND ANY WARRANTY OF NON
+INFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE PROGRAM, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The Original Contributor from time to time may change this License, and the
+amended license will apply to all copies of the Program downloaded after the new
+license is posted. This License provides you no implied rights or licenses to the
+intellectual property of any Contributor.
+
+								Created 01/01/2005

+ 105 - 0
lib/JSExpressionEval_src/Stack.js

@@ -0,0 +1,105 @@
+/*------------------------------------------------------------------------------
+ * NAME    : Stack.js
+ * PURPOSE : Stack dta structure using java script
+ * AUTHOR  : Prasad P. Khandekar
+ * CREATED : August 21, 2005 
+ *------------------------------------------------------------------------------
+ * Copyright (c) 2005. Khan Information Systems. All Rights Reserved
+ * The contents of this file are subject to the KIS Public License 1.0
+ * (the "License"); you may not use this file except in compliance with the 
+ * License. You should have received a copy of the KIS Public License along with 
+ * this library; if not, please ask your software vendor to provide one.
+ * 
+ * YOU AGREE THAT THE PROGRAM IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND
+ * (EITHER EXPRESS OR IMPLIED) INCLUDING, WITHOUT LIMITATION, ANY IMPLIED 
+ * WARRANTY OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND ANY 
+ * WARRANTY OF NON INFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 
+ * PROGRAM, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * See the License for the specific language governing rights and limitations 
+ * under the License.
+ *-----------------------------------------------------------------------------*/
+// Stack object constructor
+function Stack()
+{
+    this.arrStack = new Array();
+    this.intIndex = 0;
+
+    this.Size     = getSize;
+    this.IsEmpty  = isStackEmpty;
+    this.Push     = pushElement;
+    this.Pop      = popElement;
+    this.Get      = getElement;
+    this.toString = dumpStack;
+}
+
+// Converts stack contents into a comma seperated string
+function dumpStack()
+{
+    var intCntr = 0;
+    var strRet  =  "";
+    if (this.intIndex == 0) return null;
+    for (intCntr = 0; intCntr < this.intIndex; intCntr++)
+    {
+        if (strRet.length == 0)
+            strRet += this.arrStack[intCntr];
+        else
+            strRet += "," + this.arrStack[intCntr];
+    }
+    return strRet;
+}
+
+// Returns size of stack
+function getSize()
+{
+    return this.intIndex;
+}
+
+// This method tells us if this Stack object is empty
+function isStackEmpty()
+{
+	if (this.intIndex == 0)
+		return true;
+	else
+		return false;
+}
+
+// This method pushes a new element onto the top of the stack
+function pushElement(newData)
+{
+	// Assign our new element to the top
+	debugAssert ("Pushing " + newData);
+	this.arrStack[this.intIndex] = newData;
+	this.intIndex++;
+}
+
+// This method pops the top element off of the stack
+function popElement()
+{
+    var retVal;
+
+    retVal = null;
+    if (this.intIndex > 0)
+    {
+	   // Assign our new element to the top
+	   this.intIndex--;
+	   retVal = this.arrStack[this.intIndex];
+	}
+	return retVal;
+}
+
+// Gets an element at a particular offset from top of the stack
+function getElement(intPos)
+{
+    var retVal;
+
+    //alert ("Size : " + this.intIndex + ", Index " + intPos);
+    if (intPos >= 0 && intPos < this.intIndex)
+        retVal = this.arrStack[this.intIndex - intPos - 1];
+    return retVal;
+}

+ 108 - 0
lib/JSExpressionEval_src/StackImpl.htm

@@ -0,0 +1,108 @@
+<HTML>
+<HEAD>
+<TITLE>Simple Stack Object Example</TITLE>
+<SCRIPT LANGUAGE="JavaScript">
+<!-
+// This is the constructor for our stack object
+// If the "length" instance variable is 0, the stack is empty
+function oStack() {
+	this.top = null;
+	this.length = 0;
+	this.isEmpty = oStackIsEmpty;
+	this.push = oStackPush;
+	this.pop = oStackPop;
+	}
+
+// This method tells us if this oStack object is empty
+function oStackIsEmpty() {
+	if (this.length == 0)
+		return true;
+	else
+		return false;
+	}
+
+// This method pushes a new element onto the top of the stack
+function oStackPush( newData) {
+	// Create the new element
+	var newElement = new oStackElement();
+	// Add the data to it
+	newElement.setData( newData);
+	// Put the current top in our next field
+	newElement.setNext( this.top);
+	// Assign our new element to the top
+	this.top = newElement;
+	this.length++;
+	}
+	
+// This method pops the top element off of the stack
+function oStackPop() {
+	// Put old top away for safe keeping
+	var oldTop = this.top;
+	// Get the new top
+	this.top = this.top.getNext();
+	this.length-;
+	return oldTop;
+	}
+
+// This is the constructor for an element in our stack object
+// The "data" variable holds our actual data (whatever it may be)
+// and the "next" variable holds the oStackElement object that is
+// 'underneath' this oStackElement object.
+function oStackElement() {
+	this.data = 0;
+	this.next = null;
+	this.setData = oStackElementSetData;
+	this.getData = oStackElementGetData;
+	this.setNext = oStackElementSetNext;
+	this.getNext = oStackElementGetNext;
+	}
+
+// This method sets the data field of this oStackElement
+function oStackElementSetData( newData) {
+	this.data = newData;
+	}
+
+// This method returns the data field of this oStackElement
+function oStackElementGetData() {
+	return this.data;
+	}
+
+// This method sets the next field of this oStackElement
+function oStackElementSetNext( newNext) {
+	this.next = newNext;
+	}
+
+// This method returns the next field of this oStackElement
+function oStackElementGetNext() {
+	return this.next;
+	}
+
+// create a new oStack object
+var stack1 = new oStack();
+
+// Fill it with something interesting
+// In this case, the names of my cats
+stack1.push( "Puddin' Head")
+stack1.push( "Sunshine")
+stack1.push( "Shadow")
+stack1.push( "Sassy")
+stack1.push( "Toby")
+stack1.push( "Sandy")
+stack1.push( "Pixie")
+stack1.push( "Edi")
+stack1.push( "Gizmo")
+
+// Get the number of items in the stack
+var numElements = stack1.length;
+
+// Print out the contents of the stack
+for (var i = 0; i < numElements; i++)
+	document.write( stack1.length + " - " + stack1.pop().getData() 
++ "<br>");
+//->
+</SCRIPT>
+</HEAD>
+<BODY>
+</BODY>
+</HTML>
+

+ 119 - 0
lib/JSExpressionEval_src/StackTester.htm

@@ -0,0 +1,119 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1250">
+<meta name="generator" content="PSPad editor, www.pspad.com">
+<title>Stack Tester</title>
+<script language="javascript" type="text/javascript" src="Stack.js"></script>
+<style>
+body {font-family: Verdana, Tahoma; font-size: 8pt; font-weight: normal}
+button {font-family: Verdana, Tahoma; font-size: 8pt; font-weight: bold; width: 100px; height: 24px;}
+textarea {font-family: Verdana, Tahoma; font-size: 8pt; font-weight: normal; border: 1px solid;}
+input {font-family: Verdana, Tahoma; font-size: 8pt; font-weight: normal; border: 1px solid;}
+</style>
+<script language="javascript">
+var myStack = new Stack();
+
+function pushValue()
+{
+    var objFrm;
+    var strVal;
+
+    objFrm = document.frmMain;
+    if (objFrm) strVal = objFrm.txtValue.value;
+    if (strVal) myStack.Push(strVal);
+    objFrm.txtValue.value = "";
+}
+
+function popValue()
+{
+    var objFrm;
+    var strVal;
+
+    objFrm = document.frmMain;
+    if (!myStack.IsEmpty())
+    {
+        strVal = myStack.Pop();
+        objFrm.txtValue.value = strVal;
+    }
+}
+
+function getValue()
+{
+    var objFrm;
+    var strVal;
+    var intIndex = NaN;
+
+    objFrm = document.frmMain;
+    if (objFrm) intIndex = parseInt(objFrm.txtIndex.value);
+    if (!isNaN(intIndex))
+    {
+        strVal = myStack.Get(intIndex);
+        objFrm.txtValue.value = strVal;
+    }
+}
+
+function showStack()
+{
+    var objFrm;
+    var strVal, strTemp;
+    var intCntr = 0;
+
+    if (myStack.IsEmpty())
+    {
+        alert ("No values stored on stack!");
+        return;
+    }
+
+    strTemp = "";
+    objFrm = document.frmMain;
+    for (intCntr = 0; intCntr < myStack.Size(); intCntr++)
+    {
+        strVal = myStack.Get(intCntr);
+        if (intCntr == 0)
+            strTemp += "Stack[Top] = " + strVal + "\r\n";
+        else
+            strTemp += "Stack[Top + " + intCntr + "] = " + strVal + "\r\n";
+        strVal = "";
+    }
+    objFrm.txtStack.value = strTemp;
+}
+</script>
+</head>
+<body>
+    <form name="frmMain" id="frmMain" method="POST">
+        <table>
+            <tr>
+                <td valign="top">
+                    <table>
+                        <tr>
+                            <td>Value : </td>
+                            <td><input type="text" name="txtValue" id="txtValue" size="20" /></td>
+                        </tr>
+                        <tr>
+                            <td colspan="2">
+                                <button id="btnPush" name="btnPush" onClick="javascript:pushValue()">Push</button>
+                                <button id="btnPop" name="btnPop" onClick="javascript:popValue()">Pop</button>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td colspan="2">
+                                <button id="btnGet" name="btnGet" onClick="getValue()">Get</button>
+                                @&nbsp;<input type="text" size="2" maxlength="2" id="txtIndex" name="txtIndex" />
+                            </td>
+                        </tr>
+                        <tr>
+                            <td colspan="2">
+                                <button name="btnWalk" id="btnWalk" onClick="javascript:showStack()">Walk</button>
+                            </td>
+                        </tr>
+                    </table>
+                </td>
+                <td>
+                    <textarea id="txtStack" name="txtStack" cols="30" rows="10"></textarea>
+                </td>
+            </tr>
+        </table>
+    </form>
+</body>
+</html>

+ 54 - 0
lib/JSExpressionEval_src/TokanTester.htm

@@ -0,0 +1,54 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1250">
+<meta name="generator" content="PSPad editor, www.pspad.com">
+<title>Tokanizer Tester</title>
+<script language="javascript" type="text/javascript" src="Tokanizer.js"></script>
+<script language="javascript">
+<!--//
+function getTokens()
+{
+    var intCntr;
+    var form = document.frmMain;
+    var arrTokens = Tokanize(form.txtExp.value);
+    var strTemp = "";
+    var strTmp = "";
+    if (arrTokens.length > 0)
+    {
+        for (intCntr = 0; intCntr < arrTokens.length; intCntr++)
+        {
+            strTemp += arrTokens[intCntr];
+            strTmp += "Token " + intCntr + " : " + arrTokens[intCntr] + "\r\n";
+        }
+        form.txtResult.value = strTemp + "\r\n" + strTmp     ;
+    }
+    else
+        alert ("No Tokens");
+}
+//-->
+</script>
+</head>
+<body>
+    <form name="frmMain" id="frmMain" method="POST">
+        <table>
+        <tbody>
+            <tr>
+                <td>Enter an Expression : </td>
+                <td><input type="text" size="60" name="txtExp" id="txtExp"></input></td>
+            </tr>
+            <tr>
+                <td colspan="2">
+                    <textarea name="txtResult" id="txtResult" cols="60" rows="10"></textarea>
+                </td>
+            </tr>
+            <tr>
+                <td colspan="2">
+                    <button name="btnTokanize" id="btnTokanize" onClick="javascript:getTokens();">Tokanize</button>
+                </td>
+            </tr>
+        </tbody>
+        </table>
+    </form>
+</body>
+</html>

+ 493 - 0
lib/JSExpressionEval_src/Tokanizer.js

@@ -0,0 +1,493 @@
+/*------------------------------------------------------------------------------
+ * NAME    : Tokanizer.js
+ * PURPOSE : Parse a string a make an array of tokens. The following tokens are
+ *           reconized.
+ *           () 
+ *           ^ * / % + -  
+ *           ! & | TRUE FALSE
+ *           < <= > >= <> =
+ *           AVG ABS ACOS ASC ASIN ATAN CDATE CHR COS DATE FIX HEX IIF 
+ *           LCASE LEFT LOG MAX MID MIN RIGHT ROUND SIN SQRT TAN UCASE 
+ *           , ' "
+ * AUTHOR  : Prasad P. Khandekar
+ * CREATED : August 19, 2005
+ *------------------------------------------------------------------------------
+ * -3              // Negative 3 - is the first token
+ * 3+-2            // Negative 2 - previous token is an operator and next is a digit
+ * 3*-(2)          // Negative 2 - previous token is an operator and next is an opening brace
+ * 3*ABS(-2)       // Negative 2 - previous token is an opening brace and next is a digit
+ * 3+-SQR(4)       // Negative SQR - previous token is an operator and next is a alpha
+ *
+ * 3-2             // Positive 2 - previous token is a digit and next is a digit
+ * 3 - 2           // Positive 2 - previous token is a digit or space and next is a space
+ * ABS(3.4)-2      // Positive 2 - previous token is a closing brace and next is a digit
+ * ABS(3.4)- 2     // Positive 2 - previous token is a digit and next is a space
+ * ABS(3.4) - 2    // Positive 2 - previous token is a closing brace or space and next is a space
+ *------------------------------------------------------------------------------
+ * Copyright (c) 2005. Khan Information Systems. All Rights Reserved
+ * The contents of this file are subject to the KIS Public License 1.0
+ * (the "License"); you may not use this file except in compliance with the 
+ * License. You should have received a copy of the KIS Public License along with 
+ * this library; if not, please ask your software vendor to provide one.
+ * 
+ * YOU AGREE THAT THE PROGRAM IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND
+ * (EITHER EXPRESS OR IMPLIED) INCLUDING, WITHOUT LIMITATION, ANY IMPLIED 
+ * WARRANTY OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND ANY 
+ * WARRANTY OF NON INFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 
+ * PROGRAM, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * See the License for the specific language governing rights and limitations 
+ * under the License.
+ *-----------------------------------------------------------------------------*/
+ var lstAlpha    = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,uv,w,x,y,z";
+ var lstDigits   = "0,1,2,3,4,5,6,7,8,9";
+ var lstArithOps = "^,*,/,%,+,-";
+ var lstLogicOps = "!,&,|";
+ var lstCompaOps = "<,<=,>,>=,<>,=";
+ var lstFuncOps  = ["AVG","ABS","ACOS","ASC","ASIN","ATAN","CDATE","CHR","COS","DATE","FIX","HEX","IIF","LCASE","LEFT","LOG","MAX","MID","MIN","RIGHT","ROUND","SIN","SQRT","TAN","UCASE"];
+
+/*------------------------------------------------------------------------------
+ * NAME       : Tokanize
+ * PURPOSE    : Breaks the string into a token array. It also checks whether the
+ *              parenthesis, single quotes and double quotes are balanced or not.
+ * PARAMETERS : pstrExpression - The string from which token array is to be 
+ *              constructed.
+ * RETURNS    : An array of tokens.
+ * THROWS     : Unterminated string constant - Single/Double quotes are not 
+ *                                             properly terminated
+ *              Unbalanced parenthesis - Opening/closing braces are not balanced
+ *----------------------------------------------------------------------------*/
+ function Tokanize(pstrExpression)
+ {
+    var intCntr, intBraces;
+    var arrTokens;
+    var intIndex, intPos;
+    var chrChar, chrNext;
+    var strToken, prevToken;
+
+    intCntr   = 0;
+    intBraces = 0;
+    intIndex  = 0;
+    strToken  = "";
+    arrTokens = new Array();
+    pstrExpression = Trim(pstrExpression);
+    while (intCntr < pstrExpression.length)
+    {
+        prevToken = "";
+        chrChar = pstrExpression.substr(intCntr, 1);
+        if (window)
+            if (window.status)
+                window.status = "Processing " + chrChar;
+        switch (chrChar)
+        {
+            case " " :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                break;
+            case "(":
+                intBraces++;
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case ")" :
+                intBraces--;
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "^" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "*" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "/" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "%" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "&" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "|" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "," :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "-" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                chrNext = pstrExpression.substr(intCntr + 1, 1);
+                if (arrTokens.length > 0)
+                    prevToken = arrTokens[intIndex - 1];
+                if (intCntr == 0 || ((IsOperator(prevToken) ||
+                    prevToken == "(" || prevToken == ",") && 
+                    (IsDigit(chrNext) || chrNext == "(")))
+                {
+                    // Negative Number
+                    strToken += chrChar;
+                }
+                else
+                {
+                    arrTokens[intIndex] = chrChar;
+                    intIndex++;
+                    strToken = "";
+                }
+                break;
+            case "+" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                chrNext = pstrExpression.substr(intCntr + 1, 1);
+                if (arrTokens.length > 0)
+                    prevToken = arrTokens[intIndex - 1];
+                if (intCntr == 0 || ((IsOperator(prevToken) ||
+                    prevToken == "(" || prevToken == ",") && 
+                    (IsDigit(chrNext) || chrNext == "(")))
+                {
+                    // positive Number
+                    strToken += chrChar;
+                }
+                else
+                {
+                    arrTokens[intIndex] = chrChar;
+                    intIndex++;
+                    strToken = "";
+                }
+                break;
+            case "<" :
+                chrNext = pstrExpression.substr(intCntr + 1, 1);
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                if (chrNext == "=")
+                {
+                    arrTokens[intIndex] = chrChar + "=";
+                    intIndex++;
+                    intCntr++;
+                }
+                else if (chrNext == ">")
+                {
+                    arrTokens[intIndex] = chrChar + ">";
+                    intIndex++;
+                    intCntr++;
+                }
+                else
+                {
+                    arrTokens[intIndex] = chrChar;
+                    intIndex++;
+                }
+                break;
+            case ">" :
+                chrNext = pstrExpression.substr(intCntr + 1, 1);
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                if (chrNext == "=")
+                {
+                    arrTokens[intIndex] = chrChar + "=";
+                    intIndex++;
+                    intCntr++;
+                }
+                else
+                {
+                    arrTokens[intIndex] = chrChar;
+                    intIndex++;
+                }
+                break;
+            case "=" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+                arrTokens[intIndex] = chrChar;
+                intIndex++;
+                break;
+            case "'" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+
+                intPos = pstrExpression.indexOf(chrChar, intCntr + 1);
+                if (intPos < 0) 
+                    throw "Unterminated string constant";
+                else
+                {
+                    strToken += pstrExpression.substring(intCntr + 1, intPos);
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                    intCntr = intPos;
+                }
+                break;
+            case "\"" :
+                if (strToken.length > 0)
+                {
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                }
+
+                intPos = pstrExpression.indexOf(chrChar, intCntr + 1);
+                if (intPos < 0)
+                {
+                    throw "Unterminated string constant";
+                }
+                else
+                {
+                    strToken += pstrExpression.substring(intCntr + 1, intPos);
+                    arrTokens[intIndex] = strToken;
+                    intIndex++;
+                    strToken = "";
+                    intCntr = intPos;
+                }
+                break;
+            default :
+                strToken += chrChar;
+                break;
+        }
+        intCntr++;
+    }
+    if (intBraces > 0)
+        throw "Unbalanced parenthesis!";
+
+    if (strToken.length > 0)
+        arrTokens[intIndex] = strToken;
+    return arrTokens;
+}
+
+/*------------------------------------------------------------------------------
+ * NAME       : IsDigit
+ * PURPOSE    : Checks whether the character specified by chrArg is a numeric 
+ *              character.
+ * PARAMETERS : chrArg - The character to be checked
+ * RETURNS    : False - If chrArg is not a numeric character
+ *              True - Otherwise 
+ *----------------------------------------------------------------------------*/
+function IsDigit(chrArg)
+{
+    if (lstDigits.indexOf(chrArg) >= 0)
+        return true;
+    return false;
+}
+
+/*------------------------------------------------------------------------------
+ * NAME       : IsAlpha
+ * PURPOSE    : Checks whether the character specified by chrArg is a alphabet 
+ * PARAMETERS : chrArg - The character to be checked
+ * RETURNS    : False - If chrArg is not a alphabet
+ *              True - Otherwise 
+ *----------------------------------------------------------------------------*/
+function IsAlpha(chrArg)
+{
+    if (lstAlpha.indexOf(chrArg) >= 0 || 
+        lstAlpha.toUpperCase().indexOf(chrArg) >= 0)
+        return true;
+    return false;
+}
+
+/*------------------------------------------------------------------------------
+ * NAME       : IsOperator
+ * PURPOSE    : Checks whether the string specified by strArg is an operator
+ * PARAMETERS : strArg - The string to be checked
+ * RETURNS    : False - If strArg is not an operator symbol
+ *              True - Otherwise 
+ *----------------------------------------------------------------------------*/
+function IsOperator(strArg)
+{
+    if (lstArithOps.indexOf(strArg) >= 0 || lstCompaOps.indexOf(strArg) >= 0)
+        return true;
+    return false;
+}
+
+/*------------------------------------------------------------------------------
+ * NAME       : IsFunction
+ * PURPOSE    : Checks whether the string specified by strArg is a function name
+ * PARAMETERS : strArg - The string to be checked
+ * RETURNS    : False - If strArg is not a valid built-in function name.
+ *              True - Otherwise 
+ *----------------------------------------------------------------------------*/
+function IsFunction(strArg)
+{
+	var idx = 0;
+
+	strArg = strArg.toUpperCase();
+	for (idx = 0; idx < lstFuncOps.length; idx++)
+	{
+	    if (strArg == lstFuncOps[idx])
+	        return true;
+	}
+	return false;
+}
+
+/*------------------------------------------------------------------------------
+ * NAME       : Trim
+ * PURPOSE    : Removes trailing and leading spaces from a string.
+ * PARAMETERS : pstrVal - The string from which leading and trailing spaces are 
+ *              to be removed.
+ * RETURNS    : A string with leading and trailing spaces removed.
+ *----------------------------------------------------------------------------*/
+function Trim(pstrVal)
+{
+    if (pstrVal.length < 1) return "";
+
+    pstrVal = RTrim(pstrVal);
+    pstrVal = LTrim(pstrVal);
+    if (pstrVal == "")
+		return "";
+    else
+        return pstrVal;
+}
+
+/*------------------------------------------------------------------------------
+ * NAME       : RTrim
+ * PURPOSE    : Removes trailing spaces from a string.
+ * PARAMETERS : pstrValue - The string from which trailing spaces are to be removed.
+ * RETURNS    : A string with trailing spaces removed.
+ *----------------------------------------------------------------------------*/
+function RTrim(pstrValue)
+{
+    var w_space = String.fromCharCode(32);
+    var v_length = pstrValue.length;
+    var strTemp = "";
+    if(v_length < 0)
+    {
+        return"";
+    }
+    var iTemp = v_length - 1;
+
+    while(iTemp > -1)
+    {
+        if(pstrValue.charAt(iTemp) == w_space)
+        {
+        }
+        else
+        {
+            strTemp = pstrValue.substring(0, iTemp + 1);
+            break;
+        }
+        iTemp = iTemp - 1;
+    }
+    return strTemp;
+}
+
+/*------------------------------------------------------------------------------
+ * NAME       : LTrim
+ * PURPOSE    : Removes leading spaces from a string.
+ * PARAMETERS : pstrValue - The string from which leading spaces are to be removed.
+ * RETURNS    : A string with leading spaces removed.
+ *----------------------------------------------------------------------------*/
+function LTrim(pstrValue)
+{
+    var w_space = String.fromCharCode(32);
+    if(v_length < 1)
+    {
+        return "";
+    }
+    var v_length = pstrValue.length;
+    var strTemp = "";
+    var iTemp = 0;
+
+    while(iTemp < v_length)
+    {
+        if(pstrValue.charAt(iTemp) == w_space)
+        {
+        }
+        else
+        {
+            strTemp = pstrValue.substring(iTemp, v_length);
+            break;
+        }
+        iTemp = iTemp + 1;
+    }
+    return strTemp;
+}

+ 47 - 0
lib/JSExpressionEval_src/VBEval.htm

@@ -0,0 +1,47 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1250">
+<meta name="generator" content="PSPad editor, www.pspad.com">
+<style>
+body {font-family: Verdana,Tahoma; font-size: 9pt; font-weight: normal;}
+table {font-family: Verdana,Tahoma; font-size: 9pt; font-weight: normal;}
+input {font-family: Verdana,Tahoma; font-size: 10pt; font-weight: normal; border: 1px solid;}
+textarea {font-family: Verdana,Tahoma; font-size: 10pt; font-weight: normal; border: 1px solid;}
+button {font-family: Verdana,Tahoma; font-size: 8pt; font-weight: bold; width: 125px; height: 24px;}
+</style>
+<title>VB Eval</title>
+<script language="vbscript">
+Sub Calculate()
+    Dim frm
+    Dim strExp
+    Dim strResult
+
+    Set frm = document.frmMain
+    strExp = frm.txtExp.Value
+    strResult = Eval(strExp)
+    frm.txtResult.Value = strResult
+End Sub
+</script>
+</head>
+<body>
+    <form name="frmMain" id="frmMain" method="POST">
+        <table>
+            <tr>
+                <td>Expression : </td>
+                <td><input type="text" size="50" name="txtExp" id="txtExp" /></td>
+            </tr>
+            <tr>
+                <td colspan="2">
+                    <textarea name="txtResult" id="txtResult" cols="61" rows="5"></textarea>
+                </td>
+            </tr>
+            <tr>
+                <td colspan="2" align ="center">
+                    <button name="btnCalc" id="btnCalc" onClick="vbscript:Calculate()">Calculate</button>
+                </td>
+            </tr>
+        </table>
+    </form>
+</body>
+</html>

+ 123 - 0
lib/JSExpressionEval_src/postfix.txt

@@ -0,0 +1,123 @@
+(           99
+-!          10
+Functions   9
+^           7
+*/%         6
++-          5
+&|          3
+><=<><=>=   4
+
+
+10/Abs(3) + 2 > 2 & 3 >= 2
+--------------------------------------
+10      (       10
+/       (/      10
+Abs     (/Abs   10
+(       (/Abs(  10
+3       (/Abs(  10 3
+)       (/Abs   10 3
++       (+      10 3 Abs /
+2       (+      10 3 Abs / 2
+>       (>      10 3 Abs / 2 +
+2       (>      10 3 Abs / 2 + 2
+&       (&      10 3 Abs / 2 + 2 >
+3       (&      10 3 Abs / 2 + 2 > 3
+>=      (&>=    10 3 Abs / 2 + 2 > 3
+2       (&>=    10 3 Abs / 2 + 2 > 3 2
+--------------------------------------
+10 3 Abs / 2 + 2 > 3 2 >= & 
+10 3 Abs / 2 + 2 > 3 2 >= &
+
+10/Abs(3) + 2
+----------------------------------
+10      (       10
+/       (/      10
+Abs     (/Abs   10
+(       (/Abs(  10
+3       (/Abs(  10 3
+)       (/Abs   10 3
++       (+      10 3 Abs /
+2       (+      10 3 Abs / 2
+-----------------------------------
+10 3 Abs / 2 +
+10 3 Abs / 2 +
+
+
+2+3.4*56/MAX(3,4,5)
+---------------------------------
+2	(	2
++	(+	2
+3.4	(+	2 3.4
+*	(+*	2 3.4
+56	(+*	2 3.4 56
+/	(+/	2 3.4 56 *
+MAX	(+/MAX	2 3.4 56 *
+(	(+/MAX(	2 3.4 56 *
+3	(+/MAX(	2 3.4 56 * 3
+,	(+/MAX(	2 3.4 56 * 3
+4	(+/MAX(	2 3.4 56 * 3 4
+,	(+/MAX(	2 3.4 56 * 3 4
+5	(+/MAX(	2 3.4 56 * 3 4 5
+)	(+/MAX	2 3.4 56 * 3 4 5
+--------------------------------
+2 3.4 56 * 3 4 5 MAX / +
+2 3.4 56 * 3 4 5 MAX / +
+
+
+2+3.4*56^AVG(3,4,5)
+--------------------------------------
+2	(		2
++	(+		2
+3.4	(+		2 3.4
+*	(+*		2 3.4
+56	(+*		2 3.4 56
+^	(+*^		2 3.4 56
+AVG	(+*^AVG		2 3.4 56
+(	(+*^AVG(	2 3.4 56
+3	(+*^AVG(	2 3.4 56 3
+,	(+*^AVG(	2 3.4 56 3
+4	(+*^AVG(	2 3.4 56 3 4
+,	(+*^AVG(	2 3.4 56 3 4
+5	(+*^AVG(	2 3.4 56 3 4 5
+)	(+*^AVG		2 3.4 56 3 4 5
+---------------------------------------
+2 3.4 56 3 4 5 AVG ^ * +
+
+
+3+MAX(3*4,6,7)
+--------------------------------------------
+3	(		3
++	(+		3
+MAX	(+MAX		3
+(	(+MAX(		3
+3	(+MAX(		3 3
+*	(+MAX(*		3 3
+4	(+MAX(*		3 3 4
+,	(+MAX(		3 3 4 *
+6	(+MAX(		3 3 4 * 6
+,	(+MAX(		3 3 4 * 6
+7	(+MAX(		3 3 4 * 6 7
+)	(+MAX		3 3 4 * 6 7
+--------------------------------------------
+3 3 4 * 6 7 MAX
+
+
+3+MAX(3+2*4,6,7)
+------------------------------------------
+3	(		3
++	(+		3
+MAX	(+MAX		3
+(	(+MAX(		3
+3	(+MAX(		3 3
++	(+MAX(+		3 3
+2	(+MAX(+		3 3 2
+*	(+MAX(+*	3 3 2
+4	(+MAX(+*	3 3 2 4
+,	(+MAX(		3 3 2 4 * +
+6	(+MAX(		3 3 2 4 * + 6
+,	(+MAX(		3 3 2 4 * + 6
+7	(+MAX(		3 3 2 4 * + 6 7
+)	(+MAX		3 3 2 4 * + 6 7
+--------------------------------------------
+3 3 2 4 * + 6 7 MAX +
+3 3 2 4 * + 6 7 MAX +

+ 1 - 1
lib/ztree/jquery.ztree.exedit.js

@@ -241,7 +241,7 @@
 		setSonNodeLevel: function(setting, parentNode, node) {
 			if (!node) return;
 			var childKey = setting.data.key.children;
-			node.level = (parentNode)? parentNode.level + 1 : 0;
+			node.level = (parentNode) ? parentNode.level + 1 : 0;
 			if (!node[childKey]) return;
 			for (var i = 0, l = node[childKey].length; i < l; i++) {
 				if (node[childKey][i]) data.setSonNodeLevel(setting, node, node[childKey][i]);

+ 1 - 0
modules/main/models/bills.js

@@ -24,6 +24,7 @@ let billsSchema = new Schema({
     name: String,
     unit: String,
     quantity: String, // Decimal
+    isFromDetail:{type: Number,default:0},//1 true 2 false
     programID: Number,
     comments: String,
     // 调价

+ 2 - 1
modules/main/models/proj_counter.js

@@ -11,7 +11,8 @@ class projCounter extends baseModel {
         let projCounterSchema = new Schema({
             projectID: Number,
             bills: Number,
-            ration: Number
+            ration: Number,
+            volume_price: Number
         });
         let projCounterModel = db.model(name, projCounterSchema);
         super(projCounterModel);

+ 4 - 0
modules/main/models/project.js

@@ -7,7 +7,9 @@ var GLJData = require('./glj');
 var ration_glj_data = require('../../ration_glj/facade/ration_glj_facade');
 var ration_coe_data = require('../../ration_glj/facade/ration_coe_facade');
 var ration_ass_data = require('../../ration_glj/facade/ration_ass_facade');
+var quantity_detail_data = require('../../ration_glj/facade/quantity_detail_facade');
 let projCounter = require('./proj_counter');
+let volumePriceData = require('../../volume_price/models/volume_price_model');
 var consts = require('./project_consts');
 var projectConsts = consts.projectConst;
 var async = require("async");
@@ -20,7 +22,9 @@ moduleMap[projectConsts.RATION] = rationData;
 moduleMap[projectConsts.RATION_GLJ] = ration_glj_data;
 moduleMap[projectConsts.RATION_COE] = ration_coe_data;
 moduleMap[projectConsts.RATION_ASS] = ration_ass_data;
+moduleMap[projectConsts.QUANTITY_DETAIL] = quantity_detail_data;
 moduleMap[projCounter.collectionName] = projCounter;
+moduleMap[volumePriceData.collectionName] = volumePriceData;
 
 
 var Project = function (){};

+ 3 - 2
modules/main/models/project_consts.js

@@ -8,11 +8,12 @@ var projectConst = {
     RATION_GLJ:'ration_glj',
     RATION_COE:'ration_coe',
     RATION_ASS:'ration_ass',
+    QUANTITY_DETAIL:'quantity_detail',
     PROJECTGLJ: 'projectGLJ',
     GLJLIST: 'GLJList',
     UNITPRICEFILE: 'unitPriceFile',
-    PROPERTIES: 'properties'
-
+    PROPERTIES: 'properties',
+    VOLUMEPRICE: 'volume_price'
 };
 
 var commonConst = {

+ 1 - 0
modules/main/models/ration.js

@@ -37,6 +37,7 @@ let rationSchema = new Schema({
     caption: String,
     unit: String,
     quantity: String, // Decimal
+    isFromDetail:{type: Number,default:0},  //1 true 2 false
     programID: Number,
     adjustState: String,
     content: String,

+ 2 - 0
modules/pm/controllers/copy_proj_controller.js

@@ -5,6 +5,7 @@
 let billsData = require('../../main/models/bills');
 let rationData = require('../../main/models/ration');
 let projCounter = require('../../main/models/proj_counter');
+let volumePriceData = require('../../volume_price/models/volume_price_model');
 let async = require('async');
 
 module.exports = {
@@ -29,6 +30,7 @@ module.exports = {
         fun.push(copyData(billsData));
         fun.push(copyData(rationData));
         fun.push(copyData(projCounter));
+        fun.push(copyData(volumePriceData));
         async.parallel(fun, (err) => callback(err));
     }
 };

+ 1 - 1
modules/pm/models/project_schema.js

@@ -20,7 +20,7 @@ let ProjectSchema = new Schema({
     'fullFolder': Array
 });
 
-module.exports = mongoose.model(collectionName, ProjectSchema)
+module.exports = mongoose.model(collectionName, ProjectSchema);
 
 
 

+ 16 - 3
modules/ration_glj/facade/glj_calculate_facade.js

@@ -9,9 +9,12 @@ let ration_glj = mongoose.model('ration_glj');
 let ration = mongoose.model('ration');
 let ration_coe = mongoose.model('ration_coe');
 let std_ration_lib_ration_items = mongoose.model('std_ration_lib_ration_items');
+let glj_type_util = require('../../../public/cache/std_glj_type_util');
+
 
 module.exports={
-    calculateQuantity:calculateQuantity
+    calculateQuantity:calculateQuantity,
+    getGLJTypeByID:getGLJTypeByID
 }
 //辅助定额调整、替换工料机、标准附注条件调整、添加工料机、自定义消耗量(包括删除工料机)、自定义乘系数、市场单价调整
 let stateSeq ={
@@ -197,7 +200,7 @@ function everyCoe(quantity,coe,glj) {
                 coeQuantity = getCalculateResult(coeQuantity,coe.coes[i]);
             } else if(coe.coes[i].coeType=='定额'){
                 coeQuantity = getCalculateResult(coeQuantity,coe.coes[i]);
-            }else if(coe.coes[i].coeType==glj.gljDistType){
+            }else if(coe.coes[i].coeType==getGLJTypeByID(glj.type)){
                 coeQuantity = getCalculateResult(coeQuantity,coe.coes[i]);
             }
         }
@@ -212,7 +215,7 @@ function calculateQuantityByCustomerCoes(quantify,coe,glj) {
         return getCalculateResult(quantify, coe.coes[0])
     }else {
         for(let i=1;i<coe.coes.length;i++){
-            if(coe.coes[i].coeType.search(glj.gljDistType)!=-1){
+            if(coe.coes[i].coeType.search(getGLJTypeByID(glj.type))!=-1){
                 return getCalculateResult(quantify,coe.coes[i])
             }
         }
@@ -240,5 +243,15 @@ function getCalculateResult(quantify,c) {
             break;
     }
     return q;
+}
 
+function getGLJTypeByID(id) {
+    let glj_type_object = glj_type_util.getStdGljTypeCacheObj();
+    let topTypeId = glj_type_object.getTopParentIdByItemId(id);
+    let type = glj_type_object.getItemById(topTypeId);
+    if(type!=undefined){
+        return type.fullName;
+    }else {
+        return '';
+    }
 }

+ 633 - 0
modules/ration_glj/facade/quantity_detail_facade.js

@@ -0,0 +1,633 @@
+/**
+ * Created by chen on 2017/7/20.
+ */
+
+/**
+ * Created by chen on 2017/7/10.
+ */
+let mongoose = require('mongoose');
+let consts = require('../../main/models/project_consts');
+let commonConsts = consts.commonConst;
+let _=require("lodash");
+let async_n = require("async");
+let quantity_detail_model = mongoose.model('quantity_detail');
+const uuidV1 = require('uuid/v1');
+let ration_model = mongoose.model('ration');
+let bill_model=mongoose.model("bills");
+
+
+module.exports={
+    save:save,
+    getData:getData,
+    deleteByRation:deleteByRation,
+    deleteByBill:deleteByBill
+};
+
+let operationMap={
+    'ut_create':create_quantity_detail,
+    'ut_update':update_quantity_detail,
+    'ut_delete':delete_quantity_detail
+};
+
+let updateFunctionMap = {
+    'normalUpdate':normalUpdate,
+    'updateQuantityRegex':updateQuantityRegex,
+    'insertRecode':insertRecode
+}
+
+function create_quantity_detail(user_id,datas) {
+    return function (callback) {
+        let doc = datas.doc;
+        doc.ID = uuidV1();
+        if(doc.hasOwnProperty('regex')){
+            insertRecodeWithReg(doc).then(function (resultObject) {
+                if(resultObject.err){
+                    callback(null,{
+                        moduleName:consts.projectConst.QUANTITY_DETAIL,
+                        err:{
+                            message:result.err.message
+                        }
+                    });
+                } else {
+                    callback(null,resultObject.return_list);
+                }
+            })
+        }else {
+            createNormalRecode(doc,callback)
+        }
+
+    }
+}
+
+function insertRecode(user_id,datas) {
+    return function (callback) {
+        let doc = datas.doc;
+        doc.ID = uuidV1();
+        doInsertRecode(doc).then(function (result) {
+            //console.log(result);
+            if(result.err){
+                callback(result.err,'')
+            }else {
+                callback(null,result.returndata)
+            }
+
+        })
+    }
+}
+
+async function doInsertRecode(doc) {
+    let result={
+        err:null
+    }
+    try{
+        let query = {
+            projectID:doc.projectID,
+            seq : { $gte: doc.seq }
+        }
+        if(doc.hasOwnProperty('rationID')){
+            query.rationID = doc.rationID;
+        }else {
+            query.billID = doc.billID;
+        }
+        let quantity_detail_List = await getDatailList(doc,{data:{}});
+        let update_task = getUpdateReferenceTask(quantity_detail_List,doc.seq,1);
+        await quantity_detail_model.update(query,{$inc:{seq:1}},{multi: true});
+        if(update_task.length>0){
+            await quantity_detail_model.bulkWrite(generateUpdateTaks(update_task));
+        }
+        let newrecode = await quantity_detail_model.create(doc);
+        let returndata ={
+            updateTpye:commonConsts.UT_CREATE,
+            moduleName:consts.projectConst.QUANTITY_DETAIL,
+            data:{
+                doc:newrecode,
+                resort:true,
+                update_task:update_task
+            }
+        };
+        result.returndata =returndata
+        return result;
+    }catch (error){
+        console.log(error)
+        result.err;
+        return result
+    }
+}
+
+function getUpdateReferenceTask(quantity_detail_List,seq,re) {
+    let update_task=[];
+    for(let q of quantity_detail_List){
+        let need_update =false;
+        let newReg = q.regex;
+        let newReferenceIndex;
+        if(q.referenceIndexs.length>0){
+            for (let i =0;i< q.referenceIndexs.length;i++){
+                if(q.referenceIndexs[i]>seq){
+                    newReg = replaceAll('C'+q.referenceIndexs[i],'C'+(q.referenceIndexs[i]+re),newReg);
+                    newReg = replaceAll('c'+q.referenceIndexs[i],'c'+(q.referenceIndexs[i]+re),newReg);
+                    q.referenceIndexs[i] +=re;
+                    need_update = true;
+                }
+            }
+        }
+        if(need_update){
+            newReferenceIndex = q.referenceIndexs;
+            let task ={
+                query:{
+                    ID:q.ID,
+                    projectID:q.projectID
+                },
+                doc:{
+                    regex:newReg,
+                    referenceIndexs:newReferenceIndex
+                }
+            }
+            update_task.push(task);
+        }
+    }
+    return update_task;
+}
+
+
+function createNormalRecode(doc,callback) {
+    quantity_detail_model.create(doc,(err,result)=>{
+        if(err){
+            callback(err,null);
+        }else {
+            console.log(result);
+            let returndata ={
+                updateTpye:commonConsts.UT_CREATE,
+                moduleName:consts.projectConst.QUANTITY_DETAIL,
+                data:result
+            }
+            callback(null,returndata)
+        }
+    });
+}
+
+
+async function insertRecodeWithReg (doc) {
+    let returnObjec={
+        err:null,
+        return_list:[]
+    }
+    try {
+        let returnData={
+            moduleName:'',
+            data:{
+                updateTpye:commonConsts.UT_UPDATE,
+            }
+        }
+        let regex = doc.regex.toUpperCase();
+        let referenceIndexs = doc.referenceIndexs;
+        let detailList = await getDatailList(doc,returnData);
+        doc.result =getEvalResult(referenceIndexs,detailList,regex);
+        let refreshQuantity =false;
+        if(doc.refreshQuantity=true){
+            refreshQuantity = true;
+        }
+        delete doc.refreshQuantity;
+        let newRecode = await quantity_detail_model.create(doc) ;
+        detailList.push(newRecode);
+        if(refreshQuantity==true){
+            returnData.data.quantity = await summateResuts(doc,detailList);
+            returnData.data.quantityRefresh = true;
+            returnObjec.return_list.push(returnData);
+        }
+        returnObjec.return_list.push({
+            updateTpye:commonConsts.UT_CREATE,
+            moduleName:consts.projectConst.QUANTITY_DETAIL,
+            data:newRecode
+        });
+        return returnObjec;
+    }catch (error){
+        returnObjec.err = new Error('输入的表达式有误,请重新输入!');
+        console.log(error)
+        return returnObjec;
+    }
+
+
+}
+
+
+
+function normalUpdate(user_id,datas) {
+    return function(callback) {
+        if(datas.doc.hasOwnProperty('isSummation')){
+            doIsSummationUpdate(datas.query,datas.doc).then(function (sresult) {
+                let returndata ={
+                    moduleName:consts.projectConst.QUANTITY_DETAIL,
+                    data:{
+                        updateTpye:commonConsts.UT_UPDATE,
+                        query:datas.query,
+                        doc:datas.doc
+                    }
+                }
+                let retrunArr = [];
+                retrunArr.push(returndata);
+                if(sresult){
+                    retrunArr.push(sresult);
+                }
+                callback(null,retrunArr);
+            })
+        }else {
+            updateRecored(datas.query,datas.doc,callback);
+        }
+    }
+}
+function updateQuantityRegex(user_id,datas) {
+    return function(callback){
+        console.log(datas);
+        doRegexUpdate(datas).then(function (result) {
+            if(result.err){
+                callback(null,{
+                    moduleName:consts.projectConst.QUANTITY_DETAIL,
+                    err:{
+                        message:result.err.message
+                    }
+                });
+            }else {
+                callback(null,result.rList);
+            }
+
+        })
+    }
+}
+
+async function doIsSummationUpdate(query,doc) {
+    try {
+        let returnData={
+            moduleName:'',
+            data:{
+                updateTpye:commonConsts.UT_UPDATE,
+            }
+        }
+        let  refreshQuantity=false;
+        if(query.refreshQuantity==true){
+            refreshQuantity=true;
+        }
+        delete query.refreshQuantity;
+        let updateDoc = await quantity_detail_model.update(query,doc);
+        let detailList = await getDatailList(query,returnData);
+        if(refreshQuantity==true){
+            let quantity = await summateResuts(query,detailList);
+            returnData.data.quantity = quantity;
+            returnData.data.quantityRefresh = true;
+            return returnData;
+        }else {
+            return null;
+        }
+    }catch (error){
+        console.log(error)
+    }
+}
+
+async function getDatailList(query,resultObject) {
+    let detailList = [];
+    if(query.hasOwnProperty('rationID')){
+        detailList = await quantity_detail_model.find({'projectID':query.projectID,'rationID':query.rationID}).sort('seq').exec();
+        resultObject.moduleName = consts.projectConst.RATION;
+        resultObject.data.rationID=query.rationID;
+
+    }else {
+        detailList = await quantity_detail_model.find({'projectID':query.projectID,'billID':query.billID}).sort('seq').exec();
+        resultObject.moduleName = consts.projectConst.BILLS;
+        resultObject.data.billID=query.billID;
+    }
+    return detailList;
+}
+
+
+async function doRegexUpdate(datas) {
+    let resultObjec ={
+        err:null,
+        rList:[]
+    }
+    try {
+        let detailList = [];
+        let quantityResult ={
+            moduleName:'',
+            data:{
+                updateTpye:commonConsts.UT_UPDATE,
+                quantityRefresh:true
+            }
+        }
+        if(datas.query.hasOwnProperty('rationID')){
+            detailList = await quantity_detail_model.find({'projectID':datas.query.projectID,'rationID':datas.query.rationID}).sort('seq').exec();
+            quantityResult.moduleName = consts.projectConst.RATION;
+            quantityResult.data.rationID = datas.query.rationID;
+        }else {
+            detailList = await quantity_detail_model.find({'projectID':datas.query.projectID,'billID':datas.query.billID}).sort('seq').exec();
+            quantityResult.data.billID = datas.query.billID;
+            quantityResult.moduleName = consts.projectConst.BILLS;
+        }
+        let regex;
+        let result;
+        if(datas.doc.regex==null){
+            result=0
+            datas.doc.referenceIndexs=[];
+        }else {
+            regex = datas.doc.regex.toUpperCase();
+            let referenceIndexs = datas.doc.referenceIndexs;
+            result =getEvalResult(referenceIndexs,detailList,regex);
+        }
+        detailList[datas.query.index].result =result;
+        detailList[datas.query.index].regex=datas.doc.regex;
+        detailList[datas.query.index].referenceIndexs =datas.doc.referenceIndexs;
+        let updateTasks =[];
+        datas.doc.result=result;
+        updateTasks.push({
+            query:{
+                ID:datas.query.ID,
+                projectID:datas.query.projectID
+            },
+            doc:datas.doc
+        })
+        updateReferenceRecode(datas.query.index+1,detailList,updateTasks);
+        let updateEdit = await quantity_detail_model.bulkWrite(generateUpdateTaks(updateTasks));
+        resultObjec.rList.push(gernerateResultList(updateTasks));
+        if(datas.query.refreshQuantity==true){
+            quantityResult.data.quantity = await summateResuts(datas.query,detailList);
+            resultObjec.rList.push(quantityResult);
+        }
+        return resultObjec;
+    }catch (error){
+        console.log(error);
+        resultObjec.err=error;
+        return resultObjec
+
+    }
+}
+
+function gernerateResultList(updateTasks) {
+    let returndata ={
+        moduleName:consts.projectConst.QUANTITY_DETAIL,
+        data:{
+            updateTpye:commonConsts.UT_UPDATE,
+            refreshList:updateTasks
+        }
+    }
+    return returndata;
+
+}
+
+
+
+function updateReferenceRecode(index,detailList,updateTasks) {
+    for(let d of detailList){
+        if(_.includes(d.referenceIndexs,index)){
+            let tResult = getEvalResult(d.referenceIndexs,detailList,d.regex);
+            let t = {
+                query:{
+                    ID:d.ID,
+                    projectID:d.projectID
+                },
+                doc:{
+                    result:tResult
+                }
+            };
+            d.result = tResult;
+            updateTasks.push(t);
+            updateReferenceRecode(d.seq+1,detailList,updateTasks);
+        }
+    }
+}
+
+
+async function summateResuts (query,detailList) {
+    let quantity = 0;
+    for(let d of detailList){
+        if(d.isSummation==1){
+            let result = d.result==null||d.result==undefined?0:d.result
+            quantity+=result;
+        }
+    }
+    quantity = quantity.toFixed(4);
+    if(query.hasOwnProperty('rationID')){
+        await ration_model.update({'ID':query.rationID,'projectID':query.projectID,deleteInfo: null},{quantity:quantity,isFromDetail:1});
+    }else {
+        await bill_model.update({'ID':query.billID,'projectID':query.projectID,deleteInfo: null},{quantity:quantity,isFromDetail:1});
+    }
+    return quantity
+}
+
+
+
+function  getEvalResult(referenceIndexs,detailList,regex) {
+    try {
+        for(let i of referenceIndexs){
+            regex = replaceReference(i,detailList,regex)
+        }
+        console.log('replace all C reference -----'+regex);
+        regex =replaceSqr(regex);
+        console.log('replace all sqar reference -----'+regex);
+        return _.round(eval(regex), 4);
+    }catch (error){
+        throw new Error('输入的表达式有误,请重新输入!');
+    }
+
+}
+
+function  generateUpdateTaks(updateTasks) {
+    var tasks=[];
+    for(let u of updateTasks){
+        let t ={
+            updateOne:{
+                filter:u.query,
+                update: u.doc
+            }
+        }
+        tasks.push(t);
+    }
+    return tasks;
+}
+
+function replaceReference(index,detailList,str) {
+    str=str.toUpperCase();
+    let rstr= detailList[index-1].regex==null?'0':'('+detailList[index-1].regex+')';
+    str=replaceAll('C'+index,rstr,str);
+    if(detailList[index-1].referenceIndexs.length>0){
+        for (let i of detailList[index-1].referenceIndexs){
+            str =replaceReference(i,detailList,str);
+        }
+    }
+    return str;
+}
+
+
+function replaceAll (FindText, RepText,str) {
+    let regExp = new RegExp(FindText, "g");
+    return str.replace(regExp, RepText);
+}
+
+
+function replaceSqr(text) {
+    var squarRegex = /\([^\^]+\)\^\d+/g;
+    var sqararr = text.match(squarRegex);
+
+    var squarRegex2 = /C[0-9]+\^\d+|[0-9]+([.]{1}[0-9]+){0,1}\^\d+/g; //匹配没有括号的
+    var sqararr2=text.match(squarRegex2);
+    if(sqararr){
+        text=converSqrByArr(sqararr,text);
+    }
+    if(sqararr2){
+        text=converSqrByArr(sqararr2,text);
+    }
+    return text;
+}
+
+function converSqrByArr (sqararr,text) {
+    var temp = text;
+    sqararr.forEach(function (item) {
+        var arr = item.split('\^');
+        var y = parseInt(arr[1]);
+        var x_arr = [];
+        for (var i = 0; i < y; i++) {
+            x_arr.push(arr[0]);
+        }
+        var temStr = x_arr.join('*');
+        temp = temp.replace(item, temStr);
+    });
+    return temp;
+};
+
+
+function updateRecored(query,doc,callback) {
+    quantity_detail_model.update(query,doc,(err,result)=>{
+        if(err){
+            callback(err,'');
+        }else {
+            let returndata ={
+                moduleName:consts.projectConst.QUANTITY_DETAIL,
+                data:{
+                    updateTpye:commonConsts.UT_UPDATE,
+                    query:query,
+                    doc:doc
+                }
+            }
+            callback(null,returndata);
+        }
+    })
+}
+
+
+function update_quantity_detail(user_id,datas) {
+    if(datas.updateFunction){
+        return updateFunctionMap[datas.updateFunction](user_id,datas);
+    }else {
+        console.log(datas);
+        return normalUpdate(user_id,datas);
+    }
+}
+
+function delete_quantity_detail(user_id,datas) {
+    return function (callback) {
+        doQuantityDelete(datas.doc).then(function (result) {
+            console.log(result);
+            if(result.err){
+                callback(result.err,'')
+            }else {
+                callback(null,result.returndata)
+            }
+        });
+    }
+}
+
+async function doQuantityDelete(doc) {
+   console.log(doc) ;
+    let result={
+        err:null
+    }
+    try{
+        let query = {
+            projectID:doc.projectID,
+            seq : { $gt: doc.seq }
+        }
+        if(doc.hasOwnProperty('rationID')){
+            query.rationID = doc.rationID;
+        }else {
+            query.billID = doc.billID;
+        }
+        let quantity_detail_List = await getDatailList(doc,{data:{}});
+        let update_task = getUpdateReferenceTask(quantity_detail_List,doc.seq,-1);
+        await quantity_detail_model.update(query,{$inc:{seq:-1}},{multi: true});
+        if(update_task.length>0){
+            await quantity_detail_model.bulkWrite(generateUpdateTaks(update_task));
+        }
+        await quantity_detail_model.deleteOne({ID:doc.ID,projectID:doc.projectID});
+        let returndata ={
+            moduleName:consts.projectConst.QUANTITY_DETAIL,
+            data:{
+                doc:doc,
+                resort:true,
+                update_task:update_task,
+                updateTpye:commonConsts.UT_DELETE
+            }
+        };
+        result.returndata =returndata
+        return result;
+    }catch (error){
+        console.log(error)
+        result.err;
+        return result
+    }
+
+
+}
+
+
+function getData(projectID, callback) {
+    quantity_detail_model.find({'projectID':projectID}).sort('seq').exec((err,datas)=>{
+        if(err){
+            callback(1, '', null);
+        }else {
+            callback(0, consts.projectConst.QUANTITY_DETAIL, datas);
+        }
+    })
+}
+
+function save (user_id, datas, callback) {
+    let operations=[];
+    if(_.isArray(datas)){
+        for(let i=0;i<datas.length;i++){
+            operations.push(operationMap[datas[i].updateType](user_id,datas[i]));
+        }
+    }else {
+        operations.push(operationMap[datas.updateType](user_id,datas));
+    }
+    async_n.parallel(operations,function (err,results) {
+        if(err){
+            callback(err,'');
+        }else {
+            if(results.length==1){
+                callback(null,results[0])
+            }else {
+                callback(null,results)
+            }
+        }
+    })
+}
+
+function deleteByRation(data) {
+    return function (callback) {
+        quantity_detail_model.deleteMany({projectID: data.projectID, rationID: data.ID},(err,result)=>{
+            commonCallback(callback,result,err);
+        });
+    }
+}
+function deleteByBill(data) {
+    return function (callback) {
+        console.log({projectID: data.projectID, billID: data.ID});
+        quantity_detail_model.deleteMany({projectID: data.projectID, billID: data.ID},(err,result)=>{
+            commonCallback(callback,result,err);
+        });
+    }
+}
+
+function commonCallback(callback,result,err) {
+    if(err){
+        callback(err,'');
+    }else {
+        callback(null,result);
+    }
+}

+ 9 - 3
modules/ration_glj/facade/ration_glj_facade.js

@@ -3,7 +3,7 @@
  */
 
 let mongoose = require('mongoose');
-const uuidV1 = require('uuid/v1')
+const uuidV1 = require('uuid/v1');
 let consts = require('../../main/models/project_consts')
 let commonConsts = consts.commonConst;
 let _=require("lodash");
@@ -17,6 +17,8 @@ let ration_coe_facade = require('./ration_coe_facade');
 let ration_coe = mongoose.model('ration_coe');
 let std_ration_lib_ration_items = mongoose.model('std_ration_lib_ration_items');
 let glj_calculate_facade = require('./glj_calculate_facade');
+let quantity_detail_facade = require('./quantity_detail_facade');
+
 
 
 module.exports={
@@ -92,7 +94,7 @@ function get_lib_glj_info(ration_glj) {
                 ration_glj.unit = glj.unit;
                 ration_glj.specs = glj.specs;
                 ration_glj.basePrice = glj.basePrice;
-                ration_glj.gljDistType = glj.gljDistType;
+                ration_glj.shortName = glj.shortName;
                 ration_glj.type = glj.gljType;
                 getInfoFromProjectGLJ(ration_glj).then(function (result) {
                     if(result){
@@ -121,6 +123,7 @@ async function getInfoFromProjectGLJ(ration_glj) {
          base_price: ration_glj.basePrice,
          market_price: ration_glj.basePrice
      };
+
      try {
          let projectGljModel = new GLJListModel();
          let result = await projectGljModel.addList(data);
@@ -329,7 +332,8 @@ function deleteByRation(datas,callback) {
     let data = datas.updateData;
     let tasks=[];
     tasks.push(deleteGLJList(data));
-    tasks.push(ration_coe_facade.delete_ration_coe(data))
+    tasks.push(ration_coe_facade.delete_ration_coe(data));
+    tasks.push(quantity_detail_facade.deleteByRation(data));
     async_n.parallel(tasks,function (err,result) {
         commonCallback(callback,result,err)
     })
@@ -429,7 +433,9 @@ function deleteByMultiRations(datas) {
         for(let i=0;i<rations.length;i++){
             delete_tasks.push(deleteOne(rations[i]._doc));
             delete_tasks.push(ration_coe_facade.delete_ration_coe(rations[i]._doc));
+            delete_tasks.push(quantity_detail_facade.deleteByRation(rations[i]._doc));
         }
+        delete_tasks.push(quantity_detail_facade.deleteByBill(datas.updateData));
         async_n.parallel(delete_tasks,(err,results)=>{
             if (err){
                 deleteCallBack(err,'')

+ 21 - 0
modules/ration_glj/models/quantity_detail.js

@@ -0,0 +1,21 @@
+/**
+ * Created by chen on 2017/7/20.
+ */
+
+var mongoose = require('mongoose'),
+    Schema = mongoose.Schema;
+
+var quantity_detail = new Schema({
+    ID:String,
+    projectID: Number,
+    rationID:Number,
+    billID:Number,
+    name:String,
+    regex:String,
+    result:Number,
+    isSummation: {type: Number,default:1},//0:false 1:true
+    referenceIndexs:[Number],
+    seq:Number
+},{versionKey:false});
+
+mongoose.model('quantity_detail', quantity_detail);

+ 2 - 2
modules/ration_glj/models/ration_glj.js

@@ -5,7 +5,7 @@ var mongoose = require('mongoose'),
     Schema = mongoose.Schema;
 
 var ration_glj = new Schema({
-    ID:{ type:String,unique:true},
+    ID:String,
     GLJID:Number,
     projectID: Number,
     rationID:Number,
@@ -15,7 +15,7 @@ var ration_glj = new Schema({
     specs:String,
     unit:String,
     basePrice:Number,
-    gljDistType:String,
+    shortName:String,
     type:Number,
     quantity:Number,
     customQuantity:Number,

+ 42 - 2
modules/ration_glj/models/ration_glj_temp.js

@@ -32,7 +32,8 @@ var gljSchema =new Schema({
     unit: String,
     basePrice: Number,
     gljType: Number, //这个是UI显示上的详细分类,对应gljTypeSchema
-    gljDistType: String  //人工,材料,机械
+    shortName: String,  //人工,材料,机械
+    gljClass:Number
 },{versionKey:false});
 
 mongoose.model("std_ration_lib_glj_list",gljSchema,"std_ration_lib_glj_list");
@@ -49,6 +50,7 @@ let rationSchema = new Schema({
     caption: String,
     unit: String,
     quantity: String, // Decimal
+    isFromDetail:{type: Number,default:0},//1 true 2 false
     programID: Number,
     adjustState: String,
     content: String,
@@ -115,4 +117,42 @@ var rationItemSchema = mongoose.Schema({
     rationCoeList: Array,
     rationAssList: [rationAssItemSchema]
 });
-mongoose.model("std_ration_lib_ration_items",rationItemSchema, "std_ration_lib_ration_items")
+mongoose.model("std_ration_lib_ration_items",rationItemSchema, "std_ration_lib_ration_items");
+
+let billsSchema = new Schema({
+    ID: Number,
+    ParentID: Number,
+    NextSiblingID: Number,
+    projectID: Number,
+    serialNo: Number,
+    chapterID: Number,
+    code: String,
+    fullCode: String,
+    name: String,
+    unit: String,
+    quantity: String, // Decimal
+    isFromDetail:{type: Number,default:0},//1 true 2 false
+    programID: Number,
+    comments: String,
+    // 调价
+    xs_Labour: String, // Decimal
+    xs_Material: String, // Decimal
+    xs_Machine: String, // Decimal
+    xs_FeeRate: String, // Decimal
+    xs_LabourPrice: String, // Decimal
+    xs_MaterialPrice: String, // Decimal
+    xs_MachinePrice: String, // Decimal
+    isTender_Labour: Boolean,
+    isTender_Material: Boolean,
+    isTender_Machine: Boolean,
+    tenderTargetPrice: String, // Decimal
+    tenderTargetUnitPrice: String, // Decimal
+    tenderTargetUnitPrice: String, // Decimal
+    // 费用字段
+    fees: [subSchema.feesSchema],
+    // 标记字段
+    flags: [subSchema.flagsSchema],
+    deleteInfo: deleteSchema
+});
+
+mongoose.model("bills", billsSchema);

+ 2 - 1
modules/ration_repository/models/glj_repository.js

@@ -28,7 +28,8 @@ var gljSchema = mongoose.Schema({
     unit: String,
     basePrice: Number,
     gljType: Number, //这个是UI显示上的详细分类,对应gljTypeSchema
-    gljDistType: String  //人工,材料,机械
+    shortName: String,  //人工,材料,机械
+    gljClass:Number
 });
 var gljTypeModel = db.model("std_ration_lib_glj_type",gljTypeSchema, "std_ration_lib_glj_type");
 var gljItemModel = mongoose.model("std_ration_lib_glj_list");

+ 1 - 1
modules/users/controllers/login_controller.js

@@ -7,6 +7,7 @@
  */
 import UserModel from "../models/user_model";
 
+
 class LoginController {
 
     /**
@@ -70,7 +71,6 @@ class LoginController {
             console.log(error);
             return response.json({error: 1, msg: error});
         }
-
         response.json({error: 0, msg: '', exist: userExist ? 1 : 0});
     }
 

+ 66 - 0
modules/volume_price/models/volume_price_model.js

@@ -0,0 +1,66 @@
+/**
+ * Created by Mai on 2017/7/25.
+ */
+
+let consts = require('../../main/models/project_consts');
+let commonConsts = consts.commonConst;
+
+let mongoose = require('mongoose');
+let volumePrice = require("./volume_price_schema");
+let async = require("async");
+
+let baseModel = require('../../main/models/base_model');
+
+class volumePriceModel extends baseModel {
+    constructor (name) {
+        super(volumePrice);
+        this.collectionName = name;
+    };
+
+    getData (projectID, callback) {
+        volumePrice.find({'$or': [
+            {
+                projectID: projectID,
+                deleteInfo: null
+            }, {
+                projectID: projectID,
+                'deleteInfo.deleted': {$in: [null, false]}
+            }
+        ]},(err,datas)=>{
+            if(err){
+                callback(1, '', null);
+            }else {
+                callback(0, consts.projectConst.VOLUMEPRICE, datas);
+            }
+        })
+    };
+
+    save (user_id, datas, callback) {
+        let funs = [];
+
+        function saveOne(doc) {
+            return function (cb) {
+                switch (doc.updateType) {
+                    case commonConsts.UT_UPDATE:
+                        volumePrice.update({projectID: doc.updateData.projectID, ID: doc.updateData.ID}, doc.updateData, cb);
+                        break;
+                    case commonConsts.UT_CREATE:
+                        volumePrice.create(doc.updateData, cb);
+                        break;
+                    case commonConsts.UT_DELETE:
+                        doc.updateData.deleteInfo = {deleted: true, deleteDateTime: new Date(), deleteBy: user_id};
+                        volumePrice.update({projectID: doc.updateData.projectID, ID: doc.updateData.ID}, doc.updateData, cb);
+                        break;
+                }
+            }
+        }
+        for (let data of datas){
+            funs.push(saveOne(data));
+        }
+
+        async.parallel(funs, callback);
+    };
+
+};
+
+module.exports = new volumePriceModel(consts.projectConst.VOLUMEPRICE);

+ 33 - 0
modules/volume_price/models/volume_price_schema.js

@@ -0,0 +1,33 @@
+/**
+ * Created by Mai on 2017/7/25.
+ * 量价
+ */
+
+let mongoose = require("mongoose");
+let Schema = mongoose.Schema;
+let deleteSchema = require('../../../public/models/delete_schema');
+let subSchema = require('../../main/models/bills_sub_schemas');
+
+let collectionName = 'volume_price';
+let volumePriceSchema = new Schema({
+    // id
+    ID: Number,
+    // 关联pm
+    projectID: Number,
+    // 关联 Bills
+    billsItemID: Number,
+    // 排序
+    serialNo: Number,
+    // 名称
+    name: String,
+    // 单位
+    unit: String,
+    // 数量
+    quantity: Number,
+    // 费用字段
+    fees: [subSchema.feesSchema],
+    // 是否删除
+    deleteInfo: deleteSchema
+});
+
+module.exports = mongoose.model(collectionName, volumePriceSchema);

+ 7 - 6
public/calc_util.js

@@ -19,7 +19,7 @@ let executeObj = {
     at: function(code) {
         let me = executeObj,
             rst = 0;
-        rst = me.currentTpl.compileAssistantObj[code].execRst;
+        rst = me.currentTpl.compileAssistantObj[code].unitFee;
         return rst;
     },
     base: function(calcBaseCode) {
@@ -28,9 +28,9 @@ let executeObj = {
         if (idx >= 0) {
             if (dummyCalcBaseCodeTypeCollection[idx].length > 0) {
                 let tmpSum = 0;
-                for (let glj of me.currentRationItem.rationGljList) {
-                    if (dummyCalcBaseCodeTypeCollection[idx].indexOf(glj["glj"]["gljType"]) >= 0) {
-                        tmpSum += glj["glj"]["basePrice"] * glj["consumeAmt"];
+                for (let glj of me.currentRationItem.data.gljList) {
+                    if (dummyCalcBaseCodeTypeCollection[idx].indexOf(glj["type"]) >= 0) {
+                        tmpSum += glj["basePrice"] * glj["quantity"];
                     }
                 }
                 rst = tmpSum;
@@ -184,10 +184,11 @@ class calculation {
             $CE.compiledFeeRateFile = me.compiledFee;
             for (let idx of me.calcTpl.compiledSeq) {
                 let item = me.calcTpl.calcItems[idx];
-                item.execRst = eval(item.compiledExpr);
+                item.unitFee = eval(item.compiledExpr);
+                item.totalFee = eval(item.unitFee * 5);     // AAAAA 5为测试值 $RATION.data.Quantity
             }
         }
     }
 }
 
-module.exports = new calculation();
+//module.exports = new calculation();

+ 2 - 0
public/web/sheet/sheet_common.js

@@ -10,6 +10,8 @@ var sheetCommonObj = {
         var spreadBook = new GC.Spread.Sheets.Workbook(container, { sheetCount: SheetCount });
         spreadBook.options.tabStripVisible = false;
         spreadBook.options.showHorizontalScrollbar = false;
+        spreadBook.options.allowCopyPasteExcelStyle = false;
+        spreadBook.options.allowUserDragDrop = true;
         return spreadBook;
     },
 

+ 10 - 1
public/web/tree_sheet/tree_sheet_helper.js

@@ -5,7 +5,7 @@
 var TREE_SHEET_HELPER = {
     getSheetCellStyle: function (setting) {
         var style = new GC.Spread.Sheets.Style();
-        style.locked = setting.readOnly ? true : false;
+        //style.locked = setting.readOnly ? true : false;
         style.name = setting.id;
         style.font = setting.data.font;
         style.hAlign = setting.data.hAlign;
@@ -99,6 +99,15 @@ var TREE_SHEET_HELPER = {
                 } else {
                     cell.value(getFieldText2());
                 }
+                if (colSetting.readOnly) {
+                    if (Object.prototype.toString.apply(colSetting.readOnly) === "[object Function]") {
+                        cell.locked(colSetting.readOnly(node));
+                    } else {
+                        cell.locked(true);
+                    }
+                } else {
+                    cell.locked(false);
+                }
             });
             sheet.autoFitRow(node.serialNo());
             if (recursive) {

+ 4 - 4
test/calculation/test_ration_calc.js

@@ -2,7 +2,7 @@
  * Created by CSL on 2017/7/14.
  */
 var test = require('tape');
-var rationCalc = require('../../public/calc_util');
+var calcUtil = require('../../public/calc_util');
 
 let dummyFee = [
     {
@@ -428,9 +428,9 @@ let dummyRation = {
 
 //*
 test('计算式测试', function(t){
-    rationCalc.init(dummyCalcTpl, dummyFee);
-    rationCalc.compile();
-    rationCalc.calculate(dummyRation);
+    calcUtil.init(dummyCalcTpl, dummyFee);
+    calcUtil.compile();
+    calcUtil.calculate(dummyRation);
     for (let idx of dummyCalcTpl.compiledSeq) {
         let item = dummyCalcTpl.calcItems[idx];
         console.log('code: ' + item.code + ' | expression: ' + item.compiledExpr +  ' | result: ' + item.execRst);

+ 9 - 67
test/tmp_data/bills_grid_setting.js

@@ -4,72 +4,12 @@
 var BillsGridSetting ={
     "emptyRows":3,
     "headRows":1,
-    "treeCol": 2,
+    "treeCol": 0,
     "headRowHeight":[
         47
     ],
     "cols":[
         {
-            "width":50,
-            "readOnly":false,
-            "head":{
-                "titleNames":[
-                    "ID"
-                ],
-                "spanCols":[
-                    1
-                ],
-                "spanRows":[
-                    1
-                ],
-                "vAlign":[
-                    1
-                ],
-                "hAlign":[
-                    1
-                ],
-                "font":[
-                    "Comic Sans MS"
-                ]
-            },
-            "data":{
-                "field":"ID",
-                "vAlign":1,
-                "hAlign":0,
-                "font":"Arial"
-            }
-        },
-        {
-            "width":50,
-            "readOnly":false,
-            "head":{
-                "titleNames":[
-                    "serialNo"
-                ],
-                "spanCols":[
-                    1
-                ],
-                "spanRows":[
-                    1
-                ],
-                "vAlign":[
-                    1
-                ],
-                "hAlign":[
-                    1
-                ],
-                "font":[
-                    "Comic Sans MS"
-                ]
-            },
-            "data":{
-                "field":"serialNo",
-                "vAlign":1,
-                "hAlign":0,
-                "font":"Arial"
-            }
-        },
-        {
             "width":150,
             "readOnly":false,
             "head":{
@@ -101,7 +41,7 @@ var BillsGridSetting ={
         },
         {
             "width":50,
-            "readOnly":false,
+            "readOnly":true,
             "head":{
                 "titleNames":[
                     "类别"
@@ -125,8 +65,9 @@ var BillsGridSetting ={
             "data":{
                 "field":"type",
                 "vAlign":1,
-                "hAlign":0,
-                "font":"Arial"
+                "hAlign":1,
+                "font":"Arial",
+                "getText": 'getText.type'
             }
         },
         {
@@ -156,7 +97,8 @@ var BillsGridSetting ={
                 "field":"name",
                 "vAlign":1,
                 "hAlign":0,
-                "font":"Arial"
+                "font":"Arial",
+                "wordWrap": true
             }
         },
         {
@@ -314,7 +256,7 @@ var BillsGridSetting ={
         },
         {
             "width":120,
-            "readOnly":false,
+            "readOnly": 'readOnly.volumePrice',
             "head":{
                 "titleNames":[
                     "工程量计算规则"
@@ -643,4 +585,4 @@ var BillsGridSetting ={
             }
         }
     ]
-};
+};

+ 209 - 0
test/tmp_data/test_bills_calc/bills_calc_base.js

@@ -0,0 +1,209 @@
+let BillsCalcBase = [
+    {
+        'classKey': 'FBFX',
+        'className': '分部分项',
+        'type': 'bills',
+        'subBase': [
+            {
+                'subKey': 'GCF',
+                'subName': '工程费',
+                'dispName': '分部分项--工程费'
+            },{
+                'subKey': 'DEJJRGF',
+                'subName': '定额基价人工费',
+                'dispName': '分部分项--定额基价人工费'
+            },{
+                'subKey': 'DEJJCLF',
+                'subName': '定额基价材料费',
+                'dispName': '分部分项--定额基价材料费'
+            },{
+                'subKey': 'DEJJJXF',
+                'dispName': '分部分项--定额基价机械费'
+            },{
+                'subKey': 'TZRGF',
+                'dispName': '分部分项--调整人工费'
+            },{
+                'subKey': 'TZJSRGF',
+                'dispName': '分部分项--调整机上人工费'
+            },{
+                'subKey': 'ZCF',
+                'dispName': '分部分项--主材费'
+            },{
+                'subKey': 'SBF',
+                'dispName': '分部分项--定额基价设备费'
+            },{
+                'subKey': 'WJJCLF',
+                'dispName': '分部分项--未计价材料费'
+            },{
+                'subKey': 'RGGR',
+                'dispName': '分部分项--人工工日'
+            },{
+                'subKey': 'GCLQDZDJJZJGCF',
+                'dispName': '分部分项--工程量清单中的基价直接工程费'
+            },{
+                'subKey': 'GCLQDZDJJRGF',
+                'dispName': '分部分项--工程量清单中的基价人工费'
+            }
+        ]
+    },{
+        'className': 'CSXM',
+        'type': 'bills',
+        'className': '措施项目',
+        'subBase': [
+            {
+                'subKey': 'CSXMF',
+                'dispName': '措施项目费'
+            },{
+                'subKey': 'ZZCSXMF',
+                'dispName': '组织措施项目费'
+            },{
+                'subKey': 'ZZCSXMDEJJZJGCF',
+                'dispName': '组织措施项目定额基价直接工程费'
+            },{
+                'subKey': 'ZZCSXMDEJJRGF',
+                'dispName': '组织措施项目定额基价人工费'
+            },{
+                'subKey': 'ZZCSXMDEJJCLF',
+                'dispName': '组织措施项目定额基价材料费'
+            },{
+                'subKey': 'ZZCSXMDEJJJXF',
+                'dispName': '组织措施项目定额基价机械费'
+            },{
+                'subKey': 'JSCSXMF',
+                'dispName': '技术措施项目费'
+            },{
+                'subKey': 'JSCSXMDEJJZJGCF',
+                'dispName': '技术措施项目定额基价直接工程费'
+            },{
+                'subKey': 'JSCSXMDEJJRGF',
+                'dispName': '技术措施项目定额基价人工费'
+            },{
+                'subKey': 'JSCSXMDEJJCLF',
+                'dispName': '技术措施项目定额基价材料费'
+            },{
+                'subKey': 'JSCSXMDEJJJXF',
+                'dispName': '技术措施项目定额基价机械费'
+            },{
+                'subKey': 'JSCSXMTZRGF',
+                'dispName': '技术措施项目调整人工费'
+            },{
+                'subKey': 'JSCSXMTZJSRGF',
+                'dispName': '技术措施项目调整机上人工费'
+            },{
+                'subKey': 'JSCSXMZCF',
+                'dispName': '技术措施项目主材费'
+            },{
+                'subKey': 'JSCSXMSBF',
+                'dispName': '技术措施项目设备费'
+            },{
+                'subKey': 'JSCSXMWJJCLF',
+                'dispName': '技术措施项目未计价材料费'
+            },{
+                'subKey': 'JSCSXMQDZDDEJJZJGCF',
+                'dispName': '技术措施项目清单中的定额基价直接工程费'
+            },{
+                'subKey': 'JSCSXMQDZDDEJJRGF',
+                'dispName': '技术措施项目清单中的定额基价人工费'
+            }
+        ]
+    },{
+        'classKey': 'QTXM',
+        'className': '其他项目',
+        'subBase': [
+            {
+                'subKey': 'QTXMF',
+                'dispName': '其他项目费'
+            },{
+                'subKey': 'GF',
+                'dispName': '规费'
+            },{
+                'subKey': 'SJ',
+                'dispName': '税金'
+            },{
+                'subKey': 'SQGCZJ',
+                'dispName': '税前工程造价'
+            }
+        ]
+    },{
+        'classKey': 'FBF',
+        'className': '分包费',
+        'subBase': [
+            {
+                'subKey': 'FBF',
+                'dispName': '分包费'
+            },{
+                'subKey': 'FBJJZJGCF',
+                'dispName': '分包基价直接工程费'
+            },{
+                'subKey': 'FBJJRGF',
+                'dispName': '分包基价人工费'
+            },{
+                'subKey': 'FBJJCLF',
+                'dispName': '分包基价材料费'
+            },{
+                'subKey': 'FBJJJXF',
+                'dispName': '分包基价机械费'
+            },{
+                'subKey': 'FBJJZCF',
+                'dispName': '分包基价主材费'
+            },{
+                'subKey': 'FBJJSBF',
+                'dispName': '分包基价设备费'
+            },{
+                'subKey': 'FBJJRGGR',
+                'dispName': '分包基价人工工日'
+            }
+        ]
+    },{
+
+        'classKey': 'RCJ',
+        'type': 'projectGLJ',
+        'className': '人材机',
+        'subBase': [
+            {
+                'subKey': 'RGJC',
+                'dispName': '人工价差'
+            },{
+                'subKey': 'CLJC',
+                'dispName': '材料价差'
+            },{
+                'subKey': 'JXJC',
+                'dispName': '机械价差'
+            },{
+                'subKey': 'JGRGF',
+                'dispName': '甲供人工费'
+            },{
+                'subKey': 'JGCLF',
+                'dispName': '甲供材料费'
+            },{
+                'ubKey': 'JGJXF',
+                'dispName': '甲供机械费'
+            },{
+                'ubKey': 'JGZCF',
+                'dispName': '甲供主材费'
+            },{
+                'ubKey': 'JGSBF',
+                'dispName': '甲供设备费'
+            },{
+                'subKey': 'JDRGF',
+                'dispName': '甲定人工费'
+            },{
+                'subKey': 'JDCLF',
+                'dispName': '甲供材料费'
+            },{
+                'ubKey': 'JDJXF',
+                'dispName': '甲供机械费'
+            },{
+                'ubKey': 'JDZCF',
+                'dispName': '甲供主材费'
+            },{
+                'ubKey': 'JDSBF',
+                'dispName': '甲供设备费'
+            }
+        ]
+    },{
+        'className': '结算价'
+    },{
+        'className': '变量表'
+    }
+];

+ 86 - 0
test/tmp_data/test_ration_calc/ration_calc_base.js

@@ -0,0 +1,86 @@
+/**
+ * Created by Mai on 2017/7/21.
+ */
+"use strict";
+
+const baseCalc = 0, adjustCalc = 1, budgetCalc = 2, diffCalc = 3,  offerCalc = 4;
+
+const gljType = {
+    // 人工
+    LABOUR: 1,
+    // ==============材料类型=================
+    // 普通材料
+    GENERAL_MATERIAL: 201,
+    // 混凝土
+    CONCRETE: 202,
+    // 砂浆
+    MORTAR: 203,
+    // 配合比
+    MIX_RATIO: 204,
+    // 商品混凝土
+    COMMERCIAL_CONCRETE: 205,
+    // 商品砂浆
+    COMMERCIAL_MORTAR: 206,
+    // ==============材料类型=================
+    // ==============机械类型=================
+    // 普通机械
+    GENERAL_MACHINE: 301,
+    // 机械组成物
+    MACHINE_COMPOSITION: 302,
+    // 机上人工
+    MACHINE_LABOUR: 303,
+    // ==============机械类型=================
+    // 主材
+    MAIN_MATERIAL: 4,
+    // 设备
+    EQUIPMENT: 5
+};
+
+let rationCalcBase = [
+    {
+        'dispName': '定额基价人工费',
+        'calcFun': 'base',
+        'calcType': baseCalc,
+        'gljTypes': [gljType.LABOUR]
+    },{
+        'dispName': '定额基价材料费',
+        'calcFun': 'base',
+        'calcType': baseCalc,
+        'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]
+    },{
+        'dispName': '定额基价机械费',
+        'calcFun': 'base',
+        'calcType': baseCalc,
+        'gljTypes': [gljType.GENERAL_MACHINE]
+    },{
+        'dispName': '定额基价机上人工费',
+        'calcFun': 'base',
+        'calcType': baseCalc,
+        'gljTypes': [gljType.MACHINE_LABOUR]
+    },{
+        'dispName': '人工费价差',
+        'calcFun': 'diff',
+        'calcType': budgetCalc,
+        'gljTypes': [gljType.LABOUR]
+    },{
+        'dispName': '材料费价差',
+        'calcFun': 'diff',
+        'calcType': diffCalc,
+        'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]
+    },{
+        'dispName': '机械费价差',
+        'calcFun': 'diff',
+        'calcType': diffCalc,
+        'gljTypes': [gljType.GENERAL_MACHINE]
+    },{
+        'dispName': '主材费',
+        'calcFun': 'budget',
+        'calcType': diffCalc,
+        'gljTypes': [gljType.MAIN_MATERIAL]
+    },{
+        'dispName': '设备费',
+        'calcFun': 'budget',
+        'calcType': budgetCalc,
+        'gljTypes': [gljType.EQUIPMENT]
+    }
+];

+ 2 - 0
web/building_saas/fee_rates/fee_rate.html

@@ -14,6 +14,8 @@
     <script src="/lib/spreadjs/views/gc.spread.views.dataview.10.0.0.min.js" type="text/javascript"></script>
     <script src="/lib/spreadjs/views/plugins/gc.spread.views.gridlayout.10.0.0.min.js" type="text/javascript"></script>
     <script src="/lib/spreadjs/views/locale/gc.spread.views.dataview.locale.zh-CN.10.0.0.min.js" type="text/javascript"></script>
+    <script>GC.Spread.Sheets.LicenseKey = "559432293813965#A0y3iTOzEDOzkjMyMDN9UTNiojIklkI1pjIEJCLi4TPB9mM5AFNTd4cvZ7SaJUVy3CWKtWYXx4VVhjMpp7dYNGdx2ia9sEVlZGOTh7NRlTUwkWR9wEV4gmbjBDZ4ElR8N7cGdHVvEWVBtCOwIGW0ZmeYVWVr3mI0IyUiwCMzETN8kzNzYTM0IicfJye&Qf35VfiEzRwEkI0IyQiwiIwEjL6ByUKBCZhVmcwNlI0IiTis7W0ICZyBlIsIyNyMzM5ADI5ADNwcTMwIjI0ICdyNkIsIibj9SbvNmL4N7bjRnch56ciojIz5GRiwiI8+Y9sWY9QmZ0Jyp96uL9v6L0wap9biY9qiq95q197Wr9g+89iojIh94Wiqi";</script>
+
 
     <style>
         .grid { height: 500px; margin:0 10px 10px 40px;}

+ 18 - 1
web/building_saas/main/html/main.html

@@ -462,6 +462,12 @@
 
     <script src="/lib/tether/tether.min.js"></script>
     <script src="/lib/bootstrap/bootstrap.min.js"></script>
+    <!--expression calculate-->
+    <script src="/lib/JSExpressionEval_src/Date.js"></script>
+    <script src="/lib/JSExpressionEval_src/Stack.js"></script>
+    <script src="/lib/JSExpressionEval_src/Tokanizer.js"></script>
+    <script src="/lib/JSExpressionEval_src/Evaluator.js"></script>
+    <!--end expression calculate-->
     <!--<script type="text/javascript" src="/lib/bootstrap/bootstrap-select.min.js"></script>-->
     <script type="text/javascript" src="/lib/jquery-contextmenu/jquery.contextMenu.js"></script>
     <script type="text/javascript" src="/lib/jquery-contextmenu/jquery.ui.position.js"></script>
@@ -487,20 +493,28 @@
     <script type="text/javascript" src="/web/building_saas/main/js/models/ration_glj.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/models/ration_coe.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/models/ration_ass.js"></script>
+    <script type="text/javascript" src="/web/building_saas/main/js/models/volume_price.js"></script>
 
     <script type="text/javascript" src="/public/web/id_tree.js"></script>
+	<script type="text/javascript" src="/test/tmp_data/test_ration_calc/ration_calc_base.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/models/cache_tree.js"></script>
-    <script type="text/javascript" src="/web/building_saas/main/js/models/bills_calc.js"></script>
+    <script type="text/javascript" src="/web/building_saas/main/js/calc/calc_fees.js"></script>
+    <script type="text/javascript" src="/web/building_saas/main/js/calc/ration_calc.js"></script>
+    <script type="text/javascript" src="/web/building_saas/main/js/calc/bills_calc.js"></script>
+    <script type="text/javascript" src="/public/calc_util.js"></script>
+    <script type="text/javascript" src="/web/building_saas/main/js/models/ration_calc.js"></script>
     <!-- Controller -->
     <script type="text/javascript" src="/public/web/tree_sheet/tree_sheet_controller.js"></script>
     <script type="text/javascript" src="/public/web/tree_sheet/tree_sheet_helper.js"></script>
     <script type="text/javascript" src="/public/web/sheet/sheet_data_helper.js"></script>
+
     <!-- Test Data -->
     <script type="text/javascript" src="/test/tmp_data/bills_grid_setting.js"></script>
     <!--<script type="text/javascript" src="/test/tmp_data/test_bills_calc/bills_grid_setting_test_calc.js"></script>
     <script type="text/javascript" src="/test/tmp_data/test_bills_calc/bills_data_15690.js"></script>
     <script type="text/javascript" src="/test/tmp_data/test_bills_calc/drawing_data_10268.js"></script>-->
     <!-- view -->
+    <script type="text/javascript" src="/web/building_saas/main/js/views/main_tree_col.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/project_info.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/project_view.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/main_ajax.js"></script>
@@ -509,6 +523,8 @@
     <script type="text/javascript" src="/web/building_saas/main/js/views/side_tools.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/std_bills_lib.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/std_ration_lib.js"></script>
+    <script type="text/javascript" src="/web/building_saas/main/js/models/quantity_detail.js"></script>
+    <script type="text/javascript" src="/web/building_saas/main/js/views/glj_view_contextMenu.js"></script>
     <!-- reports -->
     <script type="text/javascript" src="/public/web/treeDataHelper.js"></script>
     <script type="text/javascript" src="/public/web/ztree_common.js"></script>
@@ -518,6 +534,7 @@
     <script type="text/javascript" src="/web/building_saas/main/js/views/glj_view.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/ration_calc_view.js"></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/sub_view.js"></script>
+    <script src="/public/debug.js"></script>
 
     <SCRIPT type="text/javascript">
   		<!--

+ 45 - 77
web/building_saas/main/js/models/bills_calc.js

@@ -32,71 +32,15 @@ let billsPriceCalcFields = [
     {'type': 'machine', 'unitFeeFlag': billsPriceUnitFeeFlag, 'totalFeeFlag': sumTotalFeeFlag},
 ];
 
-Number.prototype.toDecimal = function (ADigit) {
-    return parseFloat(this.toFixed(ADigit));
-};
-
 let nodeCalcObj = {
     node: null,
     digit: 2,
     field: null,
-    findFee: function (fieldName) {
-        if (!this.node.data.fees) {
-            this.node.data.fees = [];
-        }
-        for (let fee of this.node.data.fees) {
-            if (fee.fieldName === fieldName) {
-                return fee;
-            }
-        }
-        return null;
-    },
-    AddFee: function (fieldName) {
-        let fee = {
-            'fieldName': fieldName,
-            'unitFee': 0,
-            'totalFee': 0,
-            'tenderUnitFee': 0,
-            'tenderTotalFee': 0
-        };
-        this.node.data.fees.push(fee);
-        this.node.data.feesIndex[fieldName] = fee;
-    },
-    checkFields: function (fields) {
-        for (let field of fields) {
-            if (!this.findFee(field.type)) {
-                this.AddFee(field.type);
-            }
-        }
-    },
-    getFee: function (data, fullField) {
-        let fields = fullField.split('.'), value = data;
-        for (let field of fields) {
-            if (value[field]) {
-                value = value[field];
-            } else {
-                return 0;
-            }
-        }
-        return value;
-    },
-    getFeeSplit: function (data, fullFields) {
-        let value = data;
-        for (let field of fullFields) {
-            if (value[field]) {
-                value = value[field];
-            } else {
-                return 0;
-            }
-        }
-        return value;
-    },
+    getFee: calcFees.getFee,
     sumTotalFee: function() {
         let result = 0, child;
         for (child of this.node.children) {
             result += this.getFee(child.data, this.field.totalFee);
-            //result += child.data.feesIndex[this.field.type].totalFee;
-            //result += this.getFeeSplit(child.data, this.field.totalFeeSplit);
         }
         return result;
     },
@@ -111,8 +55,6 @@ let nodeCalcObj = {
     },
     totalFee: function () {
         return this.getFee(this.node.data, this.field.unitFee) * this.getFee(this.node.data, 'quantity');
-        //return this.node.data.feesIndex[this.field.type].unitFee * this.node.data.quantity;
-        //return this.getFeeSplit(this.node.data, this.field.unitFeeSplit) * this.getFee(this.node.data, 'quantity');
     },
     rationContentUnitFee: function () {
         let result = 0, child, qty = this.getFee(this.node.data, 'quantity');
@@ -121,42 +63,57 @@ let nodeCalcObj = {
         }
         for (child of this.node.children) {
             result += (this.getFee(child.data, this.field.unitFee) * this.getFee(child.data, 'quantity') / qty).toDecimal(this.digit);
-            //result += (child.data.feesIndex[this.field.type].unitFee * child.data.quantity / qty).toDecimal(this.digit);
-            //result += (this.getFeeSplit(child.data, this.field.unitFeeSplit) * this.getFee(child.data, 'quantity') / qty).toDecimal(this.digit);
         }
         return result;
     }
 };
 
-class BillsCalc {
+class BillsCalcHelper {
     constructor (project, CalcFlag) {
         this.project = project;
         this.CalcFlag = CalcFlag;
         this.digit = 2;
         switch (this.CalcFlag) {
             case rationContent:
-                this.calcFieldName = rationContentCalcFields;
+                this.calcField = rationContentCalcFields;
                 break;
             case rationPrice:
-                this.calcFieldName = rationPriceCalcFields;
+                this.calcField = rationPriceCalcFields;
                 break;
             case rationPriceConverse:
-                this.calcFieldName = rationPriceConverseCalcFields;
+                this.calcField = rationPriceConverseCalcFields;
                 break;
             case billsPrice:
-                this.calcFieldName = billsPriceCalcFields;
+                this.calcField = billsPriceCalcFields;
                 break;
             default:
-                this.calcFieldName = [];
+                this.calcField = [];
         }
-        this.InitFields(this.calcFieldName);
+        this.InitFields(this.calcField);
     };
 
-    calcLeaf (node, fields) {
+    getBillsGLjs (node) {
+        let rations = this.project.Ration.getBillsSortRation(node.source.getID());
+        let gljs = this.project.ration_glj.getGatherGljArrByRations(rations);
+        for (let glj of gljs) {
+            glj.quantity = (glj.quantity / calcFees.getFee(node.data, 'quantity')).toDecimal(4);
+        }
+        return gljs;
+    };
+    calcRationLeaf (node, fields) {
         nodeCalcObj.node = node;
         nodeCalcObj.digit = this.digit;
-        nodeCalcObj.checkFields(fields);
-        let nodeCalc = nodeCalcObj;
+        calcFees.checkFields(node.data, fields);
+        let nodeCalc = nodeCalcObj, virData= null;
+
+        // 清单单价:套用定额计算程序
+        if (this.CalcFlag === billsPrice) {
+            rationCalcObj.calcGljs = this.getBillsGLjs(node);
+            console.log(rationCalcObj.calcGljs);
+            rationCalcObj.calcFields = rationCalcFields;
+            virData = rationCalcObj.calculate();
+        }
+
         for (let field of fields) {
             nodeCalcObj.field = field;
             switch (field.unitFeeFlag) {
@@ -166,9 +123,9 @@ class BillsCalc {
                 case averageQtyUnitFeeFlag:
                     node.data.feesIndex[field.type].unitFee = nodeCalcObj.averageQty().toDecimal(this.digit);
                     break;
-                // to do billsPriceUnitFeeFlag(套用定额计算程序)
-                // case billsPriceUnitFeeFlag:
-                //     break;
+                 case billsPriceUnitFeeFlag:
+                     node.data.feesIndex[field.type].unitFee = virData[field.type];
+                     break;
                 default:
                     node.data.feesIndex[field.type].unitFee = 0;
             }
@@ -184,9 +141,12 @@ class BillsCalc {
             }
         }
     };
+    calcVolumePriceLeaf (node, fields) {
+
+    };
     calcParent (node, fields) {
         nodeCalcObj.node = node;
-        nodeCalcObj.checkFields(fields);
+        calcFees.checkFields(node.data, fields);
         for (let field of fields) {
             nodeCalcObj.field = field;
             node.data.feesIndex[field.type].totalFee = nodeCalcObj.sumTotalFee().toDecimal(this.digit);
@@ -200,9 +160,17 @@ class BillsCalc {
 
             if (node.source.children.length > 0) {
                 this.calcNodes(node.children);
-                this.calcParent(node, this.calcFieldName);
+                this.calcParent(node, this.calcField);
             } else {
-                this.calcLeaf(node, this.calcFieldName);
+                if (node.children.length > 0) {
+                    if (node.firstChild().sourceType === this.project.Ration.getSourceType()) {
+                        this.calcRationLeaf(node, this.calcField);
+                    } else {
+                        this.calcVolumePriceLeaf(node, this.calcField);
+                    }
+                } else {
+
+                }
             }
         }
     };

+ 61 - 0
web/building_saas/main/js/calc/calc_fees.js

@@ -0,0 +1,61 @@
+/**
+ * Created by Mai on 2017/7/21.
+ */
+
+let calcFees = {
+    findFee: function (data, fieldName) {
+        if (!data.fees) {
+            data.fees = [];
+        }
+        for (let fee of data.fees) {
+            if (fee.fieldName === fieldName) {
+                return fee;
+            }
+        }
+        return null;
+    },
+    AddFee: function (data, fieldName) {
+        let fee = {
+            'fieldName': fieldName,
+            'unitFee': 0,
+            'totalFee': 0,
+            'tenderUnitFee': 0,
+            'tenderTotalFee': 0
+        };
+        data.fees.push(fee);
+        data.feesIndex[fieldName] = fee;
+    },
+    checkFields: function (data, fields) {
+        if (!data.fees) {
+            data.fees = [];
+            data.feesIndex = {};
+        }
+        for (let field of fields) {
+            if (!this.findFee(data, field.type)) {
+                this.AddFee(data, field.type);
+            }
+        }
+    },
+    getFee: function (data, fullField) {
+        let fields = fullField.split('.'), value = data;
+        for (let field of fields) {
+            if (value[field]) {
+                value = value[field];
+            } else {
+                return 0;
+            }
+        }
+        return value;
+    },
+    getFeeSplit: function (data, fullFields) {
+        let value = data;
+        for (let field of fullFields) {
+            if (value[field]) {
+                value = value[field];
+            } else {
+                return 0;
+            }
+        }
+        return value;
+    }
+}

+ 330 - 0
web/building_saas/main/js/calc/ration_calc.js

@@ -0,0 +1,330 @@
+/**
+ * Created by Mai on 2017/7/13.
+ */
+
+/*
+let rationCalcFields = [
+    {
+        type: 'baseDirect', code: "1", name: "基价直接工程费",
+        dispExpr: "1.1+1.2+1.3+1.4", expression: "at('1.1') + at('1.2') + at('1.3') + at('1.4')", compiledExpr: "",
+        statement: "基价人工费+基价材料费+基价机械费+未计价材料费"
+    },
+    {
+        type: 'baseLabour', code: "1.1", name: "基价人工费",
+        dispExpr: "1.1.1+1.1.2", expression: "at('1.1.1') + at('1.1.2')", compiledExpr: "",
+        statement: "定额基价人工费+定额人工单价(基价)调整"
+    },
+    {
+        type: 'rationBaseLabour', code: "1.1.1", name: "定额基价人工费",
+        dispExpr: "定额基价人工费", expression: "定额基价人工费", compiledExpr: "",
+        statement: "定额基价人工费"
+    },
+    {
+        type: 'rationAdjustLabour', code: "1.1.2", name: "定额基价人工费(调整后)",
+        dispExpr: "定额基价人工费(调整后)", expression: "定额基价人工费(调整后)", compiledExpr: "",
+        statement: "定额基价人工费(调整后)"
+    },
+    {
+        type: 'baseMaterial', code: "1.2", name: "基价材料费",
+        dispExpr: "定额基价材料费", expression: "定额基价材料费", compiledExpr: "",
+        statement: "定额基价材料费"
+    },
+    {
+        type: 'baseMachine', code: "1.3", name: "基价机械费",
+        dispExpr: "1.3.1+1.3.2", expression: "at('1.3.1') + at('1.3.2')", compiledExpr: "",
+        statement: "定额基价机械费+定额基价机上人工费(调整后)"
+    },
+    {
+        type: 'rationBaseMachine', code: "1.3.1", name: "定额基价机械费",
+        dispExpr: "定额基价机械费", expression: "定额基价机械费", compiledExpr: "",
+        statement: "定额基价机械费"
+    },
+    {
+        type: 'rationBaseMachineLabour', code: "1.3.1.1", name: "其中:定额基价机上人工费",
+        dispExpr: "定额基价机上人工费", expression: "定额基价机上人工费", compiledExpr: "",
+        statement: "定额基价机上人工费"
+    },
+    {
+        type: 'rationBaseMachineLabourFixed', code: "1.3.2", name: "定额基价机上人工费(调整后)",
+        dispExpr: "定额基价机上人工费(调整后)", expression: "定额基价机上人工费(调整后)", compiledExpr: "",
+        statement: "定额基价机上人工费(调整后)"
+    },
+    {
+        type: 'unPriceMaterial', code: "1.4", name: "未计价材料费",
+        dispExpr: "主材费+设备费", expression: "主材费+设备费", compiledExpr: "",
+        statement: "主材费+设备费"
+    },
+    {
+        type: 'management', code: "2", name: "企业管理费",
+        dispExpr: "1.1.1", expression: "at('1.1.1')", compiledExpr: "",
+        statement: "定额基价人工费"
+    },
+    {
+        type: 'profit', code: "3", name: "利润",
+        dispExpr: "1.1.1", expression: "at('1.1.1')", compiledExpr: "",
+        statement: "定额基价人工费"
+    },
+    {
+        type: 'risk', code: "4", name: "风险因素",
+        dispExpr: "", expression: "0", compiledExpr: "",
+        statement: ""
+    },
+    {
+        type: 'gljDiff', code: "5", name: "人材机价差",
+        dispExpr: "5.1+5.2+5.3", expression: "at('5.1') + at('5.2') + at('5.3')", compiledExpr: "",
+        statement: "人工费价差+材料费价差+机械费价差"
+    },
+    {
+        type: 'labourDiff', code: "5.1", name: "人工费价差",
+        dispExpr: "市场价格人工费-定额基价人工费(调整后)", expression: "市场价格人工费-定额基价人工费(调整后)", compiledExpr: "",
+        statement: "市场价格人工费-定额基价人工费(调整后)"
+    },
+    {
+        type: 'materialDiff', code: "5.2", name: "材料费价差",
+        dispExpr: "市场价格材料费-定额基价材料费", expression: "budget('材料') - base('材料')", compiledExpr: "",
+        statement: "市场价格材料费-定额基价材料费"
+    },
+    {
+        type: 'machineDiff', code: "5.3", name: "机械费价差",
+        dispExpr: "市场价格机械费-定额基价机械费-定额基价机上人工费(调整后)", expression: "市场价格机械费-定额基价机械费-定额基价机上人工费(调整后)", compiledExpr: "",
+        statement: "市场价格机械费-定额基价机械费-定额基价机上人工费(调整后)"
+    },
+    {
+        type: 'common', code: "6", name: "综合单价",
+        dispExpr: "1+2+3+4+5", expression: "at('1') + at('2') + at('3') + at('4') + at('5')", compiledExpr: "",
+        statement: "基价直接工程费+企业管理费+利润+风险因素+人材机价差"
+    }
+];
+*/
+"use strict";
+
+let calcEvaluate = function (expr) {
+    let exp = new Expression('');
+    exp.Expression(expr);
+    return exp.Evaluate();
+}
+
+let rationCalcFields = [
+    {
+        type: 'rationBaseLabour', code: "1.1.1", name: "定额基价人工费",
+        dispExpr: "定额基价人工费", expression: "定额基价人工费", compiledExpr: "",
+        statement: "定额基价人工费"
+    },
+    {
+        type: 'rationAdjustLabour', code: "1.1.2", name: "定额基价人工费(调整后)",
+        dispExpr: "定额基价人工费(调整后)", expression: "at('1.1.1')*(1.89-1)", compiledExpr: "",
+        statement: "定额基价人工费(调整后)"
+    },
+    {
+        type: 'baseLabour', code: "1.1", name: "基价人工费",
+        dispExpr: "1.1.1+1.1.2", expression: "at('1.1.1') + at('1.1.2')", compiledExpr: "",
+        statement: "定额基价人工费+定额人工单价(基价)调整"
+    },
+    {
+        type: 'baseMaterial', code: "1.2", name: "基价材料费",
+        dispExpr: "定额基价材料费", expression: "定额基价材料费", compiledExpr: "",
+        statement: "定额基价材料费"
+    },
+    {
+        type: 'rationBaseMachine', code: "1.3.1", name: "定额基价机械费",
+        dispExpr: "定额基价机械费", expression: "定额基价机械费", compiledExpr: "",
+        statement: "定额基价机械费"
+    },
+    {
+        type: 'rationBaseMachineLabour', code: "1.3.1.1", name: "其中:定额基价机上人工费",
+        dispExpr: "定额基价机上人工费", expression: "定额基价机上人工费", compiledExpr: "",
+        statement: "定额基价机上人工费"
+    },
+    {
+        type: 'rationBaseMachineLabourFixed', code: "1.3.2", name: "定额基价机上人工费(调整后)",
+        dispExpr: "定额基价机上人工费(调整后)", expression: "定额基价机上人工费*(1.89-1)", compiledExpr: "",
+        statement: "定额基价机上人工费(调整后)"
+    },
+    {
+        type: 'baseMachine', code: "1.3", name: "基价机械费",
+        dispExpr: "1.3.1+1.3.2", expression: "at('1.3.1') + at('1.3.2')", compiledExpr: "",
+        statement: "定额基价机械费+定额基价机上人工费(调整后)"
+    },
+    {
+        type: 'unPriceMaterial', code: "1.4", name: "未计价材料费",
+        dispExpr: "主材费+设备费", expression: "主材费+设备费", compiledExpr: "",
+        statement: "主材费+设备费"
+    },
+    {
+        type: 'baseDirect', code: "1", name: "基价直接工程费",
+        dispExpr: "1.1+1.2+1.3+1.4", expression: "at('1.1') + at('1.2') + at('1.3') + at('1.4')", compiledExpr: "",
+        statement: "基价人工费+基价材料费+基价机械费+未计价材料费"
+    },
+    {
+        type: 'management', code: "2", name: "企业管理费", feeRate: 7,
+        dispExpr: "1.1.1", expression: "at('1.1.1')", compiledExpr: "",
+        statement: "定额基价人工费"
+    },
+    {
+        type: 'profit', code: "3", name: "利润", feeRate: 13,
+        dispExpr: "1.1.1", expression: "at('1.1.1')", compiledExpr: "",
+        statement: "定额基价人工费"
+    },
+    {
+        type: 'risk', code: "4", name: "风险因素",
+        dispExpr: "", expression: "0", compiledExpr: "",
+        statement: ""
+    },
+    {
+        type: 'labourDiff', code: "5.1", name: "人工费价差",
+        dispExpr: "市场价格人工费-定额基价人工费(调整后)", expression: "人工费价差", compiledExpr: "",
+        statement: "市场价格人工费-定额基价人工费(调整后)"
+    },
+    {
+        type: 'materialDiff', code: "5.2", name: "材料费价差",
+        dispExpr: "市场价格材料费-定额基价材料费", expression: "材料费价差", compiledExpr: "",
+        statement: "市场价格材料费-定额基价材料费"
+    },
+    {
+        type: 'machineDiff', code: "5.3", name: "机械费价差",
+        dispExpr: "市场价格机械费-定额基价机械费-定额基价机上人工费(调整后)", expression: "机械费价差", compiledExpr: "",
+        statement: "市场价格机械费-定额基价机械费-定额基价机上人工费(调整后)"
+    },
+    {
+        type: 'gljDiff', code: "5", name: "人材机价差",
+        dispExpr: "5.1+5.2+5.3", expression: "at('5.1') + at('5.2') + at('5.3')", compiledExpr: "",
+        statement: "人工费价差+材料费价差+机械费价差"
+    },
+    {
+        type: 'common', code: "6", name: "综合单价",
+        dispExpr: "1+2+3+4+5", expression: "at('1') + at('2') + at('3') + at('4') + at('5')", compiledExpr: "",
+        statement: "基价直接工程费+企业管理费+利润+风险因素+人材机价差"
+    }
+];
+
+let rationCalcObj = {
+    calcGljs: [],
+    calcFields: null,
+    baseFields: rationCalcBase,
+    getFee: calcFees.getFee,
+    base: function (type) {
+        let value = 0;
+        for (let glj of this.calcGljs) {
+            if (type.indexOf(glj.type) >= 0) {
+                value = value + this.getFee(glj, 'quantity') * this.getFee(glj, 'basePrice');
+            }
+            glj = null;
+        }
+        return value;
+    },
+    adjust: function (type) {
+        let value = 0;
+        for (let glj of this.calcGljs) {
+            if (type.indexOf(glj.type) >= 0) {
+                value = value + this.getFee(glj, 'quantity') * this.getFee(glj, 'adjustPrice');
+            }
+            glj = null;
+        }
+        return value;
+    },
+    budget: function (type) {
+        let value = 0;
+        for (let glj of this.calcGljs) {
+            if (type.indexOf(glj.type) >= 0) {
+                value = value + this.getFee(glj, 'quantity') * this.getFee(glj, 'marketPrice');
+            }
+            glj = null;
+        }
+        return value;
+    },
+    diff: function (type) {
+        let value = 0;
+        for (let glj of this.calcGljs) {
+            if (type.indexOf(glj.type) >= 0) {
+                value = value + (this.getFee(glj, 'quantity') * this.getFee(glj, 'marketPrice')).toDecimal(2) - (this.getFee(glj, 'quantity') * this.getFee(glj, 'adjustPrice')).toDecimal(2);
+            }
+            glj = null;
+        }
+        return value;
+    },
+    at: function (calcCode) {
+        for (let field of this.calcFields) {
+            if (field.code === calcCode) {
+                return field.fee;
+            }
+        }
+        return 0;
+    },
+    initFields: function () {
+        for (let field of this.calcFields) {
+            field.fee = 0;
+        }
+    },
+    getCalcExpr: function (expression) {
+        let calcExpr = expression, reg;
+        for (let base of this.baseFields) {
+            reg = new RegExp(base.dispName);
+            if (reg.test(calcExpr)) {
+                let value = 0;
+                switch (base.calcType) {
+                    case baseCalc:
+                        value = this.base(base.gljTypes);
+                        break;
+                    case adjustCalc:
+                        value = this.adjust(base.gljTypes);
+                        break;
+                    case budgetCalc:
+                        value = this.budget(base.gljTypes);
+                        break;
+                    case diffCalc:
+                        value = this.diff(base.gljTypes);
+                        break;
+                    case offerCalc:
+                        value = this.offer(base.gljTypes);
+                        break;
+                }
+                calcExpr = calcExpr.replace(reg, value);
+            }
+            base = null;
+        }
+        reg = null;
+        let atReg = /at\('([0-9\.]+)'\)/i, strs;
+        while (strs = calcExpr.match(atReg)) {
+            calcExpr = calcExpr.replace(strs[0], this.at(strs[1]));
+        }
+        atReg = null;
+        strs = null;
+        return calcExpr;
+    },
+    calculate () {
+        let result = {};
+        for (let field of this.calcFields) {
+            let calcExpr = this.getCalcExpr(field.expression);
+            let feeRate = (field.feeRate && field.feeRate !== 0) ? field.feeRate : 1;
+            field.fee = (calcEvaluate(calcExpr) * feeRate).toDecimal(2);
+            result[field.type] = field.fee;
+        }
+        return result;
+    }
+};
+
+class RationCalcHelper {
+    constructor (project) {
+        this.project = project;
+    }
+
+    calculate (ration) {
+        rationCalcObj.calcGljs = this.project.ration_glj.getGljArrByRation(ration.ID);
+        rationCalcObj.calcFields = rationCalcFields;
+        calcFees.checkFields(ration, rationCalcFields);
+        for (let field of rationCalcFields) {
+            let calcExpr = rationCalcObj.getCalcExpr(field.expression);
+            let feeRate = (field.feeRate && field.feeRate !== 0) ? field.feeRate : 1;
+            field.fee = (calcEvaluate(calcExpr) * feeRate).toDecimal(2);
+            ration.feesIndex[field.type].unitFee = field.fee;
+            ration.feesIndex[field.type].totalFee = (field.fee * calcFees.getFee(ration, 'quantity')).toDecimal(2);
+            calcExpr = null;
+        }
+    }
+
+    calculateAll () {
+        for (let rationData of this.project.Ration.datas) {
+            this.calculate(rationData);
+        }
+    };
+}

+ 29 - 1
web/building_saas/main/js/controllers/project_controller.js

@@ -68,9 +68,37 @@ ProjectController = {
             this.syncDisplayNewNode(sheetController, newNode);
         }
     },
+    addVolumePrice: function (project, sheetController) {
+        if (!project || !sheetController) { return null; }
+
+        var selected = project.mainTree.selected;
+        var newSource = null, newNode = null;
+        if(selected === null) {
+            return;
+        }
+
+        if (selected.sourceType === project.Bills.getSourceType() && selected.source.children.length === 0) {
+            newSource = project.VolumePrice.insertVolumePrice(selected.source.getID());
+            newNode = project.mainTree.insert(selected.getID(), selected.tree.rootID());
+        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+            newSource = project.VolumePrice.insertVolumePrice(selected.source[project.masterField.volumePrice], selected.source);
+            newNode = project.mainTree.insert(selected.getParentID(), selected.getNextSiblingID());
+        }
+        if (newNode) {
+            newNode.source = newSource;
+            newNode.sourceType = project.VolumePrice.getSourceType();
+            newNode.data = newSource;
+
+            this.syncDisplayNewNode(sheetController, newNode);
+        }
+    },
     calculateAll: function (project, sheetController, CalcType) {
+        let date0 = new Date();
+        let ration_calc = new RationCalcHelper(project);
+        ration_calc.calculateAll();
         let date1 = new Date();
-        let calc = new BillsCalc(project, CalcType);
+        console.log(date1 - date0);
+        let calc = new BillsCalcHelper(project, CalcType);
         calc.calcAll();
         calc = null;
         let date2 = new Date();

+ 1 - 1
web/building_saas/main/js/main_ajax.js

@@ -86,4 +86,4 @@ var GetRations = function (proj_id, callback) {
             alert('error ' + textStatus + " " + errorThrown);
         }
     });
-};
+};

+ 21 - 1
web/building_saas/main/js/models/bills.js

@@ -94,7 +94,26 @@ var Bills = {
 
         // 提交数据后的错误处理方法
         bills.prototype.doAfterUpdate = function(err, data){
-            // to do
+            console.log(data)
+            if(data.quantityRefresh){
+                this.refreshDatas(data,'quantity');
+            }
+        };
+
+        bills.prototype.refreshDatas = function(data,fieldName){
+            var dataIndex = _.findIndex(this.datas,function(item) {
+                return item.ID ==data.billID;
+            });
+            this.datas[dataIndex][fieldName] = data[fieldName];
+            if(fieldName=='quantity'){
+                this.datas[dataIndex]['isFromDetail']=1
+            }
+            var controller = projectObj.mainController;
+            var selected = controller.sheet.getSelections();
+            var col =   _.findIndex(BillsGridSetting.cols,function (col) {
+                return col.data.field ==fieldName;
+            });
+            controller.sheet.getCell(selected[0].row,col).value(data[fieldName]);
         };
 
         bills.prototype.getCounterData = function (count) {
@@ -148,6 +167,7 @@ var Bills = {
             var moudles =[ModuleNames.bills,ModuleNames.ration_glj];
             var deleteDatas=[tools.coverseTreeUpdateData(deleteData, this.project.ID()),ration_glj.getDeleteDataByBills(deleteData)];
             project.ration_glj.deleteByBills(deleteData);
+            project.quantity_detail.deleteByBills(deleteData);
             project.pushNow('deleteBILL', moudles, deleteDatas);
             this.delete(deleteData);
 

+ 3 - 1
web/building_saas/main/js/models/main_consts.js

@@ -9,5 +9,7 @@ const ModuleNames = {
     projectGLJ: 'projectGLJ',
     ration_glj:'ration_glj',
     ration_coe:'ration_coe',
-    ration_ass:'ration_ass'
+    ration_ass:'ration_ass',
+    quantity_detail:'quantity_detail',
+    volume_price: 'volume_price'
 };

+ 13 - 1
web/building_saas/main/js/models/project.js

@@ -71,9 +71,11 @@ var PROJECT = {
             this.ration_glj = ration_glj.createNew(this);
             this.ration_coe = ration_coe.createNew(this);
             this.ration_ass = ration_ass.createNew(this);
+            this.quantity_detail = quantity_detail.createNew(this);
             this.FeeRate = FeeRate.createNew(this);
+            this.VolumePrice = VolumePrice.createNew(this);
 
-            this.masterField = {ration: 'billsItemID'};
+            this.masterField = {ration: 'billsItemID', volumePrice: 'billsItemID'};
         };
 
         // prototype用于定义public方法
@@ -101,6 +103,15 @@ var PROJECT = {
                     newNode.data = br[i];
                 }
             };
+            let loadVolumePriceNode = function (cacheNode) {
+                let newNode = null, bv = that.VolumePrice.getBillsSortVolumePrice(cacheNode.source.getID());
+                for (let v of bv) {
+                    newNode = that.mainTree.addNode(cacheNode);
+                    newNode.source = v;
+                    newNode.sourceType = that.VolumePrice.getSourceType();
+                    newNode.data = v;
+                }
+            };
             var loadIdTreeNode = function (nodes, parent) {
                 var newNode, i;
                 for (i = 0; i < nodes.length; i++) {
@@ -111,6 +122,7 @@ var PROJECT = {
 
                     if (nodes[i].children.length === 0) {
                         loadRationNode(that.Ration.datas, newNode);
+                        loadVolumePriceNode(newNode);
                     } else {
                         loadIdTreeNode(nodes[i].children, newNode);
                     }

+ 450 - 0
web/building_saas/main/js/models/quantity_detail.js

@@ -0,0 +1,450 @@
+/**
+ * Created by Mai on 2017/4/1.
+ */
+var quantity_detail = {
+    createNew: function (project) {
+        // 用户定义private方法
+        var tools = {};
+
+        // 所有通过this访问的属性,都不应在此单元外部进行写入操作
+        var quantity_detail = function (proj) {
+            this.gljTree = cacheTree.createNew(this);
+           // this.project = proj;
+            this.datas = [];
+
+            var sourceType = ModuleNames.quantity_detail;
+            this.getSourceType = function () {
+                return sourceType;
+            }
+            proj.registerModule(ModuleNames.quantity_detail, this);
+            this.temList=[];
+        };
+
+        // prototype用于定义public方法
+        quantity_detail.prototype.loadData = function (datas) {
+            this.datas = datas;
+        };
+
+        // 提交数据后返回数据处理
+        quantity_detail.prototype.doAfterUpdate = function(err, data){
+            if(!err){
+                if(data.updateTpye=='ut_update'){
+                    this.refreshAfterUpdate(data);
+                }else if(data.updateTpye=='ut_delete'){
+                    this.refreshAfterDelete(data);
+                } else {
+                    this.refreshAfterSave(data);
+                }
+            }else {
+                alert(err.message);
+                this.refreshSheetData();
+            }
+        };
+        quantity_detail.prototype.refreshAfterSave=function(data){
+            console.log(data);
+            var me = this;
+            if(data.hasOwnProperty('resort')){
+                this.resortData(data.doc,1);
+                _.forEach(data.update_task,function (item) {
+                    me.refreshEachItme(item.query,item.doc);
+                })
+                gljOprObj.detailData.push(data.doc);
+                this.datas.push(data.doc);
+            }else {
+                this.datas.push(data);
+                gljOprObj.detailData.push(data);
+            }
+            gljOprObj.detailData=_.sortBy(gljOprObj.detailData,'seq');
+            this.refreshSheetData();
+        };
+        quantity_detail.prototype.resortData=function(data,req){
+
+            for(var i =0;i<gljOprObj.detailData.length;i++){
+                var item = gljOprObj.detailData[i];
+                if(item.seq>=data.seq){
+                    item.seq=item.seq+req;
+                }
+            }
+        };
+        quantity_detail.prototype.refreshAfterUpdate=function(data){
+            var me = this;
+            var filter_object;
+            if(data.hasOwnProperty('refreshList')){
+                _.forEach(data.refreshList,function (item) {
+                    filter_object= me.refreshEachItme(item.query,item.doc);
+                })
+            }else {
+                filter_object = me.refreshEachItme(data.query,data.doc);
+            }
+            var showList = _.filter(this.datas,filter_object);
+            gljOprObj.detailData=showList;
+            gljOprObj.detailData=_.sortBy(gljOprObj.detailData,'seq');
+            this.refreshSheetData();
+        };
+        quantity_detail.prototype.refreshEachItme = function(query,doc){
+            var detail_list = this.datas;
+            var detail_index= _.findIndex(detail_list,function(detail){
+                return detail.ID==query.ID;
+            })
+            _.forEach(doc, function(n, key) {
+                detail_list[detail_index][key] = n;
+            });
+            var filter_object;
+            if(detail_list[detail_index].hasOwnProperty('rationID')){
+                filter_object={'rationID':detail_list[detail_index].rationID};
+            }else {
+                filter_object={'billID':detail_list[detail_index].billID};
+            }
+            return filter_object;
+        };
+        quantity_detail.prototype.refreshAfterDelete=function(data){
+            var me = this;
+            if(data.doc.seq != gljOprObj.detailData.length - 1){
+                this.resortData(data.doc,-1);
+            }
+            _.forEach(data.update_task,function (item) {
+                me.refreshEachItme(item.query,item.doc);
+            });
+            _.remove(this.datas,{ID:data.doc.ID});
+            _.remove(gljOprObj.detailData,{ID:data.doc.ID});
+            this.refreshSheetData();
+        };
+        quantity_detail.prototype.refreshSheetData=function () {
+            sheetCommonObj.showData(gljOprObj.detailSheet,gljOprObj.detailSetting,gljOprObj.detailData);
+        };
+        quantity_detail.prototype.getUpdateData=function(type,query,doc,callfunction){
+            var updateData = [];
+            var newobj = {
+                'updateType': type,
+                'query': query,
+            }
+            if(doc){
+                newobj['doc']=doc;
+            }
+            if(callfunction){
+                newobj['updateFunction']=callfunction;
+            }
+            updateData.push(newobj);
+            return updateData;
+        };
+        quantity_detail.prototype.saveQuantityDetail=function (args,dataCode) {
+            var doc={};
+            var selected = projectObj.project.mainTree.selected;
+            if(selected.sourceType==ModuleNames.ration){
+                doc.rationID=selected.data.ID;
+            }
+            if(selected.sourceType==ModuleNames.bills){
+                doc.billID=selected.data.ID;
+            }
+            doc.projectID = selected.data.projectID;
+            doc[dataCode]=args.editingText;
+            doc.seq=args.row;
+            if(dataCode=='regex'){
+                if(!this.regexChecking(args.editingText)||!this.referenceChecking(args.editingText,args.row,doc)){
+                    return;
+                }
+                doc.refreshQuantity=true;
+                if(!selected.data.hasOwnProperty('isFromDetail')||selected.data.isFromDetail==0){
+                    var c = confirm("确定要使用工程量明细替换原工程量吗?");
+                    if(!c){
+                        doc.refreshQuantity=false;
+                    }
+                }
+
+            }
+            var updateData
+            if(args.hasOwnProperty("insertRecode")){
+                updateData = this.getUpdateData('ut_update',null,doc,'insertRecode');
+            }else{
+                updateData = this.getUpdateData('ut_create',null,doc);
+            }
+            project.pushNow('saveQuantityDetail',[this.getSourceType()],updateData);
+        };
+        quantity_detail.prototype.insertQuantityDetail = function (row) {
+            var args = {
+                row:row,
+                editingText:1
+            }
+            if(row < gljOprObj.detailData.length){
+                args.insertRecode = true;
+            }
+            this.saveQuantityDetail(args,'isSummation');
+
+        };
+
+        quantity_detail.prototype.deleteQuantityDetail = function (row) {
+           var deleteable = this.checkReference(row);
+           if(deleteable){
+               var recode = gljOprObj.detailData[row];
+               var updateData = this.getUpdateData('ut_delete',null,recode);
+               project.pushNow('deleteQuantityDetail',[this.getSourceType()],updateData);
+           }else {
+               alert("当前行已被引用,不可删除。");
+           }
+
+        };
+        quantity_detail.prototype.checkReference = function (row) {
+            var deleteable = true;
+           for(var i =0;i<gljOprObj.detailData.length;i++){
+                var item = gljOprObj.detailData[i];
+                if(_.includes(item.referenceIndexs,row+1)){
+                    deleteable = false;
+                    break;
+                }
+            }
+            return deleteable;
+        };
+
+        quantity_detail.prototype.moveDown = function (row) {
+            this.swapRow(row);
+        };
+        quantity_detail.prototype.moveUp = function (row) {
+            this.swapRow(row-1);
+        };
+        quantity_detail.prototype.swapRow = function (preRow) {
+            var me = this;
+            var update_task = [];
+            var a_row = gljOprObj.detailData[preRow];//
+            var b_row = gljOprObj.detailData[preRow +1];//
+            var temA = a_row.seq;
+            var temB = b_row.seq;
+            a_row.seq = temB;
+            update_task.push({query:{ID:a_row.ID,projectID:a_row.projectID},doc:{seq:a_row.seq}});
+            b_row.seq = temA;
+            update_task.push({query:{ID:b_row.ID,projectID:b_row.projectID},doc:{seq:b_row.seq}});
+            gljOprObj.detailData.forEach(function (item) {
+                if(_.includes(item.referenceIndexs,temA+1)||_.includes(item.referenceIndexs,temB+1)){
+                    var regex = item.regex;
+                    for (var i=0;i<item.referenceIndexs.length;i++){
+                        if(item.referenceIndexs[i]==temA+1){
+                            regex = me.replaceAll('C'+item.referenceIndexs[i],'B'+(temB+1),regex);
+                            regex = me.replaceAll('c'+item.referenceIndexs[i],'b'+(temB+1),regex);
+                            item.referenceIndexs[i]=temB+1;
+                        }else if(item.referenceIndexs[i]==temB+1){
+                            regex = me.replaceAll('C'+item.referenceIndexs[i],'B'+(temA+1),regex);
+                            regex = me.replaceAll('c'+item.referenceIndexs[i],'b'+(temA+1),regex);
+                            item.referenceIndexs[i]=temA+1;
+                        }
+                    }
+                    regex =  me.replaceAll('B','C',regex);
+                    regex =  me.replaceAll('b','c',regex);
+                    update_task.push({query:{ID:item.ID,projectID:item.projectID},doc:{regex:regex,referenceIndexs:item.referenceIndexs}});
+                }
+            })
+
+            var updateData=[];
+            update_task.forEach(function (task) {
+                updateData.push({'updateType': 'ut_update', 'query': task.query,'doc':task.doc});
+            })
+            project.pushNow('updateQuantityDetail',[this.getSourceType()],[updateData]);
+
+        };
+        quantity_detail.prototype.replaceAll=function(FindText, RepText,str) {
+            let regExp = new RegExp(FindText, "g");
+            return str.replace(regExp, RepText);
+        };
+        quantity_detail.prototype.updateQuantityDetail=function (args,dataCode,recode) {
+            var doc ={};
+            var query={
+                ID:recode.ID,
+                projectID:recode.projectID
+            };
+            var selected = projectObj.project.mainTree.selected;
+            doc[dataCode]=args.editingText;
+            if (dataCode == 'regex') {
+                if(recode.hasOwnProperty('rationID')){
+                    query.rationID=recode.rationID;
+                }else {
+                    query.billID = recode.billID
+                }
+                query.refreshQuantity=true;
+                if(!selected.data.hasOwnProperty('isFromDetail')||selected.data.isFromDetail==0){
+                    var c = confirm("确定要使用工程量明细替换原工程量吗?");
+                    if(!c){
+                        query.refreshQuantity=false;
+                    }
+                }
+                query.index = args.row;
+                this.updateQuantityRegex(query,doc,args)
+            }else {
+                this.normalUpdate(query,doc);
+            }
+        };
+        quantity_detail.prototype.updateQuantityRegex=function(query,doc,args){
+            var needupdate = false;
+            if(args.editingText==null){
+                needupdate =true;
+            }else {
+                args.editingText = _.trim(args.editingText,/\r\n/);
+                if(this.regexChecking(args.editingText)&&this.referenceChecking(args.editingText,args.row,doc)){
+                    needupdate = true;
+                }
+            }
+            if(needupdate){
+                var updateData = this.getUpdateData('ut_update',query,doc,'updateQuantityRegex');
+                project.pushNow('updateQuantityDetail',[this.getSourceType()],updateData);
+            }
+        };
+
+        quantity_detail.prototype.isSummationUpdate=function (args,detailList,newval) {
+            var query={
+                ID:detailList[args.row].ID,
+                projectID:detailList[args.row].projectID
+            };
+            var selected = projectObj.project.mainTree.selected;
+            query.refreshQuantity=true;
+            if(!selected.data.hasOwnProperty('isFromDetail')||selected.data.isFromDetail==0){
+                var c = confirm("确定要使用工程量明细替换原工程量吗?");
+                if(!c){
+                    query.refreshQuantity=false;
+                }
+            }
+            if(detailList[args.row].hasOwnProperty('rationID')){
+                query.rationID=detailList[args.row].rationID;
+            }else {
+                query.billID = detailList[args.row].billID
+            }
+            var doc={
+                isSummation:newval
+            };
+            this.normalUpdate(query,doc);
+        };
+        quantity_detail.prototype.normalUpdate=function(query,doc){
+            var updateData = this.getUpdateData('ut_update',query,doc);
+            project.pushNow('updateQuantityDetail',[this.getSourceType()],updateData);
+        };
+        quantity_detail.prototype.regexChecking=function(text){
+            var regex=/^[0-9Cc\+\-\*\^/\(\)\.]*$/g;
+            if(!regex.test(text)){
+                alert("输入了非法字符,请重新输入!")
+                return false;
+            }else {
+                return true;
+            }
+        };
+        quantity_detail.prototype.referenceChecking=function (text,row,doc) {
+            text = text.toUpperCase();
+            //text= this.replaceSqr(text);
+            var me = this;
+            var refReg = /C\d+/g;
+            var self ='C'+(row+1);
+
+            var refList = text.match(refReg);
+            var invalidate = _.includes(refList,self);
+            var referenceIndexs = [];
+            var indexOut = false;
+            _.forEach(refList,function (item) {
+                var ref_index = parseInt(item.substring(1));
+                if(ref_index>gljOprObj.detailData.length){
+                    indexOut=true;
+                    return;
+                }else {
+                    referenceIndexs.push(ref_index);
+                }
+            });
+            if(indexOut){
+                alert("引用有误,请重新输入!");
+                return false;
+            }
+            referenceIndexs=_.uniq(referenceIndexs);
+            doc.referenceIndexs = referenceIndexs;
+
+            this.temList = referenceIndexs;
+
+            invalidate=this.getAllReferenceList((row+1),referenceIndexs);
+
+            if(invalidate){
+                alert("计算式中产生了循环引用,请重新输入!");
+                return false;
+            }
+            return true;
+        };
+
+        quantity_detail.prototype.getAllReferenceList=function(original,refList){
+            var me =this;
+            var invalidate=false;
+            _.forEach(refList,function (item) {
+                if(me.getReferenceList(item,original)){
+                    invalidate=true;
+                }
+            })
+            return invalidate;
+
+        };
+
+        quantity_detail.prototype.getReferenceList=function(item,original) {
+            var invalidate =false;
+            if(gljOprObj.detailData.length>=item){
+                var recode = gljOprObj.detailData[item - 1];
+                if (recode.referenceIndexs.length > 0) {
+                    if(_.includes(recode.referenceIndexs,original)){
+                        invalidate = true;
+                        return invalidate;
+                    }
+                    this.temList = this.temList.concat(recode.referenceIndexs);
+                    _.forEach(recode.referenceIndex, function (item) {
+                        if(this.getReferenceList(item,original)){
+                            invalidate = true;
+                        }
+                    })
+                }
+            }
+            return invalidate;
+        }
+
+        quantity_detail.prototype.replaceSqr = function(text) {
+            var squarRegex = /\([^\^]+\)\^\d+/g;
+            var sqararr = text.match(squarRegex);
+
+            var squarRegex2 = /C[0-9]+\^\d+|[0-9]+([.]{1}[0-9]+){0,1}\^\d+/g; //匹配没有括号的
+            var sqararr2=text.match(squarRegex2);
+            if(sqararr){
+                text=converSqrByArr(sqararr,text);
+            }
+            if(sqararr2){
+                text=converSqrByArr(sqararr2,text);
+            }
+            return text;
+        };
+        quantity_detail.prototype.converSqrByArr = function (sqararr,text) {
+            var temp = text;
+            sqararr.forEach(function (item) {
+                var arr = item.split('\^');
+                var y = parseInt(arr[1]);
+                var x_arr = [];
+                for (var i = 0; i < y; i++) {
+                    x_arr.push(arr[0]);
+                }
+                var temStr = x_arr.join('*');
+                temp = temp.replace(item, temStr);
+            });
+            return temp;
+        };
+        quantity_detail.prototype.deleteByRation = function(ration){
+            var detail_list = this.datas;
+            var newList =_.filter(detail_list,(d)=>{
+                return d.rationID!=ration.ID;
+            });
+            if(newList!=undefined){
+                this.datas = newList;
+            }
+        };
+        quantity_detail.prototype.deleteByBills=function(deleteData){
+            var detail_list = this.datas;
+            var billIDList = [];
+            for(var i=0;i<deleteData.length;i++){
+                if(deleteData[i].type=='delete'){
+                    billIDList.push(deleteData[i].data.ID);
+                }
+            }
+            var newList =_.filter(detail_list,(d)=>{
+                return !_.includes(billIDList,d.billID);
+            });
+            if(newList!=undefined){
+                this.datas = newList;
+            }
+        };
+        return new quantity_detail(project);
+    }
+
+};

+ 22 - 17
web/building_saas/main/js/models/ration.js

@@ -59,29 +59,34 @@ var Ration = {
             if(data.stateRefresh){
                 this.refreshAdjustState(data);
             }
+            if(data.quantityRefresh){
+                this.refreshQuantity(data);
+            }
         };
         ration.prototype.refreshAdjustState = function(data){
+            this.refreshDatas(data,'adjustState');
+            if(data.hasOwnProperty('name')){
+              this.refreshDatas(data,'name')
+            }
+        };
+        ration.prototype.refreshQuantity = function(data){
+            this.refreshDatas(data,'quantity');
+        };
+        ration.prototype.refreshDatas = function(data,fieldName){
+            var dataIndex = _.findIndex(this.datas,function(item) {
+                return item.ID ==data.rationID;
+            });
+            this.datas[dataIndex][fieldName] = data[fieldName];
+            if(fieldName=='quantity'){
+                this.datas[dataIndex]['isFromDetail']=1
+            }
             var controller = projectObj.mainController;
-             var dataIndex = _.findIndex(this.datas,function(item) {
-                 return item.ID ==data.rationID;
-             });
-            this.datas[dataIndex].adjustState = data.adjustState;
             var selected = controller.sheet.getSelections();
             var col =   _.findIndex(BillsGridSetting.cols,function (col) {
-                return col.data.field =='adjustState';
+                return col.data.field ==fieldName;
             });
-            controller.sheet.getCell(selected[0].row,col).value(data.adjustState);
-            if(data.hasOwnProperty('name')){
-                this.datas[dataIndex].name = data.name;
-                var nameCol = _.findIndex(BillsGridSetting.cols,function (col) {
-                    return col.data.field =='name';
-                })
-                controller.sheet.getCell(selected[0].row,nameCol).value(data.name);
-            }
-
-        }
-
-
+            controller.sheet.getCell(selected[0].row,col).value(data[fieldName]);
+        };
         ration.prototype.getTempRationData = function (id, billsID, serialNo) {
             var newData = {'ID': id, 'serialNo': serialNo, projectID: this.project.ID()};
             newData[project.masterField.ration] = billsID;

+ 385 - 0
web/building_saas/main/js/models/ration_calc.js

@@ -0,0 +1,385 @@
+/**
+ * Created by CSL on 2017-07-19.
+ */
+
+let calcFeeRate = [
+    {
+        "ID" : 1,
+        "ParentID" : null,
+        "name" : "企业管理费",
+        "rate" : null,
+        "memo" : null
+    },
+    {
+        "ID" : 2,
+        "ParentID" : 1,
+        "name" : "建筑工程",
+        "rate" : null,
+        "memo" : null
+    },
+    {
+        "ID" : 3,
+        "ParentID" : 2,
+        "name" : "一类工程",
+        "rate" : 16.03,
+        "memo" : null
+    },
+    {
+        "ID" : 4,
+        "ParentID" : 2,
+        "name" : "二类工程",
+        "rate" : 14.95,
+        "memo" : null
+    },
+    {
+        "ID" : 5,
+        "ParentID" : 2,
+        "name" : "三类工程",
+        "rate" : 12.47,
+        "memo" : null
+    },
+    {
+        "ID" : 6,
+        "ParentID" : 2,
+        "name" : "四类工程",
+        "rate" : 9.3,
+        "memo" : null
+    },
+    {
+        "ID" : 7,
+        "ParentID" : 1,
+        "name" : "市政工程",
+        "rate" : null,
+        "memo" : null
+    },
+    {
+        "ID" : 8,
+        "ParentID" : 7,
+        "name" : "一类工程",
+        "rate" : 16.33,
+        "memo" : null
+    },
+    {
+        "ID" : 9,
+        "ParentID" : 7,
+        "name" : "二类工程",
+        "rate" : 15,
+        "memo" : null
+    },
+    {
+        "ID" : 10,
+        "ParentID" : 7,
+        "name" : "三类工程",
+        "rate" : 12.5,
+        "memo" : null
+    },
+    {
+        "ID" : 11,
+        "ParentID" : 7,
+        "name" : "四类工程",
+        "rate" : 9.5,
+        "memo" : null
+    },
+    {
+        "ID" : 12,
+        "ParentID" : 1,
+        "name" : "机械土石方",
+        "rate" : 15.5,
+        "memo" : null
+    },
+    {
+        "ID" : 13,
+        "ParentID" : 1,
+        "name" : "仿古建筑工程",
+        "rate" : 12,
+        "memo" : null
+    },
+    {
+        "ID" : 14,
+        "ParentID" : 1,
+        "name" : "建筑修缮工程",
+        "rate" : 12.47,
+        "memo" : null
+    },
+    {
+        "ID" : 15,
+        "ParentID" : 1,
+        "name" : "炉窑砌筑工程",
+        "rate" : null,
+        "memo" : null
+    },
+    {
+        "ID" : 16,
+        "ParentID" : 15,
+        "name" : "一类工程",
+        "rate" : 14.25,
+        "memo" : null
+    },
+    {
+        "ID" : 17,
+        "ParentID" : 15,
+        "name" : "二类工程",
+        "rate" : 12.47,
+        "memo" : null
+    },
+    {
+        "ID" : 18,
+        "ParentID" : 15,
+        "name" : "三类工程",
+        "rate" : 10.8,
+        "memo" : null
+    },
+    {
+        "ID" : 19,
+        "ParentID" : 15,
+        "name" : "四类工程",
+        "rate" : null,
+        "memo" : null
+    },
+    {
+        "ID" : 20,
+        "ParentID" : null,
+        "name" : "规费",
+        "rate" : null,
+        "memo" : null
+    },
+    {
+        "ID" : 21,
+        "ParentID" : 20,
+        "name" : "建筑工程",
+        "rate" : 4.87,
+        "memo" : null
+    },
+    {
+        "ID" : 22,
+        "ParentID" : 20,
+        "name" : "市政工程",
+        "rate" : 3.61,
+        "memo" : null
+    },
+    {
+        "ID" : 23,
+        "ParentID" : 20,
+        "name" : "机械土石方",
+        "rate" : 2.15,
+        "memo" : null
+    },
+    {
+        "ID" : 24,
+        "ParentID" : 20,
+        "name" : "仿古建筑工程",
+        "rate" : 2.84,
+        "memo" : null
+    },
+    {
+        "ID" : 25,
+        "ParentID" : 20,
+        "name" : "建筑修缮工程",
+        "rate" : 2.84,
+        "memo" : null
+    },
+    {
+        "ID" : 26,
+        "ParentID" : 20,
+        "name" : "炉窑砌筑工程",
+        "rate" : 3.61,
+        "memo" : null
+    },
+    {
+        "ID" : 27,
+        "ParentID" : null,
+        "name" : "利润",
+        "rate" : null,
+        "memo" : null
+    },
+    {
+        "ID" : 28,
+        "ParentID" : 27,
+        "name" : "建筑工程",
+        "rate" : null,
+        "memo" : null
+    },
+    {
+        "ID" : 29,
+        "ParentID" : 28,
+        "name" : "一类工程",
+        "rate" : 8.73,
+        "memo" : null
+    },
+    {
+        "ID" : 30,
+        "ParentID" : 28,
+        "name" : "二类工程",
+        "rate" : 6.94,
+        "memo" : null
+    }
+];
+
+let calcTemplate = {
+    calType: 3,
+    calTypeName: "测试用_重庆",
+    compiledSeq: [],
+    calcItems: [
+        {
+            code: "1",
+            name: "基价直接工程费",
+            dispExpr: "A2+A5+A6+A10",
+            expression: "A('2') + A('5') + A('6') + A('10')",
+            compiledExpr: "",
+            statement: "基价人工费+基价材料费+基价机械费+未计价材料费"
+        },
+        {
+            code: "2",
+            name: "基价人工费",
+            dispExpr: "A3+A4",
+            expression: "A('3') + A('4')",
+            compiledExpr: "",
+            statement: "定额基价人工费+定额人工单价(基价)调整"
+        },
+        {
+            code: "3",
+            name: "定额基价人工费",
+            dispExpr: "定额基价人工费",
+            expression: "base('定额基价人工费').toFixed(2)",
+            compiledExpr: "",
+            statement: "定额基价人工费"
+        },
+        {
+            code: "4",
+            name: "定额人工单价(基价)调整",
+            dispExpr: "A3*[1.89-1]",
+            expression: "A('3') * (1.89-1)",
+            compiledExpr: "",
+            statement: "定额基价人工费*[定额人工单价(基价)调整系数-1]",
+            memo: "渝建发(2013)51"
+        },
+        {
+            code: "5",
+            name: "基价材料费",
+            dispExpr: "定额基价材料费",
+            expression: "base('定额基价材料费')",
+            compiledExpr: "",
+            statement: "定额基价材料费"
+        },
+        {
+            code: "6",
+            name: "基价机械费",
+            dispExpr: "A7+A9",
+            expression: "A('7') + A('9')",
+            compiledExpr: "",
+            statement: "定额基价机械费+定额机上人工单价(基价)调整"
+        },
+        {
+            code: "7",
+            name: "定额基价机械费",
+            dispExpr: "定额基价机械费",
+            expression: "base('定额基价机械费')",
+            compiledExpr: "",
+            statement: "定额基价机械费"
+        },
+        {
+            code: "8",
+            name: "其中:定额基价机上人工费",
+            dispExpr: "定额基价机上人工费",
+            expression: "base('定额基价机上人工费')",
+            compiledExpr: "",
+            statement: "定额基价机上人工费"
+        },
+        {
+            code: "9",
+            name: "定额机上人工单价(基价)调整",
+            dispExpr: "A8*[1.89-1]",
+            expression: "A('8') * (1.89-1)",
+            compiledExpr: "",
+            statement: "定额基价机上人工费*[定额机上人工单价(基价)调整系数-1]"
+        },
+        {
+            code: "10",
+            name: "未计价材料费",
+            dispExpr: "主材费+设备费",
+            expression: "base('主材费') + base('设备费')",
+            compiledExpr: "",
+            statement: "主材费+设备费"
+        },
+        {
+            code: "11",
+            name: "企业管理费",
+            dispExpr: "A3",
+            expression: "A('3')",
+            compiledExpr: "",
+            statement: "定额基价人工费",
+            memo: "渝建发[2014]27号"
+        },
+        {
+            code: "12",
+            name: "利润",
+            dispExpr: "A3",
+            expression: "A('3')",
+            compiledExpr: "",
+            statement: "定额基价人工费"
+        },
+        {
+            code: "13",
+            name: "风险因素",
+            dispExpr: "A3",
+            expression: "A('3')",
+            compiledExpr: "",
+            statement: "定额基价人工费",
+            memo: "同定额包干费"
+        },
+        {
+            code: "14",
+            name: "人材机价差",
+            dispExpr: "A15+A16+A17",
+            expression: "A('15') + A('16') + A('17')",
+            compiledExpr: "",
+            statement: "人工费价差+材料费价差+机械费价差"
+        },
+        {
+            code: "15",
+            name: "人工费价差",
+            dispExpr: "信息价或市场价格-调整后的定额人工费(基价)",
+            expression: "base('市场价格人工费') - base('定额基价人工费(调整后)')",
+            compiledExpr: "",
+            statement: "市场价格人工费-调整后的定额人工费(基价)"
+        },
+        {
+            code: "16",
+            name: "材料费价差",
+            dispExpr: "信息价或市场价格-定额基价材料费",
+            expression: "base('市场价格材料费') - base('定额基价材料费(调整后)')",
+            compiledExpr: "",
+            statement: "市场价格材料费-定额基价材料费"
+        },
+        {
+            code: "17",
+            name: "机械费价差",
+            dispExpr: "信息价或市场价格-调整后的定额基价机械费(基价)",
+            expression: "base('市场价格机械费') - base('定额基价机械费(调整后)')",
+            compiledExpr: "",
+            statement: "市场价格机械费-调整后的定额基价机械费(基价)"
+        },
+        {
+            code: "18",
+            name: "综合单价",
+            dispExpr: "A1+A11+A12+A13+A14",
+            expression: "A('1') + A('11') + A('12') + A('13') + A('14')",
+            compiledExpr: "",
+            statement: "基价直接工程费+企业管理费+利润+风险因素+人材机价差"
+        }
+    ]
+};
+
+class RationCalc {
+    constructor(project){
+        this.project = project;
+    };
+
+    calculate(ration){
+        let calc = new calculation();
+        calc.init(calcTemplate, calcFeeRate);
+        calc.compile();
+        ration.data.gljList = projectObj.project.ration_glj.getGljArrByRation(ration.data.ID);
+        calc.calculate(ration);
+    };
+}

+ 52 - 8
web/building_saas/main/js/models/ration_glj.js

@@ -41,6 +41,46 @@ var ration_glj = {
             this.datas = datas;
         };
 
+        ration_glj.prototype.getGljArrByRation = function (rationID) {
+            return this.datas.filter(function (data) {
+                return data.rationID === rationID;
+            })
+        };
+        ration_glj.prototype.getGatherGljArrByRations = function (rations) {
+            let result = [];
+            let clone = function (obj) {
+                if (obj === null) return null;
+
+                var o = Object.prototype.toString.apply(obj) === "[object Array]" ? [] : {};
+                for (var i in obj) {
+                    o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? clone(obj[i]) : obj[i]);
+                }
+                return o;
+            }
+            let findGlj = function (sourceGlj, gljArr) {
+                for (let glj of gljArr) {
+                    if (glj.projectGLJID === sourceGlj.projectGLJID) {
+                        return glj;
+                    }
+                }
+                return null;
+            }
+            for (let ration of rations) {
+                let rationGljs = this.getGljArrByRation(ration.ID);
+                for (let glj of rationGljs) {
+                    let sameGlj = findGlj(glj, result);
+                    if (!sameGlj) {
+                        sameGlj = clone(glj);
+                        sameGlj.quantity = (sameGlj.quantity * ration.quantity).toDecimal(4);
+                        result.push(sameGlj);
+                    } else {
+                        sameGlj.quantity = sameGlj.quantity + (glj.quantity * ration.quantity).toDecimal(4);
+                    }
+                }
+            }
+            return result;
+        }
+
         // 提交数据后返回数据处理
         ration_glj.prototype.doAfterUpdate = function(err, data){
             if(!err){
@@ -171,8 +211,10 @@ var ration_glj = {
         ration_glj.prototype.getDeleteDataByBills=function(datas){
             var updateData = [];
             datas.forEach(function (deleteData) {
-                var billData = deleteData.data;
-                updateData.push({'deleteType':'BILL','updateType': 'ut_delete', 'updateData': {'ID': billData.ID, 'projectID': billData.projectID}});
+                if(deleteData.type=='delete'){
+                    var billData = deleteData.data;
+                    updateData.push({'deleteType':'BILL','updateType': 'ut_delete', 'updateData': {'ID': billData.ID, 'projectID': billData.projectID}});
+                }
             })
             return updateData;
         };
@@ -189,20 +231,22 @@ var ration_glj = {
             var rationList = projectObj.project.Ration.datas;
             var deleteRationList = [];
             for(var i=0;i<deleteData.length;i++){
-                var billID = deleteData[i].data.ID;
-                var raList =_.filter(rationList,(ration)=>{
-                    return ration.billsItemID==billID;
-                });
-                deleteRationList = deleteRationList.concat(raList);
+                if(deleteData[i].type=='delete'){
+                    var billID = deleteData[i].data.ID;
+                    var raList =_.filter(rationList,(ration)=>{
+                        return ration.billsItemID==billID;
+                    });
+                    deleteRationList = deleteRationList.concat(raList);
+                }
             }
             for(var i=0;i<deleteRationList.length;i++){
                 this.deleteByRation(deleteRationList[i]);
                 projectObj.project.ration_coe.deleteByRation(deleteRationList[i]);
+                projectObj.project.quantity_detail.deleteByRation(deleteRationList[i]);
                 projectObj.project.Ration.datas.splice(projectObj.project.Ration.datas.indexOf(deleteRationList[i]), 1);
             }
         }
 
-
         ration_glj.prototype.updataOrdelete=function(row){
             var updateData = null;
             if(row.rationItemQuantity==0){

+ 93 - 0
web/building_saas/main/js/models/volume_price.js

@@ -0,0 +1,93 @@
+/**
+ * Created by Mai on 2017/7/25.
+ */
+
+var VolumePrice = {
+    createNew: function (project) {
+        let tools = {
+            owner: project
+        };
+
+        class volumePrice {
+            constructor () {
+                this.datas = [];
+
+                let maxID = 0;
+                this.getNewID = function () {
+                    return maxID += 1;
+                };
+                this.maxID = function (ID) {
+                    if (arguments.length === 0) {
+                        return maxID;
+                    } else {
+                        maxID = Math.max(ID, maxID);
+                    }
+                };
+                tools.owner.registerModule(ModuleNames.volume_price, this);
+            };
+
+            getProject () {
+                return tools.owner;
+            };
+            getSourceType () {
+                return ModuleNames.volume_price;
+            };
+
+            loadData (datas) {
+                this.datas = datas;
+            };
+            setMaxID (ID) {
+                this.maxID(ID);
+            }
+
+            getTempVolumePrice (newID, billsID, serialNo) {
+                var newData = {'ID': newID, 'serialNo': serialNo, projectID: tools.owner.ID()};
+                newData[project.masterField.volumePrice] = billsID;
+                return newData;
+            };
+            getBillsSortVolumePrice (billsID) {
+                var arr = this.datas.filter(function (data) {
+                    return data[tools.owner.masterField.volumePrice] === billsID;
+                });
+                arr.sort(function (x, y) {
+                    return x.serialNo - y.serialNo;
+                });
+                return arr;
+            };
+
+            getInsertVolumePriceData (billsID, pre) {
+                let bv = this.getBillsSortVolumePrice(billsID);
+                let updateData = [];
+                if (pre) {
+                    let preIndex = bv.indexOf(pre), i;
+                    updateData.push({updateType: 'ut_create', updateData: this.getTempVolumePrice(this.maxID() + 1, billsID, preIndex < bv.length - 1 ? bv[preIndex + 1].serialNo : bv[preIndex].serialNo + 1)});
+                    for (i = preIndex + 1; i < bv.length; i++) {
+                        updateData.push({updateType: 'ut_update', updateData: this.getTempVolumePrice(bv[i].ID, billsID, i < bv.length - 1 ? bv[i+1].serialNo : bv[i].serialNo + 1)});
+                    }
+                } else {
+                    updateData.push({updateType: 'ut_create', updateData: this.getTempVolumePrice(this.maxID() + 1, billsID, bv.length > 0 ? bv[bv.length - 1].serialNo + 1 : 1)});
+                }
+                return updateData;
+            };
+            insertVolumePrice (billsID, pre) {
+                tools.owner.pushNow('insertVolumePrice', [this.getSourceType()], [this.getInsertVolumePriceData(billsID, pre)]);
+
+                let bv = this.getBillsSortVolumePrice(billsID), newVP = null;
+                if (pre) {
+                    let preIndex = bv.indexOf(pre);
+                    newVP = this.getTempVolumePrice(this.getNewID(), billsID, preIndex < bv.length - 1 ? bv[preIndex + 1].serialNo : bv[preIndex].serialNo + 1);
+                    this.datas.push(newVP);
+                    for (let i = preIndex + 1; i < bv.length; i++) {
+                        bv[i].serialNo = i < bv.length - 1 ? bv [i + 1].serialNo : bv[i].serialNo + 1;
+                    }
+                } else {
+                    newVP = this.getTempVolumePrice(this.getNewID(), billsID, bv.length > 0 ? bv[bv.length - 1].serialNo + 1 : 1);
+                    this.datas.push(newVP);
+                }
+                return newVP;
+            }
+        }
+
+        return new volumePrice();
+    }
+}

+ 83 - 56
web/building_saas/main/js/views/glj_view.js

@@ -12,13 +12,15 @@ var gljOprObj = {
     coeSheet:null,
     assSheet:null,
     assSheetData:[],
+    detailSheet:null,
+    detailData:[],
     setting: {
         header: [
             {headerName: "编码", headerWidth: 100, dataCode: "code", dataType: "String", formatter: "@"},
             {headerName: "名称", headerWidth: 120, dataCode: "name", dataType: "String"},
             {headerName: "规格型号", headerWidth: 80, dataCode: "specs", dataType: "String", hAlign: "center"},
             {headerName: "单位", headerWidth: 60, dataCode: "unit", dataType: "String", hAlign: "center"},
-            {headerName: "类别", headerWidth: 50, dataCode: "gljDistType", dataType: "String", hAlign: "center"},
+            {headerName: "类别", headerWidth: 50, dataCode: "shortName", dataType: "String", hAlign: "center"},
             {headerName: "定额消耗量", headerWidth: 80, dataCode: "rationItemQuantity", dataType: "Number", hAlign: "right",formatter:"0.000",tofix:3},    // dataType: "Number", formatter: "0.00"
             {headerName: "自定义消耗量", headerWidth: 80, dataCode: "customQuantity", dataType: "Number", hAlign: "right",formatter:"0.000",tofix:3},
             {headerName: "消耗量", headerWidth: 80, dataCode: "quantity", dataType: "Number", hAlign: "right",formatter:"0.000",tofix:3},
@@ -55,7 +57,17 @@ var gljOprObj = {
             lockColumns:[0,1]
         }
     },
-
+    detailSetting:{
+        header:[
+            {headerName: "名称", headerWidth: 100, dataCode: "name", dataType: "String"},
+            {headerName: "计算式", headerWidth: 120, dataCode: "regex", dataType: "String"},
+            {headerName: "结果(C)", headerWidth: 120, dataCode: "result", dataType: "Number",formatter:"0.0000",tofix:4},
+            {headerName: "累加", headerWidth: 120, dataCode: "isSummation", dataType: "String",cellType:"checkBox"}
+        ],
+        view:{
+            lockColumns:[2,3]
+        }
+    },
     initSheet: function(sheet) {
         var me = this;
         me.sheet = sheet;
@@ -63,7 +75,7 @@ var gljOprObj = {
         sheet.name('ration_glj');
         me.bindSheetEvent(sheet);
         sheet.bind(GC.Spread.Sheets.Events.CellClick, me.onCellClick);
-        this.loadGLJSpreadContextMenu();
+        gljContextMenu.loadGLJSpreadContextMenu();
     },
     initCoeSheet:function (sheet) {
         var me = this;
@@ -81,6 +93,13 @@ var gljOprObj = {
         sheet.name('ration_ass');
         me.bindSheetEvent(sheet);
     },
+    initDetailSheet: function(sheet) {
+        var me = this;
+        me.detailSheet = sheet;
+        sheetCommonObj.initSheet(me.detailSheet, me.detailSetting, 30);
+        sheet.name('quantity_detail');
+        me.bindSheetEvent(sheet);
+    },
     showCoeData:function(sheet,setting,datas){
         sheet.floatingObjects.remove("customerCoe");
         sheetCommonObj.showData(sheet,setting,datas);
@@ -112,21 +131,40 @@ var gljOprObj = {
 
     onClipboardPasted: function(e, info) {
         var me = gljOprObj;
-        if (!me.ration) {return;};
+        console.log('past');
+      //  if (!me.ration) {return;};
         // your code...
     },
 
     onEditEnded: function(sender,args){
         var me = gljOprObj;
-        if(subSpread.getActiveSheetIndex()==0){
+        if(args.sheetName=='ration_glj'){
             me.onEditGLJSheet(args)
         }
-        if(subSpread.getActiveSheetIndex()==1){
+        if(args.sheetName=='ration_ass'){
             me.onEditAssSheet(args);
         }
+        if(args.sheetName=='quantity_detail'){
+            me.onEditDetailSheet(args);
+        }
+    },
+    onEditDetailSheet:function(args){
+        var me = gljOprObj;
+        if(args.row>me.detailData.length){
+            return;
+        }
+        if(args.row==me.detailData.length&&args.editingText==null){
+            return;
+        }
+       if(args.row==me.detailData.length){
+           projectObj.project.quantity_detail.saveQuantityDetail(args,me.detailSetting.header[args.col].dataCode);
+       }
+        if(args.row<me.detailData.length){
+            projectObj.project.quantity_detail.updateQuantityDetail(args,me.detailSetting.header[args.col].dataCode,me.detailData[args.row]);
+        }
 
     },
-    onEditGLJSheet(args){
+    onEditGLJSheet:function(args){
         var me = gljOprObj;
         if(args.row>=me.sheetData.length){
             me.sheet.getCell(args.row, args.col).value(null);
@@ -142,7 +180,7 @@ var gljOprObj = {
         }
         me.updateRationGLJ(args,updateFunction);
     },
-    onEditAssSheet(args){
+    onEditAssSheet:function(args){
         var me = gljOprObj;
         if(args.row>=me.assSheetData.length){
             me.assSheet.getCell(args.row, args.col).value(null);
@@ -159,21 +197,26 @@ var gljOprObj = {
         if (cellType instanceof GC.Spread.Sheets.CellTypes.Button) {
             me.onCusButtonClick(sender,args);
         }else {
-           me.onCoeCheckBoxClick(sender,args)
+           me.onCheckBoxClick(sender,args)
         }
     },
-    onCoeCheckBoxClick:function(sender,args){
-        if(subSpread.getActiveSheetIndex()==2){
-            var checkboxValue = gljOprObj.coeSheet.getCell(args.row, args.col).value();
-            var newval = 0;
-            if(checkboxValue){
-                newval = 0;
-                gljOprObj.coeSheet.getCell(args.row, args.col).value(newval);
-            }else {
-                newval=1
-                gljOprObj.coeSheet.getCell(args.row, args.col).value(newval);
-            }
+    onCheckBoxClick:function(sender,args){
+        if(args.sheetName=='ration_glj'){
+            return;
+        }
+        var checkboxValue = args.sheet.getCell(args.row, args.col).value();
+        var newval = 0;
+        if(checkboxValue){
+            newval = 0;
+            args.sheet.getCell(args.row, args.col).value(newval);
+        }else {
+            newval=1
+            args.sheet.getCell(args.row, args.col).value(newval);
+        }
+        if(args.sheetName=='ration_coe'){
             gljOprObj.updateRationCoe(args,newval)
+        }else {
+            projectObj.project.quantity_detail.isSummationUpdate(args,gljOprObj.detailData,newval);
         }
     },
     onCusButtonClick:function (sender,args){
@@ -197,7 +240,7 @@ var gljOprObj = {
             return;
         }
         if(me.setting.header[args.col].dataCode=='marketPriceAdjust'){//市场单价调整
-            var type = me.sheetData[args.row].gljDistType;
+            var type = me.sheetData[args.row].shortName;
             var index= _.indexOf(me.setting.notEditedType,type);
             if(index!=-1){
                 me.sheet.getCell(args.row, args.col, GC.Spread.Sheets.SheetArea.viewport).locked(true);
@@ -396,6 +439,7 @@ var gljOprObj = {
                this.showRationAssData(node);
                 isShow=true;
             }
+            this.showQuantityDetailData(node);
         }else {
             this.selectedNodeId=null;
         }
@@ -420,9 +464,9 @@ var gljOprObj = {
         this.sheetData=gljList;
     },
     showRationCoeData:function (node) {
-        let coeList = [];
-        let ration_coe= projectObj.project.ration_coe;
-        let ration = node.data;
+        var coeList = [];
+        var ration_coe= projectObj.project.ration_coe;
+        var ration = node.data;
         if(ration_coe.datas&&ration_coe.datas.length>0){
             coeList = _.filter(ration_coe.datas,{'projectID':ration.projectID,'rationID':ration.ID})
         }
@@ -430,17 +474,31 @@ var gljOprObj = {
         this.coeSheetData=coeList;
     },
     showRationAssData:function (node) {
-        let assList = node.data.rationAssList;
+        var assList = node.data.rationAssList;
         sheetCommonObj.showData(this.assSheet,this.assSetting,assList);
         this.assSheetData =assList;
     },
+    showQuantityDetailData:function (node) {
+        var details=[];
+        var quantity_detail =projectObj.project.quantity_detail;
+        if(node.sourceType==ModuleNames.ration){
+            details=_.filter(quantity_detail.datas,{'rationID':node.data.ID});
+        }else if(node.sourceType==ModuleNames.bills){
+            details=_.filter(quantity_detail.datas,{'billID':node.data.ID});
+        }
+        details=_.sortBy(details,'seq');
+        sheetCommonObj.showData(this.detailSheet,this.detailSetting,details);
+        this.detailData = details;
+    },
     clearSheetData:function () {
         sheetCommonObj.showData(this.sheet,this.setting,[]);
         sheetCommonObj.showData(this.coeSheet,this.coeSetting,[]);
         sheetCommonObj.showData(this.assSheet,this.assSetting,[]);
+      //  sheetCommonObj.showData(this.detailSheet,this.detailSetting,[]);
         this.sheetData = [];
         this.coeSheetData = [];
         this.assSheetData = [];
+        //this.detailData=[];
     },
  /*   lockRationGLJCell:function(){
         sheetCommonObj.lockCells(this.sheet,this.setting);
@@ -459,37 +517,6 @@ var gljOprObj = {
     lockRationGLJCell:function(){
         sheetCommonObj.lockCells(this.sheet,this.setting);
     },*/
-    loadGLJSpreadContextMenu: function () {
-        var project =projectObj.project, spread =subSpread;
-        var selectedRow =null;
-        $.contextMenu({
-            selector: '#subSpread',
-            build: function ($trigger, e) {
-                var target = SheetDataHelper.safeRightClickSelection($trigger, e, spread);
-                selectedRow = target.row;
-                //controller.setTreeSelected(controller.tree.items[target.row]);
-                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
-            },
-            items: {
-                "delete_glj": {
-                    name: '删除工料机',
-                    icon: 'fa-remove',
-                    disabled: function () {
-                        //var selected = project.mainTree.selected;
-                        var disable = true;
-                        if(subSpread.getActiveSheetIndex()==0&&gljOprObj.sheetData!=null&&gljOprObj.sheetData.length>0&&selectedRow<gljOprObj.sheetData.length){
-                            disable=false
-                        }
-                        return disable;
-                    },
-                    callback: function () {
-                        var deleteRow = gljOprObj.sheetData[selectedRow];
-                        projectObj.project.ration_glj.updataOrdelete(deleteRow);
-                    }
-                }
-            }
-        });
-    },
     updateRationGLJ:function (args,updateFunction) {
         if(!updateFunction){
             return

+ 130 - 0
web/building_saas/main/js/views/glj_view_contextMenu.js

@@ -0,0 +1,130 @@
+/**
+ * Created by chen on 2017/7/25.
+ */
+
+var gljContextMenu = {
+    selectedRow :null,
+    selectedCol:null,
+    clipboard:null,
+    loadGLJSpreadContextMenu: function () {
+        $.contextMenu({
+            selector: '#subSpread',
+            build: this.onbuild,
+            items: {
+                "delete_glj": {
+                    name: '删除工料机',
+                    icon: 'fa-remove',
+                    disabled: function () {
+                        var sheetData = gljOprObj.sheetData;
+                        var disable = true;
+                        if(subSpread.getActiveSheetIndex()==0&&sheetData!=null&&sheetData.length>0&&gljContextMenu.selectedRow<sheetData.length){
+                            disable=false
+                        }
+                        return disable;
+                    },
+                    callback: function () {
+                        var sheetData = gljOprObj.sheetData;
+                        var deleteRow = sheetData[gljContextMenu.selectedRow];
+                        projectObj.project.ration_glj.updataOrdelete(deleteRow);
+                    }
+                }
+            }
+        });
+    },
+    loadQuantityDetailMenu:function () {
+        $.contextMenu({
+            selector: '#subSpread',
+            build: this.onbuild,
+            items: {
+                "insert_detail": {
+                    name: '插入行',
+                    icon: 'fa-sign-in',
+                    disabled: function () {
+                        var sheetData = gljOprObj.detailData;
+                        return gljContextMenu.selectedRow>sheetData.length;
+                    },
+                    callback: function () {
+                        projectObj.project.quantity_detail.insertQuantityDetail(gljContextMenu.selectedRow);
+                    }
+                },
+                "delete_detail": {
+                    name: '删除行',
+                    icon: 'fa-remove',
+                    disabled: function () {
+                        var sheetData = gljOprObj.detailData;
+                        return gljContextMenu.selectedRow>sheetData.length-1;
+                    },
+                    callback:function () {
+                        projectObj.project.quantity_detail.deleteQuantityDetail(gljContextMenu.selectedRow);
+                    }
+                },
+                "move_up": {
+                    name: '上移',
+                    icon: 'fa-arrow-up',
+                    disabled: function () {
+                        var sheetData = gljOprObj.detailData;
+                        return gljContextMenu.selectedRow==0||gljContextMenu.selectedRow>sheetData.length-1;
+                    },
+                    callback: function () {
+                        projectObj.project.quantity_detail.moveUp(gljContextMenu.selectedRow);
+                    }
+                },
+                "move_down": {
+                    name: '下移',
+                    icon: 'fa-arrow-down',
+                    disabled: function () {
+                        var sheetData = gljOprObj.detailData;
+                        return gljContextMenu.selectedRow>sheetData.length-2;
+                    },
+                    callback: function () {
+                        projectObj.project.quantity_detail.moveDown(gljContextMenu.selectedRow);
+                    }
+                },
+                "copy": {
+                    name: '复制',
+                    icon: 'fa-files-o',
+                    disabled: function () {
+                        var sheet = subSpread.getActiveSheet();
+                        var sheetData = gljOprObj.detailData;
+                        var value = sheet.getCell(gljContextMenu.selectedRow,gljContextMenu.selectedCol).value();
+                        return gljContextMenu.selectedRow>sheetData.length-1||value==null;
+                    },
+                    callback: function () {
+                        gljContextMenu.clipboard={
+                          row:gljContextMenu.selectedRow,
+                          col:gljContextMenu.selectedCol
+                        };
+                    }
+                },
+                "paste": {
+                    name: '粘贴',
+                    icon: 'fa-clipboard',
+                    disabled: function () {
+                        var sheetData = gljOprObj.detailData;
+                        return gljContextMenu.selectedRow>sheetData.length||gljContextMenu.clipboard==null;
+                    },
+                    callback: function () {
+                        var sheet = subSpread.getActiveSheet();
+                        var c=gljContextMenu.clipboard;
+                        console.log(sheet.getCell(c.row,c.col).value());
+                        var args={
+                            'row':gljContextMenu.selectedRow,
+                            'col':gljContextMenu.selectedCol,
+                            'editingText':sheet.getCell(c.row,c.col).value()
+                        }
+                        gljOprObj.onEditDetailSheet(args);
+                    }
+                }
+            }
+        });
+
+    },
+    onbuild:function ($trigger, e) {
+        var target = SheetDataHelper.safeRightClickSelection($trigger, e, subSpread);
+        gljContextMenu.selectedRow = target.row;
+        gljContextMenu.selectedCol = target.col;
+        //controller.setTreeSelected(controller.tree.items[target.row]);
+        return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+    }
+
+}

+ 40 - 0
web/building_saas/main/js/views/main_tree_col.js

@@ -0,0 +1,40 @@
+/**
+ * Created by Mai on 2017/7/25.
+ */
+
+let MainTreeCol = {
+    getText: {
+        type: function (node) {
+            if (node.sourceType === projectObj.project.Bills.getSourceType()) {
+                return '';
+            } else if (node.sourceType === projectObj.project.Ration.getSourceType()) {
+                return '定';
+            } else if (node.sourceType === projectObj.project.VolumePrice.getSourceType()) {
+                return '量';
+            } else if (node.sourceType === projectObj.project.ration_glj.getSourceType()) {
+                return '主';
+            }
+        }
+    },
+    readOnly: {
+        volumePrice: function (node) {
+            return node.sourceType === projectObj.project.VolumePrice.getSourceType();
+        }
+    },
+    getEvent: function (eventName) {
+        let names = eventName.split('.');
+        let event = this;
+        for (let name of names) {
+            if (event[name]) {
+                event = event[name];
+            } else {
+                return null;
+            }
+        }
+        if (event && Object.prototype.toString.apply(event) !== "[object Function]") {
+            return null;
+        } else {
+            return event;
+        }
+    }
+}

+ 79 - 18
web/building_saas/main/js/views/project_view.js

@@ -25,6 +25,12 @@ var projectObj = {
             if (!err) {
                 BillsGridSetting.cols.forEach(function (col) {
                     col.data.splitFields = col.data.field.split('.');
+                    if (col.data.getText && Object.prototype.toString.apply(col.data.getText) === "[object String]") {
+                        col.data.getText = MainTreeCol.getEvent(col.data.getText);
+                    }
+                    if (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object String]") {
+                        col.readOnly = MainTreeCol.getEvent(col.readOnly);
+                    }
                 });
                 that.mainController = TREE_SHEET_CONTROLLER.createNew(that.project.mainTree, that.mainSpread.getActiveSheet(), BillsGridSetting);
                 that.mainController.showTreeData();
@@ -36,11 +42,34 @@ var projectObj = {
                             btn.addClass('disabled');
                         }
                     };
-                    setButtonValid(tree.selected && tree.selected.canUpLevel(), $('#upLevel'));
-                    setButtonValid(tree.selected && tree.selected.canDownLevel(), $('#downLevel'));
-                    setButtonValid(tree.selected && tree.selected.canUpMove(), $('#upMove'));
-                    setButtonValid(tree.selected && tree.selected.canDownMove(), $('#downMove'));
-                    setButtonValid(tree.selected ? true : false, $('#delete'));
+                    let selected = tree.selected;
+                    let canUpLevel = function (node) {
+                        if (selected && selected.depth() > 0 && selected.canUpLevel()) {
+                            if (selected.sourceType === that.project.Bills.getSourceType()) {
+                                return (!selected.nextSibling) || (selected.children.length === 0) || (selected.source.children.length > 0);
+                            } else {
+                                return false;
+                            }
+                        } else {
+                            return false;
+                        }
+                    };
+                    let canDownLevel = function (node) {
+                        if (selected && selected.depth() > 0 && selected.canDownLevel()) {
+                            if (selected.sourceType === that.project.Bills.getSourceType()) {
+                                return (selected.preSibling.children.length === 0) || (selected.preSibling.source.children.length > 0);
+                            } else {
+                                return false;
+                            }
+                        } else {
+                            return false;
+                        }
+                    };
+                    setButtonValid(canUpLevel(selected), $('#upLevel'));
+                    setButtonValid(canDownLevel(selected), $('#downLevel'));
+                    setButtonValid(selected && (selected.depth() > 0) && selected.canUpMove(), $('#upMove'));
+                    setButtonValid(selected && (selected.depth() > 0) && selected.canDownMove(), $('#downMove'));
+                    setButtonValid(selected, $('#delete'));
                 });
 
               /*  if(!projectObj.gljSpreed){
@@ -51,6 +80,15 @@ var projectObj = {
 
                 that.mainController.bind(TREE_SHEET_CONTROLLER.eventName.treeSelectedChanged, function (node) {
                     gljOprObj.showDataIfRationSelect(node);
+
+                    // CSL.2017.07.25
+                    if(node.sourceType=="ration" && SubActiveSheetNameIs('JSCX')){
+                        rationCalcView.showData(node);
+                    }else{
+                        if (SubActiveSheetNameIs('JSCX')) {
+                            rationCalcView.clearData();
+                        }
+                    }
                 });
 
                 that.loadMainSpreadContextMenu();
@@ -89,7 +127,13 @@ var projectObj = {
                             if (selected.sourceType === project.Ration.getSourceType()) {
                                 return false;
                             } else if (selected.sourceType === project.Bills.getSourceType()) {
-                                return selected.source.children.length !== 0;
+                                if (selected.source.children.length === 0) {
+                                    return selected.children.length !== 0 ? selected.firstChild().sourceType !== project.Ration.getSourceType() : false;
+                                } else {
+                                    return true;
+                                }
+                            } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+                                return true;
                             };
                         } else {
                             return true;
@@ -106,13 +150,22 @@ var projectObj = {
                         var selected = project.mainTree.selected;
                         if (selected) {
                             if (selected.sourceType === project.Ration.getSourceType()) {
-                                return false;
+                                return true;
                             } else if (selected.sourceType === project.Bills.getSourceType()) {
-                                return selected.source.children.length !== 0;
+                                if (selected.source.children.length === 0) {
+                                    return selected.children.length !== 0 ? selected.firstChild().sourceType !== project.VolumePrice.getSourceType() : false;
+                                } else {
+                                    return true;
+                                }
+                            } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+                                return false;
                             };
                         } else {
                             return true;
                         }
+                    },
+                    callback: function (key, opt) {
+                        ProjectController.addVolumePrice(project, controller);
                     }
                 },
                 "spr1": '--------',
@@ -171,6 +224,8 @@ $('#insert').click(function () {
         ProjectController.addBills(project, controller);
     } else if (selected.sourceType === project.Ration.getSourceType()) {
         ProjectController.addRation(project, controller);
+    } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+        ProjectController.addVolumePrice(project, controller);
     }
 });
 $('#delete').click(function () {
@@ -185,6 +240,10 @@ $('#delete').click(function () {
             project.Ration.delete(selected.source);
             project.ration_glj.deleteByRation(selected.source);
             project.ration_coe.deleteByRation(selected.source);
+            project.quantity_detail.deleteByRation(selected.source);
+            controller.delete();
+        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+            project.VolumePrice.delete(selected.source);
             controller.delete();
         };
     }
@@ -216,21 +275,23 @@ $('#upMove').click(function () {
     } else if (selected.sourceType === project.Ration.getSourceType()) {
         project.Ration.changePos(selected.source, selected.preSibling.source);
         controller.upMove();
-    }
-    if (selected) {
+    } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+        project.VolumePrice.changePos(selected.source, selected.preSibling.source);
+        controller.upMove();
     }
 });
 $('#downMove').click(function () {
     var controller = projectObj.mainController, project = projectObj.project;
     var selected = controller.tree.selected, next, nextSerialNo;
 
-    if (selected) {
-        if (selected.sourceType === project.Bills.getSourceType()) {
-            project.Bills.downMoveBills(selected.source);
-            controller.downMove();
-        } else if (selected.sourceType === project.Ration.getSourceType()) {
-            project.Ration.changePos(selected.source, selected.nextSibling.source);
-            controller.downMove();
-        }
+    if (selected.sourceType === project.Bills.getSourceType()) {
+        project.Bills.downMoveBills(selected.source);
+        controller.downMove();
+    } else if (selected.sourceType === project.Ration.getSourceType()) {
+        project.Ration.changePos(selected.source, selected.nextSibling.source);
+        controller.downMove();
+    } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+        project.VolumePrice.changePos(selected.source, selected.nextSibling.source);
+        controller.downMove();
     }
 });

+ 92 - 17
web/building_saas/main/js/views/ration_calc_view.js

@@ -1,31 +1,106 @@
 /**
  * Created by CSL on 2017-07-17.
  */
+//for test AAAAAAAAAAAAA
+let calcRation = {
+    "sectionId" : 76,
+    "ID" : 15,
+    "code" : "AA0001",
+    "name" : "人工挖土方",
+    "unit" : "100m3",
+    "basePrice" : 840.84,
+    "caption" : "人工挖土方",
+    "feeType" : 2,
+    "rationGljList" : [
+        {
+            "glj" : {
+                "repositoryId" : 3,
+                "ID" : 17,
+                "code" : "00010201",
+                "name" : "土石方综合工日",
+                "specs" : null,
+                "unit" : "工日",
+                "basePrice" : 22,
+                "gljDistType" : "人工",
+                "gljType" : 2
+            },
+            "consumeAmt" : 38.22,
+            "proportion" : 0
+        },{
+            "glj": {
+                "repositoryId" : 3,
+                "ID" : 68,
+                "code" : "85030207",
+                "name" : "履带式起重机",
+                "specs" : "50t",
+                "unit" : "台班",
+                "basePrice" : 1194.05,
+                "gljDistType" : "机械",
+                "gljType" : 64
+            },
+            "consumeAmt" : 1.22,
+            "proportion" : 0
+        },{
+            "glj": {
+                "repositoryId" : 3,
+                "ID" : 200,
+                "code" : "36290101",
+                "name" : "水",
+                "specs" : "",
+                "unit" : "m3",
+                "basePrice" : 2,
+                "gljDistType" : "材料",
+                "gljType" : 6
+            },
+            "consumeAmt" : 9.2,
+            "proportion" : 0
+        }
+    ],
+    "rationRepId" : 3
+};
 
-var rationCalcOpr = {
+let rationCalcView = {
     sheet: null,
-    libID: null,
     ration: null,
+
     setting: {
-        header:[
-            {headerName:"费用代号",headerWidth:80, dataCode:"code", dataType: "String", hAlign: "center"},
-            {headerName:"费用名称",headerWidth:180, dataCode:"name", dataType: "String"},
-            {headerName:"计算基数",headerWidth:180, dataCode:"dispExpr", dataType: "String"},
-            {headerName:"基数说明",headerWidth:180, dataCode:"dispExprMemo", dataType: "String"},
-            {headerName:"费率",headerWidth:80, dataCode:"feeRate", dataType: "Number"},   // precision: 3
-            {headerName:"单价",headerWidth:100, dataCode:"unitFee", dataType: "Number"},
-            {headerName:"合价",headerWidth:100,dataCode:"totalFee", dataType: "Number"},
-            {headerName:"备注",headerWidth:120, dataCode:"memo", dataType: "String"}
+        header: [
+            {headerName: "费用代号", headerWidth: 75, dataCode: "code", dataType: "String", formatter: "A"+"0", hAlign: "center"},
+            {headerName: "费用名称", headerWidth: 200, dataCode: "name", dataType: "String"},
+            {headerName: "计算基数", headerWidth: 180, dataCode: "dispExpr", dataType: "String"},
+            {headerName: "基数说明", headerWidth: 220, dataCode: "statement", dataType: "String"},
+            {headerName: "费率", headerWidth: 80, dataCode: "feeRate", dataType: "Number"},   // precision: 3
+            {headerName: "单价", headerWidth: 100, dataCode: "unitFee", dataType: "Number"},  // execRst
+            {headerName: "合价", headerWidth: 100, dataCode: "totalFee", dataType: "Number"},
+            {headerName: "备注", headerWidth: 120, dataCode: "memo", dataType: "String"}
         ],
-        view:{
-            comboBox:[],
-            lockColumns:[1,2,3,4,5,6,7,8]
+        view: {
+            comboBox: [],
+            lockColumns: [0, 1, 2, 3, 5, 6, 7]
         }
     },
 
-    initSheet: function(sheet) {
+    initSheet: function (sheet) {
         var me = this;
         me.sheet = sheet;
-        sheetCommonObj.initSheet(me.sheet, me.setting, 20);
+        sheetCommonObj.initSheet(me.sheet, me.setting, 20);     // AAAAAAAA
+    },
+
+    showData: function (ration) {
+        var me = this;
+        me.ration = ration;  // AAAAAAA
+        //me.ration = calcRation;
+        let rationCalc = new RationCalc(projectObj.project);
+        rationCalc.calculate(me.ration);
+
+        me.datas = calcTemplate.calcItems;   // AAAAAAAAA
+        sheetCommonObj.showData(me.sheet, me.setting, me.datas);
+    },
+
+    clearData: function (){
+        var me = this;
+        me.ration = null;
+        sheetCommonObj.cleanSheet(me.sheet, me.setting, -1);
     }
-}
+
+}

+ 30 - 7
web/building_saas/main/js/views/sub_view.js

@@ -4,6 +4,7 @@
 
 // Tab panes 下有多个Spread时,相互之间不能正确显示。改成一个Spread下多个Sheet。
 var subSpread = sheetCommonObj.createSpread($("#subSpread")[0], 7);
+subSpread.getSheet(4).name('JSCX');
 
 
 // 工料机
@@ -15,39 +16,56 @@ SheetDataHelper.protectdSheet(subSpread.getSheet(0));
 //附注条件
 gljOprObj.initCoeSheet(subSpread.getSheet(2));
 SheetDataHelper.protectdSheet(subSpread.getSheet(2));
-//辅助定额
 
+//辅助定额
 gljOprObj.initAssSheet(subSpread.getSheet(1));
 SheetDataHelper.protectdSheet(subSpread.getSheet(1));
+
+//工程量明细
+gljOprObj.initDetailSheet(subSpread.getSheet(3));
+SheetDataHelper.protectdSheet(subSpread.getSheet(3));
+
+
 $("#linkGLJ").click(function(){
     subSpread.setActiveSheetIndex(0);
-
+    $.contextMenu( 'destroy', "#subSpread" );
+    gljContextMenu.loadGLJSpreadContextMenu();
     // for test
     //subSpread.getActiveSheet().setValue(0, 0, "工料机");
 });
 
 $("#linkFZDE").click(function(){
     subSpread.setActiveSheetIndex(1);
+    $.contextMenu( 'destroy', "#subSpread" );
     // for test
   //  subSpread.getActiveSheet().setValue(0, 0, "辅助定额");
 });
 
 $("#linkFZTJ").click(function(){
     subSpread.setActiveSheetIndex(2);
+    $.contextMenu( 'destroy', "#subSpread" );
     // for test
     //subSpread.getActiveSheet().setValue(0, 0, "附注条件");
 });
 
 $("#linkGCLMX").click(function(){
     subSpread.setActiveSheetIndex(3);
+    $.contextMenu( 'destroy', "#subSpread" );
+    gljContextMenu.loadQuantityDetailMenu();
     // for test
-    subSpread.getActiveSheet().setValue(0, 0, "工程量明细");
+    //subSpread.getActiveSheet().setValue(0, 0, "工程量明细");
 });
 
-$("#linkJSCX").click(function(){
+$("#linkJSCX").click(function(){        // 计算程序
     subSpread.setActiveSheetIndex(4);
-    rationCalcOpr.initSheet(subSpread.getSheet(4));
-    subSpread.getActiveSheet().setValue(0, 0, "计算程序");
+    rationCalcView.initSheet(subSpread.getSheet(4));
+    let sel = projectObj.mainController.tree.selected;
+    if (sel.sourceType == 'ration'){
+        rationCalcView.showData(sel);
+    }
+    else{
+        rationCalcView.clearData();
+    };
 });
 
 $("#linkFXSM").click(function(){
@@ -60,4 +78,9 @@ $("#linkDESM").click(function(){
     subSpread.setActiveSheetIndex(6);
     // for test
     subSpread.getActiveSheet().setValue(0, 0, "定额说明");
-});
+});
+
+function SubActiveSheetNameIs(sheetName){
+    let rst = subSpread.getActiveSheet().name() == sheetName;
+    return rst;
+}