Commit ca0d506b authored by André Hoarau's avatar André Hoarau 🍆
Browse files

Resolve "Add LocationsPage"

parent 1e879873
......@@ -74,6 +74,8 @@ AllowShortFunctionsOnASingleLine: Inline
# re-order includes separated by empty lines, the feature is not used.
SortIncludes: false
ReflowComments: true
# macros for which the opening brace stays attached.
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ]
......
......@@ -38,7 +38,9 @@ SOURCES += \
variety.cpp \
keyword.cpp \
mdate.cpp \
taskimplementmodel.cpp
taskimplementmodel.cpp \
treemodel.cpp \
nametree.cpp
HEADERS += \
......@@ -73,7 +75,10 @@ HEADERS += \
variety.h \
keyword.h \
mdate.h \
taskimplementmodel.h
taskimplementmodel.h \
treemodel.h \
nametree.h
RESOURCES += \
core_resources.qrc
......@@ -24,6 +24,7 @@
DatabaseUtility::DatabaseUtility(QObject *parent)
: QObject(parent)
, m_table("")
, m_viewTable("")
, m_idFieldName("")
{
}
......@@ -62,10 +63,9 @@ void DatabaseUtility::debugQuery(const QSqlQuery &query) const
if (query.lastError().type() == QSqlError::ErrorType::NoError) {
return;
// qDebug() << "Query OK: " << query.lastQuery();
} else {
qWarning() << "Query ERROR: " << query.lastError().text();
qWarning() << "Query text: " << query.lastQuery();
}
qWarning() << "[Query ERROR] " << query.lastError().text();
qWarning() << "[Query TEXT]: " << query.lastQuery();
}
QList<int> DatabaseUtility::queryIds(const QString &queryString, const QString &idFieldName) const
......@@ -146,7 +146,7 @@ QList<QVariantMap> DatabaseUtility::mapListFromIdList(const QString &tableName,
QList<QVariantMap> mapList;
QList<QSqlRecord> recordList = recordListFromIdList(tableName, idList);
for (auto &record : recordList)
for (const auto &record : recordList)
if (!record.isEmpty())
mapList.push_back(mapFromRecord(record));
return mapList;
......@@ -154,21 +154,20 @@ QList<QVariantMap> DatabaseUtility::mapListFromIdList(const QString &tableName,
int DatabaseUtility::add(const QVariantMap &map) const
{
qDebug() << "MAP:" << map;
QString queryNameString = QString("INSERT INTO %1 (").arg(table());
QString queryValueString = " VALUES (";
QString fieldName = idFieldName();
for (const auto &key : map.keys())
for (const auto &key : map.keys()) {
if (key != fieldName) {
queryNameString.append(QString(" %1,").arg(key));
queryValueString.append(QString(" :%1,").arg(key));
}
}
// Remove last semicolons.
// Remove trailing semicolons.
queryNameString.chop(1);
queryValueString.chop(1);
queryNameString.append(")");
queryValueString.append(")");
......@@ -261,7 +260,6 @@ void DatabaseUtility::remove(int id) const
void DatabaseUtility::removeList(const QList<int> &idList) const
{
qDebug() << "Batch remove:" << idList;
QSqlDatabase::database().transaction();
for (const int id : idList)
remove(id);
......@@ -276,3 +274,28 @@ void DatabaseUtility::removeLink(const QString &table, const QString &field1, in
query.exec();
debugQuery(query);
}
QVariantMap DatabaseUtility::commonValues(const QList<int> &idList) const
{
if (idList.length() < 1)
return {};
QList<QVariantMap> list = mapListFromIdList(m_viewTable, idList);
if (list.isEmpty())
return {};
QVariantMap common = list[0];
if (list.length() == 1)
return common;
for (const auto &key : common.keys()) {
int i;
for (i = 1; i < list.length(); i++)
if (list[i].value(key) != common.value(key))
break;
if (i != list.length())
common.remove(key);
}
return common;
}
......@@ -60,13 +60,15 @@ public:
Q_INVOKABLE void duplicateList(const QList<int> &idList) const;
virtual Q_INVOKABLE void remove(int id) const;
Q_INVOKABLE void removeList(const QList<int> &idList) const;
virtual Q_INVOKABLE void removeList(const QList<int> &idList) const;
void removeLink(const QString &table, const QString &field1, int id1, const QString &field2,
int id2) const;
Q_INVOKABLE void rollback() const;
Q_INVOKABLE QVariantMap commonValues(const QList<int> &idList) const;
protected:
QString m_table;
QString m_viewTable;
QString m_idFieldName;
};
......
......@@ -23,6 +23,7 @@
#include <QDebug>
#include "db.h"
#include "location.h"
QString databasePath()
{
......@@ -94,5 +95,30 @@ void createDatabase()
execSqlFile(":/db/tables.sql");
execSqlFile(":/db/triggers.sql", "END;");
execSqlFile(":/db/data.sql");
qInfo() << "Database successfully created";
qInfo() << "Database created";
}
void createFakeData()
{
Location location;
int parentId0;
int parentId1;
QSqlDatabase::database().transaction();
for (int i = 0; i < 10; i++) {
parentId0 =
location.add({ { "name", QString::number(i) }, { "bed_length", 30 }, { "level", 0 } });
for (int j = 0; j < 10; j++) {
parentId1 = location.add({ { "name", QString::number(j) },
{ "parent_id", parentId0 },
{ "bed_length", 30 },
{ "level", 1 } });
// for (int k = 0; k < 10; k++)
// location.add({ { "name", QString::number(k) },
// { "parent_id", parentId1 },
// { "bed_length", 30 },
// { "level", 2 } });
}
}
QSqlDatabase::database().commit();
}
......@@ -29,6 +29,7 @@ CORESHARED_EXPORT void connectToDatabase();
CORESHARED_EXPORT void execSqlFile(const QString &fileName, const QString &separator = ";");
CORESHARED_EXPORT void createDatabase();
CORESHARED_EXPORT void deleteDatabase();
CORESHARED_EXPORT void createFakeData();
// class CORESHARED_EXPORT Note : public DatabaseUtility {
// Q_OBJECT
......
insert into family (family) values ("Alliaceae");
insert into family (family) values ("Apiaceae");
insert into family (family) values ("Asteraceae");
insert into family (family) values ("Brassicaceae");
insert into family (family) values ("Chenopodiaceae");
insert into family (family) values ("Cucurbitaceae");
insert into family (family) values ("Fabaceae");
insert into family (family) values ("Solanaceae");
insert into family (family) values ("Valerianaceae");
insert into family (family, interval) values ("Alliaceae", 3);
insert into family (family, interval) values ("Apiaceae", 2);
insert into family (family, interval) values ("Asteraceae", 2);
insert into family (family, interval) values ("Brassicaceae", 4);
insert into family (family, interval) values ("Chenopodiaceae", 3);
insert into family (family, interval) values ("Cucurbitaceae", 4);
insert into family (family, interval) values ("Fabaceae", 2);
insert into family (family, interval) values ("Solanaceae", 4);
insert into family (family, interval) values ("Valerianaceae", 2);
insert into crop (crop, family_id) values ("Tomato", 1);
insert into crop (crop, family_id) values ("Pepper", 1);
......
CREATE TABLE IF NOT EXISTS family (
family_id INTEGER PRIMARY KEY AUTOINCREMENT,
family TEXT NOT NULL,
interval INTEGER NOT NULL DEFAULT 0,
color TEXT DEFAULT '#000000' NOT NULL
);
......@@ -253,8 +254,8 @@ CREATE TABLE IF NOT EXISTS expense_file (
CREATE VIEW IF NOT EXISTS planting_view AS
SELECT planting_id as planting_view_id,
crop, variety, crop_id,
crop.color as crop_color,
family, family_id, family.color as family_color, family.interval as family_interval,
crop, variety, crop_id, crop.color as crop_color,
planting.*,
unit.abbreviation as unit,
group_concat(location_id) as locations,
......@@ -266,6 +267,7 @@ FROM planting
LEFT JOIN planting_location using(planting_id)
LEFT JOIN variety USING (variety_id)
LEFT JOIN crop USING (crop_id)
LEFT JOIN family USING (family_id)
LEFT JOIN unit USING (unit_id)
LEFT JOIN planting_task USING (planting_id)
LEFT JOIN task USING (task_id)
......
......@@ -2,12 +2,12 @@
-- A task is associated to at least 1 planting. If there is no more planting
-- associated to a task, we delete it.
CREATE TRIGGER planting_task_delete AFTER DELETE ON planting_task FOR EACH ROW
WHEN (SELECT COUNT(*) FROM planting_task WHERE task_id = OLD.task_id) = 0
BEGIN
DELETE FROM task
WHERE task_id = OLD.task_id;
END;
-- CREATE TRIGGER planting_task_delete AFTER DELETE ON planting_task FOR EACH ROW
-- WHEN (SELECT COUNT(*) FROM planting_task WHERE task_id = OLD.task_id) = 0
-- BEGIN
-- DELETE FROM task
-- WHERE task_id = OLD.task_id;
-- END;
-- If the dtt of TP, raised planting if modified, update linked transplant task.
CREATE TRIGGER planting_update_dtt AFTER UPDATE ON planting FOR EACH ROW
......
......@@ -21,6 +21,6 @@ HarvestModel::HarvestModel(QObject *parent)
{
setTable("harvest");
int plantingColumn = fieldColumn("planting_id");
setRelation(plantingColumn, QSqlRelation("planting", "planting_id", "planting_id"));
// int plantingColumn = fieldColumn("planting_id");
// setRelation(plantingColumn, QSqlRelation("planting", "planting_id", "planting_id"));
}
......@@ -25,6 +25,7 @@ Keyword::Keyword(QObject *parent)
: DatabaseUtility(parent)
{
m_table = "keyword";
m_viewTable = "keyword";
}
QList<int> Keyword::keywordIdList(int plantingId) const
......
......@@ -14,27 +14,104 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QDate>
#include <QDebug>
#include <QSqlRecord>
#include <QVariant>
#include "location.h"
#include "planting.h"
#include "nametree.h"
Location::Location(QObject *parent)
: DatabaseUtility(parent)
, planting(new Planting(this))
{
m_table = "location";
m_viewTable = "location";
}
// Return a list of all locations in the subtree whose root is locationId.
QList<int> Location::childrenTree(int locationId) const
{
QList<int> list = children(locationId);
QList<int> clist;
int childId = -1;
int size = list.count();
for (int i = 0; i < size; i++) {
childId = list[i];
clist = children(childId);
list.append(clist);
size += clist.count();
}
return list;
}
void Location::remove(int id) const
{
if (id < 1)
return;
QString queryString = "DELETE FROM %1 WHERE %2 in (%3)";
QString idColumnName = table() + "_id";
QString idString(QString::number(id) + ", ");
for (int childId : childrenTree(id))
idString += QString::number(childId) + ", ";
idString.chop(2);
QSqlQuery query(queryString.arg(table()).arg(idColumnName).arg(idString));
query.exec();
debugQuery(query);
}
int Location::duplicate(int id) const
{
if (id < 0)
return -1;
if (table().isNull())
return -1;
QSqlDatabase::database().transaction();
QVariantMap map = mapFromId(table(), id);
qDebug() << map;
map.remove(idFieldName());
map["name"] = map["name"].toString() + QString(" (copy)");
int newId = add(map);
for (int childrenId : children(id))
duplicateTree(childrenId, newId);
QSqlDatabase::database().commit();
return newId;
}
int Location::duplicateTree(int id, int parentId) const
{
if (id < 0)
return -1;
if (table().isNull())
return -1;
QVariantMap map = mapFromId(table(), id);
map.remove(idFieldName());
map["parent_id"] = QString::number(parentId);
int newId = add(map);
for (int childrenId : children(id))
duplicateTree(childrenId, newId);
return 0;
}
QString Location::fullName(int locationId) const
{
if (locationId < 1)
return QString();
QSqlRecord record = recordFromId("location", locationId);
return {};
QSqlRecord record(recordFromId("location", locationId));
if (record.isEmpty())
return QString();
return {};
QString name = record.value("name").toString();
QString name(record.value("name").toString());
while (!record.value("parent_id").isNull()) {
record = recordFromId("location", record.value("parent_id").toInt());
name = record.value("name").toString() + name;
......@@ -42,30 +119,169 @@ QString Location::fullName(int locationId) const
return name;
}
QList<QSqlRecord> Location::locations(int plantingId) const
QList<QString> Location::pathName(int locationId) const
{
if (locationId < 1)
return {};
QSqlRecord record(recordFromId("location", locationId));
if (record.isEmpty())
return {};
QList<QString> list;
list.push_front(record.value("name").toString());
while (!record.value("parent_id").isNull()) {
record = recordFromId("location", record.value("parent_id").toInt());
list.push_front(record.value("name").toString());
}
return list;
}
QString Location::fullName(QList<int> locationIdList) const
{
NameTree *root = new NameTree("", 0);
QList<QString> pathList;
for (const int id : locationIdList) {
pathList = pathName(id);
root->insert(pathList);
}
QString name = root->fullName();
delete root;
return name;
}
QList<int> Location::locations(int plantingId) const
{
QString queryString = "SELECT * FROM planting_location WHERE planting_id = %1";
QString queryString("SELECT * FROM planting_location WHERE planting_id = %1");
QSqlQuery query(queryString.arg(plantingId));
debugQuery(query);
QList<QSqlRecord> recordList;
int id = -1;
while (query.next()) {
id = query.value("location_id").toInt();
recordList.append(recordFromId("location", id));
}
QList<int> recordList;
while (query.next())
recordList.push_back(query.value("location_id").toInt());
return recordList;
}
/* Return the length of plantingId assigned to locationId */
int Location::plantingLength(int plantingId, int locationId) const
{
QString queryString("SELECT length FROM planting_location "
"WHERE planting_id = %1 AND location_id = %2");
QSqlQuery query(queryString.arg(plantingId).arg(locationId));
debugQuery(query);
if (!query.next())
return 0;
return query.value("length").toInt();
}
/* Return the ids of all plantings assigned to locationId. */
QList<int> Location::plantings(int locationId) const
{
QString queryString("SELECT planting_id FROM planting_location "
"LEFT JOIN planting_view USING (planting_id) "
"WHERE location_id = %1 "
"ORDER BY planting_date DESC");
return queryIds(queryString.arg(locationId), "planting_id");
}
/* Return the ids of all plantings assigned to locationId befre \a last. */
QList<int> Location::plantings(int locationId, const QDate &last) const
{
QString lastDateString = last.toString(Qt::ISODate);
QString queryString("SELECT * FROM planting_location "
"LEFT JOIN planting_view USING (planting_id) "
"WHERE location_id = %1 "
"AND ((planting_date AND planting_date <= '%2') "
" OR (beg_harvest_date AND beg_harvest_date <= '%2') "
" OR (end_harvest_date AND end_harvest_date <= '%2'))");
return queryIds(queryString.arg(locationId).arg(lastDateString), "planting_id");
}
/* Return the ids of all plantings assigned to locationId in a given season. */
QList<int> Location::plantings(int locationId, const QDate &seasonBeg, const QDate &seasonEnd) const
{
QString begString = seasonBeg.toString(Qt::ISODate);
QString endString = seasonEnd.toString(Qt::ISODate);
QString queryString("SELECT * FROM planting_location "
"LEFT JOIN planting_view USING (planting_id) "
"WHERE location_id = %1 "
"AND (('%2' <= planting_date AND planting_date <= '%3') "
" OR ('%2' <= beg_harvest_date AND beg_harvest_date <= '%3') "
" OR ('%2' <= end_harvest_date AND end_harvest_date <= '%3'))");
return queryIds(queryString.arg(locationId).arg(begString).arg(endString), "planting_id");
}
QList<int> Location::children(int locationId) const
{
QString queryString("SELECT * FROM location WHERE parent_id = %1");
return queryIds(queryString.arg(locationId), "location_id");
}
void Location::addPlanting(int plantingId, int locationId) const
/*!
* Returns a list of planting ids of locationId conflicting with plantingId.
*/
QList<int> Location::conflictingPlantings(int locationId, int plantingId) const
{
addLink("planting_location", "planting_id", plantingId, "location_id", locationId);
QList<int> clist;
const QDate plantingDate = planting->plantingDate(plantingId);
QList<int> plantingIdList = plantings(locationId, plantingDate);
plantingIdList.removeOne(plantingId);
const int familyId = planting->familyId(plantingId).toInt();
const int intervalDays = qAbs(planting->familyInterval(plantingId).toInt()) * 365;
QDate pdate;
for (const int pid : plantingIdList) {
pdate = planting->plantingDate(pid);
if (familyId == planting->familyId(pid).toInt() && pdate.daysTo(plantingDate) < intervalDays)
clist.push_back(pid);
}
return clist;
}
int Location::availableSpace(int plantingId, int locationId, const QDate &seasonBeg,
const QDate &seasonEnd) const
{
QVariantMap map = mapFromId("location", locationId);
int length = map.value("bed_length").toInt();
QList<int> plantingIdList = plantings(locationId, seasonBeg, seasonEnd);
QDate plantingDate = planting->plantingDate(plantingId);
QDate endHarvestDate = planting->endHarvestDate(plantingId);
QDate pdate;
QDate edate;
int l = 0;
int maxl = 0;
for (int pid : plantingIdList) {
pdate = planting->plantingDate(pid);
edate = planting->endHarvestDate(pid);
l = plantingLength(pid, locationId);
if (pdate < endHarvestDate && edate > plantingDate && l > maxl)
maxl = l;
}
return length - maxl;
}
void Location::addPlanting(int plantingId, int locationId, int length, const QDate &seasonBeg,
const QDate &seasonEnd) const
{
QVariantMap map = mapFromId("location", locationId);
int lengthToAdd = qMin(length, availableSpace(plantingId, locationId, seasonBeg, seasonEnd));
if (lengthToAdd < 1)
return;
QString queryString("INSERT INTO planting_location (planting_id, location_id, length) "
"VALUES (%1, %2, %3)");
QSqlQuery query(queryString.arg(plantingId).arg(locationId).arg(lengthToAdd));
query.exec();
debugQuery(query);
}
void Location::removePlanting(int plantingId, int locationId) const
......
......@@ -20,19 +20,38 @@
#include "core_global.h"
#include "databaseutility.h"
class Planting;
class CORESHARED_EXPORT Location : public DatabaseUtility
{
Q_OBJECT
public:
Location(QObject *parent = nullptr);
// Q_INVOKABLE int duplicate(int id) { return db.duplicate(id); } // TODO: duplicate children
Q_INVOKABLE void remove(int id) const override;
Q_INVOKABLE int duplicate(int id) const override;
Q_INVOKABLE QString fullName(int locationId) const;
Q_INVOKABLE QList<QSqlRecord> locations(int plantingId) const;
QList<QString> pathName(int locationId) const;
Q_INVOKABLE QString fullName(QList<int> locationIdList) const;
Q_INVOKABLE QList<int> locations(int plantingId) const;
Q_INVOKABLE int plantingLength(int plantingId, int locationId) const;
Q_INVOKABLE QList<int> plantings(int locationId) const;
Q_INVOKABLE QList<int> plantings(int locationId, const QDate &last) const;
Q_INVOKABLE QList<int> plantings(int locationId, const QDate &seasonBeg, const QDate &seasonEnd) const;
Q_INVOKABLE QList<int> conflictingPlantings(int locationId, int plantingId) const;
int availableSpace(int plantingId, int locationId, const QDate &seasonBeg,
const QDate &seasonEnd) const;
Q_INVOKABLE QList<int> children(int locationId) const;