src/EditProductTab11.cpp

Sat, 08 Jun 2024 15:54:30 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Sat, 08 Jun 2024 15:54:30 +0200
changeset 527
84091b9cb800
parent 448
9e0da3824cf9
permissions
-rw-r--r--

Version 0.4.6a1. Added HLT equipment volume and deadspace settings. In EditProduct the target water selection is now sticky. Changed the water treatment tab. Added a row wich displays the salt adjustments. This can be selected between actual and target values. The treated water show can select between mash or sparge water. The total line will become the final water in the boil kettle. Database update function is expanded with the new settings. Added a popup message warning that the database is upgraded and user action is required for the equipment profiles.

/**
 * 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::GetPressure(double CO2, double T)
{
    if (CO2 < 0)
	return 0;

    double P = -1.09145427669121 + 0.00800006989646477 * T + 0.000260276315484684 * T * T + 0.0215142075945119 * T * CO2 +
		0.674996600795854 * CO2 + -0.00471757220150754 * CO2 * CO2;

    P = round((P * 1.01325) * 100.0) / 100.0;  // atm to bar
    qDebug() << "  GetPressure(" << CO2 << "," << T << ") CO2:" << CO2 << "Bar:" << P;
    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;
    }

    if ((product->stage >= PROD_STAGE_TERTIARY) && (product->fg >= 0.990)) {
	/* Make sure we have the final value. */
	product->package_abv = Utils::abvol(product->brew_fermenter_sg, product->fg);
	ui->pack_abvLabel->setText(tr("Package ABV %:"));
	ui->pack_finalabvLabel->setText(tr("Final ABV %:"));
	ui->pack_finalibuLabel->setText(tr("Final IBU:"));
    } else {
	product->package_abv = product->est_abv;
    }

    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_abvShow->setValue(product->package_abv);
    ui->pack_finalabvShow->setValue(product->final_abv);

    double pack_color = product->brew_fermenter_color * (product->package_volume / (product->package_volume + product->package_infuse_amount));
    double pack_ibu = product->brew_fermenter_ibu * (product->package_volume / (product->package_volume + product->package_infuse_amount));

    ui->pack_finalcolorShow->setValue(pack_color);
    ui->pack_finalcolorShow->setStyleSheet(Utils::ebc_to_style(pack_color));
    ui->pack_finalibuShow->setValue(pack_ibu);

    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 = Utils::CarbCO2toS(product->bottle_carbonation, TSec, SFactor);
		//qDebug() << "  priming CarbCO2toS(" << product->bottle_carbonation << TSec << SFactor << ") =" << product->bottle_priming_amount;
		ui->bottle_sug_amountShow->setValue(product->bottle_priming_amount);
		double total = round(product->bottle_priming_amount * product->bottle_amount * 100.0) / 100000.0;
		if (total != product->fermentables.at(i).amount) {
		    qDebug() << "  total" << 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 = Utils::GetPressureBar(product->bottle_priming_amount * (1 / SFactor), 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 = Utils::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, 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.
     */
    const QSignalBlocker blocker1(ui->keg_volumeEdit);
    if (product->keg_amount < 0)
	product->keg_amount = 0;	/* Failsafe - bugfix */
    if (val > (product->package_volume + product->package_infuse_amount)) {
	val = product->package_volume + product->package_infuse_amount;
	const QSignalBlocker blocker2(ui->bottle_volumeEdit);
	ui->bottle_volumeEdit->setValue(val);
    }

    if ((val + product->keg_amount) > (product->package_volume + product->package_infuse_amount)) {
        double kegs = product->package_volume + product->package_infuse_amount - val;
        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("name").toString();
    newf.origin = query.value("origin").toString();
    newf.supplier = query.value("supplier").toString();
    newf.cost = query.value("cost").toDouble();
    newf.type = query.value("type").toInt();
    newf.yield = query.value("yield").toDouble();
    newf.color = query.value("color").toDouble();
    newf.coarse_fine_diff = query.value("coarse_fine_diff").toDouble();
    newf.moisture = query.value("moisture").toDouble();
    newf.diastatic_power = query.value("diastatic_power").toDouble();
    newf.protein = query.value("protein").toDouble();
    newf.dissolved_protein = query.value("dissolved_protein").toDouble();
    newf.max_in_batch = query.value("max_in_batch").toDouble();
    newf.graintype = query.value("graintype").toInt();
    newf.recommend_mash = query.value("recommend_mash").toInt() ? true:false;
    newf.add_after_boil = true;
    newf.di_ph = query.value("di_ph").toDouble();
    newf.acid_to_ph_57 = query.value("acid_to_ph_57").toDouble();
    newf.inventory = query.value("inventory").toDouble();
    newf.adjust_to_total_100 = false;
    newf.percentage = 0;
    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.
     */
    const QSignalBlocker blocker1(ui->bottle_volumeEdit);
    if (product->bottle_amount < 0)
        product->bottle_amount = 0;        /* Failsafe - bugfix */
    if (val > (product->package_volume + product->package_infuse_amount)) {
        val = product->package_volume + product->package_infuse_amount;
	const QSignalBlocker blocker2(ui->keg_volumeEdit);
	ui->keg_volumeEdit->setValue(val);
    }

    if ((val + product->bottle_amount) > (product->package_volume + product->package_infuse_amount)) {
	double bottle = product->package_volume + product->package_infuse_amount - val;
	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("name").toString();
    newf.origin = query.value("origin").toString();
    newf.supplier = query.value("supplier").toString();
    newf.cost = query.value("cost").toDouble();
    newf.type = query.value("type").toInt();
    newf.yield = query.value("yield").toDouble();
    newf.color = query.value("color").toDouble();
    newf.coarse_fine_diff = query.value("coarse_fine_diff").toDouble();
    newf.moisture = query.value("moisture").toDouble();
    newf.diastatic_power = query.value("diastatic_power").toDouble();
    newf.protein = query.value("protein").toDouble();
    newf.dissolved_protein = query.value("dissolved_protein").toDouble();
    newf.max_in_batch = query.value("max_in_batch").toDouble();
    newf.graintype = query.value("graintype").toInt();
    newf.recommend_mash = query.value("recommend_mash").toInt() ? true:false;
    newf.add_after_boil = true;
    newf.di_ph = query.value("di_ph").toDouble();
    newf.acid_to_ph_57 = query.value("acid_to_ph_57").toDouble();
    newf.inventory = query.value("inventory").toDouble();
    newf.adjust_to_total_100 = false;
    newf.percentage = 0;
    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