def assign_pools(self, user): # create userpool for this user if needed pools = Pool.by_country_active(self.session, user._country.id) for pool in pools: if 'disable_rtt' in self.request.params and pool.name == 'RTT': continue pool_class = pool.vacation_class initial_amount = pool_class.get_increment_step(user=user) entry = UserPool(amount=0, user=user, pool=pool) self.session.flush() entry.increment(self.session, initial_amount, 'creation')
def delete(self, account): # cancel all associated requests for this user requests = account.requests for req in requests: req.update_status('CANCELED') # delete all request history entries for this user # otherwise it will raise a integrity error for entry in req.history: self.session.delete(entry) self.session.flush() # cancel all request history entries in case there are entries in # this table but without an existing request histo_reqs = RequestHistory.by_user(self.session, account.id) for entry in histo_reqs: self.session.delete(entry) self.session.flush() # cancel associated password recovery attempts for this user for item in account.recovery: self.session.delete(item) self.session.flush() # delete existing user pools for up in UserPool.by_user(self.session, account): self.session.delete(up) self.session.flush() super(Delete, self).delete(account) if account.ldap_user: # delete in ldap ldap = LdapCache() try: ldap.delete_user(account.dn) except IndexError: log.info('User %s seems already deleted in ldap' % account.dn)
def process_pool_cycle(self, session): """Disable expired pool, create new ones.""" today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) # handle pools cycle expiration active_pools = Pool.by_status(session, 'active') self.log.debug('found %d active pool' % len(active_pools)) for pool in active_pools: if today > pool.date_end: self.log.info('pool %r must expire: %s' % (pool, pool.date_end)) # noqa # retrieve the other pools if pool is in a pool group if pool.pool_group: all_pools = Pool.by_pool_group(session, pool.pool_group) restant = [p for p in all_pools if p.name == 'restant'][0] acquis = [p for p in all_pools if p.name == 'acquis'][0] pool_group = uuid.uuid4().hex[:6] new_restant = Pool.clone(session, restant, date_start=acquis.date_start, date_end=acquis.date_end, pool_group=pool_group, date_last_increment=today) new_acquis = Pool.clone(session, acquis, shift=12, pool_group=pool_group, date_last_increment=today) if pool.country.name == 'lu': initial_amount = 200 else: pool_class = acquis.vacation_class initial_amount = pool_class.get_increment_step() yesterday = today - relativedelta(days=1) acquis.date_last_increment = yesterday # switch current amounts from old acquis to new restant # and grant initial amount for new cycle for up in acquis.user_pools: # need to increment one last time the acquis pool # before expiring it, as we acquire CP after a full # worked month not at the start of month like RTT if pool.country.name == 'fr': up.increment_month(session, True) up.amount = round(up.amount) self.log.debug('user:%s amount:%s' % (up.user.name, up.amount)) # noqa entry = UserPool(amount=0, user=up.user, pool=new_restant) # noqa session.flush() entry.increment(session, up.amount, 'heartbeat') entry = UserPool(amount=0, user=up.user, pool=new_acquis) # noqa session.flush() # don't increment acquis after creation for fr if pool.country.name != 'fr': entry.increment(session, initial_amount, 'heartbeat') # noqa restant.expire(session) acquis.expire(session) else: # create new pool, shifted by 1 year by default new_pool = Pool.clone(session, pool, shift=12) pool_class = pool.vacation_class initial_amount = pool_class.get_increment_step() for up in pool.user_pools: entry = UserPool(amount=0, user=up.user, pool=new_pool) # noqa session.flush() entry.increment(session, initial_amount, 'heartbeat') pool.expire(session)
def render(self): try: form_date_from = self.request.params.get('date_from') if ' - ' not in form_date_from: msg = 'Invalid format for period.' self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) dates = self.request.params.get('date_from').split(' - ') date_from = datetime.strptime(dates[0], '%d/%m/%Y') date_to = datetime.strptime(dates[1], '%d/%m/%Y') breakdown = self.request.params.get('breakdown') # retrieve holidays for user so we can remove them from selection holidays = get_holiday(self.user, year=date_from.year, use_datetime=True) submitted = [ d for d in daterange(date_from, date_to) if d.isoweekday() not in [6, 7] and d not in holidays ] days = float(len(submitted)) days_diff = (date_to - date_from).days if days_diff < 0: msg = 'Invalid format for period.' self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) if (date_to == date_from) and days > 1: # same day, asking only for one or less day duration msg = 'Invalid value for days.' self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) if days <= 0: msg = 'Invalid value for days.' self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) # check if user is sudoed check_user = self.get_target_user(self.user) pool = dict([(k, v.amount) for k, v in check_user.pool.items()]) # retrieve future requests for user so we can check overlap futures = [ d for req in Request.by_user_future(self.session, check_user) for d in daterange(req.date_from, req.date_to) ] intersect = set(futures) & set(submitted) if intersect: err_intersect = True # must check for false warning in case of half day requests if len(intersect) == 1: # only one date in conflict, check if it's for an half-day dt = intersect.pop() # retrieve the request for this date req = [ req for req in Request.by_user_future( self.session, check_user) for d in daterange(req.date_from, req.date_to) if d == dt ] if len(req) < 2: req = req.pop() if req.label != breakdown: # intersect is false, it's not the same halfday err_intersect = False log.debug( 'False positive on intersect ' 'for %s (%s): request: %d (%s)' % (date_from, breakdown, req.id, req.label)) if err_intersect: msg = 'Invalid period: days already requested.' self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) vac_type = VacationType.by_id(self.session, int(self.request.params.get('type'))) if not self.user.is_admin: # check if vacation requires user role if (vac_type.visibility and self.user.role not in vac_type.visibility): msg = 'You are not allowed to use type: %s' % vac_type.name self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) # check RTT usage access if vac_type.name == u'RTT': if self.user.has_feature('disable_rtt'): msg = 'You are not allowed to use type: %s' % vac_type.name self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) # label field is used when requesting half day label = u'' if breakdown != 'FULL': # handle half day if (days > 1): msg = ('AM/PM option must be used only when requesting a ' 'single day.') self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) else: days = 0.5 label = unicode(breakdown) # check RTT usage if vac_type.name == u'RTT': rtt_pool = check_user.pool.get('RTT') if rtt_pool is not None and rtt_pool.amount <= 0: msg = 'No RTT left to take.' self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) # check that we have enough RTT to take if rtt_pool is not None and days > rtt_pool.amount: msg = 'You only have %s RTT to use.' % rtt_pool.amount self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) # check that we request vacations in the allowed year if rtt_pool is not None: if (date_from < rtt_pool.date_start or date_to > rtt_pool.date_end): msg = ('RTT can only be used between %s and %s' % (rtt_pool.date_start.strftime('%d/%m/%Y'), rtt_pool.date_end.strftime('%d/%m/%Y'))) self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url( 'home', self.request)) # noqa message = None # check Exceptionnel mandatory field if vac_type.name == u'Exceptionnel': message = self.request.params.get('exception_text') message = message.strip() if message else message if not message: msg = ('You must provide a reason for %s requests' % vac_type.name) self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) # check size if len(message) > 140: msg = ('%s reason must not exceed 140 characters' % vac_type.name) self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) # check for Compensatoire type (LU holiday recovery) if vac_type.name == u'Compensatoire': to_recover = self.request.params.get('recovered_holiday') if to_recover == '-1': msg = 'You must select a date for %s' % vac_type.name self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) recover_date = datetime.strptime(to_recover, '%d/%m/%Y') vac_class = vac_type.get_class(check_user.country) if vac_class: error = vac_class.validate_request(check_user, None, days, recover_date, date_to) if error is not None: self.request.session.flash('error;%s' % error) return HTTPFound( location=route_url('home', self.request)) message = to_recover # check Récupération reason field if vac_type.name == u'Récupération': message = self.request.params.get('exception_text') message = message.strip() if message else message # check size if message and len(message) > 140: msg = ('%s reason must not exceed 140 characters' % vac_type.name) self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request)) # check CP usage if vac_type.name == u'CP': cp_class = check_user.get_cp_class(self.session) if cp_class: # only FR and LU have a dedicated CP class to use # convert days to hours for LU if needed days = cp_class.convert_days(days) error = cp_class.validate_request(check_user, pool, days, date_from, date_to) if error is not None: self.request.session.flash('error;%s' % error) return HTTPFound( location=route_url('home', self.request)) # create the request # default values target_status = u'PENDING' target_user = self.user target_notified = False sudo_use = False if self.user.is_admin: sudo_user_id = int(self.request.params.get('sudo_user')) if sudo_user_id != -1: user = User.by_id(self.session, sudo_user_id) if user: sudo_use = True target_user = user target_status = u'APPROVED_ADMIN' target_notified = True # save pool status when making the request pool_status = json.dumps(pool) request = Request( date_from=date_from, date_to=date_to, days=days, vacation_type=vac_type, status=target_status, user=target_user, notified=target_notified, label=label, message=message, pool_status=pool_status, ) self.session.add(request) self.session.flush() # create history entry sudo_user = None if sudo_use: sudo_user = self.user RequestHistory.new(self.session, request, '', target_status, target_user, pool_status, message=message, sudo_user=sudo_user) UserPool.decrement_request(self.session, request) if request and not sudo_use: msg = 'Request sent to your manager.' self.request.session.flash('info;%s' % msg) # call celery task directly, do not wait for polling from celery.registry import tasks from celery.task import subtask req_task = tasks['worker_pending'] data = {'req_id': request.id} subtask(req_task).apply_async(kwargs={'data': data}, countdown=5) log.info('scheduling task worker_pending for %s' % data) if request and sudo_use: settings = self.request.registry.settings if 'pyvac.celery.yaml' in settings: with open(settings['pyvac.celery.yaml']) as fdesc: Conf = yaml.load(fdesc, YAMLLoader) caldav_url = Conf.get('caldav').get('url') request.add_to_cal(caldav_url, self.session) msg = 'Request added to calendar and DB.' self.request.session.flash('info;%s' % msg) except Exception as exc: log.error(exc) msg = ('An error has occured while processing this request: %r' % exc) self.request.session.flash('error;%s' % msg) return HTTPFound(location=route_url('home', self.request))
def get_new_history(self, user, today, year): """Retrieve pool history using Pool and UserPool models.""" # group userpools per vacation_type if datetime.now().year == year: pools = {} for up in user.pools: if up.pool.pool_group: pools[up.pool.name] = up else: pools[up.pool.vacation_type.name] = up else: pools = {} ups = UserPool.by_user(self.session, user) # only select active pool for the selected year of pool history for up in ups: if up.pool.date_start <= today <= up.pool.date_end: if up.pool.pool_group: p2 = Pool.by_pool_group_raw(self.session, up.pool.pool_group) for pool in p2: pools[pool.name] = UserPool.by_user_pool_id( self.session, user.id, pool.id) else: pools[up.pool.vacation_type.name] = up pool_history = {} if 'RTT' in pools: pool_history['RTT'] = pools['RTT'].get_pool_history(self.session, self.user) # noqa history = [] if 'restant' in pools: restant = pools['restant'].get_pool_history(self.session, self.user) # noqa acquis = pools['acquis'].get_pool_history(self.session, self.user) pool_restant = restant[0]['value'] restant[0]['value'] = 0 if acquis: pool_acquis = acquis.pop(0)['value'] else: pool_acquis = 0 history = restant + acquis history.sort(key=lambda x: x['date'], reverse=False) cp_history = [] for idx, entry in enumerate(history, start=1): if entry['name'] == 'acquis': pool_acquis = round(pool_acquis, 2) + entry['value'] else: pool_restant = round(pool_restant, 2) + entry['value'] item = { 'date': entry['date'], 'value': entry['value'], 'name': entry['name'], 'restant': pool_restant, 'acquis': pool_acquis, 'flavor': entry.get('flavor', ''), 'req_id': entry.get('req_id'), } # if idx == len(history): # item['acquis'] = round(item['acquis']) cp_history.append(item) skip_idx = [] for idx, entry in enumerate(cp_history): try: next_entry = cp_history[idx + 1] except: next_entry = None # merge events in case of split decrement when using 2 pools if next_entry and entry['req_id'] and (entry['date'] == next_entry['date']) and (entry['req_id'] == next_entry['req_id']): # noqa # check for refunded request # refunded requests are when both value are either # positive or negative if ((entry['value'] > 0) and (next_entry['value'] < 0)) or ((entry['value'] < 0) and (next_entry['value'] > 0)): # noqa next_entry['flavor'] = '%s refunded' % next_entry['flavor'] continue if entry['name'] == 'restant': skip_idx.append(idx) entry['restant'] = 0 else: entry['restant'] = 0 skip_idx.append(idx + 1) entry['value'] += next_entry['value'] cp_history = [i for idx, i in enumerate(cp_history) if idx not in skip_idx] # remove duplicate 1st entry as we merged 2 lines into one if len(cp_history) > 1 and (cp_history[0]['acquis'] == cp_history[1]['acquis']) and (cp_history[0]['restant'] == cp_history[1]['restant']): # noqa cp_history.pop(0) pool_history['CP'] = cp_history return pool_history