Mon, 31 Dec 2018 14:41:08 +0100
Backport water treatment to the product editor (inluding the bugs).
/***************************************************************************** * Copyright (C) 2014-2019 * * Michiel Broek <mbroek at mbse dot eu> * * This file is part of BrewCloud * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * BrewCloud is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ThermFerm; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *****************************************************************************/ var sugardensity = 1.611; //kg/l in solution // Styles dropdown list var stylesUrl = "includes/db_profile_styles.php"; var stylesSource = { datatype: "json", datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'category', type: 'string' }, { name: 'category_number', type: 'number' }, { name: 'style_letter', type: 'string' }, { name: 'style_guide', type: 'string' }, { name: 'type', type: 'string' }, { name: 'og_min', type: 'float' }, { name: 'og_max', type: 'float' }, { name: 'fg_min', type: 'float' }, { name: 'fg_max', type: 'float' }, { name: 'ibu_min', type: 'float' }, { name: 'ibu_max', type: 'float' }, { name: 'color_min', type: 'float' }, { name: 'color_max', type: 'float' }, { name: 'carb_min', type: 'float' }, { name: 'carb_max', type: 'float' }, { name: 'abv_min', type: 'float' }, { name: 'abv_max', type: 'float' }, { name: 'notes', type: 'string' }, { name: 'profile', type: 'string' }, { name: 'ingredients', type: 'string' }, { name: 'examples', type: 'string' } ], url: stylesUrl, async: true }; var styleslist = new $.jqx.dataAdapter(stylesSource); // dropdownlist datasource from inventory_fermentables var fermentableInvSource = { datatype: "json", datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'type', type: 'string' }, { name: 'yield', type: 'float' }, { name: 'color', type: 'float' }, { name: 'add_after_boil', type: 'bool' }, { name: 'origin', type: 'string' }, { name: 'supplier', type: 'string' }, { name: 'coarse_fine_diff', type: 'float' }, { name: 'moisture', type: 'float' }, { name: 'diastatic_power', type: 'float' }, { name: 'protein', type: 'float' }, { name: 'max_in_batch', type: 'float' }, { name: 'recommend_mash', type: 'bool' }, { name: 'graintype', type: 'string' }, { name: 'di_ph', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' } ], url: "getfermentablesources.php", async: true }; var fermentableinstock = false; var fermentablelist = new $.jqx.dataAdapter(fermentableInvSource, { beforeLoadComplete: function (records) { var data = new Array(); for (var i = 0; i < records.length; i++) { var row = records[i]; if (row.inventory || ! fermentableinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { $('#err').text(status + ' ' + error); }, }); // dropdownlist datasource from inventory_hops var hopInvSource = { datatype: "json", datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'origin', type: 'string' }, { name: 'type', type: 'string' }, { name: 'alpha', type: 'float' }, { name: 'beta', type: 'float' }, { name: 'humulene', type: 'float' }, { name: 'caryophyllene', type: 'float' }, { name: 'cohumulone', type: 'float' }, { name: 'myrcene', type: 'float' }, { name: 'hsi', type: 'float' }, { name: 'useat', type: 'string' }, { name: 'form', type: 'string' }, { name: 'total_oil', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' } ], url: "gethopsources.php", async: true }; var hopinstock = false; var hoplist = new $.jqx.dataAdapter(hopInvSource, { beforeLoadComplete: function (records) { var data = new Array(); for (var i = 0; i < records.length; i++) { var row = records[i]; if (row.inventory || ! hopinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { $('#err').text(status + ' ' + error); }, }); // dropdownlist datasource from inventory_miscs var miscInvSource = { datatype: "json", datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'type', type: 'string' }, { name: 'use_use', type: 'string' }, { name: 'amount_is_weight', type: 'bool' }, { name: 'time', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' } ], url: "getmiscsources.php", async: true }; var miscinstock = false; var misclist = new $.jqx.dataAdapter(miscInvSource, { beforeLoadComplete: function (records) { var data = new Array(); for (var i = 0; i < records.length; i++) { var row = records[i]; if (row.inventory || ! miscinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { $('#err').text(status + ' ' + error); }, }); // dropdownlist datasource from inventory_yeasts var yeastInvSource = { datatype: "json", datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'type', type: 'string' }, { name: 'form', type: 'string' }, { name: 'laboratory', type: 'string' }, { name: 'product_id', type: 'string' }, { name: 'min_temperature', type: 'float' }, { name: 'max_temperature', type: 'float' }, { name: 'attenuation', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' } ], url: "getyeastsources.php", async: true }; var yeastinstock = false; var yeastlist = new $.jqx.dataAdapter(yeastInvSource, { beforeLoadComplete: function (records) { var data = new Array(); for (var i = 0; i < records.length; i++) { var row = records[i]; if (row.inventory || ! yeastinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { $('#err').text(status + ' ' + error); }, }); // dropdownlist datasource from inventory_waters var waterInvSource = { datatype: "json", datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'unlimited_stock', type: 'bool' }, { name: 'calcium', type: 'float' }, { name: 'sulfate', type: 'float' }, { name: 'chloride', type: 'float' }, { name: 'sodium', type: 'float' }, { name: 'magnesium', type: 'float' }, { name: 'ph', type: 'float' }, { name: 'total_alkalinity', type: 'float' }, { name: 'inventory', type: 'float' }, { name: 'cost', type: 'float' }, ], url: "getwatersources.php", async: true }; var waterinstock = false; var waterlist = new $.jqx.dataAdapter(waterInvSource, { beforeLoadComplete: function (records) { var data = new Array(); for (var i = 0; i < records.length; i++) { var row = records[i]; if (row.inventory || row.unlimited_stock || ! waterinstock) data.push(row); } return data; }, loadError: function(jqXHR, status, error) { $('#err').text(status + ' ' + error); }, }); // dropdownlist datasource from profile_water var waterProfileSource = { datatype: "json", datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'calcium', type: 'float' }, { name: 'bicarbonate', type: 'float' }, { name: 'sulfate', type: 'float' }, { name: 'chloride', type: 'float' }, { name: 'sodium', type: 'float' }, { name: 'magnesium', type: 'float' }, { name: 'ph', type: 'float' }, { name: 'total_alkalinity', type: 'float' }, ], url: "includes/db_profile_water.php", async: true }; var waterprofiles = new $.jqx.dataAdapter(waterProfileSource); // dropdownlist datasource from profile_mash var mashInvSource = { datatype: "json", datafields: [ { name: 'record', type: 'number' }, { name: 'name', type: 'string' }, { name: 'steps', type: 'array' } ], url: "include/db_profile_mash.php", async: true }; var mashlist = new $.jqx.dataAdapter(mashInvSource); function getLocalization() { var localizationobj = {}; localizationobj.pagerGoToPageString = "Gehe zu:"; localizationobj.pagerShowRowsString = "Zeige Zeile:"; localizationobj.pagerRangeString = " von "; localizationobj.pagerNextButtonString = "voriger"; localizationobj.pagerFirstButtonString = "first"; localizationobj.pagerLastButtonString = "last"; localizationobj.pagerPreviousButtonString = "nächster"; localizationobj.sortAscendingString = "Sortiere aufsteigend"; localizationobj.sortDescendingString = "Sortiere absteigend"; localizationobj.sortRemoveString = "Entferne Sortierung"; localizationobj.firstDay = 1; localizationobj.percentSymbol = "%"; localizationobj.currencySymbol = "€"; localizationobj.currencySymbolPosition = "after"; localizationobj.decimalSeparator = ","; localizationobj.thousandsSeparator = "."; var days = { // full day names names: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"], // abbreviated day names namesAbbr: ["Sonn", "Mon", "Dien", "Mitt", "Donn", "Fre", "Sams"], // shortest day names namesShort: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"] }; localizationobj.days = days; var months = { // full month names (13 months for lunar calendards -- 13th month should be "" if not lunar) names: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", ""], // abbreviated month names namesAbbr: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dez", ""] }; var patterns = { d: "dd.MM.yyyy", D: "dddd, d. MMMM yyyy", t: "HH:mm", T: "HH:mm:ss", f: "dddd, d. MMMM yyyy HH:mm", F: "dddd, d. MMMM yyyy HH:mm:ss", M: "dd MMMM", Y: "MMMM yyyy" } localizationobj.patterns = patterns; localizationobj.months = months; return localizationobj; } $(document).ready(function () { $("#jqxMenu").jqxMenu({ width: 1280, height: '30px', theme: theme }); $("#jqxWidget").css('visibility', 'visible'); }); function ebc_to_srm(ebc) { var srm = -1.32303E-12 * Math.pow(ebc, 4) - 0.00000000291515 * Math.pow(ebc, 3) + 0.00000818515 * Math.pow(ebc, 2) + 0.372038 * ebc + 0.596351; if ((ebc < 0) || (srm < 0)) console.log("ebc_to_srm("+ebc+") = "+srm); return srm } function srm_to_ebc(srm) { var ebc = Math.round(0.000000000176506 * Math.pow(srm, 4) + 0.000000154529 * Math.pow(srm, 3) - 0.000159428 * Math.pow(srm, 2) + 2.68837 * srm - 1.6004); if ((ebc < 0) || (srm < 0)) console.log("srm_to_ebc("+srm+") = "+ebc); return ebc; } /* * Alcohol By Volume */ function abvol(og, fg) { if ((4.749804 - fg) != 0) return 486.8693 * (og - fg) / (4.749804 - fg); return 0; } /* * Kleurwerking naar SRM */ function kw_to_srm(colormethod, c) { if (colormethod == "Morey") return 1.4922 * Math.pow(c, 0.6859); if (colormethod == "Mosher") return 0.3 * c + 4.7; if (colormethod == "Daniels") return 0.2 * c + 8.4; } function kw_to_ebc(colormethod, c) { return srm_to_ebc(kw_to_srm(colormethod, c)); } /* * Berekeningen uit https://www.hobbybrouwen.nl/forum/index.php/topic,6079.msg69464.html#msg69464 */ function toIBU(Use, Form, SG, Volume, Amount, Boiltime, Alpha, Method) { var gravity = parseFloat(SG); var liters = parseFloat(Volume); var alpha = parseFloat(Alpha)/100; var mass = parseFloat(Amount) * 1000; var time = parseFloat(Boiltime); var fmoment = 1.0; var pfactor = 1.0; var ibu = 0; if ((Use == "Dry Hop") || (Use == "Dry hop") || (Use == "Whirlpool") || (Use == "Aroma")) { fmoment = 0.0; } else if (Use == "Mash") { fmoment += my_factor_mashhop / 100; // Brouwhulp } else if ((Use == "First Wort") || (Use == "First wort")) { fmoment += my_factor_fwh / 100; // Brouwhulp, Louis, Ozzie } if (Form == "Pellet") { pfactor += my_factor_pellet / 100; } if (Form == "Plug" ) { pfactor += my_factor_plug / 100; } if (Method == "Tinseth") { /* http://realbeer.com/hops/research.html */ var AddedAlphaAcids = (alpha * mass * 1000) / liters; var Bigness_factor = 1.65 * Math.pow( 0.000125, gravity - 1); var BoilTime_factor = ((1 - Math.exp(-0.04 * time)) / 4.15); var utiisation = Bigness_factor * BoilTime_factor; ibu = Math.round(utiisation * AddedAlphaAcids * fmoment * pfactor * 10) / 10.0; } if (Method == "Daniels") { var boilfactor; var sgfactor; if (Form == "Leaf") boilfactor = -(0.0041*time*time)+(0.6162*time)+1.5779; else boilfactor = -(0.0051*time*time)+(0.7835*time)+1.9348; if (gravity < 1050) sgfactor = 0; else sgfactor = (gravity - 1050) / 200; ibu = Math.round(fmoment * ((mass * (alpha * 100) * boilfactor * 0.1) / (liters * (1 + sgfactor))) * 10) / 10; } if (Method == "Rager") { var boilfactor; var sgfactor; boilfactor = fmoment * 18.11 + 13.86 * Math.tanh((time * 31.32) / 18.27); if (gravity < 1050) sgfactor = 0; else sgfactor = (gravity - 1050) / 200; ibu = Math.round((mass * (alpha * 100) * boilfactor * 0.1) / (liters * (1 + sgfactor)) * 10) / 10; } // console.log("toIBU("+Use+","+Form+","+SG+","+Volume+","+Amount+","+Boiltime+","+Alpha+","+Method+"):"+ibu+" fm:"+fmoment+" pf:"+pfactor); return ibu; } function ebc_to_color(ebc) { return srm_to_color(ebc_to_srm(ebc)); } function srm_to_color(srm) { i = Math.round(srm * 10); if (i < 0) { i = 0; } if (i > 299) { i = 299; } /* Table copied from Brouwhulp/BrewBuddy */ var R = [ 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, // 0 250, 250, 250, 250, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, // 2 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215, // 4 214, 213, 212, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 200, 199, 199, 198, 198, // 6 197, 197, 196, 196, 195, 195, 194, 194, 193, 193, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 8 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 10 192, 192, 192, 192, 192, 192, 192, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, // 12 179, 178, 177, 175, 174, 172, 171, 169, 168, 167, 195, 164, 162, 161, 159, 158, 157, 155, 154, 152, // 14 151, 149, 148, 147, 145, 144, 142, 141, 139, 138, 137, 135, 134, 132, 131, 129, 128, 127, 125, 124, // 16 122, 121, 119, 118, 117, 115, 114, 112, 111, 109, 108, 107, 105, 104, 102, 101, 99, 98, 97, 95, // 18 94, 92, 91, 89, 88, 87, 85, 84, 82, 81, 79, 78, 77, 75, 74, 72, 71, 69, 68, 67, // 20 65, 64, 62, 61, 59, 58, 57, 55, 54, 52, 51, 49, 48, 47, 45, 44, 43, 41, 39, 38, // 22 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 28, 28, // 24 27, 27, 26, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, // 26 17, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8 ]; var G = [ 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 249, 248, 247, 246, 245, 244, 242, 240, 238, 236, 234, 232, 230, 228, 226, 224, 222, 220, 218, 216, 214, 212, 210, 208, 206, 204, 202, 200, 198, 196, 194, 192, 190, 188, 186, 184, 182, 180, 178, 176, 174, 172, 170, 168, 166, 164, 162, 160, 158, 156, 154, 152, 150, 148, 146, 144, 142, 141, 140, 139, 139, 138, 137, 136, 136, 135, 134, 133, 133, 132, 131, 130, 130, 129, 128, 127, 127, 126, 125, 124, 124, 123, 122, 121, 121, 120, 119, 118, 118, 117, 116, 115, 115, 114, 113, 112, 112, 111, 110, 109, 109, 108, 107, 106, 106, 105, 104, 103, 103, 102, 101, 100, 100, 99, 98, 97, 97, 96, 95, 94, 94, 93, 92, 91, 91, 90, 89, 88, 88, 87, 86, 85, 85, 84, 83, 82, 82, 81, 80, 79, 78, 77, 76, 75, 75, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67, 66, 66, 65, 64, 63, 63, 62, 61, 60, 60, 59, 58, 57, 57, 56, 55, 54, 54, 53, 52, 51, 51, 50, 49, 48, 48, 47, 46, 45, 45, 44, 43, 42, 42, 41, 40, 39, 39, 38, 37, 36, 36, 35, 34, 33, 33, 32, 31, 30, 30, 29, 28, 27, 27, 26, 25, 24, 24, 23, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3 ]; var B = [ 210, 204, 199, 193, 188, 182, 177, 171, 166, 160, 155, 149, 144, 138, 133, 127, 122, 116, 111, 105, 100, 94, 89, 83, 78, 72, 67, 61, 56, 50, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 55, 55, 55, 55, 54, 54, 54, 54, 53, 53, 53, 53, 52, 52, 52, 52, 51, 51, 51, 51, 50, 50, 50, 50, 49, 49, 48, 47, 47, 46, 45, 45, 44, 43, 43, 42, 41, 41, 40, 39, 39, 38, 37, 37, 36, 35, 34, 33, 32, 31, 29, 28, 27, 26, 25, 24, 23, 21, 20, 19, 18, 17, 16, 15, 13, 12, 11, 10, 9, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2 ]; var color = R[i] * 65536 + G[i] * 256 + B[i]; var result = color.toString(16).toUpperCase(); if (result.length < 6) { result = '0' + result; } result = '#' + result; return result; } function sg_to_plato(sg) { if (sg > 0.5) return 259 - 259 / sg; return 0; } function plato_to_sg(plato) { if (plato < 259) return 259 / (259 - plato); return 1.000; } function estimate_sg(sugars, batch_size) { var plato = 100 * sugars / batch_size; var sg = plato_to_sg(plato); for (var i = 0; i < 20; i++) { if (sg > 0) plato = 100 * sugars / (batch_size * sg); sg = plato_to_sg(plato); } // console.log("estimate_sg(" + sugars + "," + batch_size + ") : " + sg); return sg; } function estimate_fg(percSugar, percCara, WGratio, TotTme, Temp, attenuation, og) { var BD; console.log("estimate_fg("+percSugar+","+percCara+","+WGratio+","+TotTme+","+Temp+","+attenuation+","+og+")"); if (percSugar > 40) percSugar = 0; if (percCara > 50) percCara = 0; if ((WGratio > 0) && (TotTme > 0)) { BD = WGratio; BD = max(2, min(5.5, BD)); Temp = max(60, min(72, Temp)); } else { BD = 3.5; Temp = 67; TotTme = 75; } if (attenuation < 30) attenuation = 77; var AttBeer = 0.00825 * attenuation + 0.00817 * BD - 0.00684 * Temp + 0.00026 * TotTme - 0.00356 * percCara + 0.00553 * percSugar + 0.547; var fg = Math.round((1 + (1 - AttBeer) * (og - 1)) * 1000) / 1000; console.log("fg:"+fg); return fg; } function CalcFrac(TpH, pK1, pK2, pK3) { var r1d = Math.pow(10, TpH - pK1); var r2d = Math.pow(10, TpH - pK2); var r3d = Math.pow(10, TpH - pK3); var dd = 1/(1 + r1d + r1d*r2d + r1d*r2d*r3d); var f1d = dd; var f2d = r1d*dd; var f3d = r1d*r2d*dd; var f4d = r1d*r2d*r3d*dd; return f2d + 2*f3d + 3*f4d; } /* * Steinie: * * * HCO3 = CaCo3 x 1,22 * CaCO3) = HCO3- x 50 / 61 * 1°F = 10mg/L CaCo3 */