From 3da9195697176b4a72da3cf92e87f0ee5dfc20a0 Mon Sep 17 00:00:00 2001 From: anima Date: Tue, 8 Jun 2021 00:38:26 +0200 Subject: [PATCH] release version 2.1.0 --- timeTrack.py | 454 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 296 insertions(+), 158 deletions(-) diff --git a/timeTrack.py b/timeTrack.py index ed43b6d..8b1425d 100644 --- a/timeTrack.py +++ b/timeTrack.py @@ -3,7 +3,7 @@ # # timeTrack.py # by 4nima -# v.2.0.2 +# v.2.1.0 # ######################### # simple time tracking with database @@ -24,6 +24,7 @@ class TimeTrack: self.USERNAME = '' self.OLDEVENT = 2 self.LOGFILE = 'timetrack.log' + self.DBCON = sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) logging.basicConfig( filename=self.LOGFILE, level=logging.DEBUG, @@ -33,6 +34,9 @@ class TimeTrack: self.db_setup() self.load_config() + def __del__(self): + self.DBCON.close() + ## Prepartation ### Check OS and clear screen def clear_screen(self): @@ -42,6 +46,38 @@ class TimeTrack: else: logging.debug('Winwos System detected') _ = os.system('cls') + + ### Loads or creates a config file + def load_config(self): + if os.path.isfile(self.CONFIG): + logging.info('Config file was found') + try: + with open(self.CONFIG) as config_data: + data = json.load(config_data) + except ValueError: + logging.error('Config file has no valid JSON syntax') + print('TimeTrack wird beendet: Fehler in der JSON-Konfig ({})'.format(self.CONFIG)) + quit() + else: + logging.info('Config file was loaded successfully') + self.USERID = data['user'] + self.OLDEVENT = data['oldevent'] + logging.debug('UserID {} was used'.format(data['user'])) + self.set_user() + + else: + logging.warning('Config file not found') + config = { + 'default' : 'interactive', + 'user' : 1, + 'oldevent' : 2 + } + with open(self.CONFIG, "w") as outfile: + json.dump(config, outfile, indent=4, sort_keys=True) + logging.info('Config file created successfully') + + #> wenn man keine datei erstellen darf/kann, fällt das skript in einen loop ? + self.load_config() ### Creates a database if none is found def db_setup(self): @@ -58,7 +94,8 @@ class TimeTrack: endtime TIMESTAMP NOT NULL, user_id INTEGER NOT NULL, activity TEXT, - catrgory_id INTEGER, + reference TEXT, + category_id INTEGER, client_id INTEGER, lock BOOLEAN ) @@ -95,54 +132,133 @@ class TimeTrack: ) """) - connect = sqlite3.connect(self.DATABASE) - cursor = connect.cursor() logging.debug('Create initial database tables') - for SQL in sql: - try: - cursor.execute(SQL) - except: - logging.error('Table could not be created') - logging.debug(SQL) - print('TimeTrack wird beendet: Fehler bei Datanbank Setup') - quit() - else: - logging.debug('Table was created successfully or already exists') - - connect.commit() - connect.close() - - ### Loads or creates a config file - def load_config(self): - if os.path.isfile(self.CONFIG): - logging.info('Config file was found') - try: - with open(self.CONFIG) as config_data: - data = json.load(config_data) - except ValueError: - logging.error('Config file has no valid JSON syntax') - print('TimeTrack wird beendet: Fehler in der JSON-Konfig ({})'.format(self.CONFIG)) - quit() - else: - logging.info('Config file was loaded successfully') - self.USERID = data['user'] - self.OLDEVENT = data['oldevent'] - logging.debug('UserID {} was used'.format(data['user'])) - self.set_user() - + try: + with self.DBCON as con: + for SQL in sql: + con.execute(SQL) + except sqlite3.Error as err: + logging.error('Table could not be created') + logging.debug(sql) + logging.error('SQLError: {}'.format(err)) + print('TimeTrack wird beendet: Fehler bei Datanbank Setup') + quit() else: - logging.warning('Config file not found') - config = { - 'default' : 'interactive', - 'user' : 1, - 'oldevent' : 2 - } - with open(self.CONFIG, "w") as outfile: - json.dump(config, outfile, indent=4, sort_keys=True) - logging.info('Config file created successfully') + logging.debug('Table was created successfully or already exists') - #> wenn man keine datei erstellen darf/kann, fällt das skript in einen loop ? - self.load_config() + ### Get an active event based on a user or event ID + def get_active_event(self, USERID='', EVENTID=''): + if USERID: + logging.debug('Search events based on userid: {}'.format(USERID)) + sql = "SELECT * FROM active_events WHERE user_id = ?" + searchdata = [USERID] + elif EVENTID: + logging.debug('Search event based on eventid: {}'.format(EVENTID)) + sql = "SELECT * FROM active_events WHERE id = ?" + searchdata = [EVENTID] + else: + sql = "SELECT * FROM active_events" + searchdata = False + + try: + with sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) as con: + cur = con.cursor() + if searchdata: + logging.debug('Search event') + cur.execute(sql, searchdata) + else: + logging.debug('Get all Events') + cur.execute(sql) + eventdata = cur.fetchall() + except sqlite3.Error as err: + logging.debug(sql) + logging.error('SQLError: {}'.format(err)) + print('Fehler beim auslesen der aktiven Events') + else: + logging.debug('Events could be read out successfully') + + con.close() + if eventdata == []: + logging.debug('No active events found') + return 0 + else: + logging.debug('{} events found'.format(len(eventdata))) + return eventdata[0] + + ### Get time entries based on various criteria + def get_time_entry(self, USERID='', TIMEID='', CATEGORYID='', CLIENTID='', REFERENCE='', STARTTIME='', ENDTIME='', DAY=''): + if USERID: + logging.debug('Search time entries based on userid: {}'.format(USERID)) + sql = "SELECT * FROM time_entries WHERE user_id = ?" + searchdata = [USERID] + elif TIMEID: + logging.debug('Search time entrys based on timeid: {}'.format(TIMEID)) + sql = "SELECT * FROM time_entries WHERE id = ?" + searchdata = [TIMEID] + elif CATEGORYID: + logging.debug('Search time entrys based on categoryid: {}'.format(CATEGORYID)) + sql = "SELECT * FROM time_entries WHERE category_id = ?" + searchdata = [CATEGORYID] + elif CLIENTID: + logging.debug('Search time entrys based on clientid: {}'.format(CLIENTID)) + sql = "SELECT * FROM time_entries WHERE client_id = ?" + searchdata = [CLIENTID] + elif REFERENCE: + logging.debug('Search time entrys based on reference: {}'.format(REFERENCE)) + sql = "SELECT * FROM time_entries WHERE reference = ?" + searchdata = [REFERENCE] + else: + sql = "SELECT * FROM time_entries" + searchdata = False + + try: + with sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) as con: + cur = con.cursor() + if searchdata: + logging.debug('Search time entry') + cur.execute(sql, searchdata) + else: + logging.debug('Get all time entries') + cur.execute(sql) + timedata = cur.fetchall() + except sqlite3.Error as err: + logging.debug(sql) + logging.error('SQLError: {}'.format(err)) + print('Fehler beim auslesen der Zeiteinträge') + else: + logging.debug('Time entries could be read out successfully') + + con.close() + + if DAY: + logging.debug('Search time entries by date: {}'.format(DAY)) + tmp = [] + for entry in timedata: + if entry[1].date() == DAY: + tmp.append(entry) + timedata = tmp[:] + else: + tmp = [] + if STARTTIME: + logging.debug('Search time entries by starttime: {}'.format(STARTTIME)) + for entry in timedata: + if entry[1] > STARTTIME: + tmp.append(entry) + timedata = tmp[:] + pass + tmp = [] + if ENDTIME: + logging.debug('Search time entrie by endtime: {}'.format(ENDTIME)) + for entry in timedata: + if entry[2] < ENDTIME: + tmp.append(entry) + timedata = tmp[:] + if timedata == []: + logging.debug('No time entries found') + return 0 + else: + logging.debug('{} time entries found'.format(len(timedata))) + return timedata ## user handling ### Creates a user who does not yet exist @@ -160,21 +276,16 @@ class TimeTrack: logging.debug('Accepted username: {}'.format(username)) - connect = sqlite3.connect(self.DATABASE) - cursor = connect.cursor() - sql = "INSERT INTO users ( name ) values ( ? )" - try: - cursor.execute(sql, [username]) - except: + with self.DBCON as con: + con.execute(sql, [username]) + except sqlite3.Error as err: logging.error('User could not be saved in database') logging.debug(sql) + logging.error('SQLError: {}'.format(err)) else: logging.info('User was saved successfully') - connect.commit() - - connect.close() ### Outputs existing users from the DB def get_users(self, UID=0, NAME=''): @@ -191,25 +302,23 @@ class TimeTrack: data = '' sql = "SELECT * FROM users" - connect = sqlite3.connect(self.DATABASE) - cursor = connect.cursor() - try: - if data: - cursor.execute(sql, [data]) - else: - cursor.execute(sql) - except: + with self.DBCON as con: + cur = con.cursor() + if data: + cur.execute(sql, [data]) + else: + cur.execute(sql) + data = cur.fetchall() + except sqlite3.Error as err: logging.error('Could not get user') logging.debug(sql) + logging.error('SQLError: {}'.format(err)) print('Fehler beim Zugriff auf die Benutzer Datenbank') return 1 else: logging.debug('User database read out successfully') - connect.commit() - data = cursor.fetchall() - connect.close() return data ### Defines a user for the session @@ -229,25 +338,24 @@ class TimeTrack: ## Time handling ### Creates an active event if none exists for the user def save_event(self, TIME=datetime.datetime.now()): - if not self.get_event(USERID=self.USERID): + if not self.get_active_event(USERID=self.USERID): logging.debug('No active events found for the user: {}'.format(self.USERID)) - connect = sqlite3.connect(self.DATABASE) - cursor = connect.cursor() - sql = "INSERT INTO active_events ( starttime, user_id ) VALUES ( ?, ? )" + sql = "INSERT INTO active_events ( starttime, user_id ) VALUES ( ?, ? )" try: - cursor.execute(sql, [TIME, self.USERID]) - except: + with self.DBCON as con: + con.execute(sql, [TIME, self.USERID]) + except sqlite3.Error as err: logging.error('Event could not be created') logging.debug(sql) + logging.error('SQLError: {}'.format(err)) print('Event konnte nicht gespeichert werden.') return False else: logging.info('Event was created successfully') - connect.commit() - - connect.close() + return True + else: logging.warning('Active events found for the user, new event could not be created') return False @@ -267,59 +375,18 @@ class TimeTrack: print('Keine angabe was gelöscht werden soll') return 0 - connect = sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) - cursor = connect.cursor() - try: - cursor.execute(sql, [data]) - except: + with self.DBCON as con: + con.execute(sql, [data]) + except sqlite3.Error as err: logging.error('Event could not be deleted') logging.debug(sql) + logging.error('SQLError: {}'.format(err)) print('Fehler beim löschen des Events.') else: logging.debug('Event was successfully deleted') - connect.commit() - - connect.close() - - ### Get an active event based on a user or event ID - def get_event(self, ENTRYID='', USERID=''): - if not ENTRYID == '': - logging.debug('Search event based on eventid: {}'.format(ENTRYID)) - sql = "SELECT * FROM active_events WHERE id = ?" - data = ENTRYID - elif not USERID == '': - logging.debug('Search events based on userid: {}'.format(USERID)) - sql = "SELECT * FROM active_events WHERE user_id = ?" - data = USERID - else: - sql = "SELECT * FROM active_events" - data = '' - - connect = sqlite3.connect(self.DATABASE, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) - cursor = connect.cursor() - try: - if data: - logging.debug('Search event') - cursor.execute(sql, [data]) - else: - logging.debug('Get all Events') - cursor.execute(sql) - except: - logging.error('Events could not be read') - logging.debug(sql) - print('Fehler beim auslesen der aktiven Events') - else: - logging.debug('Events could be read out successfully') - data = cursor.fetchall() - connect.close() - if data == []: - logging.debug('No active events found') - return 0 - else: - logging.debug('{} events found'.format(len(data))) - return data[0] + # Checks for existing time entries and creates a new one if none is available. def time_start(self, AUTOFORWARD=True): self.clear_screen() starttime = datetime.datetime.now() @@ -329,7 +396,7 @@ class TimeTrack: if AUTOFORWARD: self.time_stop() else: - data = self.get_event(USERID=self.USERID) + data = self.get_active_event(USERID=self.USERID) print('Es existiert bereits ein aktives Event.') if (data[1] + datetime.timedelta(hours=self.OLDEVENT)) <= datetime.datetime.now(): logging.info('Event exceeds allowed duration') @@ -378,8 +445,9 @@ class TimeTrack: print('Event von {} Uhr geladen'.format(data[1].strftime("%H:%M"))) self.time_stop() + # Stops existing time entries def time_stop(self): - data = self.get_event(USERID=self.USERID) + data = self.get_active_event(USERID=self.USERID) logging.debug('Event stop progess is started') if data: self.clear_screen() @@ -421,44 +489,40 @@ class TimeTrack: endtime = datetime.datetime.now() logging.debug('Event end process start at {}'.format(endtime)) - connect = sqlite3.connect(self.DATABASE) - cursor = connect.cursor() - sql = "INSERT INTO time_entries ( starttime, endtime, user_id, activity ) VALUES ( ?, ?, ?, ? )" + sql = "INSERT INTO time_entries ( starttime, endtime, user_id, activity ) VALUES ( ?, ?, ?, ? )" try: - cursor.execute(sql, [data[1], endtime, self.USERID, action]) - except: + with self.DBCON as con: + con.execute(sql, [data[1], endtime, self.USERID, action]) + except sqlite3.Error as err: logging.error('Time entry could not be created') logging.debug(sql) + logging.error('SQLError: {}'.format(err)) print('Zeiteintrag konnte nicht gespeichert werden.') return False else: logging.info('Time entry was created successfully') - connect.commit() - self.delete_event(data[0]) - self.print_time_entry(STARTTIME=data[1], ENDTIME=endtime, ACTIVITY=action) - print('Zeiteintrag wurde gespeichert.') - userinput = 0 - while not 0 < int(userinput) < 3: - print('Nächsten Zeiteintrag beginnen ?') - print('[1] Ja') - print('[2] Nein') - userinput = input('Aktion: ') - logging.debug('User input: {}'.format(userinput)) - try: - int(userinput) - except ValueError: - userinput = 0 - self.clear_screen() - - if userinput == "1": - self.time_start() - else: - logging.debug('Terminated by the user') - exit() - - connect.close() + self.delete_event(data[0]) + self.print_time_entry(STARTTIME=data[1], ENDTIME=endtime, ACTIVITY=action) + print('Zeiteintrag wurde gespeichert.') + userinput = 0 + while not 0 < int(userinput) < 3: + print('Nächsten Zeiteintrag beginnen ?') + print('[1] Ja') + print('[2] Nein') + userinput = input('Aktion: ') + logging.debug('User input: {}'.format(userinput)) + try: + int(userinput) + except ValueError: + userinput = 0 + self.clear_screen() + + if userinput == "1": + self.time_start() + else: + self.start_interactive_mode() elif userinput == "2": logging.info('Event should be deleted (eventid: {})'.format(data[0])) @@ -467,10 +531,16 @@ class TimeTrack: logging.debug('Terminated by the user') exit() - def get_time(self): - pass - + def timedela_to_string(self, TIME): + s = TIME.seconds + hours, remainder = divmod(s, 3600) + minutes, seconds = divmod(remainder, 60) + output = '{:02}:{:02}:{:02}'.format(int(hours), int(minutes), int(seconds)) + return output + + # Shows a time entry def print_time_entry(self, STARTTIME='', ENDTIME='', ACTIVITY=''): + self.clear_screen() s = (ENDTIME - STARTTIME).seconds hours, remainder = divmod(s, 3600) minutes, seconds = divmod(remainder, 60) @@ -484,5 +554,73 @@ class TimeTrack: print(ACTIVITY) print(50*"-") -test = TimeTrack() -test.time_start() \ No newline at end of file + def report_by_day(self, DATE=datetime.date.today(), USER=''): + if not USER: + USER = self.USERID + timedata = self.get_time_entry(DAY=DATE, USERID=USER) + + STARTTIMES = [] + ENDTIMES = [] + DURATIONS = [] + + for entry in timedata: + STARTTIMES.append(entry[1]) + ENDTIMES.append(entry[2]) + DURATIONS.append(entry[2] - entry[1]) + + if timedata: + FIRSTSTARTTIME = min(STARTTIMES) + LASTENDTIME = max(ENDTIMES) + TRACKEDTIMESPAN = (LASTENDTIME - FIRSTSTARTTIME) + TRACKEDTIME = sum(DURATIONS, datetime.timedelta()) + NONTRACKEDTIME = (TRACKEDTIMESPAN - TRACKEDTIME) + ENTRIECOUNT = len(timedata) + + self.clear_screen() + print('{:40} {}'.format("Beginn des ersten Zeiteintrags:", FIRSTSTARTTIME.strftime('%d.%m.%Y %H:%M'))) + print('{:40} {}'.format("Ende des letzen Zeiteintrags:", LASTENDTIME.strftime('%d.%m.%Y %H:%M'))) + print('{:40} {}'.format("Maximal erfassbare Zeit:", self.timedela_to_string(TRACKEDTIMESPAN))) + print('{:40} {}'.format("Nicht erfasste Zeit:", self.timedela_to_string(NONTRACKEDTIME))) + print('{:40} {}'.format("Erfasste Zeit:", self.timedela_to_string(TRACKEDTIME))) + print('{:40} {}'.format("Zeiteinträge:", ENTRIECOUNT)) + + def start_interactive_mode(self): + self.clear_screen() + userinput = 0 + while not 0 < int(userinput) < 4: + print('Was willst du tun?') + print('[1] für Zeiterfassung starten') + print('[2] für heutiger Report') + print('[3] für Report für Tag x') + print('[9] für Programm verlassen') + userinput = input('Aktion: ') + logging.debug('User input: {}'.format(userinput)) + try: + int(userinput) + except ValueError: + userinput = 0 + else: + if int(userinput) == 9: + logging.debug('Terminated by the user') + exit() + self.clear_screen() + + if userinput == "1": + logging.debug('Start TimeTrack') + self.time_start() + elif userinput == "2": + logging.info('Print todays report') + self.report_by_day() + input() + self.start_interactive_mode() + elif userinput == "3": + print('commig soon ...') + input() + self.start_interactive_mode() + + print(userinput) + +if __name__ == "__main__": + test = TimeTrack() + test.start_interactive_mode() + test.DBCON.close() \ No newline at end of file