src/EditProductTab11.cpp

Thu, 18 Aug 2022 16:11:20 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 18 Aug 2022 16:11:20 +0200
changeset 397
877420a13815
parent 332
146874d7bb47
child 404
47f1259378fe
permissions
-rw-r--r--

Edit Product, split CO2 package pressure in bottles and kegs. BU:RE code cleanup. calcPack() sets the CO2 values on the first tab. Show final EBC on tab 1 if the stage is after brew. Show final ABV and energy on tab 1 if fermentation is done. Removed wrong bottle priming calculation from calcFermentables() because calcPack() does this.

/**
 * EditProduct.cpp is part of bmsapp.
 *
 * Tab 11, package
 *
 * bmsapp 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 3 of the License, or
 * (at your option) any later version.
 *
 * bmsapp 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

double EditProduct::ResCO2(double T)
{
    double F = T * 1.8 + 32;
    return round((3.0378 - 0.050062 * F + 0.00026555 * F * F) * 1000000.0) / 1000000.0;
}


double EditProduct::CarbCO2toS(double CO2, double T, double SFactor)
{
    //var sugar = SFactor * (CO2 - ResCO2(CO2, T)) / 0.286;
    double sugar = round((SFactor * (CO2 - ResCO2(T)) * 4.014094) * 1000000.0) / 1000000.0;
    if (sugar < 0)
	sugar = 0;
    return sugar; 	//Round(sugar, 3);
}


double EditProduct::GetPressure(double CO2, double T1, double T2)
{
    double V = CO2 - ResCO2(T1);
    V = CO2; // TODO: temp only total pressure, testing
    if (V < 0)
	return 0;

    double P = -1.09145427669121 + 0.00800006989646477 * T2 + 0.000260276315484684 * T2 * T2 + 0.0215142075945119 * T2 * V +
		0.674996600795854 * V + -0.00471757220150754 * V * V;
    if (P < 0)
	P = 0;
    P = round((P * 1.01325) * 100.0) / 100.0;	// atm to bar
    qDebug() << "  GetPressure(" << CO2 << "," << T1 << "," << T2 << ") V:" << V << "Bar:" << P << "ignored ResCO2:" << ResCO2(T1);
    return P;
}


double EditProduct::CarbCO2ToPressure(double CO2, double T)
{
    return (CO2 - (-0.000005594056 * pow(T, 4) + 0.000144357886 * pow(T, 3) +
		0.000362999168 * T * T - 0.064872987645 * T + 1.641145175049)) /
		(0.00000498031 * pow(T, 4) - 0.00024358267 * pow(T, 3) + 0.00385867329 * T * T - 0.05671206825 * T + 1.53801423376);
}


void EditProduct::calcPack()
{
    int		i, j;
    bool	found1, found2;
    QSqlQuery	query;
    double	TSec, SFactor;

    qDebug() << "calcPack()" << product->package_volume << product->package_abv << "inf" << product->package_infuse_amount << product->package_infuse_abv;

    if (product->package_volume < 1) {
	/*
	 * If there is not enough to package, then don't
	 * do any calculations and clear the rest of the form.
	 */
	product->package_volume = 0;
	ui->pack_finalabvShow->setValue(0);

	return;
    }

    double bvol = product->package_volume - (product->package_abv * product->package_volume) / 100.0;
    double balc = product->package_volume - bvol;
    double mvol = product->package_infuse_amount - (product->package_infuse_abv * product->package_infuse_amount) / 100.0;
    double malc = product->package_infuse_amount - mvol;
    double talc = balc + malc;
    double tvol = bvol + mvol;

    product->final_abv = round(talc / (tvol + talc) * 10000.0) / 100.0;
    ui->pack_finalabvShow->setValue(product->final_abv);

    TSec = product->secondary_temp;
    if (TSec < 1)
	TSec = product->primary_end_temp;
    if (TSec < 1)
	TSec = 18;

    /*
     * For bottles and kegs use the following complicated procedure to
     * find the needed priming sugars.
     *
     * 1. If we need a priming sugar then first see in the fermentables
     *    table if there is one selected. If not, clear sugars and done.
     * 2. Then, we update the data from the fermentables inventory.
     * 3. Set the found sugar in the pulldown menu.
     * 4. Calculate the sugar in gr/L and total amount need.
     * 5. Calculate the ABV in the bottles/kegs.
     * 6. Calculate the pressure build by the refermentation.
     *
     * Note that this is all a leftover from the web based application that
     * did work like this. It's a bit strange but it will allways give
     * the right data and sugar concentrations.
     * Sometimes bugs have good things.
     */
    found1 = false;
    if (product->bottle_amount) {
	for (i = 0; i < product->fermentables.size(); i++) {
	    if (product->fermentables.at(i).added == FERMENTABLE_ADDED_BOTTLE) {
		found1 = true;
		break;
	    }
	}
       	if (found1) {
	    SFactor = 1 / ((product->fermentables.at(i).yield / 100) * (1 - product->fermentables.at(i).moisture / 100));
	    qDebug() << "  bottle sugar" << product->fermentables.at(i).name << SFactor << TSec;

	    query.prepare("SELECT name,supplier FROM inventory_fermentables WHERE type = '1' OR type = '3' ORDER BY name");      // Sugars or dry extract
	    query.exec();
	    j = 0;
	    found2 = false;
	    while (query.next()) {
		j++;
		if (query.value(0).toString() == product->fermentables.at(i).name && query.value(1).toString() == product->fermentables.at(i).supplier) {
		    ui->bottle_sugarEdit->setCurrentIndex(j);
		    product->bottle_priming_sugar = j;
		    found2 = true;
		    break;
		}
	    }
	    if (! found2) {
		ui->bottle_sugarEdit->setCurrentIndex(0);	// Make sure not selected
		product->fermentables.removeAt(i);		// Remove false fermentable
		refreshFermentables();
	    } else {
		product->bottle_priming_amount = CarbCO2toS(product->bottle_carbonation, TSec, SFactor);
		ui->bottle_sug_amountShow->setValue(product->bottle_priming_amount);
		double total = round(product->bottle_priming_amount * product->bottle_amount * 100.0) / 100000.0;
		qDebug() << "  total" << total << product->fermentables.at(i).amount;
		if (total != product->fermentables.at(i).amount) {
		    qDebug() << "  update priming sugar" << total;
		    product->fermentables[i].amount = total;
		    refreshFermentables();
		    is_changed();
		}
		ui->bottle_sug_weightShow->setValue(total * 1000);

		double pabv = product->final_abv + (product->bottle_priming_amount * (1 / SFactor) * 0.47) / 7.907;
		double pvol = product->bottle_amount - (pabv * product->bottle_amount) / 100;
		talc = product->bottle_amount - pvol;
  		tvol = pvol + product->bottle_priming_water;
		product->bottle_abv = talc / (tvol + talc) * 100;
		product->bottle_bar = GetPressure(product->bottle_carbonation, TSec, product->bottle_carbonation_temp);
		ui->bottle_abvShow->setValue(product->bottle_abv);
		ui->bottle_barShow->setValue(product->bottle_bar);
	    }
	}
    }
    if (! found1) {
	qDebug() << "  no bottle priming";
	ui->bottle_sug_amountShow->setValue(0);
	ui->bottle_sug_weightShow->setValue(0);
	ui->bottle_abvShow->setValue(0);
	ui->bottle_barShow->setValue(0);
	product->bottle_abv = 0;
	product->bottle_bar = 0;
    }
    ui->est_bottle_co2Edit->setValue(product->bottle_carbonation);
    ui->est_bottle_co2Show->setValue(product->bottle_carbonation);

    ui->keg_sugarLabel->setEnabled(! product->keg_forced_carb);
    ui->keg_sugarEdit->setEnabled(! product->keg_forced_carb);
    ui->keg_sug_weightLabel->setEnabled(! product->keg_forced_carb);
    ui->keg_sug_weightShow->setEnabled(! product->keg_forced_carb);
    ui->keg_sug_waterLabel->setEnabled(! product->keg_forced_carb);
    ui->keg_sug_waterEdit->setEnabled(! product->keg_forced_carb);
    ui->keg_sug_amountLabel->setEnabled(! product->keg_forced_carb);
    ui->keg_sug_amountShow->setEnabled(! product->keg_forced_carb);

    found1 = false;
    if (product->keg_amount) {

	double Pressure = CarbCO2ToPressure(product->keg_carbonation, product->keg_carbonation_temp);
	if (Pressure < 0)
	    Pressure = 0;
	product->keg_pressure = Pressure;
	ui->keg_barShow->setValue(Pressure);

	if (! product->keg_forced_carb) {
	    for (i = 0; i < product->fermentables.size(); i++) {
            	if (product->fermentables.at(i).added == FERMENTABLE_ADDED_KEGS) {
                    found1 = true;
                    break;
            	}
            }
            if (found1) {
            	SFactor = 1 / ((product->fermentables.at(i).yield / 100) * (1 - product->fermentables.at(i).moisture / 100));
            	qDebug() << "  kegs sugar" << product->fermentables.at(i).name << SFactor << TSec;

            	query.prepare("SELECT name,supplier FROM inventory_fermentables WHERE type = '1' OR type = '3' ORDER BY name");      // Sugars or dry extract
            	query.exec();
            	j = 0;
            	found2 = false;
            	while (query.next()) {
                    j++;
                    if (query.value(0).toString() == product->fermentables.at(i).name && query.value(1).toString() == product->fermentables.at(i).supplier) {
                    	ui->keg_sugarEdit->setCurrentIndex(j);
                    	product->keg_priming_sugar = j;
                    	found2 = true;
                    	break;
                    }
            	}
            	if (! found2) {
                    ui->keg_sugarEdit->setCurrentIndex(0);	// Make sure not selected
                    product->fermentables.removeAt(i);	// Remove false fermentable
                    refreshFermentables();
            	} else {
                    product->keg_priming_amount = CarbCO2toS(product->keg_carbonation, TSec, SFactor);
                    ui->keg_sug_amountShow->setValue(product->keg_priming_amount);
                    double total = round(product->keg_priming_amount * product->keg_amount * 100.0) / 100000.0;
                    qDebug() << "  total" << total << product->fermentables.at(i).amount;
                    if (total != product->fermentables.at(i).amount) {
                    	qDebug() << "  update priming sugar" << total;
                    	product->fermentables[i].amount = total;
                    	refreshFermentables();
                    	is_changed();
                    }
		    ui->keg_sug_weightShow->setValue(total * 1000);

                    double pabv = product->final_abv + (product->keg_priming_amount * (1 / SFactor) * 0.47) / 7.907;
                    double pvol = product->keg_amount - (pabv * product->keg_amount) / 100;
                    talc = product->keg_amount - pvol;
                    tvol = pvol + product->keg_priming_water;
		    product->keg_abv = talc / (tvol + talc) * 100;
		    product->keg_bar = GetPressure(product->keg_carbonation, TSec, product->keg_carbonation_temp);
                    ui->keg_abvShow->setValue(product->keg_abv);
                    ui->keg_barShow->setValue(product->keg_bar);
            	}
	    } // if priming sugar.
	    if (! found1) {
        	qDebug() << "  no keg priming";
        	ui->keg_sug_amountShow->setValue(0);
        	ui->keg_sug_weightShow->setValue(0);
        	ui->keg_abvShow->setValue(0);
		product->keg_abv = 0;
		product->keg_bar = 0;
	    }
        } else {
	    /*
	     * Forced carbonation
	     */
	    ui->keg_abvShow->setValue(product->final_abv);
	    product->keg_abv = product->final_abv;
	    ui->keg_sug_amountShow->setValue(0);
            ui->keg_sug_weightShow->setValue(0);
	}
	ui->est_kegs_co2Edit->setValue(product->keg_carbonation);
	ui->est_kegs_co2Show->setValue(product->keg_carbonation);
    } // if keg_amount
}


/*
 * Triggered by writing to ui->pack_abvShow
 */
void EditProduct::pack_abv_changed(double val)
{
    calcPack();
}


void EditProduct::pack_date_changed(QDate val)
{
    qDebug() << "pack_date_changed" << val;
    product->package_date = ui->pack_dateEdit->nullDate();
    is_changed();
    setStage();
}


void EditProduct::pack_date_button()
{
    ui->pack_dateEdit->setDate(QDate::currentDate());
}


void EditProduct::pack_date_ack()
{
    int rc = QMessageBox::warning(this, tr("Confirm package"), tr("Confirm that the beer is packaged and all data is correct"),
                            QMessageBox::Yes | QMessageBox::No, QMessageBox::No);

    if (rc == QMessageBox::No)
        return;

    product->stage = PROD_STAGE_CARBONATION;
    setStage();
    is_changed();
}


void EditProduct::pack_volume_changed(double val)
{
    /*
     * If the total volume is decreased, check if the
     * bottle and kegs amount need to be lowered as well.
     */

    product->package_volume = val;
    calcPack();
    is_changed();
}


void EditProduct::pack_ph_changed(double val)
{
    if (product->package_ph == 0) {
        product->package_ph = 4.0;
        const QSignalBlocker blocker1(ui->pack_phEdit);
        ui->pack_phEdit->setValue(4.0);
    } else {
    	product->package_ph = val;
    }
    is_changed();
}


void EditProduct::pack_infusion_vol_changed(double val)
{
    product->package_infuse_amount = val;
    calcPack();
    is_changed();
}


void EditProduct::pack_infusion_abv_changed(double val)
{
    product->package_infuse_abv = val;
    calcPack();
    is_changed();
}


void EditProduct::pack_infusion_txt_changed(QString val)
{
    product->package_infuse_notes = val;
    is_changed();
}


void EditProduct::bottle_volume_changed(double val)
{
    /*
     * Check if kegs volume plus this new volume fits in the package volume.
     */
    if ((val + product->keg_amount) > product->package_volume) {
        double kegs = product->package_volume - val;
        const QSignalBlocker blocker1(ui->keg_volumeEdit);
        product->keg_amount = kegs;
        ui->keg_volumeEdit->setValue(kegs);
    }
    product->bottle_amount = val;
    calcPack();
    is_changed();
}


void EditProduct::bottle_co2_changed(double val)
{
    product->bottle_carbonation = val;
    calcPack();
    is_changed();
}


void EditProduct::bottle_sugar_changed(int val)
{
    Fermentables newf;
    QSqlQuery   query;

    qDebug() << "bottle_sugar_changed" << product->bottle_priming_sugar << val;
    for (int i = 0; i < product->fermentables.size(); i++) {
	if (product->fermentables.at(i).added == FERMENTABLE_ADDED_BOTTLE) {
	    product->fermentables.removeAt(i);
	    refreshFermentables();
	    break;
	}
    }

    /*
     * Search the sugar pointed by the index.
     */
    QString sql = "SELECT name,origin,supplier,cost,type,yield,color,coarse_fine_diff,moisture,diastatic_power,protein,dissolved_protein,max_in_batch,"
                  "graintype,recommend_mash,add_after_boil,di_ph,acid_to_ph_57,inventory FROM inventory_fermentables "
		  "WHERE type = '1' OR type = '3' ORDER BY name";
    query.prepare(sql);
    query.exec();
    query.first();
    for (int i = 0; i < (val - 1); i++) {
        query.next();
    }

    newf.name = query.value(0).toString();
    newf.origin = query.value(1).toString();
    newf.supplier = query.value(2).toString();
    newf.cost = query.value(3).toDouble();
    newf.type = query.value(4).toInt();
    newf.yield = query.value(5).toDouble();
    newf.color = query.value(6).toDouble();
    newf.coarse_fine_diff = query.value(7).toDouble();
    newf.moisture = query.value(8).toDouble();
    newf.diastatic_power = query.value(9).toDouble();
    newf.protein = query.value(10).toDouble();
    newf.dissolved_protein = query.value(11).toDouble();
    newf.max_in_batch = query.value(12).toDouble();
    newf.graintype = query.value(13).toInt();
    newf.recommend_mash = query.value(14).toInt() ? true:false;
    newf.add_after_boil = true;
    newf.di_ph = query.value(16).toDouble();
    newf.acid_to_ph_57 = query.value(17).toDouble();
    newf.inventory = query.value(18).toDouble();
    newf.amount = 0;
    newf.added = FERMENTABLE_ADDED_BOTTLE;

    product->fermentables.append(newf);
    refreshFermentables();
    const QSignalBlocker blocker1(ui->bottle_sugarEdit);
    calcPack();
    is_changed();
}


void EditProduct::bottle_water_changed(double val)
{
    product->bottle_priming_water = val;
    calcPack();
    is_changed();
}


void EditProduct::bottle_temp_changed(double val)
{
    product->bottle_carbonation_temp = val;
    calcPack();
    is_changed();
}


void EditProduct::kegs_volume_changed(double val)
{
    /*
     * Check if bottle volume plus this new volume fits in the package volume.
     */
    if ((val + product->bottle_amount) > product->package_volume) {
	double bottle = product->package_volume - val;
	const QSignalBlocker blocker1(ui->bottle_volumeEdit);
	product->bottle_amount = bottle;
	ui->bottle_volumeEdit->setValue(bottle);
    }
    product->keg_amount = val;
    calcPack();
    is_changed();
}


void EditProduct::kegs_co2_changed(double val)
{
    product->keg_carbonation = val;
    calcPack();
    is_changed();
}


void EditProduct::kegs_sugar_changed(int val)
{
    Fermentables newf;
    QSqlQuery   query;

    qDebug() << "kegs_sugar_changed" << product->keg_priming_sugar << val;
    for (int i = 0; i < product->fermentables.size(); i++) {
        if (product->fermentables.at(i).added == FERMENTABLE_ADDED_KEGS) {
            product->fermentables.removeAt(i);
            refreshFermentables();
            break;
        }
    }

    /*
     * Search the sugar pointed by the index.
     */
    QString sql = "SELECT name,origin,supplier,cost,type,yield,color,coarse_fine_diff,moisture,diastatic_power,protein,dissolved_protein,max_in_batch,"
                  "graintype,recommend_mash,add_after_boil,di_ph,acid_to_ph_57,inventory FROM inventory_fermentables "
                  "WHERE type = '1' OR type = '3' ORDER BY name";
    query.prepare(sql);
    query.exec();
    query.first();
    for (int i = 0; i < (val - 1); i++) {
        query.next();
    }

    newf.name = query.value(0).toString();
    newf.origin = query.value(1).toString();
    newf.supplier = query.value(2).toString();
    newf.cost = query.value(3).toDouble();
    newf.type = query.value(4).toInt();
    newf.yield = query.value(5).toDouble();
    newf.color = query.value(6).toDouble();
    newf.coarse_fine_diff = query.value(7).toDouble();
    newf.moisture = query.value(8).toDouble();
    newf.diastatic_power = query.value(9).toDouble();
    newf.protein = query.value(10).toDouble();
    newf.dissolved_protein = query.value(11).toDouble();
    newf.max_in_batch = query.value(12).toDouble();
    newf.graintype = query.value(13).toInt();
    newf.recommend_mash = query.value(14).toInt() ? true:false;
    newf.add_after_boil = true;
    newf.di_ph = query.value(16).toDouble();
    newf.acid_to_ph_57 = query.value(17).toDouble();
    newf.inventory = query.value(18).toDouble();
    newf.amount = 0;
    newf.added = FERMENTABLE_ADDED_KEGS;

    product->fermentables.append(newf);
    refreshFermentables();
    const QSignalBlocker blocker1(ui->keg_sugarEdit);
    calcPack();
    is_changed();
}


void EditProduct::kegs_water_changed(double val)
{
    product->keg_priming_water = val;
    calcPack();
    is_changed();
}


void EditProduct::kegs_forced_changed(bool val)
{
    qDebug() << "kegs_forced_changed" << val;

    product->keg_forced_carb = val;
    if (val) {
	/*
	 * Make sure to remove priming sugars.
	 */
	for (int i = 0; i < product->fermentables.size(); i++) {
	    if (product->fermentables.at(i).added == FERMENTABLE_ADDED_KEGS) {
		product->fermentables.removeAt(i);
		refreshFermentables();
		break;
	    }
	}
	product->keg_priming_sugar = 0;
	product->keg_priming_amount = 0;
	product->keg_priming_water = 0;
	const QSignalBlocker blocker1(ui->keg_sugarEdit);
	const QSignalBlocker blocker2(ui->keg_sug_waterEdit);
	ui->keg_sugarEdit->setCurrentIndex(0);
	ui->keg_sug_weightShow->setValue(0);
	ui->keg_sug_waterEdit->setValue(0);
	ui->keg_sug_amountShow->setValue(0);
    }

    calcPack();
    is_changed();
}


void EditProduct::kegs_temp_changed(double val)
{
    product->keg_carbonation_temp = val;
    calcPack();
    is_changed();
}


void EditProduct::carb_log_button()
{
    ChartCarbonate dialog(product->code, product->name, this);
}

mercurial