Ejemplo n.º 1
0
class FleetVehicleLogFuel(models.Model):
    _name = 'fleet.vehicle.log.fuel'
    _description = 'Fuel log for vehicles'
    _inherits = {'fleet.vehicle.cost': 'cost_id'}

    @api.model
    def default_get(self, default_fields):
        res = super(FleetVehicleLogFuel, self).default_get(default_fields)
        service = self.env.ref('fleet.type_service_refueling',
                               raise_if_not_found=False)
        res.update({
            'date': fields.Date.context_today(self),
            'cost_subtype_id': service and service.id or False,
            'cost_type': 'fuel'
        })
        return res

    liter = fields.Float()
    price_per_liter = fields.Float()
    purchaser_id = fields.Many2one('res.partner', 'Purchaser')
    inv_ref = fields.Char('Invoice Reference', size=64)
    vendor_id = fields.Many2one('res.partner', 'Vendor')
    notes = fields.Text()
    cost_id = fields.Many2one('fleet.vehicle.cost',
                              'Cost',
                              required=True,
                              ondelete='cascade')
    # we need to keep this field as a related with store=True because the graph view doesn't support
    # (1) to address fields from inherited table
    # (2) fields that aren't stored in database
    cost_amount = fields.Float(related='cost_id.amount',
                               string='Amount',
                               store=True,
                               readonly=False)

    @api.onchange('vehicle_id')
    def _onchange_vehicle(self):
        if self.vehicle_id:
            self.odometer_unit = self.vehicle_id.odometer_unit
            self.purchaser_id = self.vehicle_id.driver_id.id

    @api.onchange('liter', 'price_per_liter', 'amount')
    def _onchange_liter_price_amount(self):
        # need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not
        # make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
        # liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
        # of 3.0/2=1.5)
        # If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
        # onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
        # computation to 2 decimal
        liter = float(self.liter)
        price_per_liter = float(self.price_per_liter)
        amount = float(self.amount)
        if liter > 0 and price_per_liter > 0 and round(liter * price_per_liter,
                                                       2) != amount:
            self.amount = round(liter * price_per_liter, 2)
        elif amount > 0 and liter > 0 and round(amount / liter,
                                                2) != price_per_liter:
            self.price_per_liter = round(amount / liter, 2)
        elif amount > 0 and price_per_liter > 0 and round(
                amount / price_per_liter, 2) != liter:
            self.liter = round(amount / price_per_liter, 2)
Ejemplo n.º 2
0
class EducationHostelMember(models.Model):
    _name = 'education.host_std'
    _rec_name = 'hostel_admission_no'
    _description = "Hostel Member"

    member_std_name = fields.Many2one('education.student',
                                      string="Admission No",
                                      required=True)
    member_fac_name = fields.Many2one('education.faculty', string="Name")
    name = fields.Char(string="Name")
    member_type = fields.Selection(string='Member Type',
                                   selection=[('is_faculty', 'Faculty'),
                                              ('is_student', 'Student')],
                                   default='is_student')
    email = fields.Char(string="Email", related='member_std_name.email')
    hostel_admission_no = fields.Char(string="Hostel Admission No",
                                      required=True,
                                      copy=False,
                                      readonly=True,
                                      index=True,
                                      default=lambda self: _('New'))
    phone = fields.Char(string="Phone", related='member_std_name.phone')
    mobile = fields.Char(string="Mobile", related='member_std_name.mobile')
    image = fields.Binary(
        "Image",
        attachment=True,
        help=
        "This field holds the image used as avatar for this contact, limited to 1024x1024px"
    )
    date_of_birth = fields.Date(string="Date Of birth",
                                related='member_std_name.date_of_birth')
    guardian_name = fields.Char(string="Guardian",
                                related='member_std_name.guardian_name.name')
    father_name = fields.Char(string="Father",
                              related='member_std_name.father_name')
    mother_name = fields.Char(string="Mother",
                              related='member_std_name.mother_name')
    gender = fields.Selection([('male', 'Male'), ('female', 'Female'),
                               ('other', 'Other')],
                              string='Gender',
                              required=True,
                              default='male',
                              track_visibility='onchange',
                              related='member_std_name.gender')
    blood_group = fields.Selection([('a+', 'A+'), ('a-', 'A-'), ('b+', 'B+'),
                                    ('o+', 'O+'), ('o-', 'O-'), ('ab-', 'AB-'),
                                    ('ab+', 'AB+')],
                                   string='Blood Group',
                                   required=True,
                                   default='a+',
                                   track_visibility='onchange')
    street = fields.Char('Street', related='member_std_name.per_street')
    street2 = fields.Char('Street2', related='member_std_name.per_street2')
    zip = fields.Char('Zip',
                      change_default=True,
                      related='member_std_name.per_zip')
    city = fields.Char('City', related='member_std_name.per_city')
    state_id = fields.Many2one("res.country.state",
                               string='State',
                               related='member_std_name.per_state_id')
    country_id = fields.Many2one('res.country',
                                 string='Country',
                                 default=19,
                                 related='member_std_name.per_country_id')
    allocation_detail = fields.One2many('education.room_member',
                                        'room_member',
                                        string="Allocation_details")
    hostel = fields.Many2one('education.hostel', string="Hostel")
    room = fields.Many2one('education.room', string="Room")
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        default=lambda self: self.env['res.company']._company_default_get())
    state = fields.Selection([('draft', 'Draft'), ('allocated', 'Allocated'),
                              ('vacated', 'Vacated')],
                             string='Status',
                             default='draft')
    vacated_date = fields.Date(string="Vacated Date")

    @api.model
    def create(self, vals):
        """computing the name of the member"""
        if vals.get('hostel_admission_no', _('New')) == _('New'):
            vals['hostel_admission_no'] = self.env['ir.sequence'].next_by_code(
                'education.host_std') or _('New')
        if vals.get('member_std_name'):
            obj = self.env['education.student'].search([
                ('id', '=', vals.get('member_std_name'))
            ])
            if obj.middle_name:
                vals[
                    'name'] = obj.name + '  ' + obj.middle_name + '  ' + obj.last_name
            else:
                vals['name'] = obj.name + '  ' + obj.last_name
        elif vals.get('member_fac_name'):
            obj = self.env['education.student'].search([
                ('id', '=', vals.get('member_fac_name'))
            ])
            if obj.middle_name:
                vals[
                    'name'] = obj.name + '  ' + obj.middle_name + '  ' + obj.last_name
            else:
                vals['name'] = obj.name + '  ' + obj.last_name
        res = super(EducationHostelMember, self).create(vals)
        return res

    @api.onchange('member_std_name', 'member_fac_name')
    def name_change(self):
        """computing the name of the member"""
        for d in self:
            if d.member_std_name:
                d.image = d.member_std_name.image
                d.name = str(
                    str(d.member_std_name.name) + ' ' +
                    str(d.member_std_name.last_name))
            if d.member_fac_name:
                d.name = str(d.member_fac_name.name + ' ' +
                             d.member_fac_name.last_name)

    @api.one
    @api.constrains('allocation_detail')
    def _check_capacity(self):
        """getting the current room and Hostel"""
        if len(self.allocation_detail) != 0:
            self.hostel = self.allocation_detail[len(self.allocation_detail) -
                                                 1].hostel_room_rel.id
            self.room = self.allocation_detail[len(self.allocation_detail) -
                                               1].room_member_rel.id
            self.vacated_date = self.allocation_detail[
                len(self.allocation_detail) - 1].vacated_date
            self.member_std_name.hostel_member = self.id
            self.member_std_name.hostel = self.hostel.id
            self.member_std_name.room = self.room.id
            self.member_std_name.hostel_fee = self.hostel.total

    @api.multi
    def allocate_member(self):
        self.ensure_one()
        if self.allocation_detail:
            length = len(self.allocation_detail)
            if not self.allocation_detail[length - 1].allocated_date:
                raise ValidationError(_('Enter the Allocated Date'))
        return self.write({'state': 'allocated'})

    @api.multi
    def vacate_member(self):
        self.ensure_one()
        if self.allocation_detail:
            length = len(self.allocation_detail)
            if not self.allocation_detail[length - 1].vacated_date:
                raise ValidationError(_('Enter the Vacated Date'))
        return self.write({'state': 'vacated'})

    @api.multi
    def reallocate(self):
        return self.write({'state': 'draft'})
Ejemplo n.º 3
0
class importAllStudent(models.Model):
    _name = 'education.import.previous.student'
    name = fields.Char('Name')

    # @api.model
    # def _get_groups(self):
    #     lst = []
    #
    #     for record in self:
    #         groups = self.env['education.application'].search([('register_id.id', '=', record.register_id.id)])
    #         for group in groups:
    #             lst.append((group.id, group.name))
    #     return lst
    date = fields.Date(default=fields.Date.today)
    import_qty = fields.Integer('No of Student to Import')
    register_id = fields.Many2one('education.admission.register',
                                  "Import student Of")
    level = fields.Integer(related='register_id.standard.id')
    import_group = fields.Char('From Group')
    import_section = fields.Char(
        string="section"
    )  #([('a','A'),('b','B'),('c','C'),('d','D')],'From Section')
    assign_class = fields.Many2one('education.class.division',
                                   "Assign Student to")
    students_to_assign = fields.One2many('education.application', 'import_id',
                                         "Student List")
    state = fields.Selection([(1, 'draft'), (2, 'done')], default='1')

    # @api.onchange('import_standard')
    # def get_student_to_import(self):
    #     for rec in self:
    #         if self.import_standard:
    #             applications=self.env['education.application'].search([('register_id.standard','=',self.import_standard)])
    #             if self.student_to_assign:
    #                 applications=self.env['education.application'].search([register_id('section','=',self.section),('group','=',self.group)])
    #

    @api.multi
    def import_students(self):
        lst = [('register_id', '=', self.register_id.id)]

        if self.import_section:
            lst.append(('section', '=', self.import_section))
        if self.import_group:
            lst.append(('group', '=', self.import_group))

        applications = self.env['education.application'].search(lst,
                                                                order='id asc')
        for app in applications:
            if app.student_id:
                # verify student
                document_ids = self.env['education.documents'].search([
                    ('application_ref', '=', app.id)
                ])
                # if not document_ids:
                #     raise ValidationError(_('No Documents provided'))
                app.write({'state': 'verification'})
                # insert documents
                doc_details = {
                    'application_ref': app.id,
                    'document_name': 1,
                    'has_hard_copy': False,
                    'reference': 1
                }
                documents = self.env['education.documents']
                document = documents.create(doc_details)
                document.write({
                    'verified_by':
                    self.env.uid,
                    'verified_date':
                    datetime.now().strftime("%Y-%m-%d"),
                    'state':
                    'done'
                })
                app.write({
                    'verified_by': self.env.uid,
                    'state': 'approve',
                    'class_id': self.assign_class.id,
                    'import_id': self.id
                })
                student = app.create_student()
Ejemplo n.º 4
0
class IrAttachment(models.Model):
    _inherit = 'ir.attachment'

    key = fields.Char()
    theme_template_id = fields.Many2one('theme.ir.attachment')
class EagleeduStudent(models.Model):
    _name = 'eagleedu.student'
    _inherits = {'res.partner': 'partner_id'}
    _description = 'This the application for student'
    _order = 'id desc'
    _rec_name = 'name'

    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100):
        if name:
            recs = self.search([('name', operator, name)] + (args or []),
                               limit=limit)
            if not recs:
                recs = self.search([('adm_no', operator, name)] + (args or []),
                                   limit=limit)
            if not recs:
                recs = self.search([('application_no', operator, name)] +
                                   (args or []),
                                   limit=limit)
            return recs.name_get()
        return super(EagleeduStudent, self).name_search(name,
                                                        args=args,
                                                        operator=operator,
                                                        limit=limit)

    @api.model
    def create(self, vals):
        """Over riding the create method to assign sequence for the newly creating the record"""
        vals['adm_no'] = self.env['ir.sequence'].next_by_code(
            'eagleedu.student')
        res = super(EagleeduStudent, self).create(vals)
        return res

    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 required=True,
                                 ondelete="cascade")
    adm_no = fields.Char(string="Admission Number", readonly=True)
    st_image = fields.Binary(string='Image',
                             help="Provide the image of the Student")
    application_no = fields.Char(string='Application  No',
                                 required=True,
                                 copy=False,
                                 readonly=True,
                                 index=True,
                                 default=lambda self: _('New'))
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env.user.company_id)
    standard_class = fields.Many2one('eagleedu.standard_class',
                                     string="Class Name",
                                     help="Enter Class Name")
    class_section_id = fields.Many2one('eagleedu.class_section',
                                       string="Section",
                                       help="Enter Class Section Name")
    group_division = fields.Many2one('eagleedu.group_division',
                                     string="Group Name",
                                     help="Enter Class Section Name")
    academic_year = fields.Many2one('eagleedu.academic.year',
                                    string="Academic Year",
                                    help="Select Academic Year")
    roll_no = fields.Integer(string="Roll No.", help="Enter Roll No.")

    st_name_b = fields.Char(string='Student Bangla Name')
    date_of_birth = fields.Date(string="Date Of birth")
    st_gender = fields.Selection([('male', 'Male'), ('female', 'Female'),
                                  ('other', 'Other')],
                                 string='Gender',
                                 required=False,
                                 track_visibility='onchange')
    st_blood_group = fields.Selection([('a+', 'A+'), ('a-', 'A-'),
                                       ('b+', 'B+'), ('o+', 'O+'),
                                       ('o-', 'O-'), ('ab-', 'AB-'),
                                       ('ab+', 'AB+')],
                                      string='Blood Group',
                                      track_visibility='onchange')
    st_passport_no = fields.Char(string="Passport No.",
                                 help="Proud to say my father is",
                                 required=False)
    application_no = fields.Char(string='Registration No',
                                 required=True,
                                 copy=False,
                                 readonly=True,
                                 index=True,
                                 default=lambda self: _('New'))
    registration_date = fields.Datetime(
        'Registration Date', default=lambda self: fields.datetime.now(
        ))  # , default=fields.Datetime.now, required=True

    st_father_name = fields.Char(string="Father's Name",
                                 help="Proud to say my father is",
                                 required=False)
    st_father_name_b = fields.Char(string="বাবার নাম",
                                   help="Proud to say my father is")
    st_father_occupation = fields.Char(string="Father's Occupation",
                                       help="father Occupation")
    st_father_email = fields.Char(string="Father's Email",
                                  help="father Occupation")
    father_mobile = fields.Char(string="Father's Mobile No",
                                help="Father's Mobile No")
    st_mother_name = fields.Char(string="Mother's Name",
                                 help="Proud to say my mother is",
                                 required=False)
    st_mother_name_b = fields.Char(string="মা এর নাম",
                                   help="Proud to say my mother is")
    st_mother_occupation = fields.Char(string="Mother Occupation",
                                       help="Proud to say my mother is")
    st_mother_email = fields.Char(string="Mother Email",
                                  help="Proud to say my mother is")
    mother_mobile = fields.Char(string="Mother's Mobile No",
                                help="mother's Mobile No")

    house_no = fields.Char(string='House No.', help="Enter the House No.")
    road_no = fields.Char(string='Area/Road No.',
                          help="Enter the Area or Road No.")
    post_office = fields.Char(string='Post Office',
                              help="Enter the Post Office Name")
    city = fields.Char(string='City', help="Enter the City name")
    bd_division_id = fields.Many2one('eagleedu.bddivision', string='Division')
    country_id = fields.Many2one('res.country',
                                 string='Country',
                                 ondelete='restrict',
                                 default=19)

    if_same_address = fields.Boolean(string="Permanent Address same as above",
                                     default=True)
    per_village = fields.Char(string='Village Name',
                              help="Enter the Village Name")
    per_po = fields.Char(string='Post Office Name',
                         help="Enter the Post office Name ")
    per_ps = fields.Char(string='Police Station',
                         help="Enter the Police Station Name")
    per_dist_id = fields.Many2one('eagleedu.bddistrict',
                                  string='District',
                                  help="Enter the City of District name")
    per_country_id = fields.Many2one('res.country',
                                     string='Country',
                                     ondelete='restrict',
                                     default=19)

    guardian_name = fields.Char(string="Guardian's Name",
                                help="Proud to say my guardian is")
    # guardian_relation = fields.Many2one('eagleedu.guardian.relation', string="Guardian's Relation", help="Proud to say my guardian is")
    guardian_mobile = fields.Char(string="Guardian's Mobile")

    religious_id = fields.Many2one('eagleedu.religious',
                                   string="Religious",
                                   help="My Religion is ")
    student_id = fields.Char('Student Id')
    section = fields.Char('Section', help="for import only")
    email = fields.Char(string="Student Email",
                        help="Enter E-mail id for contact purpose")
    phone = fields.Char(string="Student Phone",
                        help="Enter Phone no. for contact purpose")
    mobile = fields.Char(string="Student Mobile",
                         help="Enter Mobile num for contact purpose")
    nationality = fields.Many2one('res.country',
                                  string='Nationality',
                                  ondelete='restrict',
                                  default=19,
                                  help="Select the Nationality")
class Partner(models.Model):
    _inherit = ['res.partner']
    _name = 'res.partner'

    street_name = fields.Char('Street Name', compute='_split_street',
                              inverse='_set_street', store=True)
    street_number = fields.Char('House', compute='_split_street', help="House Number",
                                inverse='_set_street', store=True)
    street_number2 = fields.Char('Door', compute='_split_street', help="Door Number",
                                 inverse='_set_street', store=True)

    def _formatting_address_fields(self):
        """Returns the list of address fields usable to format addresses."""
        return super(Partner, self)._formatting_address_fields() + self.get_street_fields()

    def get_street_fields(self):
        """Returns the fields that can be used in a street format.
        Overwrite this function if you want to add your own fields."""
        return STREET_FIELDS

    def _set_street(self):
        """Updates the street field.
        Writes the `street` field on the partners when one of the sub-fields in STREET_FIELDS
        has been touched"""
        street_fields = self.get_street_fields()
        for partner in self:
            street_format = (partner.country_id.street_format or
                '%(street_number)s/%(street_number2)s %(street_name)s')
            previous_field = None
            previous_pos = 0
            street_value = ""
            separator = ""
            # iter on fields in street_format, detected as '%(<field_name>)s'
            for re_match in re.finditer(r'%\(\w+\)s', street_format):
                # [2:-2] is used to remove the extra chars '%(' and ')s'
                field_name = re_match.group()[2:-2]
                field_pos = re_match.start()
                if field_name not in street_fields:
                    raise UserError(_("Unrecognized field %s in street format.") % field_name)
                if not previous_field:
                    # first iteration: add heading chars in street_format
                    if partner[field_name]:
                        street_value += street_format[0:field_pos] + partner[field_name]
                else:
                    # get the substring between 2 fields, to be used as separator
                    separator = street_format[previous_pos:field_pos]
                    if street_value and partner[field_name]:
                        street_value += separator
                    if partner[field_name]:
                        street_value += partner[field_name]
                previous_field = field_name
                previous_pos = re_match.end()

            # add trailing chars in street_format
            street_value += street_format[previous_pos:]
            partner.street = street_value

    def _split_street_with_params(self, street_raw, street_format):
        street_fields = self.get_street_fields()
        vals = {}
        previous_pos = 0
        field_name = None
        # iter on fields in street_format, detected as '%(<field_name>)s'
        for re_match in re.finditer(r'%\(\w+\)s', street_format):
            field_pos = re_match.start()
            if not field_name:
                #first iteration: remove the heading chars
                street_raw = street_raw[field_pos:]

            # get the substring between 2 fields, to be used as separator
            separator = street_format[previous_pos:field_pos]
            field_value = None
            if separator and field_name:
                #maxsplit set to 1 to unpack only the first element and let the rest untouched
                tmp = street_raw.split(separator, 1)
                if len(tmp) == 2:
                    field_value, street_raw = tmp
                    vals[field_name] = field_value
            if field_value or not field_name:
                # select next field to find (first pass OR field found)
                # [2:-2] is used to remove the extra chars '%(' and ')s'
                field_name = re_match.group()[2:-2]
            else:
                # value not found: keep looking for the same field
                pass
            if field_name not in street_fields:
                raise UserError(_("Unrecognized field %s in street format.") % field_name)
            previous_pos = re_match.end()

        # last field value is what remains in street_raw minus trailing chars in street_format
        trailing_chars = street_format[previous_pos:]
        if trailing_chars and street_raw.endswith(trailing_chars):
            vals[field_name] = street_raw[:-len(trailing_chars)]
        else:
            vals[field_name] = street_raw
        return vals


    @api.depends('street')
    def _split_street(self):
        """Splits street value into sub-fields.
        Recomputes the fields of STREET_FIELDS when `street` of a partner is updated"""
        street_fields = self.get_street_fields()
        for partner in self:
            if not partner.street:
                for field in street_fields:
                    partner[field] = None
                continue

            street_format = (partner.country_id.street_format or
                '%(street_number)s/%(street_number2)s %(street_name)s')
            street_raw = partner.street
            vals = self._split_street_with_params(street_raw, street_format)
            # assign the values to the fields
            for k, v in vals.items():
                partner[k] = v
            for k in set(street_fields) - set(vals):
                partner[k] = None

    def write(self, vals):
        res = super(Partner, self).write(vals)
        if 'country_id' in vals and 'street' not in vals:
            self._set_street()
        return res
Ejemplo n.º 7
0
class IrRule(models.Model):
    _name = 'ir.rule'
    _description = 'Record Rule'
    _order = 'model_id DESC,id'
    _MODES = ['read', 'write', 'create', 'unlink']

    name = fields.Char(index=True)
    active = fields.Boolean(
        default=True,
        help=
        "If you uncheck the active field, it will disable the record rule without deleting it (if you delete a native record rule, it may be re-created when you reload the module)."
    )
    model_id = fields.Many2one('ir.model',
                               string='Object',
                               index=True,
                               required=True,
                               ondelete="cascade")
    groups = fields.Many2many('res.groups', 'rule_group_rel', 'rule_group_id',
                              'group_id')
    domain_force = fields.Text(string='Domain')
    perm_read = fields.Boolean(string='Apply for Read', default=True)
    perm_write = fields.Boolean(string='Apply for Write', default=True)
    perm_create = fields.Boolean(string='Apply for Create', default=True)
    perm_unlink = fields.Boolean(string='Apply for Delete', default=True)

    _sql_constraints = [
        ('no_access_rights',
         'CHECK (perm_read!=False or perm_write!=False or perm_create!=False or perm_unlink!=False)',
         'Rule must have at least one checked access right !'),
    ]

    def _eval_context_for_combinations(self):
        """Returns a dictionary to use as evaluation context for
           ir.rule domains, when the goal is to obtain python lists
           that are easier to parse and combine, but not to
           actually execute them."""
        return {'user': tools.unquote('user'), 'time': tools.unquote('time')}

    @api.model
    def _eval_context(self):
        """Returns a dictionary to use as evaluation context for
           ir.rule domains.
           Note: company_ids contains the ids of the activated companies
           by the user with the switch company menu. These companies are
           filtered and trusted.
        """
        # use an empty context for 'user' to make the domain evaluation
        # independent from the context
        return {
            'user': self.env.user.with_context({}),
            'time': time,
            'company_ids': self.env.companies.ids,
            'company_id': self.env.company.id,
        }

    @api.depends('groups')
    def _compute_global(self):
        for rule in self:
            rule['global'] = not rule.groups

    @api.constrains('model_id')
    def _check_model_transience(self):
        if any(self.env[rule.model_id.model].is_transient() for rule in self):
            raise ValidationError(
                _('Rules can not be applied on Transient models.'))

    @api.constrains('model_id')
    def _check_model_name(self):
        # Don't allow rules on rules records (this model).
        if any(rule.model_id.model == self._name for rule in self):
            raise ValidationError(
                _('Rules can not be applied on the Record Rules model.'))

    def _compute_domain_keys(self):
        """ Return the list of context keys to use for caching ``_compute_domain``. """
        return ['allowed_company_ids']

    def _get_failing(self, for_records, mode='read'):
        """ Returns the rules for the mode for the current user which fail on
        the specified records.

        Can return any global rule and/or all local rules (since local rules
        are OR-ed together, the entire group succeeds or fails, while global
        rules get AND-ed and can each fail)
        """
        Model = for_records.browse(()).sudo()
        eval_context = self._eval_context()

        all_rules = self._get_rules(Model._name, mode=mode).sudo()

        # first check if the group rules fail for any record (aka if
        # searching on (records, group_rules) filters out some of the records)
        group_rules = all_rules.filtered(
            lambda r: r.groups and r.groups & self.env.user.groups_id)
        group_domains = expression.OR([
            safe_eval(r.domain_force, eval_context) if r.domain_force else []
            for r in group_rules
        ])
        # if all records get returned, the group rules are not failing
        if Model.search_count(
                expression.AND([[('id', 'in', for_records.ids)],
                                group_domains])) == len(for_records):
            group_rules = self.browse(())

        # failing rules are previously selected group rules or any failing global rule
        def is_failing(r, ids=for_records.ids):
            dom = safe_eval(r.domain_force,
                            eval_context) if r.domain_force else []
            return Model.search_count(
                expression.AND([[('id', 'in', ids)],
                                expression.normalize_domain(dom)])) < len(ids)

        return all_rules.filtered(lambda r: r in group_rules or
                                  (not r.groups and is_failing(r))).with_user(
                                      self.env.user)

    def _get_rules(self, model_name, mode='read'):
        """ Returns all the rules matching the model for the mode for the
        current user.
        """
        if mode not in self._MODES:
            raise ValueError('Invalid mode: %r' % (mode, ))

        if self.env.su:
            return self.browse(())

        query = """ SELECT r.id FROM ir_rule r JOIN ir_model m ON (r.model_id=m.id)
                    WHERE m.model=%s AND r.active AND r.perm_{mode}
                    AND (r.id IN (SELECT rule_group_id FROM rule_group_rel rg
                                  JOIN res_groups_users_rel gu ON (rg.group_id=gu.gid)
                                  WHERE gu.uid=%s)
                         OR r.global)
                    ORDER BY r.id
                """.format(mode=mode)
        self._cr.execute(query, (model_name, self._uid))
        return self.browse(row[0] for row in self._cr.fetchall())

    @api.model
    @tools.conditional(
        'xml' not in config['dev_mode'],
        tools.ormcache('self.env.uid', 'self.env.su', 'model_name', 'mode',
                       'tuple(self._compute_domain_context_values())'),
    )
    def _compute_domain(self, model_name, mode="read"):
        rules = self._get_rules(model_name, mode=mode)
        if not rules:
            return

        # browse user and rules as SUPERUSER_ID to avoid access errors!
        eval_context = self._eval_context()
        user_groups = self.env.user.groups_id
        global_domains = []  # list of domains
        group_domains = []  # list of domains
        for rule in rules.sudo():
            # evaluate the domain for the current user
            dom = safe_eval(rule.domain_force,
                            eval_context) if rule.domain_force else []
            dom = expression.normalize_domain(dom)
            if not rule.groups:
                global_domains.append(dom)
            elif rule.groups & user_groups:
                group_domains.append(dom)

        # combine global domains and group domains
        if not group_domains:
            return expression.AND(global_domains)
        return expression.AND(global_domains + [expression.OR(group_domains)])

    def _compute_domain_context_values(self):
        for k in self._compute_domain_keys():
            v = self._context.get(k)
            if isinstance(v, list):
                # currently this could be a frozenset (to avoid depending on
                # the order of allowed_company_ids) but it seems safer if
                # possibly slightly more miss-y to use a tuple
                v = tuple(v)
            yield v

    @api.model
    def clear_cache(self):
        """ Deprecated, use `clear_caches` instead. """
        self.clear_caches()

    @api.model
    def domain_get(self, model_name, mode='read'):
        dom = self._compute_domain(model_name, mode)
        if dom:
            # _where_calc is called as superuser. This means that rules can
            # involve objects on which the real uid has no acces rights.
            # This means also there is no implicit restriction (e.g. an object
            # references another object the user can't see).
            query = self.env[model_name].sudo()._where_calc(dom,
                                                            active_test=False)
            return query.where_clause, query.where_clause_params, query.tables
        return [], [], ['"%s"' % self.env[model_name]._table]

    def unlink(self):
        res = super(IrRule, self).unlink()
        self.clear_caches()
        return res

    @api.model_create_multi
    def create(self, vals_list):
        res = super(IrRule, self).create(vals_list)
        # DLE P33: tests
        self.flush()
        self.clear_caches()
        return res

    def write(self, vals):
        res = super(IrRule, self).write(vals)
        # DLE P33: tests
        # - eagle/addons/test_access_rights/tests/test_feedback.py
        # - eagle/addons/test_access_rights/tests/test_ir_rules.py
        # - eagle/addons/base/tests/test_orm.py (/home/dle/src/eagle/master-nochange-fp/eagle/addons/base/tests/test_orm.py)
        self.flush()
        self.clear_caches()
        return res

    def _make_access_error(self, operation, records):
        _logger.info(
            'Access Denied by record rules for operation: %s on record ids: %r, uid: %s, model: %s',
            operation, records.ids[:6], self._uid, records._name)

        model = records._name
        description = self.env['ir.model']._get(model).name or model
        if not self.env.user.has_group('base.group_no_one'):
            return AccessError(
                _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: "%(document_kind)s" (%(document_model)s), Operation: %(operation)s)'
                  ) % {
                      'document_kind': description,
                      'document_model': model,
                      'operation': operation,
                  })

        # This extended AccessError is only displayed in debug mode.
        # Note that by default, public and portal users do not have
        # the group "base.group_no_one", even if debug mode is enabled,
        # so it is relatively safe here to include the list of rules and
        # record names.
        rules = self._get_failing(records, mode=operation).sudo()
        error = AccessError(
            _("""The requested operation ("%(operation)s" on "%(document_kind)s" (%(document_model)s)) was rejected because of the following rules:
%(rules_list)s
%(multi_company_warning)s
(Records: %(example_records)s, User: %(user_id)s)""") % {
                'operation':
                operation,
                'document_kind':
                description,
                'document_model':
                model,
                'rules_list':
                '\n'.join('- %s' % rule.name for rule in rules),
                'multi_company_warning':
                ('\n' + _('Note: this might be a multi-company issue.') +
                 '\n') if any('company_id' in (r.domain_force or [])
                              for r in rules) else '',
                'example_records':
                ' - '.join([
                    '%s (id=%s)' % (rec.display_name, rec.id)
                    for rec in records[:6].sudo()
                ]),
                'user_id':
                '%s (id=%s)' % (self.env.user.name, self.env.user.id),
            })
        # clean up the cache of records prefetched with display_name above
        for record in records[:6]:
            record._cache.clear()
        return error
Ejemplo n.º 8
0
class EventTicket(models.Model):
    _name = 'event.event.ticket'
    _description = 'Event Ticket'

    def _default_product_id(self):
        return self.env.ref('event_sale.product_product_event',
                            raise_if_not_found=False)

    name = fields.Char(string='Name', required=True, translate=True)
    event_type_id = fields.Many2one('event.type',
                                    string='Event Category',
                                    ondelete='cascade')
    event_id = fields.Many2one('event.event',
                               string="Event",
                               ondelete='cascade')
    company_id = fields.Many2one('res.company', related='event_id.company_id')
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 required=True,
                                 domain=[("event_ok", "=", True)],
                                 default=_default_product_id)
    registration_ids = fields.One2many('event.registration',
                                       'event_ticket_id',
                                       string='Registrations')
    price = fields.Float(string='Price', digits='Product Price')
    deadline = fields.Date(string="Sales End")
    is_expired = fields.Boolean(string='Is Expired',
                                compute='_compute_is_expired')

    price_reduce = fields.Float(string="Price Reduce",
                                compute="_compute_price_reduce",
                                digits='Product Price')
    price_reduce_taxinc = fields.Float(compute='_get_price_reduce_tax',
                                       string='Price Reduce Tax inc')
    # seats fields
    seats_availability = fields.Selection([('limited', 'Limited'),
                                           ('unlimited', 'Unlimited')],
                                          string='Available Seat',
                                          required=True,
                                          store=True,
                                          compute='_compute_seats',
                                          default="limited")
    seats_max = fields.Integer(
        string='Maximum Available Seats',
        help=
        "Define the number of available tickets. If you have too much registrations you will "
        "not be able to sell tickets anymore. Set 0 to ignore this rule set as unlimited."
    )
    seats_reserved = fields.Integer(string='Reserved Seats',
                                    compute='_compute_seats',
                                    store=True)
    seats_available = fields.Integer(string='Available Seats',
                                     compute='_compute_seats',
                                     store=True)
    seats_unconfirmed = fields.Integer(string='Unconfirmed Seat Reservations',
                                       compute='_compute_seats',
                                       store=True)
    seats_used = fields.Integer(compute='_compute_seats', store=True)

    def _compute_is_expired(self):
        for record in self:
            if record.deadline:
                current_date = fields.Date.context_today(
                    record.with_context(tz=record.event_id.date_tz))
                record.is_expired = record.deadline < current_date
            else:
                record.is_expired = False

    def _compute_price_reduce(self):
        for record in self:
            product = record.product_id
            discount = product.lst_price and (
                product.lst_price - product.price) / product.lst_price or 0.0
            record.price_reduce = (1.0 - discount) * record.price

    def _get_price_reduce_tax(self):
        for record in self:
            # sudo necessary here since the field is most probably accessed through the website
            tax_ids = record.sudo().product_id.taxes_id.filtered(
                lambda r: r.company_id == record.event_id.company_id)
            taxes = tax_ids.compute_all(record.price_reduce,
                                        record.event_id.company_id.currency_id,
                                        1.0,
                                        product=record.product_id)
            record.price_reduce_taxinc = taxes['total_included']

    @api.depends('seats_max', 'registration_ids.state')
    def _compute_seats(self):
        """ Determine reserved, available, reserved but unconfirmed and used seats. """
        # initialize fields to 0 + compute seats availability
        for ticket in self:
            ticket.seats_availability = 'unlimited' if ticket.seats_max == 0 else 'limited'
            ticket.seats_unconfirmed = ticket.seats_reserved = ticket.seats_used = ticket.seats_available = 0
        # aggregate registrations by ticket and by state
        if self.ids:
            state_field = {
                'draft': 'seats_unconfirmed',
                'open': 'seats_reserved',
                'done': 'seats_used',
            }
            query = """ SELECT event_ticket_id, state, count(event_id)
                        FROM event_registration
                        WHERE event_ticket_id IN %s AND state IN ('draft', 'open', 'done')
                        GROUP BY event_ticket_id, state
                    """
            self.env['event.registration'].flush(
                ['event_id', 'event_ticket_id', 'state'])
            self.env.cr.execute(query, (tuple(self.ids), ))
            for event_ticket_id, state, num in self.env.cr.fetchall():
                ticket = self.browse(event_ticket_id)
                ticket[state_field[state]] += num
        # compute seats_available
        for ticket in self:
            if ticket.seats_max > 0:
                ticket.seats_available = ticket.seats_max - (
                    ticket.seats_reserved + ticket.seats_used)

    @api.constrains('registration_ids', 'seats_max')
    def _check_seats_limit(self):
        for record in self:
            if record.seats_max and record.seats_available < 0:
                raise ValidationError(
                    _('No more available seats for this ticket type.'))

    @api.constrains('event_type_id', 'event_id')
    def _constrains_event(self):
        if any(ticket.event_type_id and ticket.event_id for ticket in self):
            raise UserError(
                _('Ticket cannot belong to both the event category and the event itself.'
                  ))

    @api.onchange('product_id')
    def _onchange_product_id(self):
        self.price = self.product_id.list_price or 0

    def get_ticket_multiline_description_sale(self):
        """ Compute a multiline description of this ticket, in the context of sales.
            It will often be used as the default description of a sales order line referencing this ticket.

        1. the first line is the ticket name
        2. the second line is the event name (if it exists, which should be the case with a normal workflow) or the product name (if it exists)

        We decided to ignore entirely the product name and the product description_sale because they are considered to be replaced by the ticket name and event name.
            -> the workflow of creating a new event also does not lead to filling them correctly, as the product is created through the event interface
        """

        name = self.display_name

        if self.event_id:
            name += '\n' + self.event_id.display_name
        elif self.product_id:
            name += '\n' + self.product_id.display_name

        return name
Ejemplo n.º 9
0
class Currency(models.Model):
    _name = "res.currency"
    _description = "Currency"
    _order = 'active desc, name'

    # Note: 'code' column was removed as of v6.0, the 'name' should now hold the ISO code.
    name = fields.Char(string='Currency', size=3, required=True, help="Currency Code (ISO 4217)")
    symbol = fields.Char(help="Currency sign, to be used when printing amounts.", required=True)
    rate = fields.Float(compute='_compute_current_rate', string='Current Rate', digits=(12, 6),
                        help='The rate of the currency to the currency of rate 1.')
    rate_ids = fields.One2many('res.currency.rate', 'currency_id', string='Rates')
    rounding = fields.Float(string='Rounding Factor', digits=(12, 6), default=0.01)
    decimal_places = fields.Integer(compute='_compute_decimal_places', store=True)
    active = fields.Boolean(default=True)
    position = fields.Selection([('after', 'After Amount'), ('before', 'Before Amount')], default='after',
        string='Symbol Position', help="Determines where the currency symbol should be placed after or before the amount.")
    date = fields.Date(compute='_compute_date')
    currency_unit_label = fields.Char(string="Currency Unit", help="Currency Unit Name")
    currency_subunit_label = fields.Char(string="Currency Subunit", help="Currency Subunit Name")

    _sql_constraints = [
        ('unique_name', 'unique (name)', 'The currency code must be unique!'),
        ('rounding_gt_zero', 'CHECK (rounding>0)', 'The rounding factor must be greater than 0!')
    ]

    def _get_rates(self, company, date):
        query = """SELECT c.id,
                          COALESCE((SELECT r.rate FROM res_currency_rate r
                                  WHERE r.currency_id = c.id AND r.name <= %s
                                    AND (r.company_id IS NULL OR r.company_id = %s)
                               ORDER BY r.company_id, r.name DESC
                                  LIMIT 1), 1.0) AS rate
                   FROM res_currency c
                   WHERE c.id IN %s"""
        self._cr.execute(query, (date, company.id, tuple(self.ids)))
        currency_rates = dict(self._cr.fetchall())
        return currency_rates

    @api.multi
    @api.depends('rate_ids.rate')
    def _compute_current_rate(self):
        date = self._context.get('date') or fields.Date.today()
        company = self.env['res.company'].browse(self._context.get('company_id')) or self.env['res.users']._get_company()
        # the subquery selects the last rate before 'date' for the given currency/company
        currency_rates = self._get_rates(company, date)
        for currency in self:
            currency.rate = currency_rates.get(currency.id) or 1.0

    @api.multi
    @api.depends('rounding')
    def _compute_decimal_places(self):
        for currency in self:
            if 0 < currency.rounding < 1:
                currency.decimal_places = int(math.ceil(math.log10(1/currency.rounding)))
            else:
                currency.decimal_places = 0

    @api.multi
    @api.depends('rate_ids.name')
    def _compute_date(self):
        for currency in self:
            currency.date = currency.rate_ids[:1].name

    @api.model
    def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
        results = super(Currency, self)._name_search(name, args, operator=operator, limit=limit, name_get_uid=name_get_uid)
        if not results:
            name_match = CURRENCY_DISPLAY_PATTERN.match(name)
            if name_match:
                results = super(Currency, self)._name_search(name_match.group(1), args, operator=operator, limit=limit, name_get_uid=name_get_uid)
        return results

    @api.multi
    def name_get(self):
        return [(currency.id, tools.ustr(currency.name)) for currency in self]

    @api.multi
    def amount_to_text(self, amount):
        self.ensure_one()
        def _num2words(number, lang):
            try:
                return num2words(number, lang=lang).title()
            except NotImplementedError:
                return num2words(number, lang='en').title()

        if num2words is None:
            logging.getLogger(__name__).warning("The library 'num2words' is missing, cannot render textual amounts.")
            return ""

        formatted = "%.{0}f".format(self.decimal_places) % amount
        parts = formatted.partition('.')
        integer_value = int(parts[0])
        fractional_value = int(parts[2] or 0)

        lang_code = self.env.context.get('lang') or self.env.user.lang
        lang = self.env['res.lang'].with_context(active_test=False).search([('code', '=', lang_code)])
        amount_words = tools.ustr('{amt_value} {amt_word}').format(
                        amt_value=_num2words(integer_value, lang=lang.iso_code),
                        amt_word=self.currency_unit_label,
                        )
        if not self.is_zero(amount - integer_value):
            amount_words += ' ' + _('and') + tools.ustr(' {amt_value} {amt_word}').format(
                        amt_value=_num2words(fractional_value, lang=lang.iso_code),
                        amt_word=self.currency_subunit_label,
                        )
        return amount_words

    @api.multi
    def round(self, amount):
        """Return ``amount`` rounded  according to ``self``'s rounding rules.

           :param float amount: the amount to round
           :return: rounded float
        """
        # TODO: Need to check why it calls round() from sale.py, _amount_all() with *No* ID after below commits,
        # https://github.com/eagle/eagle/commit/36ee1ad813204dcb91e9f5f20d746dff6f080ac2
        # https://github.com/eagle/eagle/commit/0b6058c585d7d9a57bd7581b8211f20fca3ec3f7
        # Removing self.ensure_one() will make few test cases to break of modules event_sale, sale_mrp and stock_dropshipping.
        #self.ensure_one()
        return tools.float_round(amount, precision_rounding=self.rounding)

    @api.multi
    def compare_amounts(self, amount1, amount2):
        """Compare ``amount1`` and ``amount2`` after rounding them according to the
           given currency's precision..
           An amount is considered lower/greater than another amount if their rounded
           value is different. This is not the same as having a non-zero difference!

           For example 1.432 and 1.431 are equal at 2 digits precision,
           so this method would return 0.
           However 0.006 and 0.002 are considered different (returns 1) because
           they respectively round to 0.01 and 0.0, even though
           0.006-0.002 = 0.004 which would be considered zero at 2 digits precision.

           :param float amount1: first amount to compare
           :param float amount2: second amount to compare
           :return: (resp.) -1, 0 or 1, if ``amount1`` is (resp.) lower than,
                    equal to, or greater than ``amount2``, according to
                    ``currency``'s rounding.

           With the new API, call it like: ``currency.compare_amounts(amount1, amount2)``.
        """
        return tools.float_compare(amount1, amount2, precision_rounding=self.rounding)

    @api.multi
    def is_zero(self, amount):
        """Returns true if ``amount`` is small enough to be treated as
           zero according to current currency's rounding rules.
           Warning: ``is_zero(amount1-amount2)`` is not always equivalent to
           ``compare_amounts(amount1,amount2) == 0``, as the former will round after
           computing the difference, while the latter will round before, giving
           different results for e.g. 0.006 and 0.002 at 2 digits precision.

           :param float amount: amount to compare with currency's zero

           With the new API, call it like: ``currency.is_zero(amount)``.
        """
        return tools.float_is_zero(amount, precision_rounding=self.rounding)

    @api.model
    def _get_conversion_rate(self, from_currency, to_currency, company, date):
        currency_rates = (from_currency + to_currency)._get_rates(company, date)
        res = currency_rates.get(to_currency.id) / currency_rates.get(from_currency.id)
        return res

    def _convert(self, from_amount, to_currency, company, date, round=True):
        """Returns the converted amount of ``from_amount``` from the currency
           ``self`` to the currency ``to_currency`` for the given ``date`` and
           company.

           :param company: The company from which we retrieve the convertion rate
           :param date: The nearest date from which we retriev the conversion rate.
           :param round: Round the result or not
        """
        self, to_currency = self or to_currency, to_currency or self
        assert self, "convert amount from unknown currency"
        assert to_currency, "convert amount to unknown currency"
        assert company, "convert amount from unknown company"
        assert date, "convert amount from unknown date"
        # apply conversion rate
        if self == to_currency:
            to_amount = from_amount
        else:
            to_amount = from_amount * self._get_conversion_rate(self, to_currency, company, date)
        # apply rounding
        return to_currency.round(to_amount) if round else to_amount

    @api.model
    def _compute(self, from_currency, to_currency, from_amount, round=True):
        _logger.warning('The `_compute` method is deprecated. Use `_convert` instead')
        date = self._context.get('date') or fields.Date.today()
        company = self.env['res.company'].browse(self._context.get('company_id')) or self.env['res.users']._get_company()
        return from_currency._convert(from_amount, to_currency, company, date)

    @api.multi
    def compute(self, from_amount, to_currency, round=True):
        _logger.warning('The `compute` method is deprecated. Use `_convert` instead')
        date = self._context.get('date') or fields.Date.today()
        company = self.env['res.company'].browse(self._context.get('company_id')) or self.env['res.users']._get_company()
        return self._convert(from_amount, to_currency, company, date)

    def _select_companies_rates(self):
        return """
Ejemplo n.º 10
0
class MassMailingList(models.Model):
    """Model of a contact list. """
    _name = 'mailing.list'
    _order = 'name'
    _description = 'Mailing List'

    name = fields.Char(string='Mailing List', required=True)
    active = fields.Boolean(default=True)
    contact_nbr = fields.Integer(compute="_compute_contact_nbr",
                                 string='Number of Contacts')
    contact_ids = fields.Many2many('mailing.contact',
                                   'mailing_contact_list_rel',
                                   'list_id',
                                   'contact_id',
                                   string='Mailing Lists')
    subscription_ids = fields.One2many('mailing.contact.subscription',
                                       'list_id',
                                       string='Subscription Information')
    is_public = fields.Boolean(
        default=True,
        help=
        "The mailing list can be accessible by recipient in the unsubscription"
        " page to allows him to update his subscription preferences.")

    # Compute number of contacts non opt-out, non blacklisted and valid email recipient for a mailing list
    def _compute_contact_nbr(self):
        self.env.cr.execute(
            '''
            select
                list_id, count(*)
            from
                mailing_contact_list_rel r
                left join mailing_contact c on (r.contact_id=c.id)
                left join mail_blacklist bl on c.email_normalized = bl.email and bl.active
            where
                list_id in %s
                AND COALESCE(r.opt_out,FALSE) = FALSE
                AND c.email_normalized IS NOT NULL
                AND bl.id IS NULL
            group by
                list_id
        ''', (tuple(self.ids), ))
        data = dict(self.env.cr.fetchall())
        for mailing_list in self:
            mailing_list.contact_nbr = data.get(mailing_list.id, 0)

    def name_get(self):
        return [(list.id, "%s (%s)" % (list.name, list.contact_nbr))
                for list in self]

    def action_view_contacts(self):
        action = self.env.ref(
            'mass_mailing.action_view_mass_mailing_contacts').read()[0]
        action['domain'] = [('list_ids', 'in', self.ids)]
        context = dict(self.env.context,
                       search_default_filter_valid_email_recipient=1,
                       default_list_ids=self.ids)
        action['context'] = context
        return action

    def action_merge(self, src_lists, archive):
        """
            Insert all the contact from the mailing lists 'src_lists' to the
            mailing list in 'self'. Possibility to archive the mailing lists
            'src_lists' after the merge except the destination mailing list 'self'.
        """
        # Explation of the SQL query with an example. There are the following lists
        # A (id=4): [email protected]; [email protected]
        # B (id=5): [email protected]; [email protected]
        # C (id=6): nothing
        # To merge the mailing lists A and B into C, we build the view st that looks
        # like this with our example:
        #
        #  contact_id |           email           | row_number |  list_id |
        # ------------+---------------------------+------------------------
        #           4 | [email protected]              |          1 |        4 |
        #           6 | [email protected]              |          2 |        5 |
        #           5 | [email protected]           |          1 |        4 |
        #           7 | [email protected]           |          1 |        5 |
        #
        # The row_column is kind of an occurence counter for the email address.
        # Then we create the Many2many relation between the destination list and the contacts
        # while avoiding to insert an existing email address (if the destination is in the source
        # for example)
        self.ensure_one()
        # Put destination is sources lists if not already the case
        src_lists |= self
        self.env['mailing.contact'].flush(['email', 'email_normalized'])
        self.env['mailing.contact.subscription'].flush(
            ['contact_id', 'opt_out', 'list_id'])
        self.env.cr.execute(
            """
            INSERT INTO mailing_contact_list_rel (contact_id, list_id)
            SELECT st.contact_id AS contact_id, %s AS list_id
            FROM
                (
                SELECT
                    contact.id AS contact_id,
                    contact.email AS email,
                    list.id AS list_id,
                    row_number() OVER (PARTITION BY email ORDER BY email) AS rn
                FROM
                    mailing_contact contact,
                    mailing_contact_list_rel contact_list_rel,
                    mailing_list list
                WHERE contact.id=contact_list_rel.contact_id
                AND COALESCE(contact_list_rel.opt_out,FALSE) = FALSE
                AND contact.email_normalized NOT IN (select email from mail_blacklist where active = TRUE)
                AND list.id=contact_list_rel.list_id
                AND list.id IN %s
                AND NOT EXISTS
                    (
                    SELECT 1
                    FROM
                        mailing_contact contact2,
                        mailing_contact_list_rel contact_list_rel2
                    WHERE contact2.email = contact.email
                    AND contact_list_rel2.contact_id = contact2.id
                    AND contact_list_rel2.list_id = %s
                    )
                ) st
            WHERE st.rn = 1;""", (self.id, tuple(src_lists.ids), self.id))
        self.flush()
        self.invalidate_cache()
        if archive:
            (src_lists - self).write({'active': False})

    def close_dialog(self):
        return {'type': 'ir.actions.act_window_close'}
Ejemplo n.º 11
0
class IrFilters(models.Model):
    _name = 'ir.filters'
    _description = 'Filters'
    _order = 'model_id, name, id desc'

    name = fields.Char(string='Filter Name', translate=True, required=True)
    user_id = fields.Many2one(
        'res.users',
        string='User',
        ondelete='cascade',
        help=
        "The user this filter is private to. When left empty the filter is public "
        "and available to all users.")
    domain = fields.Text(default='[]', required=True)
    context = fields.Text(default='{}', required=True)
    sort = fields.Text(default='[]', required=True)
    model_id = fields.Selection(selection='_list_all_models',
                                string='Model',
                                required=True)
    is_default = fields.Boolean(string='Default Filter')
    action_id = fields.Many2one(
        'ir.actions.actions',
        string='Action',
        ondelete='cascade',
        help="The menu action this filter applies to. "
        "When left empty the filter applies to all menus "
        "for this model.")
    active = fields.Boolean(default=True)

    @api.model
    def _list_all_models(self):
        self._cr.execute("SELECT model, name FROM ir_model ORDER BY name")
        return self._cr.fetchall()

    @api.multi
    def copy(self, default=None):
        self.ensure_one()
        default = dict(default or {}, name=_('%s (copy)') % self.name)
        return super(IrFilters, self).copy(default)

    @api.multi
    def _get_eval_domain(self):
        self.ensure_one()
        return ast.literal_eval(self.domain)

    @api.model
    def _get_action_domain(self, action_id=None):
        """Return a domain component for matching filters that are visible in the
           same context (menu/view) as the given action."""
        if action_id:
            # filters specific to this menu + global ones
            return [('action_id', 'in', [action_id, False])]
        # only global ones
        return [('action_id', '=', False)]

    @api.model
    def get_filters(self, model, action_id=None):
        """Obtain the list of filters available for the user on the given model.

        :param action_id: optional ID of action to restrict filters to this action
            plus global filters. If missing only global filters are returned.
            The action does not have to correspond to the model, it may only be
            a contextual action.
        :return: list of :meth:`~osv.read`-like dicts containing the
            ``name``, ``is_default``, ``domain``, ``user_id`` (m2o tuple),
            ``action_id`` (m2o tuple) and ``context`` of the matching ``ir.filters``.
        """
        # available filters: private filters (user_id=uid) and public filters (uid=NULL),
        # and filters for the action (action_id=action_id) or global (action_id=NULL)
        action_domain = self._get_action_domain(action_id)
        filters = self.search(action_domain +
                              [('model_id', '=',
                                model), ('user_id', 'in', [self._uid, False])])
        user_context = self.env.user.context_get()
        return filters.with_context(user_context).read(
            ['name', 'is_default', 'domain', 'context', 'user_id', 'sort'])

    @api.model
    def _check_global_default(self, vals, matching_filters):
        """ _check_global_default(dict, list(dict), dict) -> None

        Checks if there is a global default for the model_id requested.

        If there is, and the default is different than the record being written
        (-> we're not updating the current global default), raise an error
        to avoid users unknowingly overwriting existing global defaults (they
        have to explicitly remove the current default before setting a new one)

        This method should only be called if ``vals`` is trying to set
        ``is_default``

        :raises eagle.exceptions.UserError: if there is an existing default and
                                            we're not updating it
        """
        domain = self._get_action_domain(vals.get('action_id'))
        defaults = self.search(domain + [
            ('model_id', '=', vals['model_id']),
            ('user_id', '=', False),
            ('is_default', '=', True),
        ])

        if not defaults:
            return
        if matching_filters and (matching_filters[0]['id'] == defaults.id):
            return

        raise UserError(
            _("There is already a shared filter set as default for %(model)s, delete or change it before setting a new default"
              ) % {'model': vals.get('model_id')})

    @api.model
    @api.returns('self', lambda value: value.id)
    def create_or_replace(self, vals):
        action_id = vals.get('action_id')
        current_filters = self.get_filters(vals['model_id'], action_id)
        matching_filters = [
            f for f in current_filters
            if f['name'].lower() == vals['name'].lower()
            # next line looks for matching user_ids (specific or global), i.e.
            # f.user_id is False and vals.user_id is False or missing,
            # or f.user_id.id == vals.user_id
            if (f['user_id'] and f['user_id'][0]) == vals.get('user_id')
        ]

        if vals.get('is_default'):
            if vals.get('user_id'):
                # Setting new default: any other default that belongs to the user
                # should be turned off
                domain = self._get_action_domain(action_id)
                defaults = self.search(domain + [
                    ('model_id', '=', vals['model_id']),
                    ('user_id', '=', vals['user_id']),
                    ('is_default', '=', True),
                ])
                if defaults:
                    defaults.write({'is_default': False})
            else:
                self._check_global_default(vals, matching_filters)

        # When a filter exists for the same (name, model, user) triple, we simply
        # replace its definition (considering action_id irrelevant here)
        if matching_filters:
            matching_filter = self.browse(matching_filters[0]['id'])
            matching_filter.write(vals)
            return matching_filter

        return self.create(vals)

    _sql_constraints = [
        # Partial constraint, complemented by unique index (see below). Still
        # useful to keep because it provides a proper error message when a
        # violation occurs, as it shares the same prefix as the unique index.
        ('name_model_uid_unique',
         'unique (name, model_id, user_id, action_id)',
         'Filter names must be unique'),
    ]

    @api.model_cr_context
    def _auto_init(self):
        result = super(IrFilters, self)._auto_init()
        # Use unique index to implement unique constraint on the lowercase name (not possible using a constraint)
        tools.create_unique_index(
            self._cr, 'ir_filters_name_model_uid_unique_action_index',
            self._table, [
                'lower(name)', 'model_id', 'COALESCE(user_id,-1)',
                'COALESCE(action_id,-1)'
            ])
        return result
Ejemplo n.º 12
0
class SmsSms(models.Model):
    _name = 'sms.sms'
    _description = 'Outgoing SMS'
    _rec_name = 'number'
    _order = 'id DESC'

    IAP_TO_SMS_STATE = {
        'success': 'sent',
        'insufficient_credit': 'sms_credit',
        'wrong_number_format': 'sms_number_format',
        'server_error': 'sms_server'
    }

    number = fields.Char('Number')
    body = fields.Text()
    partner_id = fields.Many2one('res.partner', 'Customer')
    mail_message_id = fields.Many2one('mail.message', index=True)
    state = fields.Selection([('outgoing', 'In Queue'), ('sent', 'Sent'),
                              ('error', 'Error'), ('canceled', 'Canceled')],
                             'SMS Status',
                             readonly=True,
                             copy=False,
                             default='outgoing',
                             required=True)
    error_code = fields.Selection([
        ('sms_number_missing', 'Missing Number'),
        ('sms_number_format', 'Wrong Number Format'),
        ('sms_credit', 'Insufficient Credit'),
        ('sms_server', 'Server Error'),
        # mass mode specific codes
        ('sms_blacklist', 'Blacklisted'),
        ('sms_duplicate', 'Duplicate'),
    ])

    def send(self, delete_all=False, auto_commit=False, raise_exception=False):
        """ Main API method to send SMS.

          :param delete_all: delete all SMS (sent or not); otherwise delete only
            sent SMS;
          :param auto_commit: commit after each batch of SMS;
          :param raise_exception: raise if there is an issue contacting IAP;
        """
        for batch_ids in self._split_batch():
            self.browse(batch_ids)._send(delete_all=delete_all,
                                         raise_exception=raise_exception)
            # auto-commit if asked except in testing mode
            if auto_commit is True and not getattr(threading.currentThread(),
                                                   'testing', False):
                self._cr.commit()

    def cancel(self):
        self.state = 'canceled'

    @api.model
    def _process_queue(self, ids=None):
        """ Send immediately queued messages, committing after each message is sent.
        This is not transactional and should not be called during another transaction!

       :param list ids: optional list of emails ids to send. If passed no search
         is performed, and these ids are used instead.
        """
        domain = [('state', '=', 'outgoing')]

        filtered_ids = self.search(
            domain, limit=10000
        ).ids  # TDE note: arbitrary limit we might have to update
        if ids:
            ids = list(set(filtered_ids) & set(ids))
        else:
            ids = filtered_ids
        ids.sort()

        res = None
        try:
            # auto-commit except in testing mode
            auto_commit = not getattr(threading.currentThread(), 'testing',
                                      False)
            res = self.browse(ids).send(delete_all=False,
                                        auto_commit=auto_commit,
                                        raise_exception=False)
        except Exception:
            _logger.exception("Failed processing SMS queue")
        return res

    def _split_batch(self):
        batch_size = int(self.env['ir.config_parameter'].sudo().get_param(
            'sms.session.batch.size', 500))
        for sms_batch in tools.split_every(batch_size, self.ids):
            yield sms_batch

    def _send(self, delete_all=False, raise_exception=False):
        """ This method tries to send SMS after checking the number (presence and
        formatting). """
        iap_data = [{
            'res_id': record.id,
            'number': record.number,
            'content': record.body,
        } for record in self]

        try:
            iap_results = self.env['sms.api']._send_sms_batch(iap_data)
        except Exception as e:
            _logger.info('Sent batch %s SMS: %s: failed with exception %s',
                         len(self.ids), self.ids, e)
            if raise_exception:
                raise
            self._postprocess_iap_sent_sms([{
                'res_id': sms.id,
                'state': 'server_error'
            } for sms in self],
                                           delete_all=delete_all)
        else:
            _logger.info('Send batch %s SMS: %s: gave %s', len(self.ids),
                         self.ids, iap_results)
            self._postprocess_iap_sent_sms(iap_results, delete_all=delete_all)

    def _postprocess_iap_sent_sms(self,
                                  iap_results,
                                  failure_reason=None,
                                  delete_all=False):
        if delete_all:
            todelete_sms_ids = [item['res_id'] for item in iap_results]
        else:
            todelete_sms_ids = [
                item['res_id'] for item in iap_results
                if item['state'] == 'success'
            ]

        for state in self.IAP_TO_SMS_STATE.keys():
            sms_ids = [
                item['res_id'] for item in iap_results
                if item['state'] == state
            ]
            if sms_ids:
                if state != 'success' and not delete_all:
                    self.env['sms.sms'].sudo().browse(sms_ids).write({
                        'state':
                        'error',
                        'error_code':
                        self.IAP_TO_SMS_STATE[state],
                    })
                notifications = self.env['mail.notification'].sudo().search([
                    ('notification_type', '=', 'sms'),
                    ('sms_id', 'in', sms_ids),
                    ('notification_status', 'not in', ('sent', 'canceled'))
                ])
                if notifications:
                    notifications.write({
                        'notification_status':
                        'sent' if state == 'success' else 'exception',
                        'failure_type':
                        self.IAP_TO_SMS_STATE[state]
                        if state != 'success' else False,
                        'failure_reason':
                        failure_reason if failure_reason else False,
                    })

        if todelete_sms_ids:
            self.browse(todelete_sms_ids).sudo().unlink()
Ejemplo n.º 13
0
class DateRange(models.Model):
    _name = "date.range"
    _description = "Date Range"
    _order = "type_name,date_start"

    @api.model
    def _default_company(self):
        return self.env['res.company']._company_default_get('date.range')

    name = fields.Char(required=True, translate=True)
    date_start = fields.Date(string='Start date', required=True)
    date_end = fields.Date(string='End date', required=True)
    type_id = fields.Many2one(comodel_name='date.range.type',
                              string='Type',
                              index=1,
                              required=True,
                              ondelete='restrict',
                              domain="['|', ('company_id', '=', company_id), "
                              "('company_id', '=', False)]")
    type_name = fields.Char(related='type_id.name',
                            readonly=True,
                            store=True,
                            string="Type Name")
    company_id = fields.Many2one(comodel_name='res.company',
                                 string='Company',
                                 index=1,
                                 default=_default_company)
    active = fields.Boolean(
        help="The active field allows you to hide the date range without "
        "removing it.",
        default=True)

    _sql_constraints = [
        ('date_range_uniq', 'unique (name,type_id, company_id)',
         'A date range must be unique per company !')
    ]

    @api.onchange('company_id', 'type_id')
    def _onchange_company_id(self):
        if self.company_id and self.type_id.company_id and \
                self.type_id.company_id != self.company_id:
            self._cache.update(
                self._convert_to_cache({'type_id': False}, update=True))

    @api.multi
    @api.constrains('company_id', 'type_id')
    def _check_company_id_type_id(self):
        for rec in self.sudo():
            if rec.company_id and rec.type_id.company_id and\
                    rec.company_id != rec.type_id.company_id:
                raise ValidationError(
                    _('The Company in the Date Range and in '
                      'Date Range Type must be the same.'))

    @api.constrains('type_id', 'date_start', 'date_end', 'company_id')
    def _validate_range(self):
        for this in self:
            if this.date_start > this.date_end:
                raise ValidationError(
                    _("%s is not a valid range (%s > %s)") %
                    (this.name, this.date_start, this.date_end))
            if this.type_id.allow_overlap:
                continue
            # here we use a plain SQL query to benefit of the daterange
            # function available in PostgresSQL
            # (http://www.postgresql.org/docs/current/static/rangetypes.html)
            SQL = """
                SELECT
                    id
                FROM
                    date_range dt
                WHERE
                    DATERANGE(dt.date_start, dt.date_end, '[]') &&
                        DATERANGE(%s::date, %s::date, '[]')
                    AND dt.id != %s
                    AND dt.active
                    AND dt.company_id = %s
                    AND dt.type_id=%s;"""
            self.env.cr.execute(SQL,
                                (this.date_start, this.date_end, this.id,
                                 this.company_id.id or None, this.type_id.id))
            res = self.env.cr.fetchall()
            if res:
                dt = self.browse(res[0][0])
                raise ValidationError(
                    _("%s overlaps %s") % (this.name, dt.name))

    @api.multi
    def get_domain(self, field_name):
        self.ensure_one()
        return [(field_name, '>=', self.date_start),
                (field_name, '<=', self.date_end)]
Ejemplo n.º 14
0
class FleetVehicleCost(models.Model):
    _name = 'fleet.vehicle.cost'
    _description = 'Cost related to a vehicle'
    _order = 'date desc, vehicle_id asc'

    name = fields.Char(related='vehicle_id.name',
                       string='Name',
                       store=True,
                       readonly=False)
    vehicle_id = fields.Many2one('fleet.vehicle',
                                 'Vehicle',
                                 required=True,
                                 help='Vehicle concerned by this log')
    cost_subtype_id = fields.Many2one(
        'fleet.service.type',
        'Type',
        help='Cost type purchased with this cost')
    amount = fields.Float('Total Price')
    cost_type = fields.Selection([('contract', 'Contract'),
                                  ('services', 'Services'), ('fuel', 'Fuel'),
                                  ('other', 'Other')],
                                 'Category of the cost',
                                 default="other",
                                 help='For internal purpose only',
                                 required=True)
    parent_id = fields.Many2one('fleet.vehicle.cost',
                                'Parent',
                                help='Parent cost to this current cost')
    cost_ids = fields.One2many('fleet.vehicle.cost',
                               'parent_id',
                               'Included Services',
                               copy=True)
    odometer_id = fields.Many2one(
        'fleet.vehicle.odometer',
        'Odometer',
        help='Odometer measure of the vehicle at the moment of this log')
    odometer = fields.Float(
        compute="_get_odometer",
        inverse='_set_odometer',
        string='Odometer Value',
        help='Odometer measure of the vehicle at the moment of this log')
    odometer_unit = fields.Selection(related='vehicle_id.odometer_unit',
                                     string="Unit",
                                     readonly=True)
    date = fields.Date(help='Date when the cost has been executed')
    contract_id = fields.Many2one('fleet.vehicle.log.contract',
                                  'Contract',
                                  help='Contract attached to this cost')
    auto_generated = fields.Boolean('Automatically Generated', readonly=True)
    description = fields.Char("Cost Description")
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 default=lambda self: self.env.company)
    currency_id = fields.Many2one('res.currency',
                                  related='company_id.currency_id')

    def _get_odometer(self):
        self.odometer = 0.0
        for record in self:
            record.odometer = False
            if record.odometer_id:
                record.odometer = record.odometer_id.value

    def _set_odometer(self):
        for record in self:
            if not record.odometer:
                raise UserError(
                    _('Emptying the odometer value of a vehicle is not allowed.'
                      ))
            odometer = self.env['fleet.vehicle.odometer'].create({
                'value':
                record.odometer,
                'date':
                record.date or fields.Date.context_today(record),
                'vehicle_id':
                record.vehicle_id.id
            })
            self.odometer_id = odometer

    @api.model_create_multi
    def create(self, vals_list):
        for data in vals_list:
            # make sure that the data are consistent with values of parent and contract records given
            if 'parent_id' in data and data['parent_id']:
                parent = self.browse(data['parent_id'])
                data['vehicle_id'] = parent.vehicle_id.id
                data['date'] = parent.date
                data['cost_type'] = parent.cost_type
            if 'contract_id' in data and data['contract_id']:
                contract = self.env['fleet.vehicle.log.contract'].browse(
                    data['contract_id'])
                data['vehicle_id'] = contract.vehicle_id.id
                data['cost_subtype_id'] = contract.cost_subtype_id.id
                data['cost_type'] = contract.cost_type
            if 'odometer' in data and not data['odometer']:
                # if received value for odometer is 0, then remove it from the
                # data as it would result to the creation of a
                # odometer log with 0, which is to be avoided
                del data['odometer']
        return super(FleetVehicleCost, self).create(vals_list)
Ejemplo n.º 15
0
class RemovalStrategy(models.Model):
    _name = 'product.removal'
    _description = 'Removal Strategy'

    name = fields.Char('Name', required=True)
    method = fields.Char("Method", required=True, help="FIFO, LIFO...")
Ejemplo n.º 16
0
class FleetVehicle(models.Model):
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _name = 'fleet.vehicle'
    _description = 'Vehicle'
    _order = 'license_plate asc, acquisition_date asc'

    def _get_default_state(self):
        state = self.env.ref('fleet.fleet_vehicle_state_registered', raise_if_not_found=False)
        return state if state and state.id else False

    name = fields.Char(compute="_compute_vehicle_name", store=True)
    active = fields.Boolean('Active', default=True, track_visibility="onchange")
    company_id = fields.Many2one('res.company', 'Company')
    license_plate = fields.Char(track_visibility="onchange",
        help='License plate number of the vehicle (i = plate number for a car)')
    vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
    driver_id = fields.Many2one('res.partner', 'Driver', track_visibility="onchange", help='Driver of the vehicle', copy=False, auto_join=True)
    model_id = fields.Many2one('fleet.vehicle.model', 'Model',
        track_visibility="onchange", required=True, help='Model of the vehicle')
    brand_id = fields.Many2one('fleet.vehicle.model.brand', 'Brand', related="model_id.brand_id", store=True, readonly=False)
    log_drivers = fields.One2many('fleet.vehicle.assignation.log', 'vehicle_id', string='Assignation Logs')
    log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
    log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
    log_contracts = fields.One2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts')
    cost_count = fields.Integer(compute="_compute_count_all", string="Costs")
    contract_count = fields.Integer(compute="_compute_count_all", string='Contract Count')
    service_count = fields.Integer(compute="_compute_count_all", string='Services')
    fuel_logs_count = fields.Integer(compute="_compute_count_all", string='Fuel Log Count')
    odometer_count = fields.Integer(compute="_compute_count_all", string='Odometer')
    acquisition_date = fields.Date('Immatriculation Date', required=False,
        default=fields.Date.today, help='Date when the vehicle has been immatriculated')
    first_contract_date = fields.Date(string="First Contract Date", default=fields.Date.today)
    color = fields.Char(help='Color of the vehicle')
    state_id = fields.Many2one('fleet.vehicle.state', 'State',
        default=_get_default_state, group_expand='_read_group_stage_ids',
        track_visibility="onchange",
        help='Current state of the vehicle', ondelete="set null")
    location = fields.Char(help='Location of the vehicle (garage, ...)')
    seats = fields.Integer('Seats Number', help='Number of seats of the vehicle')
    model_year = fields.Char('Model Year',help='Year of the model')
    doors = fields.Integer('Doors Number', help='Number of doors of the vehicle', default=5)
    tag_ids = fields.Many2many('fleet.vehicle.tag', 'fleet_vehicle_vehicle_tag_rel', 'vehicle_tag_id', 'tag_id', 'Tags', copy=False)
    odometer = fields.Float(compute='_get_odometer', inverse='_set_odometer', string='Last Odometer',
        help='Odometer measure of the vehicle at the moment of this log')
    odometer_unit = fields.Selection([
        ('kilometers', 'Kilometers'),
        ('miles', 'Miles')
        ], 'Odometer Unit', default='kilometers', help='Unit of the odometer ', required=True)
    transmission = fields.Selection([('manual', 'Manual'), ('automatic', 'Automatic')], 'Transmission', help='Transmission Used by the vehicle')
    fuel_type = fields.Selection([
        ('gasoline', 'Gasoline'),
        ('diesel', 'Diesel'),
        ('lpg', 'LPG'),
        ('electric', 'Electric'),
        ('hybrid', 'Hybrid')
        ], 'Fuel Type', help='Fuel Used by the vehicle')
    horsepower = fields.Integer()
    horsepower_tax = fields.Float('Horsepower Taxation')
    power = fields.Integer('Power', help='Power in kW of the vehicle')
    co2 = fields.Float('CO2 Emissions', help='CO2 emissions of the vehicle')
    image = fields.Binary(related='model_id.image', string="Logo", readonly=False)
    image_medium = fields.Binary(related='model_id.image_medium', string="Logo (medium)", readonly=False)
    image_small = fields.Binary(related='model_id.image_small', string="Logo (small)", readonly=False)
    contract_renewal_due_soon = fields.Boolean(compute='_compute_contract_reminder', search='_search_contract_renewal_due_soon',
        string='Has Contracts to renew', multi='contract_info')
    contract_renewal_overdue = fields.Boolean(compute='_compute_contract_reminder', search='_search_get_overdue_contract_reminder',
        string='Has Contracts Overdue', multi='contract_info')
    contract_renewal_name = fields.Text(compute='_compute_contract_reminder', string='Name of contract to renew soon', multi='contract_info')
    contract_renewal_total = fields.Text(compute='_compute_contract_reminder', string='Total of contracts due or overdue minus one',
        multi='contract_info')
    car_value = fields.Float(string="Catalog Value (VAT Incl.)", help='Value of the bought vehicle')
    residual_value = fields.Float()

    @api.depends('model_id.brand_id.name', 'model_id.name', 'license_plate')
    def _compute_vehicle_name(self):
        for record in self:
            record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + (record.license_plate or _('No Plate'))

    def _get_odometer(self):
        FleetVehicalOdometer = self.env['fleet.vehicle.odometer']
        for record in self:
            vehicle_odometer = FleetVehicalOdometer.search([('vehicle_id', '=', record.id)], limit=1, order='value desc')
            if vehicle_odometer:
                record.odometer = vehicle_odometer.value
            else:
                record.odometer = 0

    def _set_odometer(self):
        for record in self:
            if record.odometer:
                date = fields.Date.context_today(record)
                data = {'value': record.odometer, 'date': date, 'vehicle_id': record.id}
                self.env['fleet.vehicle.odometer'].create(data)

    def _compute_count_all(self):
        Odometer = self.env['fleet.vehicle.odometer']
        LogFuel = self.env['fleet.vehicle.log.fuel']
        LogService = self.env['fleet.vehicle.log.services']
        LogContract = self.env['fleet.vehicle.log.contract']
        Cost = self.env['fleet.vehicle.cost']
        for record in self:
            record.odometer_count = Odometer.search_count([('vehicle_id', '=', record.id)])
            record.fuel_logs_count = LogFuel.search_count([('vehicle_id', '=', record.id)])
            record.service_count = LogService.search_count([('vehicle_id', '=', record.id)])
            record.contract_count = LogContract.search_count([('vehicle_id', '=', record.id),('state','!=','closed')])
            record.cost_count = Cost.search_count([('vehicle_id', '=', record.id), ('parent_id', '=', False)])

    @api.depends('log_contracts')
    def _compute_contract_reminder(self):
        for record in self:
            overdue = False
            due_soon = False
            total = 0
            name = ''
            for element in record.log_contracts:
                if element.state in ('open', 'expired') and element.expiration_date:
                    current_date_str = fields.Date.context_today(record)
                    due_time_str = element.expiration_date
                    current_date = fields.Date.from_string(current_date_str)
                    due_time = fields.Date.from_string(due_time_str)
                    diff_time = (due_time - current_date).days
                    if diff_time < 0:
                        overdue = True
                        total += 1
                    if diff_time < 15 and diff_time >= 0:
                            due_soon = True
                            total += 1
                    if overdue or due_soon:
                        log_contract = self.env['fleet.vehicle.log.contract'].search([
                            ('vehicle_id', '=', record.id),
                            ('state', 'in', ('open', 'expired'))
                            ], limit=1, order='expiration_date asc')
                        if log_contract:
                            # we display only the name of the oldest overdue/due soon contract
                            name = log_contract.cost_subtype_id.name

            record.contract_renewal_overdue = overdue
            record.contract_renewal_due_soon = due_soon
            record.contract_renewal_total = total - 1  # we remove 1 from the real total for display purposes
            record.contract_renewal_name = name

    def _search_contract_renewal_due_soon(self, operator, value):
        res = []
        assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'
        if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False):
            search_operator = 'in'
        else:
            search_operator = 'not in'
        today = fields.Date.context_today(self)
        datetime_today = fields.Datetime.from_string(today)
        limit_date = fields.Datetime.to_string(datetime_today + relativedelta(days=+15))
        self.env.cr.execute("""SELECT cost.vehicle_id,
                        count(contract.id) AS contract_number
                        FROM fleet_vehicle_cost cost
                        LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
                        WHERE contract.expiration_date IS NOT NULL
                          AND contract.expiration_date > %s
                          AND contract.expiration_date < %s
                          AND contract.state IN ('open', 'expired')
                        GROUP BY cost.vehicle_id""", (today, limit_date))
        res_ids = [x[0] for x in self.env.cr.fetchall()]
        res.append(('id', search_operator, res_ids))
        return res

    def _search_get_overdue_contract_reminder(self, operator, value):
        res = []
        assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'
        if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False):
            search_operator = 'in'
        else:
            search_operator = 'not in'
        today = fields.Date.context_today(self)
        self.env.cr.execute('''SELECT cost.vehicle_id,
                        count(contract.id) AS contract_number
                        FROM fleet_vehicle_cost cost
                        LEFT JOIN fleet_vehicle_log_contract contract ON contract.cost_id = cost.id
                        WHERE contract.expiration_date IS NOT NULL
                          AND contract.expiration_date < %s
                          AND contract.state IN ('open', 'expired')
                        GROUP BY cost.vehicle_id ''', (today,))
        res_ids = [x[0] for x in self.env.cr.fetchall()]
        res.append(('id', search_operator, res_ids))
        return res

    @api.onchange('model_id')
    def _onchange_model(self):
        if self.model_id:
            self.image_medium = self.model_id.image
        else:
            self.image_medium = False

    @api.model
    def create(self, vals):
        res = super(FleetVehicle, self).create(vals)
        if 'driver_id' in vals and vals['driver_id']:
            res.create_driver_history(vals['driver_id'])
        return res

    @api.multi
    def write(self, vals):
        res = super(FleetVehicle, self).write(vals)
        if 'driver_id' in vals and vals['driver_id']:
            self.create_driver_history(vals['driver_id'])
        if 'active' in vals and not vals['active']:
            self.mapped('log_contracts').write({'active': False})
        return res

    def create_driver_history(self, driver_id):
        for vehicle in self:
            self.env['fleet.vehicle.assignation.log'].create({
                'vehicle_id': vehicle.id,
                'driver_id': driver_id,
                'date_start': fields.Date.today(), 
            })

    @api.model
    def _read_group_stage_ids(self, stages, domain, order):
        return self.env['fleet.vehicle.state'].search([], order=order)

    @api.model
    def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
        domain = args or []
        domain = expression.AND([domain, ['|', ('name', operator, name), ('driver_id.name', operator, name)]])
        rec = self._search(domain, limit=limit, access_rights_uid=name_get_uid)
        return self.browse(rec).name_get()

    @api.multi
    def return_action_to_open(self):
        """ This opens the xml view specified in xml_id for the current vehicle """
        self.ensure_one()
        xml_id = self.env.context.get('xml_id')
        if xml_id:
            res = self.env['ir.actions.act_window'].for_xml_id('fleet', xml_id)
            res.update(
                context=dict(self.env.context, default_vehicle_id=self.id, group_by=False),
                domain=[('vehicle_id', '=', self.id)]
            )
            return res
        return False

    @api.multi
    def act_show_log_cost(self):
        """ This opens log view to view and add new log for this vehicle, groupby default to only show effective costs
            @return: the costs log view
        """
        self.ensure_one()
        copy_context = dict(self.env.context)
        copy_context.pop('group_by', None)
        res = self.env['ir.actions.act_window'].for_xml_id('fleet', 'fleet_vehicle_costs_action')
        res.update(
            context=dict(copy_context, default_vehicle_id=self.id, search_default_parent_false=True),
            domain=[('vehicle_id', '=', self.id)]
        )
        return res

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'driver_id' in init_values:
            return 'fleet.mt_fleet_driver_updated'
        return super(FleetVehicle, self)._track_subtype(init_values)

    def open_assignation_logs(self):
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window',
            'name': 'Assignation Logs',
            'view_mode': 'tree',
            'res_model': 'fleet.vehicle.assignation.log',
            'domain': [('vehicle_id', '=', self.id)],
            'context': {'default_driver_id': self.driver_id.id, 'default_vehicle_id': self.id}
        }
Ejemplo n.º 17
0
class FetchmailServer(models.Model):
    """Incoming POP/IMAP mail server account"""

    _name = 'fetchmail.server'
    _description = 'Incoming Mail Server'
    _order = 'priority'

    name = fields.Char('Name', required=True)
    active = fields.Boolean('Active', default=True)
    state = fields.Selection([
        ('draft', 'Not Confirmed'),
        ('done', 'Confirmed'),
    ],
                             string='Status',
                             index=True,
                             readonly=True,
                             copy=False,
                             default='draft')
    server = fields.Char(string='Server Name',
                         readonly=True,
                         help="Hostname or IP of the mail server",
                         states={'draft': [('readonly', False)]})
    port = fields.Integer(readonly=True,
                          states={'draft': [('readonly', False)]})
    server_type = fields.Selection([
        ('pop', 'POP Server'),
        ('imap', 'IMAP Server'),
        ('local', 'Local Server'),
    ],
                                   string='Server Type',
                                   index=True,
                                   required=True,
                                   default='pop')
    is_ssl = fields.Boolean(
        'SSL/TLS',
        help=
        "Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)"
    )
    attach = fields.Boolean(
        'Keep Attachments',
        help="Whether attachments should be downloaded. "
        "If not enabled, incoming emails will be stripped of any attachments before being processed",
        default=True)
    original = fields.Boolean(
        'Keep Original',
        help=
        "Whether a full original copy of each email should be kept for reference "
        "and attached to each processed message. This will usually double the size of your message database."
    )
    date = fields.Datetime(string='Last Fetch Date', readonly=True)
    user = fields.Char(string='Username',
                       readonly=True,
                       states={'draft': [('readonly', False)]})
    password = fields.Char(readonly=True,
                           states={'draft': [('readonly', False)]})
    object_id = fields.Many2one(
        'ir.model',
        string="Create a New Record",
        help="Process each incoming mail as part of a conversation "
        "corresponding to this document type. This will create "
        "new documents for new conversations, or attach follow-up "
        "emails to the existing conversations (documents).")
    priority = fields.Integer(
        string='Server Priority',
        readonly=True,
        states={'draft': [('readonly', False)]},
        help=
        "Defines the order of processing, lower values mean higher priority",
        default=5)
    message_ids = fields.One2many('mail.mail',
                                  'fetchmail_server_id',
                                  string='Messages',
                                  readonly=True)
    configuration = fields.Text('Configuration', readonly=True)
    script = fields.Char(readonly=True,
                         default='/mail/static/scripts/eagle-mailgate.py')

    @api.onchange('server_type', 'is_ssl', 'object_id')
    def onchange_server_type(self):
        self.port = 0
        if self.server_type == 'pop':
            self.port = self.is_ssl and 995 or 110
        elif self.server_type == 'imap':
            self.port = self.is_ssl and 993 or 143
        else:
            self.server = ''

        conf = {
            'dbname': self.env.cr.dbname,
            'uid': self.env.uid,
            'model': self.object_id.model if self.object_id else 'MODELNAME'
        }
        self.configuration = """Use the below script with the following command line options with your Mail Transport Agent (MTA)
eagle-mailgate.py --host=HOSTNAME --port=PORT -u %(uid)d -p PASSWORD -d %(dbname)s
Example configuration for the postfix mta running locally:
/etc/postfix/virtual_aliases: @youdomain eagle_mailgate@localhost
/etc/aliases:
eagle_mailgate: "|/path/to/eagle-mailgate.py --host=localhost -u %(uid)d -p PASSWORD -d %(dbname)s"
        """ % conf

    @api.model
    def create(self, values):
        res = super(FetchmailServer, self).create(values)
        self._update_cron()
        return res

    def write(self, values):
        res = super(FetchmailServer, self).write(values)
        self._update_cron()
        return res

    def unlink(self):
        res = super(FetchmailServer, self).unlink()
        self._update_cron()
        return res

    def set_draft(self):
        self.write({'state': 'draft'})
        return True

    def connect(self):
        self.ensure_one()
        if self.server_type == 'imap':
            if self.is_ssl:
                connection = IMAP4_SSL(self.server, int(self.port))
            else:
                connection = IMAP4(self.server, int(self.port))
            connection.login(self.user, self.password)
        elif self.server_type == 'pop':
            if self.is_ssl:
                connection = POP3_SSL(self.server, int(self.port))
            else:
                connection = POP3(self.server, int(self.port))
            #TODO: use this to remove only unread messages
            #connection.user("recent:"+server.user)
            connection.user(self.user)
            connection.pass_(self.password)
        # Add timeout on socket
        connection.sock.settimeout(MAIL_TIMEOUT)
        return connection

    def button_confirm_login(self):
        for server in self:
            try:
                connection = server.connect()
                server.write({'state': 'done'})
            except Exception as err:
                _logger.info("Failed to connect to %s server %s.",
                             server.server_type,
                             server.name,
                             exc_info=True)
                raise UserError(
                    _("Connection test failed: %s") % tools.ustr(err))
            finally:
                try:
                    if connection:
                        if server.server_type == 'imap':
                            connection.close()
                        elif server.server_type == 'pop':
                            connection.quit()
                except Exception:
                    # ignored, just a consequence of the previous exception
                    pass
        return True

    @api.model
    def _fetch_mails(self):
        """ Method called by cron to fetch mails from servers """
        return self.search([('state', '=', 'done'),
                            ('server_type', 'in', ['pop',
                                                   'imap'])]).fetch_mail()

    def fetch_mail(self):
        """ WARNING: meant for cron usage only - will commit() after each email! """
        additionnal_context = {'fetchmail_cron_running': True}
        MailThread = self.env['mail.thread']
        for server in self:
            _logger.info('start checking for new emails on %s server %s',
                         server.server_type, server.name)
            additionnal_context['default_fetchmail_server_id'] = server.id
            additionnal_context['server_type'] = server.server_type
            count, failed = 0, 0
            imap_server = None
            pop_server = None
            if server.server_type == 'imap':
                try:
                    imap_server = server.connect()
                    imap_server.select()
                    result, data = imap_server.search(None, '(UNSEEN)')
                    for num in data[0].split():
                        res_id = None
                        result, data = imap_server.fetch(num, '(RFC822)')
                        imap_server.store(num, '-FLAGS', '\\Seen')
                        try:
                            res_id = MailThread.with_context(
                                **additionnal_context).message_process(
                                    server.object_id.model,
                                    data[0][1],
                                    save_original=server.original,
                                    strip_attachments=(not server.attach))
                        except Exception:
                            _logger.info(
                                'Failed to process mail from %s server %s.',
                                server.server_type,
                                server.name,
                                exc_info=True)
                            failed += 1
                        imap_server.store(num, '+FLAGS', '\\Seen')
                        self._cr.commit()
                        count += 1
                    _logger.info(
                        "Fetched %d email(s) on %s server %s; %d succeeded, %d failed.",
                        count, server.server_type, server.name,
                        (count - failed), failed)
                except Exception:
                    _logger.info(
                        "General failure when trying to fetch mail from %s server %s.",
                        server.server_type,
                        server.name,
                        exc_info=True)
                finally:
                    if imap_server:
                        imap_server.close()
                        imap_server.logout()
            elif server.server_type == 'pop':
                try:
                    while True:
                        pop_server = server.connect()
                        (num_messages, total_size) = pop_server.stat()
                        pop_server.list()
                        for num in range(
                                1,
                                min(MAX_POP_MESSAGES, num_messages) + 1):
                            (header, messages, octets) = pop_server.retr(num)
                            message = (b'\n').join(messages)
                            res_id = None
                            try:
                                res_id = MailThread.with_context(
                                    **additionnal_context).message_process(
                                        server.object_id.model,
                                        message,
                                        save_original=server.original,
                                        strip_attachments=(not server.attach))
                                pop_server.dele(num)
                            except Exception:
                                _logger.info(
                                    'Failed to process mail from %s server %s.',
                                    server.server_type,
                                    server.name,
                                    exc_info=True)
                                failed += 1
                            self.env.cr.commit()
                        if num_messages < MAX_POP_MESSAGES:
                            break
                        pop_server.quit()
                        _logger.info(
                            "Fetched %d email(s) on %s server %s; %d succeeded, %d failed.",
                            num_messages, server.server_type, server.name,
                            (num_messages - failed), failed)
                except Exception:
                    _logger.info(
                        "General failure when trying to fetch mail from %s server %s.",
                        server.server_type,
                        server.name,
                        exc_info=True)
                finally:
                    if pop_server:
                        pop_server.quit()
            server.write({'date': fields.Datetime.now()})
        return True

    @api.model
    def _update_cron(self):
        if self.env.context.get('fetchmail_cron_running'):
            return
        try:
            # Enabled/Disable cron based on the number of 'done' server of type pop or imap
            cron = self.env.ref('fetchmail.ir_cron_mail_gateway_action')
            cron.toggle(model=self._name,
                        domain=[('state', '=', 'done'),
                                ('server_type', 'in', ['pop', 'imap'])])
        except ValueError:
            pass
Ejemplo n.º 18
0
class ResPartner(models.Model):
    """Adds last name and first name; name becomes a stored function field."""
    _inherit = 'res.partner'

    firstname = fields.Char(
        "First name",
        index=True,
    )
    lastname = fields.Char(
        "Last name",
        index=True,
    )
    name = fields.Char(
        compute="_compute_name",
        inverse="_inverse_name_after_cleaning_whitespace",
        required=False,
        store=True)

    @api.model
    def create(self, vals):
        """Add inverted names at creation if unavailable."""
        context = dict(self.env.context)
        name = vals.get("name", context.get("default_name"))

        if name is not None:
            # Calculate the splitted fields
            inverted = self._get_inverse_name(
                self._get_whitespace_cleaned_name(name),
                vals.get("is_company",
                         self.default_get(["is_company"])["is_company"]))
            for key, value in inverted.items():
                if not vals.get(key) or context.get("copy"):
                    vals[key] = value

            # Remove the combined fields
            if "name" in vals:
                del vals["name"]
            if "default_name" in context:
                del context["default_name"]

        return super(ResPartner, self.with_context(context)).create(vals)

    @api.multi
    def copy(self, default=None):
        """Ensure partners are copied right.

        Eagle adds ``(copy)`` to the end of :attr:`~.name`, but that would get
        ignored in :meth:`~.create` because it also copies explicitly firstname
        and lastname fields.
        """
        return super(ResPartner, self.with_context(copy=True)).copy(default)

    @api.model
    def default_get(self, fields_list):
        """Invert name when getting default values."""
        result = super(ResPartner, self).default_get(fields_list)

        inverted = self._get_inverse_name(
            self._get_whitespace_cleaned_name(result.get("name", "")),
            result.get("is_company", False))

        for field in list(inverted.keys()):
            if field in fields_list:
                result[field] = inverted.get(field)

        return result

    @api.model
    def _names_order_default(self):
        return 'first_last'

    @api.model
    def _get_names_order(self):
        """Get names order configuration from system parameters.
        You can override this method to read configuration from language,
        country, company or other"""
        return self.env['ir.config_parameter'].sudo().get_param(
            'partner_names_order', self._names_order_default())

    @api.model
    def _get_computed_name(self, lastname, firstname):
        """Compute the 'name' field according to splitted data.
        You can override this method to change the order of lastname and
        firstname the computed name"""
        order = self._get_names_order()
        if order == 'last_first_comma':
            return ", ".join((p for p in (lastname, firstname) if p))
        elif order == 'first_last':
            return " ".join((p for p in (firstname, lastname) if p))
        else:
            return " ".join((p for p in (lastname, firstname) if p))

    @api.multi
    @api.depends("firstname", "lastname")
    def _compute_name(self):
        """Write the 'name' field according to splitted data."""
        for record in self:
            record.name = record._get_computed_name(
                record.lastname, record.firstname,
            )

    @api.multi
    def _inverse_name_after_cleaning_whitespace(self):
        """Clean whitespace in :attr:`~.name` and split it.

        The splitting logic is stored separately in :meth:`~._inverse_name`, so
        submodules can extend that method and get whitespace cleaning for free.
        """
        for record in self:
            # Remove unneeded whitespace
            clean = record._get_whitespace_cleaned_name(record.name)

            # Clean name avoiding infinite recursion
            if record.name != clean:
                record.name = clean

            # Save name in the real fields
            else:
                record._inverse_name()

    @api.model
    def _get_whitespace_cleaned_name(self, name, comma=False):
        """Remove redundant whitespace from :param:`name`.

        Removes leading, trailing and duplicated whitespace.
        """
        try:
            name = " ".join(name.split()) if name else name
        except UnicodeDecodeError:
            # with users coming from LDAP, name can be a str encoded as utf-8
            # this happens with ActiveDirectory for instance, and in that case
            # we get a UnicodeDecodeError during the automatic ASCII -> Unicode
            # conversion that Python does for us.
            # In that case we need to manually decode the string to get a
            # proper unicode string.
            name = ' '.join(name.decode('utf-8').split()) if name else name

        if comma:
            name = name.replace(" ,", ",")
            name = name.replace(", ", ",")
        return name

    @api.model
    def _get_inverse_name(self, name, is_company=False):
        """Compute the inverted name.

        - If the partner is a company, save it in the lastname.
        - Otherwise, make a guess.

        This method can be easily overriden by other submodules.
        You can also override this method to change the order of name's
        attributes

        When this method is called, :attr:`~.name` already has unified and
        trimmed whitespace.
        """
        # Company name goes to the lastname
        if is_company or not name:
            parts = [name or False, False]
        # Guess name splitting
        else:
            order = self._get_names_order()
            # Remove redundant spaces
            name = self._get_whitespace_cleaned_name(
                name, comma=(order == 'last_first_comma'))
            parts = name.split("," if order == 'last_first_comma' else " ", 1)
            if len(parts) > 1:
                if order == 'first_last':
                    parts = [" ".join(parts[1:]), parts[0]]
                else:
                    parts = [parts[0], " ".join(parts[1:])]
            else:
                while len(parts) < 2:
                    parts.append(False)
        return {"lastname": parts[0], "firstname": parts[1]}

    @api.multi
    def _inverse_name(self):
        """Try to revert the effect of :meth:`._compute_name`."""
        for record in self:
            parts = record._get_inverse_name(record.name, record.is_company)
            record.lastname = parts['lastname']
            record.firstname = parts['firstname']

    @api.multi
    @api.constrains("firstname", "lastname")
    def _check_name(self):
        """Ensure at least one name is set."""
        for record in self:
            if all((
                record.type == 'contact' or record.is_company,
                not (record.firstname or record.lastname)
            )):
                raise exceptions.EmptyNamesError(record)

    @api.onchange("firstname", "lastname")
    def _onchange_subnames(self):
        """Avoid recursion when the user changes one of these fields.

        This forces to skip the :attr:`~.name` inversion when the user is
        setting it in a not-inverted way.
        """
        # Modify self's context without creating a new Environment.
        # See https://github.com/eagle/eagle/issues/7472#issuecomment-119503916.
        self.env.context = self.with_context(skip_onchange=True).env.context

    @api.onchange("name")
    def _onchange_name(self):
        """Ensure :attr:`~.name` is inverted in the UI."""
        if self.env.context.get("skip_onchange"):
            # Do not skip next onchange
            self.env.context = (
                self.with_context(skip_onchange=False).env.context)
        else:
            self._inverse_name_after_cleaning_whitespace()

    @api.model
    def _install_partner_firstname(self):
        """Save names correctly in the database.

        Before installing the module, field ``name`` contains all full names.
        When installing it, this method parses those names and saves them
        correctly into the database. This can be called later too if needed.
        """
        # Find records with empty firstname and lastname
        records = self.search([("firstname", "=", False),
                               ("lastname", "=", False)])

        # Force calculations there
        records._inverse_name()
        _logger.info("%d partners updated installing module.", len(records))

    # Disabling SQL constraint givint a more explicit error using a Python
    # contstraint
    _sql_constraints = [(
        'check_name',
        "CHECK( 1=1 )",
        'Contacts require a name.'
    )]
Ejemplo n.º 19
0
class CrmLead(models.Model):
    _inherit = 'crm.lead'

    future_student_ids = fields.One2many(
        comodel_name='crm.lead.future.student',
        inverse_name='crm_lead_id',
        string='Future students')
    educational_category = fields.Selection(
        string='Educational category',
        related='partner_id.educational_category')
    family_ids = fields.One2many(comodel_name='res.partner.family',
                                 inverse_name='family_id',
                                 string='Families',
                                 related='partner_id.family_ids')
    name = fields.Char(required=False, related='partner_id.name', store=True)
    allowed_student_ids = fields.Many2many(
        comodel_name='res.partner', compute='_compute_allowed_student_ids')
    payer_ids = fields.One2many(comodel_name='res.partner.family',
                                inverse_name='crm_lead_id',
                                string='Payers')

    @api.depends('future_student_ids', 'future_student_ids.child_id')
    def _compute_allowed_student_ids(self):
        for lead in self.filtered(lambda c: c.future_student_ids):
            students = lead.mapped('future_student_ids.child_id')
            if students:
                lead.allowed_student_ids = [(6, 0, students.ids)]

    @api.model
    def create(self, values):
        if (values.get('type') == 'opportunity' or
            ('type' not in values
             and self.env.context.get('default_type') == 'opportunity')):
            raise ValidationError(
                _('You aren\'t allowed to create opportunities, you must '
                  'start from lead'))
        return super(CrmLead, self).create(values)

    @api.multi
    def _create_lead_partner_data(self, name, is_company, parent_id=False):
        partner_dict = super(CrmLead, self)._create_lead_partner_data(
            name, is_company, parent_id=parent_id)
        partner_dict.update({
            'educational_category':
            'family' if is_company else 'progenitor',
        })
        return partner_dict

    @api.multi
    def convert_opportunity(self, partner_id, user_ids=False, team_id=False):
        partner_model = self.env['res.partner']
        family_model = self.env['res.partner.family']
        res = super(CrmLead, self).convert_opportunity(partner_id,
                                                       user_ids=user_ids,
                                                       team_id=team_id)
        for lead in self:
            for future_student in lead.future_student_ids.filtered(
                    lambda c: not c.child_id):
                new_student = partner_model.create(
                    self.catch_new_student_vals(future_student))
                future_student.child_id = new_student
                family_model.create({
                    'crm_lead_id': lead.id,
                    'child2_id': new_student.id,
                    'responsible_id': partner_id,
                    'family_id': lead.partner_id.id,
                    'relation': 'progenitor',
                })
        return res

    def catch_new_student_vals(self, future_student):
        partner_dict = self._create_lead_partner_data(
            future_student.name, False, parent_id=self.partner_id.id)
        partner_dict.update({
            'birthdate_date': future_student.birth_date,
            'gender': future_student.gender,
            'educational_category': 'other',
        })
        return partner_dict

    @api.multi
    def _convert_opportunity_data(self, customer, team_id=False):
        res = super(CrmLead, self)._convert_opportunity_data(customer,
                                                             team_id=team_id)
        if customer and customer.parent_id:
            res['partner_id'] = customer.parent_id.id
        return res
Ejemplo n.º 20
0
class AcquirerBuckaroo(models.Model):
    _inherit = 'payment.acquirer'

    provider = fields.Selection(selection_add=[('buckaroo', 'Buckaroo')])
    brq_websitekey = fields.Char('WebsiteKey',
                                 required_if_provider='buckaroo',
                                 groups='base.group_user')
    brq_secretkey = fields.Char('SecretKey',
                                required_if_provider='buckaroo',
                                groups='base.group_user')

    def _get_buckaroo_urls(self, environment):
        """ Buckaroo URLs
        """
        if environment == 'prod':
            return {
                'buckaroo_form_url': 'https://checkout.buckaroo.nl/html/',
            }
        else:
            return {
                'buckaroo_form_url': 'https://testcheckout.buckaroo.nl/html/',
            }

    def _buckaroo_generate_digital_sign(self, inout, values):
        """ Generate the shasign for incoming or outgoing communications.

        :param browse acquirer: the payment.acquirer browse record. It should
                                have a shakey in shaky out
        :param string inout: 'in' (eagle contacting buckaroo) or 'out' (buckaroo
                             contacting eagle).
        :param dict values: transaction values

        :return string: shasign
        """
        assert inout in ('in', 'out')
        assert self.provider == 'buckaroo'

        keys = "add_returndata Brq_amount Brq_culture Brq_currency Brq_invoicenumber Brq_return Brq_returncancel Brq_returnerror Brq_returnreject brq_test Brq_websitekey".split(
        )

        def get_value(key):
            if values.get(key):
                return values[key]
            return ''

        values = dict(values or {})

        if inout == 'out':
            for key in list(values):
                # case insensitive keys
                if key.upper() == 'BRQ_SIGNATURE':
                    del values[key]
                    break

            items = sorted(values.items(), key=lambda pair: pair[0].lower())
            sign = ''.join('%s=%s' % (k, urls.url_unquote_plus(v))
                           for k, v in items)
        else:
            sign = ''.join('%s=%s' % (k, get_value(k)) for k in keys)
        # Add the pre-shared secret key at the end of the signature
        sign = sign + self.brq_secretkey
        shasign = sha1(sign.encode('utf-8')).hexdigest()
        return shasign

    def buckaroo_form_generate_values(self, values):
        base_url = self.get_base_url()
        buckaroo_tx_values = dict(values)
        buckaroo_tx_values.update({
            'Brq_websitekey':
            self.brq_websitekey,
            'Brq_amount':
            values['amount'],
            'Brq_currency':
            values['currency'] and values['currency'].name or '',
            'Brq_invoicenumber':
            values['reference'],
            'brq_test':
            True if self.state == 'test' else False,
            'Brq_return':
            urls.url_join(base_url, BuckarooController._return_url),
            'Brq_returncancel':
            urls.url_join(base_url, BuckarooController._cancel_url),
            'Brq_returnerror':
            urls.url_join(base_url, BuckarooController._exception_url),
            'Brq_returnreject':
            urls.url_join(base_url, BuckarooController._reject_url),
            'Brq_culture': (values.get('partner_lang')
                            or 'en_US').replace('_', '-'),
            'add_returndata':
            buckaroo_tx_values.pop('return_url', '') or '',
        })
        buckaroo_tx_values[
            'Brq_signature'] = self._buckaroo_generate_digital_sign(
                'in', buckaroo_tx_values)
        return buckaroo_tx_values

    def buckaroo_get_form_action_url(self):
        self.ensure_one()
        environment = 'prod' if self.state == 'enabled' else 'test'
        return self._get_buckaroo_urls(environment)['buckaroo_form_url']
Ejemplo n.º 21
0
class Slide(models.Model):
    _name = 'slide.slide'
    _inherit = [
        'mail.thread', 'image.mixin', 'website.seo.metadata',
        'website.published.mixin'
    ]
    _description = 'Slides'
    _mail_post_access = 'read'
    _order_by_strategy = {
        'sequence': 'sequence asc',
        'most_viewed': 'total_views desc',
        'most_voted': 'likes desc',
        'latest': 'date_published desc',
    }
    _order = 'sequence asc, is_category asc'

    # description
    name = fields.Char('Title', required=True, translate=True)
    active = fields.Boolean(default=True)
    sequence = fields.Integer('Sequence', default=0)
    user_id = fields.Many2one('res.users',
                              string='Uploaded by',
                              default=lambda self: self.env.uid)
    description = fields.Text('Description', translate=True)
    channel_id = fields.Many2one('slide.channel',
                                 string="Course",
                                 required=True)
    tag_ids = fields.Many2many('slide.tag',
                               'rel_slide_tag',
                               'slide_id',
                               'tag_id',
                               string='Tags')
    is_preview = fields.Boolean(
        'Allow Preview',
        default=False,
        help=
        "The course is accessible by anyone : the users don't need to join the channel to access the content of the course."
    )
    completion_time = fields.Float(
        'Duration',
        digits=(10, 4),
        help="The estimated completion time for this slide")
    # Categories
    is_category = fields.Boolean('Is a category', default=False)
    category_id = fields.Many2one('slide.slide',
                                  string="Section",
                                  compute="_compute_category_id",
                                  store=True)
    slide_ids = fields.One2many('slide.slide', "category_id", string="Slides")
    # subscribers
    partner_ids = fields.Many2many('res.partner',
                                   'slide_slide_partner',
                                   'slide_id',
                                   'partner_id',
                                   string='Subscribers',
                                   groups='website.group_website_publisher',
                                   copy=False)
    slide_partner_ids = fields.One2many(
        'slide.slide.partner',
        'slide_id',
        string='Subscribers information',
        groups='website.group_website_publisher',
        copy=False)
    user_membership_id = fields.Many2one(
        'slide.slide.partner',
        string="Subscriber information",
        compute='_compute_user_membership_id',
        compute_sudo=False,
        help="Subscriber information for the current logged in user")
    # Quiz related fields
    question_ids = fields.One2many("slide.question",
                                   "slide_id",
                                   string="Questions")
    questions_count = fields.Integer(string="Numbers of Questions",
                                     compute='_compute_questions_count')
    quiz_first_attempt_reward = fields.Integer("First attempt reward",
                                               default=10)
    quiz_second_attempt_reward = fields.Integer("Second attempt reward",
                                                default=7)
    quiz_third_attempt_reward = fields.Integer(
        "Third attempt reward",
        default=5,
    )
    quiz_fourth_attempt_reward = fields.Integer(
        "Reward for every attempt after the third try", default=2)
    # content
    slide_type = fields.Selection(
        [('infographic', 'Infographic'), ('webpage', 'Web Page'),
         ('presentation', 'Presentation'), ('document', 'Document'),
         ('video', 'Video'), ('quiz', "Quiz")],
        string='Type',
        required=True,
        default='document',
        help=
        "The document type will be set automatically based on the document URL and properties (e.g. height and width for presentation and document)."
    )
    datas = fields.Binary('Content', attachment=True)
    url = fields.Char('Document URL', help="Youtube or Google Document URL")
    document_id = fields.Char('Document ID',
                              help="Youtube or Google Document ID")
    link_ids = fields.One2many('slide.slide.link',
                               'slide_id',
                               string="External URL for this slide")
    mime_type = fields.Char('Mime-type')
    html_content = fields.Html(
        "HTML Content",
        help="Custom HTML content for slides of type 'Web Page'.",
        translate=True)
    # website
    website_id = fields.Many2one(related='channel_id.website_id',
                                 readonly=True)
    date_published = fields.Datetime('Publish Date',
                                     readonly=True,
                                     tracking=True)
    likes = fields.Integer('Likes',
                           compute='_compute_user_info',
                           store=True,
                           compute_sudo=False)
    dislikes = fields.Integer('Dislikes',
                              compute='_compute_user_info',
                              store=True,
                              compute_sudo=False)
    user_vote = fields.Integer('User vote',
                               compute='_compute_user_info',
                               compute_sudo=False)
    embed_code = fields.Text('Embed Code',
                             readonly=True,
                             compute='_compute_embed_code')
    # views
    embedcount_ids = fields.One2many('slide.embed',
                                     'slide_id',
                                     string="Embed Count")
    slide_views = fields.Integer('# of Website Views',
                                 store=True,
                                 compute="_compute_slide_views")
    public_views = fields.Integer('# of Public Views')
    total_views = fields.Integer("Views",
                                 default="0",
                                 compute='_compute_total',
                                 store=True)
    # comments
    comments_count = fields.Integer('Number of comments',
                                    compute="_compute_comments_count")
    # channel
    channel_type = fields.Selection(related="channel_id.channel_type",
                                    string="Channel type")
    channel_allow_comment = fields.Boolean(related="channel_id.allow_comment",
                                           string="Allows comment")
    # Statistics in case the slide is a category
    nbr_presentation = fields.Integer("Number of Presentations",
                                      compute='_compute_slides_statistics',
                                      store=True)
    nbr_document = fields.Integer("Number of Documents",
                                  compute='_compute_slides_statistics',
                                  store=True)
    nbr_video = fields.Integer("Number of Videos",
                               compute='_compute_slides_statistics',
                               store=True)
    nbr_infographic = fields.Integer("Number of Infographics",
                                     compute='_compute_slides_statistics',
                                     store=True)
    nbr_webpage = fields.Integer("Number of Webpages",
                                 compute='_compute_slides_statistics',
                                 store=True)
    nbr_quiz = fields.Integer("Number of Quizs",
                              compute="_compute_slides_statistics",
                              store=True)
    total_slides = fields.Integer(compute='_compute_slides_statistics',
                                  store=True)

    _sql_constraints = [(
        'exclusion_html_content_and_url',
        "CHECK(html_content IS NULL OR url IS NULL)",
        "A slide is either filled with a document url or HTML content. Not both."
    )]

    @api.depends('channel_id.slide_ids.is_category',
                 'channel_id.slide_ids.sequence')
    def _compute_category_id(self):
        """ Will take all the slides of the channel for which the index is higher
        than the index of this category and lower than the index of the next category.

        Lists are manually sorted because when adding a new browse record order
        will not be correct as the added slide would actually end up at the
        first place no matter its sequence."""
        self.category_id = False  # initialize whatever the state

        channel_slides = {}
        for slide in self:
            if slide.channel_id.id not in channel_slides:
                channel_slides[
                    slide.channel_id.id] = slide.channel_id.slide_ids

        for cid, slides in channel_slides.items():
            current_category = self.env['slide.slide']
            slide_list = list(slides)
            slide_list.sort(key=lambda s: (s.sequence, not s.is_category))
            for slide in slide_list:
                if slide.is_category:
                    current_category = slide
                elif slide.category_id != current_category:
                    slide.category_id = current_category.id

    @api.depends('question_ids')
    def _compute_questions_count(self):
        for slide in self:
            slide.questions_count = len(slide.question_ids)

    @api.depends('website_message_ids.res_id', 'website_message_ids.model',
                 'website_message_ids.message_type')
    def _compute_comments_count(self):
        for slide in self:
            slide.comments_count = len(slide.website_message_ids)

    @api.depends('slide_views', 'public_views')
    def _compute_total(self):
        for record in self:
            record.total_views = record.slide_views + record.public_views

    @api.depends('slide_partner_ids.vote')
    @api.depends_context('uid')
    def _compute_user_info(self):
        slide_data = dict.fromkeys(
            self.ids, dict({
                'likes': 0,
                'dislikes': 0,
                'user_vote': False
            }))
        slide_partners = self.env['slide.slide.partner'].sudo().search([
            ('slide_id', 'in', self.ids)
        ])
        for slide_partner in slide_partners:
            if slide_partner.vote == 1:
                slide_data[slide_partner.slide_id.id]['likes'] += 1
                if slide_partner.partner_id == self.env.user.partner_id:
                    slide_data[slide_partner.slide_id.id]['user_vote'] = 1
            elif slide_partner.vote == -1:
                slide_data[slide_partner.slide_id.id]['dislikes'] += 1
                if slide_partner.partner_id == self.env.user.partner_id:
                    slide_data[slide_partner.slide_id.id]['user_vote'] = -1
        for slide in self:
            slide.update(slide_data[slide.id])

    @api.depends('slide_partner_ids.slide_id')
    def _compute_slide_views(self):
        # TODO awa: tried compute_sudo, for some reason it doesn't work in here...
        read_group_res = self.env['slide.slide.partner'].sudo().read_group(
            [('slide_id', 'in', self.ids)], ['slide_id'], groupby=['slide_id'])
        mapped_data = dict((res['slide_id'][0], res['slide_id_count'])
                           for res in read_group_res)
        for slide in self:
            slide.slide_views = mapped_data.get(slide.id, 0)

    @api.depends('slide_ids.sequence', 'slide_ids.slide_type',
                 'slide_ids.is_published', 'slide_ids.is_category')
    def _compute_slides_statistics(self):
        # Do not use dict.fromkeys(self.ids, dict()) otherwise it will use the same dictionnary for all keys.
        # Therefore, when updating the dict of one key, it updates the dict of all keys.
        keys = [
            'nbr_%s' % slide_type for slide_type in
            self.env['slide.slide']._fields['slide_type'].get_values(self.env)
        ]
        default_vals = dict((key, 0) for key in keys + ['total_slides'])

        res = self.env['slide.slide'].read_group(
            [('is_published', '=', True), ('category_id', 'in', self.ids),
             ('is_category', '=', False)], ['category_id', 'slide_type'],
            ['category_id', 'slide_type'],
            lazy=False)

        type_stats = self._compute_slides_statistics_type(res)

        for record in self:
            record.update(type_stats.get(record._origin.id, default_vals))

    def _compute_slides_statistics_type(self, read_group_res):
        """ Compute statistics based on all existing slide types """
        slide_types = self.env['slide.slide']._fields['slide_type'].get_values(
            self.env)
        keys = ['nbr_%s' % slide_type for slide_type in slide_types]
        result = dict((cid, dict((key, 0) for key in keys + ['total_slides']))
                      for cid in self.ids)
        for res_group in read_group_res:
            cid = res_group['category_id'][0]
            slide_type = res_group.get('slide_type')
            if slide_type:
                slide_type_count = res_group.get('__count', 0)
                result[cid]['nbr_%s' % slide_type] = slide_type_count
                result[cid]['total_slides'] += slide_type_count
        return result

    @api.depends('slide_partner_ids.partner_id')
    @api.depends('uid')
    def _compute_user_membership_id(self):
        slide_partners = self.env['slide.slide.partner'].sudo().search([
            ('slide_id', 'in', self.ids),
            ('partner_id', '=', self.env.user.partner_id.id),
        ])

        for record in self:
            record.user_membership_id = next(
                (slide_partner for slide_partner in slide_partners
                 if slide_partner.slide_id == record),
                self.env['slide.slide.partner'])

    @api.depends('document_id', 'slide_type', 'mime_type')
    def _compute_embed_code(self):
        base_url = request and request.httprequest.url_root or self.env[
            'ir.config_parameter'].sudo().get_param('web.base.url')
        if base_url[-1] == '/':
            base_url = base_url[:-1]
        for record in self:
            if record.datas and (not record.document_id or record.slide_type
                                 in ['document', 'presentation']):
                slide_url = base_url + url_for(
                    '/slides/embed/%s?page=1' % record.id)
                record.embed_code = '<iframe src="%s" class="o_wslides_iframe_viewer" allowFullScreen="true" height="%s" width="%s" frameborder="0"></iframe>' % (
                    slide_url, 315, 420)
            elif record.slide_type == 'video' and record.document_id:
                if not record.mime_type:
                    # embed youtube video
                    record.embed_code = '<iframe src="//www.youtube.com/embed/%s?theme=light" allowFullScreen="true" frameborder="0"></iframe>' % (
                        record.document_id)
                else:
                    # embed google doc video
                    record.embed_code = '<iframe src="//drive.google.com/file/d/%s/preview" allowFullScreen="true" frameborder="0"></iframe>' % (
                        record.document_id)
            else:
                record.embed_code = False

    @api.onchange('url')
    def _on_change_url(self):
        self.ensure_one()
        if self.url:
            res = self._parse_document_url(self.url)
            if res.get('error'):
                raise Warning(
                    _('Could not fetch data from url. Document or access right not available:\n%s'
                      ) % res['error'])
            values = res['values']
            if not values.get('document_id'):
                raise Warning(
                    _('Please enter valid Youtube or Google Doc URL'))
            for key, value in values.items():
                self[key] = value

    @api.onchange('datas')
    def _on_change_datas(self):
        """ For PDFs, we assume that it takes 5 minutes to read a page. """
        if self.datas:
            data = base64.b64decode(self.datas)
            if data.startswith(b'%PDF-'):
                pdf = PyPDF2.PdfFileReader(io.BytesIO(data),
                                           overwriteWarnings=False)
                self.completion_time = (5 * len(pdf.pages)) / 60

    @api.depends('name', 'channel_id.website_id.domain')
    def _compute_website_url(self):
        # TDE FIXME: clena this link.tracker strange stuff
        super(Slide, self)._compute_website_url()
        for slide in self:
            if slide.id:  # avoid to perform a slug on a not yet saved record in case of an onchange.
                base_url = slide.channel_id.get_base_url()
                # link_tracker is not in dependencies, so use it to shorten url only if installed.
                if self.env.registry.get('link.tracker'):
                    url = self.env['link.tracker'].sudo().create({
                        'url':
                        '%s/slides/slide/%s' % (base_url, slug(slide)),
                        'title':
                        slide.name,
                    }).short_url
                else:
                    url = '%s/slides/slide/%s' % (base_url, slug(slide))
                slide.website_url = url

    def get_backend_menu_id(self):
        return self.env.ref('website_slides.website_slides_menu_root').id

    @api.depends('channel_id.can_publish')
    def _compute_can_publish(self):
        for record in self:
            record.can_publish = record.channel_id.can_publish

    @api.model
    def _get_can_publish_error_message(self):
        return _(
            "Publishing is restricted to the responsible of training courses or members of the publisher group for documentation courses"
        )

    # ---------------------------------------------------------
    # ORM Overrides
    # ---------------------------------------------------------

    @api.model
    def create(self, values):
        # Do not publish slide if user has not publisher rights
        channel = self.env['slide.channel'].browse(values['channel_id'])
        if not channel.can_publish:
            # 'website_published' is handled by mixin
            values['date_published'] = False

        if values.get('slide_type'
                      ) == 'infographic' and not values.get('image_1920'):
            values['image_1920'] = values['datas']
        if values.get('is_category'):
            values['is_preview'] = True
            values['is_published'] = True
        if values.get('is_published') and not values.get('date_published'):
            values['date_published'] = datetime.datetime.now()
        if values.get('url') and not values.get('document_id'):
            doc_data = self._parse_document_url(values['url']).get(
                'values', dict())
            for key, value in doc_data.items():
                values.setdefault(key, value)

        slide = super(Slide, self).create(values)

        if slide.is_published and not slide.is_category:
            slide._post_publication()
        return slide

    def write(self, values):
        if values.get('url') and values['url'] != self.url:
            doc_data = self._parse_document_url(values['url']).get(
                'values', dict())
            for key, value in doc_data.items():
                values.setdefault(key, value)
        if values.get('is_category'):
            values['is_preview'] = True
            values['is_published'] = True

        res = super(Slide, self).write(values)
        if values.get('is_published'):
            self.date_published = datetime.datetime.now()
            self._post_publication()

        if 'is_published' in values or 'active' in values:
            # if the slide is published/unpublished, recompute the completion for the partners
            self.slide_partner_ids._set_completed_callback()

        return res

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        """Sets the sequence to zero so that it always lands at the beginning
        of the newly selected course as an uncategorized slide"""
        rec = super(Slide, self).copy(default)
        rec.sequence = 0
        return rec

    # ---------------------------------------------------------
    # Mail/Rating
    # ---------------------------------------------------------

    @api.returns('mail.message', lambda value: value.id)
    def message_post(self, *, message_type='notification', **kwargs):
        self.ensure_one()
        if message_type == 'comment' and not self.channel_id.can_comment:  # user comments have a restriction on karma
            raise AccessError(_('Not enough karma to comment'))
        return super(Slide, self).message_post(message_type=message_type,
                                               **kwargs)

    def get_access_action(self, access_uid=None):
        """ Instead of the classic form view, redirect to website if it is published. """
        self.ensure_one()
        if self.website_published:
            return {
                'type': 'ir.actions.act_url',
                'url': '%s' % self.website_url,
                'target': 'self',
                'target_type': 'public',
                'res_id': self.id,
            }
        return super(Slide, self).get_access_action(access_uid)

    def _notify_get_groups(self):
        """ Add access button to everyone if the document is active. """
        groups = super(Slide, self)._notify_get_groups()

        if self.website_published:
            for group_name, group_method, group_data in groups:
                group_data['has_button_access'] = True

        return groups

    # ---------------------------------------------------------
    # Business Methods
    # ---------------------------------------------------------

    def _post_publication(self):
        base_url = self.env['ir.config_parameter'].sudo().get_param(
            'web.base.url')
        for slide in self.filtered(lambda slide: slide.website_published and
                                   slide.channel_id.publish_template_id):
            publish_template = slide.channel_id.publish_template_id
            html_body = publish_template.with_context(
                base_url=base_url)._render_template(publish_template.body_html,
                                                    'slide.slide', slide.id)
            subject = publish_template._render_template(
                publish_template.subject, 'slide.slide', slide.id)
            slide.channel_id.with_context(
                mail_create_nosubscribe=True).message_post(
                    subject=subject,
                    body=html_body,
                    subtype='website_slides.mt_channel_slide_published',
                    email_layout_xmlid='mail.mail_notification_light',
                )
        return True

    def _generate_signed_token(self, partner_id):
        """ Lazy generate the acces_token and return it signed by the given partner_id
            :rtype tuple (string, int)
            :return (signed_token, partner_id)
        """
        if not self.access_token:
            self.write({'access_token': self._default_access_token()})
        return self._sign_token(partner_id)

    def _send_share_email(self, email, fullscreen):
        # TDE FIXME: template to check
        mail_ids = []
        base_url = self.env['ir.config_parameter'].sudo().get_param(
            'web.base.url')
        for record in self:
            if self.env.user.has_group('base.group_portal'):
                mail_ids.append(
                    self.channel_id.share_template_id.with_context(
                        user=self.env.user,
                        email=email,
                        base_url=base_url,
                        fullscreen=fullscreen).sudo().send_mail(
                            record.id,
                            notif_layout='mail.mail_notification_light',
                            email_values={
                                'email_from':
                                self.env['res.company'].catchall
                                or self.env['res.company'].email,
                                'email_to':
                                email
                            }))
            else:
                mail_ids.append(
                    self.channel_id.share_template_id.with_context(
                        user=self.env.user,
                        email=email,
                        base_url=base_url,
                        fullscreen=fullscreen).send_mail(
                            record.id,
                            notif_layout='mail.mail_notification_light',
                            email_values={'email_to': email}))
        return mail_ids

    def action_like(self):
        self.check_access_rights('read')
        self.check_access_rule('read')
        return self._action_vote(upvote=True)

    def action_dislike(self):
        self.check_access_rights('read')
        self.check_access_rule('read')
        return self._action_vote(upvote=False)

    def _action_vote(self, upvote=True):
        """ Private implementation of voting. It does not check for any real access
        rights; public methods should grant access before calling this method.

          :param upvote: if True, is a like; if False, is a dislike
        """
        self_sudo = self.sudo()
        SlidePartnerSudo = self.env['slide.slide.partner'].sudo()
        slide_partners = SlidePartnerSudo.search([
            ('slide_id', 'in', self.ids),
            ('partner_id', '=', self.env.user.partner_id.id)
        ])
        slide_id = slide_partners.mapped('slide_id')
        new_slides = self_sudo - slide_id
        channel = slide_id.channel_id
        karma_to_add = 0

        for slide_partner in slide_partners:
            if upvote:
                new_vote = 0 if slide_partner.vote == -1 else 1
                if slide_partner.vote != 1:
                    karma_to_add += channel.karma_gen_slide_vote
            else:
                new_vote = 0 if slide_partner.vote == 1 else -1
                if slide_partner.vote != -1:
                    karma_to_add -= channel.karma_gen_slide_vote
            slide_partner.vote = new_vote

        for new_slide in new_slides:
            new_vote = 1 if upvote else -1
            new_slide.write({
                'slide_partner_ids': [(0, 0, {
                    'vote':
                    new_vote,
                    'partner_id':
                    self.env.user.partner_id.id
                })]
            })
            karma_to_add += new_slide.channel_id.karma_gen_slide_vote * (
                1 if upvote else -1)

        if karma_to_add:
            self.env.user.add_karma(karma_to_add)

    def action_set_viewed(self, quiz_attempts_inc=False):
        if not all(slide.channel_id.is_member for slide in self):
            raise UserError(
                _('You cannot mark a slide as viewed if you are not among its members.'
                  ))

        return bool(
            self._action_set_viewed(self.env.user.partner_id,
                                    quiz_attempts_inc=quiz_attempts_inc))

    def _action_set_viewed(self, target_partner, quiz_attempts_inc=False):
        self_sudo = self.sudo()
        SlidePartnerSudo = self.env['slide.slide.partner'].sudo()
        existing_sudo = SlidePartnerSudo.search([('slide_id', 'in', self.ids),
                                                 ('partner_id', '=',
                                                  target_partner.id)])
        if quiz_attempts_inc:
            for exsting_slide in existing_sudo:
                exsting_slide.write({
                    'quiz_attempts_count':
                    exsting_slide.quiz_attempts_count + 1
                })

        new_slides = self_sudo - existing_sudo.mapped('slide_id')
        return SlidePartnerSudo.create([{
            'slide_id':
            new_slide.id,
            'channel_id':
            new_slide.channel_id.id,
            'partner_id':
            target_partner.id,
            'quiz_attempts_count':
            1 if quiz_attempts_inc else 0,
            'vote':
            0
        } for new_slide in new_slides])

    def action_set_completed(self):
        if not all(slide.channel_id.is_member for slide in self):
            raise UserError(
                _('You cannot mark a slide as completed if you are not among its members.'
                  ))

        return self._action_set_completed(self.env.user.partner_id)

    def _action_set_completed(self, target_partner):
        self_sudo = self.sudo()
        SlidePartnerSudo = self.env['slide.slide.partner'].sudo()
        existing_sudo = SlidePartnerSudo.search([('slide_id', 'in', self.ids),
                                                 ('partner_id', '=',
                                                  target_partner.id)])
        existing_sudo.write({'completed': True})

        new_slides = self_sudo - existing_sudo.mapped('slide_id')
        SlidePartnerSudo.create([{
            'slide_id': new_slide.id,
            'channel_id': new_slide.channel_id.id,
            'partner_id': target_partner.id,
            'vote': 0,
            'completed': True
        } for new_slide in new_slides])

        return True

    def _action_set_quiz_done(self):
        if not all(slide.channel_id.is_member for slide in self):
            raise UserError(
                _('You cannot mark a slide quiz as completed if you are not among its members.'
                  ))

        points = 0
        for slide in self:
            user_membership_sudo = slide.user_membership_id.sudo()
            if not user_membership_sudo or user_membership_sudo.completed or not user_membership_sudo.quiz_attempts_count:
                continue

            gains = [
                slide.quiz_first_attempt_reward,
                slide.quiz_second_attempt_reward,
                slide.quiz_third_attempt_reward,
                slide.quiz_fourth_attempt_reward
            ]
            points += gains[
                user_membership_sudo.quiz_attempts_count -
                1] if user_membership_sudo.quiz_attempts_count <= len(
                    gains) else gains[-1]

        return self.env.user.sudo().add_karma(points)

    def _compute_quiz_info(self, target_partner, quiz_done=False):
        result = dict.fromkeys(self.ids, False)
        slide_partners = self.env['slide.slide.partner'].sudo().search([
            ('slide_id', 'in', self.ids),
            ('partner_id', '=', target_partner.id)
        ])
        slide_partners_map = dict(
            (sp.slide_id.id, sp) for sp in slide_partners)
        for slide in self:
            if not slide.question_ids:
                gains = [0]
            else:
                gains = [
                    slide.quiz_first_attempt_reward,
                    slide.quiz_second_attempt_reward,
                    slide.quiz_third_attempt_reward,
                    slide.quiz_fourth_attempt_reward
                ]
            result[slide.id] = {
                'quiz_karma_max':
                gains[0],  # what could be gained if succeed at first try
                'quiz_karma_gain':
                gains[0],  # what would be gained at next test
                'quiz_karma_won': 0,  # what has been gained
                'quiz_attempts_count': 0,  # number of attempts
            }
            slide_partner = slide_partners_map.get(slide.id)
            if slide.question_ids and slide_partner:
                if slide_partner.quiz_attempts_count:
                    result[slide.id]['quiz_karma_gain'] = gains[
                        slide_partner.
                        quiz_attempts_count] if slide_partner.quiz_attempts_count < len(
                            gains) else gains[-1]
                    result[slide.id][
                        'quiz_attempts_count'] = slide_partner.quiz_attempts_count
                if quiz_done or slide_partner.completed:
                    result[slide.id]['quiz_karma_won'] = gains[
                        slide_partner.quiz_attempts_count -
                        1] if slide_partner.quiz_attempts_count < len(
                            gains) else gains[-1]
        return result

    # --------------------------------------------------
    # Parsing methods
    # --------------------------------------------------

    @api.model
    def _fetch_data(self, base_url, params, content_type=False):
        result = {'values': dict()}
        try:
            response = requests.get(base_url, timeout=3, params=params)
            response.raise_for_status()
            if content_type == 'json':
                result['values'] = response.json()
            elif content_type in ('image', 'pdf'):
                result['values'] = base64.b64encode(response.content)
            else:
                result['values'] = response.content
        except requests.exceptions.HTTPError as e:
            result['error'] = e.response.content
        except requests.exceptions.ConnectionError as e:
            result['error'] = str(e)
        return result

    def _find_document_data_from_url(self, url):
        url_obj = urls.url_parse(url)
        if url_obj.ascii_host == 'youtu.be':
            return ('youtube', url_obj.path[1:] if url_obj.path else False)
        elif url_obj.ascii_host in ('youtube.com', 'www.youtube.com',
                                    'm.youtube.com'):
            v_query_value = url_obj.decode_query().get('v')
            if v_query_value:
                return ('youtube', v_query_value)
            split_path = url_obj.path.split('/')
            if len(split_path) >= 3 and split_path[1] in ('v', 'embed'):
                return ('youtube', split_path[2])

        expr = re.compile(
            r'(^https:\/\/docs.google.com|^https:\/\/drive.google.com).*\/d\/([^\/]*)'
        )
        arg = expr.match(url)
        document_id = arg and arg.group(2) or False
        if document_id:
            return ('google', document_id)

        return (None, False)

    def _parse_document_url(self, url, only_preview_fields=False):
        document_source, document_id = self._find_document_data_from_url(url)
        if document_source and hasattr(self,
                                       '_parse_%s_document' % document_source):
            return getattr(self, '_parse_%s_document' % document_source)(
                document_id, only_preview_fields)
        return {'error': _('Unknown document')}

    def _parse_youtube_document(self, document_id, only_preview_fields):
        """ If we receive a duration (YT video), we use it to determine the slide duration.
        The received duration is under a special format (e.g: PT1M21S15, meaning 1h 21m 15s). """

        key = self.env['website'].get_current_website(
        ).website_slide_google_app_key
        fetch_res = self._fetch_data(
            'https://www.googleapis.com/youtube/v3/videos', {
                'id': document_id,
                'key': key,
                'part': 'snippet,contentDetails',
                'fields': 'items(id,snippet,contentDetails)'
            }, 'json')
        if fetch_res.get('error'):
            return fetch_res

        values = {'slide_type': 'video', 'document_id': document_id}
        items = fetch_res['values'].get('items')
        if not items:
            return {'error': _('Please enter valid Youtube or Google Doc URL')}
        youtube_values = items[0]

        youtube_duration = youtube_values.get('contentDetails',
                                              {}).get('duration')
        if youtube_duration:
            parsed_duration = re.search(
                r'^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$', youtube_duration)
            values['completion_time'] = (int(parsed_duration.group(1) or 0)) + \
                                        (int(parsed_duration.group(2) or 0) / 60) + \
                                        (int(parsed_duration.group(3) or 0) / 3600)

        if youtube_values.get('snippet'):
            snippet = youtube_values['snippet']
            if only_preview_fields:
                values.update({
                    'url_src': snippet['thumbnails']['high']['url'],
                    'title': snippet['title'],
                    'description': snippet['description']
                })

                return values

            values.update({
                'name':
                snippet['title'],
                'image_1920':
                self._fetch_data(snippet['thumbnails']['high']['url'], {},
                                 'image')['values'],
                'description':
                snippet['description'],
                'mime_type':
                False,
            })
        return {'values': values}

    @api.model
    def _parse_google_document(self, document_id, only_preview_fields):
        def get_slide_type(vals):
            # TDE FIXME: WTF ??
            slide_type = 'presentation'
            if vals.get('image_1920'):
                image = Image.open(
                    io.BytesIO(base64.b64decode(vals['image_1920'])))
                width, height = image.size
                if height > width:
                    return 'document'
            return slide_type

        # Google drive doesn't use a simple API key to access the data, but requires an access
        # token. However, this token is generated in module google_drive, which is not in the
        # dependencies of website_slides. We still keep the 'key' parameter just in case, but that
        # is probably useless.
        params = {}
        params['projection'] = 'BASIC'
        if 'google.drive.config' in self.env:
            access_token = self.env['google.drive.config'].get_access_token()
            if access_token:
                params['access_token'] = access_token
        if not params.get('access_token'):
            params['key'] = self.env['website'].get_current_website(
            ).website_slide_google_app_key

        fetch_res = self._fetch_data(
            'https://www.googleapis.com/drive/v2/files/%s' % document_id,
            params, "json")
        if fetch_res.get('error'):
            return fetch_res

        google_values = fetch_res['values']
        if only_preview_fields:
            return {
                'url_src': google_values['thumbnailLink'],
                'title': google_values['title'],
            }

        values = {
            'name':
            google_values['title'],
            'image_1920':
            self._fetch_data(
                google_values['thumbnailLink'].replace('=s220', ''), {},
                'image')['values'],
            'mime_type':
            google_values['mimeType'],
            'document_id':
            document_id,
        }
        if google_values['mimeType'].startswith('video/'):
            values['slide_type'] = 'video'
        elif google_values['mimeType'].startswith('image/'):
            values['datas'] = values['image_1920']
            values['slide_type'] = 'infographic'
        elif google_values['mimeType'].startswith(
                'application/vnd.google-apps'):
            values['slide_type'] = get_slide_type(values)
            if 'exportLinks' in google_values:
                values['datas'] = self._fetch_data(
                    google_values['exportLinks']['application/pdf'], params,
                    'pdf')['values']
        elif google_values['mimeType'] == 'application/pdf':
            # TODO: Google Drive PDF document doesn't provide plain text transcript
            values['datas'] = self._fetch_data(google_values['webContentLink'],
                                               {}, 'pdf')['values']
            values['slide_type'] = get_slide_type(values)

        return {'values': values}

    def _default_website_meta(self):
        res = super(Slide, self)._default_website_meta()
        res['default_opengraph']['og:title'] = res['default_twitter'][
            'twitter:title'] = self.name
        res['default_opengraph']['og:description'] = res['default_twitter'][
            'twitter:description'] = self.description
        res['default_opengraph']['og:image'] = res['default_twitter'][
            'twitter:image'] = self.env['website'].image_url(
                self, 'image_1024')
        res['default_meta_description'] = self.description
        return res
Ejemplo n.º 22
0
class AccountBankStatementImport(models.TransientModel):
    _name = 'account.bank.statement.import'
    _description = 'Import Bank Statement'

    data_file = fields.Binary(string='Bank Statement File', required=True, help='Get you bank statements in electronic format from your bank and select them here.')
    filename = fields.Char()

    @api.multi
    def import_file(self):
        """ Process the file chosen in the wizard, create bank statement(s) and go to reconciliation. """
        self.ensure_one()
        # Let the appropriate implementation module parse the file and return the required data
        # The active_id is passed in context in case an implementation module requires information about the wizard state (see QIF)
        currency_code, account_number, stmts_vals = self.with_context(active_id=self.ids[0])._parse_file(base64.b64decode(self.data_file))
        # Check raw data
        self._check_parsed_data(stmts_vals)
        # Try to find the currency and journal in eagle
        currency, journal = self._find_additional_data(currency_code, account_number)
        # If no journal found, ask the user about creating one
        if not journal:
            # The active_id is passed in context so the wizard can call import_file again once the journal is created
            return self.with_context(active_id=self.ids[0])._journal_creation_wizard(currency, account_number)
        if not journal.default_debit_account_id or not journal.default_credit_account_id:
            raise UserError(_('You have to set a Default Debit Account and a Default Credit Account for the journal: %s') % (journal.name,))
        # Prepare statement data to be used for bank statements creation
        stmts_vals = self._complete_stmts_vals(stmts_vals, journal, account_number)
        # Create the bank statements
        statement_ids, notifications = self._create_bank_statements(stmts_vals)
        # Now that the import worked out, set it as the bank_statements_source of the journal
        if journal.bank_statements_source != 'file_import':
            # Use sudo() because only 'account.group_account_manager'
            # has write access on 'account.journal', but 'account.group_account_user'
            # must be able to import bank statement files
            journal.sudo().bank_statements_source = 'file_import'
        # Finally dispatch to reconciliation interface
        action = self.env.ref('account.action_bank_reconcile_bank_statements')
        return {
            'name': action.name,
            'tag': action.tag,
            'context': {
                'statement_ids': statement_ids,
                'notifications': notifications
            },
            'type': 'ir.actions.client',
        }

    def _journal_creation_wizard(self, currency, account_number):
        """ Calls a wizard that allows the user to carry on with journal creation """
        return {
            'name': _('Journal Creation'),
            'type': 'ir.actions.act_window',
            'res_model': 'account.bank.statement.import.journal.creation',
            'view_type': 'form',
            'view_mode': 'form',
            'target': 'new',
            'context': {
                'statement_import_transient_id': self.env.context['active_id'],
                'default_bank_acc_number': account_number,
                'default_name': _('Bank') + ' ' + account_number,
                'default_currency_id': currency and currency.id or False,
                'default_type': 'bank',
            }
        }

    def _parse_file(self, data_file):
        """ Each module adding a file support must extends this method. It processes the file if it can, returns super otherwise, resulting in a chain of responsability.
            This method parses the given file and returns the data required by the bank statement import process, as specified below.
            rtype: triplet (if a value can't be retrieved, use None)
                - currency code: string (e.g: 'EUR')
                    The ISO 4217 currency code, case insensitive
                - account number: string (e.g: 'BE1234567890')
                    The number of the bank account which the statement belongs to
                - bank statements data: list of dict containing (optional items marked by o) :
                    - 'name': string (e.g: '000000123')
                    - 'date': date (e.g: 2013-06-26)
                    -o 'balance_start': float (e.g: 8368.56)
                    -o 'balance_end_real': float (e.g: 8888.88)
                    - 'transactions': list of dict containing :
                        - 'name': string (e.g: 'KBC-INVESTERINGSKREDIET 787-5562831-01')
                        - 'date': date
                        - 'amount': float
                        - 'unique_import_id': string
                        -o 'account_number': string
                            Will be used to find/create the res.partner.bank in eagle
                        -o 'note': string
                        -o 'partner_name': string
                        -o 'ref': string
        """
        raise UserError(_('Could not make sense of the given file.\nDid you install the module to support this type of file ?'))

    def _check_parsed_data(self, stmts_vals):
        """ Basic and structural verifications """
        if len(stmts_vals) == 0:
            raise UserError(_('This file doesn\'t contain any statement.'))

        no_st_line = True
        for vals in stmts_vals:
            if vals['transactions'] and len(vals['transactions']) > 0:
                no_st_line = False
                break
        if no_st_line:
            raise UserError(_('This file doesn\'t contain any transaction.'))

    def _check_journal_bank_account(self, journal, account_number):
        return journal.bank_account_id.sanitized_acc_number == account_number

    def _find_additional_data(self, currency_code, account_number):
        """ Look for a res.currency and account.journal using values extracted from the
            statement and make sure it's consistent.
        """
        company_currency = self.env.user.company_id.currency_id
        journal_obj = self.env['account.journal']
        currency = None
        sanitized_account_number = sanitize_account_number(account_number)

        if currency_code:
            currency = self.env['res.currency'].search([('name', '=ilike', currency_code)], limit=1)
            if not currency:
                raise UserError(_("No currency found matching '%s'.") % currency_code)
            if currency == company_currency:
                currency = False

        journal = journal_obj.browse(self.env.context.get('journal_id', []))
        if account_number:
            # No bank account on the journal : create one from the account number of the statement
            if journal and not journal.bank_account_id:
                journal.set_bank_account(account_number)
            # No journal passed to the wizard : try to find one using the account number of the statement
            elif not journal:
                journal = journal_obj.search([('bank_account_id.sanitized_acc_number', '=', sanitized_account_number)])
            # Already a bank account on the journal : check it's the same as on the statement
            else:
                if not self._check_journal_bank_account(journal, sanitized_account_number):
                    raise UserError(_('The account of this statement (%s) is not the same as the journal (%s).') % (account_number, journal.bank_account_id.acc_number))

        # If importing into an existing journal, its currency must be the same as the bank statement
        if journal:
            journal_currency = journal.currency_id
            if currency is None:
                currency = journal_currency
            if currency and currency != journal_currency:
                statement_cur_code = not currency and company_currency.name or currency.name
                journal_cur_code = not journal_currency and company_currency.name or journal_currency.name
                raise UserError(_('The currency of the bank statement (%s) is not the same as the currency of the journal (%s).') % (statement_cur_code, journal_cur_code))

        # If we couldn't find / can't create a journal, everything is lost
        if not journal and not account_number:
            raise UserError(_('Cannot find in which journal import this statement. Please manually select a journal.'))

        return currency, journal

    def _complete_stmts_vals(self, stmts_vals, journal, account_number):
        for st_vals in stmts_vals:
            st_vals['journal_id'] = journal.id
            if not st_vals.get('reference'):
                st_vals['reference'] = self.filename
            if st_vals.get('number'):
                #build the full name like BNK/2016/00135 by just giving the number '135'
                st_vals['name'] = journal.sequence_id.with_context(ir_sequence_date=st_vals.get('date')).get_next_char(st_vals['number'])
                del(st_vals['number'])
            for line_vals in st_vals['transactions']:
                unique_import_id = line_vals.get('unique_import_id')
                if unique_import_id:
                    sanitized_account_number = sanitize_account_number(account_number)
                    line_vals['unique_import_id'] = (sanitized_account_number and sanitized_account_number + '-' or '') + str(journal.id) + '-' + unique_import_id

                if not line_vals.get('bank_account_id'):
                    # Find the partner and his bank account or create the bank account. The partner selected during the
                    # reconciliation process will be linked to the bank when the statement is closed.
                    identifying_string = line_vals.get('account_number')
                    if identifying_string:
                        partner_bank = self.env['res.partner.bank'].search([('acc_number', '=', identifying_string)], limit=1)
                        if partner_bank:
                            line_vals['bank_account_id'] = partner_bank.id
                            line_vals['partner_id'] = partner_bank.partner_id.id
        return stmts_vals

    def _create_bank_statements(self, stmts_vals):
        """ Create new bank statements from imported values, filtering out already imported transactions, and returns data used by the reconciliation widget """
        BankStatement = self.env['account.bank.statement']
        BankStatementLine = self.env['account.bank.statement.line']

        # Filter out already imported transactions and create statements
        statement_ids = []
        ignored_statement_lines_import_ids = []
        for st_vals in stmts_vals:
            filtered_st_lines = []
            for line_vals in st_vals['transactions']:
                if 'unique_import_id' not in line_vals \
                   or not line_vals['unique_import_id'] \
                   or not bool(BankStatementLine.sudo().search([('unique_import_id', '=', line_vals['unique_import_id'])], limit=1)):
                    filtered_st_lines.append(line_vals)
                else:
                    ignored_statement_lines_import_ids.append(line_vals['unique_import_id'])
                    if 'balance_start' in st_vals:
                        st_vals['balance_start'] += float(line_vals['amount'])

            if len(filtered_st_lines) > 0:
                # Remove values that won't be used to create records
                st_vals.pop('transactions', None)
                # Create the statement
                st_vals['line_ids'] = [[0, False, line] for line in filtered_st_lines]
                statement_ids.append(BankStatement.create(st_vals).id)
        if len(statement_ids) == 0:
            raise UserError(_('You already have imported that file.'))

        # Prepare import feedback
        notifications = []
        num_ignored = len(ignored_statement_lines_import_ids)
        if num_ignored > 0:
            notifications += [{
                'type': 'warning',
                'message': _("%d transactions had already been imported and were ignored.") % num_ignored if num_ignored > 1 else _("1 transaction had already been imported and was ignored."),
                'details': {
                    'name': _('Already imported items'),
                    'model': 'account.bank.statement.line',
                    'ids': BankStatementLine.search([('unique_import_id', 'in', ignored_statement_lines_import_ids)]).ids
                }
            }]
        return statement_ids, notifications
Ejemplo n.º 23
0
class L10nInPaymentReport(models.AbstractModel):
    _name = "l10n_in.payment.report"
    _description = "Indian accounting payment report"

    account_move_id = fields.Many2one('account.move', string="Account Move")
    payment_id = fields.Many2one('account.payment', string='Payment')
    currency_id = fields.Many2one('res.currency', string="Currency")
    amount = fields.Float(string="Amount")
    payment_amount = fields.Float(string="Payment Amount")
    partner_id = fields.Many2one('res.partner', string="Customer")
    payment_type = fields.Selection([('outbound', 'Send Money'),
                                     ('inbound', 'Receive Money')],
                                    string='Payment Type')
    journal_id = fields.Many2one('account.journal', string="Journal")
    company_id = fields.Many2one(related="journal_id.company_id",
                                 string="Company")
    place_of_supply = fields.Char(string="Place of Supply")
    supply_type = fields.Char(string="Supply Type")

    l10n_in_tax_id = fields.Many2one('account.tax', string="Tax")
    tax_rate = fields.Float(string="Rate")
    igst_amount = fields.Float(compute="_compute_tax_amount",
                               string="IGST amount")
    cgst_amount = fields.Float(compute="_compute_tax_amount",
                               string="CGST amount")
    sgst_amount = fields.Float(compute="_compute_tax_amount",
                               string="SGST amount")
    cess_amount = fields.Float(compute="_compute_tax_amount",
                               string="CESS amount")
    gross_amount = fields.Float(compute="_compute_tax_amount",
                                string="Gross advance")

    def _compute_l10n_in_tax(self,
                             taxes,
                             price_unit,
                             currency=None,
                             quantity=1.0,
                             product=None,
                             partner=None):
        """common method to compute gst tax amount base on tax group"""
        res = {
            'igst_amount': 0.0,
            'sgst_amount': 0.0,
            'cgst_amount': 0.0,
            'cess_amount': 0.0
        }
        AccountTaxRepartitionLine = self.env['account.tax.repartition.line']
        tax_report_line_igst = self.env.ref('l10n_in.tax_report_line_igst',
                                            False)
        tax_report_line_cgst = self.env.ref('l10n_in.tax_report_line_cgst',
                                            False)
        tax_report_line_sgst = self.env.ref('l10n_in.tax_report_line_sgst',
                                            False)
        tax_report_line_cess = self.env.ref('l10n_in.tax_report_line_cess',
                                            False)
        filter_tax = taxes.filtered(lambda t: t.type_tax_use != 'none')
        tax_compute = filter_tax.compute_all(price_unit,
                                             currency=currency,
                                             quantity=quantity,
                                             product=product,
                                             partner=partner)
        for tax_data in tax_compute['taxes']:
            tax_report_lines = AccountTaxRepartitionLine.browse(
                tax_data['tax_repartition_line_id']).mapped(
                    'tag_ids.tax_report_line_ids')
            if tax_report_line_sgst in tax_report_lines:
                res['sgst_amount'] += tax_data['amount']
            if tax_report_line_cgst in tax_report_lines:
                res['cgst_amount'] += tax_data['amount']
            if tax_report_line_igst in tax_report_lines:
                res['igst_amount'] += tax_data['amount']
            if tax_report_line_cess in tax_report_lines:
                res['cess_amount'] += tax_data['amount']
        res.update(tax_compute)
        return res

    #TO BE OVERWRITTEN
    @api.depends('currency_id')
    def _compute_tax_amount(self):
        """Calculate tax amount base on default tax set in company"""

    def _select(self):
        return """SELECT aml.id AS id,
            aml.move_id as account_move_id,
            ap.id AS payment_id,
            ap.payment_type,
            tax.id as l10n_in_tax_id,
            tax.amount AS tax_rate,
            am.partner_id,
            am.amount_total AS payment_amount,
            ap.journal_id,
            aml.currency_id,
            (CASE WHEN ps.l10n_in_tin IS NOT NULL
                THEN concat(ps.l10n_in_tin,'-',ps.name)
                WHEN p.id IS NULL and cps.l10n_in_tin IS NOT NULL
                THEN concat(cps.l10n_in_tin,'-',cps.name)
                ELSE ''
                END) AS place_of_supply,
            (CASE WHEN ps.id = cp.state_id or p.id IS NULL
                THEN 'Intra State'
                WHEN ps.id != cp.state_id and p.id IS NOT NULL
                THEN 'Inter State'
                END) AS supply_type"""

    def _from(self):
        return """FROM account_move_line aml
            JOIN account_move am ON am.id = aml.move_id
            JOIN account_payment ap ON ap.id = aml.payment_id
            JOIN account_account AS ac ON ac.id = aml.account_id
            JOIN account_journal AS aj ON aj.id = am.journal_id
            JOIN res_company AS c ON c.id = aj.company_id
            JOIN account_tax AS tax ON tax.id = (
                CASE WHEN ap.payment_type = 'inbound'
                    THEN c.account_sale_tax_id
                    ELSE c.account_purchase_tax_id END)
            JOIN res_partner p ON p.id = aml.partner_id
            LEFT JOIN res_country_state ps ON ps.id = p.state_id
            LEFT JOIN res_partner cp ON cp.id = c.partner_id
            LEFT JOIN res_country_state cps ON cps.id = cp.state_id
            """

    def _where(self):
        return """WHERE aml.payment_id IS NOT NULL
            AND tax.tax_group_id in (SELECT res_id FROM ir_model_data WHERE module='l10n_in' AND name in ('igst_group','gst_group'))
            AND ac.internal_type IN ('receivable', 'payable') AND am.state = 'posted'"""

    def init(self):
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute(
            """CREATE or REPLACE VIEW %s AS (
            %s %s %s)""" %
            (self._table, self._select(), self._from(), self._where()))
Ejemplo n.º 24
0
class link_tracker(models.Model):
    """link_tracker allow users to wrap any URL into a short and trackable URL.
    link_tracker counts clicks on each tracked link.
    This module is also used by mass_mailing, where each link in mail_mail html_body are converted into
    a trackable link to get the click-through rate of each mass_mailing."""

    _name = "link.tracker"
    _rec_name = "short_url"
    _description = 'Link Tracker'

    _inherit = ['utm.mixin']

    url = fields.Char(string='Target URL', required=True)
    count = fields.Integer(string='Number of Clicks',
                           compute='_compute_count',
                           store=True)
    short_url = fields.Char(string='Tracked URL', compute='_compute_short_url')
    link_click_ids = fields.One2many('link.tracker.click',
                                     'link_id',
                                     string='Clicks')
    title = fields.Char(string='Page Title', store=True)
    favicon = fields.Char(string='Favicon',
                          compute='_compute_favicon',
                          store=True)
    link_code_ids = fields.One2many('link.tracker.code',
                                    'link_id',
                                    string='Codes')
    code = fields.Char(string='Short URL code', compute='_compute_code')
    redirected_url = fields.Char(string='Redirected URL',
                                 compute='_compute_redirected_url')
    short_url_host = fields.Char(string='Host of the short URL',
                                 compute='_compute_short_url_host')
    icon_src = fields.Char(string='Favicon Source',
                           compute='_compute_icon_src')

    @api.model
    def convert_links(self, html, vals, blacklist=None):
        for match in re.findall(URL_REGEX, html):

            short_schema = self.env['ir.config_parameter'].sudo().get_param(
                'web.base.url') + '/r/'

            href = match[0]
            long_url = match[1]

            vals['url'] = utils.unescape(long_url)

            if not blacklist or not [
                    s for s in blacklist if s in long_url
            ] and not long_url.startswith(short_schema):
                link = self.create(vals)
                shorten_url = self.browse(link.id)[0].short_url

                if shorten_url:
                    new_href = href.replace(long_url, shorten_url)
                    html = html.replace(href, new_href)

        return html

    @api.one
    @api.depends('link_click_ids.link_id')
    def _compute_count(self):
        self.count = len(self.link_click_ids)

    @api.one
    @api.depends('code')
    def _compute_short_url(self):
        base_url = self.env['ir.config_parameter'].sudo().get_param(
            'web.base.url')
        self.short_url = urls.url_join(base_url,
                                       '/r/%(code)s' % {'code': self.code})

    @api.one
    def _compute_short_url_host(self):
        self.short_url_host = self.env['ir.config_parameter'].sudo().get_param(
            'web.base.url') + '/r/'

    @api.one
    def _compute_code(self):
        record = self.env['link.tracker.code'].search(
            [('link_id', '=', self.id)], limit=1, order='id DESC')
        self.code = record.code

    @api.one
    @api.depends('favicon')
    def _compute_icon_src(self):
        self.icon_src = 'data:image/png;base64,' + self.favicon

    @api.one
    @api.depends('url')
    def _compute_redirected_url(self):
        parsed = urls.url_parse(self.url)

        utms = {}
        for key, field, cook in self.env['utm.mixin'].tracking_fields():
            attr = getattr(self, field).name
            if attr:
                utms[key] = attr
        utms.update(parsed.decode_query())

        self.redirected_url = parsed.replace(
            query=urls.url_encode(utms)).to_url()

    @api.model
    @api.depends('url')
    def _get_title_from_url(self, url):
        try:
            page = requests.get(url, timeout=5)
            p = html.fromstring(page.text.encode('utf-8'),
                                parser=html.HTMLParser(encoding='utf-8'))
            title = p.find('.//title').text
        except:
            title = url

        return title

    @api.one
    @api.depends('url')
    def _compute_favicon(self):
        try:
            icon = requests.get('http://www.google.com/s2/favicons',
                                params={
                                    'domain': self.url
                                },
                                timeout=5).content
            icon_base64 = base64.b64encode(icon).replace(b"\n",
                                                         b"").decode('ascii')
        except:
            icon_base64 = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAACiElEQVQ4EaVTzU8TURCf2tJuS7tQtlRb6UKBIkQwkRRSEzkQgyEc6lkOKgcOph78Y+CgjXjDs2i44FXY9AMTlQRUELZapVlouy3d7kKtb0Zr0MSLTvL2zb75eL838xtTvV6H/xELBptMJojeXLCXyobnyog4YhzXYvmCFi6qVSfaeRdXdrfaU1areV5KykmX06rcvzumjY/1ggkR3Jh+bNf1mr8v1D5bLuvR3qDgFbvbBJYIrE1mCIoCrKxsHuzK+Rzvsi29+6DEbTZz9unijEYI8ObBgXOzlcrx9OAlXyDYKUCzwwrDQx1wVDGg089Dt+gR3mxmhcUnaWeoxwMbm/vzDFzmDEKMMNhquRqduT1KwXiGt0vre6iSeAUHNDE0d26NBtAXY9BACQyjFusKuL2Ry+IPb/Y9ZglwuVscdHaknUChqLF/O4jn3V5dP4mhgRJgwSYm+gV0Oi3XrvYB30yvhGa7BS70eGFHPoTJyQHhMK+F0ZesRVVznvXw5Ixv7/C10moEo6OZXbWvlFAF9FVZDOqEABUMRIkMd8GnLwVWg9/RkJF9sA4oDfYQAuzzjqzwvnaRUFxn/X2ZlmGLXAE7AL52B4xHgqAUqrC1nSNuoJkQtLkdqReszz/9aRvq90NOKdOS1nch8TpL555WDp49f3uAMXhACRjD5j4ykuCtf5PP7Fm1b0DIsl/VHGezzP1KwOiZQobFF9YyjSRYQETRENSlVzI8iK9mWlzckpSSCQHVALmN9Az1euDho9Xo8vKGd2rqooA8yBcrwHgCqYR0kMkWci08t/R+W4ljDCanWTg9TJGwGNaNk3vYZ7VUdeKsYJGFNkfSzjXNrSX20s4/h6kB81/271ghG17l+rPTAAAAAElFTkSuQmCC'

        self.favicon = icon_base64

    @api.multi
    def action_view_statistics(self):
        action = self.env['ir.actions.act_window'].for_xml_id(
            'link_tracker', 'action_view_click_statistics')
        action['domain'] = [('link_id', '=', self.id)]
        return action

    @api.multi
    def action_visit_page(self):
        return {
            'name': _("Visit Webpage"),
            'type': 'ir.actions.act_url',
            'url': self.url,
            'target': 'new',
        }

    @api.model
    def recent_links(self, filter, limit):
        if filter == 'newest':
            return self.search_read([], order='create_date DESC', limit=limit)
        elif filter == 'most-clicked':
            return self.search_read([('count', '!=', 0)],
                                    order='count DESC',
                                    limit=limit)
        elif filter == 'recently-used':
            return self.search_read([('count', '!=', 0)],
                                    order='write_date DESC',
                                    limit=limit)
        else:
            return {'Error': "This filter doesn't exist."}

    @api.model
    def create(self, vals):
        create_vals = vals.copy()

        if 'url' not in create_vals:
            raise ValueError('URL field required')
        else:
            create_vals['url'] = VALIDATE_URL(vals['url'])

        search_domain = []
        for fname, value in create_vals.items():
            search_domain.append((fname, '=', value))

        result = self.search(search_domain, limit=1)

        if result:
            return result

        if not create_vals.get('title'):
            create_vals['title'] = self._get_title_from_url(create_vals['url'])

        # Prevent the UTMs to be set by the values of UTM cookies
        for (key, fname, cook) in self.env['utm.mixin'].tracking_fields():
            if fname not in create_vals:
                create_vals[fname] = False

        link = super(link_tracker, self).create(create_vals)

        code = self.env['link.tracker.code'].get_random_code_string()
        self.env['link.tracker.code'].sudo().create({
            'code': code,
            'link_id': link.id
        })

        return link

    @api.model
    def get_url_from_code(self, code, context=None):
        code_rec = self.env['link.tracker.code'].sudo().search([('code', '=',
                                                                 code)])

        if not code_rec:
            return None

        return code_rec.link_id.redirected_url

    sql_constraints = [
        ('url_utms_uniq', 'unique (url, campaign_id, medium_id, source_id)',
         'The URL and the UTM combination must be unique')
    ]
Ejemplo n.º 25
0
class ConverterTestSub(models.Model):
    _name = 'web_editor.converter.test.sub'
    _description = 'Web Editor Converter Subtest'

    name = fields.Char()
Ejemplo n.º 26
0
class ribbonMedalquotation(models.TransientModel):
    _name = "ribbon.quotation"
    force_member_id = fields.Many2one("res.partner")
    curiar_address = fields.Char('Address')
    curiar_Contact = fields.Char('Mobile')
    delivery_date = fields.Date('Delivery Date')
    ribbon_qty = fields.Integer("Ribbon Qty")
    pin_qty = fields.Integer("Safety Pin")
    vel_qty = fields.Integer("Velcro")
    ribbon_details = fields.One2many("ribbon.ribbon.quotation.details",
                                     'quotation_id')
    ribbon_price_per_set = fields.Float('price/set',
                                        compute="get_ribbon_per_set_price")
    ribbon_refund_price = fields.Float('Refund')
    ribbon_net_price = fields.Float('price')

    bmedal_qty = fields.Integer("Tunic Medal Qty")
    bmedal_details = fields.One2many("ribbon.ribbon.quotation.details",
                                     'quotation_id')
    bmedal_price_per_set = fields.Float('price/set',
                                        compute="get_bmedal_per_set_price")
    bmedal_refund_price = fields.Float('Refund')
    bmedal_net_price = fields.Float('price')

    smedal_qty = fields.Integer("Meskit Medal Qty")
    smedal_details = fields.One2many("ribbon.ribbon.quotation.details",
                                     'quotation_id')
    smedal_price_per_set = fields.Float('price/set',
                                        compute="get_smedal_per_set_price")
    smedal_refund_price = fields.Float('Refund')
    smedal_net_price = fields.Float('price')

    @api.onchange("ribbon_qty")
    @api.multi
    def get_ribbon_per_set_price(self):
        set_price = 0
        refund = 0
        for rec in self.ribbon_details:
            set_price = set_price + rec.rate + rec.making
            refund = refund + (rec.rate * rec.ref_qty)
        self.ribbon_price_per_set = set_price
        self.ribbon_refund_price = refund
        self.ribbon_net_price = (self.ribbon_qty * set_price) - refund

    @api.onchange("bmedal_qty")
    @api.multi
    def get_bmedal_per_set_price(self):
        set_price = 0
        refund = 0
        for rec in self.bmedal_details:
            set_price = set_price + rec.rate + rec.making
            refund = refund + (rec.rate * rec.ref_qty)
        self.bmedal_price_per_set = set_price
        self.bmedal_refund_price = refund
        self.bmedal_net_price = (self.bmedal_qty * set_price) - refund

    @api.onchange("smedal_qty")
    @api.multi
    def get_smedal_per_set_price(self):
        set_price = 0
        refund = 0
        for rec in self.smedal_details:
            set_price = set_price + rec.rate + rec.making
            refund = refund + (rec.rate * rec.ref_qty)
        self.smedal_price_per_set = set_price
        self.smedal_refund_price = refund
        self.smedal_net_price = (self.smedal_qty * set_price) - refund
Ejemplo n.º 27
0
class AccountMove(models.Model):
    _inherit = 'account.move'

    l10n_sg_permit_number = fields.Char(string="Permit No.")

    l10n_sg_permit_number_date = fields.Date(string="Date of permit number")
Ejemplo n.º 28
0
class RibbonMedalAcquisitionWizard(models.TransientModel):
    _name = 'ribbon.acquisition.wizard'
    _description = 'Get acquired ribbons and medals by a force Member'
    ribbon_holder = fields.Many2one('res.partner', "Person")
    force_id = fields.Many2one("ribbon.force",
                               "Force Name",
                               related="ribbon_holder.force_id")
    id_no = fields.Char("ID No", related="ribbon_holder.id_no")
    rank = fields.Many2one("ribbon.rank", "Rank", related="ribbon_holder.rank")
    unit = fields.Many2one("ribbon.force.unit",
                           "Unit",
                           related="ribbon_holder.unit")
    post = fields.Many2one("ribbon.post", "Post", related="ribbon_holder.post")
    joining = fields.Date("Joining Date", related="ribbon_holder.joining")
    bcs = fields.Boolean("BCS ?", related="ribbon_holder.bcs")
    retired = fields.Date("Retired Date?", related="ribbon_holder.retired")
    service_length = fields.Char("Service Length",
                                 related="ribbon_holder.service_length")
    services = fields.Many2one("ribbon.personal.service")
    awards = fields.Many2many("ribbon.personal.award")
    missions = fields.Many2many("ribbon.personal.mission")
    acquired_ribbons = fields.Many2many("ribbon.acquired.ribbon.wizard",
                                        "party_acquired_rel")
    ribbon_set_image = fields.Html("Ribbons Immage", compute="getRibonImage")
    ribbon_point_size = fields.Integer("Point Size", default='15')
    ribbon_point_unit = fields.Selection(
        string='Unit',
        selection=[
            ('vw', 'Screen Width (vw)'),
            ('vh', 'Screen Height(vh)'),
            ('px', 'Point (px)'),
        ],
        help='You can set here the size depends on screen.',
        default='px')

    @api.onchange('ribbon_holder')
    def get_acquired_ribbons(self):
        if self.ribbon_holder.retired:
            ribbon_for_age = self.env["ribbon.regulation"].search([
                ('force_id', '=', self.force_id.id),
                ('acquisition.name', '=', "Service Age"),
                ('service_length', '<=', self.ribbon_holder.service_year),
                ("shedule_date", "<=", self.ribbon_holder.retired)
            ])
        else:
            ribbon_for_age = self.env["ribbon.regulation"].search([
                ('force_id', '=', self.force_id.id),
                ('acquisition.name', '=', "Service Age"),
                ('service_length', '<=', self.ribbon_holder.service_year),
            ])
        #todo here to apply search for shedule_date between joining date and Retired date
        data = []
        for rec in ribbon_for_age:
            input = self.env["ribbon.acquired.ribbon.wizard"].create({
                'force_member_id':
                self.ribbon_holder.id,
                'ribbon_id':
                rec.id,
                'extension':
                1,
                'serial':
                rec.serial
            })
            data.append(input.id)
        self.acquired_ribbons = [(6, 0, data)]
        ribbon_for_awards = self.env["ribbon.personal.award"].search([
            ("partner_id", "=", self.ribbon_holder.id)
        ])
        for rec in ribbon_for_awards:
            input = self.env["ribbon.acquired.ribbon.wizard"].create({
                'force_member_id':
                self.ribbon_holder.id,
                'ribbon_id':
                rec.ribbon_id.id,
                'extension':
                rec.extension.id,
                'serial':
                rec.ribbon_id.serial
            })
            self.acquired_ribbons = [(4, input.id)]
        ribbon_for_missions = self.env["ribbon.personal.mission"].search([
            ("partner_id", "=", self.ribbon_holder.id)
        ])
        for rec in ribbon_for_missions:
            input = self.env["ribbon.acquired.ribbon.wizard"].create({
                'force_member_id':
                self.ribbon_holder.id,
                'ribbon_id':
                rec.ribbon_id.id,
                'extension':
                rec.extension.id,
                'serial':
                rec.ribbon_id.serial
            })
            self.acquired_ribbons = [(4, input.id)]

    @api.onchange('ribbon_point_size', 'ribbon_point_unit', 'acquired_ribbons')
    def getRibonImage(self):
        point_unit = self.ribbon_point_unit
        pointsize = self.ribbon_point_size
        point_length = 6 * pointsize
        point_height = 2 * pointsize
        total_ribbon_point = len(self.acquired_ribbons)
        total_row = total_ribbon_point // 4
        ribbon_point_remainder = total_ribbon_point
        first_row_point = total_ribbon_point % 4
        row_point = 4
        row_no = 1
        position_left = 0
        position_height = 0
        first_point = 1
        image_set = '<div style = "width:100%; height=6' + point_unit + ';position:relative;  top:' + str(
            (row_no - 1) * point_height) + point_unit + ';  " >' + chr(10)
        if first_row_point > 0:
            total_row = total_row + 1

        for rec in self.acquired_ribbons:
            if first_point == 1:
                # start div for the row if this is the first point of the row
                image_set = image_set + '<div style = "width:100%; height=6' + point_unit + ';position:absolute;  top:' + str(
                    (row_no - 1) *
                    point_height) + point_unit + ';  " >' + chr(10)
                if row_no == 1:
                    if first_row_point > 0:
                        position_left = point_length * (4 -
                                                        first_row_point) / 2
                        row_point = first_row_point
            image_set = image_set + '<img style=" position:absolute;  left: '+str(position_left)+point_unit+';   width:'+str(point_length)+point_unit+';'\
                        + 'height:'+ str(point_height)+point_unit+';  border: 1px solid blue;z-index: 1;" src="../web/image/ribbon.extension/'+str(rec.extension.id)+'/image"/>'+chr(10)
            #here to impliment extension rule if extension exist!

            image_set=image_set+'<img style=" position:absolute;  left: '+str(position_left)+point_unit+';   width:'+str(point_length)+point_unit+';'\
                      + 'height:'+ str(point_height)+point_unit+';  border: 1px solid blue;z-index: 0;" src="../web/image/product.product/'+str(rec.ribbon_id.big_ribbon.id)+'/image"/>'+chr(10)
            row_point = row_point - 1
            first_point = 0
            position_left = position_left + point_length
            if row_point == 0:
                row_point = 4
                first_point = 1
                row_no = row_no + 1
                position_left = 0
                image_set = image_set + '</div>' + chr(10)
        image_set = image_set + '</div>'
        self.ribbon_set_image = image_set
Ejemplo n.º 29
0
class MailCC(models.Model):
    _name = 'mail.test.cc'
    _description = "Test Email CC Thread"
    _inherit = ['mail.thread.cc']

    name = fields.Char()
Ejemplo n.º 30
0
class FleetVehicleLogContract(models.Model):
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _inherits = {'fleet.vehicle.cost': 'cost_id'}
    _name = 'fleet.vehicle.log.contract'
    _description = 'Contract information on a vehicle'
    _order = 'state desc,expiration_date'

    def compute_next_year_date(self, strdate):
        oneyear = relativedelta(years=1)
        start_date = fields.Date.from_string(strdate)
        return fields.Date.to_string(start_date + oneyear)

    @api.model
    def default_get(self, default_fields):
        res = super(FleetVehicleLogContract, self).default_get(default_fields)
        contract = self.env.ref('fleet.type_contract_leasing',
                                raise_if_not_found=False)
        res.update({
            'date': fields.Date.context_today(self),
            'cost_subtype_id': contract and contract.id or False,
            'cost_type': 'contract'
        })
        return res

    name = fields.Text(compute='_compute_contract_name', store=True)
    active = fields.Boolean(default=True)
    user_id = fields.Many2one('res.users',
                              'Responsible',
                              default=lambda self: self.env.user,
                              index=True)
    start_date = fields.Date(
        'Contract Start Date',
        default=fields.Date.context_today,
        help='Date when the coverage of the contract begins')
    expiration_date = fields.Date(
        'Contract Expiration Date',
        default=lambda self: self.compute_next_year_date(
            fields.Date.context_today(self)),
        help=
        'Date when the coverage of the contract expirates (by default, one year after begin date)'
    )
    days_left = fields.Integer(compute='_compute_days_left',
                               string='Warning Date')
    insurer_id = fields.Many2one('res.partner', 'Vendor')
    purchaser_id = fields.Many2one(
        'res.partner',
        'Driver',
        default=lambda self: self.env.user.partner_id.id,
        help='Person to which the contract is signed for')
    ins_ref = fields.Char('Contract Reference', size=64, copy=False)
    state = fields.Selection(
        [('futur', 'Incoming'), ('open', 'In Progress'),
         ('diesoon', 'Expiring Soon'), ('expired', 'Expired'),
         ('closed', 'Closed')],
        'Status',
        default='open',
        readonly=True,
        help='Choose whether the contract is still valid or not',
        tracking=True,
        copy=False)
    notes = fields.Text(
        'Terms and Conditions',
        help=
        'Write here all supplementary information relative to this contract',
        copy=False)
    cost_generated = fields.Float(
        'Recurring Cost Amount',
        tracking=True,
        help="Costs paid at regular intervals, depending on the cost frequency. "
        "If the cost frequency is set to unique, the cost will be logged at the start date"
    )
    cost_frequency = fields.Selection([('no', 'No'), ('daily', 'Daily'),
                                       ('weekly', 'Weekly'),
                                       ('monthly', 'Monthly'),
                                       ('yearly', 'Yearly')],
                                      'Recurring Cost Frequency',
                                      default='no',
                                      help='Frequency of the recuring cost',
                                      required=True)
    generated_cost_ids = fields.One2many('fleet.vehicle.cost', 'contract_id',
                                         'Generated Costs')
    sum_cost = fields.Float(compute='_compute_sum_cost',
                            string='Indicative Costs Total')
    cost_id = fields.Many2one('fleet.vehicle.cost',
                              'Cost',
                              required=True,
                              ondelete='cascade')
    # we need to keep this field as a related with store=True because the graph view doesn't support
    # (1) to address fields from inherited table
    # (2) fields that aren't stored in database
    cost_amount = fields.Float(related='cost_id.amount',
                               string='Amount',
                               store=True,
                               readonly=False)
    odometer = fields.Float(
        string='Creation Contract Odometer',
        help=
        'Odometer measure of the vehicle at the moment of the contract creation'
    )

    @api.depends('vehicle_id', 'cost_subtype_id', 'date')
    def _compute_contract_name(self):
        for record in self:
            name = record.vehicle_id.name
            if record.cost_subtype_id.name:
                name += ' / ' + record.cost_subtype_id.name
            if record.date:
                name += ' / ' + str(record.date)
            record.name = name

    @api.depends('expiration_date', 'state')
    def _compute_days_left(self):
        """return a dict with as value for each contract an integer
        if contract is in an open state and is overdue, return 0
        if contract is in a closed state, return -1
        otherwise return the number of days before the contract expires
        """
        for record in self:
            if record.expiration_date and record.state in [
                    'open', 'diesoon', 'expired'
            ]:
                today = fields.Date.from_string(fields.Date.today())
                renew_date = fields.Date.from_string(record.expiration_date)
                diff_time = (renew_date - today).days
                record.days_left = diff_time > 0 and diff_time or 0
            else:
                record.days_left = -1

    @api.depends('cost_ids.amount')
    def _compute_sum_cost(self):
        for contract in self:
            contract.sum_cost = sum(contract.cost_ids.mapped('amount'))

    @api.onchange('vehicle_id')
    def _onchange_vehicle(self):
        if self.vehicle_id:
            self.odometer_unit = self.vehicle_id.odometer_unit

    def write(self, vals):
        res = super(FleetVehicleLogContract, self).write(vals)
        if vals.get('expiration_date') or vals.get('user_id'):
            self.activity_reschedule(
                ['fleet.mail_act_fleet_contract_to_renew'],
                date_deadline=vals.get('expiration_date'),
                new_user_id=vals.get('user_id'))
        return res

    def contract_close(self):
        for record in self:
            record.state = 'closed'

    def contract_open(self):
        for record in self:
            record.state = 'open'

    def act_renew_contract(self):
        assert len(
            self.ids
        ) == 1, "This operation should only be done for 1 single contract at a time, as it it suppose to open a window as result"
        for element in self:
            # compute end date
            startdate = fields.Date.from_string(element.start_date)
            enddate = fields.Date.from_string(element.expiration_date)
            diffdate = (enddate - startdate)
            default = {
                'date':
                fields.Date.context_today(self),
                'start_date':
                fields.Date.to_string(
                    fields.Date.from_string(element.expiration_date) +
                    relativedelta(days=1)),
                'expiration_date':
                fields.Date.to_string(enddate + diffdate),
            }
            newid = element.copy(default).id
        return {
            'name': _("Renew Contract"),
            'view_mode': 'form',
            'view_id':
            self.env.ref('fleet.fleet_vehicle_log_contract_view_form').id,
            'res_model': 'fleet.vehicle.log.contract',
            'type': 'ir.actions.act_window',
            'domain': '[]',
            'res_id': newid,
            'context': {
                'active_id': newid
            },
        }

    @api.model
    def scheduler_manage_auto_costs(self):
        # This method is called by a cron task
        # It creates costs for contracts having the "recurring cost" field setted, depending on their frequency
        # For example, if a contract has a reccuring cost of 200 with a weekly frequency, this method creates a cost of 200 on the
        # first day of each week, from the date of the last recurring costs in the database to today
        # If the contract has not yet any recurring costs in the database, the method generates the recurring costs from the start_date to today
        # The created costs are associated to a contract thanks to the many2one field contract_id
        # If the contract has no start_date, no cost will be created, even if the contract has recurring costs
        VehicleCost = self.env['fleet.vehicle.cost']
        deltas = {
            'yearly': relativedelta(years=+1),
            'monthly': relativedelta(months=+1),
            'weekly': relativedelta(weeks=+1),
            'daily': relativedelta(days=+1)
        }
        contracts = self.env['fleet.vehicle.log.contract'].search(
            [('state', '!=', 'closed')], offset=0, limit=None, order=None)
        for contract in contracts:
            if not contract.start_date or contract.cost_frequency == 'no':
                continue
            found = False
            startdate = contract.start_date
            if contract.generated_cost_ids:
                last_autogenerated_cost = VehicleCost.search(
                    [('contract_id', '=', contract.id),
                     ('auto_generated', '=', True)],
                    offset=0,
                    limit=1,
                    order='date desc')
                if last_autogenerated_cost:
                    found = True
                    startdate = last_autogenerated_cost.date
            if found:
                startdate += deltas.get(contract.cost_frequency)
            today = fields.Date.context_today(self)
            while (startdate <= today) & (startdate <=
                                          contract.expiration_date):
                data = {
                    'amount': contract.cost_generated,
                    'date': fields.Date.context_today(self),
                    'vehicle_id': contract.vehicle_id.id,
                    'cost_subtype_id': contract.cost_subtype_id.id,
                    'contract_id': contract.id,
                    'auto_generated': True
                }
                self.env['fleet.vehicle.cost'].create(data)
                startdate += deltas.get(contract.cost_frequency)
        return True

    @api.model
    def scheduler_manage_contract_expiration(self):
        # This method is called by a cron task
        # It manages the state of a contract, possibly by posting a message on the vehicle concerned and updating its status
        params = self.env['ir.config_parameter'].sudo()
        delay_alert_contract = int(
            params.get_param('hr_fleet.delay_alert_contract', default=30))
        date_today = fields.Date.from_string(fields.Date.today())
        outdated_days = fields.Date.to_string(date_today + relativedelta(
            days=+delay_alert_contract))
        nearly_expired_contracts = self.search([('state', '=', 'open'),
                                                ('expiration_date', '<',
                                                 outdated_days)])

        nearly_expired_contracts.write({'state': 'diesoon'})
        for contract in nearly_expired_contracts.filtered(
                lambda contract: contract.user_id):
            contract.activity_schedule(
                'fleet.mail_act_fleet_contract_to_renew',
                contract.expiration_date,
                user_id=contract.user_id.id)

        expired_contracts = self.search([
            ('state', 'not in', ['expired', 'closed']),
            ('expiration_date', '<', fields.Date.today())
        ])
        expired_contracts.write({'state': 'expired'})

        futur_contracts = self.search([
            ('state', 'not in', ['futur', 'closed']),
            ('start_date', '>', fields.Date.today())
        ])
        futur_contracts.write({'state': 'futur'})

        now_running_contracts = self.search([('state', '=', 'futur'),
                                             ('start_date', '<=',
                                              fields.Date.today())])
        now_running_contracts.write({'state': 'open'})

    def run_scheduler(self):
        self.scheduler_manage_auto_costs()
        self.scheduler_manage_contract_expiration()