Moved recipe load, save and delete to it's own file.

Wed, 01 Jun 2022 21:46:26 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Wed, 01 Jun 2022 21:46:26 +0200
changeset 249
3c28dc8dd51d
parent 248
1a7a5dffba58
child 250
baab61fb8bcd

Moved recipe load, save and delete to it's own file.

CMakeLists.txt file | annotate | diff | comparison | revisions
src/EditProduct.cpp file | annotate | diff | comparison | revisions
src/EditRecipe.cpp file | annotate | diff | comparison | revisions
src/database/db_recipe.cpp file | annotate | diff | comparison | revisions
src/database/db_recipe.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Wed Jun 01 19:30:38 2022 +0200
+++ b/CMakeLists.txt	Wed Jun 01 21:46:26 2022 +0200
@@ -172,6 +172,7 @@
     ${SRCDIR}/MainWindow.cpp
     ${SRCDIR}/database/database.cpp
     ${SRCDIR}/database/db_product.cpp
+    ${SRCDIR}/database/db_recipe.cpp
     ${SRCDIR}/RangedSlider.cpp
     ${SRCDIR}/NullDateEdit.cpp
     ${SRCDIR}/global.cpp
@@ -215,6 +216,7 @@
     ${SRCDIR}/MainWindow.h
     ${SRCDIR}/database/database.h
     ${SRCDIR}/database/db_product.h
+    ${SRCDIR}/database/db_recipe.h
     ${SRCDIR}/RangedSlider.h
     ${SRCDIR}/NullDateEdit.h
     ${SRCDIR}/global.h
--- a/src/EditProduct.cpp	Wed Jun 01 19:30:38 2022 +0200
+++ b/src/EditProduct.cpp	Wed Jun 01 21:46:26 2022 +0200
@@ -799,28 +799,12 @@
 
 void EditProduct::on_deleteButton_clicked()
 {
-//    QSqlQuery query;
-
     int rc = QMessageBox::warning(this, tr("Delete product"), tr("Delete %1").arg(product->name),
                     QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
     if (rc == QMessageBox::No)
         return;
 
     DB_product::dele(this, this->recno);
-/*    query.prepare("DELETE FROM products WHERE record = :recno");
-    query.bindValue(":recno", this->recno);
-    query.exec();
-    if (query.lastError().isValid()) {
-	qDebug() << "EditProduct" << query.lastError();
-	QMessageBox::warning(this, tr("Database error"),
-                        tr("MySQL error: %1\n%2\n%3")
-                        .arg(query.lastError().nativeErrorCode())
-                        .arg(query.lastError().driverText())
-                        .arg(query.lastError().databaseText()));
-    } else {
-	qDebug() << "EditProduct Deleted" << this->recno;
-    }
-*/
 
     this->close();
     this->setResult(1);
--- a/src/EditRecipe.cpp	Wed Jun 01 19:30:38 2022 +0200
+++ b/src/EditRecipe.cpp	Wed Jun 01 21:46:26 2022 +0200
@@ -20,7 +20,7 @@
 #include "../ui/ui_EditRecipe.h"
 #include "Utils.h"
 #include "global.h"
-
+#include "database/db_recipe.h"
 
 
 EditRecipe::EditRecipe(int id, QWidget *parent) : QDialog(parent), ui(new Ui::EditRecipe)
@@ -94,342 +94,11 @@
     }
 
     if (id >= 0) {
-	query.prepare("SELECT * FROM recipes WHERE record = :recno");
-	query.bindValue(":recno", id);
-	query.exec();
-	if (! query.first()) {
-	    qDebug() << "EditRecipe seek error record" << id;
-	    QMessageBox::warning(this, tr("Database error"), tr("MySQL error: record %1 not found").arg(id));
-	    this->done(QDialog::Rejected);	// At this stage, this doesn't work because the dialog is not yet visible.
-	    return;
-	}
-
-	QSqlRecord rec = query.record();
-	for (int i = 0; i < rec.count(); i++)
-            qDebug() << i << rec.fieldName(i) << query.value(i);
-
-	recipe->record = query.value(0).toInt();
-	recipe->uuid = query.value(1).toString();
-	recipe->locked = query.value(2).toInt() ? true:false;
-	recipe->st_name = query.value(3).toString();
-	recipe->st_letter = query.value(4).toString();
-	recipe->st_guide = query.value(5).toString();
-	recipe->st_category = query.value(6).toString();
-	recipe->st_category_number = query.value(7).toInt();
-	recipe->st_type = query.value(8).toInt();
-	recipe->st_og_min = query.value(9).toDouble();
-	recipe->st_og_max = query.value(10).toDouble();
-	recipe->st_fg_min = query.value(11).toDouble();
-	recipe->st_fg_max = query.value(12).toDouble();
-	recipe->st_ibu_min = query.value(13).toDouble();
-	recipe->st_ibu_max = query.value(14).toDouble();
-	recipe->st_color_min = query.value(15).toDouble();
-	recipe->st_color_max = query.value(16).toDouble();
-	recipe->st_carb_min = query.value(17).toDouble();
-	recipe->st_carb_max = query.value(18).toDouble();
-	recipe->st_abv_min = query.value(19).toDouble();
-	recipe->st_abv_max = query.value(20).toDouble();
-	recipe->name = query.value(21).toString();
-	recipe->notes = query.value(22).toString();
-	recipe->type = query.value(23).toInt();
-	recipe->batch_size = query.value(24).toDouble();
-	recipe->boil_size = query.value(25).toDouble();
-	recipe->boil_time = query.value(26).toDouble();
-	recipe->efficiency = query.value(27).toDouble();
-	recipe->est_og = query.value(28).toDouble();
-	recipe->est_fg = query.value(29).toDouble();
-	recipe->est_abv = query.value(30).toDouble();
-	recipe->est_color = query.value(31).toDouble();
-	recipe->color_method = query.value(32).toInt();
-	recipe->est_ibu = query.value(33).toDouble();
-	recipe->ibu_method = query.value(34).toInt();
-	recipe->est_carb = query.value(35).toDouble();
-
-	recipe->sparge_temp = query.value(36).toDouble();
-	recipe->sparge_ph = query.value(37).toDouble();
-	recipe->sparge_volume = query.value(38).toDouble();
-	recipe->sparge_source = query.value(39).toInt();
-	recipe->sparge_acid_type = query.value(40).toInt();
-	recipe->sparge_acid_perc = query.value(41).toDouble();
-	recipe->sparge_acid_amount = query.value(42).toDouble();
-	recipe->mash_ph = query.value(43).toDouble();
-	recipe->mash_name = query.value(44).toString();
-	recipe->calc_acid = query.value(45).toInt() ? true:false;
-
-	recipe->w1_name = query.value(46).toString();
-	recipe->w1_amount = query.value(47).toDouble();
-	recipe->w1_calcium = query.value(48).toDouble();
-	recipe->w1_sulfate = query.value(49).toDouble();
-	recipe->w1_chloride = query.value(50).toDouble();
-	recipe->w1_sodium = query.value(51).toDouble();
-	recipe->w1_magnesium = query.value(52).toDouble();
-	recipe->w1_total_alkalinity = query.value(53).toDouble();
-	recipe->w1_ph = query.value(54).toDouble();
-	recipe->w1_cost = query.value(55).toDouble();
-	recipe->w2_name = query.value(56).toString();
-        recipe->w2_amount = query.value(57).toDouble();
-        recipe->w2_calcium = query.value(58).toDouble();
-        recipe->w2_sulfate = query.value(59).toDouble();
-        recipe->w2_chloride = query.value(60).toDouble();
-        recipe->w2_sodium = query.value(61).toDouble();
-        recipe->w2_magnesium = query.value(62).toDouble();
-        recipe->w2_total_alkalinity = query.value(63).toDouble();
-        recipe->w2_ph = query.value(64).toDouble();
-        recipe->w2_cost = query.value(65).toDouble();
-	recipe->wg_amount = query.value(66).toDouble();
-        recipe->wg_calcium = query.value(67).toDouble();
-        recipe->wg_sulfate = query.value(68).toDouble();
-        recipe->wg_chloride = query.value(69).toDouble();
-        recipe->wg_sodium = query.value(70).toDouble();
-        recipe->wg_magnesium = query.value(71).toDouble();
-        recipe->wg_total_alkalinity = query.value(72).toDouble();
-        recipe->wg_ph = query.value(73).toDouble();
-	recipe->wb_calcium = query.value(74).toDouble();
-        recipe->wb_sulfate = query.value(75).toDouble();
-        recipe->wb_chloride = query.value(76).toDouble();
-        recipe->wb_sodium = query.value(77).toDouble();
-        recipe->wb_magnesium = query.value(78).toDouble();
-        recipe->wb_total_alkalinity = query.value(79).toDouble();
-        recipe->wb_ph = query.value(80).toDouble();
-	recipe->wa_acid_name = query.value(81).toInt();
-        recipe->wa_acid_perc = query.value(82).toDouble();
-        recipe->wa_base_name = query.value(83).toInt();
-
-	QJsonParseError parseError;
-        const auto& f_json = query.value(84).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) {
-                qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
-	    } else if (fermentables.isArray()) {
-		double percentcheck = 0;
-		for (int i = 0; i < fermentables.array().size(); i++) {
-		    QJsonObject obj = fermentables.array().at(i).toObject();
-		    //qDebug() << i << obj;
-		    Fermentables f;
-		    f.f_name = obj["f_name"].toString();
-		    f.f_origin = obj["f_origin"].toString();
-		    f.f_supplier = obj["f_supplier"].toString();
-		    f.f_amount = obj["f_amount"].toDouble();
-		    f.f_cost = obj["f_cost"].toDouble();
-		    f.f_type = obj["f_type"].toInt();
-		    f.f_yield = obj["f_yield"].toDouble();
-		    f.f_color = obj["f_color"].toDouble();
-		    f.f_coarse_fine_diff = obj["f_coarse_fine_diff"].toDouble();
-		    f.f_moisture = obj["f_moisture"].toDouble();
-		    f.f_diastatic_power = obj["f_diastatic_power"].toDouble();
-		    f.f_protein = obj["f_protein"].toDouble();
-		    f.f_dissolved_protein = obj["f_dissolved_protein"].toDouble();
-		    f.f_max_in_batch = obj["f_max_in_batch"].toDouble();
-		    f.f_graintype = obj["f_graintype"].toInt();
-		    f.f_added = obj["f_added"].toInt();
-		    f.f_recommend_mash = obj["f_recommend_mash"].toInt() ? true:false;
-		    f.f_add_after_boil = obj["f_add_after_boil"].toInt() ? true:false;
-		    f.f_adjust_to_total_100 = obj["f_adjust_to_total_100"].toInt() ? true:false;
-		    f.f_percentage = obj["f_percentage"].toDouble();
-		    f.f_di_ph = obj["f_di_ph"].toDouble();
-		    f.f_acid_to_ph_57 = obj["f_acid_to_ph_57"].toDouble();
-		    if (f.f_adjust_to_total_100)
-			recipe->fermentables_use100 = true;
-		    percentcheck += f.f_percentage;
-		    recipe->fermentables.append(f);
-		}
-		qDebug() << "fermentables" << recipe->fermentables.size() << percentcheck;
-		if (percentcheck == 0) {
-		    /* Missing percentages, fix it. */
-		    double total = 0;
-		    for (int i = 0; i < recipe->fermentables.size(); i++) {
-			if (recipe->fermentables.at(i).f_added < 4)
-			    total += recipe->fermentables.at(i).f_amount;
-		    }
-		    for (int i = 0; i < recipe->fermentables.size(); i++) {
-			if (recipe->fermentables.at(i).f_added < 4)
-			    recipe->fermentables[i].f_percentage = round((recipe->fermentables.at(i).f_amount / total) * 10000.0) / 100.0;
-		    }
-		    qDebug() << " fixed missing percentages";
-		    is_changed();
-		}
-	    }
-        } else {
-	    qDebug() << "empty fermentables";
-	}
-
-	const auto& h_json = query.value(85).toString();
-        if (!h_json.trimmed().isEmpty()) {
-            const auto& formattedJson = QString("%1").arg(h_json);
-            QJsonDocument hops = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError);
-            if (parseError.error != QJsonParseError::NoError) {
-                qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset;
-	    } else if (hops.isArray()) {
-		for (int i = 0; i < hops.array().size(); i++) {
-		    QJsonObject obj = hops.array().at(i).toObject();
-		    //qDebug() << i << obj;
-		    Hops h;
-		    h.h_name = obj["h_name"].toString();
-		    h.h_origin = obj["h_origin"].toString();
-		    h.h_amount = obj["h_amount"].toDouble();
-		    h.h_cost = obj["h_cost"].toDouble();
-		    h.h_type = obj["h_type"].toInt();
-		    h.h_form = obj["h_form"].toInt();
-		    h.h_useat = obj["h_useat"].toInt();
-		    h.h_time = obj["h_time"].toInt();
-		    h.h_alpha = obj["h_alpha"].toDouble();
-		    h.h_beta = obj["h_beta"].toDouble();
-		    h.h_hsi = obj["h_hsi"].toDouble();
-		    h.h_humulene = obj["h_humulene"].toDouble();
-		    h.h_caryophyllene = obj["h_caryophyllene"].toDouble();
-		    h.h_cohumulone = obj["h_cohumulone"].toDouble();
-		    h.h_myrcene = obj["h_myrcene"].toDouble();
-		    h.h_total_oil = obj["h_total_oil"].toDouble();
-		    recipe->hops.append(h);
-		}
-		qDebug() << "hops" << recipe->hops.size();
-	    }
-        } else {
-            qDebug() << "empty hops";
-        }
-
-	const auto& m_json = query.value(86).toString();
-        if (!m_json.trimmed().isEmpty()) {
-            const auto& formattedJson = QString("%1").arg(m_json);
-            QJsonDocument miscs = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError);
-            if (parseError.error != QJsonParseError::NoError) {
-                qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset;
-	    } else if (miscs.isArray()) {
-		for (int i = 0; i < miscs.array().size(); i++) {
-		    QJsonObject obj = miscs.array().at(i).toObject();
-		    //qDebug() << i << obj;
-		    Miscs m;
-		    m.m_name = obj["m_name"].toString();
-		    m.m_amount = obj["m_amount"].toDouble();
-		    m.m_type = obj["m_type"].toInt();
-		    m.m_use_use = obj["m_use_use"].toInt();
-		    m.m_time = obj["m_time"].toDouble();
-		    m.m_amount_is_weight = obj["m_amount_is_weight"].toInt() ? true:false;
-		    m.m_cost = obj["m_cost"].toDouble();
-		    recipe->miscs.append(m);
-		}
-		qDebug() << "miscs" << recipe->miscs.size();
-	    }
-        } else {
-            qDebug() << "empty miscs";
-        }
-
-	const auto& y_json = query.value(87).toString();
-        if (!y_json.trimmed().isEmpty()) {
-            const auto& formattedJson = QString("%1").arg(y_json);
-            QJsonDocument yeasts = QJsonDocument::fromJson(formattedJson.toUtf8(),  &parseError);
-            if (parseError.error != QJsonParseError::NoError) {
-                qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
-	    } else if (yeasts.isArray()) {
-		for (int i = 0; i < yeasts.array().size(); i++) {
-		    QJsonObject obj = yeasts.array().at(i).toObject();
-		    //qDebug() << i << obj;
-		    Yeasts y;
-		    /* First some defaults for newer fields. */
-		    y.y_tolerance = y.y_harvest_time = y.y_pitch_temperature = y.y_zymocide = 0;
-                    y.y_sta1 = y.y_bacteria = y.y_harvest_top = y.y_pofpos = false;
-		    y.y_gr_hl_lo = 50;
-                    y.y_sg_lo = 1.04;
-                    y.y_gr_hl_hi = 80;
-                    y.y_sg_hi = 1.08;
-		    /* Now get what we have got */
-		    y.y_name = obj["y_name"].toString();
-		    y.y_laboratory = obj["y_laboratory"].toString();
-		    y.y_product_id = obj["y_product_id"].toString();
-		    y.y_amount = obj["y_amount"].toDouble();
-		    y.y_type = obj["y_type"].toInt();
-		    y.y_form = obj["y_form"].toInt();
-		    y.y_min_temperature = obj["y_min_temperature"].toDouble();
-		    y.y_max_temperature = obj["y_max_temperature"].toDouble();
-		    y.y_flocculation = obj["y_flocculation"].toInt();
-		    y.y_attenuation = obj["y_attenuation"].toDouble();
-		    y.y_cells = obj["y_cells"].toDouble();
-		    y.y_tolerance = obj["y_tolerance"].toDouble();
-		    y.y_inventory = obj["y_inventory"].toDouble();
-		    y.y_use = obj["y_use"].toInt();
-		    y.y_sta1 = obj["y_sta1"].toInt() ? true:false;
-		    y.y_bacteria = obj["y_bacteria"].toInt() ? true:false;
-		    y.y_harvest_top = obj["y_harvest_top"].toInt() ? true:false;
-		    y.y_harvest_time = obj["y_harvest_time"].toInt();
-		    y.y_pitch_temperature = obj["y_pitch_temperature"].toDouble();
-		    y.y_pofpos = obj["y_pofpos"].toInt() ? true:false;
-		    y.y_zymocide = obj["y_zymocide"].toInt();
-		    y.y_gr_hl_lo = obj["y_gr_hl_lo"].toInt();
-		    y.y_sg_lo = obj["y_sg_lo"].toDouble();
-		    y.y_gr_hl_hi = obj["y_gr_hl_hi"].toInt();
-		    y.y_sg_hi = obj["y_sg_hi"].toDouble();
-		    y.y_cost = obj["y_cost"].toDouble();
-
-		    if (y.y_tolerance == 0 || y.y_cells == 0) {	// More and better tests?
-			/*
-			 * Possible data upgrade needed.
-			 */
-			yquery.prepare("SELECT tolerance,cells,sta1,bacteria,harvest_top,harvest_time,pitch_temperature,"
-				       "pofpos,zymocide,gr_hl_lo,sg_lo,gr_hl_hi,sg_hi "
-				       "FROM inventory_yeasts WHERE name=:name AND laboratory=:laboratory AND product_id=:product_id");
-			yquery.bindValue(":name", y.y_name);
-			yquery.bindValue(":laboratory", y.y_laboratory);
-			yquery.bindValue(":product_id", y.y_product_id);
-            		yquery.exec();
-            		if (yquery.first()) {
-			    y.y_tolerance = yquery.value(0).toDouble();
-			    y.y_cells = yquery.value(1).toDouble();
-			    y.y_sta1 = yquery.value(2).toInt() ? true:false;
-			    y.y_bacteria = yquery.value(3).toInt() ? true:false;
-			    y.y_harvest_top = yquery.value(4).toInt() ? true:false;
-			    y.y_harvest_time = yquery.value(5).toInt();
-			    y.y_pitch_temperature = yquery.value(6).toDouble();
-			    y.y_pofpos = yquery.value(7).toInt() ? true:false;
-			    y.y_zymocide = yquery.value(8).toInt();
-			    y.y_gr_hl_lo = yquery.value(9).toInt();
-			    y.y_sg_lo = yquery.value(10).toDouble();
-			    y.y_gr_hl_hi = yquery.value(11).toInt();
-			    y.y_sg_hi = yquery.value(12).toDouble();
-			} else {
-			    qDebug() << y.y_name << y.y_product_id << "not found for upgrade";
-			}
-		    }
-		    recipe->yeasts.append(y);
-		}
-		qDebug() << "yeasts" << recipe->yeasts.size();
-	    }
-        } else {
-            qDebug() << "empty yeasts";
-        }
-
-	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) {
-                qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
-	    } else if (mashs.isArray()) {
-        	for (int i = 0; i < mashs.array().size(); i++) {
-            	    QJsonObject obj = mashs.array().at(i).toObject();
-		    qDebug() << i << obj;
-		    Mashs m;
-		    m.step_name = obj["step_name"].toString();
-		    m.step_type = obj["step_type"].toInt();
-		    m.step_volume = obj["step_volume"].toDouble();
-		    m.step_infuse_amount = obj["step_infuse_amount"].toDouble();
-		    m.step_infuse_temp = obj["step_infuse_temp"].toDouble();
-		    m.step_temp = obj["step_temp"].toDouble();
-		    m.step_time = obj["step_time"].toDouble();
-		    m.ramp_time = obj["ramp_time"].toDouble();
-		    m.end_temp = obj["end_temp"].toDouble();
-		    m.step_wg_ratio = obj["step_wg_ratio"].toDouble();
-		    recipe->mashs.append(m);
-		}
-	    }
-        } else {
-            qDebug() << "empty mashs";
-        }
-	qDebug() << "mashs" << recipe->mashs.size();
-
+	if (! DB_recipe::load(recipe, this, id))
+            return;
     } else {
 	/* Set some defaults */
+	recipe->record = -1;
 	recipe->locked = false;
 	recipe->st_name = "";
 	recipe->st_letter = "";
@@ -803,328 +472,13 @@
     }
 
     if (this->textIsChanged) {
-    	if (this->recno == -1) {
-    	    query.prepare("INSERT INTO recipes SET locked=:locked, st_name=:st_name, st_letter=:st_letter, "
-		"st_guide=:st_guide, st_category=:st_category, st_category_number=:st_catnr, st_type=:st_type, "
-		"st_og_min=:st_og_min, st_og_max=:st_og_max, st_fg_min=:st_fg_min, st_fg_max=:st_fg_max, "
-		"st_ibu_min=:st_ibu_min, st_ibu_max=:st_ibu_max, st_color_min=:st_color_min, st_color_max=:st_color_max, "
-		"st_carb_min=:st_carb_min, st_carb_max=:st_carb_max, st_abv_min=:st_abv_min, st_abv_max=:st_abv_max, "
-		"name=:name, notes=:notes, type=:type, batch_size=:batch_size, boil_size=:boil_size, "
-		"boil_time=:boil_time, efficiency=:efficiency, est_og=:est_og, est_fg=:est_fg, est_abv=:est_abv, "
-		"est_color=:est_color, color_method=:color_method, est_ibu=:est_ibu, ibu_method=:ibu_method, "
-		"est_carb=:est_carb, sparge_temp=:sparge_temp, sparge_ph=:sparge_ph, "
-		"sparge_volume=:sparge_volume, sparge_source=:sparge_source, sparge_acid_type=:sparge_acid_type, "
-		"sparge_acid_perc=:sparge_acid_perc, sparge_acid_amount=:sparge_acid_amount, mash_ph=:mash_ph, "
-		"mash_name=:mash_name, calc_acid=:calc_acid, "
-		"w1_name=:w1_name, w1_amount=:w1_amount, w1_calcium=:w1_calcium, w1_sulfate=:w1_sulfate, "
-		"w1_chloride=:w1_chloride, w1_sodium=:w1_sodium, w1_magnesium=:w1_magnesium, "
-		"w1_total_alkalinity=:w1_total_alkalinity, w1_ph=:w1_ph, w1_cost=:w1_cost, "
-		"w2_name=:w2_name, w2_amount=:w2_amount, w2_calcium=:w2_calcium, w2_sulfate=:w2_sulfate, "
-                "w2_chloride=:w2_chloride, w2_sodium=:w2_sodium, w2_magnesium=:w2_magnesium, "
-                "w2_total_alkalinity=:w2_total_alkalinity, w2_ph=:w2_ph, w2_cost=:w2_cost, "
-		"wg_amount=:wg_amount, wg_calcium=:wg_calcium, wg_sulfate=:wg_sulfate, "
-                "wg_chloride=:wg_chloride, wg_sodium=:wg_sodium, wg_magnesium=:wg_magnesium, "
-                "wg_total_alkalinity=:wg_total_alkalinity, wg_ph=:wg_ph, "
-		"wb_calcium=:wb_calcium, wb_sulfate=:wb_sulfate, wb_chloride=:wb_chloride, wb_sodium=:wb_sodium, "
-		"wb_magnesium=:wb_magnesium, wb_total_alkalinity=:wb_total_alkalinity, wb_ph=:wb_ph, "
-		"wa_acid_name=:wa_acid_name, wa_acid_perc=:wa_acid_perc, wa_base_name=:wa_base_name, "
-		"json_fermentables=:json_fermentables, json_hops=:json_hops, json_miscs=:json_miscs, "
-		"json_yeasts=:json_yeasts, json_mashs=:json_mashs, uuid=:uuid");
-    	} else {
-	    query.prepare("UPDATE recipes SET locked=:locked, st_name=:st_name, st_letter=:st_letter, "
-		"st_guide=:st_guide, st_category=:st_category, st_category_number=:st_catnr, st_type=:st_type, "
-                "st_og_min=:st_og_min, st_og_max=:st_og_max, st_fg_min=:st_fg_min, st_fg_max=:st_fg_max, "
-                "st_ibu_min=:st_ibu_min, st_ibu_max=:st_ibu_max, st_color_min=:st_color_min, st_color_max=:st_color_max, "
-                "st_carb_min=:st_carb_min, st_carb_max=:st_carb_max, st_abv_min=:st_abv_min, st_abv_max=:st_abv_max, "
-                "name=:name, notes=:notes, type=:type, batch_size=:batch_size, boil_size=:boil_size, "
-		"boil_time=:boil_time, efficiency=:efficiency, est_og=:est_og, est_fg=:est_fg, est_abv=:est_abv, "
-                "est_color=:est_color, color_method=:color_method, est_ibu=:est_ibu, ibu_method=:ibu_method, "
-                "est_carb=:est_carb, sparge_temp=:sparge_temp, sparge_ph=:sparge_ph, "
-		"sparge_volume=:sparge_volume, sparge_source=:sparge_source, sparge_acid_type=:sparge_acid_type, "
-                "sparge_acid_perc=:sparge_acid_perc, sparge_acid_amount=:sparge_acid_amount, mash_ph=:mash_ph, "
-                "mash_name=:mash_name, calc_acid=:calc_acid, "
-                "w1_name=:w1_name, w1_amount=:w1_amount, w1_calcium=:w1_calcium, w1_sulfate=:w1_sulfate, "
-                "w1_chloride=:w1_chloride, w1_sodium=:w1_sodium, w1_magnesium=:w1_magnesium, "
-                "w1_total_alkalinity=:w1_total_alkalinity, w1_ph=:w1_ph, w1_cost=:w1_cost, "
-                "w2_name=:w2_name, w2_amount=:w2_amount, w2_calcium=:w2_calcium, w2_sulfate=:w2_sulfate, "
-                "w2_chloride=:w2_chloride, w2_sodium=:w2_sodium, w2_magnesium=:w2_magnesium, "
-                "w2_total_alkalinity=:w2_total_alkalinity, w2_ph=:w2_ph, w2_cost=:w2_cost, "
-                "wg_amount=:wg_amount, wg_calcium=:wg_calcium, wg_sulfate=:wg_sulfate, "
-                "wg_chloride=:wg_chloride, wg_sodium=:wg_sodium, wg_magnesium=:wg_magnesium, "
-                "wg_total_alkalinity=:wg_total_alkalinity, wg_ph=:wg_ph, "
-                "wb_calcium=:wb_calcium, wb_sulfate=:wb_sulfate, wb_chloride=:wb_chloride, wb_sodium=:wb_sodium, "
-                "wb_magnesium=:wb_magnesium, wb_total_alkalinity=:wb_total_alkalinity, wb_ph=:wb_ph, "
-                "wa_acid_name=:wa_acid_name, wa_acid_perc=:wa_acid_perc, wa_base_name=:wa_base_name, "
-		"json_fermentables=:json_fermentables, json_hops=:json_hops, json_miscs=:json_miscs, "
-                "json_yeasts=:json_yeasts, json_mashs=:json_mashs WHERE record = :recno");
-    	}
-	query.bindValue(":locked", recipe->locked ? 1:0);
-	query.bindValue(":st_name", recipe->st_name);
-	query.bindValue(":st_letter", recipe->st_letter);
-	query.bindValue(":st_guide", recipe->st_guide);
-	query.bindValue(":st_category", recipe->st_category);
-	query.bindValue(":st_catnr", recipe->st_category_number);
-	query.bindValue(":st_type", recipe->st_type);
-	query.bindValue(":st_og_min", round(recipe->st_og_min * 1000) / 1000);
-	query.bindValue(":st_og_max", round(recipe->st_og_max * 1000) / 1000);
-	query.bindValue(":st_fg_min", round(recipe->st_fg_min * 1000) / 1000);
-        query.bindValue(":st_fg_max", round(recipe->st_fg_max * 1000) / 1000);
-	query.bindValue(":st_ibu_min", round(recipe->st_ibu_min * 10) / 10);
-        query.bindValue(":st_ibu_max", round(recipe->st_ibu_max * 10) / 10);
-	query.bindValue(":st_color_min", round(recipe->st_color_min * 10) / 10);
-        query.bindValue(":st_color_max", round(recipe->st_color_max * 10) / 10);
-	query.bindValue(":st_carb_min", round(recipe->st_carb_min * 10) / 10);
-        query.bindValue(":st_carb_max", round(recipe->st_carb_max * 10) / 10);
-	query.bindValue(":st_abv_min", round(recipe->st_abv_min * 10) / 10);
-        query.bindValue(":st_abv_max", round(recipe->st_abv_max * 10) / 10);
-	query.bindValue(":name", recipe->name);
-	query.bindValue(":notes", recipe->notes);
-	query.bindValue(":type", recipe->type);
-	query.bindValue(":batch_size", round(recipe->batch_size * 10) / 10);
-	query.bindValue(":boil_size", round(recipe->boil_size * 10) / 10);
-	query.bindValue(":boil_time", round(recipe->boil_time * 10) / 10);
-	query.bindValue(":efficiency", round(recipe->efficiency * 10) / 10);
-	query.bindValue(":est_og", round(recipe->est_og * 1000) / 1000);
-	query.bindValue(":est_fg", round(recipe->est_fg * 1000) / 1000);
-	query.bindValue(":est_abv", round(recipe->est_abv * 10) / 10);
-	query.bindValue(":est_color", round(recipe->est_color * 10) / 10);
-	query.bindValue(":color_method", recipe->color_method);
-	query.bindValue(":est_ibu", round(recipe->est_ibu * 10) / 10);
-	query.bindValue(":ibu_method", recipe->ibu_method);
-	query.bindValue(":est_carb", round(recipe->est_carb * 10) / 10);
-	query.bindValue(":sparge_temp", round(recipe->sparge_temp * 10) / 10);
-	query.bindValue(":sparge_ph", round(recipe->sparge_ph * 100) / 100);
-	query.bindValue(":sparge_volume", round(recipe->sparge_volume * 10) / 10);
-	query.bindValue(":sparge_source", recipe->sparge_source);
-	query.bindValue(":sparge_acid_type", recipe->sparge_acid_type);
-	query.bindValue(":sparge_acid_perc", round(recipe->sparge_acid_perc * 10) / 10);
-	query.bindValue(":sparge_acid_amount", round(recipe->sparge_acid_amount * 100000) / 100000);
-	query.bindValue(":mash_ph", round(recipe->mash_ph * 100) / 100);
-	query.bindValue(":mash_name", recipe->mash_name);
-	query.bindValue(":calc_acid", recipe->calc_acid ?1:0);
-	query.bindValue(":w1_name", recipe->w1_name);
-	query.bindValue(":w1_amount", round(recipe->w1_amount * 10) / 10);
-	query.bindValue(":w1_calcium", round(recipe->w1_calcium * 100000) / 100000);
-	query.bindValue(":w1_sulfate", round(recipe->w1_sulfate * 100000) / 100000);
-	query.bindValue(":w1_chloride", round(recipe->w1_chloride * 100000) / 100000);
-	query.bindValue(":w1_sodium", round(recipe->w1_sodium * 100000) / 100000);
-	query.bindValue(":w1_magnesium", round(recipe->w1_magnesium * 100000) / 100000);
-	query.bindValue(":w1_total_alkalinity", round(recipe->w1_total_alkalinity * 100000) / 100000);
-	query.bindValue(":w1_ph", round(recipe->w1_ph * 100) / 100);
-	query.bindValue(":w1_cost", round(recipe->w1_cost * 100) / 100);
-	query.bindValue(":w2_name", recipe->w2_name);
-        query.bindValue(":w2_amount", round(recipe->w2_amount * 10) / 10);
-        query.bindValue(":w2_calcium", round(recipe->w2_calcium * 100000) / 100000);
-        query.bindValue(":w2_sulfate", round(recipe->w2_sulfate * 100000) / 100000);
-        query.bindValue(":w2_chloride", round(recipe->w2_chloride * 100000) / 100000);
-        query.bindValue(":w2_sodium", round(recipe->w2_sodium * 100000) / 100000);
-        query.bindValue(":w2_magnesium", round(recipe->w2_magnesium * 100000) / 100000);
-        query.bindValue(":w2_total_alkalinity", round(recipe->w2_total_alkalinity * 100000) / 100000);
-        query.bindValue(":w2_ph", round(recipe->w2_ph * 100) / 100);
-        query.bindValue(":w2_cost", round(recipe->w2_cost * 100) / 100);
-	query.bindValue(":wg_amount", round(recipe->wg_amount * 10) / 10);
-        query.bindValue(":wg_calcium", round(recipe->wg_calcium * 100000) / 100000);
-        query.bindValue(":wg_sulfate", round(recipe->wg_sulfate * 100000) / 100000);
-        query.bindValue(":wg_chloride", round(recipe->wg_chloride * 100000) / 100000);
-        query.bindValue(":wg_sodium", round(recipe->wg_sodium * 100000) / 100000);
-        query.bindValue(":wg_magnesium", round(recipe->wg_magnesium * 100000) / 100000);
-        query.bindValue(":wg_total_alkalinity", round(recipe->wg_total_alkalinity * 100000) / 100000);
-        query.bindValue(":wg_ph", round(recipe->wg_ph * 100) / 100);
-	query.bindValue(":wb_calcium", round(recipe->wb_calcium * 100000) / 100000);
-        query.bindValue(":wb_sulfate", round(recipe->wb_sulfate * 100000) / 100000);
-        query.bindValue(":wb_chloride", round(recipe->wb_chloride * 100000) / 100000);
-        query.bindValue(":wb_sodium", round(recipe->wb_sodium * 100000) / 100000);
-        query.bindValue(":wb_magnesium", round(recipe->wb_magnesium * 100000) / 100000);
-        query.bindValue(":wb_total_alkalinity", round(recipe->wb_total_alkalinity * 100000) / 100000);
-        query.bindValue(":wb_ph", round(recipe->wb_ph * 100) / 100);
-	query.bindValue(":wa_acid_name", recipe->wa_acid_name);
-	query.bindValue(":wa_acid_perc", round(recipe->wa_acid_perc * 10) / 10);
-	query.bindValue(":wa_base_name", recipe->wa_base_name);
-
-	if (recipe->fermentables.size() == 0) {
-	    query.bindValue(":json_fermentables", "[]");
-	} else {
-	    QJsonArray array;
-	    for (int i = 0; i < recipe->fermentables.size(); i++) {
-		QJsonObject obj;
-		obj.insert("f_name", recipe->fermentables.at(i).f_name);
-		obj.insert("f_origin", recipe->fermentables.at(i).f_origin);
-		obj.insert("f_supplier", recipe->fermentables.at(i).f_supplier);
-		obj.insert("f_amount", round(recipe->fermentables.at(i).f_amount * 10000) / 10000);
-		obj.insert("f_cost", round(recipe->fermentables.at(i).f_cost * 1000) / 1000);
-		obj.insert("f_type", recipe->fermentables.at(i).f_type);
-		obj.insert("f_yield", round(recipe->fermentables.at(i).f_yield * 10) / 10);
-		obj.insert("f_color", round(recipe->fermentables.at(i).f_color * 10) / 10);
-		obj.insert("f_coarse_fine_diff", round(recipe->fermentables.at(i).f_coarse_fine_diff * 10) / 10);
-		obj.insert("f_moisture", round(recipe->fermentables.at(i).f_moisture * 10) / 10);
-		obj.insert("f_diastatic_power", round(recipe->fermentables.at(i).f_diastatic_power * 100000) / 100000);
-		obj.insert("f_protein", round(recipe->fermentables.at(i).f_protein * 10) / 10);
-		obj.insert("f_dissolved_protein", round(recipe->fermentables.at(i).f_dissolved_protein * 10) / 10);
-		obj.insert("f_max_in_batch", recipe->fermentables.at(i).f_max_in_batch);
-		obj.insert("f_graintype", recipe->fermentables.at(i).f_graintype);
-		obj.insert("f_added", recipe->fermentables.at(i).f_added);
-		obj.insert("f_recommend_mash", recipe->fermentables.at(i).f_recommend_mash ? 1:0);
-		obj.insert("f_add_after_boil", recipe->fermentables.at(i).f_add_after_boil ? 1:0);
-		obj.insert("f_adjust_to_total_100", recipe->fermentables.at(i).f_adjust_to_total_100 ? 1:0);
-		obj.insert("f_percentage", round(recipe->fermentables.at(i).f_percentage * 10) / 10);
-		obj.insert("f_di_ph", round(recipe->fermentables.at(i).f_di_ph * 100000) / 100000);
-		obj.insert("f_acid_to_ph_57", round(recipe->fermentables.at(i).f_acid_to_ph_57 * 100000) / 100000);
-		qDebug() << "fermentables_Json" << i << obj;
-		array.append(obj);      /* Append this object */
-	    }
-	    QJsonDocument doc;
-	    doc.setArray(array);
-//	    qDebug() << doc.toJson(QJsonDocument::Compact);
-	    query.bindValue(":json_fermentables", doc.toJson(QJsonDocument::Compact));
-	}
-
-	if (recipe->hops.size() == 0) {
-            query.bindValue(":json_hops", "[]");
-        } else {
-            QJsonArray array;
-            for (int i = 0; i < recipe->hops.size(); i++) {
-                QJsonObject obj;
-		obj.insert("h_name", recipe->hops.at(i).h_name);
-                obj.insert("h_origin", recipe->hops.at(i).h_origin);
-		obj.insert("h_amount", round(recipe->hops.at(i).h_amount * 10000) / 10000);
-		obj.insert("h_cost", round(recipe->hops.at(i).h_cost * 100) / 100);
-		obj.insert("h_type", recipe->hops.at(i).h_type);
-		obj.insert("h_form", recipe->hops.at(i).h_form);
-		obj.insert("h_useat", recipe->hops.at(i).h_useat);
-		obj.insert("h_time", round(recipe->hops.at(i).h_time));
-		obj.insert("h_alpha", round(recipe->hops.at(i).h_alpha * 100) / 100);
-		obj.insert("h_beta", round(recipe->hops.at(i).h_beta * 100) / 100);
-		obj.insert("h_hsi", round(recipe->hops.at(i).h_hsi * 100) / 100);
-		obj.insert("h_humulene", round(recipe->hops.at(i).h_humulene * 100) / 100);
-		obj.insert("h_caryophyllene", round(recipe->hops.at(i).h_caryophyllene * 100) / 100);
-		obj.insert("h_cohumulone", round(recipe->hops.at(i).h_cohumulone * 100) / 100);
-		obj.insert("h_myrcene", round(recipe->hops.at(i).h_myrcene * 100) / 100);
-		obj.insert("h_total_oil", round(recipe->hops.at(i).h_total_oil * 100) / 100);
-		qDebug() << "hops_Json" << i << obj;
-                array.append(obj);      /* Append this object */
-            }
-	    QJsonDocument doc;
-            doc.setArray(array);
-            query.bindValue(":json_hops", doc.toJson(QJsonDocument::Compact));
+	recipe->record = this->recno;
+        if (DB_recipe::save(recipe, this)) {
+            /*
+             * If this was a new record, update the global recno.
+             */
+            this->recno = recipe->record;
         }
-
-	if (recipe->miscs.size() == 0) {
-            query.bindValue(":json_miscs", "[]");
-        } else {
-            QJsonArray array;
-            for (int i = 0; i < recipe->miscs.size(); i++) {
-                QJsonObject obj;
-		obj.insert("m_name", recipe->miscs.at(i).m_name);
-		obj.insert("m_amount", round(recipe->miscs.at(i).m_amount * 10000) / 10000);
-		obj.insert("m_type", recipe->miscs.at(i).m_type);
-		obj.insert("m_use_use", recipe->miscs.at(i).m_use_use);
-		obj.insert("m_time", round(recipe->miscs.at(i).m_time));
-		obj.insert("m_amount_is_weight", recipe->miscs.at(i).m_amount_is_weight ? 1:0);
-		obj.insert("m_cost", round(recipe->miscs.at(i).m_cost * 10000) / 10000);
-                qDebug() << "miscs_Json" << i << obj;
-                array.append(obj);      /* Append this object */
-            }
-	    QJsonDocument doc;
-            doc.setArray(array);
-//            qDebug() << doc.toJson(QJsonDocument::Compact);
-            query.bindValue(":json_miscs", doc.toJson(QJsonDocument::Compact));
-        }
-
-	if (recipe->yeasts.size() == 0) {
-            query.bindValue(":json_yeasts", "[]");
-        } else {
-            QJsonArray array;
-            for (int i = 0; i < recipe->yeasts.size(); i++) {
-                QJsonObject obj;
-		obj.insert("y_name", recipe->yeasts.at(i).y_name);
-		obj.insert("y_laboratory", recipe->yeasts.at(i).y_laboratory);
-		obj.insert("y_product_id", recipe->yeasts.at(i).y_product_id);
-		obj.insert("y_amount", round(recipe->yeasts.at(i).y_amount * 10000) / 10000);
-		obj.insert("y_type", recipe->yeasts.at(i).y_type);
-		obj.insert("y_form", recipe->yeasts.at(i).y_form);
-		obj.insert("y_min_temperature", round(recipe->yeasts.at(i).y_min_temperature * 10) / 10);
-		obj.insert("y_max_temperature", round(recipe->yeasts.at(i).y_max_temperature * 10) / 10);
-		obj.insert("y_flocculation", recipe->yeasts.at(i).y_flocculation);
-		obj.insert("y_attenuation", round(recipe->yeasts.at(i).y_attenuation * 10) / 10);
-		obj.insert("y_cells", recipe->yeasts.at(i).y_cells);
-		obj.insert("y_tolerance", round(recipe->yeasts.at(i).y_tolerance * 10) / 10);
-		obj.insert("y_inventory", round(recipe->yeasts.at(i).y_inventory * 10000) / 10000);
-		obj.insert("y_use", recipe->yeasts.at(i).y_use);
-		obj.insert("y_sta1", recipe->yeasts.at(i).y_sta1 ? 1:0);
-		obj.insert("y_bacteria", recipe->yeasts.at(i).y_bacteria ? 1:0);
-		obj.insert("y_harvest_top", recipe->yeasts.at(i).y_harvest_top ? 1:0);
-		obj.insert("y_harvest_time", recipe->yeasts.at(i).y_harvest_time);
-		obj.insert("y_pitch_temperature", round(recipe->yeasts.at(i).y_pitch_temperature * 10) / 10);
-		obj.insert("y_pofpos", recipe->yeasts.at(i).y_pofpos ? 1:0);
-		obj.insert("y_zymocide", recipe->yeasts.at(i).y_zymocide);
-		obj.insert("y_gr_hl_lo", recipe->yeasts.at(i).y_gr_hl_lo);
-		obj.insert("y_sg_lo", round(recipe->yeasts.at(i).y_sg_lo * 1000) / 1000);
-		obj.insert("y_gr_hl_hi", recipe->yeasts.at(i).y_gr_hl_hi);
-		obj.insert("y_sg_hi", round(recipe->yeasts.at(i).y_sg_hi * 1000) / 1000);
-		obj.insert("y_cost", round(recipe->yeasts.at(i).y_cost * 1000) / 1000);
-                qDebug() << "yeasts_Json" << i << obj;
-                array.append(obj);      /* Append this object */
-            }
-	    QJsonDocument doc;
-            doc.setArray(array);
-//            qDebug() << doc.toJson(QJsonDocument::Compact);
-            query.bindValue(":json_yeasts", doc.toJson(QJsonDocument::Compact));
-        }
-
-	if (recipe->mashs.size() == 0) {
-            query.bindValue(":json_mashs", "[]");
-	    qDebug() << "Saved empty mashs";
-        } else {
-            QJsonArray array;
-            for (int i = 0; i < recipe->mashs.size(); i++) {
-                QJsonObject obj;
-		obj.insert("step_name", recipe->mashs.at(i).step_name);
-		obj.insert("step_type", recipe->mashs.at(i).step_type);
-		obj.insert("step_volume", round(recipe->mashs.at(i).step_volume * 100) / 100);
-		obj.insert("step_infuse_amount", round(recipe->mashs.at(i).step_infuse_amount * 100) / 100);
-		obj.insert("step_infuse_temp", round(recipe->mashs.at(i).step_infuse_temp * 100) / 100);
-		obj.insert("step_temp", round(recipe->mashs.at(i).step_temp * 100) / 100);
-		obj.insert("step_time", round(recipe->mashs.at(i).step_time * 100) / 100);
-		obj.insert("ramp_time", round(recipe->mashs.at(i).ramp_time * 100) / 100);
-		obj.insert("end_temp", round(recipe->mashs.at(i).end_temp * 100) / 100);
-		obj.insert("step_wg_ratio", round(recipe->mashs.at(i).step_wg_ratio * 100) / 100);
-                qDebug() << "mashs_Json" << i << obj;
-                array.append(obj);      /* Append this object */
-            }
-	    QJsonDocument doc;
-            doc.setArray(array);
-            qDebug() << doc.toJson(QJsonDocument::Compact);
-            query.bindValue(":json_mashs", doc.toJson(QJsonDocument::Compact));
-        }
-
-	if (this->recno == -1) {
-	    query.bindValue(":uuid", QUuid::createUuid().toString().mid(1, 36));
-	} else {
-	    query.bindValue(":recno", this->recno);
-	}
-	query.exec();
-	qDebug() << query.lastQuery();
-	if (query.lastError().isValid()) {
-	    qDebug() << "EditRecipe" << query.lastError();
-	    QMessageBox::warning(this, tr("Database error"),
-                        tr("MySQL error: %1\n%2\n%3")
-                        .arg(query.lastError().nativeErrorCode())
-                        .arg(query.lastError().driverText())
-                        .arg(query.lastError().databaseText()));
-	} else {
-	    /*
-	     * If this was a new recipe, find out what record number we
-	     * have got and set it. So when the user saves this record
-	     * again, it will be updated instead of inserting a new copy.
-	     */
-	    if (this->recno < 0) {
-		QVariant id = query.lastInsertId();
-		this->recno = recipe->record = id.toInt();
-		qDebug() << "EditRecipe Inserted record" << this->recno;
-	    } else {
-		qDebug() << "EditRecipe Updated record" << this->recno;
-	    }
-	}
     }
 
     ui->saveButton->setEnabled(false);
@@ -1145,26 +499,12 @@
 
 void EditRecipe::on_deleteButton_clicked()
 {
-    QSqlQuery query;
-
     int rc = QMessageBox::warning(this, tr("Delete recipe"), tr("Delete %1").arg(recipe->name),
                     QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
     if (rc == QMessageBox::No)
         return;
 
-    query.prepare("DELETE FROM recipes WHERE record = :recno");
-    query.bindValue(":recno", this->recno);
-    query.exec();
-    if (query.lastError().isValid()) {
-	qDebug() << "EditRecipe" << query.lastError();
-	QMessageBox::warning(this, tr("Database error"),
-                        tr("MySQL error: %1\n%2\n%3")
-                        .arg(query.lastError().nativeErrorCode())
-                        .arg(query.lastError().driverText())
-                        .arg(query.lastError().databaseText()));
-    } else {
-	qDebug() << "EditRecipe Deleted" << this->recno;
-    }
+    DB_recipe::dele(this, this->recno);
 
     this->close();
     this->setResult(1);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/database/db_recipe.cpp	Wed Jun 01 21:46:26 2022 +0200
@@ -0,0 +1,697 @@
+/**
+ * db_recipe.cpp is part of bmsapp.
+ *
+ * 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/>.
+ */
+
+#include "global.h"
+#include "db_recipe.h"
+
+
+bool DB_recipe::load(Recipe *reci, QDialog *dialog, int recno)
+{
+    QSqlQuery query, yquery;
+
+    query.prepare("SELECT * FROM recipes WHERE record = :recno");
+    query.bindValue(":recno", recno);
+    query.exec();
+    if (! query.first()) {
+	qDebug() << "loadRecipe seek error record" << recno;
+	QMessageBox::warning(dialog, QObject::tr("Database error"), QObject::tr("MySQL error: record %1 not found").arg(recno));
+	dialog->done(QDialog::Rejected);	// At this stage, this doesn't work because the dialog is not yet visible.
+	return false;
+    }
+
+//    QSqlRecord rec = query.record();
+//    for (int i = 0; i < rec.count(); i++)
+//	qDebug() << i << rec.fieldName(i) << query.value(i);
+
+    reci->record = query.value("record").toInt();
+    reci->uuid = query.value("uuid").toString();
+    reci->locked = query.value("locked").toInt() ? true:false;
+
+    reci->st_name = query.value("st_name").toString();
+    reci->st_letter = query.value("st_letter").toString();
+    reci->st_guide = query.value("st_guide").toString();
+    reci->st_category = query.value("st_category").toString();
+    reci->st_category_number = query.value("st_category_number").toInt();
+    reci->st_type = query.value("st_type").toInt();
+    reci->st_og_min = query.value("st_og_min").toDouble();
+    reci->st_og_max = query.value("st_og_max").toDouble();
+    reci->st_fg_min = query.value("st_fg_min").toDouble();
+    reci->st_fg_max = query.value("st_fg_max").toDouble();
+    reci->st_ibu_min = query.value("st_ibu_min").toDouble();
+    reci->st_ibu_max = query.value("st_ibu_max").toDouble();
+    reci->st_color_min = query.value("st_color_min").toDouble();
+    reci->st_color_max = query.value("st_color_max").toDouble();
+    reci->st_carb_min = query.value("st_carb_min").toDouble();
+    reci->st_carb_max = query.value("st_carb_max").toDouble();
+    reci->st_abv_min = query.value("st_abv_min").toDouble();
+    reci->st_abv_max = query.value("st_abv_max").toDouble();
+
+    reci->name = query.value("name").toString();
+    reci->notes = query.value("notes").toString();
+    reci->type = query.value("type").toInt();
+    reci->batch_size = query.value("batch_size").toDouble();
+    reci->boil_size = query.value("boil_size").toDouble();
+    reci->boil_time = query.value("boil_time").toDouble();
+    reci->efficiency = query.value("efficiency").toDouble();
+    reci->est_og = query.value("est_og").toDouble();
+    reci->est_fg = query.value("est_fg").toDouble();
+    reci->est_abv = query.value("est_abv").toDouble();
+    reci->est_color = query.value("est_color").toDouble();
+    reci->color_method = query.value("color_method").toInt();
+    reci->est_ibu = query.value("est_ibu").toDouble();
+    reci->ibu_method = query.value("ibu_method").toInt();
+    reci->est_carb = query.value("est_carb").toDouble();
+
+    reci->sparge_temp = query.value("sparge_temp").toDouble();
+    reci->sparge_ph = query.value("sparge_ph").toDouble();
+    reci->sparge_volume = query.value("sparge_volume").toDouble();
+    reci->sparge_source = query.value("sparge_source").toInt();
+    reci->sparge_acid_type = query.value("sparge_acid_type").toInt();
+    reci->sparge_acid_perc = query.value("sparge_acid_perc").toDouble();
+    reci->sparge_acid_amount = query.value("sparge_acid_amount").toDouble();
+    reci->mash_ph = query.value("mash_ph").toDouble();
+    reci->mash_name = query.value("mash_name").toString();
+    reci->calc_acid = query.value("calc_acid").toInt() ? true:false;
+
+    reci->w1_name = query.value("w1_name").toString();
+    reci->w1_amount = query.value("w1_amount").toDouble();
+    reci->w1_calcium = query.value("w1_calcium").toDouble();
+    reci->w1_sulfate = query.value("w1_sulfate").toDouble();
+    reci->w1_chloride = query.value("w1_chloride").toDouble();
+    reci->w1_sodium = query.value("w1_sodium").toDouble();
+    reci->w1_magnesium = query.value("w1_magnesium").toDouble();
+    reci->w1_total_alkalinity = query.value("w1_total_alkalinity").toDouble();
+    reci->w1_ph = query.value("w1_ph").toDouble();
+    reci->w1_cost = query.value("w1_cost").toDouble();
+    reci->w2_name = query.value("w2_name").toString();
+    reci->w2_amount = query.value("w2_amount").toDouble();
+    reci->w2_calcium = query.value("w2_calcium").toDouble();
+    reci->w2_sulfate = query.value("w2_sulfate").toDouble();
+    reci->w2_chloride = query.value("w2_chloride").toDouble();
+    reci->w2_sodium = query.value("w2_sodium").toDouble();
+    reci->w2_magnesium = query.value("w2_magnesium").toDouble();
+    reci->w2_total_alkalinity = query.value("w2_total_alkalinity").toDouble();
+    reci->w2_ph = query.value("w2_ph").toDouble();
+    reci->w2_cost = query.value("w2_cost").toDouble();
+    reci->wg_amount = query.value("wg_amount").toDouble();
+    reci->wg_calcium = query.value("wg_calcium").toDouble();
+    reci->wg_sulfate = query.value("wg_sulfate").toDouble();
+    reci->wg_chloride = query.value("wg_chloride").toDouble();
+    reci->wg_sodium = query.value("wg_sodium").toDouble();
+    reci->wg_magnesium = query.value("wg_magnesium").toDouble();
+    reci->wg_total_alkalinity = query.value("wg_total_alkalinity").toDouble();
+    reci->wg_ph = query.value("wg_ph").toDouble();
+    reci->wb_calcium = query.value("wb_calcium").toDouble();
+    reci->wb_sulfate = query.value("wb_sulfate").toDouble();
+    reci->wb_chloride = query.value("wb_chloride").toDouble();
+    reci->wb_sodium = query.value("wb_sodium").toDouble();
+    reci->wb_magnesium = query.value("wb_magnesium").toDouble();
+    reci->wb_total_alkalinity = query.value("wb_total_alkalinity").toDouble();
+    reci->wb_ph = query.value("wb_ph").toDouble();
+    reci->wa_acid_name = query.value("wa_acid_name").toInt();
+    reci->wa_acid_perc = query.value("wa_acid_perc").toDouble();
+    reci->wa_base_name = query.value("wa_base_name").toInt();
+
+    QJsonParseError parseError;
+    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) {
+            qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
+	} else if (fermentables.isArray()) {
+	    double percentcheck = 0;
+	    for (int i = 0; i < fermentables.array().size(); i++) {
+		QJsonObject obj = fermentables.array().at(i).toObject();
+		Fermentables f;
+		f.f_name = obj["f_name"].toString();
+		f.f_origin = obj["f_origin"].toString();
+		f.f_supplier = obj["f_supplier"].toString();
+		f.f_amount = obj["f_amount"].toDouble();
+		f.f_cost = obj["f_cost"].toDouble();
+		f.f_type = obj["f_type"].toInt();
+		f.f_yield = obj["f_yield"].toDouble();
+		f.f_color = obj["f_color"].toDouble();
+		f.f_coarse_fine_diff = obj["f_coarse_fine_diff"].toDouble();
+		f.f_moisture = obj["f_moisture"].toDouble();
+		f.f_diastatic_power = obj["f_diastatic_power"].toDouble();
+		f.f_protein = obj["f_protein"].toDouble();
+		f.f_dissolved_protein = obj["f_dissolved_protein"].toDouble();
+		f.f_max_in_batch = obj["f_max_in_batch"].toDouble();
+		f.f_graintype = obj["f_graintype"].toInt();
+		f.f_added = obj["f_added"].toInt();
+		f.f_recommend_mash = obj["f_recommend_mash"].toInt() ? true:false;
+		f.f_add_after_boil = obj["f_add_after_boil"].toInt() ? true:false;
+		f.f_adjust_to_total_100 = obj["f_adjust_to_total_100"].toInt() ? true:false;
+		f.f_percentage = obj["f_percentage"].toDouble();
+		f.f_di_ph = obj["f_di_ph"].toDouble();
+		f.f_acid_to_ph_57 = obj["f_acid_to_ph_57"].toDouble();
+		if (f.f_adjust_to_total_100)
+		    reci->fermentables_use100 = true;
+		percentcheck += f.f_percentage;
+		reci->fermentables.append(f);
+	    }
+	    if (percentcheck == 0) {
+		/* Missing percentages, fix it. */
+		double total = 0;
+		for (int i = 0; i < reci->fermentables.size(); i++) {
+			if (reci->fermentables.at(i).f_added < 4)
+			    total += reci->fermentables.at(i).f_amount;
+		}
+		for (int i = 0; i < reci->fermentables.size(); i++) {
+			if (reci->fermentables.at(i).f_added < 4)
+			    reci->fermentables[i].f_percentage = round((reci->fermentables.at(i).f_amount / total) * 10000.0) / 100.0;
+		}
+		qDebug() << " fixed missing percentages";
+	    }
+	}
+    } else {
+	qDebug() << "empty fermentables";
+    }
+
+    const auto& h_json = query.value("json_hops").toString();
+    if (!h_json.trimmed().isEmpty()) {
+        const auto& formattedJson = QString("%1").arg(h_json);
+        QJsonDocument hops = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError);
+        if (parseError.error != QJsonParseError::NoError) {
+            qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset;
+	} else if (hops.isArray()) {
+	    for (int i = 0; i < hops.array().size(); i++) {
+		QJsonObject obj = hops.array().at(i).toObject();
+		Hops h;
+		h.h_name = obj["h_name"].toString();
+		h.h_origin = obj["h_origin"].toString();
+		h.h_amount = obj["h_amount"].toDouble();
+		h.h_cost = obj["h_cost"].toDouble();
+		h.h_type = obj["h_type"].toInt();
+		h.h_form = obj["h_form"].toInt();
+		h.h_useat = obj["h_useat"].toInt();
+		h.h_time = obj["h_time"].toInt();
+		h.h_alpha = obj["h_alpha"].toDouble();
+		h.h_beta = obj["h_beta"].toDouble();
+		h.h_hsi = obj["h_hsi"].toDouble();
+		h.h_humulene = obj["h_humulene"].toDouble();
+		h.h_caryophyllene = obj["h_caryophyllene"].toDouble();
+		h.h_cohumulone = obj["h_cohumulone"].toDouble();
+		h.h_myrcene = obj["h_myrcene"].toDouble();
+		h.h_total_oil = obj["h_total_oil"].toDouble();
+		reci->hops.append(h);
+	    }
+	}
+    } else {
+	qDebug() << "empty hops";
+    }
+
+    const auto& m_json = query.value("json_miscs").toString();
+    if (!m_json.trimmed().isEmpty()) {
+	const auto& formattedJson = QString("%1").arg(m_json);
+	QJsonDocument miscs = QJsonDocument::fromJson(formattedJson.toUtf8(), &parseError);
+	if (parseError.error != QJsonParseError::NoError) {
+	    qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset;
+	} else if (miscs.isArray()) {
+	    for (int i = 0; i < miscs.array().size(); i++) {
+		QJsonObject obj = miscs.array().at(i).toObject();
+		Miscs m;
+		m.m_name = obj["m_name"].toString();
+		m.m_amount = obj["m_amount"].toDouble();
+		m.m_type = obj["m_type"].toInt();
+		m.m_use_use = obj["m_use_use"].toInt();
+		m.m_time = obj["m_time"].toDouble();
+		m.m_amount_is_weight = obj["m_amount_is_weight"].toInt() ? true:false;
+		m.m_cost = obj["m_cost"].toDouble();
+		reci->miscs.append(m);
+	    }
+	}
+    } else {
+	qDebug() << "empty miscs";
+    }
+
+    const auto& y_json = query.value("json_yeasts").toString();
+    if (!y_json.trimmed().isEmpty()) {
+	const auto& formattedJson = QString("%1").arg(y_json);
+	QJsonDocument yeasts = QJsonDocument::fromJson(formattedJson.toUtf8(),  &parseError);
+	if (parseError.error != QJsonParseError::NoError) {
+	    qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
+	} else if (yeasts.isArray()) {
+	    for (int i = 0; i < yeasts.array().size(); i++) {
+		QJsonObject obj = yeasts.array().at(i).toObject();
+		Yeasts y;
+		/* First some defaults for newer fields. */
+		y.y_tolerance = y.y_harvest_time = y.y_pitch_temperature = y.y_zymocide = 0;
+                y.y_sta1 = y.y_bacteria = y.y_harvest_top = y.y_pofpos = false;
+		y.y_gr_hl_lo = 50;
+                y.y_sg_lo = 1.04;
+                y.y_gr_hl_hi = 80;
+                y.y_sg_hi = 1.08;
+		/* Now get what we have got */
+		y.y_name = obj["y_name"].toString();
+		y.y_laboratory = obj["y_laboratory"].toString();
+		y.y_product_id = obj["y_product_id"].toString();
+		y.y_amount = obj["y_amount"].toDouble();
+		y.y_type = obj["y_type"].toInt();
+		y.y_form = obj["y_form"].toInt();
+		y.y_min_temperature = obj["y_min_temperature"].toDouble();
+		y.y_max_temperature = obj["y_max_temperature"].toDouble();
+		y.y_flocculation = obj["y_flocculation"].toInt();
+		y.y_attenuation = obj["y_attenuation"].toDouble();
+		y.y_cells = obj["y_cells"].toDouble();
+		y.y_tolerance = obj["y_tolerance"].toDouble();
+		y.y_inventory = obj["y_inventory"].toDouble();
+		y.y_use = obj["y_use"].toInt();
+		y.y_sta1 = obj["y_sta1"].toInt() ? true:false;
+		y.y_bacteria = obj["y_bacteria"].toInt() ? true:false;
+		y.y_harvest_top = obj["y_harvest_top"].toInt() ? true:false;
+		y.y_harvest_time = obj["y_harvest_time"].toInt();
+		y.y_pitch_temperature = obj["y_pitch_temperature"].toDouble();
+		y.y_pofpos = obj["y_pofpos"].toInt() ? true:false;
+		y.y_zymocide = obj["y_zymocide"].toInt();
+		y.y_gr_hl_lo = obj["y_gr_hl_lo"].toInt();
+		y.y_sg_lo = obj["y_sg_lo"].toDouble();
+		y.y_gr_hl_hi = obj["y_gr_hl_hi"].toInt();
+		y.y_sg_hi = obj["y_sg_hi"].toDouble();
+		y.y_cost = obj["y_cost"].toDouble();
+
+		if (y.y_tolerance == 0 || y.y_cells == 0) {	// More and better tests?
+		    /*
+		     * Possible data upgrade needed.
+		     */
+		    yquery.prepare("SELECT tolerance,cells,sta1,bacteria,harvest_top,harvest_time,pitch_temperature,"
+				       "pofpos,zymocide,gr_hl_lo,sg_lo,gr_hl_hi,sg_hi "
+				       "FROM inventory_yeasts WHERE name=:name AND laboratory=:laboratory AND product_id=:product_id");
+		    yquery.bindValue(":name", y.y_name);
+		    yquery.bindValue(":laboratory", y.y_laboratory);
+		    yquery.bindValue(":product_id", y.y_product_id);
+            	    yquery.exec();
+            	    if (yquery.first()) {
+			y.y_tolerance = yquery.value(0).toDouble();
+			y.y_cells = yquery.value(1).toDouble();
+			y.y_sta1 = yquery.value(2).toInt() ? true:false;
+			y.y_bacteria = yquery.value(3).toInt() ? true:false;
+			y.y_harvest_top = yquery.value(4).toInt() ? true:false;
+			y.y_harvest_time = yquery.value(5).toInt();
+			y.y_pitch_temperature = yquery.value(6).toDouble();
+			y.y_pofpos = yquery.value(7).toInt() ? true:false;
+			y.y_zymocide = yquery.value(8).toInt();
+			y.y_gr_hl_lo = yquery.value(9).toInt();
+			y.y_sg_lo = yquery.value(10).toDouble();
+			y.y_gr_hl_hi = yquery.value(11).toInt();
+			y.y_sg_hi = yquery.value(12).toDouble();
+		    } else {
+			qDebug() << y.y_name << y.y_product_id << "not found for upgrade";
+		    }
+		}
+		reci->yeasts.append(y);
+	    }
+	}
+    } else {
+	qDebug() << "empty yeasts";
+    }
+
+    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) {
+	    qDebug() << "Parse error: " << parseError.errorString() << "at" << parseError.offset ;
+	} else if (mashs.isArray()) {
+	    for (int i = 0; i < mashs.array().size(); i++) {
+            	QJsonObject obj = mashs.array().at(i).toObject();
+		Mashs m;
+		m.step_name = obj["step_name"].toString();
+		m.step_type = obj["step_type"].toInt();
+		m.step_volume = obj["step_volume"].toDouble();
+		m.step_infuse_amount = obj["step_infuse_amount"].toDouble();
+		m.step_infuse_temp = obj["step_infuse_temp"].toDouble();
+		m.step_temp = obj["step_temp"].toDouble();
+		m.step_time = obj["step_time"].toDouble();
+		m.ramp_time = obj["ramp_time"].toDouble();
+		m.end_temp = obj["end_temp"].toDouble();
+		m.step_wg_ratio = obj["step_wg_ratio"].toDouble();
+		reci->mashs.append(m);
+	    }
+	}
+    } else {
+	qDebug() << "empty mashs";
+    }
+
+    qDebug() << "loadRecipe" << reci->record << "done";
+    return true;
+}
+
+
+bool DB_recipe::save(Recipe *reci, QDialog *dialog)
+{
+    QSqlQuery query;
+
+    if (reci->record == -1) {
+	query.prepare("INSERT INTO recipes SET locked=:locked, st_name=:st_name, st_letter=:st_letter, "
+		"st_guide=:st_guide, st_category=:st_category, st_category_number=:st_catnr, st_type=:st_type, "
+		"st_og_min=:st_og_min, st_og_max=:st_og_max, st_fg_min=:st_fg_min, st_fg_max=:st_fg_max, "
+		"st_ibu_min=:st_ibu_min, st_ibu_max=:st_ibu_max, st_color_min=:st_color_min, st_color_max=:st_color_max, "
+		"st_carb_min=:st_carb_min, st_carb_max=:st_carb_max, st_abv_min=:st_abv_min, st_abv_max=:st_abv_max, "
+		"name=:name, notes=:notes, type=:type, batch_size=:batch_size, boil_size=:boil_size, "
+		"boil_time=:boil_time, efficiency=:efficiency, est_og=:est_og, est_fg=:est_fg, est_abv=:est_abv, "
+		"est_color=:est_color, color_method=:color_method, est_ibu=:est_ibu, ibu_method=:ibu_method, "
+		"est_carb=:est_carb, sparge_temp=:sparge_temp, sparge_ph=:sparge_ph, "
+		"sparge_volume=:sparge_volume, sparge_source=:sparge_source, sparge_acid_type=:sparge_acid_type, "
+		"sparge_acid_perc=:sparge_acid_perc, sparge_acid_amount=:sparge_acid_amount, mash_ph=:mash_ph, "
+		"mash_name=:mash_name, calc_acid=:calc_acid, "
+		"w1_name=:w1_name, w1_amount=:w1_amount, w1_calcium=:w1_calcium, w1_sulfate=:w1_sulfate, "
+		"w1_chloride=:w1_chloride, w1_sodium=:w1_sodium, w1_magnesium=:w1_magnesium, "
+		"w1_total_alkalinity=:w1_total_alkalinity, w1_ph=:w1_ph, w1_cost=:w1_cost, "
+		"w2_name=:w2_name, w2_amount=:w2_amount, w2_calcium=:w2_calcium, w2_sulfate=:w2_sulfate, "
+                "w2_chloride=:w2_chloride, w2_sodium=:w2_sodium, w2_magnesium=:w2_magnesium, "
+                "w2_total_alkalinity=:w2_total_alkalinity, w2_ph=:w2_ph, w2_cost=:w2_cost, "
+		"wg_amount=:wg_amount, wg_calcium=:wg_calcium, wg_sulfate=:wg_sulfate, "
+                "wg_chloride=:wg_chloride, wg_sodium=:wg_sodium, wg_magnesium=:wg_magnesium, "
+                "wg_total_alkalinity=:wg_total_alkalinity, wg_ph=:wg_ph, "
+		"wb_calcium=:wb_calcium, wb_sulfate=:wb_sulfate, wb_chloride=:wb_chloride, wb_sodium=:wb_sodium, "
+		"wb_magnesium=:wb_magnesium, wb_total_alkalinity=:wb_total_alkalinity, wb_ph=:wb_ph, "
+		"wa_acid_name=:wa_acid_name, wa_acid_perc=:wa_acid_perc, wa_base_name=:wa_base_name, "
+		"json_fermentables=:json_fermentables, json_hops=:json_hops, json_miscs=:json_miscs, "
+		"json_yeasts=:json_yeasts, json_mashs=:json_mashs, uuid=:uuid");
+    } else {
+	query.prepare("UPDATE recipes SET locked=:locked, st_name=:st_name, st_letter=:st_letter, "
+		"st_guide=:st_guide, st_category=:st_category, st_category_number=:st_catnr, st_type=:st_type, "
+                "st_og_min=:st_og_min, st_og_max=:st_og_max, st_fg_min=:st_fg_min, st_fg_max=:st_fg_max, "
+                "st_ibu_min=:st_ibu_min, st_ibu_max=:st_ibu_max, st_color_min=:st_color_min, st_color_max=:st_color_max, "
+                "st_carb_min=:st_carb_min, st_carb_max=:st_carb_max, st_abv_min=:st_abv_min, st_abv_max=:st_abv_max, "
+                "name=:name, notes=:notes, type=:type, batch_size=:batch_size, boil_size=:boil_size, "
+		"boil_time=:boil_time, efficiency=:efficiency, est_og=:est_og, est_fg=:est_fg, est_abv=:est_abv, "
+                "est_color=:est_color, color_method=:color_method, est_ibu=:est_ibu, ibu_method=:ibu_method, "
+                "est_carb=:est_carb, sparge_temp=:sparge_temp, sparge_ph=:sparge_ph, "
+		"sparge_volume=:sparge_volume, sparge_source=:sparge_source, sparge_acid_type=:sparge_acid_type, "
+                "sparge_acid_perc=:sparge_acid_perc, sparge_acid_amount=:sparge_acid_amount, mash_ph=:mash_ph, "
+                "mash_name=:mash_name, calc_acid=:calc_acid, "
+                "w1_name=:w1_name, w1_amount=:w1_amount, w1_calcium=:w1_calcium, w1_sulfate=:w1_sulfate, "
+                "w1_chloride=:w1_chloride, w1_sodium=:w1_sodium, w1_magnesium=:w1_magnesium, "
+                "w1_total_alkalinity=:w1_total_alkalinity, w1_ph=:w1_ph, w1_cost=:w1_cost, "
+                "w2_name=:w2_name, w2_amount=:w2_amount, w2_calcium=:w2_calcium, w2_sulfate=:w2_sulfate, "
+                "w2_chloride=:w2_chloride, w2_sodium=:w2_sodium, w2_magnesium=:w2_magnesium, "
+                "w2_total_alkalinity=:w2_total_alkalinity, w2_ph=:w2_ph, w2_cost=:w2_cost, "
+                "wg_amount=:wg_amount, wg_calcium=:wg_calcium, wg_sulfate=:wg_sulfate, "
+                "wg_chloride=:wg_chloride, wg_sodium=:wg_sodium, wg_magnesium=:wg_magnesium, "
+                "wg_total_alkalinity=:wg_total_alkalinity, wg_ph=:wg_ph, "
+                "wb_calcium=:wb_calcium, wb_sulfate=:wb_sulfate, wb_chloride=:wb_chloride, wb_sodium=:wb_sodium, "
+                "wb_magnesium=:wb_magnesium, wb_total_alkalinity=:wb_total_alkalinity, wb_ph=:wb_ph, "
+                "wa_acid_name=:wa_acid_name, wa_acid_perc=:wa_acid_perc, wa_base_name=:wa_base_name, "
+		"json_fermentables=:json_fermentables, json_hops=:json_hops, json_miscs=:json_miscs, "
+                "json_yeasts=:json_yeasts, json_mashs=:json_mashs WHERE record = :recno");
+    }
+    query.bindValue(":locked", reci->locked ? 1:0);
+    query.bindValue(":st_name", reci->st_name);
+    query.bindValue(":st_letter", reci->st_letter);
+    query.bindValue(":st_guide", reci->st_guide);
+    query.bindValue(":st_category", reci->st_category);
+    query.bindValue(":st_catnr", reci->st_category_number);
+    query.bindValue(":st_type", reci->st_type);
+    query.bindValue(":st_og_min", round(reci->st_og_min * 1000) / 1000);
+    query.bindValue(":st_og_max", round(reci->st_og_max * 1000) / 1000);
+    query.bindValue(":st_fg_min", round(reci->st_fg_min * 1000) / 1000);
+    query.bindValue(":st_fg_max", round(reci->st_fg_max * 1000) / 1000);
+    query.bindValue(":st_ibu_min", round(reci->st_ibu_min * 10) / 10);
+    query.bindValue(":st_ibu_max", round(reci->st_ibu_max * 10) / 10);
+    query.bindValue(":st_color_min", round(reci->st_color_min * 10) / 10);
+    query.bindValue(":st_color_max", round(reci->st_color_max * 10) / 10);
+    query.bindValue(":st_carb_min", round(reci->st_carb_min * 10) / 10);
+    query.bindValue(":st_carb_max", round(reci->st_carb_max * 10) / 10);
+    query.bindValue(":st_abv_min", round(reci->st_abv_min * 10) / 10);
+    query.bindValue(":st_abv_max", round(reci->st_abv_max * 10) / 10);
+    query.bindValue(":name", reci->name);
+    query.bindValue(":notes", reci->notes);
+    query.bindValue(":type", reci->type);
+    query.bindValue(":batch_size", round(reci->batch_size * 10) / 10);
+    query.bindValue(":boil_size", round(reci->boil_size * 10) / 10);
+    query.bindValue(":boil_time", round(reci->boil_time * 10) / 10);
+    query.bindValue(":efficiency", round(reci->efficiency * 10) / 10);
+    query.bindValue(":est_og", round(reci->est_og * 1000) / 1000);
+    query.bindValue(":est_fg", round(reci->est_fg * 1000) / 1000);
+    query.bindValue(":est_abv", round(reci->est_abv * 10) / 10);
+    query.bindValue(":est_color", round(reci->est_color * 10) / 10);
+    query.bindValue(":color_method", reci->color_method);
+    query.bindValue(":est_ibu", round(reci->est_ibu * 10) / 10);
+    query.bindValue(":ibu_method", reci->ibu_method);
+    query.bindValue(":est_carb", round(reci->est_carb * 10) / 10);
+    query.bindValue(":sparge_temp", round(reci->sparge_temp * 10) / 10);
+    query.bindValue(":sparge_ph", round(reci->sparge_ph * 100) / 100);
+    query.bindValue(":sparge_volume", round(reci->sparge_volume * 10) / 10);
+    query.bindValue(":sparge_source", reci->sparge_source);
+    query.bindValue(":sparge_acid_type", reci->sparge_acid_type);
+    query.bindValue(":sparge_acid_perc", round(reci->sparge_acid_perc * 10) / 10);
+    query.bindValue(":sparge_acid_amount", round(reci->sparge_acid_amount * 100000) / 100000);
+    query.bindValue(":mash_ph", round(reci->mash_ph * 100) / 100);
+    query.bindValue(":mash_name", reci->mash_name);
+    query.bindValue(":calc_acid", reci->calc_acid ?1:0);
+    query.bindValue(":w1_name", reci->w1_name);
+    query.bindValue(":w1_amount", round(reci->w1_amount * 10) / 10);
+    query.bindValue(":w1_calcium", round(reci->w1_calcium * 100000) / 100000);
+    query.bindValue(":w1_sulfate", round(reci->w1_sulfate * 100000) / 100000);
+    query.bindValue(":w1_chloride", round(reci->w1_chloride * 100000) / 100000);
+    query.bindValue(":w1_sodium", round(reci->w1_sodium * 100000) / 100000);
+    query.bindValue(":w1_magnesium", round(reci->w1_magnesium * 100000) / 100000);
+    query.bindValue(":w1_total_alkalinity", round(reci->w1_total_alkalinity * 100000) / 100000);
+    query.bindValue(":w1_ph", round(reci->w1_ph * 100) / 100);
+    query.bindValue(":w1_cost", round(reci->w1_cost * 100) / 100);
+    query.bindValue(":w2_name", reci->w2_name);
+    query.bindValue(":w2_amount", round(reci->w2_amount * 10) / 10);
+    query.bindValue(":w2_calcium", round(reci->w2_calcium * 100000) / 100000);
+    query.bindValue(":w2_sulfate", round(reci->w2_sulfate * 100000) / 100000);
+    query.bindValue(":w2_chloride", round(reci->w2_chloride * 100000) / 100000);
+    query.bindValue(":w2_sodium", round(reci->w2_sodium * 100000) / 100000);
+    query.bindValue(":w2_magnesium", round(reci->w2_magnesium * 100000) / 100000);
+    query.bindValue(":w2_total_alkalinity", round(reci->w2_total_alkalinity * 100000) / 100000);
+    query.bindValue(":w2_ph", round(reci->w2_ph * 100) / 100);
+    query.bindValue(":w2_cost", round(reci->w2_cost * 100) / 100);
+    query.bindValue(":wg_amount", round(reci->wg_amount * 10) / 10);
+    query.bindValue(":wg_calcium", round(reci->wg_calcium * 100000) / 100000);
+    query.bindValue(":wg_sulfate", round(reci->wg_sulfate * 100000) / 100000);
+    query.bindValue(":wg_chloride", round(reci->wg_chloride * 100000) / 100000);
+    query.bindValue(":wg_sodium", round(reci->wg_sodium * 100000) / 100000);
+    query.bindValue(":wg_magnesium", round(reci->wg_magnesium * 100000) / 100000);
+    query.bindValue(":wg_total_alkalinity", round(reci->wg_total_alkalinity * 100000) / 100000);
+    query.bindValue(":wg_ph", round(reci->wg_ph * 100) / 100);
+    query.bindValue(":wb_calcium", round(reci->wb_calcium * 100000) / 100000);
+    query.bindValue(":wb_sulfate", round(reci->wb_sulfate * 100000) / 100000);
+    query.bindValue(":wb_chloride", round(reci->wb_chloride * 100000) / 100000);
+    query.bindValue(":wb_sodium", round(reci->wb_sodium * 100000) / 100000);
+    query.bindValue(":wb_magnesium", round(reci->wb_magnesium * 100000) / 100000);
+    query.bindValue(":wb_total_alkalinity", round(reci->wb_total_alkalinity * 100000) / 100000);
+    query.bindValue(":wb_ph", round(reci->wb_ph * 100) / 100);
+    query.bindValue(":wa_acid_name", reci->wa_acid_name);
+    query.bindValue(":wa_acid_perc", round(reci->wa_acid_perc * 10) / 10);
+    query.bindValue(":wa_base_name", reci->wa_base_name);
+
+    if (reci->fermentables.size() == 0) {
+	query.bindValue(":json_fermentables", "[]");
+    } else {
+	QJsonArray array;
+	for (int i = 0; i < reci->fermentables.size(); i++) {
+		QJsonObject obj;
+		obj.insert("f_name", reci->fermentables.at(i).f_name);
+		obj.insert("f_origin", reci->fermentables.at(i).f_origin);
+		obj.insert("f_supplier", reci->fermentables.at(i).f_supplier);
+		obj.insert("f_amount", round(reci->fermentables.at(i).f_amount * 10000) / 10000);
+		obj.insert("f_cost", round(reci->fermentables.at(i).f_cost * 1000) / 1000);
+		obj.insert("f_type", reci->fermentables.at(i).f_type);
+		obj.insert("f_yield", round(reci->fermentables.at(i).f_yield * 10) / 10);
+		obj.insert("f_color", round(reci->fermentables.at(i).f_color * 10) / 10);
+		obj.insert("f_coarse_fine_diff", round(reci->fermentables.at(i).f_coarse_fine_diff * 10) / 10);
+		obj.insert("f_moisture", round(reci->fermentables.at(i).f_moisture * 10) / 10);
+		obj.insert("f_diastatic_power", round(reci->fermentables.at(i).f_diastatic_power * 100000) / 100000);
+		obj.insert("f_protein", round(reci->fermentables.at(i).f_protein * 10) / 10);
+		obj.insert("f_dissolved_protein", round(reci->fermentables.at(i).f_dissolved_protein * 10) / 10);
+		obj.insert("f_max_in_batch", reci->fermentables.at(i).f_max_in_batch);
+		obj.insert("f_graintype", reci->fermentables.at(i).f_graintype);
+		obj.insert("f_added", reci->fermentables.at(i).f_added);
+		obj.insert("f_recommend_mash", reci->fermentables.at(i).f_recommend_mash ? 1:0);
+		obj.insert("f_add_after_boil", reci->fermentables.at(i).f_add_after_boil ? 1:0);
+		obj.insert("f_adjust_to_total_100", reci->fermentables.at(i).f_adjust_to_total_100 ? 1:0);
+		obj.insert("f_percentage", round(reci->fermentables.at(i).f_percentage * 10) / 10);
+		obj.insert("f_di_ph", round(reci->fermentables.at(i).f_di_ph * 100000) / 100000);
+		obj.insert("f_acid_to_ph_57", round(reci->fermentables.at(i).f_acid_to_ph_57 * 100000) / 100000);
+		array.append(obj);      /* Append this object */
+	}
+	QJsonDocument doc;
+	doc.setArray(array);
+	query.bindValue(":json_fermentables", doc.toJson(QJsonDocument::Compact));
+    }
+
+    if (reci->hops.size() == 0) {
+        query.bindValue(":json_hops", "[]");
+    } else {
+        QJsonArray array;
+        for (int i = 0; i < reci->hops.size(); i++) {
+                QJsonObject obj;
+		obj.insert("h_name", reci->hops.at(i).h_name);
+                obj.insert("h_origin", reci->hops.at(i).h_origin);
+		obj.insert("h_amount", round(reci->hops.at(i).h_amount * 10000) / 10000);
+		obj.insert("h_cost", round(reci->hops.at(i).h_cost * 100) / 100);
+		obj.insert("h_type", reci->hops.at(i).h_type);
+		obj.insert("h_form", reci->hops.at(i).h_form);
+		obj.insert("h_useat", reci->hops.at(i).h_useat);
+		obj.insert("h_time", round(reci->hops.at(i).h_time));
+		obj.insert("h_alpha", round(reci->hops.at(i).h_alpha * 100) / 100);
+		obj.insert("h_beta", round(reci->hops.at(i).h_beta * 100) / 100);
+		obj.insert("h_hsi", round(reci->hops.at(i).h_hsi * 100) / 100);
+		obj.insert("h_humulene", round(reci->hops.at(i).h_humulene * 100) / 100);
+		obj.insert("h_caryophyllene", round(reci->hops.at(i).h_caryophyllene * 100) / 100);
+		obj.insert("h_cohumulone", round(reci->hops.at(i).h_cohumulone * 100) / 100);
+		obj.insert("h_myrcene", round(reci->hops.at(i).h_myrcene * 100) / 100);
+		obj.insert("h_total_oil", round(reci->hops.at(i).h_total_oil * 100) / 100);
+                array.append(obj);      /* Append this object */
+        }
+	QJsonDocument doc;
+        doc.setArray(array);
+        query.bindValue(":json_hops", doc.toJson(QJsonDocument::Compact));
+    }
+
+    if (reci->miscs.size() == 0) {
+        query.bindValue(":json_miscs", "[]");
+    } else {
+        QJsonArray array;
+        for (int i = 0; i < reci->miscs.size(); i++) {
+                QJsonObject obj;
+		obj.insert("m_name", reci->miscs.at(i).m_name);
+		obj.insert("m_amount", round(reci->miscs.at(i).m_amount * 10000) / 10000);
+		obj.insert("m_type", reci->miscs.at(i).m_type);
+		obj.insert("m_use_use", reci->miscs.at(i).m_use_use);
+		obj.insert("m_time", round(reci->miscs.at(i).m_time));
+		obj.insert("m_amount_is_weight", reci->miscs.at(i).m_amount_is_weight ? 1:0);
+		obj.insert("m_cost", round(reci->miscs.at(i).m_cost * 10000) / 10000);
+                array.append(obj);      /* Append this object */
+        }
+	QJsonDocument doc;
+        doc.setArray(array);
+        query.bindValue(":json_miscs", doc.toJson(QJsonDocument::Compact));
+    }
+
+    if (reci->yeasts.size() == 0) {
+        query.bindValue(":json_yeasts", "[]");
+    } else {
+        QJsonArray array;
+        for (int i = 0; i < reci->yeasts.size(); i++) {
+                QJsonObject obj;
+		obj.insert("y_name", reci->yeasts.at(i).y_name);
+		obj.insert("y_laboratory", reci->yeasts.at(i).y_laboratory);
+		obj.insert("y_product_id", reci->yeasts.at(i).y_product_id);
+		obj.insert("y_amount", round(reci->yeasts.at(i).y_amount * 10000) / 10000);
+		obj.insert("y_type", reci->yeasts.at(i).y_type);
+		obj.insert("y_form", reci->yeasts.at(i).y_form);
+		obj.insert("y_min_temperature", round(reci->yeasts.at(i).y_min_temperature * 10) / 10);
+		obj.insert("y_max_temperature", round(reci->yeasts.at(i).y_max_temperature * 10) / 10);
+		obj.insert("y_flocculation", reci->yeasts.at(i).y_flocculation);
+		obj.insert("y_attenuation", round(reci->yeasts.at(i).y_attenuation * 10) / 10);
+		obj.insert("y_cells", reci->yeasts.at(i).y_cells);
+		obj.insert("y_tolerance", round(reci->yeasts.at(i).y_tolerance * 10) / 10);
+		obj.insert("y_inventory", round(reci->yeasts.at(i).y_inventory * 10000) / 10000);
+		obj.insert("y_use", reci->yeasts.at(i).y_use);
+		obj.insert("y_sta1", reci->yeasts.at(i).y_sta1 ? 1:0);
+		obj.insert("y_bacteria", reci->yeasts.at(i).y_bacteria ? 1:0);
+		obj.insert("y_harvest_top", reci->yeasts.at(i).y_harvest_top ? 1:0);
+		obj.insert("y_harvest_time", reci->yeasts.at(i).y_harvest_time);
+		obj.insert("y_pitch_temperature", round(reci->yeasts.at(i).y_pitch_temperature * 10) / 10);
+		obj.insert("y_pofpos", reci->yeasts.at(i).y_pofpos ? 1:0);
+		obj.insert("y_zymocide", reci->yeasts.at(i).y_zymocide);
+		obj.insert("y_gr_hl_lo", reci->yeasts.at(i).y_gr_hl_lo);
+		obj.insert("y_sg_lo", round(reci->yeasts.at(i).y_sg_lo * 1000) / 1000);
+		obj.insert("y_gr_hl_hi", reci->yeasts.at(i).y_gr_hl_hi);
+		obj.insert("y_sg_hi", round(reci->yeasts.at(i).y_sg_hi * 1000) / 1000);
+		obj.insert("y_cost", round(reci->yeasts.at(i).y_cost * 1000) / 1000);
+                array.append(obj);      /* Append this object */
+        }
+	QJsonDocument doc;
+        doc.setArray(array);
+        query.bindValue(":json_yeasts", doc.toJson(QJsonDocument::Compact));
+    }
+
+    if (reci->mashs.size() == 0) {
+        query.bindValue(":json_mashs", "[]");
+    } else {
+        QJsonArray array;
+        for (int i = 0; i < reci->mashs.size(); i++) {
+                QJsonObject obj;
+		obj.insert("step_name", reci->mashs.at(i).step_name);
+		obj.insert("step_type", reci->mashs.at(i).step_type);
+		obj.insert("step_volume", round(reci->mashs.at(i).step_volume * 100) / 100);
+		obj.insert("step_infuse_amount", round(reci->mashs.at(i).step_infuse_amount * 100) / 100);
+		obj.insert("step_infuse_temp", round(reci->mashs.at(i).step_infuse_temp * 100) / 100);
+		obj.insert("step_temp", round(reci->mashs.at(i).step_temp * 100) / 100);
+		obj.insert("step_time", round(reci->mashs.at(i).step_time * 100) / 100);
+		obj.insert("ramp_time", round(reci->mashs.at(i).ramp_time * 100) / 100);
+		obj.insert("end_temp", round(reci->mashs.at(i).end_temp * 100) / 100);
+		obj.insert("step_wg_ratio", round(reci->mashs.at(i).step_wg_ratio * 100) / 100);
+                array.append(obj);      /* Append this object */
+        }
+	QJsonDocument doc;
+        doc.setArray(array);
+        query.bindValue(":json_mashs", doc.toJson(QJsonDocument::Compact));
+    }
+
+    if (reci->record == -1) {
+	query.bindValue(":uuid", QUuid::createUuid().toString().mid(1, 36));
+    } else {
+	query.bindValue(":recno", reci->record);
+    }
+    query.exec();
+    qDebug() << query.lastQuery();
+    if (query.lastError().isValid()) {
+	qDebug() << "EditRecipe" << query.lastError();
+	QMessageBox::warning(dialog, QObject::tr("Database error"),
+                        QObject::tr("MySQL error: %1\n%2\n%3")
+                        .arg(query.lastError().nativeErrorCode())
+                        .arg(query.lastError().driverText())
+                        .arg(query.lastError().databaseText()));
+	return false;
+    } else {
+	/*
+	 * If this was a new recipe, find out what record number we
+	 * have got and set it. So when the user saves this record
+	 * again, it will be updated instead of inserting a new copy.
+	 */
+	if (reci->record < 0) {
+	    QVariant id = query.lastInsertId();
+	    reci->record = id.toInt();
+	    qDebug() << "saveRecipe Inserted record" << reci->record;
+	} else {
+	    qDebug() << "saveRecipe Updated record" << reci->record;
+	}
+    }
+    return true;
+}
+
+
+bool DB_recipe::dele(QDialog *dialog, int recno)
+{
+    QSqlQuery query;
+
+    query.prepare("DELETE FROM recipes WHERE record = :recno");
+    query.bindValue(":recno", recno);
+    query.exec();
+    if (query.lastError().isValid()) {
+	qDebug() << "deleteRecipe" << query.lastError();
+	QMessageBox::warning(dialog, QObject::tr("Database error"),
+                        QObject::tr("MySQL error: %1\n%2\n%3")
+                        .arg(query.lastError().nativeErrorCode())
+                        .arg(query.lastError().driverText())
+                        .arg(query.lastError().databaseText()));
+	return false;
+    }
+
+    qDebug() << "deleteRecipe Deleted" << recno;
+    return true;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/database/db_recipe.h	Wed Jun 01 21:46:26 2022 +0200
@@ -0,0 +1,30 @@
+#ifndef	_DB_RECIPE_H
+#define _DB_RECIPE_H
+
+#include <QDebug>
+#include <QMessageBox>
+#include <QUuid>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QTranslator>
+#include <QSqlQuery>
+#include <QSqlError>
+#include <QSqlRecord>
+#include <QObject>
+#include <math.h>
+
+
+/**
+ * @namespace db_recipe
+ *
+ * @brief Global database recipe functions..
+ */
+namespace DB_recipe {
+
+    bool load(Recipe *reci, QDialog *dialog, int recno);
+    bool save(Recipe *reci, QDialog *dialog);
+    bool dele(QDialog *dialog, int recno);
+}
+
+#endif

mercurial