--- a/src/PrinterDialog.cpp Mon Aug 01 13:05:23 2022 +0200 +++ b/src/PrinterDialog.cpp Mon Aug 01 21:49:57 2022 +0200 @@ -2141,6 +2141,231 @@ } } } + } else if (p_job == PR_REP_TOTAL) { + + qInfo() << "Print total production"; + printHeader(&painter); + y = 120; + + /* Report header */ + painter.setFont(QFont("Helvetica", 9, QFont::Bold)); + painter.setPen(Qt::black); + painter.fillRect( 20, y, 540, 20, c_header); + painter.drawText( 20, y+4, 80, 20, Qt::AlignHCenter, tr("Number")); + painter.drawText(100, y+4, 80, 20, Qt::AlignHCenter, tr("Year")); + painter.drawText(180, y+4, 120, 20, Qt::AlignRight, tr("Brew sessions")); + painter.drawText(300, y+4, 120, 20, Qt::AlignRight, tr("Brew volume")); + painter.drawText(420, y+4, 120, 20, Qt::AlignRight, tr("Average volume")); + y += 20; + painter.setFont(QFont("Helvetica", 9, QFont::Normal)); + query.exec("SELECT DISTINCT YEAR(package_date) FROM products WHERE package_date ORDER BY package_date"); + query.first(); + int regel = 0, brews = 0, total = 0; + double packaged = 0, tvolume = 0, average; + QString year = ""; + for (int i = 0 ; i < query.size() ; i++ ) { + if ((y + 20) > painter.device()->height()) { + printer->newPage(); + printHeader(&painter); + y = 120; + } + + brews = 0; + packaged = 0; + regel++; + year = query.value(0).toString(); + QSqlQuery query2; + query2.exec("SELECT package_volume FROM products WHERE package_date AND YEAR(package_date) = '" + year + "'"); + while (query2.next()) { + brews++; + total++; + packaged += query2.value(0).toDouble(); + tvolume += query2.value(0).toDouble(); + } + average = packaged / brews; + painter.fillRect( 20, y, 540, 20, (i % 2) ? c_line1:c_line2); + painter.drawText( 20, y+4, 80, 20, Qt::AlignCenter, QString("%1").arg(regel)); + painter.drawText(100, y+4, 100, 20, Qt::AlignCenter, year); + painter.drawText(180, y+4, 120, 20, Qt::AlignRight, QString("%1").arg(brews)); + painter.drawText(300, y+4, 120, 20, Qt::AlignRight, QString("%1 L").arg(packaged, 2, 'f', 1, '0')); + painter.drawText(420, y+4, 120, 20, Qt::AlignRight, QString("%1 L").arg(average, 2, 'f', 1, '0')); + query.next(); + y += 20; + } + average = tvolume / total; + painter.fillRect(180, y, 360, 20, w_line); + painter.drawText(180, y+4, 120, 20, Qt::AlignRight, QString("%1").arg(total)); + painter.drawText(300, y+4, 120, 20, Qt::AlignRight, QString("%1 L").arg(tvolume, 2, 'f', 1, '0')); + painter.drawText(420, y+4, 120, 20, Qt::AlignRight, QString("%1 L").arg(average, 2, 'f', 1, '0')); + y += 20; + + } else if (p_job == PR_REP_EFF) { + + qInfo() << "Print efficiency"; + y = painter.device()->height() + 100; + + query.exec("SELECT * FROM products WHERE package_date AND type='2' ORDER BY code"); + query.first(); + for (int i = 0 ; i < query.size() ; i++ ) { + if ((y + 20) > painter.device()->height()) { + if (i > 0) + printer->newPage(); + printHeader(&painter); + y = 120; + + /* Report header */ + painter.setFont(QFont("Helvetica", 9, QFont::Bold)); + painter.setPen(Qt::black); + painter.fillRect( 20, y, 715, 20, c_header); + painter.drawText( 25, y+4, 65, 20, Qt::AlignLeft, tr("Code")); + painter.drawText( 90, y+4, 200, 20, Qt::AlignLeft, tr("Name")); + painter.drawText(290, y+4, 120, 20, Qt::AlignLeft, tr("Beer style")); + painter.drawText(410, y+4, 80, 20, Qt::AlignRight, tr("Max extract")); + painter.drawText(490, y+4, 80, 20, Qt::AlignRight, tr("Mash eff.")); + painter.drawText(570, y+4, 80, 20, Qt::AlignRight, tr("Sparge eff")); + painter.drawText(650, y+4, 80, 20, Qt::AlignRight, tr("Boil eff")); + y += 20; + painter.setFont(QFont("Helvetica", 9, QFont::Normal)); + } + + /* + * Data is not always available, calculate the missing pieces. + */ + double mvol = 0, msugars = 0, ssugars = 0; + QJsonParseError parseError; + + const auto& ma_json = query.value("json_mashs").toString().trimmed(); + if (!ma_json.trimmed().isEmpty()) { + const auto& formattedJson = QString("%1").arg(ma_json); + QJsonDocument mashs = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ; + } else if (mashs.isArray()) { + for (int j = 0; j < mashs.array().size(); j++) { + QJsonObject obj = mashs.array().at(j).toObject(); + if (obj["step_type"].toInt() == 0) + mvol += obj["step_infuse_amount"].toDouble(); + } + } + } + + const auto& f_json = query.value("json_fermentables").toString(); + if (!f_json.trimmed().isEmpty()) { + const auto& formattedJson = QString("%1").arg(f_json); + QJsonDocument fermentables = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ; + } else if (fermentables.isArray()) { + for (int j = 0; j < fermentables.array().size(); j++) { + QJsonObject obj = fermentables.array().at(j).toObject(); + if (obj["f_added"].toInt() == 0) { + double d = obj["f_amount"].toDouble() * (obj["f_yield"].toDouble() / 100) * (1 - obj["f_moisture"].toDouble() / 100); + ssugars += obj["f_amount"].toDouble(); + mvol += obj["f_amount"].toDouble() * obj["f_moisture"].toDouble() / 100; + msugars += d; + } + } + } + } + + double sugardensity = 1.611; + double v = msugars / sugardensity + mvol; + double plato = 1000 * msugars / (v * 10); // deg. Plato + double mash_efficiency = query.value("brew_mash_efficiency").toDouble(); + double mash_sg = query.value("brew_mash_sg").toDouble(); + if ((mash_efficiency == 0) && (mash_sg > 1)) { + mash_efficiency = 100 * Utils::sg_to_plato(mash_sg) / plato; + } + double mash_extract = 100 * msugars / ssugars; + double preboil_sg = query.value("brew_preboil_sg").toDouble(); + double preboil_volume = query.value("brew_preboil_volume").toDouble(); + double est_preboil_plato = Utils::sg_to_plato(preboil_sg) * (preboil_volume / 1.04) * preboil_sg * 10 / 1000; + double preboil_efficiency = query.value("brew_preboil_efficiency").toDouble(); + if ((msugars > 0) && (preboil_efficiency == 0)) + preboil_efficiency = est_preboil_plato / msugars * 100; + if (preboil_efficiency < 0) + preboil_efficiency = 0; + double aboil_efficiency = query.value("brew_aboil_efficiency").toDouble(); + + painter.fillRect( 20, y, 715, 20, (i % 2) ? c_line1:c_line2); + painter.drawText( 25, y+4, 65, 20, Qt::AlignLeft, query.value("code").toString()); + painter.drawText( 90, y+4, 200, 20, Qt::AlignLeft, query.value("name").toString()); + painter.drawText(290, y+4, 120, 20, Qt::AlignLeft, query.value("st_name").toString()); + painter.drawText(410, y+4, 80, 20, Qt::AlignRight, QString("%1%").arg(mash_extract, 2, 'f', 1, '0')); + painter.drawText(490, y+4, 80, 20, Qt::AlignRight, QString("%1%").arg(mash_efficiency, 2, 'f', 1, '0')); + painter.drawText(570, y+4, 80, 20, Qt::AlignRight, QString("%1%").arg(preboil_efficiency, 2, 'f', 1, '0')); + painter.drawText(650, y+4, 80, 20, Qt::AlignRight, QString("%1%").arg(aboil_efficiency, 2, 'f', 1, '0')); + query.next(); + y += 20; + } + + } else if (p_job == PR_REP_SVG) { + + qInfo() << "Print fermentations"; + y = painter.device()->height() + 100; + QString y_name, y_lab, y_product; + + /* + * Works from MariaDB 10.6.x and later, MySQL 8.x and later. + * Pick the first yeast record, that should be the one used for primary. + */ + query.exec("SELECT code,name,brew_date_end,primary_end_temp,primary_end_date,secondary_temp,secondary_end_date,tertiary_temp," + "package_date,brew_fermenter_sg,fg,json_yeasts," + "JSON_EXTRACT(json_yeasts, '$[0].y_laboratory') AS yeastLab,JSON_EXTRACT(json_yeasts, '$[0].y_product_id') AS yeastID " + "FROM products WHERE package_date AND type='2' ORDER BY yeastID"); + query.first(); + + for (int i = 0 ; i < query.size() ; i++ ) { + if ((y + 20) > painter.device()->height()) { + if (i > 0) + printer->newPage(); + printHeader(&painter); + y = 120; + + /* Report header */ + painter.setFont(QFont("Helvetica", 9, QFont::Bold)); + painter.setPen(Qt::black); + painter.fillRect( 20, y, 715, 20, c_header); + painter.drawText( 25, y+4, 65, 20, Qt::AlignLeft, tr("Code")); + painter.drawText( 90, y+4, 180, 20, Qt::AlignLeft, tr("Name")); + painter.drawText(270, y+4, 110, 20, Qt::AlignLeft, tr("Yeast")); + painter.drawText(380, y+4, 60, 20, Qt::AlignHCenter, tr("Primary")); + painter.drawText(440, y+4, 60, 20, Qt::AlignHCenter, tr("Secondary")); + painter.drawText(500, y+4, 60, 20, Qt::AlignHCenter, tr("Tertiary")); + painter.drawText(560, y+4, 40, 20, Qt::AlignRight, tr("Days")); + painter.drawText(600, y+4, 40, 20, Qt::AlignRight, tr("OG")); + painter.drawText(640, y+4, 40, 20, Qt::AlignRight, tr("FG")); + painter.drawText(680, y+4, 50, 20, Qt::AlignRight, tr("AA")); + y += 20; + painter.setFont(QFont("Helvetica", 9, QFont::Normal)); + } + + int primary = query.value("brew_date_end").toDate().daysTo(query.value("primary_end_date").toDate()); + int secondary = query.value("primary_end_date").toDate().daysTo(query.value("secondary_end_date").toDate()); + int tertiary = query.value("secondary_end_date").toDate().daysTo(query.value("package_date").toDate()); + int total = query.value("brew_date_end").toDate().daysTo(query.value("package_date").toDate()); + double og = query.value("brew_fermenter_sg").toDouble(); + double fg = query.value("fg").toDouble(); + double aa = Utils::calc_svg(og, fg); + + painter.fillRect( 20, y, 715, 20, (i % 2) ? c_line1:c_line2); + painter.drawText( 25, y+4, 65, 20, Qt::AlignLeft, query.value("code").toString()); + painter.drawText( 90, y+4, 180, 20, Qt::AlignLeft, query.value("name").toString()); + painter.drawText(270, y+4, 110, 20, Qt::AlignLeft, query.value("yeastID").toString() + " " + query.value("yeastLab").toString()); + painter.drawText(380, y+4, 40, 20, Qt::AlignRight, QString("%1°").arg(query.value("primary_end_temp").toDouble(), 2, 'f', 1)); + painter.drawText(420, y+4, 20, 20, Qt::AlignRight, QString("%1").arg(primary)); + painter.drawText(440, y+4, 40, 20, Qt::AlignRight, QString("%1°").arg(query.value("secondary_temp").toDouble(), 2, 'f', 1)); + painter.drawText(480, y+4, 20, 20, Qt::AlignRight, QString("%1").arg(secondary)); + painter.drawText(500, y+4, 40, 20, Qt::AlignRight, QString("%1°").arg(query.value("tertiary_temp").toDouble(), 2, 'f', 1)); + painter.drawText(540, y+4, 20, 20, Qt::AlignRight, QString("%1").arg(tertiary)); + painter.drawText(560, y+4, 40, 20, Qt::AlignRight, QString("%1").arg(total)); + painter.drawText(600, y+4, 40, 20, Qt::AlignRight, QString("%1").arg(og, 4, 'f', 3, '0')); + painter.drawText(640, y+4, 40, 20, Qt::AlignRight, QString("%1").arg(fg, 4, 'f', 3, '0')); + painter.drawText(680, y+4, 50, 20, Qt::AlignRight, QString("%1%").arg(aa, 2, 'f', 1, '0')); + + query.next(); + y += 20; + } } painter.end(); @@ -2232,6 +2457,12 @@ painter->drawText(140, 0, 500, 40, Qt::AlignLeft, recipe->name); } else if (p_job == PR_PRODUCT || p_job == PR_CHECKLIST) { painter->drawText(140, 0, 500, 40, Qt::AlignLeft, product->code + " " + product->name); + } else if (p_job == PR_REP_TOTAL) { + painter->drawText(140, 0, 500, 40, Qt::AlignLeft, tr("Year production") + " " + my_brewery_name); + } else if (p_job == PR_REP_EFF) { + painter->drawText(140, 0, 500, 40, Qt::AlignLeft, tr("Brew efficiency") + " " + my_brewery_name); + } else if (p_job == PR_REP_SVG) { + painter->drawText(140, 0, 500, 40, Qt::AlignLeft, tr("Fermentations") + " " + my_brewery_name); } else { painter->drawText(140, 0, 500, 40, Qt::AlignLeft, "?? " + my_brewery_name); }