Removed the last compressed css file. Reworked all mash steps, implemented deconction steps. Added calculations for infuse amounts and decoctions amounts. The mash steps are now manually sorted in the editor grids to have full control over the steps order. Display errors in red in the grid. Updated beerxml export, the product checklist and print output of the products and recipes for all these mash steps changes.

Wed, 06 May 2020 14:14:14 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Wed, 06 May 2020 14:14:14 +0200
changeset 667
1246550451ca
parent 666
029e65ca3678
child 668
c09b3041da42

Removed the last compressed css file. Reworked all mash steps, implemented deconction steps. Added calculations for infuse amounts and decoctions amounts. The mash steps are now manually sorted in the editor grids to have full control over the steps order. Display errors in red in the grid. Updated beerxml export, the product checklist and print output of the products and recipes for all these mash steps changes.

www/Makefile file | annotate | diff | comparison | revisions
www/css/style-min.css file | annotate | diff | comparison | revisions
www/includes/db_product.php file | annotate | diff | comparison | revisions
www/includes/db_recipes.php file | annotate | diff | comparison | revisions
www/includes/global.inc.php 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_beerxml.php file | annotate | diff | comparison | revisions
www/prod_checklist.php file | annotate | diff | comparison | revisions
www/prod_edit.php file | annotate | diff | comparison | revisions
www/prod_print.php file | annotate | diff | comparison | revisions
www/rec_beerxml.php file | annotate | diff | comparison | revisions
www/rec_edit.php file | annotate | diff | comparison | revisions
www/rec_print.php file | annotate | diff | comparison | revisions
--- a/www/Makefile	Fri May 01 21:37:23 2020 +0200
+++ b/www/Makefile	Wed May 06 14:14:14 2020 +0200
@@ -29,11 +29,6 @@
 		  jqwidgets/styles/images/* jqwidgets/globalization/* js/* \
 		  includes/* fpdf/* import/*
 OTHER		= Makefile
-CSS_FILES	= $(filter-out %-min.css,$(wildcard css/*.css css/**/*.css ))
-YUI_COMPRESSOR	= yui-compressor
-YUI_COMPRESSOR_FLAGS	= --charset utf-8 --verbose
-
-CSS_MINIFIED = $(CSS_FILES:.css=-min.css)
 
 
 
@@ -42,17 +37,10 @@
 .c.o:
 		${CC} ${CFLAGS} ${INCLUDES} ${DEFINES} -c $<
 
-all:		minify-css
+all:
 
 clean:
-		rm -f version.php ${CSS_MINIFIED}
-
-minify-css:	$(CSS_FILES) $(CSS_MINIFIED)
-
-%-min.css:	%.css
-		@echo '==> Minifying $<'
-		$(YUI_COMPRESSOR) $(YUI_COMPRESSOR_FLAGS) --type css $< >$@
-		@echo
+		rm -f version.php
 
 install:
 		${INSTALL} -d -g 314 -o 314 ${PREFIX} ${WWWDIR} ${WWWDIR}/css ${WWWDIR}/fpdf \
--- a/www/css/style-min.css	Fri May 01 21:37:23 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-body{background:#ccc;font-family:Verdana,Arial,sans-serif;margin:0}#MainPanel,#fermenter,#co2meter,#ispindel{width:1278px;height:628px;border:2px solid #4297d7;background:#252526;float:left;color:#eee}#water_totals{width:960px;background:#353536;margin-top:15px;border:1px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#propagator{width:800px;background:#353536;margin-top:15px;border:1px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#export_table,#about_table{width:960px;background:#353536;margin:150px;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#node_table{width:600px;background:#353536;margin:50px;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#fermenter_table,#co2meter_table,#ispindel_table{width:960px;height:210px;background:#252526;margin:5px;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}.ebccolor{float:left;margin-left:10px;width:75px;height:23px;border:1px solid #59b4d4;border-radius:6px;moz-border-radius:6px;webkit-border-radius:6px;background:#212121}#fermenter_thermometers,#co2meter_meters,#ispindel_meters{width:960px;height:390px;float:left;background-color:#252526;margin:5px;margin-top:3px;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#fermenter_panel_top,#co2meter_panel_top,#ispindel_panel_top{width:290px;height:100px;float:right;margin:5px;background-color:#252526;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#fermenter_doorled,#fermenter_lightled,#fermenter_alarmled,#fermenter_powerled{width:50px;height:30px;float:left;text-align:center;margin-top:15px;margin-left:20px}#co2meter_alarmled,#co2meter_powerled,#ispindel_alarmled,#ispindel_powerled{width:50px;height:30px;float:right;text-align:center;margin-top:15px;margin-right:20px}#co2meter_panel_display,#ispindel_panel_display{width:290px;height:260px;float:right;margin:5px;margin-top:3px;background-color:#252526;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#fermenter_panel_display{width:290px;height:98px;float:right;margin:5px;margin-top:3px;background-color:#252526;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#fermenter_display{width:145px;height:98px;float:left;text-align:center}#fermenter_panel_control{width:290px;height:150px;float:right;margin:5px;margin-top:3px;background-color:#252526;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}#fermenter_led1,#fermenter_led2,#fermenter_led3{width:96px;height:30px;float:left;text-align:center;margin-top:13px}#fermenter_toggle1{float:left;margin-left:29px;margin-top:20px}#fermenter_toggle2,#fermenter_toggle3{float:left;margin-left:60px;margin-top:20px}#fermenter_panel_buttons,#co2meter_panel_buttons,#ispindel_panel_buttons{width:290px;height:227px;float:right;margin:5px;margin-top:3px;background-color:#252526;border:2px solid;border-color:#59b4d4;border-radius:5px 5px 5px 5px}.LEDred_on{margin:5px auto;width:18px;height:18px;background-color:#F40;border-radius:50%;box-shadow:#000 0 0 4px 1px,inset #C33 0 -1px 5px,#f44 0 2px 12px}.LEDred_off{margin:5px auto;width:18px;height:18px;background-color:#820;border-radius:50%;box-shadow:#400 0 0 1px 1px}.LEDyellow_on{margin:5px auto;width:18px;height:18px;background-color:#FF0;border-radius:50%;box-shadow:#000 0 0 4px 1px,inset #860 0 -1px 5px,#DD0 0 2px 12px}.LEDyellow_off{margin:5px auto;width:18px;height:18px;background-color:#A90;border-radius:50%;box-shadow:#440 0 0 1px 1px}.LEDgreen_on{margin:5px auto;width:18px;height:18px;background-color:#5E0;border-radius:50%;box-shadow:#000 0 0 4px 1px,inset #270 0 -1px 5px,#5D0 0 2px 12px}.LEDgreen_off{margin:5px auto;width:18px;height:18px;background-color:#270;border-radius:50%;box-shadow:#250 0 0 1px 1px}.LEDblue_on{margin:5px auto;width:18px;height:18px;background-color:#4AF;border-radius:50%;box-shadow:#000 0 0 4px 1px,inset #247 0 -1px 5px,#48F 0 2px 12px}.LEDblue_off{margin:5px auto;width:18px;height:18px;background-color:#137;border-radius:50%;box-shadow:#024 0 0 1px 1px}#hintBase,#hintStyle,#hintEq{float:left;margin-top:30px;margin-right:20px;padding:5px;width:600px;color:yellow}#section{margin:5px}#baseButtonsWrapper{float:right;margin-top:30px;margin-right:10px;width:160px}#styleButtonsWrapper{float:right;margin-top:30px;margin-right:10px;width:320px}#eqButtonsWrapper{float:right;margin-top:30px;margin-right:10px;width:320px}#completedButtonsWrapper{float:right;margin-right:10px;width:320px;margin-top:160px}.nextButton{float:right;margin-left:0}.backButton{float:left;margin-left:10px}
\ No newline at end of file
--- a/www/includes/db_product.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/includes/db_product.php	Wed May 06 14:14:14 2020 +0200
@@ -464,28 +464,35 @@
 	$comma = FALSE;
 	if (isset($_POST['mashs'])) {
 		$array = $_POST['mashs'];
-		// Sort the array
-                $temp = array();
-                for ($i = 0; $i < count($array); $i++) {
-                        $temp[] = $array[$i]['step_temp'];
-                }
-                array_multisort($temp, SORT_ASC, SORT_NUMERIC, $array);
-                // Write the sorted array.
+                // Write the array and fix missing data.
 		foreach($array as $key => $item){
 			if ($comma)
 				$mashs .= ',';
 			$comma = TRUE;
 			$mash  = '{"step_name":"' . str_replace($rescapers,$rreplacements,$item['step_name']);
 			$mash .= '","step_type":' . $item['step_type'];
+			if (isset($item['step_volume']) && $item['step_volume'] != "")
+                                $mash .= ',"step_volume":' . $item['step_volume'];
+                        else
+                                $mash .= ',"step_volume":0';
 			if (isset($item['step_infuse_amount']) && $item['step_infuse_amount'] != "")
 				$mash .= ',"step_infuse_amount":' . $item['step_infuse_amount'];
 			else
 				$mash .= ',"step_infuse_amount":0';
+			if (isset($item['step_infuse_temp']) && $item['step_infuse_temp'] != "")
+				$mash .= ',"step_infuse_temp":' . $item['step_infuse_temp'];
+			else
+				$mash .= ',"step_infuse_temp":0';
 			$mash .= ',"step_temp":' . $item['step_temp'];
 			$mash .= ',"step_time":' . $item['step_time'];
 			$mash .= ',"ramp_time":' . $item['ramp_time'];
-			$mash .= ',"end_temp":' . $item['end_temp'] . '}';
-			//syslog(LOG_NOTICE, $mash);
+			$mash .= ',"end_temp":' . $item['end_temp'];
+			if (isset($item['step_wg_ratio']) && $item['step_wg_ratio'] != "")
+                                $mash .= ',"step_wg_ratio":' . $item['step_wg_ratio'];
+                        else
+                                $mash .= ',"step_wg_ratio":0';
+			$mash .= '}';
+			syslog(LOG_NOTICE, $mash);
 			$mashs .= $mash;
 		}
 	}
@@ -1003,6 +1010,7 @@
 			$brew .= ',"miscs":' . $row['json_miscs'];
 			$brew .= ',"yeasts":' . $row['json_yeasts'];
 		}
+syslog(LOG_NOTICE, $row['json_mashs']);
 		$brew .= ',"mashs":' . $row['json_mashs'];
 		$brew .= '}';
 //		syslog(LOG_NOTICE, $brew);
--- a/www/includes/db_recipes.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/includes/db_recipes.php	Wed May 06 14:14:14 2020 +0200
@@ -304,27 +304,34 @@
 	$comma = FALSE;
 	if (isset($_POST['mashs'])) {
 		$array = $_POST['mashs'];
-		// Sort the array
-                $temp = array();
-                for ($i = 0; $i < count($array); $i++) {
-                        $temp[] = $array[$i]['step_temp'];
-                }
-                array_multisort($temp, SORT_ASC, SORT_NUMERIC, $array);
-                // Write the sorted array.
+                // Write the array and fix missing data.
 		foreach($array as $key => $item) {
 			if ($comma)
 				$mashs .= ',';
 			$comma = TRUE;
 			$mash  = '{"step_name":"' . str_replace($rescapers,$rreplacements,$item['step_name']);
 			$mash .= '","step_type":' . $item['step_type'];
+			if (isset($item['step_volume']) && $item['step_volume'] != "")
+				$mash .= ',"step_volume":' . $item['step_volume'];
+			else
+				$mash .= ',"step_volume":0';
 			if (isset($item['step_infuse_amount']) && $item['step_infuse_amount'] != "")
 				$mash .= ',"step_infuse_amount":' . $item['step_infuse_amount'];
 			else
 				$mash .= ',"step_infuse_amount":0';
+			if (isset($item['step_infuse_temp']) && $item['step_infuse_temp'] != "")
+				$mash .= ',"step_infuse_temp":' . $item['step_infuse_temp'];
+			else
+				$mash .= ',"step_infuse_temp":0';
 			$mash .= ',"step_temp":' . $item['step_temp'];
 			$mash .= ',"step_time":' . $item['step_time'];
 			$mash .= ',"ramp_time":' . $item['ramp_time'];
-			$mash .= ',"end_temp":' . $item['end_temp'] . '}';
+			$mash .= ',"end_temp":' . $item['end_temp'];
+			if (isset($item['step_wg_ratio']) && $item['step_wg_ratio'] != "")
+				$mash .= ',"step_wg_ratio":' . $item['step_wg_ratio'];
+			else
+				$mash .= ',"step_wg_ratio":0';
+			$mash .= '}';
 			$mashs .= $mash;
 			//syslog(LOG_NOTICE, $mash);
 		}
--- a/www/includes/global.inc.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/includes/global.inc.php	Wed May 06 14:14:14 2020 +0200
@@ -127,7 +127,7 @@
  <head>
   <meta http-equiv="content-type" content="text/html; charset=utf-8" />
   <title>BMS v<?php echo $my_version;?> - <?php echo $title;?></title>
-  <link type="text/css" href="css/style-min.css" rel="stylesheet" media="all" />
+  <link type="text/css" href="css/style.css" rel="stylesheet" media="all" />
   <link type="text/css" href="jqwidgets/styles/jqx.base.css" rel="stylesheet" />
   <link type="text/css" href="jqwidgets/styles/jqx.<?php echo $my_style; ?>.css" rel="stylesheet" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
--- a/www/js/prod_edit.js	Fri May 01 21:37:23 2020 +0200
+++ b/www/js/prod_edit.js	Wed May 06 14:14:14 2020 +0200
@@ -121,6 +121,9 @@
  dataRecord = {},
  url = 'includes/db_product.php',
  MaltVolume = 0.87,    // l/kg 0.688 volgens internetbronnen, gemeten 0.874 l/kg, na enige tijd maischen 0,715 l/kg (A3 Otten).
+ SpecificHeatWater = 1.0,
+ SpecificHeatMalt = 0.399, //cal/g.°C
+ SlakingHeat = 10.318, //cal/g.°C
 
  // Prepare the data
  source = {
@@ -1473,10 +1476,12 @@
    datafields: [
     { name: 'step_name', type: 'string' },
     { name: 'step_type', type: 'int' },
+    { name: 'step_volume', type: 'float' },
     { name: 'step_infuse_amount', type: 'float' },
+    { name: 'step_infuse_temp', type: 'float' },
     { name: 'step_temp', type: 'float' },
     { name: 'step_time', type: 'float' },
-    { name: 'step_thickness', type: 'float' },
+    { name: 'step_wg_ratio', type: 'float' },
     { name: 'ramp_time', type: 'float' },
     { name: 'end_temp', type: 'float' }
    ],
@@ -1491,7 +1496,7 @@
      row = records[i];
      if (row.step_type == 0) // Infusion
       mash_infuse += parseFloat(row.step_infuse_amount);
-     row.step_thickness = 0; // Init this field.
+     row.step_wg_ratio = 0; // Init this field.
      data.push(row);
     }
    },
@@ -1514,16 +1519,21 @@
      row['step_name'] = 'Stap ' + (rowscount + 1);
      if (rowscount > 0) {
       row['step_type'] = 1;
+      row['step_infuse_amount'] = 0;
+      row['step_volume'] = mash_infuse;
      } else {
       row['step_type'] = 0;
       row['step_infuse_amount'] = 15;
+      row['step_volume'] = 15;
      }
+     row['step_infuse_temp'] = 0;
      row['step_temp'] = 62.0;
      row['step_time'] = 20.0;
-     row['step_thickness'] = 0;
+     row['step_wg_ratio'] = 0;
      row['ramp_time'] = 1.0;
      row['end_temp'] = 62.0;
      $('#mashGrid').jqxGrid('addrow', null, row);
+     calcMash();
     });
     // delete selected step.
     $('#sdeleterowbutton').jqxButton({ template: 'danger', theme: theme, height: 27, width: 150, disabled: (dataRecord.stage > 3) });
@@ -1533,6 +1543,7 @@
      if (selectedrowindex >= 0 && selectedrowindex < rowscount) {
       id = $('#mashGrid').jqxGrid('getrowid', selectedrowindex);
       $('#mashGrid').jqxGrid('deleterow', id);
+      calcMash();
      }
     });
    },
@@ -1563,9 +1574,57 @@
     { text: 'Eind &deg;C', datafield: 'end_temp', width: 90, align: 'right', cellsalign: 'right', cellsformat: 'f1' },
     { text: 'Rust min.', datafield: 'step_time', width: 90, align: 'right', cellsalign: 'right' },
     { text: 'Stap min.', datafield: 'ramp_time', width: 90, align: 'right', cellsalign: 'right' },
-    { text: 'Infuse L.', datafield: 'step_infuse_amount', width: 90, align: 'right', cellsalign: 'right', cellsformat: 'f1' },
-    { text: 'L/Kg.', datafield: 'step_thickness', width: 90, align: 'right', cellsalign: 'right', cellsformat: 'f2' },
-    { text: '', datafield: 'Edit', columntype: 'button', width: 100, align: 'center',
+    { text: 'Inf/dec L.', datafield: 'step_infuse_amount', width: 90, align: 'right',
+      cellsrenderer: function(row, columnfield, value, defaulthtml, columnproperties, rowdata) {
+       if (rowdata.step_type == 1)
+        return '<span></span>';
+       var color = '#ffffff';
+       var mvol = mashkg * MaltVolume;
+       if ((rowdata.step_wg_ratio * mashkg + mvol) > dataRecord.eq_tun_volume)
+        color = '#ff4040';
+       return '<span style="margin: 4px; margin-top: 6px; float: right; color: ' + color + ';">' + dataAdapter.formatNumber(value, 'f1') + '</span>';
+      }
+    },
+    { text: 'Inf/dec &deg;C', datafield: 'step_infuse_temp', width: 90, align: 'right',
+      cellsrenderer: function(row, columnfield, value, defaulthtml, columnproperties, rowdata) {
+       if (rowdata.step_type == 1)
+        return '<span></span>';
+       return '<span style="margin: 4px; margin-top: 6px; float: right;">' + dataAdapter.formatNumber(value, 'f2') + '</span>';
+      }
+    },
+    { text: 'L/Kg.', datafield: 'step_wg_ratio', width: 90, align: 'right',
+      cellsrenderer: function(row, columnfield, value, defaulthtml, columnproperties, rowdata) {
+       var color = '#ffffff';
+       if (value < 2.0 || value > 6.0)
+        color = '#ff4040';
+       return '<span style="margin: 4px; margin-top: 6px; float: right; color: ' + color + ';">' + dataAdapter.formatNumber(value, 'f2') + '</span>';
+      }
+    },
+    { text: '', columntype: 'button', width: 15, align: 'center',
+     cellsrenderer: function(row) {
+      if (row < 2)
+       return ' ';
+      return '▴';
+     }, buttonclick: function(row) {
+      if (row >= 2) {
+       swapMash(row, row-1);
+      }
+     }
+    },
+    { text: '', columntype: 'button', width: 15, align: 'center',
+     cellsrenderer: function(row) {
+      rowscount = $('#mashGrid').jqxGrid('getdatainformation').rowscount;
+      if (row < 1 || row > (rowscount -2))
+       return ' ';
+      return '▾';
+     }, buttonclick: function(row) {
+      rowscount = $('#mashGrid').jqxGrid('getdatainformation').rowscount;
+      if (row >= 1 && row <= (rowscount -2)) {
+       swapMash(row, row+1);
+      }
+     }
+    },
+    { text: '', datafield: 'Edit', columntype: 'button', width: 80, align: 'center',
      cellsrenderer: function() {
       return 'Wijzig';
      }, buttonclick: function(row) {
@@ -1574,19 +1633,30 @@
       } else {
        mashRow = row;
        mashData = $('#mashGrid').jqxGrid('getrowdata', mashRow);
+       if (mashRow == 0)
+        $("#wstep_type").jqxDropDownList('disableAt', 2);
+       else
+        $("#wstep_type").jqxDropDownList('enableAt', 2);
        $('#wstep_name').val(mashData.step_name);
        $('#wstep_type').val(mashData.step_type);
        $('#wstep_infuse_amount').val(mashData.step_infuse_amount);
+       $('#wstep_infuse_temp').val(mashData.step_infuse_temp);
        $('#wstep_temp').val(mashData.step_temp);
        $('#wend_temp').val(mashData.end_temp);
        $('#wstep_time').val(mashData.step_time);
        $('#wramp_time').val(mashData.ramp_time);
+       $('#wstep_infuse_amount').hide(); // Hide all untile we need it.
+       $('#wstep_infuse_temp').hide();
+       $('#wstep_pmpt_amount').hide();
+       $('#wstep_pmpt_temp').hide();
        if (mashData.step_type == 0) {
-        $('#wstep_infuse_amount').show();
-        $('#wstep_pmpt').show();
-       } else {
-        $('#wstep_infuse_amount').hide();
-        $('#wstep_pmpt').hide();
+        if (mashRow == 0) {
+         $('#wstep_infuse_amount').show();
+         $('#wstep_pmpt_amount').show();
+        } else {
+         $('#wstep_infuse_temp').show();
+         $('#wstep_pmpt_temp').show();
+        }
        }
        // show the popup window.
        $('#popupMash').jqxWindow('open');
@@ -1619,6 +1689,41 @@
   }
  }
 
+ function swapMash(r1, r2) {
+
+  console.log('swap mash rows ' + r1 + ' ' + r2);
+  var row1 = $('#mashGrid').jqxGrid('getrowdata', r1);
+  var row2 = $('#mashGrid').jqxGrid('getrowdata', r2);
+  var obj1 = { step_name: row1.step_name, step_type: row1.step_type, step_volume: row1.step_volume, step_infuse_amount: row1.step_infuse_amount,
+               step_infuse_temp: row1.step_infuse_temp, step_temp: row1.step_temp, step_time: row1.step_time,
+               ramp_time: row1.ramp_time, end_temp: row1.end_temp, step_wg_ratio: row1.step_wg_ratio };
+  var obj2 = { step_name: row2.step_name, step_type: row2.step_type, step_volume: row2.step_volume, step_infuse_amount: row2.step_infuse_amount,
+               step_infuse_temp: row2.step_infuse_temp, step_temp: row2.step_temp, step_time: row2.step_time,
+               ramp_time: row2.ramp_time, end_temp: row2.end_temp, step_wg_ratio: row2.step_wg_ratio };
+  $("#mashGrid").jqxGrid('updaterow', r1, obj2);
+  $("#mashGrid").jqxGrid('updaterow', r2, obj1);
+ }
+
+ function infusionVol(step_infused, step_mashkg, infuse_temp, step_temp, last_temp) {
+  var a = last_temp * (dataRecord.eq_tun_weight * dataRecord.eq_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt);
+  var b = step_temp * (dataRecord.eq_tun_weight * dataRecord.eq_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt);
+  //console.log('b - a: ' + (b - a) + ' t: ' + ((infuse_temp - step_temp) * SpecificHeatWater));
+  //console.log('res: ' + ((b - a) / ((infuse_temp - step_temp) * SpecificHeatWater)));
+  var vol = Round(((b - a) / ((infuse_temp - step_temp) * SpecificHeatWater)), 2);
+  console.log('infusionVol(' + step_infused + ', ' + step_mashkg + ', ' + infuse_temp + ', ' + step_temp + ', ' + last_temp + '): ' + vol);
+  return vol;
+ }
+
+ function decoctionVol(step_volume, step_temp, prev_temp) {
+  var a = (dataRecord.eq_tun_weight * dataRecord.eq_tun_specific_heat + step_volume * SpecificHeatWater) * (step_temp - prev_temp);
+  var b = SpecificHeatWater * (99 - step_temp);
+  var vol = 0;
+  if (b > 0)
+   vol = Round(a / b, 6);
+  console.log('decoctionVol(' + step_volume + ', ' + step_temp + ', ' + prev_temp + '): ' + vol);
+  return vol;
+ }
+
  function calcViability() {
   var vpm = 1.00;
   var max = 100;
@@ -1985,15 +2090,52 @@
 
  function calcMash() {
 
-  var h, m, infused = 0, mashtime = 0, mashvol = 0, i, row;
+  var h, m, infused = 0, mashtime = 0, mashvol = 0, vol, i, j, n, a, b, row, temp;
+  var lasttemp = 18.0;
+  var graintemp = 18.0;
+  var tuntemp = 18.0;
 
   if ((rows = $('#mashGrid').jqxGrid('getrows')) && (mashkg > 0)) {
+   console.log('calcMash()');
    for (i = 0; i < rows.length; i++) {
     row = $('#mashGrid').jqxGrid('getrowdata', i);
-    if (row.step_type == 0) // Infusion
+    if (row.step_type == 0) { // Infusion
+     if (i == 0) {
+      // First mash step, temperature from the mashtun and malt.
+      n = 20; // tun is preheated.
+      tuntemp = row.step_temp;
+      for (j = 0; j < n; j++) {
+       a = mashkg * graintemp * SpecificHeatMalt + dataRecord.eq_tun_weight * tuntemp * dataRecord.eq_tun_specific_heat;
+       b = row.step_temp * (dataRecord.eq_tun_weight * dataRecord.eq_tun_specific_heat + row.step_infuse_amount * SpecificHeatWater + mashkg * SpecificHeatMalt) - SlakingHeat * mashkg;
+       if (row.step_infuse_amount > 0) {
+        temp = (b - a) / (row.step_infuse_amount * SpecificHeatWater);
+       } else {
+        temp = 99;
+       }
+       tuntemp += (temp - tuntemp) / 2;
+       row.step_infuse_temp = Round(temp, 6);
+      }
+      console.log('init infuse temp: ' + row.step_infuse_temp);
+     } else {
+      // Calculate amount of infusion water.
+      row.step_infuse_amount = infusionVol(infused, mashkg, row.step_infuse_temp, row.step_temp, lasttemp);
+      //console.log('vol: ' + row.step_infuse_amount + ' temp: ' + row.step_infuse_temp);
+     }
      infused += row.step_infuse_amount;
+    } else if (row.step_type == 1) { // Temperature
+     if (i > 0)
+      row.step_infuse_amount = 0;
+     row.step_infuse_temp = 0;
+    } else if (row.step_type == 2) { // Decoction
+     row.step_infuse_amount = decoctionVol(infused, row.step_temp, lasttemp);
+     row.step_infuse_temp = 99;
+    }
+    row.step_volume = infused;
+    //console.log(i + ' type: ' + row.step_type + ' volume: ' + row.step_infuse_amount + ' temp: ' + row.step_infuse_temp);
+    lasttemp = row.step_temp;
     mashtime += row.step_time + row.ramp_time;
-    $('#mashGrid').jqxGrid('setcellvalue', i, 'step_thickness', infused / mashkg);
+    row.step_wg_ratio = Round(infused / mashkg, 6);
+    $('#mashGrid').jqxGrid('updaterow', i, row);
    }
   }
   mashvol = mashkg * MaltVolume + infused;
@@ -5399,7 +5541,7 @@
  });
  $('#mash_select').on('select', function(event) {
   if (event.args) {
-   var data, datarecord, rowIDs, rows, i, row, index = event.args.index;
+   var infused = 0, data, datarecord, rowIDs, rows, i, row, index = event.args.index;
    // First delete all current steps
    rowIDs = new Array();
    rows = $('#mashGrid').jqxGrid('getdisplayrows');
@@ -5415,22 +5557,29 @@
     data = datarecord.steps[i];
     row = {};
     row['step_name'] = data.step_name;
-    row['step_type'] = data.step_type;
-    // For now, but this must be smarter.
-    if (mash_infuse == 0 && dataRecord.w1_amount > 0)
-     mash_infuse = dataRecord.w1_amount;
-    if (i == 0)
-     row['step_infuse_amount'] = mash_infuse;
+    row['step_type'] = parseInt(data.step_type);
+    row['step_temp'] = parseFloat(data.step_temp);
+    row['end_temp'] = parseFloat(data.end_temp);
+    row['step_time'] = parseFloat(data.step_time);
+    row['ramp_time'] = parseFloat(data.ramp_time);
+    row['step_infuse_temp'] = 0.0;
+    row['step_infuse_amount'] = 0.0;
+    if (mash_infuse == 0 && dataRecord.wg_amount > 0)
+     mash_infuse = dataRecord.wg_amount;
+    if (data.step_type == 0) { // Infusion
+     if (i == 0) {
+      row['step_infuse_amount'] = parseFloat(mash_infuse);
+     } else {
+      row['step_infuse_temp'] = 99.0;
+     }
+    }
+    //console.log(i + ' type: ' + row['step_type'] + ' start infusion: ' + parseFloat(row['step_infuse_amount']) + ' mash_infuse: ' + mash_infuse);
+    infused += parseFloat(row['step_infuse_amount']);
+    row['step_volume'] = infused;
+    if (mashkg > 0)
+     row['step_wg_ratio'] = Round(parseFloat(mash_infuse / mashkg), 2);
     else
-     row['step_infuse_amount'] = 0;
-    if (mashkg > 0)
-     row['step_thickness'] = parseFloat(mash_infuse / mashkg);
-    else
-     row['step_thickness'] = 0;
-    row['step_temp'] = data.step_temp;
-    row['end_temp'] = data.end_temp;
-    row['step_time'] = data.step_time;
-    row['ramp_time'] = data.ramp_time;
+     row['step_wg_ratio'] = 0;
     $('#mashGrid').jqxGrid('addrow', null, row);
    }
    calcMash();
@@ -5442,7 +5591,7 @@
  $('#est_mashtime').jqxInput({ theme: theme, width: 70, height: 23 });
  $('#popupMash').jqxWindow({
   width: 800,
-  height: 350,
+  height: 375,
   position: { x: 230, y: 100 },
   resizable: false,
   theme: theme,
@@ -5453,12 +5602,15 @@
  });
  $('#MashReady').jqxButton({ template: 'success', width: '90px', theme: theme });
  $('#MashReady').click(function() {
+  calcFermentables();
+  calcWater();
+  calcSparge();
   calcMash();
  });
  $('#wstep_name').jqxInput({ theme: theme, width: 320, height: 23 });
  $('#wstep_name').on('change', function(event) {
   var rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
-  rowdata.step_name = event.args.value;
+  rowdata.step_name = $('#wstep_name').val();
  });
  $('#wstep_type').jqxDropDownList({
   theme: theme,
@@ -5473,29 +5625,62 @@
   if (event.args) {
    var rowdata, i, rows, row, index = event.args.index;
    rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
-   rowdata.step_type = index;
-   if (index == 0) {
-    $('#wstep_infuse_amount').show();
-    $('#wstep_pmpt').show();
-   } else {
-    rowdata.step_infuse_amount = 0;
+   if (rowdata.step_type != index) {
+    rowdata.step_type = index;
     $('#wstep_infuse_amount').hide();
-    $('#wstep_pmpt').hide();
-   }
-   mash_infuse = 0;
-   rows = $('#mashGrid').jqxGrid('getrows');
-   for (i = 0; i < rows.length; i++) {
-    row = rows[i];
-    if (row.step_type == 0) // Infusion
-     mash_infuse += parseFloat(row.step_infuse_amount);
+    $('#wstep_infuse_temp').hide();
+    $('#wstep_pmpt_amount').hide();
+    $('#wstep_pmpt_temp').hide();
+    if (index == 0) { // Infusion
+     if (mashRow == 0) {
+      $('#wstep_infuse_amount').show();
+      $('#wstep_pmpt_amount').show();
+     } else {
+      $('#wstep_infuse_temp').show();
+      $('#wstep_pmpt_temp').show();
+     }
+    }
+    if (index == 1) { // Temperature
+     if (mashRow > 0)
+      rowdata.step_infuse_amount = 0;
+     rowdata.step_infuse_temp = 0;
+    }
+    if (index == 2) { // Decoction
+     var rowprev = $('#mashGrid').jqxGrid('getrowdata', mashRow-1);
+     rowdata.step_infuse_temp = 99;
+     rowdata.step_infuse_amount = decoctionVol(rowdata.step_volume, rowdata.step_temp, rowprev.end_temp);
+     console.log('decoction: ' + rowdata.step_infuse_amount + '/' + rowdata.step_infuse_temp);
+    }
+    $('#mashGrid').jqxGrid('updaterow', mashRow, rowdata);
+    mash_infuse = 0;
+    rows = $('#mashGrid').jqxGrid('getrows');
+    for (i = 0; i < rows.length; i++) {
+     row = rows[i];
+     if (row.step_type == 0) // Infusion
+      mash_infuse += parseFloat(row.step_infuse_amount);
+    }
+    calcMash();
    }
   }
  });
  $('#wstep_temp').on('change', function(event) {
   var rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
-  rowdata.step_temp = parseFloat(event.args.value);
+  if (rowdata.step_type == 2) { // Decoction
+   var rowprev = $('#mashGrid').jqxGrid('getrowdata', mashRow-1);
+   var a = (dataRecord.eq_tun_weight * dataRecord.eq_tun_specific_heat + rowdata.step_volume * SpecificHeatWater) *
+           (parseFloat(event.args.value) - rowprev.end_temp);
+   var b = SpecificHeatWater * (99 - parseFloat(event.args.value));
+   if (b > 0) {
+    rowdata.step_temp = parseFloat(event.args.value);
+    rowdata.step_infuse_amount = Round(a / b, 2);
+   } else
+    rowdata.step_infuse_amount = 0;
+   console.log('change temp ' + rowdata.step_temp + ' decoction: ' + rowdata.step_infuse_amount + '/' + rowdata.step_infuse_temp);
+  } else {
+   rowdata.step_temp = parseFloat(event.args.value);
+  }
  });
- $('#wstep_temp,#wend_temp,#wstep_infuse_amount').jqxNumberInput(Spin1dec);
+ $('#wstep_temp,#wend_temp,#wstep_infuse_amount,#wstep_infuse_temp').jqxNumberInput(Spin1dec);
  $('#wend_temp').on('change', function(event) {
   var rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
   rowdata.end_temp = parseFloat(event.args.value);
@@ -5514,29 +5699,40 @@
  $('#wstep_infuse_amount').on('change', function(event) {
   var row, i, rows, rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
   rowdata.step_infuse_amount = parseFloat(event.args.value);
-  mash_infuse = 0;
-  rows = $('#mashGrid').jqxGrid('getrows');
-  for (i = 0; i < rows.length; i++) {
-   row = rows[i];
-   if (row.step_type == 0) // Infusion
-    mash_infuse += parseFloat(row.step_infuse_amount);
+  if (mashRow == 0) {
+   rowdata.step_infuse_amount = parseFloat(event.args.value);
+   mash_infuse = 0;
+   rows = $('#mashGrid').jqxGrid('getrows');
+   for (i = 0; i < rows.length; i++) {
+    row = rows[i];
+    if (row.step_type == 0) // Infusion
+     mash_infuse += parseFloat(row.step_infuse_amount);
+   }
+   if (dataRecord.w2_amount == 0) {
+    dataRecord.w1_amount = mash_infuse;
+    $('#w1_amount').val(mash_infuse);
+   } else {
+    var w1_amount = (dataRecord.w1_amount / (dataRecord.w1_amount + dataRecord.w2_amount)) * mash_infuse;
+    var w2_amount = (dataRecord.w2_amount / (dataRecord.w1_amount + dataRecord.w2_amount)) * mash_infuse;
+    dataRecord.w1_amount = Round(w1_amount, 3);
+    dataRecord.w2_amount = Round(w2_amount, 3);
+    $('#w1_amount').val(dataRecord.w1_amount);
+    $('#w2_amount').val(dataRecord.w2_amount);
+   }
+   $('#wg_amount').val(mash_infuse);
+   console.log('new infuse amount: ' + mash_infuse);
+   calcWater();
+//   calcMash();
   }
-  if (dataRecord.w2_amount == 0) {
-   dataRecord.w1_amount = mash_infuse;
-   $('#w1_amount').val(mash_infuse);
-  } else {
-   var w1_amount = (dataRecord.w1_amount / (dataRecord.w1_amount + dataRecord.w2_amount)) * mash_infuse;
-   var w2_amount = (dataRecord.w2_amount / (dataRecord.w1_amount + dataRecord.w2_amount)) * mash_infuse;
-   dataRecord.w1_amount = Round(w1_amount, 3);
-   dataRecord.w2_amount = Round(w2_amount, 3);
-   $('#w1_amount').val(dataRecord.w1_amount);
-   $('#w2_amount').val(dataRecord.w2_amount);
-  }
-  $('#wg_amount').val(mash_infuse);
-  calcFermentables();
-  calcWater();
-  calcSparge();
-  calcMash();
+ });
+ $('#wstep_infuse_temp').on('change', function(event) {
+  var prevdata = $('#mashGrid').jqxGrid('getrowdata', mashRow-1);
+  var rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
+  rowdata.step_infuse_temp = parseFloat(event.args.value);
+  var vol = infusionVol(prevdata.step_volume, mashkg, rowdata.step_infuse_temp, rowdata.step_temp, prevdata.end_temp);
+  console.log('new vol: ' + vol);
+  rowdata.step_infuse_amount = vol;
+  $('#wstep_infuse_amount').val(vol);
  });
 
  // Tab 8, Water
--- a/www/js/rec_edit.js	Fri May 01 21:37:23 2020 +0200
+++ b/www/js/rec_edit.js	Wed May 06 14:14:14 2020 +0200
@@ -48,7 +48,12 @@
  MMNaHCO3 = 84.007,
  MMNa2CO3 = 105.996,
  MMNaCl = 58.443,
- MMCaOH2 = 74.06268;
+ MMCaOH2 = 74.06268,
+ SpecificHeatWater = 1.0,
+ SpecificHeatMalt = 0.399, //cal/g.°C
+ SlakingHeat = 10.318, //cal/g.°C
+ eq_tun_weight = 2.0, // 2 Kg pot
+ eq_tun_specific_heat = 0.110, // Stainless Steel
  data_loaded = 0;
 
 function createDelElements() {
@@ -348,18 +353,90 @@
 }
 
 
+function swapMash(r1, r2) {
+
+ console.log('swap mash rows ' + r1 + ' ' + r2);
+ var row1 = $('#mashGrid').jqxGrid('getrowdata', r1);
+ var row2 = $('#mashGrid').jqxGrid('getrowdata', r2);
+ var obj1 = { step_name: row1.step_name, step_type: row1.step_type, step_volume: row1.step_volume, step_infuse_amount: row1.step_infuse_amount,
+              step_infuse_temp: row1.step_infuse_temp, step_temp: row1.step_temp, step_time: row1.step_time,
+              ramp_time: row1.ramp_time, end_temp: row1.end_temp, step_wg_ratio: row1.step_wg_ratio };
+ var obj2 = { step_name: row2.step_name, step_type: row2.step_type, step_volume: row2.step_volume, step_infuse_amount: row2.step_infuse_amount,
+              step_infuse_temp: row2.step_infuse_temp, step_temp: row2.step_temp, step_time: row2.step_time,
+              ramp_time: row2.ramp_time, end_temp: row2.end_temp, step_wg_ratio: row2.step_wg_ratio };
+ $("#mashGrid").jqxGrid('updaterow', r1, obj2);
+ $("#mashGrid").jqxGrid('updaterow', r2, obj1);
+}
+
+
+function infusionVol(step_infused, step_mashkg, infuse_temp, step_temp, last_temp) {
+ var a = last_temp * (eq_tun_weight * eq_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt);
+ var b = step_temp * (eq_tun_weight * eq_tun_specific_heat + step_infused * SpecificHeatWater + step_mashkg * SpecificHeatMalt);
+ var vol = Round(((b - a) / ((infuse_temp - step_temp) * SpecificHeatWater)), 2);
+ console.log('infusionVol(' + step_infused + ', ' + step_mashkg + ', ' + infuse_temp + ', ' + step_temp + ', ' + last_temp + '): ' + vol);
+ return vol;
+}
+
+
+function decoctionVol(step_volume, step_temp, prev_temp) {
+ var a = (eq_tun_weight * eq_tun_specific_heat + step_volume * SpecificHeatWater) * (step_temp - prev_temp);
+ var b = SpecificHeatWater * (99 - step_temp);
+ var vol = 0;
+ if (b > 0)
+  vol = Round(a / b, 6);
+ console.log('decoctionVol(' + step_volume + ', ' + step_temp + ', ' + prev_temp + '): ' + vol);
+ return vol;
+}
+
+
 function calcMash() {
- var infused = 0, i, row, rows;
- if (!(rows = $('#mashGrid').jqxGrid('getrows')))
-  return;
- if (mashkg == 0)
-  return;
+ var infused = 0, vol, i, j, n, a, b, row, rows, temp;
+ var lasttemp = 18.0;
+ var graintemp = 18.0;
+ var tuntemp = 18.0;
 
- for (i = 0; i < rows.length; i++) {
-  row = $('#mashGrid').jqxGrid('getrowdata', i);
-  if (row.step_type == 0) // Infusion
-   infused += row.step_infuse_amount;
-  $('#mashGrid').jqxGrid('setcellvalue', i, 'step_thickness', infused / mashkg);
+ if ((rows = $('#mashGrid').jqxGrid('getrows')) && (mashkg > 0)) {
+  console.log('calcMash()');
+  for (i = 0; i < rows.length; i++) {
+   row = $('#mashGrid').jqxGrid('getrowdata', i);
+   if (row.step_type == 0) { // Infusion
+    if (i == 0) {
+      // First mash step, temperature from the mashtun and malt.
+      n = 20; // tun is preheated.
+      tuntemp = row.step_temp;
+      for (j = 0; j < n; j++) {
+       a = mashkg * graintemp * SpecificHeatMalt + eq_tun_weight * tuntemp * eq_tun_specific_heat;
+       b = row.step_temp * (eq_tun_weight * eq_tun_specific_heat + row.step_infuse_amount * SpecificHeatWater + mashkg * SpecificHeatMalt) - SlakingHeat * mashkg;
+       if (row.step_infuse_amount > 0) {
+        temp = (b - a) / (row.step_infuse_amount * SpecificHeatWater);
+       } else {
+        temp = 99;
+       }
+       tuntemp += (temp - tuntemp) / 2;
+       row.step_infuse_temp = Round(temp, 6);
+      }
+      console.log('init infuse temp: ' + row.step_infuse_temp);
+    } else {
+      // Calculate amount of infusion water.
+      row.step_infuse_amount = infusionVol(infused, mashkg, row.step_infuse_temp, row.step_temp, lasttemp);
+      //console.log('vol: ' + row.step_infuse_amount + ' temp: ' + row.step_infuse_temp);
+    }
+    infused += row.step_infuse_amount;
+   } else if (row.step_type == 1) { // Temperature
+     if (i > 0)
+      row.step_infuse_amount = 0;
+     row.step_infuse_temp = 0;
+   } else if (row.step_type == 2) { // Decoction
+     row.step_infuse_amount = decoctionVol(infused, row.step_temp, lasttemp);
+     row.step_infuse_temp = 99;
+   }
+    row.step_volume = infused;
+    //console.log(i + ' type: ' + row.step_type + ' volume: ' + row.step_infuse_amount + ' temp: ' + row.step_infuse_temp);
+    lasttemp = row.step_temp;
+    mashtime += row.step_time + row.ramp_time;
+    row.step_wg_ratio = Round(infused / mashkg, 6);
+    $('#mashGrid').jqxGrid('updaterow', i, row);
+  }
  }
 }
 
@@ -1685,18 +1762,6 @@
 
  // inline mash editor
  var editMash = function(data) {
-  var generaterow = function() {
-   var row = {};
-   row['step_name'] = 'Stap 1';
-   row['step_type'] = 0;
-   row['step_infuse_amount'] = 15;
-   row['step_temp'] = 62.0;
-   row['step_time'] = 20.0;
-   row['step_thickness'] = 0;
-   row['ramp_time'] = 1.0;
-   row['end_temp'] = 62.0;
-   return row;
-  };
   var mashSource = {
    localdata: data.mashs,
    datatype: 'local',
@@ -1705,19 +1770,17 @@
    datafields: [
     { name: 'step_name', type: 'string' },
     { name: 'step_type', type: 'int' },
+    { name: 'step_volume', type: 'float' },
     { name: 'step_infuse_amount', type: 'float' },
+    { name: 'step_infuse_temp', type: 'float' },
     { name: 'step_temp', type: 'float' },
     { name: 'step_time', type: 'float' },
-    { name: 'step_thickness', type: 'float' },
+    { name: 'step_wg_ratio', type: 'float' },
     { name: 'ramp_time', type: 'float' },
     { name: 'end_temp', type: 'float' }
    ],
-   addrow: function(rowid, rowdata, position, commit) {
-    commit(true);
-   },
-   deleterow: function(rowid, commit) {
-    commit(true);
-   }
+   addrow: function(rowid, rowdata, position, commit) { commit(true); },
+   deleterow: function(rowid, commit) { commit(true); }
   },
   mashAdapter = new $.jqx.dataAdapter(mashSource, {
    beforeLoadComplete: function(records) {
@@ -1727,7 +1790,7 @@
      row = records[i];
      if (row.step_type == 0) // Infusion
       mash_infuse += parseFloat(row.step_infuse_amount);
-     row.step_thickness = 0; // Init this field.
+     row.step_wg_ratio = 0; // Init this field.
      data.push(row);
     }
    },
@@ -1747,8 +1810,25 @@
     container.append('<input style="float: left; margin-left: 565px;" id="sdeleterowbutton" type="button" value="Verwijder stap" />');
     $('#saddrowbutton').jqxButton({ template: 'primary', theme: theme, height: 27, width: 150 });
     $('#saddrowbutton').on('click', function() {
-     var datarow = generaterow();
+     var row = {}, rowscount = $('#mashGrid').jqxGrid('getdatainformation').rowscount;
+     row['step_name'] = 'Stap ' + (rowscount + 1);
+     if (rowscount > 0) {
+      row['step_type'] = 1;
+      row['step_infuse_amount'] = 0;
+      row['step_volume'] = mash_infuse;
+     } else {
+      row['step_type'] = 0;
+      row['step_infuse_amount'] = 15;
+      row['step_volume'] = 15;
+     }
+     row['step_infuse_temp'] = 0;
+     row['step_temp'] = 62.0;
+     row['step_time'] = 20.0;
+     row['step_wg_ratio'] = 0;
+     row['ramp_time'] = 1.0;
+     row['end_temp'] = 62.0;
      $('#mashGrid').jqxGrid('addrow', null, datarow);
+     calcMash();
     });
     // delete selected yeast.
     $('#sdeleterowbutton').jqxButton({ template: 'danger', theme: theme, height: 27, width: 150 });
@@ -1758,6 +1838,7 @@
      if (selectedrowindex >= 0 && selectedrowindex < rowscount) {
       id = $('#mashGrid').jqxGrid('getrowid', selectedrowindex);
       $('#mashGrid').jqxGrid('deleterow', id);
+      calcMash();
      }
     });
    },
@@ -1779,27 +1860,82 @@
     { text: 'Eind &deg;C', datafield: 'end_temp', width: 90, align: 'right', cellsalign: 'right', cellsformat: 'f1' },
     { text: 'Rust min.', datafield: 'step_time', width: 90, align: 'right', cellsalign: 'right' },
     { text: 'Stap min.', datafield: 'ramp_time', width: 90, align: 'right', cellsalign: 'right' },
-    { text: 'Infuse L.', datafield: 'step_infuse_amount', width: 90, align: 'right', cellsalign: 'right', cellsformat: 'f1' },
-    { text: 'L/Kg.', datafield: 'step_thickness', width: 90, align: 'right', cellsalign: 'right', cellsformat: 'f2' },
-    { text: '', datafield: 'Edit', columntype: 'button', width: 100, align: 'center',
+    { text: 'Inf/dec L.', datafield: 'step_infuse_amount', width: 90, align: 'right',
+      cellsrenderer: function(row, columnfield, value, defaulthtml, columnproperties, rowdata) {
+       if (rowdata.step_type == 1)
+        return '<span></span>';
+       return '<span style="margin: 4px; margin-top: 6px; float: right;">' + dataAdapter.formatNumber(value, 'f1') + '</span>';
+      }
+    },
+    { text: 'Inf/dec &deg;C', datafield: 'step_infuse_temp', width: 90, align: 'right',
+      cellsrenderer: function(row, columnfield, value, defaulthtml, columnproperties, rowdata) {
+       if (rowdata.step_type == 1)
+        return '<span></span>';
+       return '<span style="margin: 4px; margin-top: 6px; float: right;">' + dataAdapter.formatNumber(value, 'f2') + '</span>';
+      }
+    },
+    { text: 'L/Kg.', datafield: 'step_wg_ratio', width: 90, align: 'right',
+      cellsrenderer: function(row, columnfield, value, defaulthtml, columnproperties, rowdata) {
+       var color = '#ffffff';
+       if (value < 2.0 || value > 6.0)
+        color = '#ff4040';
+       return '<span style="margin: 4px; margin-top: 6px; float: right; color: ' + color + ';">' + dataAdapter.formatNumber(value, 'f2') + '</span>';
+      }
+    },
+    { text: '', columntype: 'button', width: 15, align: 'center',
+     cellsrenderer: function(row) {
+      if (row < 2)
+       return ' ';
+      return '▴';
+     }, buttonclick: function(row) {
+      if (row >= 2) {
+       swapMash(row, row-1);
+      }
+     }
+    },
+    { text: '', columntype: 'button', width: 15, align: 'center',
+     cellsrenderer: function(row) {
+      rowscount = $('#mashGrid').jqxGrid('getdatainformation').rowscount;
+      if (row < 1 || row > (rowscount -2))
+       return ' ';
+      return '▾';
+     }, buttonclick: function(row) {
+      rowscount = $('#mashGrid').jqxGrid('getdatainformation').rowscount;
+      if (row >= 1 && row <= (rowscount -2)) {
+       swapMash(row, row+1);
+      }
+     }
+    },
+    { text: '', datafield: 'Edit', columntype: 'button', width: 80, align: 'center',
      cellsrenderer: function() {
       return 'Wijzig';
      }, buttonclick: function(row) {
       mashRow = row;
       mashData = $('#mashGrid').jqxGrid('getrowdata', mashRow);
+      if (mashRow == 0)
+       $("#wstep_type").jqxDropDownList('disableAt', 2);
+      else
+       $("#wstep_type").jqxDropDownList('enableAt', 2);
       $('#wstep_name').val(mashData.step_name);
       $('#wstep_type').val(mashData.step_type);
       $('#wstep_infuse_amount').val(mashData.step_infuse_amount);
+      $('#wstep_infuse_temp').val(mashData.step_infuse_temp);
       $('#wstep_temp').val(mashData.step_temp);
       $('#wend_temp').val(mashData.end_temp);
       $('#wstep_time').val(mashData.step_time);
       $('#wramp_time').val(mashData.ramp_time);
+      $('#wstep_infuse_amount').hide(); // Hide all untile we need it.
+      $('#wstep_infuse_temp').hide();
+      $('#wstep_pmpt_amount').hide();
+      $('#wstep_pmpt_temp').hide();
       if (mashData.step_type == 0) {
-       $('#wstep_infuse_amount').show();
-       $('#wstep_pmpt').show();
-      } else {
-       $('#wstep_infuse_amount').hide();
-       $('#wstep_pmpt').hide();
+       if (mashRow == 0) {
+        $('#wstep_infuse_amount').show();
+        $('#wstep_pmpt_amount').show();
+       } else {
+        $('#wstep_infuse_temp').show();
+        $('#wstep_pmpt_temp').show();
+       }
       }
       // show the popup window.
       $('#popupMash').jqxWindow('open');
@@ -1826,7 +1962,6 @@
   theme: theme
  });
 
-
  function setWaterAgent(name, amount) {
 
   var record, records, miscs, i, id, row, found = false, rows = $('#miscGrid').jqxGrid('getrows');
@@ -3582,7 +3717,7 @@
  });
  $('#mash_select').on('select', function(event) {
   if (event.args) {
-   var data, datarecord, i, row, rows, rowIDs, index = event.args.index;
+   var infused = 0, data, datarecord, i, row, rows, rowIDs, index = event.args.index;
    // First delete all current steps
    rowIDs = new Array();
    rows = $('#mashGrid').jqxGrid('getdisplayrows');
@@ -3598,29 +3733,37 @@
     data = datarecord.steps[i];
     row = {};
     row['step_name'] = data.step_name;
-    row['step_type'] = data.step_type;
-    // For now, but this must be smarter.
-    if (mash_infuse == 0 && dataRecord.w1_amount > 0)
-     mash_infuse = dataRecord.w1_amount;
-    if (i == 0)
-     row['step_infuse_amount'] = mash_infuse;
+    row['step_type'] = parseInt(data.step_type);
+    row['step_temp'] = parseFloat(data.step_temp);
+    row['end_temp'] = parseFloat(data.end_temp);
+    row['step_time'] = parseFloat(data.step_time);
+    row['ramp_time'] = parseFloat(data.ramp_time);
+    row['step_infuse_temp'] = 0.0;
+    row['step_infuse_amount'] = 0.0;
+    if (mash_infuse == 0 && dataRecord.wg_amount > 0)
+     mash_infuse = dataRecord.wg_amount;
+    if (data.step_type == 0) { // Infusion
+     if (i == 0) {
+      row['step_infuse_amount'] = parseFloat(mash_infuse);
+     } else {
+      row['step_infuse_temp'] = 99.0;
+     }
+    }
+    //console.log(i + ' type: ' + row['step_type'] + ' start infusion: ' + parseFloat(row['step_infuse_amount']) + ' mash_infuse: ' + mash_infuse);
+    infused += parseFloat(row['step_infuse_amount']);
+    row['step_volume'] = infused;
+    if (mashkg > 0)
+     row['step_wg_ratio'] = Round(parseFloat(mash_infuse / mashkg), 2);
     else
-     row['step_infuse_amount'] = 0;
-    row['step_temp'] = data.step_temp;
-    if (mashkg > 0)
-     row['step_thickness'] = parseFloat(mash_infuse / mashkg);
-    else
-     row['step_thickness'] = 0;
-    row['end_temp'] = data.end_temp;
-    row['step_time'] = data.step_time;
-    row['ramp_time'] = data.ramp_time;
+     row['step_wg_ratio'] = 0;
     $('#mashGrid').jqxGrid('addrow', null, row);
    }
+   calcMash();
   }
  });
  $('#popupMash').jqxWindow({
   width: 800,
-  height: 350,
+  height: 375,
   position: { x: 230, y: 100 },
   resizable: false,
   theme: theme,
@@ -3636,7 +3779,7 @@
  $('#wstep_name').jqxInput({ theme: theme, width: 320, height: 23 });
  $('#wstep_name').on('change', function(event) {
   var rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
-  rowdata.step_name = event.args.value;
+  rowdata.step_name = $('#wstep_name').val();
  });
  $('#wstep_type').jqxDropDownList({
   theme: theme,
@@ -3651,28 +3794,61 @@
   if (event.args) {
    var rowdata, rows, i, row, index = event.args.index;
    rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
-   rowdata.step_type = index;
-   if (index == 0) {
-    $('#wstep_infuse_amount').show();
-    $('#wstep_pmpt').show();
-   } else {
-    rowdata.step_infuse_amount = 0;
+   if (rowdata.step_type != index) {
+    rowdata.step_type = index;
     $('#wstep_infuse_amount').hide();
-    $('#wstep_pmpt').hide();
-   }
-   mash_infuse = 0;
-   rows = $('#mashGrid').jqxGrid('getrows');
-   for (i = 0; i < rows.length; i++) {
-    row = rows[i];
-    if (row.step_type == 0) // Infusion
-     mash_infuse += parseFloat(row.step_infuse_amount);
+    $('#wstep_infuse_temp').hide();
+    $('#wstep_pmpt_amount').hide();
+    $('#wstep_pmpt_temp').hide();
+    if (index == 0) { // Infusion
+     if (mashRow == 0) {
+      $('#wstep_infuse_amount').show();
+      $('#wstep_pmpt_amount').show();
+     } else {
+      $('#wstep_infuse_temp').show();
+      $('#wstep_pmpt_temp').show();
+     }
+    }
+    if (index == 1) { // Temperature
+     if (mashRow > 0)
+      rowdata.step_infuse_amount = 0;
+     rowdata.step_infuse_temp = 0;
+    }
+    if (index == 2) { // Decoction
+     var rowprev = $('#mashGrid').jqxGrid('getrowdata', mashRow-1);
+     rowdata.step_infuse_temp = 99;
+     rowdata.step_infuse_amount = decoctionVol(rowdata.step_volume, rowdata.step_temp, rowprev.end_temp);
+     console.log('decoction: ' + rowdata.step_infuse_amount + '/' + rowdata.step_infuse_temp);
+    }
+    $('#mashGrid').jqxGrid('updaterow', mashRow, rowdata);
+    mash_infuse = 0;
+    rows = $('#mashGrid').jqxGrid('getrows');
+    for (i = 0; i < rows.length; i++) {
+     row = rows[i];
+     if (row.step_type == 0) // Infusion
+      mash_infuse += parseFloat(row.step_infuse_amount);
+    }
+    calcMash();
    }
   }
  });
  $('#wstep_temp').jqxNumberInput(Spin1dec);
  $('#wstep_temp').on('change', function(event) {
   var rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
-  rowdata.step_temp = parseFloat(event.args.value);
+  if (rowdata.step_type == 2) { // Decoction
+   var rowprev = $('#mashGrid').jqxGrid('getrowdata', mashRow-1);
+   var a = (eq_tun_weight * eq_tun_specific_heat + rowdata.step_volume * SpecificHeatWater) *
+           (parseFloat(event.args.value) - rowprev.end_temp);
+   var b = SpecificHeatWater * (99 - parseFloat(event.args.value));
+   if (b > 0) {
+    rowdata.step_temp = parseFloat(event.args.value);
+    rowdata.step_infuse_amount = Round(a / b, 2);
+   } else
+    rowdata.step_infuse_amount = 0;
+   console.log('change temp ' + rowdata.step_temp + ' decoction: ' + rowdata.step_infuse_amount + '/' + rowdata.step_infuse_temp);
+  } else {
+   rowdata.step_temp = parseFloat(event.args.value);
+  }
  });
  $('#wend_temp').jqxNumberInput(Spin1dec);
  $('#wend_temp').on('change', function(event) {
@@ -3693,23 +3869,40 @@
  $('#wstep_infuse_amount').on('change', function(event) {
   var i, rows, row, rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
   rowdata.step_infuse_amount = parseFloat(event.args.value);
-  mash_infuse = 0;
-  rows = $('#mashGrid').jqxGrid('getrows');
-  for (i = 0; i < rows.length; i++) {
-   row = rows[i];
-   if (row.step_type == 0) // Infusion
-    mash_infuse += parseFloat(row.step_infuse_amount);
+  if (mashRow == 0) {
+   rowdata.step_infuse_amount = parseFloat(event.args.value);
+   mash_infuse = 0;
+   rows = $('#mashGrid').jqxGrid('getrows');
+   for (i = 0; i < rows.length; i++) {
+    row = rows[i];
+    if (row.step_type == 0) // Infusion
+     mash_infuse += parseFloat(row.step_infuse_amount);
+   }
+   if (dataRecord.w2_amount == 0) {
+    dataRecord.w1_amount = mash_infuse;
+    $('#w1_amount').val(mash_infuse);
+   } else {
+    var w1_amount = (dataRecord.w1_amount / (dataRecord.w1_amount + dataRecord.w2_amount)) * mash_infuse;
+    var w2_amount = (dataRecord.w2_amount / (dataRecord.w1_amount + dataRecord.w2_amount)) * mash_infuse;
+    dataRecord.w1_amount = Round(w1_amount, 3);
+    dataRecord.w2_amount = Round(w2_amount, 3);
+    $('#w1_amount').val(dataRecord.w1_amount);
+    $('#w2_amount').val(dataRecord.w2_amount);
+   }
+   $('#wg_amount').val(mash_infuse);
+   console.log('new infuse amount: ' + mash_infuse);
+   calcWater();
   }
-  if (dataRecord.w2_amount == 0) {
-   dataRecord.w1_amount = mash_infuse;
-   $('#w1_amount').val(mash_infuse);
-  } else {
-   dataRecord.w1_amount = (dataRecord.w1_amount / (dataRecord.w1_amount + dataRecord.w2_amount)) * mash_infuse;
-   dataRecord.w2_amount = (dataRecord.w2_amount / (dataRecord.w1_amount + dataRecord.w2_amount)) * mash_infuse;
-   $('#w1_amount').val(dataRecord.w1_amount);
-   $('#w2_amount').val(dataRecord.w2_amount);
-  }
-  $('#wg_amount').val(mash_infuse);
+ });
+ $('#wstep_infuse_temp').jqxNumberInput(Spin1dec);
+ $('#wstep_infuse_temp').on('change', function(event) {
+  var prevdata = $('#mashGrid').jqxGrid('getrowdata', mashRow-1);
+  var rowdata = $('#mashGrid').jqxGrid('getrowdata', mashRow);
+  rowdata.step_infuse_temp = parseFloat(event.args.value);
+  var vol = infusionVol(prevdata.step_volume, mashkg, rowdata.step_infuse_temp, rowdata.step_temp, prevdata.end_temp);
+  console.log('new vol: ' + vol);
+  rowdata.step_infuse_amount = vol;
+  $('#wstep_infuse_amount').val(vol);
  });
 
  // Tab 7, Water
--- a/www/prod_beerxml.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/prod_beerxml.php	Wed May 06 14:14:14 2020 +0200
@@ -293,8 +293,13 @@
 	field($xw, 'VERSION', '1');
 	field($xw, 'NAME', $item['step_name']);
 	field($xw, 'TYPE', $mashsteptype[$item['step_type']]);
-	if ($item['step_type'] == 0)
-		field($xw, 'INFUSE_AMOUNT', sprintf("%.1f",floatval($item['step_infuse_amount'])));
+	if ($item['step_type'] == 0) {
+		field($xw, 'INFUSE_AMOUNT', sprintf("%.3f",floatval($item['step_infuse_amount'])));
+		field($xw, 'INFUSE_TEMP', sprintf("%.3f",floatval($item['step_infuse_temp'])));
+	}
+	if ($item['step_type'] == 2) {
+		field($xw, 'DECOCTION_AMT', sprintf("%.3f",floatval($item['step_infuse_amount'])));
+	}
 	field($xw, 'STEP_TEMP', sprintf("%.1f",floatval($item['step_temp'])));
 	field($xw, 'STEP_TIME', sprintf("%.1f",floatval($item['step_time'])));
 	field($xw, 'RAMP_TIME', sprintf("%.1f",floatval($item['ramp_time'])));
--- a/www/prod_checklist.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/prod_checklist.php	Wed May 06 14:14:14 2020 +0200
@@ -161,7 +161,7 @@
 					else
 						$l = $mashwater;
 					$s = sprintf("%.1f",$l).' liter water opwarmen tot ';
-					$s .= sprintf("%.1f",$item['step_temp']).' '.DEG.'C (';
+					$s .= sprintf("%.1f",$item['step_infuse_temp']).' '.DEG.'C (';
 					$s .= sprintf("%.1f",kettle_cm($l,$row['eq_tun_volume'],$row['eq_tun_height']));
 					$s .= ' cm onder de rand)';
 					$this->Checkline($s);
@@ -187,11 +187,13 @@
 					$this->Checkline('pH meten en bijstellen (doel pH beslag: '.sprintf("%.1f",$row['mash_ph']).' pH)');
 				} else {
 					if ($item['step_type'] == 0) {	// Infusion
-						$s = 'toevoegen '.($item['step_infuse_amount'] * $factor).' liter water van xx '.DEG.'C';
+						$s = 'toevoegen '.sprintf("%.1f",$item['step_infuse_amount'] * $factor).' liter water van ';
+						$s .= $item['step_infuse_temp'].' '.DEG.'C';
 					} else if ($item['step_type'] == 1) {	// Direct heat
 						$s = 'opwarmen tot '.$item['step_temp'].' '.DEG.'C';
 					} else {	// Decoction
-						$s = 'uitnemen, opwarmen, koken en terugstorten van '.($item['step_infuse_amount']*$factor).' liter deelbeslag';
+						$s = 'uitnemen, opwarmen, koken en terugstorten van '.sprintf("%.1f",$item['step_infuse_amount']*$factor);
+						$s .= ' liter deelbeslag';
 					}
 					$this->Checkline($s);
 					$this->Checkline($item['step_time'].' min. bij '.$item['step_temp'].' '.DEG.'C');
--- a/www/prod_edit.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/prod_edit.php	Wed May 06 14:14:14 2020 +0200
@@ -1088,10 +1088,14 @@
        <td style="padding: 3px;"><div id="wramp_time"></div></td>
       </tr>
       <tr>
-       <td align="right" style="vertical-align: top;"><div id="wstep_pmpt">Infusie liters:</div></td>
+       <td align="right" style="vertical-align: top;"><div id="wstep_pmpt_amount">Infusie liters:</div></td>
        <td style="padding: 3px;"><div id="wstep_infuse_amount"></div></td>
       </tr>
       <tr>
+       <td align="right" style="vertical-align: top;"><div id="wstep_pmpt_temp">Infusie temperatuur:</div></td>
+       <td style="padding: 3px;"><div id="wstep_infuse_temp"></div></td>
+      </tr>
+      <tr>
        <td style="padding-top: 30px;" colspan="2" align="center">
         <input id="MashReady" type="button" value="Sla op" />
        </td>
--- a/www/prod_print.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/prod_print.php	Wed May 06 14:14:14 2020 +0200
@@ -457,19 +457,20 @@
                 global $svg;
 		global $mashkg;
                 global $mashtype;
-                $vul = $this->w - $this->rMargin - $this->lMargin - 137;
+                $vul = $this->w - $this->rMargin - $this->lMargin - 138;
 		if ($this->GetY() > 250)
                         $this->AddPage();
                 else
                         $this->Ln();
                 $this->AddCol($vul,'Maisch stap naam','L');
                 $this->AddCol(25,'Stap type','L');
-                $this->AddCol(18,'Start '.DEG.'C','R');
-                $this->AddCol(18,'Eind '.DEG.'C','R');
-                $this->AddCol(18,'Rust min','R');
-                $this->AddCol(18,'Stap min','R');
-                $this->AddCol(20,'L/Kg','R');
-                $this->AddCol(20,'Infusie L','R');
+                $this->AddCol(16,'Start '.DEG.'C','R');
+                $this->AddCol(16,'Eind '.DEG.'C','R');
+                $this->AddCol(15,'Rust min','R');
+                $this->AddCol(15,'Stap min','R');
+                $this->AddCol(15,'L/Kg','R');
+                $this->AddCol(18,'Inf/dec L','R');
+		$this->AddCol(18,'Inf/dec '.DEG.'C','R');
 
 		$cMargin=$this->cMargin;
                 $this->cMargin=2;
@@ -492,12 +493,18 @@
 				$thick = 0;
 			$this->Cell($vul,5,$item['step_name'],0,0,'L',true);
 			$this->Cell(25,5,$mashtype[$item['step_type']],0,0,'L',true);
-			$this->Cell(18,5,sprintf("%.1f",$item['step_temp']),0,0,'R',true);
-			$this->Cell(18,5,sprintf("%.1f",$item['end_temp']),0,0,'R',true);
-			$this->Cell(18,5,sprintf("%.0f",$item['step_time']),0,0,'R',true);
-			$this->Cell(18,5,sprintf("%.0f",$item['ramp_time']),0,0,'R',true);
-			$this->Cell(20,5,sprintf("%.2f",$thick),0,0,'R',true);
-			$this->Cell(20,5,sprintf("%.1f",$item['step_infuse_amount']),0,0,'R',true);
+			$this->Cell(16,5,sprintf("%.1f",$item['step_temp']),0,0,'R',true);
+			$this->Cell(16,5,sprintf("%.1f",$item['end_temp']),0,0,'R',true);
+			$this->Cell(15,5,sprintf("%.0f",$item['step_time']),0,0,'R',true);
+			$this->Cell(15,5,sprintf("%.0f",$item['ramp_time']),0,0,'R',true);
+			$this->Cell(15,5,sprintf("%.2f",$thick),0,0,'R',true);
+			if ($item['step_type'] == 1) {
+				$this->Cell(18,5,' ',0,0,'R',true);
+				$this->Cell(18,5,' ',0,0,'R',true);
+			} else {
+				$this->Cell(18,5,sprintf("%.1f",$item['step_infuse_amount']),0,0,'R',true);
+				$this->Cell(18,5,sprintf("%.1f",$item['step_infuse_temp']),0,0,'R',true);
+			}
 			$this->Ln();
 		}
 		$this->ProcessingTable=false;
--- a/www/rec_beerxml.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/rec_beerxml.php	Wed May 06 14:14:14 2020 +0200
@@ -38,6 +38,13 @@
 $ibumethod = array( 'Tinseth', 'Rager', 'Daniels', 'Garetz', 'Mosher', 'Noonan' );
 
 
+function field($x, $field, $value) {
+	xmlwriter_start_element($x, $field);
+	xmlwriter_text($x, $value);
+	xmlwriter_end_element($x);
+}
+
+
 /*
  * Create beerxml output
  */
@@ -51,597 +58,216 @@
 xmlwriter_start_element($xw, 'RECIPE');
 
 // Recipe basics
-xmlwriter_start_element($xw, 'VERSION');
-xmlwriter_text($xw, '1');
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'NAME');
-xmlwriter_text($xw, $row['name']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'NOTES');
-xmlwriter_text($xw, $row['notes']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'TYPE');
-xmlwriter_text($xw, $recipetype[$row['type']]);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'BREWER');
-xmlwriter_text($xw, 'Anonymous');
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'BATCH_SIZE');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['batch_size'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'BOIL_SIZE');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['boil_size'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'BOIL_TIME');
-xmlwriter_text($xw, sprintf("%.0f",floatval($row['boil_time'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'EFFICIENCY');
-xmlwriter_text($xw, sprintf("%.1f",floatval($row['efficiency'])));
-xmlwriter_end_element($xw);
+field($xw, 'VERSION', '1');
+field($xw, 'NAME', $row['name']);
+field($xw, 'NOTES', $row['notes']);
+field($xw, 'TYPE', $recipetype[$row['type']]);
+field($xw, 'BREWER', 'Anonymous');
+field($xw, 'BATCH_SIZE', sprintf("%.4f",floatval($row['batch_size'])));
+field($xw, 'BOIL_SIZE', sprintf("%.4f",floatval($row['boil_size'])));
+field($xw, 'BOIL_TIME', sprintf("%.3f",floatval($row['boil_time'])));
+field($xw, 'EFFICIENCY', sprintf("%.4f",floatval($row['efficiency'])));
 
 if (floatval($row['est_og']) > 0) {
-	xmlwriter_start_element($xw, 'EST_OG');
-	xmlwriter_text($xw, sprintf("%.3f",floatval($row['est_og'])));
-	xmlwriter_end_element($xw);
+        field($xw, 'EST_OG', sprintf("%.3f",floatval($row['est_og'])));
 }
 
 if (floatval($row['est_fg']) > 0) {
-        xmlwriter_start_element($xw, 'EST_FG');
-        xmlwriter_text($xw, sprintf("%.3f",floatval($row['est_fg'])));
-        xmlwriter_end_element($xw);
+        field($xw, 'EST_FG', sprintf("%.3f",floatval($row['est_fg'])));
 }
 
 if (floatval($row['est_abv']) > 0) {
-        xmlwriter_start_element($xw, 'EST_ABV');
-        xmlwriter_text($xw, sprintf("%.1f",floatval($row['est_abv'])));
-        xmlwriter_end_element($xw);
+        field($xw, 'EST_ABV', sprintf("%.1f",floatval($row['est_abv'])));
 }
 
 if (floatval($row['est_color']) > 0) {
-        xmlwriter_start_element($xw, 'EST_COLOR');
-        xmlwriter_text($xw, sprintf("%.6f", ebc_to_srm(floatval($row['est_color']))));
-        xmlwriter_end_element($xw);
-	xmlwriter_start_element($xw, 'COLOR_METHOD');
-	xmlwriter_text($xw, $colormethod[$row['color_method']]);
-	xmlwriter_end_element($xw);
+        field($xw, 'EST_COLOR', sprintf("%.6f", ebc_to_srm(floatval($row['est_color']))));
+        field($xw, 'COLOR_METHOD', $colormethod[$row['color_method']]);
 }
 
 if (floatval($row['est_ibu']) > 0) {
-        xmlwriter_start_element($xw, 'EST_IBU');
-        xmlwriter_text($xw, sprintf("%.1f",floatval($row['est_ibu'])));
-        xmlwriter_end_element($xw);
-	xmlwriter_start_element($xw, 'IBU_METHOD');
-	xmlwriter_text($xw, $ibumethod[$row['ibu_method']]);
-	xmlwriter_end_element($xw);
+        field($xw, 'EST_IBU', sprintf("%.1f",floatval($row['est_ibu'])));
+        field($xw, 'IBU_METHOD', $ibumethod[$row['ibu_method']]);
 }
 
 
 // Style
 xmlwriter_start_element($xw, 'STYLE');
-
-xmlwriter_start_element($xw, 'VERSION');
-xmlwriter_text($xw, '1');
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'NAME');
-xmlwriter_text($xw, $row['st_name']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'CATEGORY');
-xmlwriter_text($xw, $row['st_category']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'CATEGORY_NUMBER');
-xmlwriter_text($xw, $row['st_category_number']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'STYLE_LETTER');
-xmlwriter_text($xw, $row['st_letter']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'STYLE_GUIDE');
-xmlwriter_text($xw, $row['st_guide']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'TYPE');
-xmlwriter_text($xw, $styletype[$row['st_type']]);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'OG_MIN');
-xmlwriter_text($xw, sprintf("%.3f",floatval($row['st_og_min'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'OG_MAX');
-xmlwriter_text($xw, sprintf("%.3f",floatval($row['st_og_max'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'FG_MIN');
-xmlwriter_text($xw, sprintf("%.3f",floatval($row['st_fg_min'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'FG_MAX');
-xmlwriter_text($xw, sprintf("%.3f",floatval($row['st_fg_max'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'IBU_MIN');
-xmlwriter_text($xw, sprintf("%.0f",floatval($row['st_ibu_min'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'IBU_MAX');
-xmlwriter_text($xw, sprintf("%.0f",floatval($row['st_ibu_max'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'COLOR_MIN');
-xmlwriter_text($xw, sprintf("%.2f",ebc_to_srm(floatval($row['st_color_min']))));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'COLOR_MAX');
-xmlwriter_text($xw, sprintf("%.2f",ebc_to_srm(floatval($row['st_color_max']))));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'CARB_MIN');
-xmlwriter_text($xw, sprintf("%.1f",floatval($row['st_carb_min'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'CARB_MAX');
-xmlwriter_text($xw, sprintf("%.1f",floatval($row['st_carb_max'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'ABV_MIN');
-xmlwriter_text($xw, sprintf("%.1f",floatval($row['st_abv_min'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'ABV_MAX');
-xmlwriter_text($xw, sprintf("%.1f",floatval($row['st_abv_max'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_end_element($xw);	// STYLE
-
+field($xw, 'VERSION', '1');
+field($xw, 'NAME', $row['st_name']);
+field($xw, 'CATEGORY', $row['st_category']);
+field($xw, 'CATEGORY_NUMBER', $row['st_category_number']);
+field($xw, 'STYLE_LETTER', $row['st_letter']);
+field($xw, 'STYLE_GUIDE', $row['st_guide']);
+field($xw, 'TYPE', $styletype[$row['st_type']]);
+field($xw, 'OG_MIN', sprintf("%.3f",floatval($row['st_og_min'])));
+field($xw, 'OG_MAX', sprintf("%.3f",floatval($row['st_og_max'])));
+field($xw, 'FG_MIN', sprintf("%.3f",floatval($row['st_fg_min'])));
+field($xw, 'FG_MAX', sprintf("%.3f",floatval($row['st_fg_max'])));
+field($xw, 'IBU_MIN', sprintf("%.0f",floatval($row['st_ibu_min'])));
+field($xw, 'IBU_MAX', sprintf("%.0f",floatval($row['st_ibu_max'])));
+field($xw, 'COLOR_MIN', sprintf("%.2f",ebc_to_srm(floatval($row['st_color_min']))));
+field($xw, 'COLOR_MAX', sprintf("%.2f",ebc_to_srm(floatval($row['st_color_max']))));
+field($xw, 'CARB_MIN', sprintf("%.1f",floatval($row['st_carb_min'])));
+field($xw, 'CARB_MAX', sprintf("%.1f",floatval($row['st_carb_max'])));
+field($xw, 'ABV_MIN', sprintf("%.1f",floatval($row['st_abv_min'])));
+field($xw, 'ABV_MAX', sprintf("%.1f",floatval($row['st_abv_max'])));
+xmlwriter_end_element($xw);     // STYLE
 
 xmlwriter_start_element($xw, 'EQUIPMENT');
-
-xmlwriter_start_element($xw, 'VERSION');
-xmlwriter_text($xw, '1');
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'NAME');
-xmlwriter_text($xw, 'Dummy brewery');
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'BATCH_SIZE');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['batch_size'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'BOIL_SIZE');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['boil_size'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'BOIL_TIME');
-xmlwriter_text($xw, sprintf("%.0f",floatval($row['boil_time'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_end_element($xw);	// EQUIPMENT
-
+field($xw, 'VERSION', '1');
+field($xw, 'NAME', 'Dummy Brewery');
+field($xw, 'BATCH_SIZE', sprintf("%.2f",floatval($row['batch_size'])));
+field($xw, 'BOIL_SIZE', sprintf("%.2f",floatval($row['boil_size'])));
+field($xw, 'BOIL_TIME', sprintf("%.0f",floatval($row['boil_time'])));
+xmlwriter_end_element($xw);     // EQUIPMENT
 
 xmlwriter_start_element($xw, 'HOPS');
 $arr = json_decode($row['json_hops'], true);
 foreach($arr as $item) { //foreach element in $arr
-
-	xmlwriter_start_element($xw, 'HOP');
-
-	xmlwriter_start_element($xw, 'VERSION');
-	xmlwriter_text($xw, '1');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'NAME');
-	xmlwriter_text($xw, $item['h_name']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'ALPHA');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['h_alpha'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'AMOUNT');
-	xmlwriter_text($xw, sprintf("%.4f",floatval($item['h_amount'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'USE');
-	xmlwriter_text($xw, $hopuse[$item['h_useat']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'TIME');
-	xmlwriter_text($xw, $item['h_time']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'TYPE');
-	xmlwriter_text($xw, $hoptype[$item['h_type']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'FORM');
-	xmlwriter_text($xw, $hopform[$item['h_form']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'BETA');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['h_beta'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'HSI');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['h_hsi'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'ORIGIN');
-	xmlwriter_text($xw, $item['h_origin']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_end_element($xw);	// HOP
+        xmlwriter_start_element($xw, 'HOP');
+        field($xw, 'VERSION', '1');
+        field($xw, 'NAME', $item['h_name']);
+        field($xw, 'ALPHA', sprintf("%.1f",floatval($item['h_alpha'])));
+        field($xw, 'AMOUNT', sprintf("%.4f",floatval($item['h_amount'])));
+        field($xw, 'USE', $hopuse[$item['h_useat']]);
+        field($xw, 'TIME', $item['h_time']);
+        field($xw, 'TYPE', $hoptype[$item['h_type']]);
+        field($xw, 'FORM', $hopform[$item['h_form']]);
+        field($xw, 'BETA', sprintf("%.1f",floatval($item['h_beta'])));
+        field($xw, 'HSI', sprintf("%.1f",floatval($item['h_hsi'])));
+        field($xw, 'ORIGIN', $item['h_origin']);
+        xmlwriter_end_element($xw);     // HOP
 }
-xmlwriter_end_element($xw);	// HOPS
-
+xmlwriter_end_element($xw);     // HOPS
 
 xmlwriter_start_element($xw, 'FERMENTABLES');
 $arr = json_decode($row['json_fermentables'], true);
 foreach($arr as $item) { //foreach element in $arr
     if ($item['f_added'] <= 3) {
-	xmlwriter_start_element($xw, 'FERMENTABLE');
-
-	xmlwriter_start_element($xw, 'VERSION');
-	xmlwriter_text($xw, '1');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'NAME');
-	xmlwriter_text($xw, $item['f_name']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'TYPE');
-	xmlwriter_text($xw, $fermentabletype[$item['f_type']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'AMOUNT');
-	xmlwriter_text($xw, sprintf("%.4f",floatval($item['f_amount'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'YIELD');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['f_yield'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'COLOR');
-	xmlwriter_text($xw, sprintf("%.1f",ebc_to_srm(floatval($item['f_color']))));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'ADD_AFTER_BOIL');
-	($item['f_added'] > 1) ? xmlwriter_text($xw, 'TRUE') : xmlwriter_text($xw, 'FALSE');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'ORIGIN');
-	xmlwriter_text($xw, $item['f_origin']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'SUPPLIER');
-	xmlwriter_text($xw, $item['f_supplier']);
-	xmlwriter_end_element($xw);
-
-	if (floatval($item['f_coarse_fine_diff'])) {
-		xmlwriter_start_element($xw, 'COARSE_FINE_DIFF');
-		xmlwriter_text($xw, sprintf("%.4f",floatval($item['f_coarse_fine_diff'])));
-		xmlwriter_end_element($xw);
-	}
-
-	if (floatval($item['f_moisture'])) {
-		xmlwriter_start_element($xw, 'MOISTURE');
-		xmlwriter_text($xw, sprintf("%.4f",floatval($item['f_moisture'])));
-		xmlwriter_end_element($xw);
-	}
-
-	if (floatval($item['f_diastatic_power'])) {
-		xmlwriter_start_element($xw, 'DIASTATIC_POWER');
-		xmlwriter_text($xw, sprintf("%.4f",floatval($item['f_diastatic_power'])));
-		xmlwriter_end_element($xw);
-	}
-
-	if (floatval($item['f_protein'])) {
-		xmlwriter_start_element($xw, 'PROTEIN');
-		xmlwriter_text($xw, sprintf("%.4f",floatval($item['f_protein'])));
-		xmlwriter_end_element($xw);
-	}
-
-	if (floatval($item['f_max_in_batch'])) {
-		xmlwriter_start_element($xw, 'MAX_IN_BATCH');
-		xmlwriter_text($xw, sprintf("%.1f",floatval($item['f_max_in_batch'])));
-		xmlwriter_end_element($xw);
-	}
-
-	xmlwriter_start_element($xw, 'RECOMMEND_MASH');
-	($item['f_recommend_mash']) ? xmlwriter_text($xw, 'TRUE') : xmlwriter_text($xw, 'FALSE');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'GRAINTYPE');
-	xmlwriter_text($xw, $graintype[$item['f_graintype']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_end_element($xw);	// FERMENTABLE
+        xmlwriter_start_element($xw, 'FERMENTABLE');
+        field($xw, 'VERSION', '1');
+        field($xw, 'NAME', $item['f_name']);
+        field($xw, 'TYPE', $fermentabletype[$item['f_type']]);
+        field($xw, 'AMOUNT', sprintf("%.4f",floatval($item['f_amount'])));
+        field($xw, 'YIELD', sprintf("%.1f",floatval($item['f_yield'])));
+        field($xw, 'COLOR', sprintf("%.1f",ebc_to_srm(floatval($item['f_color']))));
+        field($xw, 'ADD_AFTER_BOIL', ($item['f_added'] > 1) ? 'TRUE':'FALSE');
+        field($xw, 'ORIGIN', $item['f_origin']);
+        field($xw, 'SUPPLIER', $item['f_supplier']);
+        if (floatval($item['f_coarse_fine_diff']))
+                field($xw, 'COARSE_FINE_DIFF', sprintf("%.4f",floatval($item['f_coarse_fine_diff'])));
+        if (floatval($item['f_moisture']))
+                field($xw, 'MOISTURE', sprintf("%.4f",floatval($item['f_moisture'])));
+        if (floatval($item['f_diastatic_power']))
+                field($xw, 'DIASTATIC_POWER', sprintf("%.4f",floatval($item['f_diastatic_power'])));
+        if (floatval($item['f_protein']))
+                field($xw, 'PROTEIN', sprintf("%.4f",floatval($item['f_protein'])));
+        if (floatval($item['f_max_in_batch']))
+                field($xw, 'MAX_IN_BATCH', sprintf("%.1f",floatval($item['f_max_in_batch'])));
+        field($xw, 'RECOMMEND_MASH', ($item['f_recommend_mash']) ? 'TRUE':'FALSE');
+        field($xw, 'GRAINTYPE', $graintype[$item['f_graintype']]);
+        xmlwriter_end_element($xw);     // FERMENTABLE
     }
 }
-xmlwriter_end_element($xw);	// FERMENTABLES
-
+xmlwriter_end_element($xw);     // FERMENTABLES
 
 xmlwriter_start_element($xw, 'MISCS');
 $arr = json_decode($row['json_miscs'], true);
 foreach($arr as $item) {
-
-	xmlwriter_start_element($xw, 'MISC');
-
-	xmlwriter_start_element($xw, 'VERSION');
-	xmlwriter_text($xw, '1');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'NAME');
-	xmlwriter_text($xw, $item['m_name']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'TYPE');
-	xmlwriter_text($xw, $misctype[$item['m_type']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'AMOUNT');
-	xmlwriter_text($xw, sprintf("%.5f",floatval($item['m_amount'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'AMOUNT_IS_WEIGHT');
-	($item['m_amount_is_weight']) ? xmlwriter_text($xw, 'TRUE') : xmlwriter_text($xw, 'FALSE');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'USE');
-	xmlwriter_text($xw, $miscuse[$item['m_use_use']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'TIME');
-	xmlwriter_text($xw, sprintf("%.0f",floatval($item['m_time'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_end_element($xw);	// MISC
+        xmlwriter_start_element($xw, 'MISC');
+        field($xw, 'VERSION', '1');
+        field($xw, 'NAME', $item['m_name']);
+        field($xw, 'TYPE', $misctype[$item['m_type']]);
+        field($xw, 'AMOUNT', sprintf("%.5f",floatval($item['m_amount'])));
+        field($xw, 'AMOUNT_IS_WEIGHT', ($item['m_amount_is_weight']) ? 'TRUE':'FALSE');
+        field($xw, 'USE', $miscuse[$item['m_use_use']]);
+        field($xw, 'TIME', sprintf("%.0f",floatval($item['m_time'])));
+        xmlwriter_end_element($xw);     // MISC
 }
-xmlwriter_end_element($xw);	// MISCS
-
+xmlwriter_end_element($xw);     // MISCS
 
 xmlwriter_start_element($xw, 'YEASTS');
 $arr = json_decode($row['json_yeasts'], true);
 foreach($arr as $item) { //foreach element in $arr
-
-	xmlwriter_start_element($xw, 'YEAST');
-
-	xmlwriter_start_element($xw, 'VERSION');
-	xmlwriter_text($xw, '1');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'NAME');
-	xmlwriter_text($xw, $item['y_name']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'TYPE');
-	xmlwriter_text($xw, $yeasttype[$item['y_type']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'FORM');
-	xmlwriter_text($xw, $yeastform[$item['y_form']]);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'AMOUNT');
-	xmlwriter_text($xw, sprintf("%.5f",floatval($item['y_amount'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'AMOUNT_IS_WEIGHT');
-	($item['y_form'] == 1) ? xmlwriter_text($xw, 'TRUE') : xmlwriter_text($xw, 'FALSE');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'LABORATORY');
-	xmlwriter_text($xw, $item['y_laboratory']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'PRODUCT_ID');
-	xmlwriter_text($xw, $item['y_product_id']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'MIN_TEMPERATURE');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['y_min_temperature'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'MAX_TEMPERATURE');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['y_max_temperature'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'ATTENUATION');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['y_attenuation'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'ADD_TO_SECONDARY');
-	($item['y_use'] == 0) ? xmlwriter_text($xw, 'FALSE') : xmlwriter_text($xw, 'TRUE');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_end_element($xw);	// YEAST
+        xmlwriter_start_element($xw, 'YEAST');
+        field($xw, 'VERSION', '1');
+        field($xw, 'NAME', $item['y_name']);
+        field($xw, 'TYPE', $yeasttype[$item['y_type']]);
+        field($xw, 'FORM', $yeastform[$item['y_form']]);
+        field($xw, 'AMOUNT', sprintf("%.5f",floatval($item['y_amount'])));
+        field($xw, 'AMOUNT_IS_WEIGHT', ($item['y_form'] == 1) ? 'TRUE':'FALSE');
+        field($xw, 'LABORATORY', $item['y_laboratory']);
+        field($xw, 'PRODUCT_ID', $item['y_product_id']);
+        field($xw, 'MIN_TEMPERATURE', sprintf("%.1f",floatval($item['y_min_temperature'])));
+        field($xw, 'MAX_TEMPERATURE', sprintf("%.1f",floatval($item['y_max_temperature'])));
+        field($xw, 'ATTENUATION', sprintf("%.1f",floatval($item['y_attenuation'])));
+        field($xw, 'ADD_TO_SECONDARY', ($item['y_use'] == 0) ? 'FALSE':'TRUE');
+        xmlwriter_end_element($xw);     // YEAST
 }
-xmlwriter_end_element($xw);	// YEASTS
-
+xmlwriter_end_element($xw);     // YEASTS
 
 xmlwriter_start_element($xw, 'WATERS');
-xmlwriter_start_element($xw, 'WATER');	// Source 1
-
-xmlwriter_start_element($xw, 'VERSION');
-xmlwriter_text($xw, '1');
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'NAME');
-xmlwriter_text($xw, $row['w1_name']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'AMOUNT');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['w1_amount'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'CALCIUM');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['w1_calcium'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'MAGNESIUM');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['w1_magnesium'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'BICARBONATE');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['w1_total_alkalinity']) * 1.22));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'SULFATE');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['w1_sulfate'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'CHLORIDE');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['w1_chloride'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'SODIUM');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['w1_sodium'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'PH');
-xmlwriter_text($xw, sprintf("%.1f",floatval($row['w1_ph'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'TOTAL_ALKALINITY');
-xmlwriter_text($xw, sprintf("%.2f",floatval($row['w1_total_alkalinity'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_end_element($xw);	// WATER
-
+xmlwriter_start_element($xw, 'WATER');  // Source 1
+field($xw, 'VERSION', '1');
+field($xw, 'NAME', $row['w1_name']);
+field($xw, 'AMOUNT', sprintf("%.2f",floatval($row['w1_amount'])));
+field($xw, 'CALCIUM', sprintf("%.2f",floatval($row['w1_calcium'])));
+field($xw, 'MAGNESIUM', sprintf("%.2f",floatval($row['w1_magnesium'])));
+field($xw, 'BICARBONATE', sprintf("%.2f",floatval($row['w1_total_alkalinity']) * 1.22));
+field($xw, 'SULFATE', sprintf("%.2f",floatval($row['w1_sulfate'])));
+field($xw, 'CHLORIDE', sprintf("%.2f",floatval($row['w1_chloride'])));
+field($xw, 'SODIUM', sprintf("%.2f",floatval($row['w1_sodium'])));
+field($xw, 'PH', sprintf("%.1f",floatval($row['w1_ph'])));
+field($xw, 'TOTAL_ALKALINITY', sprintf("%.2f",floatval($row['w1_total_alkalinity'])));
+xmlwriter_end_element($xw);     // WATER
 if (strlen($row['w2_name'])) {
-	xmlwriter_start_element($xw, 'WATER');	// Source 2
-
-	xmlwriter_start_element($xw, 'VERSION');
-	xmlwriter_text($xw, '1');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'NAME');
-	xmlwriter_text($xw, $row['w2_name']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'AMOUNT');
-	xmlwriter_text($xw, sprintf("%.2f",floatval($row['w2_amount'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'CALCIUM');
-	xmlwriter_text($xw, sprintf("%.2f",floatval($row['w2_calcium'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'MAGNESIUM');
-	xmlwriter_text($xw, sprintf("%.2f",floatval($row['w2_magnesium'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'BICARBONATE');
-	xmlwriter_text($xw, sprintf("%.2f",floatval($row['w2_total_alkalinity']) * 1.22));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'SULFATE');
-	xmlwriter_text($xw, sprintf("%.2f",floatval($row['w2_sulfate'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'CHLORIDE');
-	xmlwriter_text($xw, sprintf("%.2f",floatval($row['w2_chloride'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'SODIUM');
-	xmlwriter_text($xw, sprintf("%.2f",floatval($row['w2_sodium'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'PH');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($row['w2_ph'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'TOTAL_ALKALINITY');
-	xmlwriter_text($xw, sprintf("%.2f",floatval($row['w2_total_alkalinity'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_end_element($xw);	// WATER
+        xmlwriter_start_element($xw, 'WATER');  // Source 2
+        field($xw, 'VERSION', '1');
+        field($xw, 'NAME', $row['w2_name']);
+        field($xw, 'AMOUNT', sprintf("%.2f",floatval($row['w2_amount'])));
+        field($xw, 'CALCIUM', sprintf("%.2f",floatval($row['w2_calcium'])));
+        field($xw, 'MAGNESIUM', sprintf("%.2f",floatval($row['w2_magnesium'])));
+        field($xw, 'BICARBONATE', sprintf("%.2f",floatval($row['w2_total_alkalinity']) * 1.22));
+        field($xw, 'SULFATE', sprintf("%.2f",floatval($row['w2_sulfate'])));
+        field($xw, 'CHLORIDE', sprintf("%.2f",floatval($row['w2_chloride'])));
+        field($xw, 'SODIUM', sprintf("%.2f",floatval($row['w2_sodium'])));
+        field($xw, 'PH', sprintf("%.1f",floatval($row['w2_ph'])));
+        field($xw, 'TOTAL_ALKALINITY', sprintf("%.2f",floatval($row['w2_total_alkalinity'])));
+        xmlwriter_end_element($xw);     // WATER
 }
-xmlwriter_end_element($xw);	// WATERS
-
+xmlwriter_end_element($xw);     // WATERS
 
 xmlwriter_start_element($xw, 'MASH');
-
-xmlwriter_start_element($xw, 'VERSION');
-xmlwriter_text($xw, '1');
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'NAME');
-xmlwriter_text($xw, $row['mash_name']);
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'GRAIN_TEMP');
-xmlwriter_text($xw, '10.0');
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'PH');
-xmlwriter_text($xw, sprintf("%.1f",floatval($row['sparge_ph'])));
-xmlwriter_end_element($xw);
-
-xmlwriter_start_element($xw, 'SPARGE_TEMP');
-xmlwriter_text($xw, sprintf("%.1f",floatval($row['sparge_temp'])));
-xmlwriter_end_element($xw);
-
+field($xw, 'VERSION', '1');
+field($xw, 'NAME', $row['mash_name']);
+field($xw, 'GRAIN_TEMP', '10.0');
+field($xw, 'PH', sprintf("%.1f",floatval($row['sparge_ph'])));
+field($xw, 'SPARGE_TEMP', sprintf("%.1f",floatval($row['sparge_temp'])));
 xmlwriter_start_element($xw, 'MASH_STEPS');
 $arr = json_decode($row['json_mashs'], true);
 foreach($arr as $item) { //foreach element in $arr
-	xmlwriter_start_element($xw, 'MASH_STEP');
-
-	xmlwriter_start_element($xw, 'VERSION');
-	xmlwriter_text($xw, '1');
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'NAME');
-	xmlwriter_text($xw, $item['step_name']);
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'TYPE');
-	xmlwriter_text($xw, $mashsteptype[$item['step_type']]);
-	xmlwriter_end_element($xw);
-
-	if ($item['step_type'] == 0) {
-		xmlwriter_start_element($xw, 'INFUSE_AMOUNT');
-		xmlwriter_text($xw, sprintf("%.1f",floatval($item['step_infuse_amount'])));
-		xmlwriter_end_element($xw);
-	}
-
-	xmlwriter_start_element($xw, 'STEP_TEMP');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['step_temp'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'STEP_TIME');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['step_time'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'RAMP_TIME');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['ramp_time'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'END_TEMP');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($item['end_temp'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_start_element($xw, 'PH');
-	xmlwriter_text($xw, sprintf("%.1f",floatval($row['mash_ph'])));
-	xmlwriter_end_element($xw);
-
-	xmlwriter_end_element($xw);	// MASH_STEP
+        xmlwriter_start_element($xw, 'MASH_STEP');
+        field($xw, 'VERSION', '1');
+        field($xw, 'NAME', $item['step_name']);
+        field($xw, 'TYPE', $mashsteptype[$item['step_type']]);
+        if ($item['step_type'] == 0) {
+                field($xw, 'INFUSE_AMOUNT', sprintf("%.3f",floatval($item['step_infuse_amount'])));
+                field($xw, 'INFUSE_TEMP', sprintf("%.3f",floatval($item['step_infuse_temp'])));
+        }
+        if ($item['step_type'] == 2) {
+                field($xw, 'DECOCTION_AMT', sprintf("%.3f",floatval($item['step_infuse_amount'])));
+        }
+        field($xw, 'STEP_TEMP', sprintf("%.1f",floatval($item['step_temp'])));
+        field($xw, 'STEP_TIME', sprintf("%.1f",floatval($item['step_time'])));
+        field($xw, 'RAMP_TIME', sprintf("%.1f",floatval($item['ramp_time'])));
+        field($xw, 'END_TEMP', sprintf("%.1f",floatval($item['end_temp'])));
+        field($xw, 'PH', sprintf("%.1f",floatval($row['mash_ph'])));
+        xmlwriter_end_element($xw);     // MASH_STEP
 }
-xmlwriter_end_element($xw);	// MASH_STEPS
-xmlwriter_end_element($xw);	// MASH
-
-xmlwriter_end_element($xw);	// RECIPE
+xmlwriter_end_element($xw);     // MASH_STEPS
+xmlwriter_end_element($xw);     // MASH
+xmlwriter_end_element($xw);     // RECIPE
 xmlwriter_end_element($xw);	// RECIPES
 xmlwriter_end_document($xw);
 
--- a/www/rec_edit.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/rec_edit.php	Wed May 06 14:14:14 2020 +0200
@@ -539,9 +539,13 @@
        <td style="padding: 3px;"><div id="wramp_time"></div></td>
       </tr>
       <tr>
-       <td align="right" style="vertical-align: top;"><div id="wstep_pmpt">Infusie liters:</div></td>
+       <td align="right" style="vertical-align: top;"><div id="wstep_pmpt_amount">Infusie liters:</div></td>
        <td style="padding: 3px;"><div id="wstep_infuse_amount"></div></td>
       </tr>
+       <tr>
+       <td align="right" style="vertical-align: top;"><div id="wstep_pmpt_temp">Infusie temperatuur:</div></td>
+       <td style="padding: 3px;"><div id="wstep_infuse_temp"></div></td>
+      </tr>
       <tr>
        <td style="padding-top: 30px;" colspan="2" align="center">
         <input id="MashReady" type="button" value="Sla op" />
--- a/www/rec_print.php	Fri May 01 21:37:23 2020 +0200
+++ b/www/rec_print.php	Wed May 06 14:14:14 2020 +0200
@@ -391,19 +391,20 @@
                 global $svg;
                 global $mashkg;
                 global $mashtype;
-                $vul = $this->w - $this->rMargin - $this->lMargin - 137;
+                $vul = $this->w - $this->rMargin - $this->lMargin - 138;
                 if ($this->GetY() > 250)
                         $this->AddPage();
                 else
                         $this->Ln();
                 $this->AddCol($vul,'Maisch stap naam','L');
                 $this->AddCol(25,'Stap type','L');
-                $this->AddCol(18,'Start '.DEG.'C','R');
-                $this->AddCol(18,'Eind '.DEG.'C','R');
-                $this->AddCol(18,'Rust min','R');
-                $this->AddCol(18,'Stap min','R');
-                $this->AddCol(20,'L/Kg','R');
-                $this->AddCol(20,'Infusie L','R');
+                $this->AddCol(16,'Start '.DEG.'C','R');
+                $this->AddCol(16,'Eind '.DEG.'C','R');
+                $this->AddCol(15,'Rust min','R');
+                $this->AddCol(15,'Stap min','R');
+                $this->AddCol(15,'L/Kg','R');
+		$this->AddCol(18,'Inf/dec L','R');
+		$this->AddCol(18,'Inf/dec '.DEG.'C','R');
 
                 $cMargin=$this->cMargin;
                 $this->cMargin=2;
@@ -426,12 +427,18 @@
                                 $thick = 0;
                         $this->Cell($vul,5,$item['step_name'],0,0,'L',true);
                         $this->Cell(25,5,$mashtype[$item['step_type']],0,0,'L',true);
-                        $this->Cell(18,5,sprintf("%.1f",$item['step_temp']),0,0,'R',true);
-                        $this->Cell(18,5,sprintf("%.1f",$item['end_temp']),0,0,'R',true);
-                        $this->Cell(18,5,sprintf("%.0f",$item['step_time']),0,0,'R',true);
-                        $this->Cell(18,5,sprintf("%.0f",$item['ramp_time']),0,0,'R',true);
-                        $this->Cell(20,5,sprintf("%.2f",$thick),0,0,'R',true);
-                        $this->Cell(20,5,sprintf("%.1f",$item['step_infuse_amount']),0,0,'R',true);
+                        $this->Cell(16,5,sprintf("%.1f",$item['step_temp']),0,0,'R',true);
+                        $this->Cell(16,5,sprintf("%.1f",$item['end_temp']),0,0,'R',true);
+                        $this->Cell(15,5,sprintf("%.0f",$item['step_time']),0,0,'R',true);
+                        $this->Cell(15,5,sprintf("%.0f",$item['ramp_time']),0,0,'R',true);
+                        $this->Cell(15,5,sprintf("%.2f",$thick),0,0,'R',true);
+			if ($item['step_type'] == 1) {
+				$this->Cell(18,5,' ',0,0,'R',true);
+				$this->Cell(18,5,' ',0,0,'R',true);
+			} else {
+				$this->Cell(18,5,sprintf("%.1f",$item['step_infuse_amount']),0,0,'R',true);
+				$this->Cell(18,5,sprintf("%.1f",$item['step_infuse_temp']),0,0,'R',true);
+			}
                         $this->Ln();
                 }
                 $this->ProcessingTable=false;

mercurial