shift_leave.py 18.6 KB
Newer Older
François C. committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
# -*- coding: utf-8 -*-

from datetime import datetime, timedelta
import pytz
from openerp import api, fields, models, _
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DTF
from openerp.exceptions import ValidationError


class ShiftLeave(models.Model):
    _inherit = 'shift.leave'

    show_alert_proposed_date = fields.Boolean(
        string='Show Alert Proposed Date', default=False,
        compute='_compute_proposed_date', store=True)
    alert_message = fields.Html(string='Alert Message',
                                compute='_compute_proposed_date',
                                store=True)
    return_date = fields.Date(
        string='Return Date',
        compute='_compute_proposed_date',
        help='Stop Date of the Temporary Leave plus 1 day')
    proposed_date = fields.Date(string='Proposed Date',
                                compute='_compute_proposed_date',
                                store=True)
    shift_date_after_return = fields.Date(
        string="Shift Time after return",
        compute="_compute_shift_time_after_return")
    show_proceed_message = fields.Boolean(
        string='Show Message Proceed', default=False,
        compute='_compute_proceed_message', store=True)
    proceed_message = fields.Html(string='Message Proceed',
                                  compute='_compute_proceed_message',
                                  store=True)
    non_defined_type = fields.Boolean(related="type_id.is_non_defined",
                                      string="Has Non Defined",
                                      store=True)
    non_defined_leave = fields.Boolean(string="Undefined Leave")
    is_send_reminder = fields.Boolean("Send Reminder", default=False)
    event_id = fields.Many2one('shift.counter.event', string="Event Counter")

    @api.multi
    @api.depends('partner_id', 'type_id', 'stop_date', 'non_defined_leave')
    def _compute_proposed_date(self):
        """
        ABCD Member: Proposed Date = Date of the ABCD shift after End Date
            entered - 1
        Volant / FTOP Member:
            Case 1: End Date entered + 7 >= Date of FTOP shift right after
                End Date entered => Proposed Date = Date of FTOP shift right
                after End Date entered + 1
            Case 2: Otherwise, do not propose anything
        """
        for leave in self:
            if leave.type_id.is_temp_leave and leave.non_defined_leave:
                leave.reset_propose_info()
                continue

            if not (leave.partner_id and leave.type_id and leave.stop_date) \
                    or not leave.type_id.is_temp_leave:
                leave.reset_propose_info()
                continue

            stop_date = fields.Date.from_string(leave.stop_date)
            leave.return_date = stop_date + timedelta(days=1)
            partner = leave.partner_id

            leave_stop_time_utc = \
                self.convert_date_to_utc_datetime(
                    leave.stop_date, start_of_day=False)

            next_shift = partner.get_next_shift(
                start_date=leave_stop_time_utc)
            _next_shift_time, next_shift_date = \
                partner.get_next_shift_date(start_date=leave_stop_time_utc)

            if not next_shift_date or not next_shift:
79 80 81 82
		future_date_shift = leave.guess_future_date_shift(
                    leave.stop_date, is_all_team=True)
		if len(future_date_shift) > 0:
			next_shift_date = future_date_shift[0]
François C. committed
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
                if next_shift_date:
                    next_shift_date = fields.Date.to_string(
                        next_shift_date)
                else:
                    leave.reset_propose_info()
                    continue
            next_shift = next_shift and next_shift.shift_id or ''

            next_shift_date = fields.Date.from_string(next_shift_date)
            in_ftop_team = partner.in_ftop_team
            if not in_ftop_team and \
                    (next_shift_date + timedelta(days=-1) > stop_date):
                proposed_date = next_shift_date + timedelta(days=-1)
                alert_message = _("You have inputted <b><i>{end_date}</i></b> "
                                  "as the end date of the temporary leave for "
                                  "<b><i>{partner}</i></b>. But a better date"
                                  " would be <b><i>{proposed_date}</i></b> "
                                  "which is one day before the shift "
                                  "<b><i>{shift}</i></b>. Do you accept the "
                                  "proposed date?")
            elif in_ftop_team and \
                    (next_shift_date + timedelta(days=1) <
                     stop_date + timedelta(days=7)):
                proposed_date = next_shift_date + timedelta(days=1)
                alert_message = _("You have inputted <b><i>{end_date}</i>"
                                  "</b> as the end date of the temporary leave"
                                  " for <b><i>{partner}</i></b>. But a better "
                                  "date would be <b><i>{proposed_date}</i></b>"
                                  " which is one day after the shift "
                                  "<b><i>{shift}</i></b>. Do you accept the "
                                  "proposed date?")
            else:
                leave.reset_propose_info()
                continue

            leave.proposed_date = proposed_date

            if leave.proposed_date and leave.proposed_date == leave.stop_date:
                continue

            leave.show_alert_proposed_date = True
            if next_shift:
                shift_name = next_shift.name + \
                    (next_shift.begin_date_string and
                     (' ' + next_shift.begin_date_string) or '')
            else:
                shift_name = ' begin on ' +\
                    fields.Date.to_string(next_shift_date)
            leave.alert_message = alert_message.format(
                end_date=stop_date, partner=leave.partner_id.name,
                proposed_date=proposed_date, shift=shift_name)

    @api.multi
    def _compute_shift_time_after_return(self):
        '''
        @Function to get shift time after propose date
        '''
        for leave in self:
            if not leave.partner_id or not leave.return_date:
                leave.shift_date_after_return = False
                continue

            leave_return_time_utc = \
                self.convert_date_to_utc_datetime(leave.return_date)

            # Look for date next shift after return date
            _next_shift_time, next_shift_date = \
                leave.partner_id.get_next_shift_date(
                    start_date=leave_return_time_utc)

            leave.shift_date_after_return = next_shift_date

    @api.multi
    def btn_confirm_propose(self):
        for leave in self:
            leave.show_alert_proposed_date = False
            leave.stop_date = leave.proposed_date

    @api.multi
    def btn_cancel_propose(self):
        for leave in self:
            leave.reset_propose_info()

    def reset_propose_info(self):
        self.proposed_date = False
        self.show_alert_proposed_date = False
        self.alert_message = ''

    def convert_date_to_utc_datetime(self, input_date, start_of_day=True):
        '''
        @Function to convert the date (at 00:00:00) to UTC datetime
        '''
        if start_of_day:
            input_date = input_date + ' 00:00:00'
        else:
            input_date = input_date + ' 23:59:59'

        # Convert input date from string to datetime
        input_date = datetime.strptime(input_date, DTF)

        # Convert to UTC time
        tz_name = self._context.get('tz') or self.env.user.tz
        context_tz = pytz.timezone(tz_name)
        local_dt = context_tz.localize(input_date, is_dst=None)
        return_datetime = local_dt.astimezone(pytz.utc)
        return return_datetime.strftime(DTF)

    @api.multi
    @api.depends('type_id', 'stop_date', 'start_date')
    def _compute_proceed_message(self):
        for leave in self:
            if leave.stop_date and leave.start_date:
                days_leave =\
                    (datetime.strptime(leave.stop_date, "%Y-%m-%d") -
                     datetime.strptime(leave.start_date, "%Y-%m-%d")).days + 1
                if leave.type_id.is_temp_leave and days_leave < 56:
                    leave.show_proceed_message = True
                    leave.proceed_message = (_(
                        "Leave duration is under 8 weeks, do you want to proceed?"
                    ))

    @api.multi
    @api.constrains('type_id', 'partner_id', 'start_date', 'stop_date')
    def _check_leave_for_ABCD_member(self):
        for record in self:
            today = fields.Date.context_today(self)
            abcd_lines_in_leave = record.partner_id.registration_ids.filtered(
                lambda l: l.date_begin >= record.start_date and
                l.date_end <= record.stop_date and l.date_begin >=
                today and l.state != 'cancel' and
                l.shift_ticket_id.shift_type == 'standard')
            if record.type_id.is_anticipated:
                num_line_guess = record.calculate_number_shift_future_in_leave()
                total_line = len(abcd_lines_in_leave) + num_line_guess
                if record.partner_id.in_ftop_team:
                    raise ValidationError(
                        _("This member is not part of an ABCD team."))
                elif record.partner_id.final_standard_point != 0:
                    raise ValidationError(_(
                        "Normally, this member is not eligible for early" +
                        " leave because he has to catch up"))
                elif len(abcd_lines_in_leave) < 2:
                    raise ValidationError(_(
                        "The period of leave must include TWO" +
                        " minimum missed services."))
                elif record.partner_id.final_ftop_point < total_line:
                    raise ValidationError(_(
                        "The member does not have enough" +
                        " credits to cover the proposed period."))

    @api.multi
    def update_info_anticipated_leave(self):
        self.ensure_one()
        today = fields.Date.context_today(self)
        lines = self.partner_id.registration_ids
        abcd_lines_in_leave = lines.filtered(
            lambda l: l.date_begin >= self.start_date and
            l.date_end <= self.stop_date and l.date_begin >=
            today and l.state != 'cancel' and
            l.shift_ticket_id.shift_type == 'standard')

        num_shift_guess = self.calculate_number_shift_future_in_leave()

        # Update FTOP points
        count_point_remove = len(abcd_lines_in_leave) + num_shift_guess

        point_counter_env = self.env['shift.counter.event']
        event = point_counter_env.sudo().with_context(
            {'automatic': True}).create({
                'name': _('Anticipated Leave'),
                'type': 'ftop',
                'partner_id': self.partner_id.id,
                'point_qty': -count_point_remove,
                'notes': _('This event was created to remove point base' +
                           ' on anticipated leave.')
            })
        self.event_id = event.id

    @api.multi
    def calculate_number_shift_future_in_leave(self):
        self.ensure_one()
        # Find shift template include current partner
        templates = self.partner_id.tmpl_reg_line_ids.filtered(
            lambda l: (l.is_current or l.is_future) and l.date_begin >=
            self.start_date and l.shift_ticket_id.shift_type ==
            'standard').mapped('shift_template_id')

        # Get number of shifts in period that is from end date of past ABCD line to
        # the end of leave to guess line partner might register

        num_shift_guess = 0

        for template in templates:
            last_shift_date = (fields.Datetime.from_string(
                template.last_shift_date) + timedelta(days=1)).strftime('%Y-%m-%d')
            rec_dates = template.get_recurrent_dates(
                last_shift_date, self.stop_date)
            for rec in rec_dates:
                if fields.Datetime.to_string(rec) < self.stop_date:
                    num_shift_guess += 1
        return num_shift_guess

    @api.multi
    def button_cancel(self):
        '''
            Add point counter event base on cancelling leave anticipated
        '''
        super(ShiftLeave, self).button_cancel()
        for leave in self:
            if leave.type_id.is_anticipated and leave.event_id:
                last_notes = leave.event_id.notes
                last_points = leave.event_id.point_qty
                leave.event_id.point_qty = 0
                leave.event_id.notes =\
                    last_notes + '\nLast point quantity: %s' % last_points

    @api.multi
    def guess_future_date_shift(self, stop_date, is_all_team=False):
        for leave in self:
            templates = leave.partner_id.tmpl_reg_line_ids.filtered(
                lambda l: (l.is_current or l.is_future) and
                l.shift_ticket_id.shift_type ==
                'standard').mapped('shift_template_id')
            if is_all_team and not templates:
                templates = leave.partner_id.tmpl_reg_line_ids.filtered(
                    lambda l: (l.is_current or l.is_future) and
                    l.shift_ticket_id.shift_type ==
                    'ftop').mapped('shift_template_id')
            shift_after_leave = []
            for template in templates:
                # Get the day after end leave 30 days to guess shift after leave
                next_shift_mounth = (fields.Datetime.from_string(
                    stop_date) + timedelta(days=30)).strftime('%Y-%m-%d')
                rec_dates = template.get_recurrent_dates(
                    template.last_shift_date, next_shift_mounth)

                for rec in rec_dates:
                    if fields.Datetime.to_string(rec) > stop_date:
                        shift_after_leave.append(rec)
            if shift_after_leave:
                shift_after_leave = sorted(shift_after_leave)
            return shift_after_leave

    @api.multi
    def update_date_end_anticipated_leave(self, vals):
        for leave in self:
            partner_id = vals.get('partner_id', leave.partner_id.id)
            stop_date = vals.get('stop_date', False)
            partner = self.env['res.partner'].browse(partner_id)
            future_lines = partner.registration_ids.filtered(
                lambda l: l.date_begin >= stop_date)

            # Suggest date end of leave before the registration after leave a day
            date_shift_guess = leave.guess_future_date_shift(stop_date)
            if date_shift_guess:
                vals.update({
                    'stop_date': fields.Datetime.to_string(
                        date_shift_guess[0] - timedelta(days=1))
                })
            elif future_lines:
                date_suggest = fields.Date.from_string(
                    future_lines[0].date_begin) - timedelta(days=1)
                if stop_date != date_suggest:
                    vals.update({
                        'stop_date': date_suggest
                    })

    @api.model
    def create(self, vals):
        type_id = vals.get('type_id', False)
        type_leave = self.env['shift.leave.type'].browse(type_id)

        if type_leave.is_anticipated:
            partner_id = vals.get('partner_id', self.partner_id.id)
            partner = self.env['res.partner'].browse(partner_id)
            stop_date = vals.get('stop_date', False)

            future_lines = partner.registration_ids.filtered(
                lambda l: l.date_begin >= stop_date)
            date_shift_guess = self.guess_future_date_shift(stop_date)

            if date_shift_guess:
                vals.update({
                    'stop_date': fields.Datetime.to_string(
                        date_shift_guess[0] - timedelta(days=1))
                })
            elif future_lines:
                date_suggest = fields.Date.from_string(
                    future_lines[0].date_begin) - timedelta(days=1)
                if stop_date != date_suggest:
                    vals.update({
                        'stop_date': date_suggest
                    })
        return super(ShiftLeave, self).create(vals)

    @api.multi
    def write(self, vals):
        type_id = vals.get('type_id', self.type_id.id)
        type_leave = self.env['shift.leave.type'].browse(type_id)
        if type_leave.is_anticipated and 'stop_date' in vals:
            self.update_date_end_anticipated_leave(vals)
        return super(ShiftLeave, self).write(vals)

    @api.multi
    def update_registration_template_based_non_define_leave(self):
        '''
        This method is remove such members from their teams 
        immediately by putting the end day for the templates closest the leave
        of current partner. Apply for non-define leave
        '''
        for leave in self:
            if leave.non_defined_type and leave.non_defined_leave:
                # get all template on partner
                template_registration = leave.partner_id.tmpl_reg_line_ids

                # get templates before the stop date leave
                last_templates =\
                    template_registration.filtered(
                        lambda l: l.is_current).sorted(
                        key=lambda l: l.date_begin, reverse=True)

                # Must mark leave as done before setting end_date
                # for tmpl_reg_line to make _compute_is_unsubscribed
                # work as expected
                # => (Dont set unsubscribed for this partner in
                # period of non-defined leave)
                leave.state = 'done'

                # put end date to template which has closest begin day and
                # update state leave
                if last_templates:
                    last_templates[0].date_end = fields.Date.from_string(
                        leave.start_date) - timedelta(days=1)

        return True

    @api.model
    def send_mail_reminder_non_defined_leaves(self):
        leave_env = self.env['shift.leave']

        leave_to_send = leave_env.search([
            ('is_send_reminder', '=', False),
            ('non_defined_leave', '=', True),
            ('stop_date', '<=',
             (datetime.now() + timedelta(days=10)).strftime('%Y-%m-%d'))
        ])

        # get mail template and send
        mail_template = self.env.ref(
            'coop_membership.reminder_end_leave_email')
        if mail_template:
            for leave in leave_to_send:
                mail_template.send_mail(leave.id)

            # update sent reminder
            leave_to_send.write({
                'is_send_reminder': True
            })