Skip to content

Commit 448088b

Browse files
committed
demo invoicing workflow 👍
1 parent 385c0d7 commit 448088b

File tree

5 files changed

+73
-18
lines changed

5 files changed

+73
-18
lines changed

app/Tuttle.py

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
from loguru import logger
23
from textwrap import dedent
34
from pathlib import Path
@@ -17,6 +18,8 @@
1718
Icon,
1819
Dropdown,
1920
Markdown,
21+
FilePicker,
22+
FilePickerResultEvent,
2023
)
2124
from flet import icons, colors, dropdown
2225

@@ -178,15 +181,43 @@ def __init__(
178181
app: App,
179182
):
180183
super().__init__(app)
184+
self.calendar_file_path = None
181185

182186
def update(self):
183187
super().update()
184188

189+
def on_click_generate_invoices(self, event):
190+
"""Generate invoices for the selected project and date range."""
191+
logger.info("Generate invoices clicked")
192+
if not self.calendar_file_path:
193+
logger.error("No calendar file selected!")
194+
return
195+
self.app.con.billing(
196+
project_tags=[self.project_select.value],
197+
period_start=str(self.date_from_select.get_date()),
198+
period_end=str(self.date_to_select.get_date()),
199+
timetracking_method="file_calendar",
200+
calendar_file_path=self.calendar_file_path,
201+
)
202+
203+
def on_pick_calendar_file(self, event: FilePickerResultEvent):
204+
"""Handle the result of the calendar file picker."""
205+
if event.files:
206+
logger.info(f"Calendar file picked: {event.files[0].path}")
207+
self.calendar_file_path = Path(event.files[0].path)
208+
else:
209+
logger.info("Cancelled!")
210+
185211
def update_content(self):
186212
super().update_content()
187213

188214
self.main_column.controls.clear()
189215

216+
self.calendar_file_picker = FilePicker(on_result=self.on_pick_calendar_file)
217+
218+
self.app.page.overlay.append(self.calendar_file_picker)
219+
220+
# select project
190221
projects = self.app.con.query(Project)
191222

192223
self.project_select = Dropdown(
@@ -196,8 +227,8 @@ def update_content(self):
196227
autofocus=True,
197228
)
198229

199-
self.date_from_select = widgets.DateSelector()
200-
self.date_to_select = widgets.DateSelector()
230+
self.date_from_select = widgets.DateSelector(preset=datetime.date(2022, 1, 1))
231+
self.date_to_select = widgets.DateSelector(preset=datetime.date(2022, 12, 31))
201232

202233
self.main_column.controls = [
203234
Row(
@@ -220,6 +251,17 @@ def update_content(self):
220251
)
221252
]
222253
),
254+
Row(
255+
[
256+
ElevatedButton(
257+
"Pick Calendar File",
258+
icon=icons.UPLOAD_FILE,
259+
on_click=lambda _: self.calendar_file_picker.pick_files(
260+
allow_multiple=False
261+
),
262+
),
263+
]
264+
),
223265
Row(
224266
[
225267
views.make_card(
@@ -260,24 +302,13 @@ def update_content(self):
260302
ElevatedButton(
261303
"Generate invoice",
262304
icon=icons.EDIT_NOTE,
263-
on_click=self.generate_invoices_clicked,
305+
on_click=self.on_click_generate_invoices,
264306
),
265307
]
266308
),
267309
]
268310
self.update()
269311

270-
def generate_invoices_clicked(self, event):
271-
"""Generate invoices for the selected project and date range."""
272-
logger.info("Generate invoices clicked")
273-
self.app.con.billing(
274-
project_tags=[self.project_select.value],
275-
period_start=self.date_from_select.get_date(),
276-
period_end=self.date_to_select.get_date(),
277-
timetracking_method="file_calendar",
278-
calendar_file_path=None,
279-
)
280-
281312

282313
def main(page: Page):
283314

@@ -349,7 +380,7 @@ def main(page: Page):
349380
(
350381
NavigationRailDestination(
351382
icon=icons.OUTGOING_MAIL,
352-
label="Invocing",
383+
label="Invoicing",
353384
),
354385
InvoicingPage(app),
355386
),

app/widgets.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
import pytz
23

34
from flet import (
45
UserControl,
@@ -15,26 +16,36 @@
1516
class DateSelector(UserControl):
1617
"""Timeframe selector."""
1718

18-
def __init__(self):
19+
def __init__(
20+
self,
21+
preset: datetime.date = None,
22+
):
1923
super().__init__()
2024

25+
self.preset = preset
2126
self.day_dropdown = Dropdown(
2227
label="D",
2328
options=[dropdown.Option(day) for day in range(1, 32)],
2429
width=50,
2530
)
31+
if self.preset:
32+
self.day_dropdown.value = str(self.preset.day)
2633

2734
self.month_dropdown = Dropdown(
2835
label="M",
2936
options=[dropdown.Option(month) for month in range(1, 13)],
3037
width=50,
3138
)
39+
if self.preset:
40+
self.month_dropdown.value = str(self.preset.month)
3241

3342
self.year_dropdown = Dropdown(
3443
label="Y",
3544
options=[dropdown.Option(year) for year in range(2015, 2025)],
3645
width=100,
3746
)
47+
if self.preset:
48+
self.year_dropdown.value = str(self.preset.year)
3849

3950
self.view = Container(
4051
content=Row(
@@ -54,11 +65,18 @@ def __init__(self):
5465
def build(self):
5566
return self.view
5667

57-
def get_date(self) -> datetime.date:
68+
def get_date(
69+
self,
70+
as_datetime: bool = False,
71+
):
5872
"""Return the selected timeframe."""
5973
date = datetime.date(
6074
year=int(self.year_dropdown.value),
6175
month=int(self.month_dropdown.value),
6276
day=int(self.day_dropdown.value),
6377
)
78+
if as_datetime:
79+
date = datetime.datetime.combine(date, datetime.time())
80+
date = pytz.utc.localize(date)
81+
6482
return date

tuttle/controller.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ def billing(
227227
path=calendar_file_path,
228228
name=calendar_file_path.stem,
229229
)
230+
if timetracking_calendar.to_data().empty:
231+
raise ValueError(
232+
f"empty calendar loaded from file {calendar_file_path}"
233+
)
234+
logger.info(f"calendar data: \\ {timetracking_calendar.to_data()}")
230235
else:
231236
raise ValueError(f"unsupported time tracking method: {timetracking_method}")
232237

tuttle/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ class Timesheet(SQLModel, table=True):
288288
id: Optional[int] = Field(default=None, primary_key=True)
289289
title: str
290290
date: datetime.date
291-
period: str
291+
# period: str
292292
# table: pandas.DataFrame
293293
# TODO: store dataframe as dict
294294
# table: Dict = Field(default={}, sa_column=sqlalchemy.Column(sqlalchemy.JSON))

tuttle/timetracking.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def generate_timesheet(
5252
raise ValueError(f"unknown source: {source}")
5353
tag_query = f"tag == '{project.tag}'"
5454
if period_end:
55+
print(f"timetracking_data.index: {timetracking_data.index}")
5556
ts_table = (
5657
timetracking_data.loc[period_start:period_end].query(tag_query).sort_index()
5758
)

0 commit comments

Comments
 (0)