Sat, 11 Feb 2023 15:48:02 +0100
Monitor iSpindels: use global variables instead of repeated expensive MySQL calls. Use the yeast temperature ranges for the colors on the thermometer scale. Don't show SVG and ABV if the OG is not yet known. Turn statusfield red if offline. Extra mon_ispindles fields yeast_lo and yeast_hi. Need at least bmsd version 0.3.42. If a websocket message is received that cannot be parsed, show the whole received message.
/** * EditProduct.cpp is part of bmsapp. * * tab 6, yeasts. * * 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 yeaste 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/>. */ bool EditProduct::yeast_sort_test(const Yeasts &D1, const Yeasts &D2) { if (D1.use > D2.use) return false; if (D1.use < D2.use) return true; return (D1.amount > D2.amount); } bool EditProduct::block_yeast(int stage, int use) { if (stage > PROD_STAGE_PRIMARY && use < YEAST_USE_SECONDARY) return true; if (stage > PROD_STAGE_SECONDARY && use < YEAST_USE_TERTIARY) return true; if (stage > PROD_STAGE_TERTIARY && use < YEAST_USE_BOTTLE) return true; if (stage > PROD_STAGE_PACKAGE) return true; return false; } void EditProduct::refreshYeasts() { QString w; QWidget* pWidget; QHBoxLayout* pLayout; QTableWidgetItem *item; std::sort(product->yeasts.begin(), product->yeasts.end(), yeast_sort_test); const QStringList labels({tr("Yeast"), tr("Laboratory"), tr("Code"), tr("Type"), tr("Use for"), tr("Min."), tr("Max."), tr("Tol."), tr("Attn."), tr("STA"), tr("Amount"), tr("Stock"), tr("Delete"), tr("Edit") }); ui->yeastsTable->setColumnCount(14); ui->yeastsTable->setColumnWidth(0, 200); /* Yeast */ ui->yeastsTable->setColumnWidth(1, 115); /* Laboratory */ ui->yeastsTable->setColumnWidth(2, 80); /* Code */ ui->yeastsTable->setColumnWidth(3, 75); /* Type */ ui->yeastsTable->setColumnWidth(4, 75); /* Usage */ ui->yeastsTable->setColumnWidth(5, 50); /* Min. */ ui->yeastsTable->setColumnWidth(6, 50); /* Max. */ ui->yeastsTable->setColumnWidth(7, 50); /* Tolerance */ ui->yeastsTable->setColumnWidth(8, 50); /* Attenuation */ ui->yeastsTable->setColumnWidth(9, 40); /* STA1 gen */ ui->yeastsTable->setColumnWidth(10, 80); /* Amount */ ui->yeastsTable->setColumnWidth(11, 80); /* Stock */ ui->yeastsTable->setColumnWidth(12, 80); /* Delete */ ui->yeastsTable->setColumnWidth(13, 80); /* Edit */ ui->yeastsTable->setHorizontalHeaderLabels(labels); ui->yeastsTable->verticalHeader()->hide(); ui->yeastsTable->setRowCount(product->yeasts.size()); for (int i = 0; i < product->yeasts.size(); i++) { ui->yeastsTable->setItem(i, 0, new QTableWidgetItem(product->yeasts.at(i).name)); ui->yeastsTable->setItem(i, 1, new QTableWidgetItem(product->yeasts.at(i).laboratory)); ui->yeastsTable->setItem(i, 2, new QTableWidgetItem(product->yeasts.at(i).product_id)); item = new QTableWidgetItem(QCoreApplication::translate("YeastForm", g_yeast_forms[product->yeasts.at(i).form])); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); ui->yeastsTable->setItem(i, 3, item); item = new QTableWidgetItem(QCoreApplication::translate("YeastUse", g_yeast_use[product->yeasts.at(i).use])); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); ui->yeastsTable->setItem(i, 4, item); item = new QTableWidgetItem(QString("%1°C").arg(product->yeasts.at(i).min_temperature, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(i, 5, item); item = new QTableWidgetItem(QString("%1°C").arg(product->yeasts.at(i).max_temperature, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(i, 6, item); item = new QTableWidgetItem(QString("%1%").arg(product->yeasts.at(i).tolerance, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(i, 7, item); item = new QTableWidgetItem(QString("%1%").arg(product->yeasts.at(i).attenuation, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(i, 8, item); if (product->yeasts.at(i).use != YEAST_USE_BOTTLE && product->yeasts.at(i).sta1) { QWidget *pWidget = new QWidget(); QLabel *label = new QLabel; label->setPixmap(QPixmap(":icons/silk/tick.png")); QHBoxLayout *pLayout = new QHBoxLayout(pWidget); pLayout->addWidget(label); pLayout->setAlignment(Qt::AlignCenter); pLayout->setContentsMargins(0, 0, 0, 0); pWidget->setLayout(pLayout); ui->yeastsTable->setCellWidget(i, 9, pWidget); } else { ui->yeastsTable->removeCellWidget(i, 9); } if (product->yeasts.at(i).form == YEAST_FORMS_LIQUID) item = new QTableWidgetItem(QString("%1 pack").arg(product->yeasts.at(i).amount, 1, 'f', 0, '0')); else if (product->yeasts.at(i).form == YEAST_FORMS_DRY || product->yeasts.at(i).form == YEAST_FORMS_DRIED) item = new QTableWidgetItem(QString("%1 gr").arg(product->yeasts.at(i).amount * 1000.0, 3, 'f', 2, '0')); else item = new QTableWidgetItem(QString("%1 ml").arg(product->yeasts.at(i).amount * 1000.0, 3, 'f', 2, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(i, 10, item); if (block_yeast(product->stage, product->yeasts.at(i).use)) { item = new QTableWidgetItem(QString("")); } else { if (product->yeasts.at(i).form == YEAST_FORMS_LIQUID) item = new QTableWidgetItem(QString("%1 pack").arg(product->yeasts.at(i).inventory, 1, 'f', 0, '0')); else if (product->yeasts.at(i).form == YEAST_FORMS_DRY || product->yeasts.at(i).form == YEAST_FORMS_DRIED) item = new QTableWidgetItem(QString("%1 gr").arg(product->yeasts.at(i).inventory * 1000.0, 3, 'f', 2, '0')); else item = new QTableWidgetItem(QString("%1 ml").arg(product->yeasts.at(i).inventory * 1000.0, 3, 'f', 2, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); if (product->yeasts.at(i).inventory < product->yeasts.at(i).amount) item->setForeground(QBrush(QColor(Qt::red))); } ui->yeastsTable->setItem(i, 11, item); if (block_yeast(product->stage, product->yeasts.at(i).use)) { ui->yeastsTable->removeCellWidget(i, 12); /* to remove the unneeded button */ item = new QTableWidgetItem(""); item->setToolTip(tr("Yeast already used")); ui->yeastsTable->setItem(i, 12, item); ui->yeastsTable->removeCellWidget(i, 13); item = new QTableWidgetItem(""); item->setToolTip(tr("Yeast already used")); ui->yeastsTable->setItem(i, 13, item); } else { pWidget = new QWidget(); QPushButton* btn_dele = new QPushButton(); btn_dele->setObjectName(QString("%1").arg(i)); /* Send row with the button */ btn_dele->setText(tr("Delete")); connect(btn_dele, SIGNAL(clicked()), this, SLOT(deleteYeastRow_clicked())); pLayout = new QHBoxLayout(pWidget); pLayout->addWidget(btn_dele); pLayout->setContentsMargins(5, 0, 5, 0); pWidget->setLayout(pLayout); ui->yeastsTable->setCellWidget(i, 12, pWidget); pWidget = new QWidget(); QPushButton* btn_edit = new QPushButton(); btn_edit->setObjectName(QString("%1").arg(i)); /* Send row with the button */ btn_edit->setText(tr("Edit")); connect(btn_edit, SIGNAL(clicked()), this, SLOT(editYeastRow_clicked())); pLayout = new QHBoxLayout(pWidget); pLayout->addWidget(btn_edit); pLayout->setContentsMargins(5, 0, 5, 0); pWidget->setLayout(pLayout); ui->yeastsTable->setCellWidget(i, 13, pWidget); } } } void EditProduct::initYeast() { ui->est_og4Edit->setValue(product->est_og); ui->est_fg3Edit->setValue(product->est_fg); ui->est_abv2Edit->setValue(product->est_abv); ui->productionEdit->setDate(product->yeast_prod_date); ui->conditionShow->setValue(product->starter_viability); ui->startersgEdit->setValue(product->starter_sg); ui->pitchrateEdit->setValue(product->yeast_pitchrate); ui->yeastsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); for (int i = 0; i < 3; i++) { ui->stmethodEdit->addItem(QCoreApplication::translate("YeastStarter", g_yeast_starter[i])); } ui->stmethodEdit->setCurrentIndex(product->starter_type); } /* * Calculate the needed yeast for this batch. */ void EditProduct::calcYeast() { double sg = product->brew_fermenter_sg; double use_cells; double needed = 0; double initcells = 0; bool maybe_starter = false; qDebug() << "calcYeast()"; ui->yeastProcedure->setCurrentIndex(0); if (sg <= 1.0001 && product->fg > 1.000) sg = product->fg; else if (sg <= 1.0001) sg = product->est_og; double plato = Utils::sg_to_plato(sg); double volume = product->brew_fermenter_volume; if (volume > 0) { if (product->brew_fermenter_extrawater > 0) volume += product->brew_fermenter_extrawater; } else { volume = product->batch_size - product->eq_trub_loss; } if (product->yeasts.size() == 0) return; // No yeast in product. calcViability(); double dry_amount = 0; for (int i = 0; i < product->yeasts.size(); i++) { if (product->yeasts.at(i).use == YEAST_USE_PRIMARY && product->yeasts.at(i).form == YEAST_FORMS_DRY) { dry_amount += product->yeasts.at(i).amount; } } for (int i = 0; i < product->yeasts.size(); i++) { if (product->yeasts.at(i).use == YEAST_USE_PRIMARY) { // Primary if (product->yeasts.at(i).form == YEAST_FORMS_DRY) { /* * Dry yeast, build the formule with the yeast parameters. * Based on https://www.lallemandbrewing.com/en/canada/brewers-corner/brewing-tools/pitching-rate-calculator/ */ ui->yeastProcedure->setCurrentIndex(2); ui->lo_gr_hlEdit->setValue(product->yeasts.at(i).gr_hl_lo); ui->hi_gr_hlEdit->setValue(product->yeasts.at(i).gr_hl_hi); ui->lo_sgEdit->setValue(product->yeasts.at(i).sg_lo); ui->hi_sgEdit->setValue(product->yeasts.at(i).sg_hi); double og = product->yeasts.at(i).sg_lo; double f1 = product->yeasts.at(i).gr_hl_lo / 100.0; double f2 = round(f1 / 5 * 1000000.0) / 1000000.0; double multiplier = (sg <= og) ? f1 : (f1 + f2 * (sg - og) / 0.008); qDebug() << " sg:" << sg << "og:" << og << "f1:" << f1 << "f2:" << f2 << "multiplier:" << multiplier; double yeast_grams = round(volume * multiplier * 100.0) / product->starter_viability; double yeast_gr_hl = round((yeast_grams / (volume * 0.01)) * 100.0) / 100.0; double pitch_gr_hl = round(((dry_amount * 1000.0) / (volume * 0.01)) * 100.0) / 100.0; ui->dry_needShow->setValue(yeast_grams); ui->dry_pitchrateShow->setValue(yeast_gr_hl); ui->pitch_grShow->setValue(pitch_gr_hl); ui->pitch_grShow->setStyleSheet((pitch_gr_hl < yeast_gr_hl) ? "background-color: red":""); #ifdef DEBUG_YEAST qDebug() << " Need" << yeast_grams << "grams, gr/hl:" << yeast_gr_hl << "pitch:" << pitch_gr_hl; #endif calcBU(); return; } else { /* * Liquid, slant, culture etc. * pitchrate see https://www.brewersfriend.com/yeast-pitch-rate-and-starter-calculator/ * and http://braukaiser.com/blog/blog/2012/11/03/estimating-yeast-growth/ */ ui->yeastProcedure->setCurrentIndex(1); if (product->yeast_pitchrate == 0) { /* * No pitchrate yet, do a educated guess .. */ if (product->yeasts.at(i).type == YEAST_TYPES_LAGER) { product->yeast_pitchrate = 1.5; if (sg > 1.060) product->yeast_pitchrate = 2.0; } else if (product->yeasts.at(i).type == YEAST_TYPES_KVEIK) { // Real Kveik product->yeast_pitchrate = 0.075; } else { product->yeast_pitchrate = 0.75; if (sg > 1.060) product->yeast_pitchrate = 1.0; } is_changed(); ui->pitchrateEdit->setValue(product->yeast_pitchrate); #ifdef DEBUG_YEAST qDebug() << " Guessed pitchrate" << product->yeast_pitchrate; #endif } double cells = product->yeasts.at(i).cells; if (product->yeasts.at(i).yp_cells > 0) { // Use from yeastpack if set cells = product->yeasts.at(i).yp_cells; } initcells = (cells / 1000000) * product->yeasts.at(i).amount * (product->starter_viability / 100.0); if (product->yeasts.at(i).form == YEAST_FORMS_LIQUID) initcells = (cells / 1000000000) * product->yeasts.at(i).amount * (product->starter_viability / 100.0); needed = round(product->yeast_pitchrate * volume * plato * 10.0) / 10.0; ui->neededShow->setValue(needed); if ((0.9 * needed) > initcells) { // Allow 90% underpitch without a starter maybe_starter = true; } #ifdef DEBUG_YEAST qDebug() << " Pitchrate:" << product->yeast_pitchrate << "needed:" << 0.9 * needed << "/" << needed << "initcells:" << initcells << "starter" << maybe_starter; #endif } break; } } if (maybe_starter != product->starter_enable) { if (product->starter_enable && !maybe_starter) { #ifdef DEBUG_YEAST qDebug() << " Clear obsolete starter"; #endif for (int i = 0; i < 4; i++) { product->prop_volume[i] = 0; product->prop_type[i] = 0; } } product->starter_enable = maybe_starter; #ifdef DEBUG_YEAST qDebug() << " Set starter enable" << maybe_starter; #endif is_changed(); } if (product->starter_enable) { #ifdef DEBUG_YEAST qDebug() << " Starter calculate.."; #endif const QStringList labels({tr("Method"), tr("Volume"), tr("Inj. factor"), tr("New cells"), tr("Total cells"), tr("Grow factor"), "" }); ui->starterTable->show(); ui->restartLabel->show(); ui->restartButton->show(); ui->starterTable->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->starterTable->clear(); ui->starterTable->setColumnCount(7); ui->starterTable->setRowCount(0); ui->starterTable->setColumnWidth(0, 130); /* Method */ ui->starterTable->setColumnWidth(1, 90); /* Volume */ ui->starterTable->setColumnWidth(2, 90); /* Inj. factor */ ui->starterTable->setColumnWidth(3, 90); /* New cells */ ui->starterTable->setColumnWidth(4, 90); /* Total cells */ ui->starterTable->setColumnWidth(5, 90); /* Grow factor */ ui->starterTable->setColumnWidth(6, 30); /* Edit button */ ui->starterTable->setHorizontalHeaderLabels(labels); calcSteps(product->starter_type, initcells, needed); } else { ui->starterTable->hide(); ui->restartLabel->hide(); ui->restartButton->hide(); } calcBU(); } /* * http://braukaiser.com/blog/blog/2012/11/03/estimating-yeast-growth/ * * stype: 0=stirred, 1=shaken, 2=simple * totcells: initial cells * egrams: gram extract */ double EditProduct::getGrowthRate(int stype, double totcells, double egrams) { /* Cells per grams extract (B/g) */ double cpe = totcells / egrams; if (cpe > 3.5) return 0; // no growth if (stype == STARTERS_SIMPLE) return 0.4; // simple starter if (stype == STARTERS_SHAKEN) return 0.62; // shaken starter if (cpe <= 1.4) // stirred starter return 1.4; return 2.33 - (.67 * cpe); }; StepResult EditProduct::calcStep(double svol, int stype, double start) { StepResult res; double gperpoint = 2.72715; //number of grams of extract per point of starter gravity per liter double irate = round(start / svol * 10000.0) / 10.0; double egrams = (product->starter_sg - 1) * svol * gperpoint; double grate = getGrowthRate(stype, start, egrams); double ncells = round(egrams * grate * 10.0) / 10.0; double totcells = ncells + start; res.svol = svol; res.irate = irate; res.ncells = ncells; res.totcells = totcells; res.growf = round((ncells / start) * 100.0) / 100.0; #ifdef DEBUG_YEAST qDebug() << " calcStep(" << svol << "," << stype << "," << start << ") irate" << irate << "ncells" << res.ncells << "totcells" << res.totcells << "growf" << res.growf; #endif return res; } /* * Calculate all starter steps. * stype: final starter type: 0 = stirred, 1 = shaked, 2 = simple. * start: initial cells in billions * needed: needed cells in billions * * result: all values updated. */ void EditProduct::calcSteps(int stype, double start, double needed) { int i, step, svol; int lasti = 0; /* Erlenmeyer sizes */ const int uvols[] { 20, 40, 60, 80, 100, 150, 200, 250, 375, 500, 625, 750, 875, 1000, 1250, 1500, 2000, 2500, 3000, 4000, 5000 }; int mvols = sizeof(uvols); StepResult result; QTableWidgetItem *item; QWidget* pWidget; QHBoxLayout* pLayout; double tcells = start; QIcon iconT; iconT.addFile(QString::fromUtf8(":/icons/silk/pencil.png"), QSize(), QIcon::Normal, QIcon::Off); if ((product->prop_volume[0] + product->prop_volume[1] + product->prop_volume[2] + product->prop_volume[3]) == 0) { /* * Auto calculate the starter. */ #ifdef DEBUG_YEAST qDebug() << " calcSteps() auto"; #endif if (start > needed) return; for (step = 1; step < 5; step++) { for (i = lasti; i <= mvols; i++) { lasti = i; svol = uvols[lasti]; result = calcStep(svol, stype, tcells); if (result.irate < 25) { // inocculation rate too low, backup one step and break out. lasti = i - 1; svol = uvols[lasti]; result = calcStep(svol, stype, tcells); break; } if (result.totcells > needed || i == mvols) { // hit the target or loops done break; } } ui->starterTable->setRowCount(step); item = new QTableWidgetItem(QCoreApplication::translate("YeastStarter", g_yeast_starter[stype])); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); ui->starterTable->setItem(step -1, 0, item); item = new QTableWidgetItem(QString("%1").arg(result.svol / 1000.0, 4, 'f', 3, '0')); // To liters item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->starterTable->setItem(step -1, 1, item); item = new QTableWidgetItem(QString("%1").arg(result.irate, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); if ((result.irate < 25) || (result.irate > 100)) item->setForeground(QBrush(QColor(Qt::red))); ui->starterTable->setItem(step -1, 2, item); item = new QTableWidgetItem(QString("%1").arg(result.ncells, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->starterTable->setItem(step -1, 3, item); item = new QTableWidgetItem(QString("%1").arg(result.totcells, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); item->setForeground(QBrush(QColor((result.totcells > needed) ? Qt::green:Qt::red))); ui->starterTable->setItem(step -1, 4, item); item = new QTableWidgetItem(QString("%1").arg(result.growf, 3, 'f', 2, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); if (result.growf < 1) item->setForeground(QBrush(QColor(Qt::red))); if ((stype > 0) && (result.growf > 3)) item->setForeground(QBrush(QColor(Qt::red))); ui->starterTable->setItem(step -1, 5, item); pWidget = new QWidget(); QToolButton* btn_edit = new QToolButton(); btn_edit->setObjectName(QString("%1").arg(step - 1)); /* Send row with the button */ btn_edit->setIcon(iconT); connect(btn_edit, SIGNAL(clicked()), this, SLOT(yeast_starter_edit_clicked())); pLayout = new QHBoxLayout(pWidget); pLayout->addWidget(btn_edit); pLayout->setContentsMargins(5, 0, 5, 0); pWidget->setLayout(pLayout); ui->starterTable->setCellWidget(step -1, 6, pWidget); product->prop_type[step -1] = product->starter_type; product->prop_volume[step -1] = result.svol / 1000.0; #ifdef DEBUG_YEAST qDebug() << " step" << step << "type" << product->prop_type[step -1] << "vol" << product->prop_volume[step -1]; #endif tcells = result.totcells; if (result.totcells > needed) // Hit the target return; } } else { /* * Recalculate the starter. */ #ifdef DEBUG_YEAST qDebug() << " calcSteps() recalculate"; #endif for (step = 0; step < 4; step++) { if (product->prop_volume[step] > 0) { result = calcStep(product->prop_volume[step] * 1000, product->prop_type[step], tcells); ui->starterTable->setRowCount(step + 1); item = new QTableWidgetItem(QCoreApplication::translate("YeastStarter", g_yeast_starter[product->prop_type[step]])); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); ui->starterTable->setItem(step, 0, item); item = new QTableWidgetItem(QString("%1").arg(result.svol / 1000.0, 4, 'f', 3, '0')); // To liters item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->starterTable->setItem(step, 1, item); item = new QTableWidgetItem(QString("%1").arg(result.irate, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); if ((result.irate < 25) || (result.irate > 100)) item->setForeground(QBrush(QColor(Qt::red))); ui->starterTable->setItem(step, 2, item); item = new QTableWidgetItem(QString("%1").arg(result.ncells, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->starterTable->setItem(step, 3, item); item = new QTableWidgetItem(QString("%1").arg(result.totcells, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); item->setForeground(QBrush(QColor((result.totcells > needed) ? Qt::green:Qt::red))); ui->starterTable->setItem(step, 4, item); item = new QTableWidgetItem(QString("%1").arg(result.growf, 3, 'f', 2, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); if (result.growf < 1) item->setForeground(QBrush(QColor(Qt::red))); if ((stype > 0) && (result.growf > 3)) item->setForeground(QBrush(QColor(Qt::red))); ui->starterTable->setItem(step, 5, item); pWidget = new QWidget(); QToolButton* btn_edit = new QToolButton(); btn_edit->setObjectName(QString("%1").arg(step)); /* Send row with the button */ btn_edit->setIcon(iconT); connect(btn_edit, SIGNAL(clicked()), this, SLOT(yeast_starter_edit_clicked())); pLayout = new QHBoxLayout(pWidget); pLayout->addWidget(btn_edit); pLayout->setContentsMargins(5, 0, 5, 0); pWidget->setLayout(pLayout); ui->starterTable->setCellWidget(step, 6, pWidget); tcells = result.totcells; if (result.totcells > needed) { // Hit the target for (int i = step + 1; i < 4; i++) { product->prop_volume[i] = 0; product->prop_type[i] = 0; } return; } else if ((step < 3) && (product->prop_volume[step + 1] == 0)) { // Extra step needed, start with the same size. product->prop_volume[step + 1] = product->prop_volume[step]; product->prop_type[step + 1] = product->prop_type[step]; } #ifdef DEBUG_YEAST qDebug() << " step" << step << "type" << product->prop_type[step] << "vol" << product->prop_volume[step]; #endif } } } } void EditProduct::calcViability() { double vpm = 1.00; double max = 100; for (int i = 0; i < product->yeasts.size(); i++) { if (product->yeasts.at(i).use == YEAST_USE_PRIMARY) { if (! product->yeasts.at(i).yp_uuid.isNull() && (product->yeasts.at(i).yp_uuid.length() == 36)) { vpm = product->yeasts.at(i).yp_viability; max = product->yeasts.at(i).yp_max; } else if (product->yeasts.at(i).form == YEAST_FORMS_LIQUID) { // Fallback to hardcoded values. vpm = 0.80; max = 97; if (product->yeasts.at(i).laboratory == "White Labs") { // PurePitch vpm = 0.9648; // Purepitch max = 100; } } else if (product->yeasts.at(i).form == YEAST_FORMS_DRY) { vpm = 0.998; max = 100; } else if (product->yeasts.at(i).form == YEAST_FORMS_DRIED) { // dried kveik vpm = 0.92; max = 100; } else { // Slant, Culture, Frozen, Bottle vpm = 0.99; max = 97; } } } #ifdef DEBUG_YEAST qDebug() << "calcViability vpm:" << vpm << "max:" << max; #endif double base = max; /* * Calculate time days before today. If the date is cleared, * the result is 0 days. Dates in the future are ignored. */ int timeDiff = product->yeast_prod_date.daysTo(QDate::currentDate()); if (timeDiff < 0) timeDiff == 0; double degrade = 1 - ((1 - vpm) / 30.41); // viability degradation per day. for (int i = 0; i < timeDiff; i++) { base = base * degrade; } if (base > max) base = max; product->starter_viability = round(base * 10) / 10; ui->conditionShow->setValue(product->starter_viability); #ifdef DEBUG_YEAST qDebug() << " Age" << timeDiff << "degrade" << degrade << "viability" << product->starter_viability; #endif } void EditProduct::yeast_prod_date_changed(QDate val) { product->yeast_prod_date = ui->productionEdit->nullDate(); calcViability(); calcYeast(); is_changed(); } void EditProduct::yeast_prod_date_clear() { product->yeast_prod_date = QDate(); ui->productionEdit->setDate(QDate()); } void EditProduct::yeast_prod_date_today() { product->yeast_prod_date = QDate::currentDate(); ui->productionEdit->setDate(QDate::currentDate()); } void EditProduct::yeast_method_changed(int val) { #ifdef DEBUG_YEAST qDebug() << "yeast_method_changed" << val; #endif product->starter_type = val; calcYeast(); is_changed(); } void EditProduct::yeast_starter_sg_changed(double val) { #ifdef DEBUG_YEAST qDebug() << "yeast_starter_sg_changed" << val; #endif product->starter_sg = val; calcYeast(); is_changed(); } void EditProduct::pitchindex_changed(int val) { #ifdef DEBUG_YEAST qDebug() << "pitchindex_changed" << val; #endif product->pitch_pitchindex = val; } void EditProduct::pitchrate_changed(double val) { #ifdef DEBUG_YEAST qDebug() << "pitchrate_changed" << val; #endif product->yeast_pitchrate = val; calcYeast(); is_changed(); } void EditProduct::yeast_pitchrate_button_clicked() { #ifdef DEBUG_YEAST qDebug() << "yeast_pitchrate_button_clicked"; #endif product->pitch_pitchindex = 1; QDialog* dialog = new QDialog(this); dialog->resize(420, 110); dialog->setWindowTitle(tr("BMSapp - Pitchrate")); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); buttonBox->setObjectName(QString::fromUtf8("buttonBox")); buttonBox->setGeometry(QRect(30, 60, 360, 32)); buttonBox->setLayoutDirection(Qt::LeftToRight); buttonBox->setOrientation(Qt::Horizontal); buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); buttonBox->setCenterButtons(true); QLabel *typeLabel = new QLabel(dialog); typeLabel->setObjectName(QString::fromUtf8("typeLabel")); typeLabel->setText(tr("Beer pitch type:")); typeLabel->setGeometry(QRect(10, 20, 195, 20)); typeLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QComboBox *typeEdit = new QComboBox(dialog); typeEdit->setObjectName(QString::fromUtf8("typeEdit")); typeEdit->setGeometry(QRect(215, 20, 185, 23)); typeEdit->setIconSize(QSize(0, 0)); typeEdit->addItem(tr("0.075 Real Kveik")); typeEdit->addItem(tr("0.75 Ale, upto 1.060")); typeEdit->addItem(tr("1.0 Ale, above 1.060")); typeEdit->addItem(tr("1.5 Lager, upto 1.060")); typeEdit->addItem(tr("2.0 Lager, above 1.060")); typeEdit->setCurrentIndex(product->pitch_pitchindex); connect(typeEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::pitchindex_changed); connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); dialog->setModal(true); dialog->exec(); if (dialog->result() != QDialog::Rejected) { switch (product->pitch_pitchindex) { case 0: product->yeast_pitchrate = 0.075; break; case 1: product->yeast_pitchrate = 0.75; break; case 2: product->yeast_pitchrate = 1.0; break; case 3: product->yeast_pitchrate = 1.5; break; case 4: product->yeast_pitchrate = 2.0; break; } ui->pitchrateEdit->setValue(product->yeast_pitchrate); /* Will automatic call calcYeast() and is_changed() */ } disconnect(typeEdit, nullptr, nullptr, nullptr); disconnect(buttonBox, nullptr, nullptr, nullptr); } void EditProduct::yeast_retry_button_clicked() { #ifdef DEBUG_YEAST qDebug() << "yeast_retry_button_clicked"; #endif int rc = QMessageBox::warning(this, tr("Retry starter"), tr("Retry to automatic create starter steps"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (rc == QMessageBox::No) return; for (int i = 0; i < 4; i++) { product->prop_volume[i] = 0; product->prop_type[i] = product->starter_type; } calcYeast(); is_changed(); } void EditProduct::yeast_starter_edit_clicked() { QToolButton *pb = qobject_cast<QToolButton *>(QObject::sender()); int row = pb->objectName().toInt(); #ifdef DEBUG_YEAST qDebug() << "yeast_starter_edit_clicked" << row; #endif QDialog* dialog = new QDialog(this); dialog->resize(338, 140); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); buttonBox->setObjectName(QString::fromUtf8("buttonBox")); buttonBox->setGeometry(QRect(30, 90, 271, 32)); buttonBox->setLayoutDirection(Qt::LeftToRight); buttonBox->setOrientation(Qt::Horizontal); buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); buttonBox->setCenterButtons(true); QLabel *typeLabel = new QLabel(dialog); typeLabel->setObjectName(QString::fromUtf8("typeLabel")); typeLabel->setText(tr("Start step type:")); typeLabel->setGeometry(QRect(10, 10, 141, 20)); typeLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QLabel *volLabel = new QLabel(dialog); volLabel->setObjectName(QString::fromUtf8("volLabel")); volLabel->setText(tr("Starter step volume:")); volLabel->setGeometry(QRect(10, 40, 141, 20)); volLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QComboBox *typeEdit = new QComboBox(dialog); typeEdit->setObjectName(QString::fromUtf8("typeEdit")); typeEdit->setGeometry(QRect(160, 10, 121, 23)); typeEdit->addItem(tr("Stirred")); typeEdit->addItem(tr("Shaken")); typeEdit->addItem(tr("Simple")); typeEdit->setCurrentIndex(product->prop_type[row]); QDoubleSpinBox *volEdit = new QDoubleSpinBox(dialog); volEdit->setObjectName(QString::fromUtf8("volEdit")); volEdit->setGeometry(QRect(160, 40, 121, 24)); volEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); volEdit->setAccelerated(true); volEdit->setDecimals(3); volEdit->setSingleStep(0.01); volEdit->setValue(product->prop_volume[row]); volEdit->setMaximum(5); connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); dialog->setModal(true); dialog->exec(); if (dialog->result() != QDialog::Rejected) { product->prop_type[row] = typeEdit->currentIndex(); product->prop_volume[row] = volEdit->value(); calcYeast(); is_changed(); } disconnect(buttonBox, nullptr, nullptr, nullptr); } void EditProduct::addYeastRow_clicked() { Yeasts newy; #ifdef DEBUG_YEAST qDebug() << "Add yeast row"; #endif for (int i = 0; i < product->yeasts.size(); i++) { if (product->yeasts.at(i).amount == 0) return; // Add only one at a time. } newy.name = "Select one"; newy.laboratory = ""; newy.product_id = ""; newy.amount = 0; newy.type = YEAST_TYPES_ALE; newy.form = YEAST_FORMS_LIQUID; newy.min_temperature = 0; newy.max_temperature = 0; newy.flocculation = 0; newy.attenuation = 0; newy.cells = 0; newy.tolerance = 0; newy.inventory = 0; newy.use = YEAST_USE_PRIMARY; newy.sta1 = false; newy.bacteria = false; newy.harvest_top = false; newy.harvest_time = 0; newy.pitch_temperature = 0; newy.pofpos = false; newy.zymocide = 0; newy.gr_hl_lo = 0; newy.sg_lo = 0; newy.gr_hl_hi = 0; newy.sg_hi = 0; newy.cost = 0; product->yeasts.append(newy); emit refreshAll(); } void EditProduct::deleteYeastRow_clicked() { if (product->locked || product->yeasts.size() < 1) return; QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender()); int row = pb->objectName().toInt(); #ifdef DEBUG_YEAST qDebug() << "Delete yeast row" << row << product->yeasts.size(); #endif int rc = QMessageBox::warning(this, tr("Delete yeast"), tr("Delete %1").arg(product->yeasts.at(row).name), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (rc == QMessageBox::No) return; product->yeasts.removeAt(row); bool primary = false; /* Check if any primary yeast is left */ for (int i = 0; i < product->yeasts.size(); i++) { if (product->yeasts.at(i).use == YEAST_USE_PRIMARY) primary = true; } if (! primary) { #ifdef DEBUG_YEAST qDebug() << " Clear starter (if any)"; #endif for (int i = 0; i < 4; i++) product->prop_volume[i] = 0; } is_changed(); emit refreshAll(); } void EditProduct::yeast_amount_changed(double val) { QTableWidgetItem *item; #ifdef DEBUG_YEAST qDebug() << "yeast_amount_changed()" << product->yeasts_row << val; #endif if (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_LIQUID) { product->yeasts[product->yeasts_row].amount = val; item = new QTableWidgetItem(QString("%1 pack").arg(val, 1, 'f', 0, '0')); } else if ((product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRY) || (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRIED)) { product->yeasts[product->yeasts_row].amount = val / 1000.0; item = new QTableWidgetItem(QString("%1 gr").arg(val, 3, 'f', 2, '0')); } else { product->yeasts[product->yeasts_row].amount = val / 1000.0; item = new QTableWidgetItem(QString("%1 ml").arg(val, 3, 'f', 2, '0')); } item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(product->yeasts_row, 10, item); calcYeast(); is_changed(); } void EditProduct::yeast_load_packages(QString laboratory, int form) { QSqlQuery query; /* * Clear package and rebuild package select table. */ this->ypackageEdit->setCurrentIndex(-1); this->ypackageEdit->clear(); this->ypackageEdit->addItem(""); // Start with empty value query.prepare("SELECT * FROM inventory_yeastpack WHERE laboratory=:laboratory AND form=:form AND valid='1' ORDER BY laboratory,package"); query.bindValue(":laboratory", laboratory); query.bindValue(":form", form); // qDebug() << " search" << laboratory << form; query.exec(); while (query.next()) { this->ypackageEdit->addItem(query.value("laboratory").toString()+" - "+query.value("package").toString()); // qDebug() << " add package" << query.value("laboratory").toString() << query.value("package").toString(); } } void EditProduct::yeast_package_changed(int val) { QSqlQuery query; int index = 0; #ifdef DEBUG_YEAST qDebug() << "yeast_package_changed()" << product->yeasts_row << val; #endif /* * Scan the packages for result number 'val' */ query.prepare("SELECT * FROM inventory_yeastpack WHERE laboratory=:laboratory AND form=:form AND valid='1' ORDER BY laboratory,package"); query.bindValue(":laboratory", product->yeasts.at(product->yeasts_row).laboratory); query.bindValue(":form", product->yeasts.at(product->yeasts_row).form); // qDebug() << " search" << product->yeasts.at(product->yeasts_row).laboratory << product->yeasts.at(product->yeasts_row).form; query.exec(); while (query.next()) { index++; if (index == val) { // qDebug() << " result" << index << query.value("package").toString(); product->yeasts[product->yeasts_row].yp_uuid = query.value("uuid").toString(); product->yeasts[product->yeasts_row].yp_package = query.value("package").toString(); product->yeasts[product->yeasts_row].yp_cells = query.value("cells").toDouble(); product->yeasts[product->yeasts_row].yp_viability = query.value("viability").toDouble(); product->yeasts[product->yeasts_row].yp_max = query.value("max").toInt(); product->yeasts[product->yeasts_row].yp_size = query.value("size").toDouble(); is_changed(); return; } } if (! product->yeasts[product->yeasts_row].yp_uuid.isNull()) { /* * Clear package */ product->yeasts[product->yeasts_row].yp_uuid = QString(); product->yeasts[product->yeasts_row].yp_package = QString(); product->yeasts[product->yeasts_row].yp_cells = product->yeasts[product->yeasts_row].cells; product->yeasts[product->yeasts_row].yp_viability = 0.99; product->yeasts[product->yeasts_row].yp_max = 100; product->yeasts[product->yeasts_row].yp_size = 0.01; is_changed(); // qDebug() << " cleared old yp_xxx data"; } // qDebug() << " result not found"; } void EditProduct::yeast_select_changed(int val) { QSqlQuery query; bool instock = yinstockEdit->isChecked(); QString w; QTableWidgetItem *item; int oldform = product->yeasts.at(product->yeasts_row).form; if (val < 1) return; #ifdef DEBUG_YEAST qDebug() << "yeast_select_changed()" << product->yeasts_row << val << instock; #endif /* * Search the yeast pointed by the index and instock flag. */ QString sql = "SELECT name,laboratory,product_id,type,form,min_temperature,max_temperature,flocculation,attenuation," "cells,tolerance,sta1,bacteria,harvest_top,harvest_time,pitch_temperature,pofpos,zymocide," "gr_hl_lo,sg_lo,gr_hl_hi,sg_hi,cost,inventory FROM inventory_yeasts "; if (instock) sql.append("WHERE inventory > 0 "); sql.append("ORDER BY laboratory,product_id,name"); query.prepare(sql); query.exec(); query.first(); for (int i = 0; i < (val - 1); i++) { query.next(); } #ifdef DEBUG_YEAST qDebug() << "found" << query.value("name").toString() << query.value("product_id").toString(); #endif /* * Replace the yeast record contents */ product->yeasts[product->yeasts_row].name = query.value("name").toString(); product->yeasts[product->yeasts_row].laboratory = query.value("laboratory").toString(); product->yeasts[product->yeasts_row].product_id = query.value("product_id").toString(); product->yeasts[product->yeasts_row].type = query.value("type").toInt(); product->yeasts[product->yeasts_row].form = query.value("form").toInt(); product->yeasts[product->yeasts_row].min_temperature = query.value("min_temperature").toDouble(); product->yeasts[product->yeasts_row].max_temperature = query.value("max_temperature").toDouble(); product->yeasts[product->yeasts_row].flocculation = query.value("flocculation").toInt(); product->yeasts[product->yeasts_row].attenuation = query.value("attenuation").toDouble(); product->yeasts[product->yeasts_row].cells = query.value("cells").toDouble(); product->yeasts[product->yeasts_row].tolerance = query.value("tolerance").toDouble(); product->yeasts[product->yeasts_row].sta1 = query.value("sta1").toInt() ? true:false; product->yeasts[product->yeasts_row].bacteria = query.value("bacteria").toInt() ? true:false; product->yeasts[product->yeasts_row].harvest_top = query.value("harvest_top").toInt() ? true:false; product->yeasts[product->yeasts_row].harvest_time = query.value("harvest_time").toInt(); product->yeasts[product->yeasts_row].pitch_temperature = query.value("pitch_temperature").toDouble(); product->yeasts[product->yeasts_row].pofpos = query.value("pofpos").toInt() ? true:false; product->yeasts[product->yeasts_row].zymocide = query.value("zymocide").toInt(); product->yeasts[product->yeasts_row].gr_hl_lo = query.value("gr_hl_lo").toInt(); product->yeasts[product->yeasts_row].sg_lo = query.value("sg_lo").toDouble(); product->yeasts[product->yeasts_row].gr_hl_hi = query.value("gr_hl_hi").toInt(); product->yeasts[product->yeasts_row].sg_hi = query.value("sg_hi").toDouble(); product->yeasts[product->yeasts_row].cost = query.value("cost").toDouble(); product->yeasts[product->yeasts_row].inventory = query.value("inventory").toDouble(); product->yeasts[product->yeasts_row].yp_uuid = QString(); /* * Update the visible fields */ const QSignalBlocker blocker1(yamountEdit); ynameEdit->setText(product->yeasts.at(product->yeasts_row).name); ylaboratoryEdit->setText(product->yeasts.at(product->yeasts_row).laboratory); yproduct_idEdit->setText(product->yeasts.at(product->yeasts_row).product_id); if (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_LIQUID) { if (oldform != YEAST_FORMS_LIQUID) product->yeasts[product->yeasts_row].amount = 1; yamountEdit->setValue(product->yeasts[product->yeasts_row].amount); yamountEdit->setDecimals(0); yamountEdit->setSingleStep(1.0); yamountLabel->setText(tr("Total packs:")); product->yeast_pitchrate = 0; } else if ((product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRY) || (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRIED)) { if (oldform == YEAST_FORMS_LIQUID) product->yeasts[product->yeasts_row].amount = 0.01; yamountEdit->setValue(product->yeasts[product->yeasts_row].amount * 1000.0); yamountEdit->setDecimals(1); yamountEdit->setSingleStep(0.5); yamountLabel->setText(tr("Amount in gr:")); } else { if (oldform == YEAST_FORMS_LIQUID) product->yeasts[product->yeasts_row].amount = 0.01; yamountEdit->setValue(product->yeasts[product->yeasts_row].amount * 1000.0); yamountEdit->setDecimals(1); yamountEdit->setSingleStep(0.5); yamountLabel->setText(tr("Amount in ml:")); product->yeast_pitchrate = 0; } /* * Clear package and rebuild package select table. */ yeast_load_packages(query.value("laboratory").toString(), query.value("form").toInt()); ui->yeastsTable->setItem(product->yeasts_row, 0, new QTableWidgetItem(product->yeasts.at(product->yeasts_row).name)); ui->yeastsTable->setItem(product->yeasts_row, 1, new QTableWidgetItem(product->yeasts.at(product->yeasts_row).laboratory)); ui->yeastsTable->setItem(product->yeasts_row, 2, new QTableWidgetItem(product->yeasts.at(product->yeasts_row).product_id)); item = new QTableWidgetItem(QCoreApplication::translate("YeastForm", g_yeast_forms[product->yeasts.at(product->yeasts_row).form])); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); ui->yeastsTable->setItem(product->yeasts_row, 3, item); item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(product->yeasts_row).min_temperature, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(product->yeasts_row, 5, item); item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(product->yeasts_row).max_temperature, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(product->yeasts_row, 6, item); item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(product->yeasts_row).tolerance, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(product->yeasts_row, 7, item); item = new QTableWidgetItem(QString("%1").arg(product->yeasts.at(product->yeasts_row).attenuation, 2, 'f', 1, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(product->yeasts_row, 8, item); if (product->yeasts.at(product->yeasts_row).use != YEAST_USE_BOTTLE && product->yeasts.at(product->yeasts_row).sta1) { QWidget *pWidget = new QWidget(); QLabel *label = new QLabel; label->setPixmap(QPixmap(":icons/silk/tick.png")); QHBoxLayout *pLayout = new QHBoxLayout(pWidget); pLayout->addWidget(label); pLayout->setAlignment(Qt::AlignCenter); pLayout->setContentsMargins(0, 0, 0, 0); pWidget->setLayout(pLayout); ui->yeastsTable->setCellWidget(product->yeasts_row, 9, pWidget); } else { ui->yeastsTable->removeCellWidget(product->yeasts_row, 9); } if (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_LIQUID) item = new QTableWidgetItem(QString("%1 pack").arg(product->yeasts.at(product->yeasts_row).amount, 1, 'f', 0, '0')); else if (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRY || product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRIED) item = new QTableWidgetItem(QString("%1 gr").arg(product->yeasts.at(product->yeasts_row).amount * 1000.0, 3, 'f', 2, '0')); else item = new QTableWidgetItem(QString("%1 ml").arg(product->yeasts.at(product->yeasts_row).amount * 1000.0, 3, 'f', 2, '0')); item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter); ui->yeastsTable->setItem(product->yeasts_row, 10, item); /* * If there is no need for a starter, wipe it. */ bool maybe_starter = false; for (int i = 0; i < product->yeasts.size(); i++) { if (product->yeasts.at(i).use == YEAST_USE_PRIMARY && ! ((product->yeasts.at(i).form == YEAST_FORMS_DRY) || (product->yeasts.at(i).form == YEAST_FORMS_DRIED))) maybe_starter = true; } if (! maybe_starter) { #ifdef DEBUG_YEAST qDebug() << " Clear starter (if any)"; #endif for (int i = 0; i < 4; i++) product->prop_volume[i] = 0; } is_changed(); } void EditProduct::yeast_instock_changed(bool val) { QSqlQuery query; #ifdef DEBUG_YEAST qDebug() << "yeast_instock_changed()" << product->yeasts_row << val; #endif this->yselectEdit->setCurrentIndex(-1); this->yselectEdit->clear(); QString sql = "SELECT name,laboratory,product_id,inventory FROM inventory_yeasts "; if (val) sql.append("WHERE inventory > 0 "); sql.append("ORDER BY laboratory,product_id,name"); query.prepare(sql); query.exec(); query.first(); this->yselectEdit->addItem(""); // Start with empty value for (int i = 0; i < query.size(); i++) { this->yselectEdit->addItem(query.value(1).toString()+" - "+query.value(2).toString()+" "+query.value(0).toString() + QString(" (%1 gr)").arg(query.value(3).toDouble() * 1000.0, 2, 'f', 1, '0')); query.next(); } } void EditProduct::yeast_useat_changed(int val) { #ifdef DEBUG_YEAST qDebug() << "yeast_useat_changed()" << product->yeasts_row << val; #endif product->yeasts[product->yeasts_row].use = val; QTableWidgetItem *item = new QTableWidgetItem(QCoreApplication::translate("YeastUse", g_yeast_use[val])); item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter); ui->yeastsTable->setItem(product->yeasts_row, 4, item); is_changed(); } void EditProduct::editYeastRow_clicked() { QSqlQuery query; if (product->locked) return; QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender()); product->yeasts_row = pb->objectName().toInt(); #ifdef DEBUG_YEAST qDebug() << "Edit yeast row" << product->yeasts_row; #endif Yeasts backup = product->yeasts.at(product->yeasts_row); QDialog* dialog = new QDialog(this); dialog->resize(738, 290); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); buttonBox->setObjectName(QString::fromUtf8("buttonBox")); buttonBox->setGeometry(QRect(30, 240, 671, 32)); buttonBox->setLayoutDirection(Qt::LeftToRight); buttonBox->setOrientation(Qt::Horizontal); buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); buttonBox->setCenterButtons(true); QLabel *nameLabel = new QLabel(dialog); nameLabel->setObjectName(QString::fromUtf8("nameLabel")); nameLabel->setText(tr("Yeast name:")); nameLabel->setGeometry(QRect(10, 10, 141, 20)); nameLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QLabel *laboratoryLabel = new QLabel(dialog); laboratoryLabel->setObjectName(QString::fromUtf8("laboratoryLabel")); laboratoryLabel->setText(tr("Laboratory:")); laboratoryLabel->setGeometry(QRect(10, 40, 141, 20)); laboratoryLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QLabel *product_idLabel = new QLabel(dialog); product_idLabel->setObjectName(QString::fromUtf8("product_idLabel")); product_idLabel->setText(tr("Laboratory:")); product_idLabel->setGeometry(QRect(10, 70, 141, 20)); product_idLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QLabel *selectLabel = new QLabel(dialog); selectLabel->setObjectName(QString::fromUtf8("selectLabel")); selectLabel->setText(tr("Select yeast:")); selectLabel->setGeometry(QRect(10,100, 141, 20)); selectLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QLabel *instockLabel = new QLabel(dialog); instockLabel->setObjectName(QString::fromUtf8("instockLabel")); instockLabel->setText(tr("In stock:")); instockLabel->setGeometry(QRect(525,100, 121, 20)); instockLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QLabel *packageLabel = new QLabel(dialog); packageLabel->setObjectName(QString::fromUtf8("packageLabel")); packageLabel->setText(tr("Select package:")); packageLabel->setGeometry(QRect(10,130, 141, 20)); packageLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); yamountLabel = new QLabel(dialog); yamountLabel->setObjectName(QString::fromUtf8("amountLabel")); if (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_LIQUID) yamountLabel->setText(tr("Total packs:")); else if ((product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRY) || (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRIED)) yamountLabel->setText(tr("Amount in gr:")); else yamountLabel->setText(tr("Amount in ml:")); yamountLabel->setGeometry(QRect(10, 160, 141, 20)); yamountLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); QLabel *useatLabel = new QLabel(dialog); useatLabel->setObjectName(QString::fromUtf8("useatLabel")); useatLabel->setText(tr("Use at:")); useatLabel->setGeometry(QRect(10, 190, 141, 20)); useatLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); ynameEdit = new QLineEdit(dialog); ynameEdit->setObjectName(QString::fromUtf8("ynameEdit")); ynameEdit->setText(product->yeasts.at(product->yeasts_row).name); ynameEdit->setGeometry(QRect(160, 10, 511, 23)); ynameEdit->setReadOnly(true); ylaboratoryEdit = new QLineEdit(dialog); ylaboratoryEdit->setObjectName(QString::fromUtf8("ylaboratoryEdit")); ylaboratoryEdit->setText(product->yeasts.at(product->yeasts_row).laboratory); ylaboratoryEdit->setGeometry(QRect(160, 40, 511, 23)); ylaboratoryEdit->setReadOnly(true); yproduct_idEdit = new QLineEdit(dialog); yproduct_idEdit->setObjectName(QString::fromUtf8("yproduct_idEdit")); yproduct_idEdit->setText(product->yeasts.at(product->yeasts_row).product_id); yproduct_idEdit->setGeometry(QRect(160, 70, 511, 23)); yproduct_idEdit->setReadOnly(true); yselectEdit = new QComboBox(dialog); yselectEdit->setObjectName(QString::fromUtf8("selectEdit")); yselectEdit->setGeometry(QRect(160,100, 371, 23)); yinstockEdit = new QCheckBox(dialog); yinstockEdit->setObjectName(QString::fromUtf8("yinstockEdit")); yinstockEdit->setGeometry(QRect(655,100, 85, 21)); yinstockEdit->setChecked(true); ypackageEdit = new QComboBox(dialog); ypackageEdit->setObjectName(QString::fromUtf8("packageEdit")); ypackageEdit->setGeometry(QRect(160,130, 371, 23)); yamountEdit = new QDoubleSpinBox(dialog); yamountEdit->setObjectName(QString::fromUtf8("yamountEdit")); yamountEdit->setGeometry(QRect(160, 160, 121, 24)); yamountEdit->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); yamountEdit->setAccelerated(true); yamountEdit->setMaximum(10000.0); if (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_LIQUID) { yamountEdit->setDecimals(0); yamountEdit->setSingleStep(1.0); yamountEdit->setValue(product->yeasts.at(product->yeasts_row).amount); } else if ((product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRY) || (product->yeasts.at(product->yeasts_row).form == YEAST_FORMS_DRIED)) { yamountEdit->setDecimals(1); yamountEdit->setSingleStep(0.5); yamountEdit->setValue(product->yeasts.at(product->yeasts_row).amount * 1000.0); } else { yamountEdit->setDecimals(1); yamountEdit->setSingleStep(0.5); yamountEdit->setValue(product->yeasts.at(product->yeasts_row).amount * 1000.0); } yamountEdit->setMaximum(1000000000.0); useatEdit = new QComboBox(dialog); useatEdit->setObjectName(QString::fromUtf8("useatEdit")); useatEdit->setGeometry(QRect(160, 190, 161, 23)); useatEdit->addItem(tr("Primary")); useatEdit->addItem(tr("Secondary")); useatEdit->addItem(tr("Tertiary")); useatEdit->addItem(tr("Bottle")); useatEdit->setCurrentIndex(product->yeasts.at(product->yeasts_row).use); yeast_instock_changed(true); yeast_load_packages(product->yeasts.at(product->yeasts_row).laboratory, product->yeasts.at(product->yeasts_row).form); /* * Try to set the yeastpack dropdown to the selected entry if set. */ int index = 0; if (! product->yeasts.at(product->yeasts_row).yp_uuid.isNull() && (product->yeasts.at(product->yeasts_row).yp_uuid.length() == 36)) { query.prepare("SELECT * FROM inventory_yeastpack WHERE laboratory=:laboratory AND form=:form AND valid='1' ORDER BY laboratory,package"); query.bindValue(":laboratory", product->yeasts.at(product->yeasts_row).laboratory); query.bindValue(":form", product->yeasts.at(product->yeasts_row).form); query.exec(); while (query.next()) { index++; if (query.value("uuid").toString() == product->yeasts.at(product->yeasts_row).yp_uuid) { this->ypackageEdit->setCurrentIndex(index); break; } } } connect(yselectEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::yeast_select_changed); connect(ypackageEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::yeast_package_changed); connect(yamountEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::yeast_amount_changed); connect(useatEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::yeast_useat_changed); connect(yinstockEdit, &QCheckBox::stateChanged, this, &EditProduct::yeast_instock_changed); connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); dialog->setModal(true); dialog->exec(); if (dialog->result() == QDialog::Rejected) { #ifdef DEBUG_YEAST qDebug() << "reject and rollback"; #endif product->yeasts[product->yeasts_row] = backup; } else { } disconnect(yselectEdit, nullptr, nullptr, nullptr); disconnect(ypackageEdit, nullptr, nullptr, nullptr); disconnect(yamountEdit, nullptr, nullptr, nullptr); disconnect(useatEdit, nullptr, nullptr, nullptr); disconnect(yinstockEdit, nullptr, nullptr, nullptr); disconnect(buttonBox, nullptr, nullptr, nullptr); emit refreshAll(); } void EditProduct::adjustYeasts(double factor) { double amount; if (product->yeasts.size() == 0) return; for (int i = 0; i < product->yeasts.size(); i++) { if (product->yeasts.at(i).form == YEAST_FORMS_DRY) { // Only adjust dry yeast amount = product->yeasts.at(i).amount * factor; product->yeasts[i].amount = amount; } } }