Example #1
0
 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')
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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))
Example #5
0
    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