Reformatting 🧹
This commit is contained in:
		@ -18,7 +18,7 @@ from .db import (
 | 
			
		||||
    KimaiProject,
 | 
			
		||||
    KimaiActivity,
 | 
			
		||||
    HamsterActivityKimaiMapping,
 | 
			
		||||
    HamsterFactKimaiImport
 | 
			
		||||
    HamsterFactKimaiImport,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
HAMSTER_DIR = Path.home() / ".local/share/hamster"
 | 
			
		||||
@ -631,8 +631,15 @@ def db_():
 | 
			
		||||
 | 
			
		||||
@db_.command()
 | 
			
		||||
def init():
 | 
			
		||||
    db.create_tables([KimaiCustomer, KimaiProject, KimaiActivity,
 | 
			
		||||
                      HamsterActivityKimaiMapping, HamsterFactKimaiImport])
 | 
			
		||||
    db.create_tables(
 | 
			
		||||
        [
 | 
			
		||||
            KimaiCustomer,
 | 
			
		||||
            KimaiProject,
 | 
			
		||||
            KimaiActivity,
 | 
			
		||||
            HamsterActivityKimaiMapping,
 | 
			
		||||
            HamsterFactKimaiImport,
 | 
			
		||||
        ]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@db_.command()
 | 
			
		||||
@ -665,10 +672,7 @@ def mapping2db(mapping_path=None, global_=False):
 | 
			
		||||
        try:
 | 
			
		||||
            kimai_activity = KimaiActivity.get(name=row[4], project_id=kimai_project.id)
 | 
			
		||||
        except KimaiActivity.DoesNotExist:
 | 
			
		||||
            kimai_activity = KimaiActivity.get(
 | 
			
		||||
                name=row[4],
 | 
			
		||||
                project_id=None
 | 
			
		||||
            )
 | 
			
		||||
            kimai_activity = KimaiActivity.get(name=row[4], project_id=None)
 | 
			
		||||
 | 
			
		||||
        HamsterActivityKimaiMapping.create(
 | 
			
		||||
            hamster_activity=hamster_activity,
 | 
			
		||||
 | 
			
		||||
@ -38,9 +38,15 @@ class ListScreen(Screen):
 | 
			
		||||
        with Vertical():
 | 
			
		||||
            yield DataTable()
 | 
			
		||||
            with Horizontal(id="filter"):
 | 
			
		||||
                yield Input(id="search", placeholder="Category/activity name contains text")
 | 
			
		||||
                yield Input(id="date",
 | 
			
		||||
                            placeholder='After date, in {0} format'.format(datetime.now().strftime('%Y-%m-%d')))
 | 
			
		||||
                yield Input(
 | 
			
		||||
                    id="search", placeholder="Category/activity name contains text"
 | 
			
		||||
                )
 | 
			
		||||
                yield Input(
 | 
			
		||||
                    id="date",
 | 
			
		||||
                    placeholder="After date, in {0} format".format(
 | 
			
		||||
                        datetime.now().strftime("%Y-%m-%d")
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
        yield Footer()
 | 
			
		||||
 | 
			
		||||
    def action_refresh(self) -> None:
 | 
			
		||||
@ -69,7 +75,7 @@ class ListScreen(Screen):
 | 
			
		||||
        self.table.focus()
 | 
			
		||||
        self._refresh()
 | 
			
		||||
 | 
			
		||||
    @on(Input.Changed, '#filter Input')
 | 
			
		||||
    @on(Input.Changed, "#filter Input")
 | 
			
		||||
    def filter(self, event):
 | 
			
		||||
        self._refresh()
 | 
			
		||||
 | 
			
		||||
@ -81,7 +87,7 @@ class ActivityEditScreen(ModalScreen):
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    category_id = None
 | 
			
		||||
    category_name = ''
 | 
			
		||||
    category_name = ""
 | 
			
		||||
 | 
			
		||||
    def _get_categories(self, input_state):
 | 
			
		||||
        categories = [DropdownItem(c.name, str(c.id)) for c in HamsterCategory.select()]
 | 
			
		||||
@ -96,15 +102,20 @@ class ActivityEditScreen(ModalScreen):
 | 
			
		||||
 | 
			
		||||
    def compose(self) -> ComposeResult:
 | 
			
		||||
        yield Vertical(
 | 
			
		||||
            Horizontal(Label("Category:"), 
 | 
			
		||||
            Horizontal(
 | 
			
		||||
                Label("Category:"),
 | 
			
		||||
                AutoComplete(
 | 
			
		||||
                    Input(placeholder="Type to search...", id="category",
 | 
			
		||||
                          value=self.category_name),
 | 
			
		||||
                    Input(
 | 
			
		||||
                        placeholder="Type to search...",
 | 
			
		||||
                        id="category",
 | 
			
		||||
                        value=self.category_name,
 | 
			
		||||
                    ),
 | 
			
		||||
                    Dropdown(items=self._get_categories),
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
            Horizontal(Label("Activity:"), Input(value=self.activity_name,
 | 
			
		||||
                                                 id='activity')),
 | 
			
		||||
            Horizontal(
 | 
			
		||||
                Label("Activity:"), Input(value=self.activity_name, id="activity")
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @on(Input.Submitted, "#category")
 | 
			
		||||
@ -129,7 +140,7 @@ class ActivityEditScreen(ModalScreen):
 | 
			
		||||
        self.dismiss(
 | 
			
		||||
            {
 | 
			
		||||
                "category": self.category_id,
 | 
			
		||||
                "activity": self.query_one('#activity').value,
 | 
			
		||||
                "activity": self.query_one("#activity").value,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -340,18 +351,18 @@ class ActivityListScreen(ListScreen):
 | 
			
		||||
            .group_by(HamsterActivity)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        filter_search = self.query_one('#filter #search').value 
 | 
			
		||||
        filter_search = self.query_one("#filter #search").value
 | 
			
		||||
        if filter_search is not None:
 | 
			
		||||
            activities = activities.where(
 | 
			
		||||
                HamsterActivity.name.contains(filter_search)
 | 
			
		||||
                | HamsterCategory.name.contains(filter_search)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        filter_date = self.query_one('#filter #date').value 
 | 
			
		||||
        filter_date = self.query_one("#filter #date").value
 | 
			
		||||
        if filter_date is not None:
 | 
			
		||||
            try:
 | 
			
		||||
                activities = activities.where(
 | 
			
		||||
                    HamsterFact.start_time > datetime.strptime(filter_date, '%Y-%m-%d')
 | 
			
		||||
                    HamsterFact.start_time > datetime.strptime(filter_date, "%Y-%m-%d")
 | 
			
		||||
                )
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                pass
 | 
			
		||||
@ -431,15 +442,14 @@ class ActivityListScreen(ListScreen):
 | 
			
		||||
        def handle_edit(properties):
 | 
			
		||||
            if properties is None:
 | 
			
		||||
                return
 | 
			
		||||
            activity.name = properties['activity']
 | 
			
		||||
            activity.category_id = properties['category']
 | 
			
		||||
            activity.name = properties["activity"]
 | 
			
		||||
            activity.category_id = properties["category"]
 | 
			
		||||
            activity.save()
 | 
			
		||||
            self._refresh()
 | 
			
		||||
 | 
			
		||||
        self.app.push_screen(ActivityEditScreen(
 | 
			
		||||
            category=category,
 | 
			
		||||
            activity=activity
 | 
			
		||||
        ), handle_edit)
 | 
			
		||||
        self.app.push_screen(
 | 
			
		||||
            ActivityEditScreen(category=category, activity=activity), handle_edit
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def action_mapping(self):
 | 
			
		||||
        selected_activity = (
 | 
			
		||||
@ -490,26 +500,24 @@ class CategoryListScreen(ListScreen):
 | 
			
		||||
 | 
			
		||||
        categories = (
 | 
			
		||||
            HamsterCategory.select(
 | 
			
		||||
                HamsterCategory, 
 | 
			
		||||
                HamsterCategory,
 | 
			
		||||
                fn.Count(HamsterActivity.id).alias("activities_count"),
 | 
			
		||||
                HamsterFact.start_time
 | 
			
		||||
                HamsterFact.start_time,
 | 
			
		||||
            )
 | 
			
		||||
            .join(HamsterActivity, JOIN.LEFT_OUTER)
 | 
			
		||||
            .join(HamsterFact, JOIN.LEFT_OUTER)
 | 
			
		||||
            .group_by(HamsterCategory)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        filter_search = self.query_one('#filter #search').value 
 | 
			
		||||
        filter_search = self.query_one("#filter #search").value
 | 
			
		||||
        if filter_search is not None:
 | 
			
		||||
            categories = categories.where(
 | 
			
		||||
                HamsterCategory.name.contains(filter_search)
 | 
			
		||||
            )
 | 
			
		||||
            categories = categories.where(HamsterCategory.name.contains(filter_search))
 | 
			
		||||
 | 
			
		||||
        filter_date = self.query_one('#filter #date').value 
 | 
			
		||||
        filter_date = self.query_one("#filter #date").value
 | 
			
		||||
        if filter_date is not None:
 | 
			
		||||
            try:
 | 
			
		||||
                categories = categories.where(
 | 
			
		||||
                    HamsterFact.start_time > datetime.strptime(filter_date, '%Y-%m-%d')
 | 
			
		||||
                    HamsterFact.start_time > datetime.strptime(filter_date, "%Y-%m-%d")
 | 
			
		||||
                )
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                pass
 | 
			
		||||
@ -613,7 +621,7 @@ class KimaiProjectListScreen(ListScreen):
 | 
			
		||||
                        "id": project.id,
 | 
			
		||||
                        "name": project.name,
 | 
			
		||||
                        "customer_id": project.customer.id,
 | 
			
		||||
                        "allow_global_activities": project.allow_global_activities
 | 
			
		||||
                        "allow_global_activities": project.allow_global_activities,
 | 
			
		||||
                    }
 | 
			
		||||
                    for project in projects
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,20 @@
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
import logging
 | 
			
		||||
from peewee import SqliteDatabase, Model, CharField, ForeignKeyField, DateTimeField, SmallIntegerField, BooleanField
 | 
			
		||||
from peewee import (
 | 
			
		||||
    SqliteDatabase,
 | 
			
		||||
    Model,
 | 
			
		||||
    CharField,
 | 
			
		||||
    ForeignKeyField,
 | 
			
		||||
    DateTimeField,
 | 
			
		||||
    SmallIntegerField,
 | 
			
		||||
    BooleanField,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from textual.logging import TextualHandler
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger("peewee")
 | 
			
		||||
logger.addHandler(TextualHandler())
 | 
			
		||||
logger.setLevel(logging.DEBUG)
 | 
			
		||||
# logger = logging.getLogger("peewee")
 | 
			
		||||
# logger.addHandler(TextualHandler())
 | 
			
		||||
# logger.setLevel(logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
db = SqliteDatabase(None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -28,12 +28,14 @@ class KimaiAPI(object):
 | 
			
		||||
        self.user_json = self.get("users/me")
 | 
			
		||||
 | 
			
		||||
    def get(self, endpoint, params=None):
 | 
			
		||||
        return requests.get(f"{self.KIMAI_API_URL}/{endpoint}",
 | 
			
		||||
                            params=params, headers=self.auth_headers).json()
 | 
			
		||||
        return requests.get(
 | 
			
		||||
            f"{self.KIMAI_API_URL}/{endpoint}", params=params, headers=self.auth_headers
 | 
			
		||||
        ).json()
 | 
			
		||||
 | 
			
		||||
    def post(self, endpoint, data):
 | 
			
		||||
        return requests.post(f"{self.KIMAI_API_URL}/{endpoint}",
 | 
			
		||||
                            json=data, headers=self.auth_headers)
 | 
			
		||||
        return requests.post(
 | 
			
		||||
            f"{self.KIMAI_API_URL}/{endpoint}", json=data, headers=self.auth_headers
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseAPI(object):
 | 
			
		||||
@ -72,9 +74,13 @@ class Project(BaseAPI):
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def list(api):
 | 
			
		||||
        return [
 | 
			
		||||
            Project(api, p["id"], p["name"], Customer.get_by_id(api,
 | 
			
		||||
                                                                p["customer"]),
 | 
			
		||||
                    p["globalActivities"])
 | 
			
		||||
            Project(
 | 
			
		||||
                api,
 | 
			
		||||
                p["id"],
 | 
			
		||||
                p["name"],
 | 
			
		||||
                Customer.get_by_id(api, p["customer"]),
 | 
			
		||||
                p["globalActivities"],
 | 
			
		||||
            )
 | 
			
		||||
            for p in api.projects_json
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
@ -87,7 +93,7 @@ class Project(BaseAPI):
 | 
			
		||||
                    value["id"],
 | 
			
		||||
                    value["name"],
 | 
			
		||||
                    Customer.get_by_id(api, value["customer"]),
 | 
			
		||||
                    value["globalActivities"]
 | 
			
		||||
                    value["globalActivities"],
 | 
			
		||||
                )
 | 
			
		||||
        if not none:
 | 
			
		||||
            raise NotFound()
 | 
			
		||||
@ -143,19 +149,19 @@ class Timesheet(BaseAPI):
 | 
			
		||||
                Project.get_by_id(api, t["project"], none=True),
 | 
			
		||||
                t["begin"],
 | 
			
		||||
                t["end"],
 | 
			
		||||
                t["id"], 
 | 
			
		||||
                t["description"], 
 | 
			
		||||
                t["tags"], 
 | 
			
		||||
                t["id"],
 | 
			
		||||
                t["description"],
 | 
			
		||||
                t["tags"],
 | 
			
		||||
            )
 | 
			
		||||
            for t in api.get(
 | 
			
		||||
                'timesheets',
 | 
			
		||||
                "timesheets",
 | 
			
		||||
            )
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_by_id(api, id, none=False):
 | 
			
		||||
        t = api.get(
 | 
			
		||||
            f'timesheets/{id}',
 | 
			
		||||
            f"timesheets/{id}",
 | 
			
		||||
        )
 | 
			
		||||
        return Timesheet(
 | 
			
		||||
            api,
 | 
			
		||||
@ -163,19 +169,22 @@ class Timesheet(BaseAPI):
 | 
			
		||||
            Project.get_by_id(api, t["project"], none=True),
 | 
			
		||||
            t["begin"],
 | 
			
		||||
            t["end"],
 | 
			
		||||
            t["id"], 
 | 
			
		||||
            t["description"], 
 | 
			
		||||
            t["tags"], 
 | 
			
		||||
            t["id"],
 | 
			
		||||
            t["description"],
 | 
			
		||||
            t["tags"],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def upload(self):
 | 
			
		||||
        return self.api.post('timesheets', {
 | 
			
		||||
          "begin": self.begin.isoformat(),
 | 
			
		||||
          "end": self.end.isoformat(),
 | 
			
		||||
          "project": self.project.id,
 | 
			
		||||
          "activity": self.activity.id,
 | 
			
		||||
          "description": self.description,
 | 
			
		||||
          # FIXME: support multiple users
 | 
			
		||||
          # "user": self.,
 | 
			
		||||
          "tags": self.tags,
 | 
			
		||||
        })
 | 
			
		||||
        return self.api.post(
 | 
			
		||||
            "timesheets",
 | 
			
		||||
            {
 | 
			
		||||
                "begin": self.begin.isoformat(),
 | 
			
		||||
                "end": self.end.isoformat(),
 | 
			
		||||
                "project": self.project.id,
 | 
			
		||||
                "activity": self.activity.id,
 | 
			
		||||
                "description": self.description,
 | 
			
		||||
                # FIXME: support multiple users
 | 
			
		||||
                # "user": self.,
 | 
			
		||||
                "tags": self.tags,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
 | 
			
		||||
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
@ -17,7 +18,8 @@ api = KimaiAPI()
 | 
			
		||||
 | 
			
		||||
# print(Timesheet.list(api))
 | 
			
		||||
 | 
			
		||||
t = Timesheet(api,
 | 
			
		||||
t = Timesheet(
 | 
			
		||||
    api,
 | 
			
		||||
    activity=Activity.get_by_id(api, 613),
 | 
			
		||||
    project=Project.get_by_id(api, 233),
 | 
			
		||||
    begin=datetime.now() - timedelta(minutes=10),
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
 | 
			
		||||
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
@ -11,22 +12,19 @@ from hamstertools.kimai import *
 | 
			
		||||
 | 
			
		||||
api = KimaiAPI()
 | 
			
		||||
 | 
			
		||||
DATE_FROM='2023-10-01'
 | 
			
		||||
DATE_TO='2023-11-01'
 | 
			
		||||
SEARCH='auto'
 | 
			
		||||
DATE_FROM = "2023-10-01"
 | 
			
		||||
DATE_TO = "2023-11-01"
 | 
			
		||||
SEARCH = "auto"
 | 
			
		||||
 | 
			
		||||
facts = HamsterFact.select(
 | 
			
		||||
    HamsterFact,
 | 
			
		||||
    HamsterActivity,
 | 
			
		||||
    HamsterCategory
 | 
			
		||||
).join(
 | 
			
		||||
    HamsterActivity, JOIN.LEFT_OUTER
 | 
			
		||||
).join(
 | 
			
		||||
    HamsterCategory, JOIN.LEFT_OUTER
 | 
			
		||||
).where(
 | 
			
		||||
    (HamsterFact.start_time > datetime.strptime(DATE_FROM, "%Y-%m-%d"))
 | 
			
		||||
    & (HamsterFact.start_time <  datetime.strptime(DATE_TO, "%Y-%m-%d"))
 | 
			
		||||
    & HamsterCategory.name.contains(SEARCH)
 | 
			
		||||
facts = (
 | 
			
		||||
    HamsterFact.select(HamsterFact, HamsterActivity, HamsterCategory)
 | 
			
		||||
    .join(HamsterActivity, JOIN.LEFT_OUTER)
 | 
			
		||||
    .join(HamsterCategory, JOIN.LEFT_OUTER)
 | 
			
		||||
    .where(
 | 
			
		||||
        (HamsterFact.start_time > datetime.strptime(DATE_FROM, "%Y-%m-%d"))
 | 
			
		||||
        & (HamsterFact.start_time < datetime.strptime(DATE_TO, "%Y-%m-%d"))
 | 
			
		||||
        & HamsterCategory.name.contains(SEARCH)
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
has_errors = False
 | 
			
		||||
@ -35,15 +33,24 @@ has_errors = False
 | 
			
		||||
for f in facts:
 | 
			
		||||
    mappings = f.activity.mappings
 | 
			
		||||
    if len(mappings) == 0:
 | 
			
		||||
        print(f'fact {f.id}: @{f.activity.category.id} {f.activity.category.name} » @{f.activity.id} {f.activity.name} has no mapping')
 | 
			
		||||
        print(
 | 
			
		||||
            f"fact {f.id}: @{f.activity.category.id} {f.activity.category.name} » @{f.activity.id} {f.activity.name} has no mapping"
 | 
			
		||||
        )
 | 
			
		||||
        has_errors = True
 | 
			
		||||
        continue
 | 
			
		||||
    if len(mappings) > 1:
 | 
			
		||||
        print(f'fact {f.id}: activity @{f.activity.id} {f.activity.name} has multiple mappings')
 | 
			
		||||
        print(
 | 
			
		||||
            f"fact {f.id}: activity @{f.activity.id} {f.activity.name} has multiple mappings"
 | 
			
		||||
        )
 | 
			
		||||
        has_errors = True
 | 
			
		||||
        continue
 | 
			
		||||
    if mappings[0].kimai_activity.project is None and not mappings[0].kimai_project.allow_global_activities:
 | 
			
		||||
        print(f'fact {f.id}: project @{mappings[0].kimai_project.id} {mappings[0].kimai_project.name} does not allow global activity {mappings[0].kimai_activity}')
 | 
			
		||||
    if (
 | 
			
		||||
        mappings[0].kimai_activity.project is None
 | 
			
		||||
        and not mappings[0].kimai_project.allow_global_activities
 | 
			
		||||
    ):
 | 
			
		||||
        print(
 | 
			
		||||
            f"fact {f.id}: project @{mappings[0].kimai_project.id} {mappings[0].kimai_project.name} does not allow global activity {mappings[0].kimai_activity}"
 | 
			
		||||
        )
 | 
			
		||||
        has_errors = True
 | 
			
		||||
        continue
 | 
			
		||||
 | 
			
		||||
@ -55,25 +62,27 @@ for f in facts:
 | 
			
		||||
    try:
 | 
			
		||||
        mapping = f.activity.mappings[0]
 | 
			
		||||
    except IndexError:
 | 
			
		||||
        print(f"no mapping, skipping {f.id} ({f.activity.category.name} » {f.activity.name})")
 | 
			
		||||
        print(
 | 
			
		||||
            f"no mapping, skipping {f.id} ({f.activity.category.name} » {f.activity.name})"
 | 
			
		||||
        )
 | 
			
		||||
        continue
 | 
			
		||||
    t = Timesheet(api,
 | 
			
		||||
    t = Timesheet(
 | 
			
		||||
        api,
 | 
			
		||||
        activity=mapping.kimai_activity,
 | 
			
		||||
        project=mapping.kimai_project,
 | 
			
		||||
        begin=f.start_time,
 | 
			
		||||
        end=f.end_time,
 | 
			
		||||
        description=f.description if f.description != '' else mapping.kimai_description,
 | 
			
		||||
        description=f.description if f.description != "" else mapping.kimai_description,
 | 
			
		||||
        # tags=f.tags if f.tags != '' else mapping.kimai_tags
 | 
			
		||||
    )
 | 
			
		||||
    r = t.upload().json()
 | 
			
		||||
    if r.get("code", 200) != 200:
 | 
			
		||||
        print(r)
 | 
			
		||||
        print(f"{f.id} ({f.activity.category.name} » {f.activity.name})")
 | 
			
		||||
        from pdb import set_trace; set_trace()
 | 
			
		||||
        from pdb import set_trace
 | 
			
		||||
 | 
			
		||||
        set_trace()
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        HamsterFactKimaiImport.create(
 | 
			
		||||
            hamster_fact=f,
 | 
			
		||||
            kimai_id=r['id']
 | 
			
		||||
        ).save()
 | 
			
		||||
        HamsterFactKimaiImport.create(hamster_fact=f, kimai_id=r["id"]).save()
 | 
			
		||||
        print(f'Created Kimai timesheet {r["id"]}')
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user