papi-projects 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {papi_projects-0.4.1 → papi_projects-0.4.2}/PKG-INFO +1 -1
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi/__init__.py +44 -29
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi/wrappers.py +1 -1
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi_projects.egg-info/PKG-INFO +1 -1
- {papi_projects-0.4.1 → papi_projects-0.4.2}/pyproject.toml +1 -1
- papi_projects-0.4.2/scripts/generate_timesheet.py +154 -0
- papi_projects-0.4.1/scripts/generate_timesheet.py +0 -194
- {papi_projects-0.4.1 → papi_projects-0.4.2}/README.md +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi/project.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi/task.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi/user.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi/workorder.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi_projects.egg-info/SOURCES.txt +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi_projects.egg-info/dependency_links.txt +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi_projects.egg-info/entry_points.txt +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi_projects.egg-info/requires.txt +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/papi_projects.egg-info/top_level.txt +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/scripts/__init__.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/scripts/collate_toggl_hours.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/scripts/create_notion_project.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/scripts/create_notion_task.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/scripts/create_project.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/scripts/create_toggl_project.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/scripts/test_notion.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/setup.cfg +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/tests/test_project.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/tests/test_task.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/tests/test_user.py +0 -0
- {papi_projects-0.4.1 → papi_projects-0.4.2}/tests/test_wrappers.py +0 -0
|
@@ -1,41 +1,60 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import logging
|
|
3
|
-
from dotenv import dotenv_values
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
5
|
+
try:
|
|
6
|
+
import tomllib
|
|
7
|
+
except ModuleNotFoundError:
|
|
8
|
+
import tomli as tomllib
|
|
9
|
+
|
|
10
|
+
|
|
6
11
|
def load_config():
|
|
7
|
-
|
|
12
|
+
config_path = os.environ.get("PAPI_CONFIG_FILE")
|
|
13
|
+
if config_path:
|
|
14
|
+
path = Path(config_path).expanduser()
|
|
15
|
+
else:
|
|
16
|
+
path = Path.home() / ".config" / "papi" / "config.toml"
|
|
17
|
+
|
|
18
|
+
if not path.exists():
|
|
19
|
+
raise RuntimeError(f"Config file not found: {path}")
|
|
20
|
+
|
|
21
|
+
with path.open("rb") as f:
|
|
22
|
+
raw = tomllib.load(f)
|
|
23
|
+
|
|
8
24
|
config = {}
|
|
9
25
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"TOGGL_TRACK_API_KEY",
|
|
15
|
-
"TOGGL_TRACK_PASSWORD",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
26
|
+
toggl = raw.get("toggl", {})
|
|
27
|
+
notion = raw.get("notion", {})
|
|
28
|
+
|
|
29
|
+
mapping = {
|
|
30
|
+
"TOGGL_TRACK_API_KEY": toggl.get("api_key"),
|
|
31
|
+
"TOGGL_TRACK_PASSWORD": toggl.get("password"),
|
|
32
|
+
"TOGGL_TRACK_WORKSPACE": toggl.get("workspace"),
|
|
33
|
+
"NOTION_API_SECRET": notion.get("api_secret"),
|
|
34
|
+
"NOTION_CLIENTS_DB": notion.get("clients_db"),
|
|
35
|
+
"NOTION_PROJECTS_DB": notion.get("projects_db"),
|
|
36
|
+
"NOTION_TASKS_DB": notion.get("tasks_db"),
|
|
37
|
+
"NOTION_TEMPLATE_PAGE_ID": notion.get("template_page_id"),
|
|
38
|
+
"NOTION_WORKORDERS_DB": notion.get("workorders_db"),
|
|
39
|
+
"NOTION_TRAC_COSTS_DB": notion.get("trac_costs_db"),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for k, v in mapping.items():
|
|
43
|
+
if v is not None:
|
|
44
|
+
config[k] = v
|
|
45
|
+
|
|
46
|
+
for key in mapping.keys():
|
|
24
47
|
if key in os.environ:
|
|
25
48
|
config[key] = os.environ[key]
|
|
26
49
|
|
|
27
|
-
local_env_path = Path(__file__).parent / ".env"
|
|
28
|
-
if local_env_path.exists():
|
|
29
|
-
local_vals = dotenv_values(local_env_path)
|
|
30
|
-
for k, v in local_vals.items():
|
|
31
|
-
config.setdefault(k, v)
|
|
32
|
-
|
|
33
50
|
return config
|
|
34
51
|
|
|
52
|
+
|
|
35
53
|
config = load_config()
|
|
36
54
|
|
|
37
55
|
TOGGL_TRACK_API_KEY = config["TOGGL_TRACK_API_KEY"]
|
|
38
56
|
TOGGL_TRACK_PASSWORD = config["TOGGL_TRACK_PASSWORD"]
|
|
57
|
+
TOGGL_TRACK_WORKSPACE = config["TOGGL_TRACK_WORKSPACE"]
|
|
39
58
|
NOTION_API_SECRET = config["NOTION_API_SECRET"]
|
|
40
59
|
NOTION_CLIENTS_DB = config["NOTION_CLIENTS_DB"]
|
|
41
60
|
NOTION_PROJECTS_DB = config["NOTION_PROJECTS_DB"]
|
|
@@ -47,6 +66,7 @@ NOTION_TRAC_COSTS_DB = config["NOTION_TRAC_COSTS_DB"]
|
|
|
47
66
|
required = [
|
|
48
67
|
"TOGGL_TRACK_API_KEY",
|
|
49
68
|
"TOGGL_TRACK_PASSWORD",
|
|
69
|
+
"TOGGL_TRACK_WORKSPACE",
|
|
50
70
|
"NOTION_API_SECRET",
|
|
51
71
|
"NOTION_CLIENTS_DB",
|
|
52
72
|
"NOTION_PROJECTS_DB",
|
|
@@ -60,15 +80,14 @@ missing = [k for k in required if k not in config]
|
|
|
60
80
|
if missing:
|
|
61
81
|
raise RuntimeError(f"Missing required config values: {', '.join(missing)}")
|
|
62
82
|
|
|
83
|
+
|
|
63
84
|
def setup_logger(enable_logging: bool, log_level: str = 'INFO', log_file: str = None):
|
|
64
85
|
logger = logging.getLogger('papi')
|
|
65
86
|
|
|
66
87
|
if enable_logging:
|
|
67
|
-
# Convert log_level string to logging level
|
|
68
88
|
numeric_level = getattr(logging, log_level.upper(), logging.INFO)
|
|
69
89
|
logger.setLevel(numeric_level)
|
|
70
|
-
|
|
71
|
-
# Create handler
|
|
90
|
+
|
|
72
91
|
if log_file:
|
|
73
92
|
handler = logging.FileHandler(log_file)
|
|
74
93
|
else:
|
|
@@ -76,18 +95,14 @@ def setup_logger(enable_logging: bool, log_level: str = 'INFO', log_file: str =
|
|
|
76
95
|
|
|
77
96
|
handler.setLevel(numeric_level)
|
|
78
97
|
|
|
79
|
-
# Create formatter
|
|
80
98
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
81
99
|
handler.setFormatter(formatter)
|
|
82
100
|
|
|
83
|
-
# Add handler to logger if not already added
|
|
84
101
|
if not logger.handlers:
|
|
85
102
|
logger.addHandler(handler)
|
|
86
103
|
|
|
87
|
-
# Prevent propagation to root logger
|
|
88
104
|
logger.propagate = False
|
|
89
105
|
else:
|
|
90
|
-
# Set a higher log level to suppress lower-level logs
|
|
91
106
|
logger.setLevel(logging.WARNING)
|
|
92
107
|
|
|
93
108
|
return logger
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import pendulum
|
|
3
|
+
import warnings
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from papi.wrappers import TogglTrackWrapper, NotionWrapper
|
|
7
|
+
from papi import config, setup_logger
|
|
8
|
+
from papi.project import get_project_ids, check_project_id
|
|
9
|
+
from decimal import Decimal, ROUND_UP
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import tomllib
|
|
14
|
+
except ModuleNotFoundError:
|
|
15
|
+
import tomli as tomllib
|
|
16
|
+
|
|
17
|
+
def load_toggl_users_from_toml():
|
|
18
|
+
config_path = os.environ.get("PAPI_CONFIG_FILE")
|
|
19
|
+
if config_path:
|
|
20
|
+
path = Path(config_path).expanduser()
|
|
21
|
+
else:
|
|
22
|
+
path = Path.home() / ".config" / "papi" / "config.toml"
|
|
23
|
+
if not path.exists():
|
|
24
|
+
return []
|
|
25
|
+
with path.open("rb") as f:
|
|
26
|
+
raw = tomllib.load(f)
|
|
27
|
+
toggl = raw.get("toggl", {})
|
|
28
|
+
users = toggl.get("users", {})
|
|
29
|
+
out = []
|
|
30
|
+
if isinstance(users, dict):
|
|
31
|
+
for _, u in users.items():
|
|
32
|
+
if not isinstance(u, dict):
|
|
33
|
+
continue
|
|
34
|
+
name = u.get("name")
|
|
35
|
+
api_key = u.get("api_key")
|
|
36
|
+
if name and api_key:
|
|
37
|
+
out.append({"name": name, "api_key": api_key})
|
|
38
|
+
return out
|
|
39
|
+
|
|
40
|
+
def initialise_toggl(toggl_api_key, toggl_workspace):
|
|
41
|
+
toggl_api_password = config["TOGGL_TRACK_PASSWORD"]
|
|
42
|
+
toggl = TogglTrackWrapper(toggl_api_key, toggl_api_password)
|
|
43
|
+
toggl.set_default_workspace(toggl_workspace)
|
|
44
|
+
toggl.set_me()
|
|
45
|
+
return toggl
|
|
46
|
+
|
|
47
|
+
def calculate_cost(hours: float, hourly_rate: float) -> float:
|
|
48
|
+
cost = Decimal(hours * hourly_rate)
|
|
49
|
+
cost_rounded = cost.quantize(Decimal("0.01"), rounding=ROUND_UP)
|
|
50
|
+
return cost_rounded
|
|
51
|
+
|
|
52
|
+
def get_toggl_hours(start_time, end_time, toggl):
|
|
53
|
+
tracked_hours = toggl.get_user_hours(start_time=start_time, end_time=end_time)
|
|
54
|
+
projects = {p["id"]: p["name"] for p in toggl.get_user_projects() if p["id"] in tracked_hours}
|
|
55
|
+
hours_per_project = [(projects[t], tracked_hours[t]) for t in tracked_hours]
|
|
56
|
+
hours_dict = {}
|
|
57
|
+
for i, p in enumerate(hours_per_project):
|
|
58
|
+
project_id = get_project_ids([p[0]])
|
|
59
|
+
if len(project_id):
|
|
60
|
+
hours_dict[project_id[0]] = hours_per_project[i][1]
|
|
61
|
+
else:
|
|
62
|
+
hours_dict[p[0]] = hours_per_project[i][1]
|
|
63
|
+
return hours_dict
|
|
64
|
+
|
|
65
|
+
def main():
|
|
66
|
+
parser = argparse.ArgumentParser()
|
|
67
|
+
parser.add_argument("-s", "--start", type=str, required=True)
|
|
68
|
+
parser.add_argument("-e", "--end", type=str, default=False)
|
|
69
|
+
parser.add_argument("--disable-logging", action="store_true")
|
|
70
|
+
parser.add_argument("-o", "--output", type=str, required=True)
|
|
71
|
+
args = parser.parse_args()
|
|
72
|
+
|
|
73
|
+
if not args.disable_logging:
|
|
74
|
+
logger = setup_logger(True, "WARNING", None)
|
|
75
|
+
|
|
76
|
+
start_time = pendulum.parse(args.start).to_rfc3339_string()
|
|
77
|
+
end_time = pendulum.now().to_rfc3339_string() if not args.end else pendulum.parse(args.end).to_rfc3339_string()
|
|
78
|
+
|
|
79
|
+
if pendulum.now() < pendulum.parse(args.start).add(months=3):
|
|
80
|
+
toggl_workspace = config["TOGGL_TRACK_WORKSPACE"]
|
|
81
|
+
toggl_users = load_toggl_users_from_toml()
|
|
82
|
+
if not toggl_users:
|
|
83
|
+
toggl_users = [{"name": "Self", "api_key": config["TOGGL_TRACK_API_KEY"]}]
|
|
84
|
+
|
|
85
|
+
notion = NotionWrapper(config["NOTION_API_SECRET"])
|
|
86
|
+
output = args.output
|
|
87
|
+
|
|
88
|
+
df = pd.DataFrame(
|
|
89
|
+
columns=[
|
|
90
|
+
"workorder",
|
|
91
|
+
"project_id",
|
|
92
|
+
"payment_type",
|
|
93
|
+
"costing_rate",
|
|
94
|
+
"hourly_rate",
|
|
95
|
+
"hours",
|
|
96
|
+
"cost",
|
|
97
|
+
"person",
|
|
98
|
+
"description",
|
|
99
|
+
"agresso_description",
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
for user in toggl_users:
|
|
104
|
+
toggl = initialise_toggl(user["api_key"], toggl_workspace)
|
|
105
|
+
hours_dict = get_toggl_hours(start_time, end_time, toggl)
|
|
106
|
+
|
|
107
|
+
for project_id, hours in hours_dict.items():
|
|
108
|
+
hours = float(hours)
|
|
109
|
+
payment_type = ""
|
|
110
|
+
costing_rate = ""
|
|
111
|
+
hourly_rate = ""
|
|
112
|
+
cost = 0.0
|
|
113
|
+
workorder_id = ""
|
|
114
|
+
if check_project_id(project_id):
|
|
115
|
+
project = notion.get_project(
|
|
116
|
+
config["NOTION_PROJECTS_DB"],
|
|
117
|
+
project_id,
|
|
118
|
+
workorders_db_id=config["NOTION_WORKORDERS_DB"]
|
|
119
|
+
)
|
|
120
|
+
project_name = project.name
|
|
121
|
+
workorder_id = project.workorder
|
|
122
|
+
if workorder_id is not None:
|
|
123
|
+
workorder = notion.get_workorder(
|
|
124
|
+
workorders_db_id=config["NOTION_WORKORDERS_DB"],
|
|
125
|
+
workorder_id=workorder_id
|
|
126
|
+
)
|
|
127
|
+
if workorder.is_complete():
|
|
128
|
+
payment_type = workorder.payment_type
|
|
129
|
+
costing_rate = workorder.costing_rate
|
|
130
|
+
hourly_rate = workorder.hourly_rate
|
|
131
|
+
cost = calculate_cost(hours, workorder.hourly_rate)
|
|
132
|
+
else:
|
|
133
|
+
project_name = project_id
|
|
134
|
+
|
|
135
|
+
df.loc[len(df)] = [
|
|
136
|
+
workorder_id,
|
|
137
|
+
project_id,
|
|
138
|
+
payment_type,
|
|
139
|
+
costing_rate,
|
|
140
|
+
hourly_rate,
|
|
141
|
+
hours,
|
|
142
|
+
cost,
|
|
143
|
+
user["name"],
|
|
144
|
+
project_name,
|
|
145
|
+
f"{project_id}: {project_name}",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
df.to_csv(output, sep="\t", index=False)
|
|
149
|
+
logger.warning(f"{len(df)} journal entries successfully written to {output}")
|
|
150
|
+
else:
|
|
151
|
+
logger.warning("Start time must not be more than 3 months ago!")
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
main()
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import pendulum
|
|
3
|
-
import warnings
|
|
4
|
-
from papi.wrappers import TogglTrackWrapper, NotionWrapper
|
|
5
|
-
from papi import config, setup_logger
|
|
6
|
-
from papi.project import get_project_ids, check_project_id
|
|
7
|
-
from decimal import Decimal, ROUND_UP
|
|
8
|
-
import pandas as pd
|
|
9
|
-
|
|
10
|
-
def initialise_toggl(toggl_api_key, toggl_workspace):
|
|
11
|
-
"""Initialises the TogglTrackWrapper.
|
|
12
|
-
|
|
13
|
-
:param toggl_api_key: A user's Toggl Track API key.
|
|
14
|
-
:type toggl_api_key: str
|
|
15
|
-
:param toggl_workspace: The name of the Toggl Track workspace.
|
|
16
|
-
:type toggl_workspace: str
|
|
17
|
-
:return: An initialised TogglTrackWrapper.
|
|
18
|
-
:rtype: TogglTrackWrapper
|
|
19
|
-
"""
|
|
20
|
-
toggl_api_password = config["TOGGL_TRACK_PASSWORD"]
|
|
21
|
-
toggl = TogglTrackWrapper(toggl_api_key, toggl_api_password)
|
|
22
|
-
toggl.set_default_workspace(toggl_workspace)
|
|
23
|
-
toggl.set_me()
|
|
24
|
-
return toggl
|
|
25
|
-
|
|
26
|
-
def calculate_cost(hours: float, hourly_rate: float) -> float:
|
|
27
|
-
"""Calculates a properly-rounded cost given hours and an hourly rate.
|
|
28
|
-
|
|
29
|
-
:param hours: A number of hours, e.g. 1.5 for one and a half hours.
|
|
30
|
-
:type hours: float
|
|
31
|
-
:param hourly_rate: An hourly rate to charge.
|
|
32
|
-
:type hourly_rate: float
|
|
33
|
-
:return: A properly-rounded (note: rounded up) cost to charge.
|
|
34
|
-
:rtype: float
|
|
35
|
-
"""
|
|
36
|
-
cost = Decimal(hours * hourly_rate)
|
|
37
|
-
cost_rounded = cost.quantize(Decimal("0.01"), rounding=ROUND_UP)
|
|
38
|
-
return cost_rounded
|
|
39
|
-
|
|
40
|
-
def get_toggl_hours(start_time, end_time, toggl):
|
|
41
|
-
"""Get the tracked hours between a start time and end time from an
|
|
42
|
-
initialised TogglTrackWrapper instance.
|
|
43
|
-
|
|
44
|
-
:param start_time: A start time, e.g. 2025-11-01
|
|
45
|
-
:type start_time: str
|
|
46
|
-
:param end_time: An end time, e.g. 2025-11-31
|
|
47
|
-
:type end_time: str
|
|
48
|
-
:param toggl: An initialised TogglTrackWrapper instance.
|
|
49
|
-
:type toggl: TogglTrackWrapper
|
|
50
|
-
:return: A dicitonary of projects and tracked hours.
|
|
51
|
-
:rtype: dict
|
|
52
|
-
"""
|
|
53
|
-
tracked_hours = toggl.get_user_hours(start_time=start_time, end_time=end_time)
|
|
54
|
-
projects = {p["id"]: p["name"] for p in toggl.get_user_projects() if p["id"] in tracked_hours}
|
|
55
|
-
hours_per_project = [(projects[t], tracked_hours[t]) for t in tracked_hours]
|
|
56
|
-
hours_dict = {}
|
|
57
|
-
for i, p in enumerate(hours_per_project):
|
|
58
|
-
project_id = get_project_ids([p[0]])
|
|
59
|
-
if len(project_id):
|
|
60
|
-
project_id = project_id[0]
|
|
61
|
-
hours_dict[project_id] = hours_per_project[i][1]
|
|
62
|
-
else:
|
|
63
|
-
hours_dict[p[0]] = hours_per_project[i][1]
|
|
64
|
-
return hours_dict
|
|
65
|
-
|
|
66
|
-
def main():
|
|
67
|
-
"""Main function of generate-timesheet script"""
|
|
68
|
-
# Set up argparse
|
|
69
|
-
parser = argparse.ArgumentParser()
|
|
70
|
-
parser.add_argument(
|
|
71
|
-
"-s", "--start", type=str, help="start date in YYYY-MM-DD format", required=True
|
|
72
|
-
)
|
|
73
|
-
parser.add_argument(
|
|
74
|
-
"-e", "--end", type=str, help="end date in YYYY-MM-DD format, if none supplied then end date is now", default=False
|
|
75
|
-
)
|
|
76
|
-
parser.add_argument(
|
|
77
|
-
"--disable-logging",
|
|
78
|
-
action="store_true",
|
|
79
|
-
help="disable logging output for the papi library",
|
|
80
|
-
)
|
|
81
|
-
parser.add_argument(
|
|
82
|
-
"-o",
|
|
83
|
-
"--output",
|
|
84
|
-
type=str,
|
|
85
|
-
help="output TSV filename",
|
|
86
|
-
default=False,
|
|
87
|
-
required=True
|
|
88
|
-
)
|
|
89
|
-
args = parser.parse_args()
|
|
90
|
-
|
|
91
|
-
if not args.disable_logging:
|
|
92
|
-
logger = setup_logger(True, "WARNING", None)
|
|
93
|
-
|
|
94
|
-
# Set up start/end date
|
|
95
|
-
start_date = args.start
|
|
96
|
-
start_time = pendulum.parse(start_date).to_rfc3339_string()
|
|
97
|
-
end_date = args.end
|
|
98
|
-
if not end_date:
|
|
99
|
-
end_time = pendulum.now().to_rfc3339_string()
|
|
100
|
-
else:
|
|
101
|
-
end_time = pendulum.parse(end_date).to_rfc3339_string()
|
|
102
|
-
|
|
103
|
-
# Check that start time is not more than 3 months ago
|
|
104
|
-
if pendulum.now() < pendulum.parse(start_date).add(months=3):
|
|
105
|
-
toggl_api_key = config["TOGGL_TRACK_API_KEY"]
|
|
106
|
-
toggl_workspace = config["TOGGL_TRACK_WORKSPACE"]
|
|
107
|
-
toggl = initialise_toggl(toggl_api_key, toggl_workspace)
|
|
108
|
-
|
|
109
|
-
hours_dict = get_toggl_hours(start_time, end_time, toggl)
|
|
110
|
-
|
|
111
|
-
notion_api_secret = config["NOTION_API_SECRET"]
|
|
112
|
-
notion_clients_db = config["NOTION_CLIENTS_DB"]
|
|
113
|
-
notion_projects_db = config["NOTION_PROJECTS_DB"]
|
|
114
|
-
notion_workorders_db = config["NOTION_WORKORDERS_DB"]
|
|
115
|
-
notion = NotionWrapper(notion_api_secret)
|
|
116
|
-
project_ids = hours_dict.keys()
|
|
117
|
-
output = args.output
|
|
118
|
-
df = pd.DataFrame(
|
|
119
|
-
columns=[
|
|
120
|
-
"workorder",
|
|
121
|
-
"project_id",
|
|
122
|
-
"payment_type",
|
|
123
|
-
"costing_rate",
|
|
124
|
-
"hourly_rate",
|
|
125
|
-
"hours",
|
|
126
|
-
"cost",
|
|
127
|
-
"description",
|
|
128
|
-
"agresso_description",
|
|
129
|
-
]
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
for project_id in project_ids:
|
|
133
|
-
hours = float(hours_dict[project_id])
|
|
134
|
-
payment_type = ""
|
|
135
|
-
costing_rate = ""
|
|
136
|
-
hourly_rate = ""
|
|
137
|
-
cost = 0.0
|
|
138
|
-
chargeable = False
|
|
139
|
-
workorder_id = ""
|
|
140
|
-
if check_project_id(project_id):
|
|
141
|
-
project = notion.get_project(
|
|
142
|
-
notion_projects_db,
|
|
143
|
-
project_id,
|
|
144
|
-
workorders_db_id=notion_workorders_db
|
|
145
|
-
)
|
|
146
|
-
project_name = project.name
|
|
147
|
-
workorder_id = project.workorder
|
|
148
|
-
if workorder_id is not None:
|
|
149
|
-
workorder = notion.get_workorder(
|
|
150
|
-
workorders_db_id=notion_workorders_db,
|
|
151
|
-
workorder_id=workorder_id
|
|
152
|
-
)
|
|
153
|
-
if workorder.is_complete():
|
|
154
|
-
payment_type = workorder.payment_type
|
|
155
|
-
costing_rate = workorder.costing_rate
|
|
156
|
-
hourly_rate = workorder.hourly_rate
|
|
157
|
-
cost = calculate_cost(hours, workorder.hourly_rate)
|
|
158
|
-
chargeable = True
|
|
159
|
-
logger.warning(
|
|
160
|
-
f"Project: {project_id}: Has complete data and is chargeable"
|
|
161
|
-
)
|
|
162
|
-
else:
|
|
163
|
-
logger.warning(
|
|
164
|
-
f"Project {project_id}: Workorder {workorder.id} has incomplete data!"
|
|
165
|
-
)
|
|
166
|
-
else:
|
|
167
|
-
workorder_id = ""
|
|
168
|
-
logger.warning(f"Project {project_id}: Workorder is missing!")
|
|
169
|
-
else:
|
|
170
|
-
logger.warning(f"Project {project_id}: Not a valid project ID!")
|
|
171
|
-
project_name = project_id
|
|
172
|
-
|
|
173
|
-
agresso_description = f"{project_id}: {project_name}"
|
|
174
|
-
description = project_name
|
|
175
|
-
|
|
176
|
-
df.loc[len(df)] = [
|
|
177
|
-
workorder_id,
|
|
178
|
-
project_id,
|
|
179
|
-
payment_type,
|
|
180
|
-
costing_rate,
|
|
181
|
-
hourly_rate,
|
|
182
|
-
hours,
|
|
183
|
-
cost,
|
|
184
|
-
description,
|
|
185
|
-
agresso_description,
|
|
186
|
-
]
|
|
187
|
-
|
|
188
|
-
df.to_csv(output, sep="\t", index=False)
|
|
189
|
-
logger.warning(f"{len(df)} journal entries successfully written to {output}")
|
|
190
|
-
else:
|
|
191
|
-
logger.warning("Start time must not be more than 3 months ago!")
|
|
192
|
-
|
|
193
|
-
if __name__ == "__main__":
|
|
194
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|