Commit 9e5ad334 authored by Volker Krause's avatar Volker Krause

Split hotel reservations into two elements in the timeline

This allows us to show the checkout date in a meaningful way in the
timeline on longer stays too. Also, handle event moves correctly.
parent 40c6f99a
[
{
"@context": "http://schema.org",
"@type": "LodgingReservation",
"checkinDate": "2017-09-10T15:00:00+02:00",
"checkoutDate": "2017-09-15T10:00:00+02:00",
"reservationFor": {
"@type": "LodgingBusiness",
"address": {
"@type": "PostalAddress",
"addressCountry": "Switzerland",
"addressLocality": "Randa",
"addressRegion": "Wallis",
"postalCode": "3928",
"streetAddress": "Haus Maria am Weg"
},
"name": "Haus Randa",
"url": "https://randa-meetings.ch/"
}
}
]
[
{
"@context": "http://schema.org",
"@type": "LodgingReservation",
"checkinDate": "2017-09-10T15:00:00+02:00",
"checkoutDate": "2017-09-16T10:00:00+02:00",
"reservationFor": {
"@type": "LodgingBusiness",
"address": {
"@type": "PostalAddress",
"addressCountry": "Switzerland",
"addressLocality": "Randa",
"addressRegion": "Wallis",
"postalCode": "3928",
"streetAddress": "Haus Maria am Weg"
},
"name": "Haus Randa",
"url": "https://randa-meetings.ch/"
}
}
]
......@@ -88,6 +88,55 @@ private slots:
QCOMPARE(rmSpy.size(), 1);
QCOMPARE(model.rowCount(), 1);
}
void testNestedElements()
{
ReservationManager resMgr;
clearReservations(&resMgr);
TimelineModel model;
model.setReservationManager(&resMgr);
QSignalSpy insertSpy(&model, &TimelineModel::rowsInserted);
QVERIFY(insertSpy.isValid());
QSignalSpy updateSpy(&model, &TimelineModel::dataChanged);
QVERIFY(updateSpy.isValid());
QSignalSpy rmSpy(&model, &TimelineModel::rowsRemoved);
QVERIFY(rmSpy.isValid());
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineModel::TodayMarker);
resMgr.importReservation(QUrl::fromLocalFile(QLatin1String(SOURCE_DIR "/data/haus-randa-v1.json")));
QCOMPARE(insertSpy.size(), 2);
QCOMPARE(insertSpy.at(0).at(1).toInt(), 0);
QCOMPARE(insertSpy.at(0).at(2).toInt(), 0);
QCOMPARE(insertSpy.at(1).at(1).toInt(), 1);
QCOMPARE(insertSpy.at(1).at(2).toInt(), 1);
QVERIFY(updateSpy.isEmpty());
QCOMPARE(model.rowCount(), 3);
QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineModel::Hotel);
QCOMPARE(model.index(0, 0).data(TimelineModel::ElementRangeRole), TimelineModel::RangeBegin);
QCOMPARE(model.index(1, 0).data(TimelineModel::ElementTypeRole), TimelineModel::Hotel);
QCOMPARE(model.index(1, 0).data(TimelineModel::ElementRangeRole), TimelineModel::RangeEnd);
// move end date of a hotel booking: dataChanged on RangeBegin, move (or del/ins) on RangeEnd
resMgr.importReservation(QUrl::fromLocalFile(QLatin1String(SOURCE_DIR "/data/haus-randa-v2.json")));
QCOMPARE(insertSpy.size(), 3);
QCOMPARE(updateSpy.size(), 1);
QCOMPARE(rmSpy.size(), 1);
QCOMPARE(updateSpy.at(0).at(0).toModelIndex().row(), 0);
QCOMPARE(insertSpy.at(2).at(1).toInt(), 1);
QCOMPARE(insertSpy.at(2).at(2).toInt(), 1);
QCOMPARE(rmSpy.at(0).at(1), 1);
QCOMPARE(model.rowCount(), 3);
// delete a split element
const auto resId = model.data(model.index(0, 0), TimelineModel::ReservationIdRole).toString();
resMgr.removeReservation(resId);
QCOMPARE(rmSpy.size(), 3);
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.index(0, 0).data(TimelineModel::ElementTypeRole), TimelineModel::TodayMarker);
}
};
QTEST_GUILESS_MAIN(TimelineModelTest)
......
......@@ -36,6 +36,11 @@
using namespace KItinerary;
static bool needsSplitting(const QVariant &res)
{
return res.userType() == qMetaTypeId<LodgingReservation>();
}
static QDateTime relevantDateTime(const QVariant &res, TimelineModel::RangeType range)
{
if (res.isNull()) { // today marker
......@@ -94,7 +99,13 @@ void TimelineModel::setReservationManager(ReservationManager* mgr)
beginResetModel();
m_resMgr = mgr;
for (const auto &resId : mgr->reservations()) {
m_elements.push_back(Element{resId, relevantDateTime(mgr->reservation(resId), SelfContained), SelfContained});
const auto res = m_resMgr->reservation(resId);
if (needsSplitting(res)) {
m_elements.push_back(Element{resId, relevantDateTime(mgr->reservation(resId), RangeBegin), RangeBegin});
m_elements.push_back(Element{resId, relevantDateTime(mgr->reservation(resId), RangeEnd), RangeEnd});
} else {
m_elements.push_back(Element{resId, relevantDateTime(mgr->reservation(resId), SelfContained), SelfContained});
}
}
m_elements.push_back(Element{{}, relevantDateTime({}, SelfContained), SelfContained}); // today marker
std::sort(m_elements.begin(), m_elements.end(), [this](const Element &lhs, const Element &rhs) {
......@@ -136,6 +147,8 @@ QVariant TimelineModel::data(const QModelIndex& index, int role) const
}
case ReservationRole:
return res;
case ReservationIdRole:
return elem.id;
case ElementTypeRole:
if (res.isNull())
return TodayMarker;
......@@ -184,23 +197,57 @@ int TimelineModel::todayRow() const
void TimelineModel::reservationAdded(const QString &resId)
{
auto it = std::lower_bound(m_elements.begin(), m_elements.end(), resId, [this](const Element &lhs, const QString &rhs) {
return lhs.dt < relevantDateTime(m_resMgr->reservation(rhs), SelfContained);
const auto res = m_resMgr->reservation(resId);
if (needsSplitting(res)) {
insertElement(Element{resId, relevantDateTime(res, RangeBegin), RangeBegin});
insertElement(Element{resId, relevantDateTime(res, RangeEnd), RangeEnd});
} else {
insertElement(Element{resId, relevantDateTime(res, SelfContained), SelfContained});
}
emit todayRowChanged();
}
void TimelineModel::insertElement(Element &&elem)
{
auto it = std::lower_bound(m_elements.begin(), m_elements.end(), elem.dt, [](const Element &lhs, const QDateTime &rhs) {
return lhs.dt < rhs;
});
auto index = std::distance(m_elements.begin(), it);
beginInsertRows({}, index, index);
m_elements.insert(it, Element{resId, relevantDateTime(m_resMgr->reservation(resId), SelfContained), SelfContained});
m_elements.insert(it, std::move(elem));
endInsertRows();
emit todayRowChanged();
}
void TimelineModel::reservationUpdated(const QString &resId)
{
auto it = std::lower_bound(m_elements.begin(), m_elements.end(), resId, [this](const Element &lhs, const QString &rhs) {
return lhs.dt < relevantDateTime(m_resMgr->reservation(rhs), SelfContained);
});
auto row = std::distance(m_elements.begin(), it);
emit dataChanged(index(row, 0), index(row, 0));
const auto res = m_resMgr->reservation(resId);
if (needsSplitting(res)) {
updateElement(resId, res, RangeBegin);
updateElement(resId, res, RangeEnd);
} else {
updateElement(resId, res, SelfContained);
}
}
void TimelineModel::updateElement(const QString &resId, const QVariant &res, TimelineModel::RangeType rangeType)
{
const auto it = std::find_if(m_elements.begin(), m_elements.end(), [resId, rangeType](const Element &e) { return e.id == resId && e.rangeType == rangeType; });
if (it == m_elements.end()) {
return;
}
const auto row = std::distance(m_elements.begin(), it);
const auto newDt = relevantDateTime(res, rangeType);
if ((*it).dt != newDt) {
// element moved
beginRemoveRows({}, row, row);
m_elements.erase(it);
endRemoveRows();
insertElement(Element{resId, newDt, rangeType});
} else {
emit dataChanged(index(row, 0), index(row, 0));
}
}
void TimelineModel::reservationRemoved(const QString &resId)
......@@ -209,9 +256,14 @@ void TimelineModel::reservationRemoved(const QString &resId)
if (it == m_elements.end()) {
return;
}
const auto isSplit = (*it).rangeType == RangeBegin;
const auto row = std::distance(m_elements.begin(), it);
beginRemoveRows({}, row, row);
m_elements.erase(it);
endRemoveRows();
emit todayRowChanged();
if (isSplit) {
reservationRemoved(resId);
}
}
......@@ -35,6 +35,7 @@ public:
PassIdRole,
SectionHeader,
ReservationRole,
ReservationIdRole,
ElementTypeRole,
TodayEmptyRole,
IsTodayRole,
......@@ -75,18 +76,20 @@ signals:
void todayRowChanged();
private:
struct Element {
QString id; // reservation id
QDateTime dt; // relevant date/time
RangeType rangeType;
};
void reservationAdded(const QString &resId);
void insertElement(Element &&elem);
void reservationUpdated(const QString &resId);
void updateElement(const QString &resId, const QVariant &res, RangeType rangeType);
void reservationRemoved(const QString &resId);
PkPassManager *m_passMgr = nullptr;
ReservationManager *m_resMgr = nullptr;
struct Element {
QString id; // reservation id
QDateTime dt; // relevant date/time
RangeType rangeType;
};
std::vector<Element> m_elements;
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment