Initial Commit
This commit is contained in:
commit
87f1ec34b9
|
|
@ -0,0 +1,8 @@
|
||||||
|
Department
|
||||||
|
Audit
|
||||||
|
IT
|
||||||
|
Partners
|
||||||
|
Payroll
|
||||||
|
Reception
|
||||||
|
Secretarys
|
||||||
|
Tax
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
from tkinter import *
|
||||||
|
from tkinter import ttk
|
||||||
|
import pandas as pd
|
||||||
|
from lib import treerow_add, csvrow_add, treerow_del, csvrow_del, row_tracker
|
||||||
|
from tkinter import messagebox
|
||||||
|
|
||||||
|
class EmployeeManifest(Frame):
|
||||||
|
def __init__(self, parent, controller):
|
||||||
|
Frame.__init__(self, parent)
|
||||||
|
self.employeereg_columns = ['department','fname','sname','cardID']
|
||||||
|
|
||||||
|
#Data frames for list of departments and database of users
|
||||||
|
self.employee_df = pd.read_csv('employeeregister.csv', converters={'cardID': str})
|
||||||
|
self.department_df = pd.read_csv('departments.csv')
|
||||||
|
|
||||||
|
self.row_tracker = row_tracker(self.department_df, 0)
|
||||||
|
|
||||||
|
self.input_frame = Frame(self)
|
||||||
|
self.input_frame.grid(row=0, column=0, sticky='w')
|
||||||
|
|
||||||
|
self.firstname_label = Label(self.input_frame, text='First Name')
|
||||||
|
self.firstname_label.grid(row=0, column=0, padx=10, sticky="w")
|
||||||
|
self.firstname_text = Entry(self.input_frame)
|
||||||
|
self.firstname_text.grid(row=0, column=1, pady=5)
|
||||||
|
self.firstname_text.focus()
|
||||||
|
|
||||||
|
self.surname_label = Label(self.input_frame, text='Surname')
|
||||||
|
self.surname_label.grid(row=1, column=0, padx=10, sticky="w")
|
||||||
|
self.surname_text = Entry(self.input_frame)
|
||||||
|
self.surname_text.grid(row=1, column=1, pady=5)
|
||||||
|
|
||||||
|
self.card_label = Label(self.input_frame, text='Scan Card')
|
||||||
|
self.card_label.grid(row=2, column=0, padx=10, sticky="w")
|
||||||
|
self.card_text = Entry(self.input_frame)
|
||||||
|
self.card_text.grid(row=2, column=1, pady=5)
|
||||||
|
|
||||||
|
self.enter_btn = Button(self.input_frame, text='Enter Employee Info', command= lambda: self.write_line_csv(self.get_inputs()))
|
||||||
|
self.enter_btn.grid(row=3, column=0, pady=5, padx=10)
|
||||||
|
|
||||||
|
self.del_row_btn = Button(self.input_frame, text='Delete Employee(s)', command=self.delete_employees)
|
||||||
|
self.del_row_btn.grid(row=3, column=1, padx=10)
|
||||||
|
|
||||||
|
#create and store options for the department option menu
|
||||||
|
self.department_options = []
|
||||||
|
|
||||||
|
for rownum, row in self.department_df.iterrows():
|
||||||
|
self.department_options.append(row.Department)
|
||||||
|
|
||||||
|
#Create department option menu
|
||||||
|
self.value_inside = StringVar(self.input_frame)
|
||||||
|
self.value_inside.set("")
|
||||||
|
self.options_label = Label(self.input_frame, text='Department')
|
||||||
|
self.options_label.grid(row=0, column=2, padx=10)
|
||||||
|
self.department_options = OptionMenu(self.input_frame, self.value_inside, *self.department_options)
|
||||||
|
self.department_options.config(width=14)
|
||||||
|
self.department_options.grid(row=0, column=3, padx=(0, 10))
|
||||||
|
|
||||||
|
self.employee_register_tree = ttk.Treeview(self)
|
||||||
|
self.employee_register_tree.grid(column=0, row=4, padx=(10, 0), sticky="we")
|
||||||
|
self.employee_register_tree['columns'] = ('First Name', 'Surname', 'Card Code')
|
||||||
|
self.employee_register_tree.column('#0', width=120)
|
||||||
|
self.employee_register_tree.column('First Name', anchor=CENTER, width=110)
|
||||||
|
self.employee_register_tree.column('Surname', anchor=CENTER, width=110)
|
||||||
|
self.employee_register_tree.column('Card Code', anchor=CENTER, width=110)
|
||||||
|
|
||||||
|
#Define headings for each column
|
||||||
|
self.employee_register_tree.heading('#0', text='Department')
|
||||||
|
self.employee_register_tree.heading('First Name', text='First Name')
|
||||||
|
self.employee_register_tree.heading('Surname', text='Surname')
|
||||||
|
self.employee_register_tree.heading('Card Code', text='Card Code')
|
||||||
|
|
||||||
|
self.tree_scrollbar = Scrollbar(self, orient="vertical", command=self.employee_register_tree.yview)
|
||||||
|
self.employee_register_tree.configure(yscrollcommand=self.tree_scrollbar.set)
|
||||||
|
self.tree_scrollbar.grid(column=1,row=4, sticky='ns', pady=(0, 10))
|
||||||
|
|
||||||
|
for rownum, row in self.employee_df.iterrows():
|
||||||
|
treerow_add(self.row_tracker, self.employee_register_tree, row)
|
||||||
|
|
||||||
|
def delete_employees(self):
|
||||||
|
employees = self.employee_register_tree.selection()
|
||||||
|
|
||||||
|
#Prevent deletion of department parent row
|
||||||
|
for employee in employees:
|
||||||
|
if 'D' in employee:
|
||||||
|
return
|
||||||
|
|
||||||
|
delete_prompt = messagebox.askokcancel('Delete Users', 'Are you sure you wish to delete user(s)')
|
||||||
|
if delete_prompt == False:
|
||||||
|
return
|
||||||
|
|
||||||
|
#Delete user from tree and CSV
|
||||||
|
self.employee_df = csvrow_del('employeeregister.csv', self.employee_df, employees)
|
||||||
|
treerow_del(self.row_tracker, self.employee_register_tree, employees)
|
||||||
|
|
||||||
|
#Clear inputs of all fields
|
||||||
|
def clear_inputs(self, input_arr):
|
||||||
|
self.value_inside.set("")
|
||||||
|
|
||||||
|
for input in input_arr:
|
||||||
|
input.delete('0', END)
|
||||||
|
|
||||||
|
self.firstname_text.focus()
|
||||||
|
|
||||||
|
#Return array of inputs in all fields
|
||||||
|
def get_inputs(self):
|
||||||
|
inputs = [self.value_inside.get(), self.firstname_text.get(), self.surname_text.get(), self.card_text.get()]
|
||||||
|
return inputs
|
||||||
|
|
||||||
|
def write_line_csv(self, inputs):
|
||||||
|
#Field validation
|
||||||
|
for input in inputs:
|
||||||
|
if input == '':
|
||||||
|
messagebox.showerror('Validation Error', 'Please make sure all fields are completed')
|
||||||
|
return
|
||||||
|
|
||||||
|
for x in range(len(self.employee_df)):
|
||||||
|
if self.employee_df.iloc[x]['cardID'] == inputs[3]:
|
||||||
|
messagebox.showerror('Validation Error', 'Card ID must be unique for each employee')
|
||||||
|
return
|
||||||
|
|
||||||
|
self.clear_inputs([self.firstname_text, self.surname_text, self.card_text])
|
||||||
|
|
||||||
|
#Add user to tree and csv
|
||||||
|
treerow_add(self.row_tracker, self.employee_register_tree, inputs)
|
||||||
|
self.employee_df = csvrow_add('employeeregister.csv', self.employee_df, self.employeereg_columns, inputs)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
department,fname,sname,cardID
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
from tkinter import *
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
class Home(Frame):
|
||||||
|
def __init__(self, parent, controller):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
btn_frame = Frame(self)
|
||||||
|
btn_frame.pack()
|
||||||
|
|
||||||
|
employee_management_btn = Button(btn_frame, text='Employess Management', command=lambda: controller.frame_update('employee_manifest'))
|
||||||
|
employee_management_btn.pack(pady = (0,20))
|
||||||
|
|
||||||
|
signin_btn = Button(btn_frame, text='Sign in Register', command=lambda: controller.frame_update('sign_in'))
|
||||||
|
signin_btn.pack()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
from tkinter import *
|
||||||
|
from tkinter import ttk
|
||||||
|
import pandas as pd
|
||||||
|
from employee_manifest import EmployeeManifest
|
||||||
|
from home import Home
|
||||||
|
from sign_in import SignIn
|
||||||
|
import sched
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
from datetime import datetime, date , timedelta
|
||||||
|
from threading import Thread
|
||||||
|
from queue import Queue
|
||||||
|
#Special mention to James Haywood for imaging the PI $$ <3 <3!
|
||||||
|
class Main(Tk):
|
||||||
|
def __init__(self):
|
||||||
|
Tk.__init__(self)
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
self.geometry('520x450')
|
||||||
|
else:
|
||||||
|
self.geometry('720x450')
|
||||||
|
|
||||||
|
self.winfo_toplevel().title("Sign in App")
|
||||||
|
self.main_frame = Frame(self)
|
||||||
|
self.main_frame.pack(side='top', fill=BOTH, expand=1, pady=10)
|
||||||
|
|
||||||
|
#Thread safe queue and event to check queue for scheduled sign out at midnight
|
||||||
|
#Required because scheduler is run in seperate thread which causes tkinter to break without
|
||||||
|
self.sched_queue = Queue()
|
||||||
|
self.bind('<<CheckQueue>>', self.run_signout)
|
||||||
|
|
||||||
|
#List of GUI's used for mounting on button click
|
||||||
|
class FramesList():
|
||||||
|
home = Home
|
||||||
|
employee_manifest = EmployeeManifest
|
||||||
|
sign_in = SignIn
|
||||||
|
|
||||||
|
self.frames = FramesList()
|
||||||
|
|
||||||
|
#Mount home frame when class envoked (program started)
|
||||||
|
self.current_frame = self.frames.home(self.main_frame, self)
|
||||||
|
self.current_frame.place(anchor="c", relx=.5, rely=.5)
|
||||||
|
|
||||||
|
self.home_btn = Button(text = 'Home', command=lambda: Main.frame_update(self, 'home'))
|
||||||
|
self.home_btn.place(anchor='w', x=5, y=20)
|
||||||
|
|
||||||
|
self.sched_thread = Thread(target=self.run_sched, daemon=True)
|
||||||
|
self.sched_thread.start()
|
||||||
|
|
||||||
|
#Destroy the current GUI frame and mount a requested GUI
|
||||||
|
def frame_update(self, requested_frame):
|
||||||
|
self.current_frame.destroy()
|
||||||
|
next_frame = getattr(self.frames, requested_frame)
|
||||||
|
self.current_frame = next_frame(self.main_frame, self)
|
||||||
|
self.current_frame.place(anchor="c", relx=.5, rely=.5)
|
||||||
|
|
||||||
|
#Add function to the thread safe queue and then generate an event that causes the queue to be checked
|
||||||
|
def update_queue(event, self):
|
||||||
|
self.sched_queue.put(self.clear_all_signin)
|
||||||
|
self.event_generate('<<CheckQueue>>')
|
||||||
|
|
||||||
|
#When CheckQueue event is fired this function is run
|
||||||
|
#Gets function from queue and then runs it
|
||||||
|
def run_signout(self, event):
|
||||||
|
msg = self.sched_queue.get()
|
||||||
|
msg()
|
||||||
|
|
||||||
|
#Clears sign ins (scheduled to run at the start of next day) by just destroying and re-mounting the sign in frame
|
||||||
|
def clear_all_signin(self):
|
||||||
|
print('clear sign in executed')
|
||||||
|
if type(app.current_frame).__name__ == 'SignIn':
|
||||||
|
print('p0ng')
|
||||||
|
self.frame_update('sign_in')
|
||||||
|
self.run_sched()
|
||||||
|
|
||||||
|
#Rune scheduled task at 12am each day
|
||||||
|
def run_sched(self):
|
||||||
|
scheduler = sched.scheduler(time.time, time.sleep)
|
||||||
|
tomorrows_date = date.today() + timedelta(1)
|
||||||
|
#todays_date = date.today()
|
||||||
|
scheduled_time = datetime(tomorrows_date.year, tomorrows_date.month, tomorrows_date.day, 00, 00, 00)
|
||||||
|
scheduler.enterabs(scheduled_time.timestamp(), 1, self.clear_all_signin)
|
||||||
|
scheduler.run()
|
||||||
|
|
||||||
|
app = Main()
|
||||||
|
app.mainloop()
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
from tkinter import *
|
||||||
|
from tkinter import ttk
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
#Uses a counter to ensure unique iid for each row
|
||||||
|
#keeps track of parent row iids
|
||||||
|
#returns parent iids incrementing the counter each time
|
||||||
|
def row_tracker(department_frame, iid_count):
|
||||||
|
class Row_Tracker:
|
||||||
|
def __init__(self, iid_count):
|
||||||
|
self.iid_count = iid_count
|
||||||
|
|
||||||
|
def departiid(self, department):
|
||||||
|
self.iid_count += 1
|
||||||
|
setattr(self, department, 'D' + str(self.iid_count))
|
||||||
|
return getattr(self, department)
|
||||||
|
|
||||||
|
row_tracker = Row_Tracker(iid_count)
|
||||||
|
|
||||||
|
for rownum, row in department_frame.iterrows():
|
||||||
|
setattr(row_tracker, row['Department'], False)
|
||||||
|
|
||||||
|
return row_tracker
|
||||||
|
|
||||||
|
#add a row to the tree
|
||||||
|
def treerow_add(row_tracker, tree, inputs):
|
||||||
|
row_iid = inputs[3]
|
||||||
|
department = inputs[0]
|
||||||
|
department_iid = getattr(row_tracker, department)
|
||||||
|
if not getattr(row_tracker, department):
|
||||||
|
department_iid = row_tracker.departiid(department)
|
||||||
|
tree.insert(parent='', index='end', iid=department_iid, text=department, values=('', '', ''))
|
||||||
|
|
||||||
|
tree.insert(parent=department_iid, index='end', iid=row_iid, text='', values=(inputs[1], inputs[2], row_iid))
|
||||||
|
|
||||||
|
#add a row to the csv
|
||||||
|
# return frame because variable for that frame needs to be updated
|
||||||
|
def csvrow_add(file_path, frame, columns, inputs):
|
||||||
|
df_addition = {}
|
||||||
|
for i, column in enumerate(columns):
|
||||||
|
df_addition[column] = inputs[i]
|
||||||
|
frame = frame._append(df_addition, ignore_index = True)
|
||||||
|
frame.to_csv(file_path, index=False)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
#Delete employess from the tree and csv
|
||||||
|
#If parent row has no remaining children, delete it row from the tree and reset tracking for it's iid
|
||||||
|
def treerow_del(row_tracker, tree, iids):
|
||||||
|
for iid in iids:
|
||||||
|
parent_row_iid = tree.parent(iid)
|
||||||
|
tree.delete(iid)
|
||||||
|
|
||||||
|
if not tree.get_children(parent_row_iid):
|
||||||
|
parent_row = tree.item(parent_row_iid)
|
||||||
|
setattr(row_tracker, parent_row['text'], False)
|
||||||
|
tree.delete(parent_row_iid)
|
||||||
|
|
||||||
|
#Delete employee from data frame and csv
|
||||||
|
def csvrow_del(file_path, frame, iids):
|
||||||
|
for iid in iids:
|
||||||
|
frame = frame.drop(frame[frame['cardID'] == iid].index)
|
||||||
|
|
||||||
|
frame.to_csv(file_path, index=False)
|
||||||
|
return frame
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
from tkinter import *
|
||||||
|
from tkinter import ttk
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from lib import row_tracker, treerow_add, csvrow_add, csvrow_del, treerow_del
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
from reportlab.lib.pagesizes import A4
|
||||||
|
|
||||||
|
class SignIn(Frame):
|
||||||
|
def __init__(self, parent, controller):
|
||||||
|
Frame.__init__(self, parent)
|
||||||
|
print('Signin refresh')
|
||||||
|
self.columns_signin = ['department', 'fname', 'sname', 'cardID', 'signin']
|
||||||
|
self.current_date = str(datetime.now().date())
|
||||||
|
|
||||||
|
#File path generation for logs and csv of sign ins
|
||||||
|
self.todays_signins_path = 'sign_ins/signin-'+ self.current_date + '.csv'
|
||||||
|
self.todays_log_path = 'sign_in_logs/' + self.current_date + '.log'
|
||||||
|
|
||||||
|
self.recent_signin = [False, timedelta(0)]
|
||||||
|
logging.basicConfig(level=logging.INFO, filename=self.todays_log_path, filemode="a")
|
||||||
|
|
||||||
|
#Data frames for list of departments and database of users
|
||||||
|
self.employee_df = pd.read_csv('employeeregister.csv', converters={'cardID': str})
|
||||||
|
self.department_df = pd.read_csv('departments.csv', converters={'cardID': str})
|
||||||
|
|
||||||
|
self.row_trackerobj = row_tracker(self.department_df, 0)
|
||||||
|
|
||||||
|
self.signin_df = self.load_signin_df()
|
||||||
|
|
||||||
|
self.input_frame = Frame(self)
|
||||||
|
self.input_frame.grid(column=0, row=0, columnspan=2, padx=10, pady=(0,30))
|
||||||
|
|
||||||
|
self.scan_label = Label(self.input_frame, text='Scan Card')
|
||||||
|
self.scan_label.grid(column=0, row=0, pady=10)
|
||||||
|
|
||||||
|
self.border_color = Frame(self.input_frame)
|
||||||
|
self.scan_text = Entry(self.border_color)
|
||||||
|
self.scan_text.focus()
|
||||||
|
self.scan_text.bind("<FocusIn>", self.focus_color)
|
||||||
|
self.scan_text.bind("<FocusOut>", self.unfocus_color)
|
||||||
|
self.scan_text.bind('<Return>', self.register_scan)
|
||||||
|
self.scan_text.pack(padx=3, pady=3)
|
||||||
|
self.border_color.grid(column=0, row=1)
|
||||||
|
|
||||||
|
self.tree_frame = Frame(self)
|
||||||
|
self.tree_frame.grid(column=0, row=2)
|
||||||
|
|
||||||
|
self.fire_register_tree = ttk.Treeview(self.tree_frame)
|
||||||
|
self.fire_register_tree.grid(column=0, row=0, padx=10)
|
||||||
|
self.fire_register_tree['columns'] = ('First Name', 'Surname', 'Sign in Time')
|
||||||
|
self.fire_register_tree.column('#0', width=120)
|
||||||
|
self.fire_register_tree.column('First Name', anchor=CENTER, width=110)
|
||||||
|
self.fire_register_tree.column('Surname', anchor=CENTER, width=110)
|
||||||
|
self.fire_register_tree.column('Sign in Time', anchor=CENTER, width=110)
|
||||||
|
|
||||||
|
#Define headings for each column
|
||||||
|
self.fire_register_tree.heading('#0', text='Department')
|
||||||
|
self.fire_register_tree.heading('First Name', text='First Name')
|
||||||
|
self.fire_register_tree.heading('Surname', text='Surname')
|
||||||
|
self.fire_register_tree.heading('Sign in Time', text='Sign in Time')
|
||||||
|
|
||||||
|
self.pdf_btn = Button(self, text='Signin Report', command=self.generate_pdf)
|
||||||
|
self.pdf_btn.grid(column=0, row=3, pady=(10,0))
|
||||||
|
|
||||||
|
#Build tree rows for existing sign ins (needed if program restarted after users have already signed in for the day)
|
||||||
|
for rownum, row in self.signin_df.iterrows():
|
||||||
|
treerow_add(self.row_trackerobj, self.fire_register_tree, row)
|
||||||
|
|
||||||
|
def load_signin_df(self):
|
||||||
|
sign_ins = os.listdir('sign_ins')
|
||||||
|
|
||||||
|
for file in sign_ins:
|
||||||
|
if re.findall(self.current_date, file):
|
||||||
|
return pd.read_csv(self.todays_signins_path, converters={'cardID': str, 'iid': str})
|
||||||
|
|
||||||
|
df = pd.DataFrame(columns=self.columns_signin)
|
||||||
|
df.to_csv(self.todays_signins_path, index=False)
|
||||||
|
return df
|
||||||
|
|
||||||
|
#Clear input of text box
|
||||||
|
def input_clear(self):
|
||||||
|
self.scan_text.delete('0', END)
|
||||||
|
self.scan_text.focus()
|
||||||
|
|
||||||
|
#change border colour of text box when focused/unfocused
|
||||||
|
def focus_color(self, event):
|
||||||
|
self.border_color.configure(background='green')
|
||||||
|
|
||||||
|
def unfocus_color(self, event):
|
||||||
|
self.border_color.configure(background='red')
|
||||||
|
|
||||||
|
def register_scan(self, event):
|
||||||
|
card_code = self.scan_text.get()
|
||||||
|
signin_datetime = datetime.now()
|
||||||
|
signin_time = signin_datetime.strftime("%H:%M:%S")
|
||||||
|
signin_datetime_future = signin_datetime + timedelta(seconds = 10)
|
||||||
|
|
||||||
|
self.input_clear()
|
||||||
|
|
||||||
|
# If employee does not exist exit
|
||||||
|
if not self.employee_df.loc[self.employee_df['cardID'] == card_code].values.size:
|
||||||
|
return
|
||||||
|
|
||||||
|
#Prevent double tap of card
|
||||||
|
if card_code == self.recent_signin[0] and self.recent_signin[1] > signin_datetime:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.recent_signin = [card_code, signin_datetime_future]
|
||||||
|
code_linked_employee = self.employee_df.loc[self.employee_df['cardID'] == card_code].values[0].tolist()
|
||||||
|
|
||||||
|
#If employee has already signed in sign out. remove from tree, csv and create a log
|
||||||
|
if self.signin_df.loc[self.signin_df['cardID'] == card_code].values.size:
|
||||||
|
self.signin_df = csvrow_del(self.todays_signins_path, self.signin_df, [card_code])
|
||||||
|
treerow_del( self.row_trackerobj, self.fire_register_tree, [card_code])
|
||||||
|
logging.info('SIGN OUT: ' + code_linked_employee[1] + ' ' + code_linked_employee[2] + ' : ' + signin_time)
|
||||||
|
return
|
||||||
|
|
||||||
|
code_linked_employee.append(signin_time)
|
||||||
|
tree_row_toadd = [code_linked_employee[0], code_linked_employee[1], code_linked_employee[2], code_linked_employee[3]]
|
||||||
|
|
||||||
|
#Add employee to tree and sign in csv, also create log
|
||||||
|
treerow_add( self.row_trackerobj, self.fire_register_tree, tree_row_toadd)
|
||||||
|
self.signin_df = csvrow_add(self.todays_signins_path, self.signin_df, self.columns_signin, code_linked_employee)
|
||||||
|
logging.info('SIGN IN: ' + code_linked_employee[1] + ' ' + code_linked_employee[2] + ' : ' + signin_time)
|
||||||
|
|
||||||
|
#Populate PDF and send to default printer
|
||||||
|
def generate_pdf(self):
|
||||||
|
w, h = A4
|
||||||
|
current_row = 150
|
||||||
|
register_filename = "signinregister.pdf"
|
||||||
|
|
||||||
|
c = canvas.Canvas(register_filename, pagesize=A4)
|
||||||
|
c.drawString(30, h - 100, "Department")
|
||||||
|
c.drawString(150, h - 100, "First Name")
|
||||||
|
c.drawString(270, h - 100, "Surname")
|
||||||
|
c.drawString(390, h - 100, "Sign in Time")
|
||||||
|
|
||||||
|
for rownnum, row in self.signin_df.iterrows():
|
||||||
|
current_row += 20
|
||||||
|
|
||||||
|
c.drawString(30, h - current_row, row[0])
|
||||||
|
c.drawString(150, h - current_row, row[1])
|
||||||
|
c.drawString(270, h - current_row, row[2])
|
||||||
|
c.drawString(390, h - current_row, row[4])
|
||||||
|
|
||||||
|
c.showPage()
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
os.startfile(register_filename, "print")
|
||||||
|
else:
|
||||||
|
os.system(f"lp {register_filename}")
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
INFO:root:SIGN IN: Joe Bloggs : 15:35:48
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
department,fname,sname,cardID,signin
|
||||||
|
,,,,
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
%PDF-1.3
|
||||||
|
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||||
|
1 0 obj
|
||||||
|
<<
|
||||||
|
/F1 2 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 7 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 6 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
<<
|
||||||
|
/PageMode /UseNone /Pages 6 0 R /Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<<
|
||||||
|
/Author (anonymous) /CreationDate (D:20240402090541+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20240402090541+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||||
|
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<<
|
||||||
|
/Count 1 /Kids [ 3 0 R ] /Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 202
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gas3-_$YcZ'SYMZ:N;]N_RqqlkYb#$U.t@c,]oDe`__$^,[0EY`5#Xeh[bJR9ohL4Aq6&LXU#Vn>EB*opl#:[dm&7p&g7o&8sr>V_.qjil[C[M*S942GiO$@SY4F00]H!"k'%AL:2kXSeuP"ieQQ*h$eF/[_Ch;m8+nTC^f0u?nj8Y':8u(EG_%:qP-bd@`Y#iaE'RX]~>endstream
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 8
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000073 00000 n
|
||||||
|
0000000104 00000 n
|
||||||
|
0000000211 00000 n
|
||||||
|
0000000414 00000 n
|
||||||
|
0000000482 00000 n
|
||||||
|
0000000778 00000 n
|
||||||
|
0000000837 00000 n
|
||||||
|
trailer
|
||||||
|
<<
|
||||||
|
/ID
|
||||||
|
[<325c24b56bce97acfa22c1fe23eb8ce5><325c24b56bce97acfa22c1fe23eb8ce5>]
|
||||||
|
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||||
|
|
||||||
|
/Info 5 0 R
|
||||||
|
/Root 4 0 R
|
||||||
|
/Size 8
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
1129
|
||||||
|
%%EOF
|
||||||
Loading…
Reference in New Issue