from datetime import datetime from typing import Dict, List import pandas as pd from dateutil.parser import parse from type_alias import EARLY_MODE, LATE_MODE class WorkingTime(object): def __init__(self, date: datetime, checkin: str, checkout: str, config) -> None: self.MORNING_IN = parse(config.WORKING_TIME.MORNING_IN) self.MORNING_OUT = parse(config.WORKING_TIME.MORNING_OUT) self.AFTERNOON_IN = parse(config.WORKING_TIME.AFTERNOON_IN) self.AFTERNOON_OUT = parse(config.WORKING_TIME.AFTERNOON_OUT) self.LATE_TIME: Dict[str, int] = dict( morning_in=config.LATE.MORNING_IN, morning_out=config.LATE.MORNING_OUT, afternoon_in=config.LATE.AFTERNOON_IN, afternoon_out=config.LATE.AFTERNOON_OUT) self.ROUND_GAP = config.WORKING_TIME.ROUND_GAP self.date = datetime.strftime(date, "%d/%m/%Y") self.error: bool = False if checkin == 0: # self.checkin = -1 self.error = True else: self.checkin = parse(checkin) if self.checkin < self.MORNING_IN: self.checkin = parse("08:00") if checkout == 0: # self.checkout = -1 self.error = True else: self.checkout = parse(checkout) if self.checkout > self.AFTERNOON_OUT: self.checkout = parse("17:00") def __repr__(self) -> str: return "{" + ";\n".join(f"{k}={getattr(self, k)}" for k in self.__dict__) + "}" def get_late_checkin(self, time: datetime, mode: LATE_MODE) -> int: is_penalty_count: int = 0 if mode == 'morning_in': target_time: datetime = self.MORNING_IN acceptable: int = self.LATE_TIME.get('MORNING_IN', 30) elif mode == 'afternoon_in': target_time: datetime = self.AFTERNOON_IN acceptable: int = self.LATE_TIME.get('AFTERNOON_IN', 30) if time < target_time: return 0 late_time = (time - target_time).seconds // 60 if late_time > acceptable: is_penalty_count = 1 return is_penalty_count * late_time def get_early_checkout(self, time: datetime, mode: EARLY_MODE) -> int: is_penalty_count: int = 0 if mode == 'morning_out': target_time: datetime = self.MORNING_OUT acceptable: int = self.LATE_TIME.get('MORNING_OUT', 0) elif mode == 'afternoon_out': target_time: datetime = self.AFTERNOON_OUT acceptable: int = self.LATE_TIME.get('AFTERNOON_OUT', 0) if time > target_time: return 0 early_time = (target_time - time).seconds // 60 if early_time > acceptable: is_penalty_count = 1 return is_penalty_count * early_time def morning_shift(self): if self.error: return 0 if self.checkin > self.MORNING_OUT: return 0 if self.checkout < self.MORNING_IN: return 0 late_checkin_penalty = self.get_late_checkin(time=self.checkin, mode='morning_in') early_checkout_penalty = self.get_early_checkout(time=self.checkout, mode='morning_out') return (self.MORNING_OUT - self.MORNING_IN ).seconds // 60 - late_checkin_penalty - early_checkout_penalty def afternoon_shift(self): if self.error: return 0 if self.checkout < self.AFTERNOON_IN: return 0 if self.checkin > self.AFTERNOON_OUT: return 0 late_checkin_penalty = self.get_late_checkin(time=self.checkin, mode='afternoon_in') early_checkout_penalty = self.get_early_checkout(time=self.checkout, mode='afternoon_out') return (self.AFTERNOON_OUT - self.AFTERNOON_IN ).seconds // 60 - late_checkin_penalty - early_checkout_penalty def working_time(self): return self.morning_shift() + self.afternoon_shift() def normalize(self, num: int) -> int: gap = 15 return round(num / gap) * gap def to_tuple(self): if self.error: return self.date, 0 return self.date, self.normalize(self.working_time()) class Employee(object): def __init__(self, id: str, name: str, workdate: Dict[str, int] = {}) -> None: self.id = id self.name = name self.workdate = workdate def __repr__(self) -> str: return "{" + ";\n".join(f"{k}={getattr(self, k)}" for k in self.__dict__) + "}" class WorkingTable(object): def __init__(self) -> None: pass def make_table(self, employees: List[Employee]): id_list: List[str] = [] name_list: List[str] = [] work_date: List[Dict] = [] for employee in employees: id_list.append(employee.id) name_list.append(employee.name) work_date.append(employee.workdate) work_df: pd.DataFrame = pd.DataFrame(work_date, dtype=int) info_df: pd.DataFrame = pd.DataFrame({ 'Mã nhân viên': id_list, 'Tên nhân viên': name_list }) working_table: pd.DataFrame = pd.concat(objs=[info_df, work_df], axis=1) return working_table