#-*-coding: utf-8 -*- from datetime import datetime, timedelta from ppms import ppms from ppms import ppms_cu from ppms import global_setting from ppms.constants import * from ppms.module_subclasses.base_class import Base from ppms.module_subclasses.calendar_control import CalendarControl from ppms.global_setting import get_global_setting_value from ppms.calculations.timestamps import ppms_timestamp_to_date __all__ = ['LoadCreationModule', 'ResourceLoadCreation', 'CostRevenueBookingModule', 'WorkReporting'] class LoadCreationModule(Base): '''Subclass used for modules which allow the creation of loads (DT472) and task resources (DT467) as implicit follow-up. Eg. used in MOD 0099JK "Rueckmeldung erfassen"''' def on_load(self): self.get_current_resource_settings() def cleanup_task_resources(self, menupoint_id, load_da_py_name='load'): '''Loop through task resources which were created by creating loads in the invoking module. - deletion: delete related task resources of marked loads in the module - reset/close module: delete related task resources of all unsaved loads in the module This is a workaround to avoid silent creation of task resources and will be needed until dtp events are available. ''' self.load_da = self.get_da(load_da_py_name) if not hasattr(self, 'created_task_res'): self.created_task_res = [] msg_reply = 1 if menupoint_id == MENU_DELETE: # record deletion msg_id = '0038' ppms.ui_message_id(msg_id) msg_reply = ppms.msg_pop().get_reply() if msg_reply == MSG_REPLY_NO: return else: #self.menu(34) self.cleanup_marked_res() elif menupoint_id in [MENU_RESET, MENU_CLOSE]: # reset or close module msg_id = '0009' if self.has_unsaved_changes(): ppms.ui_message_id(msg_id) msg_reply = ppms.msg_pop().get_reply() if msg_reply == MSG_REPLY_NO: self.cleanup_unsaved_res() elif msg_reply == MSG_REPLY_YES: if self.has_invalid_recs(): ppms.ui_message_id('0114') mark_msg_reply = ppms.msg_pop().get_reply() if mark_msg_reply == MSG_REPLY_YES: self.mark_invalid_recs() return # call original menu point with ppms.messages_disabled(): with ppms.autoreply_to_message(msg_id, msg_reply): #ppms.ui_message_box('call mp', [menupoint_id, msg_id, msg_reply]) self.menu(menupoint_id) if menupoint_id == MENU_RESET: # reset swap safe of created loads, to not mix this up # with further actions self.created_task_res = [] def has_invalid_recs(self): for mts_rec in self.load_da.get_records(): if mts_rec.get_dtp_record().state() == RECORD_STATE_INVALID: return True return False def mark_invalid_recs(self): for mts_rec in self.load_da.get_records(): if mts_rec.get_dtp_record().state() == RECORD_STATE_INVALID: mts_rec.mark() def cleanup_unsaved_res(self): # delete task resources which were created but their related load shall not # be saved for mts_rec in self.load_da.get_records(): dtp_rec = mts_rec.get_dtp_record() if dtp_rec.state() == RECORD_STATE_STORED: continue pr_id = dtp_rec.pr_id.get_value() task_id = dtp_rec.task_id.get_value() res_id = dtp_rec.res_id.get_value() task_res_info = [pr_id, task_id, res_id] if task_res_info in self.created_task_res: task_res_rec = ppms.search_record(466, task_res_info, [1390], True) if task_res_rec: res_has_stored_loads = False child_load_dtp_recs = task_res_rec.get_children(472, di_list=['uuid'], structure=False) for child_load_dtp_rec in child_load_dtp_recs: # if a child load is stored, don't delete if child_load_dtp_rec.state() == RECORD_STATE_STORED: res_has_stored_loads = True if not res_has_stored_loads: with ppms.messages_disabled(), ppms.autoreply_to_message('0845', MSG_REPLY_YES): task_res_rec.delete() def cleanup_marked_res(self): # delete task resources which were created by load recs which are going # to be deleted marked_dtp_records = [mts_rec.get_dtp_record() for mts_rec in self.load_da.get_marked_records()] marked_uuids = set([dtp_rec.uuid.get_value() for dtp_rec in marked_dtp_records]) for dtp_rec in marked_dtp_records: if not dtp_rec.state(): continue # if 2 loads have the same task_res_rec, the ask_res_rec is deleted, -> the other load too pr_id = dtp_rec.pr_id.get_value() task_id = dtp_rec.task_id.get_value() res_id = dtp_rec.res_id.get_value() task_res_info = [pr_id, task_id, res_id] if task_res_info in self.created_task_res: task_res_rec = ppms.search_record(466, task_res_info, [1390], True) if task_res_rec: child_load_dtp_recs = task_res_rec.get_children(472, di_list=['uuid'], structure=False) child_load_uuids = set([child_load_dtp_rec.uuid.get_value() for child_load_dtp_rec in child_load_dtp_recs]) if not child_load_uuids - marked_uuids: with ppms.messages_disabled(), ppms.autoreply_to_message('0845', MSG_REPLY_YES): task_res_rec.delete() def get_current_resource_settings(self, res_id=ppms.uvar_get("@1")): Lvars = self.get_invoker_module().get_current_L_var() # get res_id from invoker if 5 in Lvars: if Lvars[5]: res_id = Lvars[5][0] # get res_id of current module if already available if hasattr(self, 'current_res_id'): res_id = self.current_res_id self.current_res_id = res_id # set variables for res_id self.set_current_L_var(5, [res_id]) return res_id def insert_load_record(self, act_end=0, time_start="0", time_end="0", pr_id="", task_id="", object_process_step_id=""): load_recs = self.load.get_records() #time attributes load_recs[0].date.set_raw_value(ppms.uvar_get("@15")) load_recs[0].time_start.set_text_value(time_start) load_recs[0].time_end.set_text_value(time_end) #object attributes load_recs[0].pr_id.set_raw_value(pr_id) load_recs[0].task_id.set_raw_value(task_id) load_recs[0].object_process_step.set_raw_value(object_process_step_id) #ask for load if time_start == "0" and time_end == "0" and ppms_cu.Helper.get_global_setting( "show_hour_dialog").alpha120.get_value() == "1": input_actual_end = act_end if input_actual_end: try: load_recs[0].load_act.set_raw_value( float(input_actual_end[0].replace(',', '.'))) except ValueError: load_recs[0].load_act.set_raw_value(0) def menu_override(self, menu_id): if menu_id == MENU_SAVE: if not self.check_max_h(): return self.MENU_OVERRIDE_FAILURE self.menu(MENU_SAVE) return self.MENU_OVERRIDE_SUCCESS return super(LoadCreationModule, self).menu_override(menu_id) def check_max_h(self): """ Show a message if the user's resource has a maximal load setting per day and exceeds this.""" res_rec = ppms.search_record(467, [self.current_res_id], ["max_hours"], True) if not res_rec: return True max_h = res_rec.max_hours.get_value() if not max_h: return True load_dict = self.get_load_dict() load_over_max_h = [] for date, act_value in load_dict.items(): if act_value > max_h: load_over_max_h.append(ppms_timestamp_to_date(date)) if load_over_max_h: self.show_max_hour_msg(load_over_max_h, max_h) return False return True def get_load_dict(self): """ Get load sums of areas per load creation "post" module """ mod_id = self.get_id() if mod_id == get_global_setting_value('post_to_planned_tasks', 'alpha120'): load_dict = self.aggregate_loads(['load']) elif mod_id == get_global_setting_value('post_to_unplanned_tasks', 'alpha120'): load_dict = self.aggregate_loads(['load', 'existing_load']) else: load_dict = self.aggregate_loads(['load']) return load_dict def aggregate_loads(self, load_areas): """ Aggregate loads per day and return as dict "day:daylie_sum". """ load_recs = [] for area_name in load_areas: load_recs.extend(self.get_da(area_name).get_records()) load_dict = {} for rec in load_recs: date = rec.date.get_raw_value() res_id = rec.res_id.get_raw_value() act_value = rec.load_act.get_raw_value() if res_id == self.current_res_id and act_value: if date not in load_dict.keys(): load_dict[date] = 0 load_dict[date] += act_value return load_dict @staticmethod def show_max_hour_msg(load_over_max_h, max_h): """ Show the user on which days he tries to book more load per day than allowed for his resource. """ load_over_max_h_str = ', '.join(load_over_max_h) msg_title = ppms_cu.Helper.get_const_title('001781') msg_text = ppms_cu.Helper.get_const_title('001711').format(max_h=int(max_h), load_over_max_h_str=load_over_max_h_str) ppms.ui_message_box(msg_title, msg_text, 1, "000205") class ResourceLoadCreation(LoadCreationModule): def init_usage_of_actual_load(self): """ Sets load_act and time_start/time_end to output/input depending on the global setting 'usage_actual_load_reporting' """ ACT_LOAD = { 0: {"show": ["time_start", "time_end", "load_act"], "hide": [], "input": ["time_start", "time_end"], "output": ["load_act"]}, 1: {"show": ["load_act"], "hide": ["time_start", "time_end"], "input": ["load_act"], "output": ["time_start", "time_end"]}, } setting = global_setting.get_global_setting_value("usage_actual_load_reporting", 'alpha120', int, 0) usage = ACT_LOAD.get(int(setting), ACT_LOAD[0]) set_window = lambda df, window: self.load.get_customizing().get_dfc( df).set_window(window) for field in usage.get("show"): set_window(field, 1) for field in usage.get("hide"): set_window(field, 9) for field in usage.get("input"): for rec in self.load.get_records(): rec.get_df(field).make_input() for field in usage.get("output"): for rec in self.load.get_records(): rec.get_df(field).make_output() def on_insert(self): """ Inserts a new load record and maked sure it uses the right settings defined in the global setting 'usage_actual_load_reporting' """ self.menu(MENU_INSERT) self.init_usage_of_actual_load() def on_load(self): super(ResourceLoadCreation, self).on_load() self.menu(MENU_FILTER) self.menu(MENU_INSERT) self.init_usage_of_actual_load() def on_initial_focus(self): pass def on_focus(self): pass def on_reset(self): self.on_load() class CostRevenueBookingModule(LoadCreationModule): def on_initial_focus(self): self.menu(MENU_INSERT) def on_reset(self): self.on_initial_focus() class WorkReporting(LoadCreationModule, CalendarControl): @property def variable_from(self): return '@D363' @property def variable_to(self): return '@D364' def on_initial_focus(self): self.menu(MENU_FILTER) self.set_calendar() def on_load(self): CalendarControl.on_load(self) LoadCreationModule.on_load(self) def on_reset(self): self.menu(MENU_FILTER) def get_start_and_end_of_week(self, date): dt = datetime.fromtimestamp(date * 86400) start = dt - timedelta(days=dt.weekday()) end = start + timedelta(days=6) date_from = (start - datetime(1970, 1, 1)).days date_to = (end - datetime(1970, 1, 1)).days return date_from, date_to def one_week_forward(self): date_from, date_to = self.get_start_and_end_of_week(self.date_from + 7) self.go_to_time_intervall(date_from, date_to, set_posting_type=True, set_calender_field=True) def one_week_backwards(self): date_from, date_to = self.get_start_and_end_of_week(self.date_from - 7) self.go_to_time_intervall(date_from, date_to, set_posting_type=True, set_calender_field=True) def switch_to_week(self): inv_df = ppms.get_context_df() inv_rec = inv_df.get_record() day = inv_rec.cc.get_raw_value() date_from, date_to = self.get_start_and_end_of_week(day) if not day: ppms.ui_message_id('1112') else: self.go_to_time_intervall(date_from, date_to, set_posting_type=True, set_calender_field=True) def switch_to_today(self): start_of_week = ppms.uvar_get('@36') date_from, date_to = self.get_start_and_end_of_week(start_of_week) self.go_to_time_intervall(date_from, date_to, set_posting_type=True, set_calender_field=True)