Module ui.control.database

Main Controller of application

Expand source code
"""
Main Controller of application
"""

import datetime
import sys

from lib import exporter
from lib.cli import import_csv
from lib.cli.payroll import run_payroll
from lib.layer.security import ChangeRequestException, SecurityException
from lib.model.employee import Employee
from lib.model.receipt import Receipt
from lib.model.timesheet import TimeSheet
from lib.repository.validator import is_valid_against, ValidationException
from lib.utils import sha_hash
from ui import store
from ui.control import Controller
from ui.control.change_requests import ChangeRequestsController
from ui.window.database import DatabaseWindow
from ui.window.dialogs.about_empdat import AboutEmpDatDialog
from ui.window.dialogs.add_receipt import AddReceiptDialog
from ui.window.dialogs.add_timesheet import AddTimesheetDialog
from ui.window.dialogs.my_password import MyPasswordDialog
from ui.window.dialogs.others_password import PasswordDialog


def _open_change_requests():
    """
    Shows change requests window
    :return: None
    """
    ChangeRequestsController().show()


def _on_password_save(view, dialog, employee_id, old_pass: str,  # pylint: disable=too-many-arguments
                      password: str, password_confirm: str, require_old=True):
    employee = Employee.read(employee_id)

    if require_old and sha_hash(old_pass) != employee.password:
        view.show_error('Error', 'Old password does not match!')
        return

    if password != password_confirm:
        view.show_error('Error', 'New Passwords do not match!')
        return

    if not is_valid_against('password', password):
        view.show_error('Error', 'Passwords must have at least 9 characters (with at least '
                                 '1 number, 1 special character, and upper and lowercase '
                                 'characters)!')
        return

    Employee.read(employee_id).update_password(password)

    view.set_status('Changing password successful!')
    view.show_info('Info', 'Changing password successful!')
    dialog.destroy()


class DatabaseController(Controller):
    """
    Controller for the employee table view
    """

    def __init__(self):
        """
        Uses DatabaseWindow
        """
        super().__init__(DatabaseWindow({
            'new_employee': self.new_employee,
            'new_receipt': self.new_receipt,
            'new_timesheet': self.new_timesheet,
            'run_payroll': self.run_payroll,
            'change_my_password': self.change_my_password,
            'save': self.save,
            'delete': self.delete,
            'refresh': self.refresh,
            'file>logout': self.logout,
            'import>employees': self.import_employees,
            'import>receipts': self.import_receipts,
            'import>timesheets': self.import_timesheets,
            'admin>review': _open_change_requests,
            'admin>change_password': self.change_password,
            'export>employees': self.export_to_csv,
            'export>pdf_employees': self.export_to_pdf,
            'help>about_empdat': self.about_empdat,
        }))

        self.new_id = 0

    def load(self):
        """
        Loads all employees and displays them on the table
        :return: None
        """
        employees = Employee.read_all()
        i = 0
        for employee in employees:
            i += 1
            self.view.add_to_result(employee.id,
                                    employee.to_view_model(security_layer=store.SECURITY_LAYER))

        self.view.table.autoResizeColumns()

    def refresh(self):
        """
        Wipes and reloads the table
        :return: None
        """
        self.view.destroy_results()
        self.load()
        self.view.table.unsaved = set()
        self.view.table.redraw()

    def show(self):
        """
        Overrides show to load data first
        :return: None
        """
        self.load()

        super().show()

    def run_payroll(self):
        """
        Runs the payroll process, asks where to save it
        :return: None
        """
        filepath = self.view.show_save_picker(
            title='Save Payroll',
            filetypes=[('Text File', '*.txt')]
        )

        if not filepath:
            self.view.set_status('Payroll cancelled')
            return

        run_payroll(filepath)
        self.view.show_info('Success', f'Payroll was processed and was written to: {filepath}!')
        self.view.set_status('Payroll was processed successfully!')

    def save(self):
        """
        When Save is pressed, the change is either performed, or a change request is submitted
        :return: None
        """
        self.view.on_before_save()
        change_request_submitted = False
        for employee_id in self.view.table.unsaved:
            view_model = self.view.table.model.data[employee_id]

            is_new = False
            if isinstance(employee_id, str) and "NEW" in employee_id:
                view_model[Employee.view_columns['id']] = None
                is_new = True

            try:
                employee = Employee.from_view_model(view_model)
                if is_new:
                    Employee.create(employee)
                else:
                    Employee.update(employee)
            except ChangeRequestException:
                change_request_submitted = True
            except SecurityException as error:
                self.view.highlight_invalid_rows([employee_id])
                self.view.show_error('Error', f'Access Denied\n{error}')
                return
            except ValidationException as error:
                self.view.highlight_invalid_cell(employee_id,
                                                 Employee.view_columns[error.database_field])
                self.view.show_error('Error', f'Invalid data: {error}')
                self.view.set_status(f'Invalid data: {error}')
                return

        if change_request_submitted:
            self.view.show_info('Success', 'Request to Change Submitted!')
            self.view.set_status(f'Request to Change {len(self.view.table.unsaved)} '
                                 f'employees Submitted!')
        else:
            self.view.set_status(f'Saved {len(self.view.table.unsaved)} employees successfully!')
            self.view.master.bell()
        self.refresh()

    def delete(self):
        """
        When Delete is pressed, the change is either performed, or a change request is submitted
        :return: None
        """
        if self.view.show_confirm(title='Confirm Employee Deletion',
                                  message='Are you sure you want to delete the employee(s)?'):
            ids = self.view.table.get_selectedRecordNames()
            for employee_id in ids:
                try:
                    Employee.destroy(employee_id)
                except SecurityException:
                    self.view.show_error('Access Denied', 'Insufficient permission to delete '
                                                          'selected employees')
                    self.refresh()
                    break
            self.view.show_info('Deletion Successful', 'The selected employee(s) '
                                                       'were deleted successfully!')
            self.refresh()

    def new_employee(self):
        """
        When New is pressed.
        :return: None
        """
        self.view.new_employee(self.new_id, Employee.new_empty().to_view_model())
        self.new_id += 1

    def change_my_password(self):
        """
        Shows the change my password dialog and saves it
        :return: None
        """

        def on_save(dialog, old_pass: str, password: str, password_confirm: str):
            _on_password_save(self.view, dialog, store.SECURITY_LAYER.user.id,
                              old_pass, password, password_confirm)

        MyPasswordDialog({
            'save': on_save
        })

    def change_password(self):
        """
        Shows the change others' passwords dialog and saves it
        :return: None
        """

        def on_save(dialog, employee_id, password: str, password_confirm: str):
            _on_password_save(self.view, dialog, employee_id, None,
                              password, password_confirm, require_old=False)

        PasswordDialog({
            'save': on_save
        }, Employee.read_all())

    def import_employees(self):
        """
        Uses import csv library from CLI
        :return: None
        """
        filepath = self.view.show_file_picker(
            title='Import Employees (CSV)',
            filetypes=(('CSV File', '*.csv'), ('Text File', '*.txt'))
        )

        if not filepath:
            self.view.set_status('Importing employees cancelled')
            return

        import_csv.import_employees(filepath, from_cmd=False)
        self.view.set_status('Importing employees successful!')
        self.refresh()

    def import_receipts(self):
        """
        Uses import csv library from CLI
        :return: None
        """
        filepath = self.view.show_file_picker(
            title='Import Receipts',
            filetypes=(('Text File', '*.txt'), ('CSV File', '*.csv'))
        )

        if not filepath:
            self.view.set_status('Importing receipts cancelled')
            return

        import_csv.import_receipts(filepath, from_cmd=False)
        self.view.set_status('Importing receipts successful!')
        self.refresh()

    def import_timesheets(self):
        """
        Uses import csv library from CLI
        :return: None
        """
        filepath = self.view.show_file_picker(
            title='Import Time Sheets',
            filetypes=(('Text File', '*.txt'), ('CSV File', '*.csv'))
        )

        if not filepath:
            self.view.set_status('Importing time sheets cancelled')
            return

        import_csv.import_timesheets(filepath, from_cmd=False)
        self.view.set_status('Importing time sheets successful!')
        self.refresh()

    def new_receipt(self):
        """
        Shows the add receipt dialog and saves it
        :return: None
        """

        def on_save(dialog, employee_id: int, amount: float):
            try:
                amount = float(amount)
            except ValueError:
                self.view.show_error('Error', 'Invalid amount given!')
                return

            if Employee.read(employee_id):
                receipt = Receipt({
                    'user_id': employee_id,
                    'amount': amount
                })
                try:
                    Receipt.create(receipt)
                    self.view.show_info('Info', 'Receipt created successfully!')
                except SecurityException as error:
                    self.view.show_error('Error', f'Unable to create receipt: {error}')
                    return
                except ChangeRequestException:
                    self.view.show_info('Info', 'A change request has been created '
                                                'and will be reviewed.')

            self.view.set_status('Receipt creation successful!')
            dialog.destroy()

        AddReceiptDialog({
            'save': on_save
        }, Employee.read_all())

    def new_timesheet(self):
        """
        Shows the add timesheet dialog and saves it
        :return: None
        """

        def on_save(dialog, employee_id: int, date: datetime.date,  # pylint: disable=too-many-arguments
                    hour_in, min_in, hour_out, min_out):
            try:
                hour_in = int(hour_in)
                min_in = int(min_in)
                hour_out = int(hour_out)
                min_out = int(min_out)
            except ValueError:
                self.view.show_error('Error', 'Invalid dates given!')
                return

            time_begin = datetime.datetime.strptime(f"{hour_in} {min_in}", "%H %M").time()
            time_end = datetime.datetime.strptime(f"{hour_out} {min_out}", "%H %M").time()
            datetime_begin = datetime.datetime.combine(date, time_begin)
            datetime_end = datetime.datetime.combine(date, time_end)

            if Employee.read(employee_id):
                timesheet = TimeSheet({
                    'user_id': employee_id,
                    'datetime_begin': datetime_begin,
                    'datetime_end': datetime_end,
                })
                try:
                    TimeSheet.create(timesheet)
                    self.view.show_info('Success', 'Timesheet created successfully!')
                except SecurityException:
                    self.view.show_error('Error', 'Access Denied')
                    return
                except ChangeRequestException:
                    self.view.show_info('Info', 'A change request has been created '
                                                'and will be reviewed.')
            self.view.set_status('Timesheet creation successful!')
            dialog.destroy()

        AddTimesheetDialog({
            'save': on_save
        }, Employee.read_all())

    def export_to_csv(self):
        """
        Exports the table to a CSV
        :return: None
        """
        self.view.table.exportTable()

    def export_to_pdf(self):
        """
        Exports the table to a PDF
        :return: None
        """
        filepath = self.view.show_save_picker(
            title='Export Employee Directory (PDF)',
            filetypes=[('PDF Document', '*.pdf')]
        )

        if not filepath:
            self.view.set_status('Export cancelled')
            return

        try:
            if not exporter.html_to_pdf(exporter.table_to_html(self.view.table.model), filepath):
                self.view.show_error('Export Failed!', 'An error occurred while creating the PDF!')
                self.view.set_status('Export Failed!')
            else:
                self.view.show_info('Export Success!', 'The PDF was created successfully!')
                self.view.set_status('Export Success!')
        except IOError as error:
            self.view.show_error('Export Failed!', f'An error occurred '
            f'while creating the PDF! {error}')
            self.view.set_status('Export Failed!')

    def about_empdat(self):  # pylint: disable=no-self-use
        """
        Shows the About EmpDat Dialog
        :return: None
        """
        AboutEmpDatDialog()

    def logout(self):  # pylint: disable=no-self-use
        """
        Logout hook. Currently unused. Exits the app
        :return: None
        """
        sys.exit()

Classes

class DatabaseController

Controller for the employee table view

Uses DatabaseWindow

Expand source code
class DatabaseController(Controller):
    """
    Controller for the employee table view
    """

    def __init__(self):
        """
        Uses DatabaseWindow
        """
        super().__init__(DatabaseWindow({
            'new_employee': self.new_employee,
            'new_receipt': self.new_receipt,
            'new_timesheet': self.new_timesheet,
            'run_payroll': self.run_payroll,
            'change_my_password': self.change_my_password,
            'save': self.save,
            'delete': self.delete,
            'refresh': self.refresh,
            'file>logout': self.logout,
            'import>employees': self.import_employees,
            'import>receipts': self.import_receipts,
            'import>timesheets': self.import_timesheets,
            'admin>review': _open_change_requests,
            'admin>change_password': self.change_password,
            'export>employees': self.export_to_csv,
            'export>pdf_employees': self.export_to_pdf,
            'help>about_empdat': self.about_empdat,
        }))

        self.new_id = 0

    def load(self):
        """
        Loads all employees and displays them on the table
        :return: None
        """
        employees = Employee.read_all()
        i = 0
        for employee in employees:
            i += 1
            self.view.add_to_result(employee.id,
                                    employee.to_view_model(security_layer=store.SECURITY_LAYER))

        self.view.table.autoResizeColumns()

    def refresh(self):
        """
        Wipes and reloads the table
        :return: None
        """
        self.view.destroy_results()
        self.load()
        self.view.table.unsaved = set()
        self.view.table.redraw()

    def show(self):
        """
        Overrides show to load data first
        :return: None
        """
        self.load()

        super().show()

    def run_payroll(self):
        """
        Runs the payroll process, asks where to save it
        :return: None
        """
        filepath = self.view.show_save_picker(
            title='Save Payroll',
            filetypes=[('Text File', '*.txt')]
        )

        if not filepath:
            self.view.set_status('Payroll cancelled')
            return

        run_payroll(filepath)
        self.view.show_info('Success', f'Payroll was processed and was written to: {filepath}!')
        self.view.set_status('Payroll was processed successfully!')

    def save(self):
        """
        When Save is pressed, the change is either performed, or a change request is submitted
        :return: None
        """
        self.view.on_before_save()
        change_request_submitted = False
        for employee_id in self.view.table.unsaved:
            view_model = self.view.table.model.data[employee_id]

            is_new = False
            if isinstance(employee_id, str) and "NEW" in employee_id:
                view_model[Employee.view_columns['id']] = None
                is_new = True

            try:
                employee = Employee.from_view_model(view_model)
                if is_new:
                    Employee.create(employee)
                else:
                    Employee.update(employee)
            except ChangeRequestException:
                change_request_submitted = True
            except SecurityException as error:
                self.view.highlight_invalid_rows([employee_id])
                self.view.show_error('Error', f'Access Denied\n{error}')
                return
            except ValidationException as error:
                self.view.highlight_invalid_cell(employee_id,
                                                 Employee.view_columns[error.database_field])
                self.view.show_error('Error', f'Invalid data: {error}')
                self.view.set_status(f'Invalid data: {error}')
                return

        if change_request_submitted:
            self.view.show_info('Success', 'Request to Change Submitted!')
            self.view.set_status(f'Request to Change {len(self.view.table.unsaved)} '
                                 f'employees Submitted!')
        else:
            self.view.set_status(f'Saved {len(self.view.table.unsaved)} employees successfully!')
            self.view.master.bell()
        self.refresh()

    def delete(self):
        """
        When Delete is pressed, the change is either performed, or a change request is submitted
        :return: None
        """
        if self.view.show_confirm(title='Confirm Employee Deletion',
                                  message='Are you sure you want to delete the employee(s)?'):
            ids = self.view.table.get_selectedRecordNames()
            for employee_id in ids:
                try:
                    Employee.destroy(employee_id)
                except SecurityException:
                    self.view.show_error('Access Denied', 'Insufficient permission to delete '
                                                          'selected employees')
                    self.refresh()
                    break
            self.view.show_info('Deletion Successful', 'The selected employee(s) '
                                                       'were deleted successfully!')
            self.refresh()

    def new_employee(self):
        """
        When New is pressed.
        :return: None
        """
        self.view.new_employee(self.new_id, Employee.new_empty().to_view_model())
        self.new_id += 1

    def change_my_password(self):
        """
        Shows the change my password dialog and saves it
        :return: None
        """

        def on_save(dialog, old_pass: str, password: str, password_confirm: str):
            _on_password_save(self.view, dialog, store.SECURITY_LAYER.user.id,
                              old_pass, password, password_confirm)

        MyPasswordDialog({
            'save': on_save
        })

    def change_password(self):
        """
        Shows the change others' passwords dialog and saves it
        :return: None
        """

        def on_save(dialog, employee_id, password: str, password_confirm: str):
            _on_password_save(self.view, dialog, employee_id, None,
                              password, password_confirm, require_old=False)

        PasswordDialog({
            'save': on_save
        }, Employee.read_all())

    def import_employees(self):
        """
        Uses import csv library from CLI
        :return: None
        """
        filepath = self.view.show_file_picker(
            title='Import Employees (CSV)',
            filetypes=(('CSV File', '*.csv'), ('Text File', '*.txt'))
        )

        if not filepath:
            self.view.set_status('Importing employees cancelled')
            return

        import_csv.import_employees(filepath, from_cmd=False)
        self.view.set_status('Importing employees successful!')
        self.refresh()

    def import_receipts(self):
        """
        Uses import csv library from CLI
        :return: None
        """
        filepath = self.view.show_file_picker(
            title='Import Receipts',
            filetypes=(('Text File', '*.txt'), ('CSV File', '*.csv'))
        )

        if not filepath:
            self.view.set_status('Importing receipts cancelled')
            return

        import_csv.import_receipts(filepath, from_cmd=False)
        self.view.set_status('Importing receipts successful!')
        self.refresh()

    def import_timesheets(self):
        """
        Uses import csv library from CLI
        :return: None
        """
        filepath = self.view.show_file_picker(
            title='Import Time Sheets',
            filetypes=(('Text File', '*.txt'), ('CSV File', '*.csv'))
        )

        if not filepath:
            self.view.set_status('Importing time sheets cancelled')
            return

        import_csv.import_timesheets(filepath, from_cmd=False)
        self.view.set_status('Importing time sheets successful!')
        self.refresh()

    def new_receipt(self):
        """
        Shows the add receipt dialog and saves it
        :return: None
        """

        def on_save(dialog, employee_id: int, amount: float):
            try:
                amount = float(amount)
            except ValueError:
                self.view.show_error('Error', 'Invalid amount given!')
                return

            if Employee.read(employee_id):
                receipt = Receipt({
                    'user_id': employee_id,
                    'amount': amount
                })
                try:
                    Receipt.create(receipt)
                    self.view.show_info('Info', 'Receipt created successfully!')
                except SecurityException as error:
                    self.view.show_error('Error', f'Unable to create receipt: {error}')
                    return
                except ChangeRequestException:
                    self.view.show_info('Info', 'A change request has been created '
                                                'and will be reviewed.')

            self.view.set_status('Receipt creation successful!')
            dialog.destroy()

        AddReceiptDialog({
            'save': on_save
        }, Employee.read_all())

    def new_timesheet(self):
        """
        Shows the add timesheet dialog and saves it
        :return: None
        """

        def on_save(dialog, employee_id: int, date: datetime.date,  # pylint: disable=too-many-arguments
                    hour_in, min_in, hour_out, min_out):
            try:
                hour_in = int(hour_in)
                min_in = int(min_in)
                hour_out = int(hour_out)
                min_out = int(min_out)
            except ValueError:
                self.view.show_error('Error', 'Invalid dates given!')
                return

            time_begin = datetime.datetime.strptime(f"{hour_in} {min_in}", "%H %M").time()
            time_end = datetime.datetime.strptime(f"{hour_out} {min_out}", "%H %M").time()
            datetime_begin = datetime.datetime.combine(date, time_begin)
            datetime_end = datetime.datetime.combine(date, time_end)

            if Employee.read(employee_id):
                timesheet = TimeSheet({
                    'user_id': employee_id,
                    'datetime_begin': datetime_begin,
                    'datetime_end': datetime_end,
                })
                try:
                    TimeSheet.create(timesheet)
                    self.view.show_info('Success', 'Timesheet created successfully!')
                except SecurityException:
                    self.view.show_error('Error', 'Access Denied')
                    return
                except ChangeRequestException:
                    self.view.show_info('Info', 'A change request has been created '
                                                'and will be reviewed.')
            self.view.set_status('Timesheet creation successful!')
            dialog.destroy()

        AddTimesheetDialog({
            'save': on_save
        }, Employee.read_all())

    def export_to_csv(self):
        """
        Exports the table to a CSV
        :return: None
        """
        self.view.table.exportTable()

    def export_to_pdf(self):
        """
        Exports the table to a PDF
        :return: None
        """
        filepath = self.view.show_save_picker(
            title='Export Employee Directory (PDF)',
            filetypes=[('PDF Document', '*.pdf')]
        )

        if not filepath:
            self.view.set_status('Export cancelled')
            return

        try:
            if not exporter.html_to_pdf(exporter.table_to_html(self.view.table.model), filepath):
                self.view.show_error('Export Failed!', 'An error occurred while creating the PDF!')
                self.view.set_status('Export Failed!')
            else:
                self.view.show_info('Export Success!', 'The PDF was created successfully!')
                self.view.set_status('Export Success!')
        except IOError as error:
            self.view.show_error('Export Failed!', f'An error occurred '
            f'while creating the PDF! {error}')
            self.view.set_status('Export Failed!')

    def about_empdat(self):  # pylint: disable=no-self-use
        """
        Shows the About EmpDat Dialog
        :return: None
        """
        AboutEmpDatDialog()

    def logout(self):  # pylint: disable=no-self-use
        """
        Logout hook. Currently unused. Exits the app
        :return: None
        """
        sys.exit()

Ancestors

Methods

def about_empdat(self)

Shows the About EmpDat Dialog :return: None

Expand source code
def about_empdat(self):  # pylint: disable=no-self-use
    """
    Shows the About EmpDat Dialog
    :return: None
    """
    AboutEmpDatDialog()
def change_my_password(self)

Shows the change my password dialog and saves it :return: None

Expand source code
def change_my_password(self):
    """
    Shows the change my password dialog and saves it
    :return: None
    """

    def on_save(dialog, old_pass: str, password: str, password_confirm: str):
        _on_password_save(self.view, dialog, store.SECURITY_LAYER.user.id,
                          old_pass, password, password_confirm)

    MyPasswordDialog({
        'save': on_save
    })
def change_password(self)

Shows the change others' passwords dialog and saves it :return: None

Expand source code
def change_password(self):
    """
    Shows the change others' passwords dialog and saves it
    :return: None
    """

    def on_save(dialog, employee_id, password: str, password_confirm: str):
        _on_password_save(self.view, dialog, employee_id, None,
                          password, password_confirm, require_old=False)

    PasswordDialog({
        'save': on_save
    }, Employee.read_all())
def delete(self)

When Delete is pressed, the change is either performed, or a change request is submitted :return: None

Expand source code
def delete(self):
    """
    When Delete is pressed, the change is either performed, or a change request is submitted
    :return: None
    """
    if self.view.show_confirm(title='Confirm Employee Deletion',
                              message='Are you sure you want to delete the employee(s)?'):
        ids = self.view.table.get_selectedRecordNames()
        for employee_id in ids:
            try:
                Employee.destroy(employee_id)
            except SecurityException:
                self.view.show_error('Access Denied', 'Insufficient permission to delete '
                                                      'selected employees')
                self.refresh()
                break
        self.view.show_info('Deletion Successful', 'The selected employee(s) '
                                                   'were deleted successfully!')
        self.refresh()
def export_to_csv(self)

Exports the table to a CSV :return: None

Expand source code
def export_to_csv(self):
    """
    Exports the table to a CSV
    :return: None
    """
    self.view.table.exportTable()
def export_to_pdf(self)

Exports the table to a PDF :return: None

Expand source code
def export_to_pdf(self):
    """
    Exports the table to a PDF
    :return: None
    """
    filepath = self.view.show_save_picker(
        title='Export Employee Directory (PDF)',
        filetypes=[('PDF Document', '*.pdf')]
    )

    if not filepath:
        self.view.set_status('Export cancelled')
        return

    try:
        if not exporter.html_to_pdf(exporter.table_to_html(self.view.table.model), filepath):
            self.view.show_error('Export Failed!', 'An error occurred while creating the PDF!')
            self.view.set_status('Export Failed!')
        else:
            self.view.show_info('Export Success!', 'The PDF was created successfully!')
            self.view.set_status('Export Success!')
    except IOError as error:
        self.view.show_error('Export Failed!', f'An error occurred '
        f'while creating the PDF! {error}')
        self.view.set_status('Export Failed!')
def import_employees(self)

Uses import csv library from CLI :return: None

Expand source code
def import_employees(self):
    """
    Uses import csv library from CLI
    :return: None
    """
    filepath = self.view.show_file_picker(
        title='Import Employees (CSV)',
        filetypes=(('CSV File', '*.csv'), ('Text File', '*.txt'))
    )

    if not filepath:
        self.view.set_status('Importing employees cancelled')
        return

    import_csv.import_employees(filepath, from_cmd=False)
    self.view.set_status('Importing employees successful!')
    self.refresh()
def import_receipts(self)

Uses import csv library from CLI :return: None

Expand source code
def import_receipts(self):
    """
    Uses import csv library from CLI
    :return: None
    """
    filepath = self.view.show_file_picker(
        title='Import Receipts',
        filetypes=(('Text File', '*.txt'), ('CSV File', '*.csv'))
    )

    if not filepath:
        self.view.set_status('Importing receipts cancelled')
        return

    import_csv.import_receipts(filepath, from_cmd=False)
    self.view.set_status('Importing receipts successful!')
    self.refresh()
def import_timesheets(self)

Uses import csv library from CLI :return: None

Expand source code
def import_timesheets(self):
    """
    Uses import csv library from CLI
    :return: None
    """
    filepath = self.view.show_file_picker(
        title='Import Time Sheets',
        filetypes=(('Text File', '*.txt'), ('CSV File', '*.csv'))
    )

    if not filepath:
        self.view.set_status('Importing time sheets cancelled')
        return

    import_csv.import_timesheets(filepath, from_cmd=False)
    self.view.set_status('Importing time sheets successful!')
    self.refresh()
def load(self)

Loads all employees and displays them on the table :return: None

Expand source code
def load(self):
    """
    Loads all employees and displays them on the table
    :return: None
    """
    employees = Employee.read_all()
    i = 0
    for employee in employees:
        i += 1
        self.view.add_to_result(employee.id,
                                employee.to_view_model(security_layer=store.SECURITY_LAYER))

    self.view.table.autoResizeColumns()
def logout(self)

Logout hook. Currently unused. Exits the app :return: None

Expand source code
def logout(self):  # pylint: disable=no-self-use
    """
    Logout hook. Currently unused. Exits the app
    :return: None
    """
    sys.exit()
def new_employee(self)

When New is pressed. :return: None

Expand source code
def new_employee(self):
    """
    When New is pressed.
    :return: None
    """
    self.view.new_employee(self.new_id, Employee.new_empty().to_view_model())
    self.new_id += 1
def new_receipt(self)

Shows the add receipt dialog and saves it :return: None

Expand source code
def new_receipt(self):
    """
    Shows the add receipt dialog and saves it
    :return: None
    """

    def on_save(dialog, employee_id: int, amount: float):
        try:
            amount = float(amount)
        except ValueError:
            self.view.show_error('Error', 'Invalid amount given!')
            return

        if Employee.read(employee_id):
            receipt = Receipt({
                'user_id': employee_id,
                'amount': amount
            })
            try:
                Receipt.create(receipt)
                self.view.show_info('Info', 'Receipt created successfully!')
            except SecurityException as error:
                self.view.show_error('Error', f'Unable to create receipt: {error}')
                return
            except ChangeRequestException:
                self.view.show_info('Info', 'A change request has been created '
                                            'and will be reviewed.')

        self.view.set_status('Receipt creation successful!')
        dialog.destroy()

    AddReceiptDialog({
        'save': on_save
    }, Employee.read_all())
def new_timesheet(self)

Shows the add timesheet dialog and saves it :return: None

Expand source code
def new_timesheet(self):
    """
    Shows the add timesheet dialog and saves it
    :return: None
    """

    def on_save(dialog, employee_id: int, date: datetime.date,  # pylint: disable=too-many-arguments
                hour_in, min_in, hour_out, min_out):
        try:
            hour_in = int(hour_in)
            min_in = int(min_in)
            hour_out = int(hour_out)
            min_out = int(min_out)
        except ValueError:
            self.view.show_error('Error', 'Invalid dates given!')
            return

        time_begin = datetime.datetime.strptime(f"{hour_in} {min_in}", "%H %M").time()
        time_end = datetime.datetime.strptime(f"{hour_out} {min_out}", "%H %M").time()
        datetime_begin = datetime.datetime.combine(date, time_begin)
        datetime_end = datetime.datetime.combine(date, time_end)

        if Employee.read(employee_id):
            timesheet = TimeSheet({
                'user_id': employee_id,
                'datetime_begin': datetime_begin,
                'datetime_end': datetime_end,
            })
            try:
                TimeSheet.create(timesheet)
                self.view.show_info('Success', 'Timesheet created successfully!')
            except SecurityException:
                self.view.show_error('Error', 'Access Denied')
                return
            except ChangeRequestException:
                self.view.show_info('Info', 'A change request has been created '
                                            'and will be reviewed.')
        self.view.set_status('Timesheet creation successful!')
        dialog.destroy()

    AddTimesheetDialog({
        'save': on_save
    }, Employee.read_all())
def refresh(self)

Wipes and reloads the table :return: None

Expand source code
def refresh(self):
    """
    Wipes and reloads the table
    :return: None
    """
    self.view.destroy_results()
    self.load()
    self.view.table.unsaved = set()
    self.view.table.redraw()
def run_payroll(self)

Runs the payroll process, asks where to save it :return: None

Expand source code
def run_payroll(self):
    """
    Runs the payroll process, asks where to save it
    :return: None
    """
    filepath = self.view.show_save_picker(
        title='Save Payroll',
        filetypes=[('Text File', '*.txt')]
    )

    if not filepath:
        self.view.set_status('Payroll cancelled')
        return

    run_payroll(filepath)
    self.view.show_info('Success', f'Payroll was processed and was written to: {filepath}!')
    self.view.set_status('Payroll was processed successfully!')
def save(self)

When Save is pressed, the change is either performed, or a change request is submitted :return: None

Expand source code
def save(self):
    """
    When Save is pressed, the change is either performed, or a change request is submitted
    :return: None
    """
    self.view.on_before_save()
    change_request_submitted = False
    for employee_id in self.view.table.unsaved:
        view_model = self.view.table.model.data[employee_id]

        is_new = False
        if isinstance(employee_id, str) and "NEW" in employee_id:
            view_model[Employee.view_columns['id']] = None
            is_new = True

        try:
            employee = Employee.from_view_model(view_model)
            if is_new:
                Employee.create(employee)
            else:
                Employee.update(employee)
        except ChangeRequestException:
            change_request_submitted = True
        except SecurityException as error:
            self.view.highlight_invalid_rows([employee_id])
            self.view.show_error('Error', f'Access Denied\n{error}')
            return
        except ValidationException as error:
            self.view.highlight_invalid_cell(employee_id,
                                             Employee.view_columns[error.database_field])
            self.view.show_error('Error', f'Invalid data: {error}')
            self.view.set_status(f'Invalid data: {error}')
            return

    if change_request_submitted:
        self.view.show_info('Success', 'Request to Change Submitted!')
        self.view.set_status(f'Request to Change {len(self.view.table.unsaved)} '
                             f'employees Submitted!')
    else:
        self.view.set_status(f'Saved {len(self.view.table.unsaved)} employees successfully!')
        self.view.master.bell()
    self.refresh()
def show(self)

Overrides show to load data first :return: None

Expand source code
def show(self):
    """
    Overrides show to load data first
    :return: None
    """
    self.load()

    super().show()