Added some icons from Brewersfriend. They should be replaced someday. Added maximum mash weight setting to the equipment database. Usefull for brew automate and RIMS systems. During recipes import acid and base additions are translated. Brews and recipes now have 2 water sources. Added water mixer. Added basic water treatment, but not for pH yet. Redesigned the fermentables and water tabs.

Sun, 23 Dec 2018 20:13:36 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Sun, 23 Dec 2018 20:13:36 +0100
changeset 149
ff45488d480e
parent 148
c0f0bbfefd63
child 150
159d7a89fcef

Added some icons from Brewersfriend. They should be replaced someday. Added maximum mash weight setting to the equipment database. Usefull for brew automate and RIMS systems. During recipes import acid and base additions are translated. Brews and recipes now have 2 water sources. Added water mixer. Added basic water treatment, but not for pH yet. Redesigned the fermentables and water tabs.

www/images/checkmark_range_high.gif file | annotate | diff | comparison | revisions
www/images/checkmark_range_low.gif file | annotate | diff | comparison | revisions
www/images/checkmark_range_normal.gif file | annotate | diff | comparison | revisions
www/import/from_brouwhulp.php file | annotate | diff | comparison | revisions
www/includes/db_inventory_equipments.php file | annotate | diff | comparison | revisions
www/includes/db_product.php file | annotate | diff | comparison | revisions
www/inv_equipments.php file | annotate | diff | comparison | revisions
www/js/global.js file | annotate | diff | comparison | revisions
www/js/inv_equipments.js file | annotate | diff | comparison | revisions
www/js/prod_edit.js file | annotate | diff | comparison | revisions
www/js/rec_edit.js file | annotate | diff | comparison | revisions
www/prod_edit.php file | annotate | diff | comparison | revisions
www/rec_edit.php file | annotate | diff | comparison | revisions
Binary file www/images/checkmark_range_high.gif has changed
Binary file www/images/checkmark_range_low.gif has changed
Binary file www/images/checkmark_range_normal.gif has changed
--- a/www/import/from_brouwhulp.php	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/import/from_brouwhulp.php	Sun Dec 23 20:13:36 2018 +0100
@@ -323,6 +323,7 @@
 		$sql .= "', kettle_volume='" . floatval($equipment->KETTLE_VOLUME);
 		$sql .= "', kettle_height='" . floatval($equipment->KETTLE_HEIGHT);
 		$sql .= "', mash_volume='" . floatval($equipment->MASH_VOLUME);
+		$sql .= "', mash_max='" . round((floatval($equipment->MASH_VOLUME) / 3) * 10) / 10;	// Not in beerxml/brouwhulp. For RIMS systems.
 		$sql .= "', efficiency='" . floatval($equipment->EFFICIENCY);
 		$sql .= "';";
 		if (! $result = mysqli_query($db, $sql)) {
@@ -854,8 +855,14 @@
 		if ($recipe->TARGET_PH) {
 			$sql .= "', mash_ph='" . floatval($recipe->TARGET_PH);
 		}
-		if ($recipe->SPARGE_ACID_TYPE) {
-			$sql .= "', sparge_acid_type='" . mysqli_real_escape_string($db, $recipe->SPARGE_ACID_TYPE);
+		if ($recipe->SPARGE_ACID_TYPE && ($recipe->SPARGE_ACID_TYPE == "Lactic")) {
+			$sql .= "', sparge_acid_type='Melkzuur";
+		} else if ($recipe->SPARGE_ACID_TYPE && ($recipe->SPARGE_ACID_TYPE == "Hydrochloric")) {
+			$sql .= "', sparge_acid_type='Zoutzuur";
+		} else if ($recipe->SPARGE_ACID_TYPE && ($recipe->SPARGE_ACID_TYPE == "Phosphoric")) {
+			$sql .= "', sparge_acid_type='Fosforzuur";
+		} else if ($recipe->SPARGE_ACID_TYPE && ($recipe->SPARGE_ACID_TYPE == "Sulfuric")) {
+			$sql .= "', sparge_acid_type='Zwavelzuur";
 		}
 		if ($recipe->ACID_SPARGE_PERC) {
 			$sql .= "', sparge_acid_perc='" . floatval($recipe->ACID_SPARGE_PERC);
@@ -1043,8 +1050,14 @@
 		if ($recipe->TARGET_PH) {
 			 $rsql .= "', mash_ph='" . floatval($recipe->TARGET_PH);
 		}
-		if ($recipe->SPARGE_ACID_TYPE) {
-			$rsql .= "', sparge_acid_type='" . mysqli_real_escape_string($db, $recipe->SPARGE_ACID_TYPE);
+		if ($recipe->SPARGE_ACID_TYPE && ($recipe->SPARGE_ACID_TYPE == "Lactic")) {
+			$rsql .= "', sparge_acid_type='Melkzuur";
+		} else if ($recipe->SPARGE_ACID_TYPE && ($recipe->SPARGE_ACID_TYPE == "Hydrochloric")) {
+			$rsql .= "', sparge_acid_type='Zoutzuur";
+		} else if ($recipe->SPARGE_ACID_TYPE && ($recipe->SPARGE_ACID_TYPE == "Phosphoric")) {
+			$rsql .= "', sparge_acid_type='Fosforzuur";
+		} else if ($recipe->SPARGE_ACID_TYPE && ($recipe->SPARGE_ACID_TYPE == "Sulfuric")) {
+			$rsql .= "', sparge_acid_type='Zwavelzuur";
 		}
 		if ($recipe->ACID_SPARGE_PERC) {
 			$rsql .= "', sparge_acid_perc='" . floatval($recipe->ACID_SPARGE_PERC);
@@ -1219,6 +1232,7 @@
 			$psql .= "', eq_kettle_volume='" . floatval($recipe->EQUIPMENT->KETTLE_VOLUME);
 			$psql .= "', eq_kettle_height='" . floatval($recipe->EQUIPMENT->KETTLE_HEIGHT);
 			$psql .= "', eq_mash_volume='" . floatval($recipe->EQUIPMENT->MASH_VOLUME);
+			$psql .= "', eq_mash_max='" . round((floatval($recipe->EQUIPMENT->MASH_VOLUME) / 3) * 10) / 10;     // Not in beerxml/brouwhulp. For RIMS systems.
 			$psql .= "', eq_efficiency='" . floatval($recipe->EQUIPMENT->EFFICIENCY);
 		}
 
--- a/www/includes/db_inventory_equipments.php	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/includes/db_inventory_equipments.php	Sun Dec 23 20:13:36 2018 +0100
@@ -42,6 +42,7 @@
 	$sql .= "', kettle_volume='" . $_GET['kettle_volume'];
 	$sql .= "', kettle_height='" . $_GET['kettle_height'] / 100.0;
 	$sql .= "', mash_volume='" . $_GET['mash_volume'];
+	$sql .= "', mash_max='" . $_GET['mash_max'];
 	$sql .= "', efficiency='" . $_GET['efficiency'];
 	if (isset($_GET['insert'])) {
 		$sql .= "';";
@@ -102,6 +103,7 @@
 			'kettle_volume' => $row['kettle_volume'],
 			'kettle_height' => $row['kettle_height'] * 100.0,
 			'mash_volume' => $row['mash_volume'],
+			'mash_max' => $row['mash_max'],
 			'efficiency' => $row['efficiency']
 		);
 	}
--- a/www/includes/db_product.php	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/includes/db_product.php	Sun Dec 23 20:13:36 2018 +0100
@@ -54,8 +54,70 @@
 	$sql .= "', eq_kettle_volume='" . $_POST['eq_kettle_volume'];
 	$sql .= "', eq_kettle_height='" . $_POST['eq_kettle_height'];
 	$sql .= "', eq_mash_volume='" . $_POST['eq_mash_volume'];
+	$sql .= "', eq_mash_max='" . $_POST['eq_mash_max'];
 	$sql .= "', eq_efficiency='" . $_POST['eq_efficiency'];
 
+	// brew_date_start
+	// brew_mash_ph
+	// brew_mash_sg
+	// brew_sparge_temperature
+	// brew_sparge_volume
+	// brew_sparge_ph
+	// brew_preboil_volume
+	// brew_preboil_sg
+	// brew_preboil_ph
+	// brew_aboil_volume
+	// brew_aboil_sg
+	// brew_aboil_ph
+	// brew_aboil_efficiency
+	// brew_cooling_method
+	// brew_cooling_time
+	// brew_cooling_to
+	// brew_whirlpool9
+	// brew_whirlpool7
+	// brew_whirlpool6
+	// brew_whirlpool2
+	// brew_fermenter_volume
+	// brew_fermenter_extrawater
+	// brew_aeration_time
+	// brew_aeration_speed
+	// brew_aeration_type
+	// brew_fermenter_sg
+	// brew_fermenter_ibu
+	// brew_date_end
+	// brew_log_available
+	// primary_start_temp
+	// primary_max_temp
+	// primary_end_temp
+	// primary_end_sg
+	// primary_end_date
+	// secondary_temp
+	// secondary_end_date
+	// tertiary_temp
+	// package_date
+	// bottle_amount
+	// bottle_carbonation
+	// bottle_priming_sugar
+	// bottle_priming_amount
+	// bottle_carbonation_temp
+	// keg_amount
+	// keg_carbonation
+	// keg_priming_sugar
+	// keg_priming_amount
+	// keg_carbonation_temp
+	// keg_forced_carb
+	// keg_pressure
+	// keg_priming_factor
+	// taste_notes
+	// taste_rate
+	// taste_date
+	// taste_color
+	// taste_transparency
+	// taste_head
+	// taste_aroma
+	// taste_taste
+	// taste_mouthfeel
+	// taste_aftertaste
 	syslog(LOG_NOTICE, $sql);
 	if (isset($_POST['insert'])) {
 		$sql .= "';";
@@ -172,6 +234,7 @@
 		$brew .= ',"eq_kettle_volume":' . floatval($row['eq_kettle_volume']);
 		$brew .= ',"eq_kettle_height":' . floatval($row['eq_kettle_height']);
 		$brew .= ',"eq_mash_volume":' . floatval($row['eq_mash_volume']);
+		$brew .= ',"eq_mash_max":' . floatval($row['eq_mash_max']);
 		$brew .= ',"eq_efficiency":' . floatval($row['eq_efficiency']);
 		$brew .= ',"eq_top_up_water":' . floatval($row['eq_top_up_water']);
 		$brew .= ',"brew_date_start":"' . $row['brew_date_start'];
@@ -267,14 +330,39 @@
 		$brew .= ',"color_method":"' . $row['color_method'];
 		$brew .= '","est_ibu":' . floatval($row['est_ibu']);
 		$brew .= ',"ibu_method":"' . $row['ibu_method'];
-		$brew .= '","mash_sparge_temp":' . $row['mash_sparge_temp'];
+		$brew .= '","sparge_temp":' . floatval($row['sparge_temp']);
+		$brew .= ',"sparge_ph":' . floatval($row['sparge_ph']);
+		$brew .= ',"sparge_volume":' . floatval($row['sparge_volume']);
+		$brew .= ',"sparge_acid_type":"' . $row['sparge_acid_type'];
+		$brew .= '","sparge_acid_perc":' . floatval($row['sparge_acid_perc']);
+		$brew .= ',"sparge_acid_amount":' . floatval($row['sparge_acid_amount']);
 		$brew .= ',"mash_ph":' . $row['mash_ph'];
 		$brew .= ',"mash_name":"' . $row['mash_name'];
-		$brew .= '","fermentables":' . $row['json_fermentables'];
+		$brew .= '","calc_acid":' . $row['calc_acid'];
+		$brew .= ',"w1_name":"' . str_replace($escapers, $replacements, $row['w1_name']);
+		$brew .= '","w1_amount":' . $row['w1_amount'];
+		$brew .= ',"w1_calcium":' . $row['w1_calcium'];
+		$brew .= ',"w1_sulfate":' . $row['w1_sulfate'];
+		$brew .= ',"w1_chloride":' . $row['w1_chloride'];
+		$brew .= ',"w1_sodium":' . $row['w1_sodium'];
+		$brew .= ',"w1_magnesium":' . $row['w1_magnesium'];
+		$brew .= ',"w1_total_alkalinity":' . $row['w1_total_alkalinity'];
+		$brew .= ',"w1_ph":' . $row['w1_ph'];
+		$brew .= ',"w1_cost":' . $row['w1_cost'];
+		$brew .= ',"w2_name":"' . str_replace($escapers, $replacements, $row['w2_name']);
+		$brew .= '","w2_amount":' . $row['w2_amount'];
+		$brew .= ',"w2_calcium":' . $row['w2_calcium'];
+		$brew .= ',"w2_sulfate":' . $row['w2_sulfate'];
+		$brew .= ',"w2_chloride":' . $row['w2_chloride'];
+		$brew .= ',"w2_sodium":' . $row['w2_sodium'];
+		$brew .= ',"w2_magnesium":' . $row['w2_magnesium'];
+		$brew .= ',"w2_total_alkalinity":' . $row['w2_total_alkalinity'];
+		$brew .= ',"w2_ph":' . $row['w2_ph'];
+		$brew .= ',"w2_cost":' . $row['w2_cost'];
+		$brew .= ',"fermentables":' . $row['json_fermentables'];
 		$brew .= ',"hops":' . $row['json_hops'];
 		$brew .= ',"miscs":' . $row['json_miscs'];
 		$brew .= ',"yeasts":' . $row['json_yeasts'];
-		$brew .= ',"waters":' . $row['json_waters'];
 		$brew .= ',"mashs":' . $row['json_mashs'];
 		$brew .= '}';
 		$brews .= $brew;
--- a/www/inv_equipments.php	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/inv_equipments.php	Sun Dec 23 20:13:36 2018 +0100
@@ -65,32 +65,37 @@
        <td><div id="top_up_kettle"></div></td>
       </tr>
       <tr>
-       <th style="text-align: center;" colspan="2">Filteren</th>
+       <td style="vertical-align: top; float: right;">Maximum moutstort kg:</td>
+       <td><div id="mash_max"></div></td>
        <td style="vertical-align: top; float: right;">Hopfactor %:</td>
        <td><div id="hop_utilization"></div></td>
       </tr>
       <tr>
+       <th style="text-align: center;" colspan="2">Filteren</th>
+       <td style="vertical-align: top; float: right;">Volume eind koken l:</td>
+       <td><div id="batch_size"></div></td>
+      </tr>
+      <tr>
        <td style="vertical-align: top; float: right;">Filter volume l:</td>
        <td><div id="lauter_volume"></div></td>
-       <td style="vertical-align: top; float: right;">Volume eind koken l:</td>
-       <td><div id="batch_size"></div></td>
+       <th style="text-align: center;" colspan="2">Koelen</th>
       </tr>
       <tr>
        <td style="vertical-align: top; float: right;">Filterkuip hoogte cm:</td>
        <td><div id="lauter_height"></div></td>
-       <th style="text-align: center;" colspan="2">Koelen</th>
+       <td style="vertical-align: top; float: right;">Trub verlies kookketel l:</td>
+       <td><div id="trub_chiller_loss"></div></td>
       </tr>
       <tr>
        <td style="vertical-align: top; float: right;">Filterkuip verlies l:</td>
        <td><div id="lauter_deadspace"></div></td>
-       <td style="vertical-align: top; float: right;">Trub verlies kookketel l:</td>
-       <td><div id="trub_chiller_loss"></div></td>
+       <td style="vertical-align: top; float: right;">Extra water in het gistvat l:</td>
+       <td><div id="top_up_water"></div></td>
       </tr>
       <tr>
        <td style="vertical-align: top; float: right;">Brouwzaalrendement %:</td>
        <td><div id="efficiency"></div></td>
-       <td style="vertical-align: top; float: right;">Extra water in het gistvat l:</td>
-       <td><div id="top_up_water"></div></td>
+       <td colspan="2"></td>
       </tr>
       <tr>
        <td style="padding-top: 10px; float: right;"><input type="button" id="Delete" value="Delete" /></td>
--- a/www/js/global.js	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/js/global.js	Sun Dec 23 20:13:36 2018 +0100
@@ -242,6 +242,26 @@
 	},
 });
 
+// dropdownlist datasource from profile_water
+var waterProfileSource = {
+	datatype: "json",
+	datafields: [
+		{ name: 'record', type: 'number' },
+		{ name: 'name', type: 'string' },
+		{ name: 'calcium', type: 'float' },
+		{ name: 'bicarbonate', type: 'float' },
+		{ name: 'sulfate', type: 'float' },
+		{ name: 'chloride', type: 'float' },
+		{ name: 'sodium', type: 'float' },
+		{ name: 'magnesium', type: 'float' },
+		{ name: 'ph', type: 'float' },
+		{ name: 'total_alkalinity', type: 'float' },
+	],
+	url: "includes/db_profile_water.php",
+	async: true
+};
+var waterprofiles = new $.jqx.dataAdapter(waterProfileSource);
+
 // dropdownlist datasource from profile_mash
 var mashInvSource = {
 	datatype: "json",
@@ -574,6 +594,20 @@
 }
 
 
+function CalcFrac(TpH, pK1, pK2, pK3) {
+
+	var r1d = Math.pow(10, TpH - pK1);
+	var r2d = Math.pow(10, TpH - pK2);
+	var r3d = Math.pow(10, TpH - pK3);
+	var dd = 1/(1 + r1d + r1d*r2d + r1d*r2d*r3d);
+	var f1d = dd;
+	var f2d = r1d*dd;
+	var f3d = r1d*r2d*dd;
+	var f4d = r1d*r2d*r3d*dd;
+	return f2d + 2*f3d + 3*f4d;
+}
+
+
 /*
  * Steinie:
  *
--- a/www/js/inv_equipments.js	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/js/inv_equipments.js	Sun Dec 23 20:13:36 2018 +0100
@@ -65,7 +65,8 @@
 	$("#tun_height").jqxTooltip({ content: 'Mash TUN height in cm.' });
 	$("#tun_weight").jqxTooltip({ content: 'Mash TUN weight in Kg.' });
 	$("#tun_material").jqxTooltip({ content: 'Mash TUN material. Needed to calculate the right strike temperature.' });
-	$("#mash_volume").jqxTooltip({ content: 'Mash water for the first step.' });
+	$("#mash_volume").jqxTooltip({ content: 'Liters maisch water voor de eerste maisch stap.' });
+	$("#mash_max").jqxTooltip({ content: 'Maximale moutstort voor deze installatie in Kg.' });
 	$("#lauter_volume").jqxTooltip({ content: 'Total lauter volume.' });
 	$("#lauter_height").jqxTooltip({ content: 'Height of the lauter TUN in cm.' });
 	$("#lauter_deadspace").jqxTooltip({ content: 'Volume loss in the lauter TUN.' });
@@ -108,6 +109,7 @@
 			{ name: 'kettle_volume', type: 'float' },
 			{ name: 'kettle_height', type: 'float' },
 			{ name: 'mash_volume', type: 'float' },
+			{ name: 'mash_max', type: 'float' },
 			{ name: 'efficiency', type: 'float' }
 		],
 		id: 'record',
@@ -169,20 +171,20 @@
 	$("#boil_size").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 200000, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.5 });
 	$("#batch_size").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 200000, decimalDigits: 2, spinButtons: true, spinButtonsStep: 0.5 });
 	$("#tun_volume").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 200000, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.5 });
-	$("#tun_weight").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 90, height: 23, min: 0, decimalDigits: 2, spinButtons: true });
+	$("#tun_weight").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 2, spinButtons: true });
 	$("#tun_specific_heat").jqxNumberInput({ inputMode: 'simple', readOnly: true, theme: theme, width: 70, height: 23, decimalDigits: 3 });
 	$("#tun_material").jqxDropDownList({ theme: theme, source: srcMaterial, selectedIndex: 0, width: 110, height: 23, dropDownHeight: 130 });
-	$("#tun_height").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 90, height: 23, min: 0, decimalDigits: 1, spinButtons: true });
+	$("#tun_height").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 1, spinButtons: true });
 	$("#top_up_water").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 20000, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 });
 	$("#trub_chiller_loss").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 });
 	$("#evap_rate").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 40000, decimalDigits: 2, spinButtons: true, spinButtonsStep: 0.05 });
-	$("#boil_time").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 90, height: 23, min: 0, max: 1440, decimalDigits: 0, spinButtons: true });
+	$("#boil_time").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 1440, decimalDigits: 0, spinButtons: true });
 	$("#calc_boil_volume").jqxCheckBox({ theme: theme, width: 120, height: 23 });
 	$("#calc_boil_volume").on('checked', function (event) {
-		$("#batch_size").jqxNumberInput({ readOnly: true, width: 70, spinButtons: false });
+		$("#batch_size").jqxNumberInput({ readOnly: true, width: 90, spinButtons: false });
 	});
 	$("#calc_boil_volume").on('unchecked', function (event) {
-		$("#batch_size").jqxNumberInput({ readOnly: false, width: 90, spinButtons: true });
+		$("#batch_size").jqxNumberInput({ readOnly: false, width: 110, spinButtons: true });
 	});
 	$("#top_up_kettle").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 });
 	$("#hop_utilization").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 90, height: 23, min: 0, decimalDigits: 0, spinButtons: true });
@@ -193,7 +195,8 @@
 	$("#kettle_volume").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 200000, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.5 });
 	$("#kettle_height").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 1, spinButtons: true });
 	$("#mash_volume").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 200000, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.5 });
-	$("#efficiency").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 90, height: 23, min: 0, decimalDigits: 1, spinButtons: true });
+	$("#mash_max").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, max: 200000, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.5 });
+	$("#efficiency").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 110, height: 23, min: 0, decimalDigits: 1, spinButtons: true });
 	var dataAdapter = new $.jqx.dataAdapter(source);
 	var editrow = -1;
 	// initialize jqxGrid
@@ -236,6 +239,7 @@
 				$("#kettle_volume").val('20');
 				$("#kettle_height").val('20');
 				$("#mash_volume").val('18');
+				$("#mash_max").val('6');
 				$("#efficiency").val('75');
 				$("#popupWindow").jqxWindow('open');
 			});
@@ -296,6 +300,7 @@
 					$("#kettle_volume").val(dataRecord.kettle_volume);
 					$("#kettle_height").val(dataRecord.kettle_height);
 					$("#mash_volume").val(dataRecord.mash_volume);
+					$("#mash_max").val(dataRecord.mash_max);
 					$("#efficiency").val(dataRecord.efficiency);
 					// show the popup window.
 					$("#popupWindow").jqxWindow('open');
@@ -354,6 +359,7 @@
 			kettle_volume: parseFloat($("#kettle_volume").jqxNumberInput('decimal')),
 			kettle_height: parseFloat($("#kettle_height").jqxNumberInput('decimal')),
 			mash_volume: parseFloat($("#mash_volume").jqxNumberInput('decimal')),
+			mash_max: parseFloat($("#mash_max").jqxNumberInput('decimal')),
 			efficiency: parseFloat($("#efficiency").jqxNumberInput('decimal'))
 		};
 		if (editrow >= 0) {
--- a/www/js/prod_edit.js	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/js/prod_edit.js	Sun Dec 23 20:13:36 2018 +0100
@@ -276,6 +276,7 @@
 			$("#eq_kettle_volume").val(datarecord.kettle_volume);
 			dataRecord.eq_kettle_height = datarecord.kettle_height / 100.0;
 			$("#eq_mash_volume").val(datarecord.mash_volume);
+			$("#eq_mash_max").val(datarecord.mash_max);
 			$("#eq_efficiency").val(datarecord.efficiency);
 		}
 	});
@@ -292,6 +293,7 @@
 	$("#eq_notes").jqxTooltip({ content: 'Opmerkingen over deze apparatuur.' });
 	$("#eq_tun_volume").jqxTooltip({ content: 'Maisch ketel volume.' });
 	$("#eq_mash_volume").jqxTooltip({ content: 'Maisch water voor de eerste stap.' });
+	$("#eq_mash_max").jqxTooltip({ content: 'De maximale moutstort in Kg.' });
 	$("#eq_lauter_volume").jqxTooltip({ content: 'Filterkuip volume.' });
 	$("#eq_lauter_deadspace").jqxTooltip({ content: 'Filterkuip verlies in liters.' });
 	$("#eq_efficiency").jqxTooltip({ content: 'Gemiddeld brouwzaal rendement.' });
@@ -343,6 +345,7 @@
 			{ name: 'eq_kettle_volume', type: 'float' },
 		        { name: 'eq_kettle_height', type: 'float' },
 			{ name: 'eq_mash_volume', type: 'float' },
+			{ name: 'eq_mash_max', type: 'float' },
 			{ name: 'eq_efficiency', type: 'float' },
 			{ name: 'brew_date_start', type: 'string' },
 			{ name: 'brew_mash_ph', type: 'float' },
@@ -484,6 +487,7 @@
 			$("#eq_lauter_deadspace").val(dataRecord.eq_lauter_deadspace);
 			$("#eq_kettle_volume").val(dataRecord.eq_kettle_volume);
 			$("#eq_mash_volume").val(dataRecord.eq_mash_volume);
+			$("#eq_mash_max").val(dataRecord.eq_mash_max);
 			$("#eq_efficiency").val(dataRecord.eq_efficiency);
 			// Brewdate
 			$("#brew_date_start").val(dataRecord.brew_date_start);
@@ -651,6 +655,7 @@
 	$("#eq_lauter_deadspace").jqxNumberInput({ inputMode: 'simple', readOnly: true, theme: theme, width: 70, height: 23, decimalDigits: 1 });
 	$("#eq_kettle_volume").jqxNumberInput({ inputMode: 'simple', readOnly: true, theme: theme, width: 70, height: 23, decimalDigits: 1 });
 	$("#eq_mash_volume").jqxNumberInput({ inputMode: 'simple', readOnly: true, theme: theme, width: 70, height: 23, decimalDigits: 1 });
+	$("#eq_mash_max").jqxNumberInput({ inputMode: 'simple', readOnly: true, theme: theme, width: 70, height: 23, decimalDigits: 1 });
 	$("#eq_efficiency").jqxNumberInput({ inputMode: 'simple', readOnly: true, theme: theme, width: 70, height: 23, decimalDigits: 1 });
 	// Brewday
 	$("#brew_date_start").jqxDateTimeInput({ theme: theme, width: 230, height: 23, formatString: 'yyyy-MM-dd HH:mm:ss', showTimeButton: true });
@@ -775,6 +780,7 @@
 			eq_kettle_volume: parseFloat($("#eq_kettle_volume").jqxNumberInput('decimal')),
 			eq_kettle_height: dataRecord.eq_kettle_height,
 			eq_mash_volume: parseFloat($("#eq_mash_volume").jqxNumberInput('decimal')),
+			eq_mash_max: parseFloat($("#eq_mash_max").jqxNumberInput('decimal')),
 			eq_efficiency: parseFloat($("#eq_efficiency").jqxNumberInput('decimal'))
 		};
 		var data = "update=true&" + $.param(row);
--- a/www/js/rec_edit.js	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/js/rec_edit.js	Sun Dec 23 20:13:36 2018 +0100
@@ -44,16 +44,35 @@
 
 $(document).ready(function () {
 
-	var	to_100 = false;	// Fermentables adjust to 100%
+	var	to_100 = false;		// Fermentables adjust to 100%
 	var	preboil_sg = 0;
-	var	sugarsm = 0;	// Sugars after mash
-	var	sugarsf = 0;	// Sugars after boil
-	var     psugar = 0;     // Percentage real sugars
-	var     pcara = 0;      // Percentage cara/crystal malts
-	var	svg = 77;	// Default attenuation
+	var	sugarsm = 0;		// Sugars after mash
+	var	sugarsf = 0;		// Sugars after boil
+	var     psugar = 0;     	// Percentage real sugars
+	var     pcara = 0;      	// Percentage cara/crystal malts
+	var	svg = 77;		// Default attenuation
+	var	mashkg = 0;		// Malt in mash weight
 	var	hop_flavour = 0;
 	var	hop_aroma = 0;
 	var	mash_infuse = 0;
+	var	last_base = '';
+	var	last_acid = '';
+
+	var     MMCa = 40.048;
+	var     MMMg = 24.305;
+	var     MMNa = 22.98976928;
+	var     MMCl = 35.453;
+	var     MMSO4 = 96.0626;
+	var     MMCO3 = 60.01684;
+	var     MMHCO3 = 61.01684;
+	var     MMCaSO4 = 172.171;
+	var     MMCaCl2 = 147.015;
+	var     MMCaCO3 = 100.087;
+	var     MMMgSO4 = 246.475;
+	var     MMNaHCO3 = 84.007;
+	var     MMNa2CO3 = 105.996;
+	var     MMNaCl = 58.443;
+	var     MMCaOH2 = 74.06268;
 
 	console.log("record:" + my_record + "  return:" + my_return + "  theme:" + theme);
 	$("#jqxLoader").jqxLoader({
@@ -70,6 +89,7 @@
 		sugarsm = 0;
 		psugar = 0;
 		pcara = 0;
+		mashkg = 0;
 		var colorw = 0;	// Colors working
 		var my_100 = false;
 
@@ -84,25 +104,29 @@
 				pcara += row.f_percentage;
 			var d = row.f_amount * (row.f_yield / 100) * (1 - row.f_moisture / 100);
 			if (row.f_added == "Mash") {
-				d = parseFloat($("#efficiency").jqxNumberInput('decimal')) / 100 * d;
+				d = parseFloat(dataRecord.efficiency) / 100 * d;
 				sugarsm += d;
+				mashkg += row.f_amount;
 			}
 			sugarsf += d;
-			colorw += row.f_amount * ebc_to_srm(row.f_color) / parseFloat($("#batch_size").jqxNumberInput('decimal')) * 8.34436;
+			colorw += row.f_amount * ebc_to_srm(row.f_color) / parseFloat(dataRecord.batch_size) * 8.34436;
 		}
-		if (to_100 != my_100)
-			console.log("change to_100 to:"+my_100);
 		to_100 = my_100;
-		var est_og = estimate_sg(sugarsf, parseFloat($("#batch_size").jqxNumberInput('decimal')));
+		var est_og = estimate_sg(sugarsf, parseFloat(dataRecord.batch_size));
 		$('#est_og').val(est_og);
 		$('#est_og2').val(est_og);
-		preboil_sg = estimate_sg(sugarsm, parseFloat($("#boil_size").jqxNumberInput('decimal')));
-		var color = kw_to_ebc($("#color_method").val(), colorw);
+		preboil_sg = estimate_sg(sugarsm, dataRecord.boil_size);
+		var color = kw_to_ebc(dataRecord.color_method, colorw);
 		$('#est_color').val(color);
 		$('#est_color2').val(color);
 		var scolor = ebc_to_color(color);
 		document.getElementById("bcolor").style.background= scolor;
 		document.getElementById("bcolor2").style.background= scolor;
+		// Use boil_size instead of mash_max from equipment.
+		pmalts = mashkg / (dataRecord.boil_size / 3) * 100;
+		$("#perc_malts").jqxProgressBar('val', pmalts);
+		$("#perc_sugars").jqxProgressBar('val', psugar);
+		$("#perc_cara").jqxProgressBar('val', pcara);
 	};
 
 	function hopFlavourContribution(bt, vol, use, amount) {
@@ -149,11 +173,11 @@
 		var rows = $('#hopGrid').jqxGrid('getrows');
 		for (var i = 0; i < rows.length; i++) {
 			var row = rows[i];
-			total_ibus += toIBU(row.h_useat, row.h_form, preboil_sg, parseFloat($("#batch_size").jqxNumberInput('decimal')),
-					parseFloat(row.h_amount), parseFloat(row.h_time), parseFloat(row.h_alpha), $("#ibu_method").val());
-			hop_flavour += hopFlavourContribution(parseFloat(row.h_time), parseFloat($("#batch_size").jqxNumberInput('decimal')),
+			total_ibus += toIBU(row.h_useat, row.h_form, preboil_sg, parseFloat(dataRecord.batch_size),
+					parseFloat(row.h_amount), parseFloat(row.h_time), parseFloat(row.h_alpha), dataRecord.ibu_method);
+			hop_flavour += hopFlavourContribution(parseFloat(row.h_time), parseFloat(dataRecord.batch_size),
 						row.h_useat, parseFloat(row.h_amount));
-			hop_aroma += hopAromaContribution(parseFloat(row.h_time), parseFloat($("#batch_size").jqxNumberInput('decimal')),
+			hop_aroma += hopAromaContribution(parseFloat(row.h_time), parseFloat(dataRecord.batch_size),
 					        row.h_useat, parseFloat(row.h_amount));
 		}
 		total_ibus = Math.round(total_ibus);
@@ -174,6 +198,59 @@
 		}
 	}
 
+	function setWaterAgent(name, amount) {
+		console.log("setWaterAgent(" + name + ", " + amount + ")");
+		var rows = $('#miscGrid').jqxGrid('getrows');
+		if (amount == 0) {
+			for (var i = 0; i < rows.length; i++) {
+				var row = rows[i];
+				if (row.m_name == name) {
+					var id = $("#miscGrid").jqxGrid('getrowid', i);
+//					console.log("name found, erase "+ id);
+					var commit = $("#miscGrid").jqxGrid('deleterow', id);
+//					console.log("result: "+commit);
+				}
+			}
+		} else {
+			var found = false;
+			for (var i = 0; i < rows.length; i++) {
+				var row = rows[i];
+				if (row.m_name == name) {
+					found = true;
+					$("#miscGrid").jqxGrid('setcellvalue', i, 'm_weight', amount);
+					$("#miscGrid").jqxGrid('setcellvalue', i, 'm_amount', amount / 1000);
+					break;
+				}
+			}
+			console.log("set something, found: "+found);
+			if (! found) {
+//				console.log("need to add this misc");
+				var miscs = new $.jqx.dataAdapter(miscInvSource, {
+					loadComplete: function () {
+						var records = miscs.records;
+						for (var i = 0; i < records.length; i++) {
+							var record = records[i];
+							if (record.name == name) {
+								var row = {};
+								row["m_name"] = record.name;
+								row["m_amount"] = amount / 1000;
+								row["m_cost"] = record.cost;
+								row["m_type"] = record.type;
+								row["m_use_use"] = record.use_use;
+								row["m_time"] = 0;
+								row["m_weight"] = amount;
+								row["m_amount_is_weight"] = record.amount_is_weight;
+								var commit = $("#miscGrid").jqxGrid('addrow', null, row);
+//								console.log("result: "+commit);
+							}
+						}
+					}
+				});
+				miscs.dataBind();
+				return;
+			}
+		}
+	}
 
 	function setRangeIndicator(ion, rangeCode) {
 		$("#wr_" + ion).html("<img src='images/checkmark_range_" + rangeCode + ".gif'><span style='font-size: 10px; font-style: italic;'>" + rangeCode + "</span>");
@@ -186,6 +263,49 @@
 		return 0;
 	}
 
+	var Ka1 = 0.0000004445;
+	var Ka2 = 0.0000000000468;
+
+	function PartCO3(pH) {
+		var H = Math.pow(10, -pH);
+		return 100 * Ka1 * Ka2 / (H*H + H * Ka1 + Ka1 * Ka2);
+	}
+
+	function PartHCO3(pH) {
+		var H = Math.pow(10, -pH);
+		return 100 * Ka1 * H / (H*H + H * Ka1 + Ka1 * Ka2);
+	}
+
+	function Charge(pH) {
+		return (-2 * PartCO3(pH) - PartHCO3(pH));
+	}
+
+	//Z alkalinity is the amount of acid (in mEq/l) needed to bring water to the target pH (Z pH)
+	function ZAlkalinity(pHZ) {
+		var C43 = Charge(4.3);
+		var Cw = Charge(parseFloat(dataRecord.mash_ph));
+		var Cz = Charge(pHZ);
+		var DeltaCNaught = -C43+Cw;
+		var CT = parseFloat($("#wg_total_alkalinity").jqxNumberInput('decimal')) / 50 / DeltaCNaught;
+		var DeltaCZ = -Cz+Cw;
+		return CT * DeltaCZ;
+	}
+
+	function ZRA(pHZ) {
+
+		var Calc = parseFloat($("#wg_calcium").jqxNumberInput('decimal')) / (MMCa / 2);
+		var Magn = parseFloat($("#wg_magnesium").jqxNumberInput('decimal')) / (MMMg / 2);
+		var Z = ZAlkalinity(pHZ);
+		return Z - (Calc / 3.5 + Magn / 7);
+	}
+
+	function ProtonDeficit(pHZ) {
+
+		var Result = ZRA(pHZ) * parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+
+		return Result;
+	}
+
 	function calcWater() {
 
 		console.log("calcWater()");
@@ -197,18 +317,29 @@
 		var chloride = 0;
 		var sulfate = 0;
 		var ph = 0;
+		var RA = 0;
+		var acid = 0;
+		var frac = 0;
+		var MolWt = 0;
+		var pK1 = 0;
+		var pK2 = 0;
+		var pK3 = 0;
+		var TpH = 0;
+		var AcidSG = 0;
+		var AcidPrc = 0;
+		var protonDeficit = 0;
 
 //		console.log((dataRecord.w1_name != "") + " " + (dataRecord.w2_name != ""));
 		if (dataRecord.w1_name != "") {
 			if (dataRecord.w2_name != "") {
 				liters = dataRecord.w1_amount + dataRecord.w2_amount;
-				calcium = Math.round(mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_calcium, dataRecord.w2_calcium) * 10) / 10;
-				magnesium = Math.round(mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_magnesium, dataRecord.w2_magnesium) * 10) / 10;
-				sodium = Math.round(mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_sodium, dataRecord.w2_sodium) * 10) / 10;
-				chloride = Math.round(mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_chloride, dataRecord.w2_chloride) * 10) / 10;
-				sulfate = Math.round(mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_sulfate, dataRecord.w2_sulfate) * 10) / 10;
-				total_alkalinity = Math.round(mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_total_alkalinity, dataRecord.w2_total_alkalinity) * 10) / 10;
-				ph = Math.round(-Math.log10(((Math.pow(10, -dataRecord.w1_ph) * dataRecord.w1_amount) + (Math.pow(10, -dataRecord.w2_ph) * dataRecord.w2_amount))  / liters) * 10) / 10;
+				calcium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_calcium, dataRecord.w2_calcium);
+				magnesium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_magnesium, dataRecord.w2_magnesium);
+				sodium = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_sodium, dataRecord.w2_sodium);
+				chloride = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_chloride, dataRecord.w2_chloride);
+				sulfate = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_sulfate, dataRecord.w2_sulfate);
+				total_alkalinity = mix(dataRecord.w1_amount, dataRecord.w2_amount, dataRecord.w1_total_alkalinity, dataRecord.w2_total_alkalinity);
+				ph = -Math.log10(((Math.pow(10, -dataRecord.w1_ph) * dataRecord.w1_amount) + (Math.pow(10, -dataRecord.w2_ph) * dataRecord.w2_amount))  / liters);
 			} else {
 				liters = dataRecord.w1_amount;
 				calcium = dataRecord.w1_calcium;
@@ -216,47 +347,140 @@
 				sodium = dataRecord.w1_sodium;
 				chloride = dataRecord.w1_chloride;
 				sulfate = dataRecord.w1_sulfate;
-				total_alkalinity = dataRecord.total_alkalinity;
+				total_alkalinity = dataRecord.w1_total_alkalinity;
 				ph = dataRecord.w1_ph;
 			}
 		}
 		$('#wg_amount').val(liters);
-		$('#wg_calcium').val(calcium);
-		$('#wg_magnesium').val(magnesium);
-		$('#wg_sodium').val(sodium);
-		$('#wg_total_alkalinity').val(total_alkalinity);
-		$('#wg_chloride').val(chloride);
-		$('#wg_sulfate').val(sulfate);
+		$('#wg_calcium').val(Math.round(calcium * 10) / 10);
+		$('#wg_magnesium').val(Math.round(magnesium * 10) / 10);
+		$('#wg_sodium').val(Math.round(sodium * 10) / 10);
+		$('#wg_total_alkalinity').val(Math.round(total_alkalinity * 10) / 10);
+		$('#wg_chloride').val(Math.round(chloride * 10) / 10);
+		$('#wg_sulfate').val(Math.round(sulfate * 10) / 10);
 		// Note: brouwhulp has the malts included here in the result.
-		$('#wg_ph').val(ph);
+		$('#wg_ph').val(Math.round(ph * 10) / 10);
+
+		// Noot: de volgende berekeningen geven bijna gelijke resultaten in Brun'water.
+		// Calculate Ca
+		RA = parseFloat($("#wa_cacl2").jqxNumberInput('decimal')) * MMCa / MMCaCl2 +
+		     parseFloat($("#wa_caso4").jqxNumberInput('decimal')) * MMCa / MMCaSO4;
+		calcium += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+
+		// Calculate Mg
+		RA = parseFloat($("#wa_mgso4").jqxNumberInput('decimal')) * MMMg / MMMgSO4;
+		magnesium += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+
+		// Calculate Na
+		RA = parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMNa / MMNaCl +
+		     parseFloat($("#wa_base").jqxNumberInput('decimal')) * MMNa / MMNaHCO3;
+		sodium += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+
+		// Calculate SO4
+		RA = parseFloat($("#wa_caso4").jqxNumberInput('decimal')) * MMSO4 / MMCaSO4 +
+		     parseFloat($("#wa_mgso4").jqxNumberInput('decimal')) * MMSO4 / MMMgSO4;
+		sulfate += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
 
-		// Brouwhulp < 40 || > 200
-		if (calcium < 50) { setRangeIndicator("calcium", "low"); }
-		if (calcium >= 50 && calcium <= 150) { setRangeIndicator("calcium", "normal"); }
-		if (calcium > 150) { setRangeIndicator("calcium", "high"); }
-		if (calcium > 250) { setRangeIndicator("calcium", "harmful"); }
+		// Calculate Cl
+		RA = 2 * parseFloat($("#wa_cacl2").jqxNumberInput('decimal')) * MMCl / MMCaCl2 +
+		     parseFloat($("#wa_nacl").jqxNumberInput('decimal')) * MMCl / MMNaCl;
+		chloride += 1000 * RA / parseFloat($("#wg_amount").jqxNumberInput('decimal'));
+		// Einde noot.
+
+		TpH = parseFloat(dataRecord.mash_ph);
+		if (TpH < 5.0 || TpH > 6.0) {
+			TpH = 5.4;
+			dataRecord.mash_ph = 5.4;
+			$("#mash_ph").val(5.4);
+			$("#tgt_mash_ph").val(5.4);
+		}
+		var acid_amount = parseFloat($("#wa_acid").jqxNumberInput('decimal'));
+		var acid_perc = parseFloat($("#wa_acid_perc").jqxNumberInput('decimal'));
+
+		switch ($("#wa_acid_name").val()) {
+			case 'Melkzuur':	pK1 = 3.08;
+						pK2 = 20;
+						pK3 = 20;
+						MolWt = 90.08;
+						AcidSG = 1214; //@88%
+						AcidPrc = 0.88;
+						frac = CalcFrac(TpH, pK1, pK2, pK3);
+						acid += acid_amount * acid_perc / 100 * AcidSG / MolWt * frac / liters; //mEq/l
+						break;
 
-		// Brouwhulp > 40
-		if (magnesium >= 0 && magnesium <= 30) { setRangeIndicator("magnesium", "normal"); }
-		if (magnesium > 30) { setRangeIndicator("magnesium", "high"); }
-		if (magnesium > 50) { setRangeIndicator("magnesium", "harmful"); }
+			case 'Zoutzuur':	pK1 = -10;
+						pK2 =  20;
+						pK3 =  20;
+						MolWt = 36.46;
+						AcidSG = 1142; //@28%
+						AcidPrc = 0.28;
+						frac = CalcFrac(TpH, pK1, pK2, pK3);
+						Acidmg = acid_amount * acid_perc / 100 * AcidSG / liters;
+						acid += Acidmg / MolWt * frac; //mEq/l
+						chloride += Acidmg / 1000 * MMCl / (MMCl + 1);
+						break;
 
-		// Brouwhulp > 100
-		if (sodium <= 150) { setRangeIndicator("sodium", "normal"); }
-		if (sodium > 150) { setRangeIndicator("sodium", "high"); }
-		if (sodium > 200) { setRangeIndicator("sodium", "harmful"); }
+			case 'Fosforzuur':	pK1 = 2.12;
+						pK2 = 7.20;
+						pK3 =  12.44;
+						MolWt = 98.00;
+						AcidSG = 1170; //@25%
+						AcidPrc = 0.25;
+						frac = CalcFrac(TpH, pK1, pK2, pK3);
+						Acidmg = acid_amount * acid_perc / 100 * AcidSG / liters;
+						acid += Acidmg / MolWt * frac; //mEq/l
+						break;
 
-		// Brouwhulp > 200
-		if (chloride <= 250) { setRangeIndicator("chloride", "normal"); }
-		if (chloride > 250) { setRangeIndicator("chloride", "high"); }
-		if (chloride > 300) { setRangeIndicator("chloride", "harmful"); }
+			case 'Zwavelzuur':	pK1 = -10;
+						pK2 = 1.92;
+						pK3 = 20;
+						MolWt = 98.07;
+						AcidSG = 1700; //@93%
+						AcidPrc = 0.93;
+						frac = CalcFrac(TpH, pK1, pK2, pK3);
+						Acidmg = acid_amount * acid_perc / 100 * AcidSG / liters;
+						acid += Acidmg / MolWt * frac; //mEq/l
+						sulfate += Acidmg / 1000 * MMSO4 / (MMSO4 + 2);
+						break;
+		}
+		protonDeficit = ProtonDeficit(TpH);
+		console.log("frac: "+frac+"  acid: "+acid+"  protonDeficit: "+protonDeficit);
+		total_alkalinity -= 50 / 61 * protonDeficit * frac / liters;
+
+                $('#wb_calcium').val(Math.round(calcium * 10) / 10);
+                $('#wb_magnesium').val(Math.round(magnesium * 10) / 10);
+                $('#wb_sodium').val(Math.round(sodium * 10) / 10);
+                $('#wb_sulfate').val(Math.round(sulfate * 10) / 10);
+                $('#wb_chloride').val(Math.round(chloride * 10) / 10);
+		$('#wb_total_alkalinity').val(Math.round(total_alkalinity * 10) / 10);
 
-		// Brouwhulp > 600
-		if (sulfate < 50) { setRangeIndicator("sulfate", "low"); }
-		if (sulfate >= 50 && sulfate <= 350) { setRangeIndicator("sulfate", "normal"); }
-		if (sulfate > 350) { setRangeIndicator("sulfate", "high"); }
-		if (sulfate > 750) { setRangeIndicator("sulfate", "harmful"); }
-
+                if (calcium < 40) {
+			setRangeIndicator("calcium", "low");
+		} else if (calcium > 150) {
+			setRangeIndicator("calcium", "high");
+		} else {
+			setRangeIndicator("calcium", "normal");
+		}
+                if (magnesium >= 0 && magnesium <= 30) {
+			setRangeIndicator("magnesium", "normal");
+		} else {
+			setRangeIndicator("magnesium", "high");
+		}
+                if (sodium <= 150) {
+			setRangeIndicator("sodium", "normal");
+		} else {
+			setRangeIndicator("sodium", "high");
+		}
+                if (chloride <= 100) {
+			setRangeIndicator("chloride", "normal");
+		} else {
+			setRangeIndicator("chloride", "high");
+		}
+                if (sulfate <= 350) {
+			setRangeIndicator("sulfate", "normal");
+		} else {
+			setRangeIndicator("sulfate", "high");
+		}
 	}
 
 	function calcFermentablesFromOG(OG) {
@@ -298,6 +522,14 @@
 
 		$("#w1_name").jqxDropDownList('selectItem', dataRecord.w1_name);
 		$("#w2_name").jqxDropDownList('selectItem', dataRecord.w2_name);
+		// Fix tap water if zero using mash infuse amount.
+		if (parseFloat($("#wg_amount").jqxNumberInput('decimal')) == 0 && mash_infuse > 0) {
+			$("#w1_amount").val(mash_infuse);
+			dataRecord.w1_amount = mash_infuse;
+			$("#wg_amount").val(mash_infuse);
+			$("#w2_amount").val(0);
+			dataRecord.w2_amount = 0;
+		}
 		calcWater();
 		$("#w2_amount").on('change', function (event) {
 			var newval = parseFloat(event.args.value);
@@ -312,6 +544,43 @@
 			console.log("new: "+event.args.value+" w1: "+dataRecord.w1_amount+"  w2: "+dataRecord.w2_amount);
 			calcWater();
 		});
+		$('#wa_cacl2').on('change', function (event) {
+			setWaterAgent('CaCl2', event.args.value);
+			calcWater();
+		});
+		$('#wa_caso4').on('change', function (event) {
+			setWaterAgent('CaSO4', event.args.value);
+			calcWater();
+		});
+		$('#wa_mgso4').on('change', function (event) {
+			setWaterAgent('MgSO4', event.args.value);
+			calcWater();
+		});
+		$('#wa_nacl').on('change', function (event) {
+			setWaterAgent('NaCl', event.args.value);
+			calcWater();
+		});
+		$('#wa_base_name').on('change', function (event) {
+			setWaterAgent(last_base, 0);
+			last_base = event.args.item.value;
+			setWaterAgent(last_base, parseFloat($("#wa_base").jqxNumberInput('decimal')));
+			calcWater();
+		});
+		$('#wa_base').on('change', function (event) {
+			setWaterAgent($("#wa_base_name").val(), parseFloat(event.args.value));
+			calcWater();
+		});
+		$('#wa_acid_name').on('change', function (event) {
+			setWaterAgent(last_acid, 0);
+			last_acid = event.args.item.value;
+			setWaterAgent(last_acid, parseFloat($("#wa_acid").jqxNumberInput('decimal')));
+			calcWater();
+		});
+		$('#wa_acid').on('change', function (event) {
+			setWaterAgent($("#wa_acid_name").val(), parseFloat(event.args.value));
+			calcWater();
+		});
+		$('#wa_acid_perc').on('change', function (event) { calcWater(); });
 
 		$('#color_method').on('change', function (event) { calcFermentables(); });
 		$('#ibu_method').on('change', function (event) {
@@ -362,6 +631,7 @@
 			calcIBUs();                                     // and the IBU's.
 		});
 		$('#mash_ph').on('change', function (event) {
+			dataRecord.mash_ph = parseFloat(event.args.value);
 			$("#tgt_mash_ph").val(parseFloat(event.args.value));
 			calcWater();
 		});
@@ -445,6 +715,11 @@
 	$("#st_abv_max").jqxTooltip({ content: 'Het maximum alcohol volume % voor deze bierstijl.'});
 	$("#st_carb_min").jqxTooltip({ content: 'Het minimum koolzuur volume voor deze bierstijl.'});
 	$("#st_carb_max").jqxTooltip({ content: 'Het maximum koolzuur volume voor deze bierstijl.'});
+	$("#wa_cacl2").jqxTooltip({ content: 'Voor het maken van een ander waterprofiel. Voegt calcium en chloride toe. Voor het verbeteren van zoetere bieren.'});
+	$("#wa_caso4").jqxTooltip({ content: 'Gips. Voor het maken van een ander waterprofiel. Voegt calcium en sulfaat toe. Voor het verbeteren van bittere bieren.'});
+	$("#wa_mgso4").jqxTooltip({ content: 'Epsom zout. Voor het maken van een ander waterprofiel. Voegt magnesium en sulfaat toe. Gebruik spaarzaam!'});
+	$("#wa_nacl").jqxTooltip({ content: 'Keukenzout. Voor het maken van een ander waterprofiel. Voegt natrium en chloride toe. Voor het accentueren van zoetheid. Bij hoge dosering wordt het bier ziltig.'});
+	$("#w2_amount").jqxTooltip({ content: 'De verdeling van het hoofd en meng water. Het totale maisch water volume blijft gelijk.'});
 
 	// prepare the data
 	var source = {
@@ -1158,14 +1433,52 @@
 					row.m_weight = row.m_amount * 1000;
 					data.push(row);
 					// Initial set water agent values.
-					if (row.m_name == 'CaCl2')
-						$("#wa_cacl2").val(row.m_weight);
-					if (row.m_name == 'CaSO4')
-						$("#wa_caso4").val(row.m_weight);
-					if (row.m_name == 'MgSO4')
-						$("#wa_mgso4").val(row.m_weight);
-					if (row.m_name == 'NaCl')
-						$("#wa_nacl").val(row.m_weight);
+					switch (row.m_name) {
+						case 'CaCl2':		$("#wa_cacl2").val(row.m_weight);
+									break;
+						case 'CaSO4':		$("#wa_caso4").val(row.m_weight);
+									break;
+						case 'MgSO4':		$("#wa_mgso4").val(row.m_weight);
+									break;
+						case 'NaCl':		$("#wa_nacl").val(row.m_weight);
+									break;
+						case 'Melkzuur':	$("#wa_acid_name").val('Melkzuur');
+									$("#wa_acid").val(row.m_weight);
+									$("#wa_acid_perc").val(80);
+									last_acid = 'Melkzuur';
+									break;
+						case 'Zoutzuur':	$("#wa_acid_name").val('Zoutzuur');
+									$("#wa_acid").val(row.m_weight);
+									$("#wa_acid_perc").val(80);
+									last_acid = 'Zoutzuur';
+									break;
+						case 'Fosforzuur':	$("#wa_acid_name").val('Fosforzuur');
+									$("#wa_acid").val(row.m_weight);
+									$("#wa_acid_perc").val(80);
+									last_acid = 'Fosforzuur';
+									break;
+						case 'Zwavelzuur':	$("#wa_acid_name").val('Zwavelzuur');
+									$("#wa_acid").val(row.m_weight);
+									$("#wa_acid_perc").val(80);
+									last_acid = 'Zwavelzuur';
+									break;
+						case 'NaHCO3':		$("#wa_base_name").val('NaHCO3');
+									$("#wa_base").val(row.m_weight);
+									last_base = 'NaHCO3';
+									break;
+						case 'Na2CO3':		$("#wa_base_name").val('Na2CO3');
+									$("#wa_base").val(row.m_weight);
+									last_base = 'Na2CO3';
+									break;
+						case 'CaCO3':		$("#wa_base_name").val('CaCO3');
+									$("#wa_base").val(row.m_weight);
+									last_base = 'CaCO3';
+									break;
+						case 'Ca(OH)2':		$("#wa_base_name").val('Ca(OH)2');
+									$("#wa_base").val(row.m_weight);
+									last_base = 'Ca(OH)2';
+									break;
+					}
 				}
 				return data;
 			},
@@ -1645,7 +1958,7 @@
 	var srcColor = [ "Morey", "Mosher", "Daniels" ];
 	//var srcIBU = [ "Tinseth", "Rager", "Garetz", "Daniels", "Mosher", "Noonan" ];
 	var srcIBU = [ "Tinseth", "Rager", "Daniels" ];	// Only these are supported at this time.
-	var srcDeAcid = [ "NaHCO3", "Na2CO3", "CaCO3", "Ca(OH)2" ];
+	var srcBase = [ "NaHCO3", "Na2CO3", "CaCO3", "Ca(OH)2" ];
 	var srcAcid = [ "Melkzuur", "Zoutzuur", "Fosforzuur", "Zwavelzuur" ];
 	$("#name").jqxInput({ theme: theme, width: 640, height: 23 });
 	$("#notes").jqxInput({ theme: theme, width: 960, height: 200 });
@@ -1693,9 +2006,12 @@
 	$("#mash_ph").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 4, max: 8, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 });
 	$("#tgt_mash_ph").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 100, height: 23, decimalDigits: 1, readOnly: true });
 	$("#sparge_temp").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 70, max: 98, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.5 });
-	// Hop flavour and aroma gauges
+	// Several gauges
 	$("#hop_flavour").jqxProgressBar({ width: 300, height: 23, theme: theme, showText: true });
 	$("#hop_aroma").jqxProgressBar({ width: 300, height: 23, theme: theme, showText: true });
+	$("#perc_malts").jqxProgressBar({ width: 300, height: 23, theme: theme, showText: true });
+	$("#perc_sugars").jqxProgressBar({ width: 300, height: 23, theme: theme, showText: true });
+	$("#perc_cara").jqxProgressBar({ width: 300, height: 23, theme: theme, showText: true });
 
 	// Water treatment
 	$("#w1_name").jqxDropDownList({
@@ -1802,13 +2118,34 @@
 	$("#wb_sulfate").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 	$("#wb_ph").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 
+	$("#pr_name").jqxDropDownList({
+		placeHolder: "Kies doel profiel:",
+		theme: theme,
+		source: waterprofiles,
+		displayMember: "name",
+		width: 250,
+		height: 27,
+		dropDownWidth: 400,
+		dropDownHeight: 300
+	});
+	$("#pr_name").on('select', function (event) {
+		if (event.args) {
+			var index = event.args.index;
+			var datarecord = waterprofiles.records[index];
+			$("#pr_calcium").val(datarecord.calcium);
+			$("#pr_sulfate").val(datarecord.sulfate);
+			$("#pr_chloride").val(datarecord.chloride);
+			$("#pr_sodium").val(datarecord.sodium);
+			$("#pr_magnesium").val(datarecord.magnesium);
+			$("#pr_total_alkalinity").val(datarecord.total_alkalinity);
+		}
+        });
 	$("#pr_calcium").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 	$("#pr_magnesium").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 	$("#pr_sodium").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 	$("#pr_total_alkalinity").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 	$("#pr_chloride").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 	$("#pr_sulfate").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
-	$("#pr_ph").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 74, height: 23, decimalDigits: 1, readOnly: true });
 
 	$("#wa_cacl2").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1, symbol: ' gr', symbolPosition: 'right' });
 	$("#wa_caso4").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1, symbol: ' gr', symbolPosition: 'right' });
@@ -1816,10 +2153,11 @@
 	$("#wa_nacl").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1, symbol: ' gr', symbolPosition: 'right' });
 
 	$("#calc_acid").jqxCheckBox({ theme: theme, width: 120, height: 23 });
-	$("#wa_ph_up").jqxDropDownList({ theme: theme, source: srcDeAcid, width: 125, height: 23, dropDownHeight: 128 });
-	$("#wa_ph_up").val('NaHCO3');
-	$("#wa_ph_down").jqxDropDownList({ theme: theme, source: srcAcid, width: 125, height: 23, dropDownHeight: 128 })
-	$("#wa_ph_down").val('Melkzuur');
+	$("#wa_base_name").jqxDropDownList({ theme: theme, source: srcBase, width: 125, height: 23, dropDownHeight: 128 });
+	$("#wa_base").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 2, spinButtons: true, spinButtonsStep: 0.05, symbol: ' gr', symbolPosition: 'right' });
+	$("#wa_acid_name").jqxDropDownList({ theme: theme, source: srcAcid, width: 125, height: 23, dropDownHeight: 128 })
+	$("#wa_acid").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, min: 0, decimalDigits: 2, spinButtons: true, spinButtonsStep: 0.05, symbol: ' ml', symbolPosition: 'right' });
+	$("#wa_acid_perc").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 80, height: 23, min: 0, max: 100, decimalDigits: 0, spinButtons: true, symbol: '%', symbolPosition: 'right' });
 
 	$("#sparge_volume").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 });
 	$("#sparge_ph").jqxNumberInput({ inputMode: 'simple', spinMode: 'simple', theme: theme, width: 100, height: 23, decimalDigits: 1, spinButtons: true, spinButtonsStep: 0.1 });
--- a/www/prod_edit.php	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/prod_edit.php	Sun Dec 23 20:13:36 2018 +0100
@@ -95,39 +95,44 @@
         <td><div id="eq_boil_size"></div></td>
        </tr>
        <tr>
-        <th style="text-align: center;" colspan="2">Filteren</th>
+        <td style="vertical-align: top; float: right;">Maximum moutstort Kg:</td>
+        <td><div id="eq_mash_max"></div></td>
         <td style="vertical-align: top; float: right;">Verdamping per uur l:</td>
         <td><div id="eq_evap_rate"></div></td>
        </tr>
        <tr>
+        <th style="text-align: center;" colspan="2">Filteren</th>
+        <td style="vertical-align: top; float: right;">Kooktijd in minuten:</td>
+        <td><div id="eq_boil_time"></div></td>
+       </tr>
+       <tr>
         <td style="vertical-align: top; float: right;">Filter volume l:</td>
         <td><div id="eq_lauter_volume"></div></td>
-        <td style="vertical-align: top; float: right;">Kooktijd in minuten:</td>
-        <td><div id="eq_boil_time"></div></td>
+        <td style="vertical-align: top; float: right;">Extra water bij koken l:</td>
+        <td><div id="eq_top_up_kettle"></div></td>
        </tr>
        <tr>
         <td style="vertical-align: top; float: right;">Filterkuip verlies l:</td>
         <td><div id="eq_lauter_deadspace"></div></td>
-        <td style="vertical-align: top; float: right;">Extra water bij koken l:</td>
-        <td><div id="eq_top_up_kettle"></div></td>
-       </tr>
-       <tr>
-        <th style="text-align: center;" colspan="2">Koelen</th>
         <td style="vertical-align: top; float: right;">Hopfactor %:</td>
         <td><div id="eq_hop_utilization"></div></td>
        </tr>
        <tr>
-        <td style="vertical-align: top; float: right;">Trub verlies kookketel l:</td>
-        <td><div id="eq_trub_chiller_loss"></div></td>
+        <th style="text-align: center;" colspan="2">Koelen</th>
         <td style="vertical-align: top; float: right;">Volume eind koken l:</td>
         <td><div id="eq_batch_size"></div></td>
        </tr>
        <tr>
-        <td style="vertical-align: top; float: right;">Extra water in gistvatl:</td>
-        <td><div id="eq_top_up_water"></div></td>
+        <td style="vertical-align: top; float: right;">Trub verlies kookketel l:</td>
+        <td><div id="eq_trub_chiller_loss"></div></td>
         <td style="vertical-align: top; float: right;">Brouwzaalrendement %:</td>
         <td><div id="eq_efficiency"></div></td>
        </tr>
+       <tr>
+        <td style="vertical-align: top; float: right;">Extra water in gistvatl:</td>
+	<td><div id="eq_top_up_water"></div></td>
+        <td colspan="2"></td>
+       </tr>
       </table>
      </div>
     </div>
--- a/www/rec_edit.php	Wed Dec 19 21:58:21 2018 +0100
+++ b/www/rec_edit.php	Sun Dec 23 20:13:36 2018 +0100
@@ -106,14 +106,22 @@
         <tr>
          <td style="vertical-align: top; float: right; padding: 3px;">Kleur:</td>
          <td style="padding: 3px;"><div style="float: left;" id="est_color2"></div><div id="bcolor2" class='ebccolor'> </div></td>
+         <td style="vertical-align: top; float: right; padding: 3px;">Percentage moutstort:</td>
+         <td style="padding: 3px;"><div style="float: left;" id="perc_malts"></div></td>
+	</tr>
+        <tr>
          <td style="vertical-align: top; float: right; padding: 3px;">Begin SG:</td>
          <td style="padding: 3px;"><div style="float: left;" id="est_og2"></div></td>
-         <td style="vertical-align: top; float: right; padding: 3px;">:</td>
-         <td style="padding: 3px;"><div style="float: left;" id="ding3"></div></td>
+         <td style="vertical-align: top; float: right; padding: 3px;">Percentage suiker:</td>
+         <td style="padding: 3px;"><div style="float: left;" id="perc_sugars"></div></td>
+        </tr>
+	<tr>
+         <td colspan="2"></td>
+         <td style="vertical-align: top; float: right; padding: 3px;">Percentage cara:</td>
+         <td style="padding: 3px;"><div style="float: left;" id="perc_cara"></div></td>
         </tr>
         <tr>
-         <td align="right" style="vertical-align: top;">Ingredi&euml;nten:</td>
-         <td align="left" colspan="5"><div id="fermentableGrid"></div></td>
+         <td colspan="4"><div id="fermentableGrid"></div></td>
         </tr>
        </table>
       </div>
@@ -262,8 +270,8 @@
              <td><div id="wr_sulfate"></div></td>
              <td><div id="wr_ph"></div></td>
             </tr>
-            <tr>
-             <td style="vertical-align: top; padding: 3px; float: left;">Doel waterprofiel:</td>
+	    <tr>
+             <td><div id="pr_name"></div></td>
              <td><div></div></td>
              <td><div id="pr_calcium"></div></td>
              <td><div id="pr_magnesium"></div></td>
@@ -271,7 +279,7 @@
              <td><div id="pr_total_alkalinity"></div></td>
              <td><div id="pr_chloride"></div></td>
              <td><div id="pr_sulfate"></div></td>
-             <td><div id="pr_ph"></div></td>
+             <td></td>
             </tr>
            </table>
 	  </div>
@@ -289,7 +297,7 @@
          <td style="vertical-align: top; float: right; padding: 3px;">Gips (CaSO4):</td>
          <td style="padding: 3px;"><div id="wa_caso4"></div></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Ontzuren met:</td>
-         <td style="padding: 3px;"><div style="float: left;" id="wa_ph_up"></div><div style="float: left; margin-left: 15px;" id="wa_phup_ml"></div></td>
+         <td style="padding: 3px;"><div style="float: left;" id="wa_base_name"></div><div style="float: left; margin-left: 15px;" id="wa_base"></div></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Spoelwater bron:</td>
          <td style="padding: 3px;"><div id="sparge_source"></div></td>
         </tr>
@@ -297,7 +305,7 @@
          <td style="vertical-align: top; float: right; padding: 3px;">Epsom zout (MgSO4):</td>
          <td style="padding: 3px;"><div id="wa_mgso4"></div></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Aanzuren met:</td>
-         <td style="padding: 3px;"><div style="float: left;" id="wa_ph_down"></div><div style="float: left; margin-left: 15px;" id="wa_phdn_ml"></div><div style="float: left; margin-left: 15px;" id="wa_lacticp"></div></td>
+         <td style="padding: 3px;"><div style="float: left;" id="wa_acid_name"></div><div style="float: left; margin-left: 15px;" id="wa_acid"></div><div style="float: left; margin-left: 15px;" id="wa_acid_perc"></div></td>
          <td style="vertical-align: top; float: right; padding: 3px;">Spoelwater pH:</td>
          <td style="padding: 3px;"><div id="sparge_ph"></div></td>
         </tr>

mercurial