Timesheet report tweaks
* Allow 'this week', 'this month', 'this year' granularity in Timesheet reports. * Default date range to start from this month. * Allow 'All Projects' for timesheet reports.
This commit is contained in:
parent
1e12cae78e
commit
9dc0a620be
5 changed files with 193 additions and 34 deletions
|
|
@ -1,3 +1,8 @@
|
||||||
|
# 0.6.3
|
||||||
|
|
||||||
|
* Allow 'this week', 'this month', 'this year' granularity in Timesheet reports. Default date range to start from this month.
|
||||||
|
* Allow 'All Projects' for timesheet reports.
|
||||||
|
|
||||||
# 0.6.2
|
# 0.6.2
|
||||||
|
|
||||||
* Ensure that adding a document whilst on an older date page, uses that date as its upload date
|
* Ensure that adding a document whilst on an older date page, uses that date as its upload date
|
||||||
|
|
|
||||||
|
|
@ -1149,6 +1149,53 @@ class DBManager:
|
||||||
for r in rows
|
for r in rows
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def time_report_all(
|
||||||
|
self,
|
||||||
|
start_date_iso: str,
|
||||||
|
end_date_iso: str,
|
||||||
|
granularity: str = "day", # 'day' | 'week' | 'month'
|
||||||
|
) -> list[tuple[str, str, str, str, int]]:
|
||||||
|
"""
|
||||||
|
Return (project_name, time_period, activity_name, note, total_minutes)
|
||||||
|
across *all* projects between start and end, grouped by project + period + activity.
|
||||||
|
"""
|
||||||
|
if granularity == "day":
|
||||||
|
bucket_expr = "page_date"
|
||||||
|
elif granularity == "week":
|
||||||
|
bucket_expr = "strftime('%Y-%W', page_date)"
|
||||||
|
else: # month
|
||||||
|
bucket_expr = "substr(page_date, 1, 7)" # YYYY-MM
|
||||||
|
|
||||||
|
cur = self.conn.cursor()
|
||||||
|
rows = cur.execute(
|
||||||
|
f"""
|
||||||
|
SELECT
|
||||||
|
p.name AS project_name,
|
||||||
|
{bucket_expr} AS bucket,
|
||||||
|
a.name AS activity_name,
|
||||||
|
t.note AS note,
|
||||||
|
SUM(t.minutes) AS total_minutes
|
||||||
|
FROM time_log t
|
||||||
|
JOIN projects p ON p.id = t.project_id
|
||||||
|
JOIN activities a ON a.id = t.activity_id
|
||||||
|
WHERE t.page_date BETWEEN ? AND ?
|
||||||
|
GROUP BY p.id, bucket, activity_name
|
||||||
|
ORDER BY LOWER(p.name), bucket, LOWER(activity_name);
|
||||||
|
""", # nosec
|
||||||
|
(start_date_iso, end_date_iso),
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
return [
|
||||||
|
(
|
||||||
|
r["project_name"],
|
||||||
|
r["bucket"],
|
||||||
|
r["activity_name"],
|
||||||
|
r["note"],
|
||||||
|
r["total_minutes"],
|
||||||
|
)
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
if self.conn is not None:
|
if self.conn is not None:
|
||||||
self.conn.close()
|
self.conn.close()
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,11 @@
|
||||||
"by_month": "by month",
|
"by_month": "by month",
|
||||||
"by_week": "by week",
|
"by_week": "by week",
|
||||||
"date_range": "Date range",
|
"date_range": "Date range",
|
||||||
|
"custom_range": "Custom",
|
||||||
|
"this_week": "This week",
|
||||||
|
"this_month": "This month",
|
||||||
|
"this_year": "This year",
|
||||||
|
"all_projects": "All projects",
|
||||||
"delete_activity": "Delete activity",
|
"delete_activity": "Delete activity",
|
||||||
"delete_activity_confirm": "Are you sure you want to delete this activity?",
|
"delete_activity_confirm": "Are you sure you want to delete this activity?",
|
||||||
"delete_activity_title": "Delete activity - are you sure?",
|
"delete_activity_title": "Delete activity - are you sure?",
|
||||||
|
|
|
||||||
|
|
@ -1001,23 +1001,41 @@ class TimeReportDialog(QDialog):
|
||||||
form = QFormLayout()
|
form = QFormLayout()
|
||||||
# Project
|
# Project
|
||||||
self.project_combo = QComboBox()
|
self.project_combo = QComboBox()
|
||||||
|
self.project_combo.addItem(strings._("all_projects"), None)
|
||||||
for proj_id, name in self._db.list_projects():
|
for proj_id, name in self._db.list_projects():
|
||||||
self.project_combo.addItem(name, proj_id)
|
self.project_combo.addItem(name, proj_id)
|
||||||
form.addRow(strings._("project"), self.project_combo)
|
form.addRow(strings._("project"), self.project_combo)
|
||||||
|
|
||||||
# Date range
|
# Date range
|
||||||
today = QDate.currentDate()
|
today = QDate.currentDate()
|
||||||
self.from_date = QDateEdit(today.addDays(-7))
|
start_of_month = QDate(today.year(), today.month(), 1)
|
||||||
|
|
||||||
|
self.range_preset = QComboBox()
|
||||||
|
self.range_preset.addItem(strings._("custom_range"), "custom")
|
||||||
|
self.range_preset.addItem(strings._("today"), "today")
|
||||||
|
self.range_preset.addItem(strings._("this_week"), "this_week")
|
||||||
|
self.range_preset.addItem(strings._("this_month"), "this_month")
|
||||||
|
self.range_preset.addItem(strings._("this_year"), "this_year")
|
||||||
|
self.range_preset.currentIndexChanged.connect(self._on_range_preset_changed)
|
||||||
|
|
||||||
|
self.from_date = QDateEdit(start_of_month)
|
||||||
self.from_date.setCalendarPopup(True)
|
self.from_date.setCalendarPopup(True)
|
||||||
self.to_date = QDateEdit(today)
|
self.to_date = QDateEdit(today)
|
||||||
self.to_date.setCalendarPopup(True)
|
self.to_date.setCalendarPopup(True)
|
||||||
|
|
||||||
range_row = QHBoxLayout()
|
range_row = QHBoxLayout()
|
||||||
|
range_row.addWidget(self.range_preset)
|
||||||
range_row.addWidget(self.from_date)
|
range_row.addWidget(self.from_date)
|
||||||
range_row.addWidget(QLabel("—"))
|
range_row.addWidget(QLabel("—"))
|
||||||
range_row.addWidget(self.to_date)
|
range_row.addWidget(self.to_date)
|
||||||
|
|
||||||
form.addRow(strings._("date_range"), range_row)
|
form.addRow(strings._("date_range"), range_row)
|
||||||
|
|
||||||
|
# After widgets are created, choose default preset
|
||||||
|
idx = self.range_preset.findData("this_month")
|
||||||
|
if idx != -1:
|
||||||
|
self.range_preset.setCurrentIndex(idx)
|
||||||
|
|
||||||
# Granularity
|
# Granularity
|
||||||
self.granularity = QComboBox()
|
self.granularity = QComboBox()
|
||||||
self.granularity.addItem(strings._("by_day"), "day")
|
self.granularity.addItem(strings._("by_day"), "day")
|
||||||
|
|
@ -1046,9 +1064,10 @@ class TimeReportDialog(QDialog):
|
||||||
|
|
||||||
# Table
|
# Table
|
||||||
self.table = QTableWidget()
|
self.table = QTableWidget()
|
||||||
self.table.setColumnCount(4)
|
self.table.setColumnCount(5)
|
||||||
self.table.setHorizontalHeaderLabels(
|
self.table.setHorizontalHeaderLabels(
|
||||||
[
|
[
|
||||||
|
strings._("project"),
|
||||||
strings._("time_period"),
|
strings._("time_period"),
|
||||||
strings._("activity"),
|
strings._("activity"),
|
||||||
strings._("note"),
|
strings._("note"),
|
||||||
|
|
@ -1058,8 +1077,9 @@ class TimeReportDialog(QDialog):
|
||||||
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||||
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||||
self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
|
self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
|
||||||
|
self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
|
||||||
self.table.horizontalHeader().setSectionResizeMode(
|
self.table.horizontalHeader().setSectionResizeMode(
|
||||||
3, QHeaderView.ResizeToContents
|
4, QHeaderView.ResizeToContents
|
||||||
)
|
)
|
||||||
root.addWidget(self.table, 1)
|
root.addWidget(self.table, 1)
|
||||||
|
|
||||||
|
|
@ -1075,39 +1095,110 @@ class TimeReportDialog(QDialog):
|
||||||
close_row.addWidget(close_btn)
|
close_row.addWidget(close_btn)
|
||||||
root.addLayout(close_row)
|
root.addLayout(close_row)
|
||||||
|
|
||||||
|
def _on_range_preset_changed(self, index: int) -> None:
|
||||||
|
preset = self.range_preset.currentData()
|
||||||
|
today = QDate.currentDate()
|
||||||
|
|
||||||
|
if preset == "today":
|
||||||
|
start = end = today
|
||||||
|
|
||||||
|
elif preset == "this_week":
|
||||||
|
# Monday-based week, clamp end to today
|
||||||
|
# dayOfWeek(): Monday=1, Sunday=7
|
||||||
|
start = today.addDays(1 - today.dayOfWeek())
|
||||||
|
end = today
|
||||||
|
|
||||||
|
elif preset == "this_month":
|
||||||
|
start = QDate(today.year(), today.month(), 1)
|
||||||
|
end = today
|
||||||
|
|
||||||
|
elif preset == "this_year":
|
||||||
|
start = QDate(today.year(), 1, 1)
|
||||||
|
end = today
|
||||||
|
|
||||||
|
else: # "custom" – leave fields as user-set
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update date edits without triggering anything else
|
||||||
|
self.from_date.blockSignals(True)
|
||||||
|
self.to_date.blockSignals(True)
|
||||||
|
self.from_date.setDate(start)
|
||||||
|
self.to_date.setDate(end)
|
||||||
|
self.from_date.blockSignals(False)
|
||||||
|
self.to_date.blockSignals(False)
|
||||||
|
|
||||||
def _run_report(self):
|
def _run_report(self):
|
||||||
idx = self.project_combo.currentIndex()
|
idx = self.project_combo.currentIndex()
|
||||||
if idx < 0:
|
if idx < 0:
|
||||||
return
|
return
|
||||||
proj_id = int(self.project_combo.itemData(idx))
|
|
||||||
|
|
||||||
|
proj_data = self.project_combo.itemData(idx)
|
||||||
start = self.from_date.date().toString("yyyy-MM-dd")
|
start = self.from_date.date().toString("yyyy-MM-dd")
|
||||||
end = self.to_date.date().toString("yyyy-MM-dd")
|
end = self.to_date.date().toString("yyyy-MM-dd")
|
||||||
gran = self.granularity.currentData()
|
gran = self.granularity.currentData()
|
||||||
|
|
||||||
# Keep human-friendly copies for PDF header
|
|
||||||
self._last_project_name = self.project_combo.currentText()
|
|
||||||
self._last_start = start
|
self._last_start = start
|
||||||
self._last_end = end
|
self._last_end = end
|
||||||
self._last_gran_label = self.granularity.currentText()
|
self._last_gran_label = self.granularity.currentText()
|
||||||
|
|
||||||
rows = self._db.time_report(proj_id, start, end, gran)
|
rows_for_table: list[tuple[str, str, str, str, int]] = []
|
||||||
|
|
||||||
self._last_rows = rows
|
if proj_data is None:
|
||||||
self._last_total_minutes = sum(r[3] for r in rows)
|
# All projects
|
||||||
|
self._last_all_projects = True
|
||||||
|
self._last_project_name = strings._("all_projects")
|
||||||
|
rows_for_table = self._db.time_report_all(start, end, gran)
|
||||||
|
else:
|
||||||
|
self._last_all_projects = False
|
||||||
|
proj_id = int(proj_data)
|
||||||
|
project_name = self.project_combo.currentText()
|
||||||
|
self._last_project_name = project_name
|
||||||
|
|
||||||
self.table.setRowCount(len(rows))
|
per_project_rows = self._db.time_report(proj_id, start, end, gran)
|
||||||
for i, (time_period, activity_name, note, minutes) in enumerate(rows):
|
# Adapt DB rows (period, activity, note, minutes) → include project
|
||||||
|
rows_for_table = [
|
||||||
|
(project_name, period, activity, note, minutes)
|
||||||
|
for (period, activity, note, minutes) in per_project_rows
|
||||||
|
]
|
||||||
|
|
||||||
|
# Store for export
|
||||||
|
self._last_rows = rows_for_table
|
||||||
|
self._last_total_minutes = sum(r[4] for r in rows_for_table)
|
||||||
|
|
||||||
|
# Per-project totals
|
||||||
|
self._last_project_totals = defaultdict(int)
|
||||||
|
for project, _period, _activity, _note, minutes in rows_for_table:
|
||||||
|
self._last_project_totals[project] += minutes
|
||||||
|
|
||||||
|
# Populate table
|
||||||
|
self.table.setRowCount(len(rows_for_table))
|
||||||
|
for i, (project, time_period, activity_name, note, minutes) in enumerate(
|
||||||
|
rows_for_table
|
||||||
|
):
|
||||||
hrs = minutes / 60.0
|
hrs = minutes / 60.0
|
||||||
self.table.setItem(i, 0, QTableWidgetItem(time_period))
|
self.table.setItem(i, 0, QTableWidgetItem(project))
|
||||||
self.table.setItem(i, 1, QTableWidgetItem(activity_name))
|
self.table.setItem(i, 1, QTableWidgetItem(time_period))
|
||||||
self.table.setItem(i, 2, QTableWidgetItem(note))
|
self.table.setItem(i, 2, QTableWidgetItem(activity_name))
|
||||||
self.table.setItem(i, 3, QTableWidgetItem(f"{hrs:.2f}"))
|
self.table.setItem(i, 3, QTableWidgetItem(note))
|
||||||
|
self.table.setItem(i, 4, QTableWidgetItem(f"{hrs:.2f}"))
|
||||||
|
|
||||||
|
# Summary label – include per-project totals when in "all projects" mode
|
||||||
total_hours = self._last_total_minutes / 60.0
|
total_hours = self._last_total_minutes / 60.0
|
||||||
self.total_label.setText(
|
if self._last_all_projects:
|
||||||
strings._("time_report_total").format(hours=total_hours)
|
per_project_bits = [
|
||||||
)
|
f"{proj}: {mins/60.0:.2f}h"
|
||||||
|
for proj, mins in sorted(self._last_project_totals.items())
|
||||||
|
]
|
||||||
|
self.total_label.setText(
|
||||||
|
strings._("time_report_total").format(hours=total_hours)
|
||||||
|
+ " ("
|
||||||
|
+ ", ".join(per_project_bits)
|
||||||
|
+ ")"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.total_label.setText(
|
||||||
|
strings._("time_report_total").format(hours=total_hours)
|
||||||
|
)
|
||||||
|
|
||||||
def _export_csv(self):
|
def _export_csv(self):
|
||||||
if not self._last_rows:
|
if not self._last_rows:
|
||||||
|
|
@ -1136,6 +1227,7 @@ class TimeReportDialog(QDialog):
|
||||||
# Header
|
# Header
|
||||||
writer.writerow(
|
writer.writerow(
|
||||||
[
|
[
|
||||||
|
strings._("project"),
|
||||||
strings._("time_period"),
|
strings._("time_period"),
|
||||||
strings._("activity"),
|
strings._("activity"),
|
||||||
strings._("note"),
|
strings._("note"),
|
||||||
|
|
@ -1144,9 +1236,17 @@ class TimeReportDialog(QDialog):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Data rows
|
# Data rows
|
||||||
for time_period, activity_name, note, minutes in self._last_rows:
|
for (
|
||||||
|
project,
|
||||||
|
time_period,
|
||||||
|
activity_name,
|
||||||
|
note,
|
||||||
|
minutes,
|
||||||
|
) in self._last_rows:
|
||||||
hours = minutes / 60.0
|
hours = minutes / 60.0
|
||||||
writer.writerow([time_period, activity_name, note, f"{hours:.2f}"])
|
writer.writerow(
|
||||||
|
[project, time_period, activity_name, note, f"{hours:.2f}"]
|
||||||
|
)
|
||||||
|
|
||||||
# Blank line + total
|
# Blank line + total
|
||||||
total_hours = self._last_total_minutes / 60.0
|
total_hours = self._last_total_minutes / 60.0
|
||||||
|
|
@ -1181,7 +1281,7 @@ class TimeReportDialog(QDialog):
|
||||||
|
|
||||||
# ---------- Build chart image (hours per period) ----------
|
# ---------- Build chart image (hours per period) ----------
|
||||||
per_period_minutes: dict[str, int] = defaultdict(int)
|
per_period_minutes: dict[str, int] = defaultdict(int)
|
||||||
for period, _activity, note, minutes in self._last_rows:
|
for _project, period, _activity, note, minutes in self._last_rows:
|
||||||
per_period_minutes[period] += minutes
|
per_period_minutes[period] += minutes
|
||||||
|
|
||||||
periods = sorted(per_period_minutes.keys())
|
periods = sorted(per_period_minutes.keys())
|
||||||
|
|
@ -1282,10 +1382,11 @@ class TimeReportDialog(QDialog):
|
||||||
|
|
||||||
# Table rows (period, activity, hours)
|
# Table rows (period, activity, hours)
|
||||||
row_html_parts: list[str] = []
|
row_html_parts: list[str] = []
|
||||||
for period, activity, note, minutes in self._last_rows:
|
for project, period, activity, note, minutes in self._last_rows:
|
||||||
hours = minutes / 60.0
|
hours = minutes / 60.0
|
||||||
row_html_parts.append(
|
row_html_parts.append(
|
||||||
"<tr>"
|
"<tr>"
|
||||||
|
f"<td>{html.escape(project)}</td>"
|
||||||
f"<td>{html.escape(period)}</td>"
|
f"<td>{html.escape(period)}</td>"
|
||||||
f"<td>{html.escape(activity)}</td>"
|
f"<td>{html.escape(activity)}</td>"
|
||||||
f"<td style='text-align:right'>{hours:.2f}</td>"
|
f"<td style='text-align:right'>{hours:.2f}</td>"
|
||||||
|
|
@ -1343,6 +1444,7 @@ class TimeReportDialog(QDialog):
|
||||||
<p><img src="chart" class="chart" /></p>
|
<p><img src="chart" class="chart" /></p>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>{html.escape(strings._("project"))}</th>
|
||||||
<th>{html.escape(strings._("time_period"))}</th>
|
<th>{html.escape(strings._("time_period"))}</th>
|
||||||
<th>{html.escape(strings._("activity"))}</th>
|
<th>{html.escape(strings._("activity"))}</th>
|
||||||
<th>{html.escape(strings._("hours"))}</th>
|
<th>{html.escape(strings._("hours"))}</th>
|
||||||
|
|
|
||||||
|
|
@ -1190,7 +1190,7 @@ def test_time_report_dialog_creation(qtbot, fresh_db):
|
||||||
dialog = TimeReportDialog(fresh_db)
|
dialog = TimeReportDialog(fresh_db)
|
||||||
qtbot.addWidget(dialog)
|
qtbot.addWidget(dialog)
|
||||||
|
|
||||||
assert dialog.project_combo.count() == 0
|
assert dialog.project_combo.count() == 1
|
||||||
assert dialog.granularity.count() == 3 # day, week, month
|
assert dialog.granularity.count() == 3 # day, week, month
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1202,18 +1202,18 @@ def test_time_report_dialog_loads_projects(qtbot, fresh_db):
|
||||||
dialog = TimeReportDialog(fresh_db)
|
dialog = TimeReportDialog(fresh_db)
|
||||||
qtbot.addWidget(dialog)
|
qtbot.addWidget(dialog)
|
||||||
|
|
||||||
assert dialog.project_combo.count() == 2
|
assert dialog.project_combo.count() == 3
|
||||||
|
|
||||||
|
|
||||||
def test_time_report_dialog_default_date_range(qtbot, fresh_db):
|
def test_time_report_dialog_default_date_range(qtbot, fresh_db):
|
||||||
"""Dialog defaults to last 7 days."""
|
"""Dialog defaults to start of month."""
|
||||||
dialog = TimeReportDialog(fresh_db)
|
dialog = TimeReportDialog(fresh_db)
|
||||||
qtbot.addWidget(dialog)
|
qtbot.addWidget(dialog)
|
||||||
|
|
||||||
today = QDate.currentDate()
|
today = QDate.currentDate()
|
||||||
week_ago = today.addDays(-7)
|
start_of_month = QDate(today.year(), today.month(), 1)
|
||||||
|
|
||||||
assert dialog.from_date.date() == week_ago
|
assert dialog.from_date.date() == start_of_month
|
||||||
assert dialog.to_date.date() == today
|
assert dialog.to_date.date() == today
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1235,7 +1235,7 @@ def test_time_report_dialog_run_report(qtbot, fresh_db):
|
||||||
dialog._run_report()
|
dialog._run_report()
|
||||||
|
|
||||||
assert dialog.table.rowCount() == 1
|
assert dialog.table.rowCount() == 1
|
||||||
assert "Activity" in dialog.table.item(0, 1).text()
|
assert "Activity" in dialog.table.item(0, 2).text()
|
||||||
assert "1.5" in dialog.total_label.text() or "1.50" in dialog.total_label.text()
|
assert "1.5" in dialog.total_label.text() or "1.50" in dialog.total_label.text()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1423,7 +1423,7 @@ def test_time_report_dialog_granularity_week(qtbot, fresh_db):
|
||||||
|
|
||||||
# Should aggregate to single week
|
# Should aggregate to single week
|
||||||
assert dialog.table.rowCount() == 1
|
assert dialog.table.rowCount() == 1
|
||||||
hours_text = dialog.table.item(0, 3).text()
|
hours_text = dialog.table.item(0, 4).text()
|
||||||
assert "2.5" in hours_text or "2.50" in hours_text
|
assert "2.5" in hours_text or "2.50" in hours_text
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1451,7 +1451,7 @@ def test_time_report_dialog_granularity_month(qtbot, fresh_db):
|
||||||
|
|
||||||
# Should aggregate to single month
|
# Should aggregate to single month
|
||||||
assert dialog.table.rowCount() == 1
|
assert dialog.table.rowCount() == 1
|
||||||
hours_text = dialog.table.item(0, 3).text()
|
hours_text = dialog.table.item(0, 4).text()
|
||||||
assert "2.5" in hours_text or "2.50" in hours_text
|
assert "2.5" in hours_text or "2.50" in hours_text
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1937,7 +1937,7 @@ def test_time_report_dialog_stores_report_state(qtbot, fresh_db):
|
||||||
dialog = TimeReportDialog(fresh_db)
|
dialog = TimeReportDialog(fresh_db)
|
||||||
qtbot.addWidget(dialog)
|
qtbot.addWidget(dialog)
|
||||||
|
|
||||||
dialog.project_combo.setCurrentIndex(0)
|
dialog.project_combo.setCurrentIndex(1)
|
||||||
dialog.from_date.setDate(QDate.fromString(_today(), "yyyy-MM-dd"))
|
dialog.from_date.setDate(QDate.fromString(_today(), "yyyy-MM-dd"))
|
||||||
dialog.to_date.setDate(QDate.fromString(_today(), "yyyy-MM-dd"))
|
dialog.to_date.setDate(QDate.fromString(_today(), "yyyy-MM-dd"))
|
||||||
dialog.granularity.setCurrentIndex(1) # week
|
dialog.granularity.setCurrentIndex(1) # week
|
||||||
|
|
@ -2154,10 +2154,10 @@ def test_full_workflow_add_project_activity_log_report(
|
||||||
|
|
||||||
# Verify report
|
# Verify report
|
||||||
assert report_dialog.table.rowCount() == 1
|
assert report_dialog.table.rowCount() == 1
|
||||||
assert "Test Activity" in report_dialog.table.item(0, 1).text()
|
assert "Test Activity" in report_dialog.table.item(0, 2).text()
|
||||||
assert (
|
assert (
|
||||||
"2.5" in report_dialog.table.item(0, 3).text()
|
"2.5" in report_dialog.table.item(0, 4).text()
|
||||||
or "2.50" in report_dialog.table.item(0, 3).text()
|
or "2.50" in report_dialog.table.item(0, 4).text()
|
||||||
)
|
)
|
||||||
|
|
||||||
# 5. Export CSV
|
# 5. Export CSV
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue