diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..42e87b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,164 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/Python,Pycharm +# Edit at https://www.toptal.com/developers/gitignore?templates=Python,Pycharm + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea + +# CMake +cmake-build-*/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# JIRA plugin +atlassian-ide-plugin.xml + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# End of https://www.toptal.com/developers/gitignore/api/Python,Pycharm diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e25ebf0 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +init: + pip install -r requirements.txt + +test: + nosetests tests \ No newline at end of file diff --git a/db/__init__.py b/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/db/access.py b/db/access.py new file mode 100644 index 0000000..9daf80f --- /dev/null +++ b/db/access.py @@ -0,0 +1,105 @@ +from typing import List + +from psycopg2 import connect, extras as dbopts + +from models.department import Department +from models.employee import Employee +from models.location import Location + +db_url: str = 'postgresql://postgres:@localhost:5432/postgres' + + +def __create_department(row): + return Department( + department_id=row["department_id"], + name=row["department_name"], + location=Location( + location_id=row["location_id"], + postal_code=row["postal_code"], + street_address=row["street_address"], + state_province=row["state_province"], + country_id=row["country_id"], + city=row["city"] + )) + + +def fetch_european_departments() -> List[Department]: + """ + Ricerca tutti i dipartimenti dei paesi europei dove sono presenti + dei dipartimenti. + :return: elenco dei dipartimenti europei + """ + with connect(db_url) as connection: + with connection.cursor(cursor_factory=dbopts.DictCursor) as cursor: + cursor.execute( + """ + select + d.department_id, + d.department_name, + d.location_id, + l.postal_code, + l.street_address, + l.city, + l.state_province, + l.country_id + from + hr.departments d + left join hr.locations l on + d.location_id = l.location_id + where + l.country_id in ('UK', 'DE') + """ + ) + + # Prendo dal DB tutti i dipartimenti presenti nel Regno Unito e Germania + departments = [__create_department(row) for row in cursor.fetchall()] + + return departments + + +def fetch_department_employees(department_id: int) -> List[Employee]: + """ + Ricerca tutti i dipendenti di un dipartimento + :param department_id: dipartimento da ricercare + :return: elenco dei dipendenti + """ + employees = [] + with connect(db_url) as connection: + with connection.cursor(cursor_factory=dbopts.DictCursor) as cursor: + cursor.execute( + f""" + select + e.employee_id, + e.first_name, + e.last_name, + e.email, + e.phone_number, + e.hire_date, + e.job_id, + e.salary, + e.manager_id, + e.department_id + from + hr.employees e + where + e.department_id = {department_id} + """, + department_id + ) + + # Prendo dal DB tutti i dipendenti di un dipartimento + for row in cursor.fetchall(): + employees.append(Employee( + employee_id=row["employee_id"], + first_name=row["first_name"], + last_name=row["last_name"], + email=row["email"], + phone_number=row["phone_number"], + hire_date=row["hire_date"], + job_id=row["job_id"], + salary=row["salary"], + manager_id=row["manager_id"], + department=None + )) + + return employees diff --git a/main.py b/main.py new file mode 100644 index 0000000..9a56ffd --- /dev/null +++ b/main.py @@ -0,0 +1,26 @@ +import itertools +from datetime import date + +from dateutil import relativedelta as datedelta + +from db.access import fetch_european_departments, fetch_department_employees + +if __name__ == '__main__': + # Ricerco tutti i dipartimenti europei + for department in fetch_european_departments(): + print(f"\n####### Department of {department.name} #######\n") + # Per ogni dipartimento ricerco i suoi impiegati + employees = [employee for employee in fetch_department_employees(department.department_id)] + for employee in employees: + print(f"{employee.last_name} {employee.first_name}") + + print("\n") + # Raggruppo gli impiegati per anni di anzianità + calculate_seniority = lambda e: datedelta.relativedelta(date.today(), e.hire_date).years + employee_group_by_years = itertools.groupby(employees, calculate_seniority) + # Stampo i risultati dell'aggregazione + for years_to_employees in employee_group_by_years: + print(f"Employees with {years_to_employees[0]} years of seniority:") + for employee in years_to_employees[1]: + print(employee.last_name, employee.first_name) + print("\n") diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/department.py b/models/department.py new file mode 100644 index 0000000..876acf2 --- /dev/null +++ b/models/department.py @@ -0,0 +1,23 @@ +from models.location import Location + + +class Department: + def __init__(self, department_id: int, name: str, location: Location): + self.__department_id = department_id + self.__name = name + self.__location = location + + @property + def department_id(self) -> int: + return self.__department_id + + @property + def name(self) -> str: + return self.__name + + @property + def location_id(self) -> Location: + return self.__location + + def __str__(self): + return f"Department({self.__department_id}, {self.__name}, {self.__location})" diff --git a/models/employee.py b/models/employee.py new file mode 100644 index 0000000..e78a2d0 --- /dev/null +++ b/models/employee.py @@ -0,0 +1,67 @@ +from datetime import date + +from models.department import Department + + +class Person: + def __init__(self, first_name, last_name): + self.__first_name = first_name + self.__last_name = last_name + + @property + def first_name(self) -> str: + return self.__first_name + + @property + def last_name(self) -> str: + return self.__last_name + + +class Employee(Person): + def __init__(self, employee_id, first_name, last_name, email, phone_number, + hire_date, department, job_id=0, salary=0, manager_id=0): + super().__init__(first_name, last_name) + self.__employee_id = employee_id + self.__email = email + self.__phone_number = phone_number + self.__hire_date = hire_date + self.__department = department + self.__job_id = job_id + self.__salary = salary + self.__manager_id = manager_id + + @property + def employee_id(self) -> int: + return self.__employee_id + + @property + def email(self) -> str: + return self.__email + + @property + def phone_number(self) -> str: + return self.__phone_number + + @property + def hire_date(self) -> date: + return self.__hire_date + + @property + def department(self) -> Department: + return self.__department + + @property + def job_id(self) -> int: + return self.__job_id + + @property + def salary(self) -> float: + return self.__salary + + @property + def manager_id(self) -> int: + return self.__manager_id + + def __str__(self): + return f"Employee({self.__employee_id}, {self.first_name}, {self.last_name}, " \ + f"{self.__email}, {self.__phone_number}, {self.__hire_date})" diff --git a/models/location.py b/models/location.py new file mode 100644 index 0000000..612cf58 --- /dev/null +++ b/models/location.py @@ -0,0 +1,37 @@ +class Location: + def __init__(self, location_id: int, street_address: str, postal_code: str, + city: str, state_province: str, country_id: int): + self.__location_id = location_id + self.__street_address = street_address + self.__postal_code = postal_code + self.__city = city + self.__state_province = state_province + self.__country_id = country_id + + @property + def location_id(self) -> int: + return self.__location_id + + @property + def street_address(self) -> str: + return self.__street_address + + @property + def postal_code(self) -> str: + return self.__postal_code + + @property + def city(self) -> str: + return self.__city + + @property + def state_province(self) -> str: + return self.__state_province + + @property + def country_id(self) -> int: + return self.__country_id + + def __str__(self): + return f"Location({self.__location_id}, {self.__postal_code}, " \ + f"{self.__street_address}, {self.__city}, {self.__state_province}) " diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9f17428 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +records~=0.5.3 +psycopg2-binary~=2.9.1 +setuptools~=57.0.0 +python-dateutil~=2.8.1 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9142566 --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup + +setup( + name='hr-resources', + version='0.1.0', + packages=[''], + url='', + license='', + author='Fabio Scotto di Santolo', + author_email='fabio.scottodisantolo@gmail.com', + description='Servizio per la visualizzazione delle risorse umane' +)