File indexing completed on 2024-11-17 04:49:33

0001 /*
0002  * Copyright (C) 2003, 2004 by Mark Bucciarelli <mark@hubcapconsutling.com>
0003  * Copyright (C) 2019  Alexander Potashev <aspotashev@gmail.com>
0004  *
0005  *   This program is free software; you can redistribute it and/or modify
0006  *   it under the terms of the GNU General Public License as published by
0007  *   the Free Software Foundation; either version 2 of the License, or
0008  *   (at your option) any later version.
0009  *
0010  *   This program is distributed in the hope that it will be useful,
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  *   GNU General Public License for more details.
0014  *
0015  *   You should have received a copy of the GNU General Public License along
0016  *   with this program; if not, write to the
0017  *      Free Software Foundation, Inc.
0018  *      51 Franklin Street, Fifth Floor
0019  *      Boston, MA  02110-1301  USA.
0020  *
0021  */
0022 
0023 #include "csvhistory.h"
0024 
0025 #include "ktimetrackerutility.h"
0026 #include "ktt_debug.h"
0027 #include "model/projectmodel.h"
0028 #include "model/task.h"
0029 #include "model/tasksmodel.h"
0030 
0031 static int64_t todaySeconds(const QDate &date, const KCalendarCore::Event::Ptr &event)
0032 {
0033     if (!event) {
0034         return 0;
0035     }
0036 
0037     qCDebug(KTT_LOG) << "found an event for task, event=" << event->uid();
0038     QDateTime startTime = event->dtStart();
0039     QDateTime endTime = event->dtEnd();
0040     QDateTime NextMidNight = startTime;
0041     NextMidNight.setTime(QTime(0, 0));
0042     NextMidNight = NextMidNight.addDays(1);
0043     // LastMidNight := mdate.setTime(0:00) as it would read in a decent
0044     // programming language
0045     QDateTime LastMidNight = QDateTime::currentDateTime();
0046     LastMidNight.setDate(date);
0047     LastMidNight.setTime(QTime(0, 0));
0048     int64_t secsstartTillMidNight = startTime.secsTo(NextMidNight);
0049     int64_t secondsToAdd = 0; // seconds that need to be added to the actual cell
0050 
0051     if (startTime.date() == date && event->dtEnd().date() == date) {
0052         // all the event occurred today
0053         secondsToAdd = startTime.secsTo(endTime);
0054     }
0055 
0056     if (startTime.date() == date && endTime.date() > date) {
0057         // the event started today, but ended later
0058         secondsToAdd = secsstartTillMidNight;
0059     }
0060 
0061     if (startTime.date() < date && endTime.date() == date) {
0062         // the event started before today and ended today
0063         secondsToAdd = LastMidNight.secsTo(event->dtEnd());
0064     }
0065 
0066     if (startTime.date() < date && endTime.date() > date) {
0067         // the event started before today and ended after
0068         secondsToAdd = 86400;
0069     }
0070 
0071     return secondsToAdd;
0072 }
0073 
0074 QString exportCSVHistoryToString(ProjectModel *projectModel, const ReportCriteria &rc)
0075 {
0076     const QDate &from = rc.from;
0077     const QDate &to = rc.to;
0078     QString delim = rc.delimiter;
0079     const QString cr = QStringLiteral("\n");
0080     const int64_t intervalLength = from.daysTo(to) + 1;
0081     QMap<QString, QVector<int64_t>> secsForUid;
0082     QMap<QString, QString> uidForName;
0083 
0084     QString retval;
0085 
0086     // Step 1: Prepare two hashmaps:
0087     // * "uid -> seconds each day": used while traversing events, as uid is their id
0088     //                              "seconds each day" are stored in a vector
0089     // * "name -> uid", ordered by name: used when creating the csv file at the end
0090     auto tasks = projectModel->tasksModel()->getAllTasks();
0091     qCDebug(KTT_LOG) << "Tasks count: " << tasks.size();
0092     for (Task *task : tasks) {
0093         qCDebug(KTT_LOG) << ", Task Name: " << task->name() << ", UID: " << task->uid();
0094         // uid -> seconds each day
0095         // * Init each element to zero
0096         QVector<int64_t> vector(intervalLength, 0);
0097         secsForUid[task->uid()] = vector;
0098 
0099         // name -> uid
0100         // * Create task fullname concatenating each parent's name
0101         QString fullName;
0102         Task *parentTask;
0103         parentTask = task;
0104         fullName += parentTask->name();
0105         parentTask = parentTask->parentTask();
0106         while (parentTask) {
0107             fullName = parentTask->name() + QStringLiteral("->") + fullName;
0108             qCDebug(KTT_LOG) << "Fullname(inside): " << fullName;
0109             parentTask = parentTask->parentTask();
0110             qCDebug(KTT_LOG) << "Parent task: " << parentTask;
0111         }
0112 
0113         uidForName[fullName] = task->uid();
0114 
0115         qCDebug(KTT_LOG) << "Fullname(end): " << fullName;
0116     }
0117 
0118     qCDebug(KTT_LOG) << "secsForUid" << secsForUid;
0119     qCDebug(KTT_LOG) << "uidForName" << uidForName;
0120 
0121     std::unique_ptr<FileCalendar> calendar = projectModel->asCalendar(QUrl());
0122 
0123     // Step 2: For each date, get the events and calculate the seconds
0124     // Store the seconds using secsForUid hashmap, so we don't need to translate
0125     // uids We rely on rawEventsForDate to get the events
0126     qCDebug(KTT_LOG) << "Let's iterate for each date: ";
0127     int dayCount = 0;
0128     for (QDate mdate = from; mdate.daysTo(to) >= 0; mdate = mdate.addDays(1)) {
0129         if (dayCount++ > 365 * 100) {
0130             return QStringLiteral("too many days to process");
0131         }
0132 
0133         qCDebug(KTT_LOG) << mdate.toString();
0134         for (const auto &event : calendar->rawEventsForDate(mdate)) {
0135             qCDebug(KTT_LOG) << "Summary: " << event->summary() << ", Related to uid: " << event->relatedTo();
0136             qCDebug(KTT_LOG) << "Today's seconds: " << todaySeconds(mdate, event);
0137             secsForUid[event->relatedTo()][from.daysTo(mdate)] += todaySeconds(mdate, event);
0138         }
0139     }
0140 
0141     // Step 3: For each task, generate the matching row for the CSV file
0142     // We use the two hashmaps to have direct access using the task name
0143 
0144     // First CSV file line
0145     // FIXME: localize strings and date formats
0146     retval.append(QStringLiteral("\"Task name\""));
0147     for (int i = 0; i < intervalLength; ++i) {
0148         retval.append(delim);
0149         retval.append(from.addDays(i).toString());
0150     }
0151     retval.append(cr);
0152 
0153     // Rest of the CSV file
0154     QMapIterator<QString, QString> nameUid(uidForName);
0155     double time;
0156     while (nameUid.hasNext()) {
0157         nameUid.next();
0158         retval.append(rc.quote + nameUid.key() + rc.quote);
0159         qCDebug(KTT_LOG) << nameUid.key() << ": " << nameUid.value() <<  Qt::endl;
0160 
0161         for (int day = 0; day < intervalLength; day++) {
0162             qCDebug(KTT_LOG) << "Secs for day " << day << ":" << secsForUid[nameUid.value()][day];
0163             retval.append(delim);
0164             time = secsForUid[nameUid.value()][day] / 60.0;
0165             retval.append(formatTime(time, rc.decimalMinutes));
0166         }
0167 
0168         retval.append(cr);
0169     }
0170 
0171     qDebug() << "Retval is \n" << retval;
0172     return retval;
0173 }