First load of changes for hops. In EditHop load the dropdown buttons from the global table. Use named query fields. Added database utilisation and bu_factor fields for hop extracts. Added edit fields for these new fields. Added post boil SG, utilisation and bu_factor parameters to the toIBU function. Added hops form parameter to the hopFlavourContribution and hopAromaContribution display bars. In the hops inventory list dispay volumes instead of weight for hop extracts. Modified the TinsethIBU function to use utilisation and bu_factor parameters. Add calculations for co2 and iso hop extracts, this is work in progress. The toIBU function makes use of the preSG and postSG values to use the correct SG to caall the TinsethIBU function. This results in a bit lower IBU values mostly affecting the late additions. Added use hop at bottling for iso hop extracts like Tetra hops using the formula from BarthHaas.

Fri, 29 Jul 2022 13:12:26 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Fri, 29 Jul 2022 13:12:26 +0200
changeset 373
b02aca4e926c
parent 372
d9c78eb19728
child 374
74d541ac514c

First load of changes for hops. In EditHop load the dropdown buttons from the global table. Use named query fields. Added database utilisation and bu_factor fields for hop extracts. Added edit fields for these new fields. Added post boil SG, utilisation and bu_factor parameters to the toIBU function. Added hops form parameter to the hopFlavourContribution and hopAromaContribution display bars. In the hops inventory list dispay volumes instead of weight for hop extracts. Modified the TinsethIBU function to use utilisation and bu_factor parameters. Add calculations for co2 and iso hop extracts, this is work in progress. The toIBU function makes use of the preSG and postSG values to use the correct SG to caall the TinsethIBU function. This results in a bit lower IBU values mostly affecting the late additions. Added use hop at bottling for iso hop extracts like Tetra hops using the formula from BarthHaas.

src/EditHop.cpp file | annotate | diff | comparison | revisions
src/EditProductExport.cpp file | annotate | diff | comparison | revisions
src/EditProductTab4.cpp file | annotate | diff | comparison | revisions
src/EditRecipeExport.cpp file | annotate | diff | comparison | revisions
src/EditRecipeTab3.cpp file | annotate | diff | comparison | revisions
src/InventoryHops.cpp file | annotate | diff | comparison | revisions
src/PrinterDialog.cpp file | annotate | diff | comparison | revisions
src/Utils.cpp file | annotate | diff | comparison | revisions
src/Utils.h file | annotate | diff | comparison | revisions
src/database/db_product.cpp file | annotate | diff | comparison | revisions
src/global.cpp file | annotate | diff | comparison | revisions
src/global.h file | annotate | diff | comparison | revisions
ui/EditHop.ui file | annotate | diff | comparison | revisions
--- a/src/EditHop.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/EditHop.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -17,6 +17,7 @@
 #include "EditHop.h"
 #include "../ui/ui_EditHop.h"
 #include "MainWindow.h"
+#include "global.h"
 
 
 EditHop::EditHop(int id, QWidget *parent) : QDialog(parent), ui(new Ui::EditHop)
@@ -27,18 +28,10 @@
     ui->setupUi(this);
     this->recno = id;
 
-    WindowTitle();
-
-    ui->typeEdit->addItem(tr("Bittering"));
-    ui->typeEdit->addItem(tr("Aroma"));
-    ui->typeEdit->addItem(tr("Both"));
-
-    ui->formEdit->addItem(tr("Pellet"));
-    ui->formEdit->addItem(tr("Plug"));
-    ui->formEdit->addItem(tr("Leaf"));
-    ui->formEdit->addItem(tr("Leaf Wet"));	/* Not in beerxml */
-    ui->formEdit->addItem(tr("Cryo"));		/* Not in beerxml */
-    ui->formEdit->addItem(tr("Extract"));	/* CO2 extract */
+    for (int i = 0; i < 3; i++)
+	ui->typeEdit->addItem(QCoreApplication::translate("HopTypes", g_hop_types[i]));
+    for (int i = 0; i < 7; i++)
+	ui->formEdit->addItem(QCoreApplication::translate("HopForm", g_hop_forms[i]));
 
     if (id >= 0) {
 	query.prepare("SELECT * FROM inventory_hops WHERE record = :recno");
@@ -46,38 +39,42 @@
 	query.exec();
 	query.next();
 
-	ui->nameEdit->setText(query.value(1).toString());
-	ui->alphaEdit->setValue(query.value(2).toDouble());
-	ui->betaEdit->setValue(query.value(3).toDouble());
-	ui->humuleneEdit->setValue(query.value(4).toDouble());
-	ui->caryEdit->setValue(query.value(5).toDouble());
-	ui->cohumuloneEdit->setValue(query.value(6).toDouble());
-	ui->myrceneEdit->setValue(query.value(7).toDouble());
-	ui->hsiEdit->setValue(query.value(8).toDouble());
-	ui->typeEdit->setCurrentIndex(query.value(9).toInt());
-	ui->formEdit->setCurrentIndex(query.value(10).toInt());
-	ui->notesEdit->setPlainText(query.value(11).toString());
-	ui->originEdit->setText(query.value(12).toString());
-	ui->substitutesEdit->setText(query.value(13).toString());
-	ui->alwaysEdit->setChecked(query.value(14).toInt() ? true:false);
-	ui->inventoryEdit->setValue(query.value(15).toDouble());
-	ui->costEdit->setValue(query.value(16).toDouble());
-	ui->valueEdit->setValue(query.value(15).toDouble() * query.value(16).toDouble());
-	if (query.value(17).toString().length() == 10) {
-            ui->prodEdit->setDate(query.value(17).toDate());
+	ui->nameEdit->setText(query.value("name").toString());
+	ui->alphaEdit->setValue(query.value("alpha").toDouble());
+	ui->betaEdit->setValue(query.value("beta").toDouble());
+	ui->humuleneEdit->setValue(query.value("humulene").toDouble());
+	ui->caryEdit->setValue(query.value("caryophyllene").toDouble());
+	ui->cohumuloneEdit->setValue(query.value("cohumulone").toDouble());
+	ui->myrceneEdit->setValue(query.value("myrcene").toDouble());
+	ui->hsiEdit->setValue(query.value("hsi").toDouble());
+	ui->typeEdit->setCurrentIndex(query.value("type").toInt());
+	ui->formEdit->setCurrentIndex(query.value("form").toInt());
+	ui->notesEdit->setPlainText(query.value("notes").toString());
+	ui->originEdit->setText(query.value("origin").toString());
+	ui->substitutesEdit->setText(query.value("substitutes").toString());
+	ui->alwaysEdit->setChecked(query.value("always_on_stock").toInt() ? true:false);
+	ui->inventoryEdit->setValue(query.value("inventory").toDouble());
+	ui->costEdit->setValue(query.value("cost").toDouble());
+	ui->valueEdit->setValue(query.value("inventory").toDouble() * query.value("cost").toDouble());
+	if (query.value("production_date").toString().length() == 10) {
+            ui->prodEdit->setDate(query.value("production_date").toDate());
         } else {
             ui->prodEdit->clear();
         }
-	if (query.value(18).toString().length() == 10) {
-	    ui->thtEdit->setDate(query.value(18).toDate());
+	if (query.value("tht_date").toString().length() == 10) {
+	    ui->thtEdit->setDate(query.value("tht_date").toDate());
 	} else {
 	    ui->thtEdit->clear();
 	}
-	ui->oilEdit->setValue(query.value(19).toDouble());
+	ui->oilEdit->setValue(query.value("total_oil").toDouble());
+	ui->utilisationEdit->setValue(query.value("utilisation").toDouble());
+	ui->bufactorEdit->setValue(query.value("bu_factor").toDouble());
     } else {
 	/* Set some defaults */
 	ui->typeEdit->setCurrentIndex(0);
 	ui->formEdit->setCurrentIndex(0);
+	ui->utilisationEdit->setValue(35.0);
+	ui->bufactorEdit->setValue(1.0);
 	ui->prodEdit->clear();
 	ui->thtEdit->clear();
     }
@@ -104,9 +101,13 @@
     connect(ui->thtButton1, SIGNAL(clicked()), this, SLOT(tht_date_today()));
     connect(ui->thtButton2, SIGNAL(clicked()), this, SLOT(tht_date_clear()));
     connect(ui->oilEdit, &QDoubleSpinBox::textChanged, this, &EditHop::is_changed);
+    connect(ui->utilisationEdit, &QDoubleSpinBox::textChanged, this, &EditHop::is_changed);
+    connect(ui->bufactorEdit, &QDoubleSpinBox::textChanged, this, &EditHop::is_changed);
 
     ui->saveButton->setEnabled(false);
     ui->deleteButton->setEnabled((ui->inventoryEdit->value() == 0 && id >= 0) ? true:false);
+
+    WindowTitle();
 }
 
 
@@ -135,12 +136,19 @@
 	txt.append((QString(" **")));
     }
     setWindowTitle(txt);
+
+    if (ui->formEdit->currentIndex() < HOP_FORMS_CO2EXTRACT) {
+        ui->inventoryEdit->setSuffix(tr(" Kg"));
+    } else {
+        ui->inventoryEdit->setSuffix(tr(" L"));
+    }
 }
 
 
 void EditHop::on_saveButton_clicked()
 {
     QSqlQuery query;
+    QString sql = "";
 
     /* If there are errors in the form, show a message and do "return;" */
     if (ui->nameEdit->text().length() < 2) {
@@ -154,18 +162,22 @@
 
     if (this->textIsChanged) {
     	if (this->recno == -1) {
-    	    query.prepare("INSERT INTO inventory_hops SET name=:name, alpha=:alpha, beta=:beta, "
+	    sql = "INSERT INTO inventory_hops SET ";
+	} else {
+	    sql = "UPDATE inventory_hops SET ";
+	}
+	sql.append("name=:name, alpha=:alpha, beta=:beta, "
 		"humulene=:humulene, caryophyllene=:cary, cohumulone=:cohumulone, myrcene=:myrcene, "
 		"hsi=:hsi, type=:type, form=:form, notes=:notes, origin=:origin, substitutes=:substitutes, "
 		"always_on_stock=:always, inventory=:inventory, cost=:cost, production_date=:prod, "
-		"tht_date=:tht, total_oil=:oil, uuid = :uuid");
-    	} else {
-	    query.prepare("UPDATE inventory_hops SET name=:name, alpha=:alpha, beta=:beta, "
-		"humulene=:humulene, caryophyllene=:cary, cohumulone=:cohumulone, myrcene=:myrcene, "
-		"hsi=:hsi, type=:type, form=:form, notes=:notes, origin=:origin, substitutes=:substitutes, "
-		"always_on_stock=:always, inventory=:inventory, cost=:cost, production_date=:prod, "
-                "tht_date=:tht, total_oil=:oil WHERE record = :recno");
+		"tht_date=:tht, total_oil=:oil, utilisation=:utilisation, bu_factor=:bu_factor");
+	if (this->recno == -1) {
+	    sql.append(", uuid=:uuid");
+	} else {
+	    sql.append(" WHERE record = :recno");
     	}
+
+	query.prepare(sql);
 	query.bindValue(":name", ui->nameEdit->text());
 	query.bindValue(":alpha", QString("%1").arg(ui->alphaEdit->value(), 2, 'f', 1, '0'));
 	query.bindValue(":beta", QString("%1").arg(ui->betaEdit->value(), 2, 'f', 1, '0'));
@@ -186,6 +198,8 @@
 	query.bindValue(":prod", ui->prodEdit->nullDate());
 	query.bindValue(":tht", ui->thtEdit->nullDate());
 	query.bindValue(":oil", QString("%1").arg(ui->oilEdit->value(), 2, 'f', 1, '0'));
+	query.bindValue(":utilisation", QString("%1").arg(ui->utilisationEdit->value(), 2, 'f', 1, '0'));
+	query.bindValue(":bu_factor", QString("%1").arg(ui->bufactorEdit->value(), 2, 'f', 1, '0'));
 	if (this->recno == -1) {
 	    query.bindValue(":uuid", QUuid::createUuid().toString().mid(1, 36));
 	} else {
--- a/src/EditProductExport.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/EditProductExport.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -454,10 +454,10 @@
     memo.append("[tabular]\n");
     memo.append("[head]Hop[/head][head]Vorm[/head][head]Alpha[/head][head]IBU[/head][head]Gram[/head][head]Toevoegen moment[/head]\n");
     for (int i = 0; i < product->hops.size(); i++) {
-        double ibu = Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg, product->batch_size,
+        double ibu = Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg, product->est_og3, product->batch_size,
                         product->hops.at(i).amount, product->hops.at(i).time, product->hops.at(i).alpha, product->ibu_method,
                         product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6, product->boil_time,
-			product->brew_cooling_method, 0, 0);
+			product->brew_cooling_method, 0, 0, product->hops.at(i).utilisation, product->hops.at(i).bu_factor);
         memo.append("[row][data]" + product->hops.at(i).name + " (" + product->hops.at(i).origin + ")[/data]");
         memo.append("[data]" + QCoreApplication::translate("HopForm", g_hop_forms[product->hops.at(i).form]) + "[/data]");
         memo.append("[data]" + QString::number(product->hops.at(i).alpha, 'f', 1) + "[/data]");
--- a/src/EditProductTab4.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/EditProductTab4.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -104,10 +104,10 @@
 	item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
         ui->hopsTable->setItem(i, 6, item);
 
-	double ibu = Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg, product->batch_size,
+	double ibu = Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg, product->est_og3, product->batch_size,
 			   product->hops.at(i).amount, product->hops.at(i).time, product->hops.at(i).alpha, product->ibu_method,
 			   product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6, product->boil_time,
-			   product->brew_cooling_method, 0, 0);
+			   product->brew_cooling_method, 0, 0, product->hops.at(i).utilisation, product->hops.at(i).bu_factor);
 	item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0'));
         item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
         ui->hopsTable->setItem(i, 7, item);
@@ -256,19 +256,21 @@
     product->hops_ok = true;
     for (int i = 0; i < product->hops.size(); i++) {
 
-	ibus += Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg,
+	ibus += Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg, product->est_og3,
 			   product->batch_size, product->hops.at(i).amount,
                            product->hops.at(i).time, product->hops.at(i).alpha, product->ibu_method,
 			   product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6, product->boil_time,
-			   product->brew_cooling_method, 0, 0);
-	ferm_ibus += Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg,
+			   product->brew_cooling_method, 0, 0, product->hops.at(i).utilisation, product->hops.at(i).bu_factor);
+	ferm_ibus += Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg, product->est_og3,
 			   product->brew_fermenter_volume + product->brew_fermenter_tcloss, product->hops.at(i).amount,
                            product->hops.at(i).time, product->hops.at(i).alpha, product->ibu_method,
 			   product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6, product->boil_time,
-			   product->brew_cooling_method, 0, 0);
+			   product->brew_cooling_method, 0, 0, product->hops.at(i).utilisation, product->hops.at(i).bu_factor);
 
-	hop_flavour += Utils::hopFlavourContribution(product->hops.at(i).time, product->batch_size, product->hops.at(i).useat, product->hops.at(i).amount);
-        hop_aroma += Utils::hopAromaContribution(product->hops.at(i).time, product->batch_size, product->hops.at(i).useat, product->hops.at(i).amount);
+	hop_flavour += Utils::hopFlavourContribution(product->hops.at(i).time, product->batch_size, product->hops.at(i).useat,
+				product->hops.at(i).amount, product->hops.at(i).form);
+        hop_aroma += Utils::hopAromaContribution(product->hops.at(i).time, product->batch_size, product->hops.at(i).useat,
+				product->hops.at(i).amount, product->hops.at(i).form);
 	if ((((product->inventory_reduced <= PROD_STAGE_BREW)    && (product->hops.at(i).useat <= HOP_USEAT_WHIRLPOOL)) ||
              ((product->inventory_reduced <= PROD_STAGE_PACKAGE) && (product->hops.at(i).useat == HOP_USEAT_DRY_HOP))) &&
               (product->hops.at(i).inventory < product->hops.at(i).amount))
@@ -360,11 +362,12 @@
     item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
     ui->hopsTable->setItem(product->hops_row, 8, item);
 
-    double ibu = Utils::toIBU(product->hops.at(product->hops_row).useat, product->hops.at(product->hops_row).form, product->preboil_sg,
+    double ibu = Utils::toIBU(product->hops.at(product->hops_row).useat, product->hops.at(product->hops_row).form, product->preboil_sg, product->est_og3,
                               product->batch_size, product->hops.at(product->hops_row).amount, product->hops.at(product->hops_row).time,
                               product->hops.at(product->hops_row).alpha, product->ibu_method,
 			      product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6, product->boil_time,
-			      product->brew_cooling_method, 0, 0);
+			      product->brew_cooling_method, 0, 0,
+			      product->hops.at(product->hops_row).utilisation, product->hops.at(product->hops_row).bu_factor);
 
     ibuEdit->setValue(ibu);
     item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0'));
@@ -395,11 +398,12 @@
     item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
     ui->hopsTable->setItem(product->hops_row, 6, item);
 
-    double ibu = Utils::toIBU(product->hops.at(product->hops_row).useat, product->hops.at(product->hops_row).form, product->preboil_sg,
+    double ibu = Utils::toIBU(product->hops.at(product->hops_row).useat, product->hops.at(product->hops_row).form, product->preboil_sg, product->est_og3,
                               product->batch_size, product->hops.at(product->hops_row).amount, product->hops.at(product->hops_row).time,
  			      product->hops.at(product->hops_row).alpha, product->ibu_method,
 			      product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6, product->boil_time,
-			      product->brew_cooling_method, 0, 0);
+			      product->brew_cooling_method, 0, 0,
+			      product->hops.at(product->hops_row).utilisation, product->hops.at(product->hops_row).bu_factor);
 
     ibuEdit->setValue(ibu);
     item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0'));
@@ -461,11 +465,12 @@
     hnameEdit->setText(product->hops.at(product->hops_row).name);
     horiginEdit->setText(product->hops.at(product->hops_row).origin);
 
-    double ibu = Utils::toIBU(product->hops.at(product->hops_row).useat, product->hops.at(product->hops_row).form, product->preboil_sg,
+    double ibu = Utils::toIBU(product->hops.at(product->hops_row).useat, product->hops.at(product->hops_row).form, product->preboil_sg, product->est_og3,
 		              product->batch_size, product->hops.at(product->hops_row).amount, product->hops.at(product->hops_row).time,
 			      product->hops.at(product->hops_row).alpha, product->ibu_method,
 			      product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6, product->boil_time,
-			      product->brew_cooling_method, 0, 0);
+			      product->brew_cooling_method, 0, 0,
+			      product->hops.at(product->hops_row).utilisation, product->hops.at(product->hops_row).bu_factor);
     ibuEdit->setValue(ibu);
 
     ui->hopsTable->setItem(product->hops_row, 0, new QTableWidgetItem(product->hops.at(product->hops_row).origin));
@@ -686,11 +691,12 @@
     ibuEdit->setReadOnly(true);
     ibuEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
     ibuEdit->setDecimals(1);
-    double ibu = Utils::toIBU(product->hops.at(product->hops_row).useat, product->hops.at(product->hops_row).form, product->preboil_sg,
+    double ibu = Utils::toIBU(product->hops.at(product->hops_row).useat, product->hops.at(product->hops_row).form, product->preboil_sg, product->est_og3,
                               product->batch_size, product->hops.at(product->hops_row).amount, product->hops.at(product->hops_row).time,
                               product->hops.at(product->hops_row).alpha, product->ibu_method,
 			      product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6, product->boil_time,
-			      product->brew_cooling_method, 0, 0);
+			      product->brew_cooling_method, 0, 0,
+			      product->hops.at(product->hops_row).utilisation, product->hops.at(product->hops_row).bu_factor);
     ibuEdit->setValue(ibu);
 
     hop_instock_changed(true);
--- a/src/EditRecipeExport.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/EditRecipeExport.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -471,9 +471,9 @@
     memo.append("[tabular]\n");
     memo.append("[head]Hop[/head][head]Vorm[/head][head]Alpha[/head][head]IBU[/head][head]Gram[/head][head]Toevoegen moment[/head]\n");
     for (int i = 0; i < recipe->hops.size(); i++) {
-	double ibu = Utils::toIBU(recipe->hops.at(i).useat, recipe->hops.at(i).form, recipe->preboil_sg, recipe->batch_size,
+	double ibu = Utils::toIBU(recipe->hops.at(i).useat, recipe->hops.at(i).form, recipe->preboil_sg, recipe->est_og, recipe->batch_size,
 			recipe->hops.at(i).amount, recipe->hops.at(i).time, recipe->hops.at(i).alpha, recipe->ibu_method,
-			0, recipe->hops.at(i).time, 0, recipe->boil_time, 0, 0, 0);
+			0, recipe->hops.at(i).time, 0, recipe->boil_time, 0, 0, 0, recipe->hops.at(i).utilisation, recipe->hops.at(i).bu_factor);
 	memo.append("[row][data]" + recipe->hops.at(i).name + " (" + recipe->hops.at(i).origin + ")[/data]");
 	memo.append("[data]" + QCoreApplication::translate("HopForm", g_hop_forms[recipe->hops.at(i).form]) + "[/data]");
 	memo.append("[data]" + QString::number(recipe->hops.at(i).alpha, 'f', 1) + "[/data]");
--- a/src/EditRecipeTab3.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/EditRecipeTab3.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -95,9 +95,10 @@
 	item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
         ui->hopsTable->setItem(i, 6, item);
 
-	double ibu = Utils::toIBU(recipe->hops.at(i).useat, recipe->hops.at(i).form, recipe->preboil_sg, recipe->batch_size, recipe->hops.at(i).amount,
+	double ibu = Utils::toIBU(recipe->hops.at(i).useat, recipe->hops.at(i).form, recipe->preboil_sg, recipe->est_og,
+			   recipe->batch_size, recipe->hops.at(i).amount,
 	                   recipe->hops.at(i).time, recipe->hops.at(i).alpha, recipe->ibu_method, 0, recipe->hops.at(i).time,
-			   0, recipe->boil_time, 0, 0, 0);
+			   0, recipe->boil_time, 0, 0, 0, recipe->hops.at(i).utilisation, recipe->hops.at(i).bu_factor);
 	item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0'));
         item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
         ui->hopsTable->setItem(i, 7, item);
@@ -222,11 +223,14 @@
 
     for (int i = 0; i < recipe->hops.size(); i++) {
 
-	ibus += Utils::toIBU(recipe->hops.at(i).useat, recipe->hops.at(i).form, recipe->preboil_sg, recipe->batch_size, recipe->hops.at(i).amount,
+	ibus += Utils::toIBU(recipe->hops.at(i).useat, recipe->hops.at(i).form, recipe->preboil_sg, recipe->est_og, recipe->batch_size,
+			   recipe->hops.at(i).amount,
                            recipe->hops.at(i).time, recipe->hops.at(i).alpha, recipe->ibu_method, 0, recipe->hops.at(i).time,
-			   0, recipe->boil_time, 0, 0, 0);
-	hop_flavour += Utils::hopFlavourContribution(recipe->hops.at(i).time, recipe->batch_size, recipe->hops.at(i).useat, recipe->hops.at(i).amount);
-        hop_aroma += Utils::hopAromaContribution(recipe->hops.at(i).time, recipe->batch_size, recipe->hops.at(i).useat, recipe->hops.at(i).amount);
+			   0, recipe->boil_time, 0, 0, 0, recipe->hops.at(i).utilisation, recipe->hops.at(i).bu_factor);
+	hop_flavour += Utils::hopFlavourContribution(recipe->hops.at(i).time, recipe->batch_size, recipe->hops.at(i).useat,
+				recipe->hops.at(i).amount, recipe->hops.at(i).form);
+        hop_aroma += Utils::hopAromaContribution(recipe->hops.at(i).time, recipe->batch_size, recipe->hops.at(i).useat,
+				recipe->hops.at(i).amount, recipe->hops.at(i).form);
     }
 
     hop_flavour = round(hop_flavour * 1000.0 / 5.0) / 10;
@@ -308,10 +312,10 @@
     item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
     ui->hopsTable->setItem(recipe->hops_row, 8, item);
 
-    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg,
+    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg, recipe->est_og,
                               recipe->batch_size, recipe->hops.at(recipe->hops_row).amount, recipe->hops.at(recipe->hops_row).time,
                               recipe->hops.at(recipe->hops_row).alpha, recipe->ibu_method, 0, recipe->hops.at(recipe->hops_row).time,
-			      0, recipe->boil_time, 0, 0, 0);
+			      0, recipe->boil_time, 0, 0, 0, recipe->hops.at(recipe->hops_row).utilisation, recipe->hops.at(recipe->hops_row).bu_factor);
 
     ibuEdit->setValue(ibu);
     item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0'));
@@ -332,10 +336,10 @@
     item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
     ui->hopsTable->setItem(recipe->hops_row, 4, item);
 
-    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg,
+    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg, recipe->est_og,
                               recipe->batch_size, recipe->hops.at(recipe->hops_row).amount, recipe->hops.at(recipe->hops_row).time,
                               recipe->hops.at(recipe->hops_row).alpha, recipe->ibu_method, 0, recipe->hops.at(recipe->hops_row).time,
-                              0, recipe->boil_time, 0, 0, 0);
+                              0, recipe->boil_time, 0, 0, 0, recipe->hops.at(recipe->hops_row).utilisation, recipe->hops.at(recipe->hops_row).bu_factor);
 
     ibuEdit->setValue(ibu);
     item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0'));
@@ -364,10 +368,10 @@
     item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
     ui->hopsTable->setItem(recipe->hops_row, 6, item);
 
-    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg,
+    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg, recipe->est_og,
                               recipe->batch_size, recipe->hops.at(recipe->hops_row).amount, recipe->hops.at(recipe->hops_row).time,
  			      recipe->hops.at(recipe->hops_row).alpha, recipe->ibu_method, 0, recipe->hops.at(recipe->hops_row).time,
-			      0, recipe->boil_time, 0, 0, 0);
+			      0, recipe->boil_time, 0, 0, 0, recipe->hops.at(recipe->hops_row).utilisation, recipe->hops.at(recipe->hops_row).bu_factor);
 
     ibuEdit->setValue(ibu);
     item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0'));
@@ -426,10 +430,10 @@
     hnameEdit->setText(recipe->hops.at(recipe->hops_row).name);
     horiginEdit->setText(recipe->hops.at(recipe->hops_row).origin);
 
-    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg,
+    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg, recipe->est_og,
 		              recipe->batch_size, recipe->hops.at(recipe->hops_row).amount, recipe->hops.at(recipe->hops_row).time,
 			      recipe->hops.at(recipe->hops_row).alpha, recipe->ibu_method, 0, recipe->hops.at(recipe->hops_row).time,
-			      0, recipe->boil_time, 0, 0, 0);
+			      0, recipe->boil_time, 0, 0, 0, recipe->hops.at(recipe->hops_row).utilisation, recipe->hops.at(recipe->hops_row).bu_factor);
     ibuEdit->setValue(ibu);
 
     ui->hopsTable->setItem(recipe->hops_row, 0, new QTableWidgetItem(recipe->hops.at(recipe->hops_row).origin));
@@ -513,10 +517,10 @@
     item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
     ui->hopsTable->setItem(recipe->hops_row, 3, item);
 
-    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg,
+    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg, recipe->est_og,
                               recipe->batch_size, recipe->hops.at(recipe->hops_row).amount, recipe->hops.at(recipe->hops_row).time,
                               recipe->hops.at(recipe->hops_row).alpha, recipe->ibu_method, 0, recipe->hops.at(recipe->hops_row).time,
-                              0, recipe->boil_time, 0, 0, 0);
+                              0, recipe->boil_time, 0, 0, 0, recipe->hops.at(recipe->hops_row).utilisation, recipe->hops.at(recipe->hops_row).bu_factor);
 
     ibuEdit->setValue(ibu);
     item = new QTableWidgetItem(QString("%1").arg(ibu, 2, 'f', 1, '0'));
@@ -688,10 +692,10 @@
     ibuEdit->setReadOnly(true);
     ibuEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
     ibuEdit->setDecimals(1);
-    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg,
+    double ibu = Utils::toIBU(recipe->hops.at(recipe->hops_row).useat, recipe->hops.at(recipe->hops_row).form, recipe->preboil_sg, recipe->est_og,
                               recipe->batch_size, recipe->hops.at(recipe->hops_row).amount, recipe->hops.at(recipe->hops_row).time,
                               recipe->hops.at(recipe->hops_row).alpha, recipe->ibu_method, 0, recipe->hops.at(recipe->hops_row).time,
-			      0, recipe->boil_time, 0, 0, 0);
+			      0, recipe->boil_time, 0, 0, 0, recipe->hops.at(recipe->hops_row).utilisation, recipe->hops.at(recipe->hops_row).bu_factor);
     ibuEdit->setValue(ibu);
 
     hop_instock_changed(true);
--- a/src/InventoryHops.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/InventoryHops.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -136,19 +136,27 @@
         item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
         this->tableHops->setItem(ridx, 7, item);
 
-	if (query.value(15).toDouble() > 0)
-	    this->tableHops->setItem(ridx, 8, new QTableWidgetItem(query.value(17).toString()));  /* Harvest */
+	if (query.value("inventory").toDouble() > 0)
+	    this->tableHops->setItem(ridx, 8, new QTableWidgetItem(query.value("production_date").toString()));
 	else
 	    this->tableHops->setItem(ridx, 8, new QTableWidgetItem(QString("")));
 
 	w = QString("");
-	if (query.value(15).toDouble() > 0) {
-	  if (query.value(15).toDouble() < 1.000) {
-	    w = QString("%1 gr").arg(query.value(15).toDouble() * 1000.0, 2, 'f', 1, '0' );
-	  } else {
-            w = QString("%1 kg").arg(query.value(15).toDouble(), 4, 'f', 3, '0' );
-	  }
-	}
+	if (query.value("inventory").toDouble() > 0) {
+	    if (query.value("form").toInt() < HOP_FORMS_CO2EXTRACT) {
+	    	if (query.value("inventory").toDouble() < 1.000) {
+	      	    w = QString("%1 gr").arg(query.value("inventory").toDouble() * 1000.0, 2, 'f', 1, '0' );
+	    	} else {
+              	    w = QString("%1 kg").arg(query.value("inventory").toDouble(), 4, 'f', 3, '0' );
+	    	}
+	    } else {
+            	if (query.value("inventory").toDouble() < 1.000) {
+              	    w = QString("%1 mL").arg(query.value("inventory").toDouble() * 1000.0, 2, 'f', 1, '0' );
+            	} else {
+              	    w = QString("%1 L").arg(query.value("inventory").toDouble(), 4, 'f', 3, '0' );
+            	}
+	    }
+        }
 	item = new QTableWidgetItem(w);
 	item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
 	this->tableHops->setItem(ridx, 9, item);
--- a/src/PrinterDialog.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/PrinterDialog.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -435,9 +435,10 @@
 
 	    double cost = recipe->hops.at(i).amount * recipe->hops.at(i).cost;
 	    cost_hops += cost;
-	    double ibu = Utils::toIBU(recipe->hops.at(i).useat, recipe->hops.at(i).form, recipe->preboil_sg, recipe->batch_size,
+	    double ibu = Utils::toIBU(recipe->hops.at(i).useat, recipe->hops.at(i).form, recipe->preboil_sg, recipe->est_og, recipe->batch_size,
 			    	      recipe->hops.at(i).amount, recipe->hops.at(i).time, recipe->hops.at(i).alpha,
-				      recipe->ibu_method, 0, recipe->hops.at(i).time, 0, recipe->boil_time, 0, 0, 0);
+				      recipe->ibu_method, 0, recipe->hops.at(i).time, 0, recipe->boil_time, 0, 0, 0,
+				      recipe->hops.at(i).utilisation, recipe->hops.at(i).bu_factor);
 
 	    if (recipe->hops.at(i).useat == 2 || recipe->hops.at(i).useat == 4)	// Boil or Whirlpool
 		use = QCoreApplication::translate("HopUse", g_hop_useat[recipe->hops.at(i).useat]) + QString(" %1 min").arg(recipe->hops.at(i).time);
@@ -846,10 +847,10 @@
 
             double cost = product->hops.at(i).amount * product->hops.at(i).cost;
             cost_hops += cost;
-            double ibu = Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg, product->batch_size,
+            double ibu = Utils::toIBU(product->hops.at(i).useat, product->hops.at(i).form, product->preboil_sg, product->est_og3, product->batch_size,
                                       product->hops.at(i).amount, product->hops.at(i).time, product->hops.at(i).alpha,
                                       product->ibu_method, product->brew_whirlpool9, product->brew_whirlpool7, product->brew_whirlpool6,
-				      product->boil_time, product->brew_cooling_method, 0, 0);
+				      product->boil_time, product->brew_cooling_method, 0, 0, product->hops.at(i).utilisation, product->hops.at(i).bu_factor);
 
             if (product->hops.at(i).useat == 2 || product->hops.at(i).useat == 4)     // Boil or Whirlpool
                 use = QCoreApplication::translate("HopUse", g_hop_useat[product->hops.at(i).useat]) + QString(" %1 min").arg(product->hops.at(i).time);
--- a/src/Utils.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/Utils.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -391,10 +391,51 @@
 }
 
 
-double Utils::TinsethIBU(int Form, double SG, double Volume, double Amount, double T1, double T2, double Alpha)
+double Utils::TinsethIBU(int Form, double SG, double Volume, double Amount, double T1, double T2, double Alpha, double Utilisation, double BU_factor)
 {
     double alpha = Alpha / 100.0;
     double mass = Amount * 1000.0;
+    double ibu = 0.0;
+
+    /*
+     * Most suppliers of CO2 hop extract (not isomerized) use formulas
+     * like this. For now, manual set a utilisation factor of 35%.
+     */
+    if (Form == HOP_FORMS_CO2EXTRACT) {
+	double utilisation = 0.35;	// 35%
+	double factor_sg = 1.0;
+	double factor_bt = 1.0;
+	double bt = T2 - T1;
+
+	/* Table from Wildabouthops */
+	if (SG >= 1.150)
+	    factor_sg = 1.3;
+	else if (SG >= 1.110)
+	    factor_sg = 1.2;
+	else if (SG >= 1.080)
+	    factor_sg = 1.1;
+
+	if (bt > 60) {
+	    factor_bt = ((bt - 60) / 300) + 1.0;
+	} else {
+	    factor_bt = (bt / 60.0);
+	}
+	qDebug() << "factor_sg" << factor_sg << "factor_bt" << factor_bt;
+
+	ibu = ((Utilisation / 100.0) * alpha * mass * 1000.0) / Volume;
+	ibu = (ibu * factor_bt) / factor_sg;
+	qDebug() << "TinsethIBU CO2" << Amount << Alpha << Volume << Utilisation << BU_factor << "SG" << SG << "T" << T1 << T2 << "ibu:" << ibu;
+	return ibu;
+    }
+
+    if (Form == HOP_FORMS_ISOEXTRACT) {
+
+	double dosageHl = ((100.0 / Utilisation) * (100.0 / Alpha) * 0.001) / BU_factor;	// IBU per liter
+	qDebug() << (100 / Utilisation) << (100 / Alpha) << dosageHl << dosageHl * Volume << mass / (dosageHl * Volume);
+	ibu = mass / (dosageHl * Volume);
+	qDebug() << "TinsethIBU ISO" << Amount << Alpha << Volume << Utilisation << BU_factor << "ibu:" << ibu;
+	return ibu;
+    }
 
     /*
      * Basic Tinseth formula.
@@ -404,7 +445,8 @@
     double Bigness_factor = 1.65 * pow(0.000125, SG - 1);
     double BoilTime_factor1 = ((1 - exp(-0.04 * T1)) / 4.15);
     double BoilTime_factor2 = ((1 - exp(-0.04 * T2)) / 4.15);
-    double ibu = Bigness_factor * (BoilTime_factor2 - BoilTime_factor1) * AddedAlphaAcids;
+    ibu = Bigness_factor * (BoilTime_factor2 - BoilTime_factor1) * AddedAlphaAcids;
+    qDebug() << "TinsethIBU nor" << SG << Amount << Alpha << Volume << Bigness_factor * (BoilTime_factor2 - BoilTime_factor1) << "ibu:" << ibu;
 
     /*
      * Correction for hop forms
@@ -417,17 +459,16 @@
 	ibu *= (1 + my_factor_wethop / 100.0);      // From https://github.com/chrisgilmerproj/brewday/blob/master/brew/constants.py
     } else if (Form == HOP_FORMS_CRYO) {
 	ibu *= (1 + my_factor_cryohop / 100.0);
-    } else if (Form == HOP_FORMS_EXTRACT) {
-	// Nothing for now.
     }
 
-    //qDebug() << "boilIBU" << Form << SG << Volume << Amount << T1 << T2 << Alpha << "IBU:" << ibu;
+    qDebug() << "boilIBU" << Form << SG << Volume << Amount << BoilTime_factor2 << BoilTime_factor1 << Alpha << "IBU:" << ibu;
     return ibu;
 }
 
 
-double Utils::toIBU(int Use, int Form, double SG, double Volume, double Amount, double Boiltime, double Alpha,
-		    int Method, double Whirlpool9, double Whirlpool7, double Whirlpool6, double Fulltime, int Cooltype, double Coolparm1, double Coolparm2)
+double Utils::toIBU(int Use, int Form, double preSG, double postSG, double Volume, double Amount, double Boiltime, double Alpha,
+		    int Method, double Whirlpool9, double Whirlpool7, double Whirlpool6, double Fulltime,
+		    int Cooltype, double Coolparm1, double Coolparm2, double Utilisation, double BU_factor)
 {
     double ibu = 0.0;
 
@@ -440,7 +481,7 @@
 	 *
 	 * http://scottjanish.com/the-locksmith-utilizing-bioengineered-yeast-and-high-bound-thiol-precersour-hops-and-phantasm-powder-to-thiol-drive-beer/
 	 */
-	ibu = TinsethIBU(Form, SG, Volume, Amount, 0, 60, Alpha) * (1 + my_factor_mashhop / 100.0);
+	ibu = TinsethIBU(Form, preSG, Volume, Amount, 0, 60, Alpha, Utilisation, BU_factor) * (1 + my_factor_mashhop / 100.0);
 
     } else if ((Use == HOP_USEAT_FWH) || (Use == HOP_USEAT_BOIL)) {
 	/*
@@ -449,10 +490,12 @@
 	double boil_time = Fulltime;
 	if (Use == HOP_USEAT_BOIL)
 	    boil_time = Boiltime;
-	ibu = TinsethIBU(Form, SG, Volume, Amount, 0, boil_time, Alpha);
+	double fromSG = postSG - ((boil_time / Fulltime) * (postSG - preSG));	/* SG when this hop addition starts */
+	double avgSG = (postSG + fromSG) / 2;					/* Average SG during this addition */
+	ibu = TinsethIBU(Form, avgSG, Volume, Amount, 0, boil_time, Alpha, Utilisation, BU_factor);
 
 	/*
-	 * Corrections for Mash and FWH
+	 * Correction for FWH
 	 */
 	if (Use == HOP_USEAT_FWH) {
             ibu *= (1 + my_factor_fwh / 100.0);
@@ -466,7 +509,7 @@
 	     * Flameout, currently fixed 1 minute.
 	     */
 	    double flameout_time = 1;
-	    double fibu = TinsethIBU(Form, SG, Volume, Amount, boil_time, boil_time + flameout_time, Alpha);
+	    double fibu = TinsethIBU(Form, postSG, Volume, Amount, boil_time, boil_time + flameout_time, Alpha, Utilisation, BU_factor);
 	    fibu *= IBU_reduction(98.0);
 	    //qDebug() << "during flameout" << fibu;
 	    nibu += fibu;
@@ -476,19 +519,19 @@
 	     * Hopstands, this boil hop adds some IBU's too.
 	     */
 	    if (Whirlpool9) {
-		double wibu9 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool9, Alpha);
+		double wibu9 = TinsethIBU(Form, postSG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool9, Alpha, Utilisation, BU_factor);
 		wibu9 *= IBU_reduction(87.0);
 		//qDebug() << "during whirlpool9" << wibu9;
 		nibu += wibu9;
 	    }
 	    if (Whirlpool7) {
-		double wibu7 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool7, Alpha);
+		double wibu7 = TinsethIBU(Form, postSG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool7, Alpha, Utilisation, BU_factor);
 		wibu7 *= IBU_reduction(74.0);
 		//qDebug() << "during whirlpool7" << wibu7;
 		nibu += wibu7;
 	    }
 	    if (Whirlpool6) {
-                double wibu6 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool6, Alpha);
+                double wibu6 = TinsethIBU(Form, postSG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool6, Alpha, Utilisation, BU_factor);
                 wibu6 *= IBU_reduction(63.0);
                 //qDebug() << "during whirlpool6" << wibu6;
                 nibu += wibu6;
@@ -502,25 +545,25 @@
 	 * At flameout, and only using extended calculation.
 	 */
 	double flameout_time = 1;
-        ibu = TinsethIBU(Form, SG, Volume, Amount, 0, flameout_time, Alpha);
+        ibu = TinsethIBU(Form, postSG, Volume, Amount, 0, flameout_time, Alpha, Utilisation, BU_factor);
         ibu *= IBU_reduction(98.0);
 	/*
          * Hopstands, this flameout hop adds some IBU's too.
          */
         if (Whirlpool9) {
-            double wibu9 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool9, Alpha);
+            double wibu9 = TinsethIBU(Form, postSG, Volume, Amount, flameout_time, flameout_time + Whirlpool9, Alpha, Utilisation, BU_factor);
             wibu9 *= IBU_reduction(87.0);
             //qDebug() << "during whirlpool9" << wibu9;
             ibu += wibu9;
         }
         if (Whirlpool7) {
-            double wibu7 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool7, Alpha);
+            double wibu7 = TinsethIBU(Form, postSG, Volume, Amount, flameout_time, flameout_time + Whirlpool7, Alpha, Utilisation, BU_factor);
             wibu7 *= IBU_reduction(74.0);
             //qDebug() << "during whirlpool7" << wibu7;
             ibu += wibu7;
         }
         if (Whirlpool6) {
-            double wibu6 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool6, Alpha);
+            double wibu6 = TinsethIBU(Form, postSG, Volume, Amount, flameout_time, flameout_time + Whirlpool6, Alpha, Utilisation, BU_factor);
             wibu6 *= IBU_reduction(63.0);
             //qDebug() << "during whirlpool6" << wibu6;
             ibu += wibu6;
@@ -531,33 +574,39 @@
          * Hopstands.
          */
         if (Whirlpool9) {
-            double wibu9 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool9, Alpha);
+            double wibu9 = TinsethIBU(Form, postSG, Volume, Amount, 0, Whirlpool9, Alpha, Utilisation, BU_factor);
             wibu9 *= IBU_reduction(87.0);
             //qDebug() << "during whirlpool9" << wibu9;
             ibu = wibu9;
         }
         if (Whirlpool7) {
-            double wibu7 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool7, Alpha);
+            double wibu7 = TinsethIBU(Form, postSG, Volume, Amount, 0, Whirlpool7, Alpha, Utilisation, BU_factor);
             wibu7 *= IBU_reduction(74.0);
             //qDebug() << "during whirlpool7" << wibu7;
             ibu = wibu7;
         }
         if (Whirlpool6) {
-            double wibu6 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool6, Alpha);
+            double wibu6 = TinsethIBU(Form, postSG, Volume, Amount, 0, Whirlpool6, Alpha, Utilisation, BU_factor);
             wibu6 *= IBU_reduction(63.0);
             //qDebug() << "during whirlpool6" << wibu6;
             ibu = wibu6;
         }
+    } else if (Use == HOP_USEAT_BOTTLING) {
+	/*
+	 * Isomerized hop extracts to use at bottling.
+	 * Assume 10% volume is lost during fermentation and transfers.
+	 */
+	ibu = TinsethIBU(Form, postSG, Volume * 0.9, Amount, 0, 0, Alpha, Utilisation, BU_factor);
     }
 
     double rc = round(ibu * 1000.0) / 1000.0;
 
-    qDebug() << "toIBU" << Use << Form << SG << Volume << Amount << Boiltime << Alpha << Method << Whirlpool9 << Whirlpool7 << Whirlpool6 << Fulltime << Cooltype << Coolparm1 << Coolparm2 << "rc:" << rc;
+    qDebug() << "toIBU" << Use << Form << preSG << postSG << Volume << Amount << Boiltime << Alpha << Method << Whirlpool9 << Whirlpool7 << Whirlpool6 << Fulltime << Cooltype << Coolparm1 << Coolparm2 << Utilisation << BU_factor << "rc:" << rc;
     return rc;
 }
 
 
-double Utils::hopFlavourContribution(double bt, double vol, int use, double amount)
+double Utils::hopFlavourContribution(double bt, double vol, int use, double amount, int form)
 {
     double result;
 
@@ -576,9 +625,13 @@
 }
 
 
-double Utils::hopAromaContribution(double bt, double vol, int use, double amount)
+double Utils::hopAromaContribution(double bt, double vol, int use, double amount, int form)
 {
     double result = 0.0;
+    double factor = 1.0;
+
+    if (form == HOP_FORMS_CRYO)
+	factor = 2.0;
 
     if (use == HOP_USEAT_DRY_HOP) {
 	result = 1.33;
@@ -595,7 +648,7 @@
     } else if (use == HOP_USEAT_AROMA) {  // Aroma
 	result = 1.2;
     }
-    return (result * amount * 1000.0) / vol;
+    return (result * amount * factor * 1000.0) / vol;
 }
 
 
--- a/src/Utils.h	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/Utils.h	Fri Jul 29 13:12:26 2022 +0200
@@ -65,16 +65,19 @@
      * @param T1 in minutes start of time window
      * @param T2 in minutes end of time window
      * @param Alpha in procent
+     * @param Utilisation Hop extract utilisation
+     * @param BU_factor Bitterness Unit multiply factor
      * @return The calculated IBU's
      */
-    double TinsethIBU(int Form, double SG, double Volume, double Amount, double T1, double T2, double Alpha);
+    double TinsethIBU(int Form, double SG, double Volume, double Amount, double T1, double T2, double Alpha, double Utilisation, double BU_factor);
 
     /**
      * @brief Calculate IBU's of a hop during the whole production process.
      * @param Use HOP_USEAT_MASH HOP_USEAT_FWH HOP_USEAT_BOIL HOP_USEAT_AROMA HOP_USEAT_WHIRLPOOL HOP_USEAT_DRY_HOP HOP_USEAT_BOTTLING
      * @param Form HOP_FORMS_PELLET HOP_FORMS_PLUG HOP_FORMS_LEAF HOP_FORMS_LEAF_WET HOP_FORMS_CRYO HOP_FORMS_EXTRACT
-     * @param SG the density
-     * @param Volume in liters
+     * @param preSG the density pre boil.
+     * @param postSG the density after the boil.
+     * @param Volume in liters after boil.
      * @param Amount in kilograms
      * @param Boiltime in minutes
      * @param Alpha in procent
@@ -86,14 +89,35 @@
      * @param Cooltype 0 = N/A, 1 = Emersion chiller, 2 = Counterflow, 3 = Au bain marie, 4 = natural
      * @param Coolparm1 to define later
      * @param Coolparm2 to define later
+     * @param Utilisation Hop extract utilisation
+     * @param BU_factor Bitterness Unit multiply factor
      * @return The calculated IBU's
      */
-    double toIBU(int Use, int Form, double SG, double Volume, double Amount, double Boiltime, double Alpha,
+    double toIBU(int Use, int Form, double preSG, double postSG, double Volume, double Amount, double Boiltime, double Alpha,
                  int Method, double Whirlpool9, double Whirlpool7, double Whirlpool6, double Fulltime,
-		 int Cooltype, double Coolparm1, double Coolparm2);
+		 int Cooltype, double Coolparm1, double Coolparm2, double Utilisation, double BU_factor);
 
-    double hopFlavourContribution(double bt, double vol, int use, double amount);
-    double hopAromaContribution(double bt, double vol, int use, double amount);
+    /**
+     * @brief Estimate hop flavour contribution.
+     * @param bt Boil time in minutes.
+     * @param vol Volume of the wort.
+     * @param use Hop use moment.
+     * @param amount The amount in kg.
+     * @param form The hop form.
+     * @return A number indicating a contribution factor.
+     */
+    double hopFlavourContribution(double bt, double vol, int use, double amount, int form);
+
+    /**
+     * @brief Estimate hop aroma contribution.
+     * @param bt Boil time in minutes.
+     * @param vol Volume of the wort.
+     * @param use Hop use moment.
+     * @param amount The amount in kg.
+     * @param form The hop form.
+     * @return A number indicating a contribution factor.
+     */
+    double hopAromaContribution(double bt, double vol, int use, double amount, int form);
     QString hours_to_string(int hours);
     double kettle_cm(double volume, double kettle_volume, double kettle_height);
     double kettle_vol(double cm, double kettle_volume, double kettle_height);
--- a/src/database/db_product.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/database/db_product.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -364,14 +364,20 @@
 		    h.cohumulone = obj["h_cohumulone"].toDouble();
 		    h.myrcene = obj["h_myrcene"].toDouble();
 		    h.total_oil = obj["h_total_oil"].toDouble();
+		    h.utilisation = obj["h_utilisation"].toDouble();
+		    h.bu_factor = obj["h_bu_factor"].toDouble();
 		    /* Check and update inventory */
-                    yquery.prepare("SELECT inventory FROM inventory_hops WHERE name=:name AND origin=:origin");
+                    yquery.prepare("SELECT inventory,utilisation,bu_factor FROM inventory_hops WHERE name=:name AND origin=:origin");
                     yquery.bindValue(":name", h.name);
                     yquery.bindValue(":origin", h.origin);
                     yquery.exec();
                     if (yquery.first()) {
                         h.avail = true;
-                        h.inventory = yquery.value(0).toDouble();
+                        h.inventory = yquery.value("inventory").toDouble();
+			if (h.utilisation == 0)
+			    h.utilisation = yquery.value("utilisation").toDouble();
+			if (h.bu_factor == 0)
+			    h.bu_factor = yquery.value("bu_factor").toDouble();
                     }
 		    prod->hops.append(h);
 		}
@@ -891,6 +897,8 @@
 		obj.insert("h_cohumulone", round(prod->hops.at(i).cohumulone * 100) / 100);
 		obj.insert("h_myrcene", round(prod->hops.at(i).myrcene * 100) / 100);
 		obj.insert("h_total_oil", round(prod->hops.at(i).total_oil * 100) / 100);
+		obj.insert("h_utilisation", round(prod->hops.at(i).utilisation * 100) / 100);
+		obj.insert("h_bu_factor", round(prod->hops.at(i).bu_factor * 100) / 100);
 //		qDebug() << "hops_Json" << i << obj;
                 array.append(obj);      /* Append this object */
         }
--- a/src/global.cpp	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/global.cpp	Fri Jul 29 13:12:26 2022 +0200
@@ -111,13 +111,14 @@
     QT_TRANSLATE_NOOP("HopTypes", "Both")
 };
 
-const char * const g_hop_forms[6] = {
+const char * const g_hop_forms[7] = {
     QT_TRANSLATE_NOOP("HopForm", "Pellet"),
     QT_TRANSLATE_NOOP("HopForm", "Plug"),
     QT_TRANSLATE_NOOP("HopForm", "Leaf"),
     QT_TRANSLATE_NOOP("HopForm", "Leaf wet"),
     QT_TRANSLATE_NOOP("HopForm", "Cryo"),
-    QT_TRANSLATE_NOOP("HopForm", "Extract")
+    QT_TRANSLATE_NOOP("HopForm", "CO2 extract"),
+    QT_TRANSLATE_NOOP("HopForm", "Iso extract")
 };
 
 const char * const g_hop_useat[7] = {
--- a/src/global.h	Tue Jul 26 19:46:44 2022 +0200
+++ b/src/global.h	Fri Jul 29 13:12:26 2022 +0200
@@ -116,6 +116,8 @@
     double	cohumulone;
     double	myrcene;
     double	total_oil;
+    double	utilisation;
+    double	bu_factor;
     double	inventory;		///< In product, current inventory.
     bool	avail;		///< Product available in database.
 };
@@ -765,12 +767,13 @@
 extern const char * const g_hop_types[];
 
 enum HopForms {
-	HOP_FORMS_PELLET,
+	HOP_FORMS_PELLET,			///< T-90 pellets
 	HOP_FORMS_PLUG,
-	HOP_FORMS_LEAF,
-	HOP_FORMS_LEAF_WET,
-	HOP_FORMS_CRYO,
-	HOP_FORMS_EXTRACT
+	HOP_FORMS_LEAF,				///< Ordinary leafs
+	HOP_FORMS_LEAF_WET,			///< Fresh picked leafs
+	HOP_FORMS_CRYO,				///< Cryo T-45 hops.
+	HOP_FORMS_CO2EXTRACT,			///< CO2 extract and IKE.
+	HOP_FORMS_ISOEXTRACT			///< Isomerized Hop Extract.
 };
 
 extern const char * const g_hop_forms[];
--- a/ui/EditHop.ui	Tue Jul 26 19:46:44 2022 +0200
+++ b/ui/EditHop.ui	Fri Jul 29 13:12:26 2022 +0200
@@ -100,7 +100,7 @@
       <property name="geometry">
        <rect>
         <x>590</x>
-        <y>350</y>
+        <y>410</y>
         <width>141</width>
         <height>20</height>
        </rect>
@@ -116,7 +116,7 @@
       <property name="geometry">
        <rect>
         <x>590</x>
-        <y>380</y>
+        <y>440</y>
         <width>141</width>
         <height>20</height>
        </rect>
@@ -226,7 +226,7 @@
       <property name="geometry">
        <rect>
         <x>740</x>
-        <y>350</y>
+        <y>410</y>
         <width>121</width>
         <height>24</height>
        </rect>
@@ -259,7 +259,7 @@
       <property name="geometry">
        <rect>
         <x>740</x>
-        <y>380</y>
+        <y>440</y>
         <width>121</width>
         <height>24</height>
        </rect>
@@ -565,7 +565,7 @@
        </rect>
       </property>
       <property name="text">
-       <string>Alpha %:</string>
+       <string>ISO Alpha %:</string>
       </property>
       <property name="alignment">
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
@@ -679,9 +679,15 @@
       <property name="alignment">
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
       </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
       <property name="decimals">
        <number>1</number>
       </property>
+      <property name="singleStep">
+       <double>0.100000000000000</double>
+      </property>
      </widget>
      <widget class="QDoubleSpinBox" name="humuleneEdit">
       <property name="geometry">
@@ -695,9 +701,15 @@
       <property name="alignment">
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
       </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
       <property name="decimals">
        <number>1</number>
       </property>
+      <property name="singleStep">
+       <double>0.100000000000000</double>
+      </property>
      </widget>
      <widget class="QDoubleSpinBox" name="caryEdit">
       <property name="geometry">
@@ -711,9 +723,15 @@
       <property name="alignment">
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
       </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
       <property name="decimals">
        <number>1</number>
       </property>
+      <property name="singleStep">
+       <double>0.100000000000000</double>
+      </property>
      </widget>
      <widget class="QDoubleSpinBox" name="myrceneEdit">
       <property name="geometry">
@@ -727,9 +745,15 @@
       <property name="alignment">
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
       </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
       <property name="decimals">
        <number>1</number>
       </property>
+      <property name="singleStep">
+       <double>0.100000000000000</double>
+      </property>
      </widget>
      <widget class="QDoubleSpinBox" name="cohumuloneEdit">
       <property name="geometry">
@@ -743,9 +767,15 @@
       <property name="alignment">
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
       </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
       <property name="decimals">
        <number>1</number>
       </property>
+      <property name="singleStep">
+       <double>0.100000000000000</double>
+      </property>
      </widget>
      <widget class="QDoubleSpinBox" name="alphaEdit">
       <property name="geometry">
@@ -759,9 +789,15 @@
       <property name="alignment">
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
       </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
       <property name="decimals">
        <number>1</number>
       </property>
+      <property name="singleStep">
+       <double>0.100000000000000</double>
+      </property>
      </widget>
      <widget class="QDoubleSpinBox" name="betaEdit">
       <property name="geometry">
@@ -775,9 +811,15 @@
       <property name="alignment">
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
       </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
       <property name="decimals">
        <number>1</number>
       </property>
+      <property name="singleStep">
+       <double>0.100000000000000</double>
+      </property>
      </widget>
      <widget class="QCheckBox" name="alwaysEdit">
       <property name="geometry">
@@ -796,7 +838,7 @@
       <property name="geometry">
        <rect>
         <x>870</x>
-        <y>350</y>
+        <y>410</y>
         <width>28</width>
         <height>22</height>
        </rect>
@@ -816,7 +858,7 @@
       <property name="geometry">
        <rect>
         <x>902</x>
-        <y>350</y>
+        <y>410</y>
         <width>28</width>
         <height>22</height>
        </rect>
@@ -836,7 +878,7 @@
       <property name="geometry">
        <rect>
         <x>902</x>
-        <y>380</y>
+        <y>440</y>
         <width>28</width>
         <height>22</height>
        </rect>
@@ -856,7 +898,7 @@
       <property name="geometry">
        <rect>
         <x>870</x>
-        <y>380</y>
+        <y>440</y>
         <width>28</width>
         <height>22</height>
        </rect>
@@ -872,6 +914,88 @@
         <normaloff>:/icons/silk/date.png</normaloff>:/icons/silk/date.png</iconset>
       </property>
      </widget>
+     <widget class="QLabel" name="utilisationLabel">
+      <property name="geometry">
+       <rect>
+        <x>660</x>
+        <y>350</y>
+        <width>141</width>
+        <height>20</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>Utilisation %:</string>
+      </property>
+      <property name="alignment">
+       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+      </property>
+     </widget>
+     <widget class="QLabel" name="bufactorLabel">
+      <property name="geometry">
+       <rect>
+        <x>660</x>
+        <y>380</y>
+        <width>141</width>
+        <height>20</height>
+       </rect>
+      </property>
+      <property name="text">
+       <string>BU factor:</string>
+      </property>
+      <property name="alignment">
+       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+      </property>
+     </widget>
+     <widget class="QDoubleSpinBox" name="utilisationEdit">
+      <property name="geometry">
+       <rect>
+        <x>810</x>
+        <y>350</y>
+        <width>121</width>
+        <height>24</height>
+       </rect>
+      </property>
+      <property name="toolTip">
+       <string>35% .. 90% for hop extracts</string>
+      </property>
+      <property name="alignment">
+       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+      </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
+      <property name="decimals">
+       <number>1</number>
+      </property>
+      <property name="singleStep">
+       <double>0.500000000000000</double>
+      </property>
+     </widget>
+     <widget class="QDoubleSpinBox" name="bufactorEdit">
+      <property name="geometry">
+       <rect>
+        <x>810</x>
+        <y>380</y>
+        <width>121</width>
+        <height>24</height>
+       </rect>
+      </property>
+      <property name="toolTip">
+       <string>Normal 1.0, tetrahop 1.7 to multiply with iso-alpfa-acid</string>
+      </property>
+      <property name="alignment">
+       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+      </property>
+      <property name="accelerated">
+       <bool>true</bool>
+      </property>
+      <property name="decimals">
+       <number>1</number>
+      </property>
+      <property name="singleStep">
+       <double>0.100000000000000</double>
+      </property>
+     </widget>
     </widget>
    </item>
   </layout>

mercurial