Remove ancient kimai csv command, add Clockify mapping2db
This commit is contained in:
@ -513,149 +513,6 @@ def check(username, api_key, just_errors, ignore_activities, mapping_path=None):
|
|||||||
found_activities.append(activity_str)
|
found_activities.append(activity_str)
|
||||||
|
|
||||||
|
|
||||||
@kimai.command("csv")
|
|
||||||
@click.option(
|
|
||||||
"--mapping-path",
|
|
||||||
help="Mapping file (default ~/.local/share/hamster/mapping.kimai.csv)",
|
|
||||||
multiple=True,
|
|
||||||
)
|
|
||||||
@click.option("--output", help="Output file (default kimai.csv)")
|
|
||||||
@click.option("--category-search", help="Category search string")
|
|
||||||
@click.option("--after", help="Only show time entries after this date")
|
|
||||||
@click.option("--show-missing", help="Just report on the missing entries", is_flag=True)
|
|
||||||
@click.argument("username")
|
|
||||||
def _csv(
|
|
||||||
username,
|
|
||||||
mapping_path=None,
|
|
||||||
output=None,
|
|
||||||
category_search=None,
|
|
||||||
after=None,
|
|
||||||
show_missing=False,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Export time tracking data in Kimai format
|
|
||||||
"""
|
|
||||||
|
|
||||||
if mapping_path is None:
|
|
||||||
mapping_path = HAMSTER_DIR / "mapping.kimai.csv"
|
|
||||||
|
|
||||||
if output is None:
|
|
||||||
timestamp = datetime.now().strftime("%F")
|
|
||||||
output = f"kimai_{timestamp}.csv"
|
|
||||||
|
|
||||||
if isinstance(mapping_path, tuple):
|
|
||||||
mapping_files = []
|
|
||||||
for mapping_path_item in mapping_path:
|
|
||||||
mapping_file = _get_kimai_mapping_file(mapping_path_item, category_search)
|
|
||||||
next(mapping_file)
|
|
||||||
mapping_files.append(mapping_file)
|
|
||||||
mapping_reader = csv.reader(chain(*mapping_files))
|
|
||||||
else:
|
|
||||||
mapping_file = _get_kimai_mapping_file(mapping_path, category_search)
|
|
||||||
next(mapping_file)
|
|
||||||
mapping_reader = csv.reader(mapping_file)
|
|
||||||
|
|
||||||
mapping = {
|
|
||||||
"{0}:{1}".format(row[0], row[1]): [row[2], row[3], row[4], row[5]]
|
|
||||||
for row in mapping_reader
|
|
||||||
}
|
|
||||||
|
|
||||||
if isinstance(mapping_path, tuple):
|
|
||||||
for mapping_file in mapping_files:
|
|
||||||
mapping_file.close()
|
|
||||||
else:
|
|
||||||
mapping_file.close()
|
|
||||||
|
|
||||||
output_file = open(output, "w")
|
|
||||||
output_writer = csv.writer(output_file)
|
|
||||||
|
|
||||||
facts = (
|
|
||||||
HamsterFact.select(HamsterFact, HamsterActivity, HamsterCategory)
|
|
||||||
.join(HamsterActivity)
|
|
||||||
.join(HamsterCategory, JOIN.LEFT_OUTER)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if category_search is not None:
|
|
||||||
facts = facts.where(HamsterCategory.name.contains(category_search))
|
|
||||||
|
|
||||||
if after is not None:
|
|
||||||
facts = facts.where(HamsterFact.start_time >= after)
|
|
||||||
|
|
||||||
if not show_missing:
|
|
||||||
output_writer.writerow(
|
|
||||||
[
|
|
||||||
"Date",
|
|
||||||
"From",
|
|
||||||
"To",
|
|
||||||
"Duration",
|
|
||||||
"Rate",
|
|
||||||
"User",
|
|
||||||
"Customer",
|
|
||||||
"Project",
|
|
||||||
"Activity",
|
|
||||||
"Description",
|
|
||||||
"Exported",
|
|
||||||
"Tags",
|
|
||||||
"HourlyRate",
|
|
||||||
"FixedRate",
|
|
||||||
"InternalRate",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
for fact in facts:
|
|
||||||
k = f"{fact.activity.category.name}:{fact.activity.name}"
|
|
||||||
try:
|
|
||||||
mapping_ = mapping[k]
|
|
||||||
except KeyError:
|
|
||||||
if show_missing:
|
|
||||||
output_writer.writerow(
|
|
||||||
[fact.activity.category.name, fact.activity.name]
|
|
||||||
)
|
|
||||||
click.secho("Can't find mapping for '{0}', skipping".format(k), fg="yellow")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if show_missing:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if fact.start_time is None or fact.end_time is None:
|
|
||||||
click.secho(
|
|
||||||
f"Missing duration data '{fact.start_time}-{fact.end_time}', skipping",
|
|
||||||
fg="yellow",
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if len(mapping_) < 5:
|
|
||||||
mapping_.append(None)
|
|
||||||
|
|
||||||
date_start, date_end = (
|
|
||||||
datetime.strptime(fact.start_time.split(".")[0], "%Y-%m-%d %H:%M:%S"),
|
|
||||||
datetime.strptime(fact.end_time.split(".")[0], "%Y-%m-%d %H:%M:%S"),
|
|
||||||
)
|
|
||||||
duration = (date_start - date_end).seconds / 3600
|
|
||||||
|
|
||||||
output_writer.writerow(
|
|
||||||
[
|
|
||||||
date_start.strftime("%Y-%m-%d"),
|
|
||||||
date_start.strftime("%H:%M"),
|
|
||||||
"", # To (time)
|
|
||||||
duration,
|
|
||||||
"", # Rate
|
|
||||||
username,
|
|
||||||
mapping_[0],
|
|
||||||
mapping_[1],
|
|
||||||
mapping_[2],
|
|
||||||
fact.description or mapping_[4] or "",
|
|
||||||
"0", # Exported
|
|
||||||
mapping_[3],
|
|
||||||
"", # Hourly rate
|
|
||||||
"", # Fixed rate
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
output_file.close()
|
|
||||||
|
|
||||||
|
|
||||||
@kimai.command("import")
|
@kimai.command("import")
|
||||||
@click.argument("search")
|
@click.argument("search")
|
||||||
@ -788,7 +645,7 @@ def kimai_db_sync(ctx):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@kimai_db.command()
|
@kimai_db.command("mapping2db")
|
||||||
@click.option(
|
@click.option(
|
||||||
"-g",
|
"-g",
|
||||||
"--global",
|
"--global",
|
||||||
@ -797,7 +654,7 @@ def kimai_db_sync(ctx):
|
|||||||
is_flag=True,
|
is_flag=True,
|
||||||
)
|
)
|
||||||
@click.option("--mapping-path", help="Mapping file")
|
@click.option("--mapping-path", help="Mapping file")
|
||||||
def mapping2db(mapping_path=None, global_=False):
|
def kimai_mapping2db(mapping_path=None, global_=False):
|
||||||
mapping_file = _get_kimai_mapping_file(mapping_path, None)
|
mapping_file = _get_kimai_mapping_file(mapping_path, None)
|
||||||
next(mapping_file)
|
next(mapping_file)
|
||||||
mapping_reader = csv.reader(mapping_file)
|
mapping_reader = csv.reader(mapping_file)
|
||||||
@ -825,6 +682,15 @@ def mapping2db(mapping_path=None, global_=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@kimai.command("app")
|
||||||
|
@click.pass_context
|
||||||
|
def kimai_app(ctx):
|
||||||
|
from .app import HamsterToolsAppKimai
|
||||||
|
|
||||||
|
app = HamsterToolsAppKimai(ctx.obj['kimai'])
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
@cli.group()
|
@cli.group()
|
||||||
@click.option("--api-key", envvar="CLOCKIFY_API_KEY", help="Clockify API key")
|
@click.option("--api-key", envvar="CLOCKIFY_API_KEY", help="Clockify API key")
|
||||||
@click.option("--workspace-id", envvar="CLOCKIFY_WORKSPACE_ID", help="Clockify workspace ID")
|
@click.option("--workspace-id", envvar="CLOCKIFY_WORKSPACE_ID", help="Clockify workspace ID")
|
||||||
@ -860,6 +726,30 @@ def clockify_db_init():
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@clockify_db.command("reset")
|
||||||
|
def clockify_db_reset():
|
||||||
|
HamsterClockifyMapping.delete().execute()
|
||||||
|
|
||||||
|
@clockify_db.command("mapping2db")
|
||||||
|
@click.argument("mapping-path")
|
||||||
|
def clockify_mapping2db(mapping_path):
|
||||||
|
mapping_file = open(mapping_path, "r")
|
||||||
|
next(mapping_file)
|
||||||
|
mapping_reader = csv.reader(mapping_file)
|
||||||
|
|
||||||
|
for row in mapping_reader:
|
||||||
|
hamster_category = HamsterCategory.get(name=row[0])
|
||||||
|
hamster_activity = HamsterActivity.get(
|
||||||
|
name=row[1], category_id=hamster_category.id
|
||||||
|
)
|
||||||
|
clockify_project = ClockifyProject.get(clockify_id=row[2])
|
||||||
|
|
||||||
|
HamsterClockifyMapping.create(
|
||||||
|
hamster_activity=hamster_activity,
|
||||||
|
clockify_project=clockify_project,
|
||||||
|
clockify_description=row[3],
|
||||||
|
)
|
||||||
|
|
||||||
@clockify.command("app")
|
@clockify.command("app")
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def clockify_app(ctx):
|
def clockify_app(ctx):
|
||||||
@ -868,14 +758,6 @@ def clockify_app(ctx):
|
|||||||
app = HamsterToolsAppClockify(ctx.obj['clockify'])
|
app = HamsterToolsAppClockify(ctx.obj['clockify'])
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
@kimai.command("app")
|
|
||||||
@click.pass_context
|
|
||||||
def kimai_app(ctx):
|
|
||||||
from .app import HamsterToolsAppKimai
|
|
||||||
|
|
||||||
app = HamsterToolsAppKimai(ctx.obj['kimai'])
|
|
||||||
app.run()
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def app():
|
def app():
|
||||||
|
|||||||
@ -46,7 +46,6 @@ def sync_projects(api, db):
|
|||||||
for project in projects
|
for project in projects
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
breakpoint()
|
|
||||||
query.execute()
|
query.execute()
|
||||||
return len(projects)
|
return len(projects)
|
||||||
|
|
||||||
|
|||||||
@ -136,6 +136,7 @@ class ClockifyProject(Model):
|
|||||||
class HamsterClockifyMapping(Model):
|
class HamsterClockifyMapping(Model):
|
||||||
hamster_activity = ForeignKeyField(HamsterActivity, backref="clockify_mappings")
|
hamster_activity = ForeignKeyField(HamsterActivity, backref="clockify_mappings")
|
||||||
clockify_project = ForeignKeyField(ClockifyProject, backref="mappings")
|
clockify_project = ForeignKeyField(ClockifyProject, backref="mappings")
|
||||||
|
clockify_description = CharField()
|
||||||
created_at = DateTimeField(default=datetime.now)
|
created_at = DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
Reference in New Issue
Block a user