Beispiel #1
0
class CRMRevealRule(models.Model):
    _name = 'crm.reveal.rule'
    _description = 'CRM Lead Generation Rules'
    _order = 'sequence'

    name = fields.Char(string='Rule Name', required=True)
    active = fields.Boolean(default=True)

    # Website Traffic Filter
    country_ids = fields.Many2many(
        'res.country',
        string='Countries',
        help=
        'Only visitors of following countries will be converted into leads/opportunities (using GeoIP).'
    )
    regex_url = fields.Char(
        string='URL Expression',
        help=
        'Regex to track website pages. Leave empty to track the entire website, or / to target the homepage. Example: /page* to track all the pages which begin with /page'
    )
    sequence = fields.Integer(
        help='Used to order the rules with same URL and countries. '
        'Rules with a lower sequence number will be processed first.')

    # Company Criteria Filter
    industry_tag_ids = fields.Many2many(
        'crm.reveal.industry',
        string='Industry Tags',
        help=
        'Leave empty to always match. Eagle will not create lead if no match')
    company_size_min = fields.Integer(
        string='Min Company Size',
        help="Leave it as 0 if you don't want to use this filter.")
    company_size_max = fields.Integer(
        string='Max Company Size',
        help="Leave it as 0 if you don't want to use this filter.")

    # Contact Generation Filter
    preferred_role_id = fields.Many2one('crm.reveal.role',
                                        string='Preferred Role')
    other_role_ids = fields.Many2many('crm.reveal.role', string='Other Roles')
    seniority_id = fields.Many2one('crm.reveal.seniority', string='Seniority')
    extra_contacts = fields.Integer(
        string='Extra Contacts',
        help=
        'This is the number of extra contacts to track if their role and seniority match your criteria.Their details will show up in the history thread of generated leads/opportunities. One credit is consumed per tracked contact.'
    )

    calculate_credits = fields.Integer(compute='_compute_credit_count',
                                       string='Credit Used',
                                       readonly=True)

    # Lead / Opportunity Data
    lead_for = fields.Selection(
        [('companies', 'Companies'), ('people', 'Companies + Contacts')],
        string='Data Tracking',
        required=True,
        default='companies',
        help=
        'If you track company data, one credit will be consumed per lead/opportunity created. If you track company and contacts data, two credits will be consumed. Such data will be visible in the lead/opportunity.'
    )
    lead_type = fields.Selection([('lead', 'Lead'),
                                  ('opportunity', 'Opportunity')],
                                 string='Type',
                                 required=True,
                                 default='opportunity')
    suffix = fields.Char(
        string='Suffix',
        help=
        'This will be appended in name of generated lead so you can identify lead/opportunity is generated with this rule'
    )
    team_id = fields.Many2one('crm.team', string='Sales Channel')
    tag_ids = fields.Many2many('crm.lead.tag', string='Tags')
    user_id = fields.Many2one('res.users', string='Salesperson')
    priority = fields.Selection(crm_stage.AVAILABLE_PRIORITIES,
                                string='Priority')
    lead_ids = fields.One2many('crm.lead',
                               'reveal_rule_id',
                               string='Generated Lead / Opportunity')
    leads_count = fields.Integer(compute='_compute_leads_count',
                                 string='Number of Generated Leads')
    opportunity_count = fields.Integer(
        compute='_compute_leads_count',
        string='Number of Generated Opportunity')

    # This limits the number of extra contact.
    # Even if more than 5 extra contacts provided service will return only 5 contacts (see service module for more)
    _sql_constraints = [
        ('limit_extra_contacts',
         'check(extra_contacts >= 0 and extra_contacts <= 5)',
         'Maximum 5 extra contacts are allowed!'),
    ]

    @api.constrains('regex_url')
    def _check_regex_url(self):
        try:
            if self.regex_url:
                re.compile(self.regex_url)
        except Exception:
            raise ValidationError(_('Enter Valid Regex.'))

    @api.model
    def _assert_geoip(self):
        if not _geoip_resolver:
            message = _(
                'Lead Generation requires a GeoIP resolver which could not be found on your system. Please consult https://pypi.org/project/GeoIP/.'
            )
            self.env['bus.bus'].sendone(
                (self._cr.dbname, 'res.partner', self.env.user.partner_id.id),
                {
                    'type': 'simple_notification',
                    'title': _('Missing Library'),
                    'message': message,
                    'sticky': True,
                    'warning': True
                })

    @api.model
    def create(self, vals):
        self.clear_caches(
        )  # Clear the cache in order to recompute _get_active_rules
        self._assert_geoip()
        return super(CRMRevealRule, self).create(vals)

    def write(self, vals):
        fields_set = {'country_ids', 'regex_url', 'active'}
        if set(vals.keys()) & fields_set:
            self.clear_caches(
            )  # Clear the cache in order to recompute _get_active_rules
        self._assert_geoip()
        return super(CRMRevealRule, self).write(vals)

    def unlink(self):
        self.clear_caches(
        )  # Clear the cache in order to recompute _get_active_rules
        return super(CRMRevealRule, self).unlink()

    @api.depends('extra_contacts', 'lead_for')
    def _compute_credit_count(self):
        """ Computes maximum IAP credit can be consumed per lead """
        credit = 1
        if self.lead_for == 'people':
            credit += 1
            if self.extra_contacts:
                credit += self.extra_contacts
        self.calculate_credits = credit

    def _compute_leads_count(self):
        leads = self.env['crm.lead'].read_group(
            [('reveal_rule_id', 'in', self.ids)],
            fields=['reveal_rule_id', 'type'],
            groupby=['reveal_rule_id', 'type'],
            lazy=False)
        mapping = {(lead['reveal_rule_id'][0], lead['type']): lead['__count']
                   for lead in leads}
        for rule in self:
            rule.leads_count = mapping.get((rule.id, 'lead'), 0)
            rule.opportunity_count = mapping.get((rule.id, 'opportunity'), 0)

    def action_get_lead_tree_view(self):
        action = self.env.ref('crm.crm_lead_all_leads').read()[0]
        action['domain'] = [('id', 'in', self.lead_ids.ids),
                            ('type', '=', 'lead')]
        return action

    def action_get_opportunity_tree_view(self):
        action = self.env.ref('crm.crm_lead_opportunities').read()[0]
        action['domain'] = [('id', 'in', self.lead_ids.ids),
                            ('type', '=', 'opportunity')]
        return action

    @api.model
    @tools.ormcache()
    def _get_active_rules(self):
        """
        Returns informations about the all rules.
        The return is in the form :
        {
            'country_rules': {
                'BE': [0, 1],
                'US': [0]
            },
            'rules': [
            {
                'id': 0,
                'url': ***,
                'country_codes': ['BE', 'US']
            },
            {
                'id': 1,
                'url': ***,
                'country_codes': ['BE']
            }
            ]
        }
        """
        country_rules = {}
        rules_records = self.search([])
        rules = []
        # Fixes for special cases
        for rule in rules_records:
            regex_url = rule['regex_url']
            if not regex_url:
                regex_url = '.*'  # for all pages if url not given
            elif regex_url == '/':
                regex_url = '.*/$'  # for home
            countries = rule.country_ids.mapped('code')
            rules.append({
                'id': rule.id,
                'regex': regex_url,
                'country_codes': countries
            })
            for country in countries:
                country_rules = self._add_to_country(country_rules, country,
                                                     len(rules) - 1)
        return {
            'country_rules': country_rules,
            'rules': rules,
        }

    def _add_to_country(self, country_rules, country, rule_index):
        """
        Add the rule index to the country code in the country_rules
        """
        if country not in country_rules:
            country_rules[country] = []
        country_rules[country].append(rule_index)
        return country_rules

    def _match_url(self, url, country_code, rules_excluded):
        """
        Return the matching rule based on the country and URL.
        """
        all_rules = self._get_active_rules()
        rules_id = all_rules['country_rules'].get(country_code, [])
        rules_matched = []
        for rule_index in rules_id:
            rule = all_rules['rules'][rule_index]
            if str(rule['id']) not in rules_excluded and re.search(
                    rule['regex'], url):
                rules_matched.append(rule)
        return rules_matched

    @api.model
    def _process_lead_generation(self, autocommit=True):
        """ Cron Job for lead generation from page view """
        _logger.info('Start Reveal Lead Generation')
        self.env['crm.reveal.view']._clean_reveal_views()
        self._unlink_unrelevant_reveal_view()
        reveal_views = self._get_reveal_views_to_process()
        view_count = 0
        while reveal_views:
            view_count += len(reveal_views)
            server_payload = self._prepare_iap_payload(dict(reveal_views))
            enough_credit = self._perform_reveal_service(server_payload)
            if autocommit:
                # auto-commit for batch processing
                self._cr.commit()
            if enough_credit:
                reveal_views = self._get_reveal_views_to_process()
            else:
                reveal_views = False
        _logger.info('End Reveal Lead Generation - %s views processed',
                     view_count)

    @api.model
    def _unlink_unrelevant_reveal_view(self):
        """
        We don't want to create the lead if in past (<6 months) we already
        created lead with given IP. So, we unlink crm.reveal.view with same IP
        as a already created lead.
        """
        months_valid = self.env['ir.config_parameter'].sudo().get_param(
            'reveal.lead_month_valid', DEFAULT_REVEAL_MONTH_VALID)
        try:
            months_valid = int(months_valid)
        except ValueError:
            months_valid = DEFAULT_REVEAL_MONTH_VALID
        domain = []
        domain.append(('reveal_ip', '!=', False))
        domain.append(
            ('create_date', '>',
             fields.Datetime.to_string(datetime.date.today() -
                                       relativedelta(months=months_valid))))
        leads = self.env['crm.lead'].with_context(
            active_test=False).search(domain)
        self.env['crm.reveal.view'].search([
            ('reveal_ip', 'in', [lead.reveal_ip for lead in leads])
        ]).unlink()

    @api.model
    def _get_reveal_views_to_process(self):
        """ Return list of reveal rule ids grouped by IPs """
        batch_limit = DEFAULT_REVEAL_BATCH_LIMIT
        query = """
            SELECT v.reveal_ip, array_agg(v.reveal_rule_id ORDER BY r.sequence)
            FROM crm_reveal_view v
            INNER JOIN crm_reveal_rule r
            ON v.reveal_rule_id = r.id
            WHERE v.reveal_state='to_process'
            GROUP BY v.reveal_ip
            LIMIT %d
            """ % batch_limit

        self.env.cr.execute(query)
        return self.env.cr.fetchall()

    def _prepare_iap_payload(self, pgv):
        """ This will prepare the page view and returns payload
            Payload sample
            {
                ips: {
                    '192.168.1.1': [1,4],
                    '192.168.1.6': [2,4]
                },
                rules: {
                    1: {rule_data},
                    2: {rule_data},
                    4: {rule_data}
                }
            }
        """
        new_list = list(set(itertools.chain.from_iterable(pgv.values())))
        rule_records = self.browse(new_list)
        return {'ips': pgv, 'rules': rule_records._get_rules_payload()}

    def _get_rules_payload(self):
        company_country = self.env.user.company_id.country_id
        rule_payload = {}
        for rule in self:
            data = {
                'rule_id': rule.id,
                'lead_for': rule.lead_for,
                'countries': rule.country_ids.mapped('code'),
                'company_size_min': rule.company_size_min,
                'company_size_max': rule.company_size_max,
                'industry_tags': rule.industry_tag_ids.mapped('reveal_id'),
                'user_country': company_country and company_country.code
                or False
            }
            if rule.lead_for == 'people':
                data.update({
                    'preferred_role':
                    rule.preferred_role_id.reveal_id or '',
                    'other_roles':
                    rule.other_role_ids.mapped('reveal_id'),
                    'seniority':
                    rule.seniority_id.reveal_id or '',
                    'extra_contacts':
                    rule.extra_contacts
                })
            rule_payload[rule.id] = data
        return rule_payload

    def _perform_reveal_service(self, server_payload):
        result = False
        account_token = self.env['iap.account'].get('reveal')
        endpoint = self.env['ir.config_parameter'].sudo().get_param(
            'reveal.endpoint', DEFAULT_ENDPOINT) + '/iap/clearbit/1/reveal'
        params = {
            'account_token': account_token.account_token,
            'data': server_payload
        }
        result = jsonrpc(endpoint, params=params, timeout=300)
        for res in result.get('reveal_data', []):
            if not res.get('not_found'):
                lead = self._create_lead_from_response(res)
                self.env['crm.reveal.view'].search([('reveal_ip', '=',
                                                     res['ip'])]).unlink()
            else:
                self.env['crm.reveal.view'].search([
                    ('reveal_ip', '=', res['ip'])
                ]).write({'reveal_state': 'not_found'})
        if result.get('credit_error'):
            self._notify_no_more_credit()
            return False
        else:
            self.env['ir.config_parameter'].sudo().set_param(
                'reveal.already_notified', False)
        return True

    def _notify_no_more_credit(self):
        """
        Notify about the number of credit.
        In order to avoid to spam people each hour, an ir.config_parameter is set
        """
        already_notified = self.env['ir.config_parameter'].sudo().get_param(
            'reveal.already_notified', False)
        if already_notified:
            return
        mail_template = self.env.ref('crm_reveal.reveal_no_credits')
        iap_account = self.env['iap.account'].search(
            [('service_name', '=', 'reveal')], limit=1)
        # Get the email address of the creators of the Lead Generation Rules
        res = self.env['crm.reveal.rule'].search_read([], ['create_uid'])
        uids = set(r['create_uid'][0] for r in res if r.get('create_uid'))
        res = self.env['res.users'].search_read([('id', 'in', list(uids))],
                                                ['email'])
        emails = set(r['email'] for r in res if r.get('email'))

        mail_values = mail_template.generate_email(iap_account.id)
        mail_values['email_to'] = ','.join(emails)
        mail = self.env['mail.mail'].create(mail_values)
        mail.send()
        self.env['ir.config_parameter'].sudo().set_param(
            'reveal.already_notified', True)

    def _create_lead_from_response(self, result):
        """ This method will get response from service and create the lead accordingly """
        if result['rule_id']:
            rule = self.browse(result['rule_id'])
        else:
            # Not create a lead if the information match no rule
            # If there is no match, the service still returns all informations
            # in order to let custom code use it.
            return False
        if not result['clearbit_id']:
            return False
        already_created_lead = self.env['crm.lead'].search([
            ('reveal_id', '=', result['clearbit_id'])
        ])
        if already_created_lead:
            _logger.info('Existing lead for this clearbit_id [%s]',
                         result['clearbit_id'])
            # Does not create a lead if the reveal_id is already known
            return False
        lead_vals = rule._lead_vals_from_response(result)
        lead = self.env['crm.lead'].create(lead_vals)
        lead.message_post_with_view(
            'crm_reveal.lead_message_template',
            values=self._format_data_for_message_post(result),
            subtype_id=self.env.ref('mail.mt_note').id)
        return lead

    # Methods responsible for format response data in to valid eagle lead data
    def _lead_vals_from_response(self, result):
        self.ensure_one()
        reveal_data = result['reveal_data']
        people_data = result.get('people_data')
        country_id = self.env['res.country'].search([
            ('code', '=', reveal_data['country_code'])
        ]).id
        website_url = 'https://www.%s' % reveal_data['domain'] if reveal_data[
            'domain'] else False
        lead_vals = {
            # Lead vals from rule itself
            'type':
            self.lead_type,
            'team_id':
            self.team_id.id,
            'tag_ids': [(6, 0, self.tag_ids.ids)],
            'priority':
            self.priority,
            'user_id':
            self.user_id.id,
            'reveal_ip':
            result['ip'],
            'reveal_rule_id':
            self.id,
            'reveal_id':
            result['clearbit_id'],
            'referred':
            'Website Visitor',
            # Lead vals from response
            'name':
            reveal_data['name'],
            'reveal_iap_credits':
            result['credit'],
            'partner_name':
            reveal_data['legal_name'] or reveal_data['name'],
            'email_from':
            ",".join(reveal_data['email'] or []),
            'phone':
            reveal_data['phone'] or
            (reveal_data['phone_numbers'] and reveal_data['phone_numbers'][0])
            or '',
            'website':
            website_url,
            'street':
            reveal_data['location'],
            'city':
            reveal_data['city'],
            'zip':
            reveal_data['postal_code'],
            'country_id':
            country_id,
            'state_id':
            self._find_state_id(reveal_data['state_name'],
                                reveal_data['state_code'], country_id),
            'description':
            self._prepare_lead_description(reveal_data),
        }

        if self.suffix:
            lead_vals['name'] = '%s - %s' % (lead_vals['name'], self.suffix)

        # If type is people then add first contact in lead data
        if people_data:
            lead_vals.update({
                'contact_name': people_data[0]['full_name'],
                'email_from': people_data[0]['email'],
                'function': people_data[0]['title'],
            })
        return lead_vals

    def _find_state_id(self, state_code, state_name, country_id):
        state_id = self.env['res.country.state'].search([
            ('code', '=', state_code), ('country_id', '=', country_id)
        ])
        if state_id:
            return state_id.id
        return False

    def _prepare_lead_description(self, reveal_data):
        description = ''
        if reveal_data['sector']:
            description += reveal_data['sector']
        if reveal_data['website_title']:
            description += '\n' + reveal_data['website_title']
        if reveal_data['twitter_bio']:
            description += '\n' + "Twitter Bio: " + reveal_data['twitter_bio']
        if reveal_data['twitter_followers']:
            description += ('\nTwitter %s followers, %s \n') % (
                reveal_data['twitter_followers'],
                reveal_data['twitter_location'] or '')

        numbers = [
            'raised', 'market_cap', 'employees', 'estimated_annual_revenue'
        ]
        millnames = ['', ' K', ' M', ' B', 'T']

        def millify(n):
            try:
                n = float(n)
                millidx = max(
                    0,
                    min(
                        len(millnames) - 1,
                        int(floor(0 if n == 0 else log10(abs(n)) / 3))))
                return '{:.0f}{}'.format(n / 10**(3 * millidx),
                                         millnames[millidx])
            except Exception:
                return n

        for key in numbers:
            if reveal_data.get(key):
                description += ' %s : %s,' % (key.replace(
                    '_', ' ').title(), millify(reveal_data[key]))
        return description

    def _format_data_for_message_post(self, result):
        reveal_data = result['reveal_data']
        people_data = result.get('people_data')
        log_data = {
            'twitter': reveal_data['twitter'],
            'description': reveal_data['description'],
            'logo': reveal_data['logo'],
            'name': reveal_data['name'],
            'phone_numbers': reveal_data['phone_numbers'],
            'facebook': reveal_data['facebook'],
            'linkedin': reveal_data['linkedin'],
            'crunchbase': reveal_data['crunchbase'],
            'tech': [t.replace('_', ' ').title() for t in reveal_data['tech']],
            'people_data': people_data,
        }
        timezone = result['ip_time_zone'] or reveal_data['timezone']
        if timezone:
            log_data.update({
                'timezone': timezone.replace('_', ' ').title(),
                'timezone_url': reveal_data['timezone_url'],
            })
        return log_data
Beispiel #2
0
class AccountMoveLine(models.Model):
    _inherit = "account.move.line"

    l10n_pe_group_id = fields.Many2one("account.group", related="account_id.group_id", store=True)
Beispiel #3
0
class Partner(models.Model):
    _inherit = 'res.partner'

    country_enforce_cities = fields.Boolean(
        related='country_id.enforce_cities', readonly=True)
    city_id = fields.Many2one('res.city', string='City of Address')

    @api.onchange('city_id')
    def _onchange_city_id(self):
        if self.city_id:
            self.city = self.city_id.name
            self.zip = self.city_id.zipcode
            self.state_id = self.city_id.state_id

    @api.model
    def _fields_view_get_address(self, arch):
        arch = super(Partner, self)._fields_view_get_address(arch)
        # render the partner address accordingly to address_view_id
        doc = etree.fromstring(arch)
        if doc.xpath("//field[@name='city_id']"):
            return arch

        replacement_xml = """
            <div>
                <field name="country_enforce_cities" invisible="1"/>
                <field name='city' placeholder="%(placeholder)s" class="o_address_city"
                    attrs="{
                        'invisible': [('country_enforce_cities', '=', True), ('city_id', '!=', False)],
                        'readonly': [('type', '=', 'contact')%(parent_condition)s]
                    }"
                />
                <field name='city_id' placeholder="%(placeholder)s" string="%(placeholder)s" class="o_address_city"
                    context="{'default_country_id': country_id,
                              'default_name': city,
                              'default_zipcode': zip,
                              'default_state_id': state_id}"
                    domain="[('country_id', '=', country_id)]"
                    attrs="{
                        'invisible': [('country_enforce_cities', '=', False)],
                        'readonly': [('type', '=', 'contact')%(parent_condition)s]
                    }"
                />
            </div>
        """

        replacement_data = {
            'placeholder': _('City'),
        }

        def _arch_location(node):
            in_subview = False
            view_type = False
            parent = node.getparent()
            while parent is not None and (not view_type or not in_subview):
                if parent.tag == 'field':
                    in_subview = True
                elif parent.tag in ['list', 'tree', 'kanban', 'form']:
                    view_type = parent.tag
                parent = parent.getparent()
            return {
                'view_type': view_type,
                'in_subview': in_subview,
            }

        for city_node in doc.xpath("//field[@name='city']"):
            location = _arch_location(city_node)
            replacement_data['parent_condition'] = ''
            if location['view_type'] == 'form' or not location['in_subview']:
                replacement_data[
                    'parent_condition'] = ", ('parent_id', '!=', False)"

            replacement_formatted = replacement_xml % replacement_data
            for replace_node in etree.fromstring(
                    replacement_formatted).getchildren():
                city_node.addprevious(replace_node)
            parent = city_node.getparent()
            parent.remove(city_node)

        arch = etree.tostring(doc, encoding='unicode')
        return arch
Beispiel #4
0
class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
    _name = "hr.expense.sheet.register.payment.wizard"
    _description = "Expense Register Payment Wizard"

    @api.model
    def default_get(self, fields):
        result = super(HrExpenseSheetRegisterPaymentWizard,
                       self).default_get(fields)

        active_model = self._context.get('active_model')
        if active_model != 'hr.expense.sheet':
            raise UserError(
                _('You can only apply this action from an expense report.'))

        active_id = self._context.get('active_id')
        if 'expense_sheet_id' in fields and active_id:
            result['expense_sheet_id'] = active_id

        if 'partner_id' in fields and active_id and not result.get(
                'partner_id'):
            expense_sheet = self.env['hr.expense.sheet'].browse(active_id)
            result[
                'partner_id'] = expense_sheet.address_id.id or expense_sheet.employee_id.id and expense_sheet.employee_id.address_home_id.id
        return result

    expense_sheet_id = fields.Many2one('hr.expense.sheet',
                                       string="Expense Report",
                                       required=True)
    partner_id = fields.Many2one(
        'res.partner',
        string='Partner',
        required=True,
        domain=
        "['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    partner_bank_account_id = fields.Many2one(
        'res.partner.bank',
        string="Recipient Bank Account",
        domain=
        "['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    journal_id = fields.Many2one(
        'account.journal',
        string='Payment Method',
        required=True,
        domain=
        "[('type', 'in', ('bank', 'cash')), ('company_id', '=', company_id)]")
    company_id = fields.Many2one('res.company',
                                 related='expense_sheet_id.company_id',
                                 string='Company',
                                 readonly=True)
    payment_method_id = fields.Many2one('account.payment.method',
                                        string='Payment Type',
                                        required=True)
    amount = fields.Monetary(string='Payment Amount', required=True)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        required=True,
        default=lambda self: self.env.company.currency_id)
    payment_date = fields.Date(string='Payment Date',
                               default=fields.Date.context_today,
                               required=True)
    communication = fields.Char(string='Memo')
    hide_payment_method = fields.Boolean(
        compute='_compute_hide_payment_method',
        help=
        "Technical field used to hide the payment method if the selected journal has only one available which is 'manual'"
    )
    show_partner_bank_account = fields.Boolean(
        compute='_compute_show_partner_bank',
        help=
        'Technical field used to know whether the field `partner_bank_account_id` needs to be displayed or not in the payments form views'
    )
    require_partner_bank_account = fields.Boolean(
        compute='_compute_show_partner_bank',
        help=
        'Technical field used to know whether the field `partner_bank_account_id` needs to be required or not in the payments form views'
    )

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        expense_sheet = self.expense_sheet_id
        if expense_sheet.employee_id.id and expense_sheet.employee_id.sudo(
        ).bank_account_id.id:
            self.partner_bank_account_id = expense_sheet.employee_id.sudo(
            ).bank_account_id.id
        elif self.partner_id and len(self.partner_id.bank_ids) > 0:
            self.partner_bank_account_id = self.partner_id.bank_ids[0]
        else:
            self.partner_bank_account_id = False

    @api.constrains('amount')
    def _check_amount(self):
        for wizard in self:
            if not wizard.amount > 0.0:
                raise ValidationError(
                    _('The payment amount must be strictly positive.'))

    @api.depends('payment_method_id')
    def _compute_show_partner_bank(self):
        """ Computes if the destination bank account must be displayed in the payment form view. By default, it
        won't be displayed but some modules might change that, depending on the payment type."""
        for payment in self:
            payment.show_partner_bank_account = payment.payment_method_id.code in self.env[
                'account.payment']._get_method_codes_using_bank_account()
            payment.require_partner_bank_account = payment.payment_method_id.code in self.env[
                'account.payment']._get_method_codes_needing_bank_account()

    @api.depends('journal_id')
    def _compute_hide_payment_method(self):
        for wizard in self:
            if not wizard.journal_id:
                wizard.hide_payment_method = True
            else:
                journal_payment_methods = wizard.journal_id.outbound_payment_method_ids
                wizard.hide_payment_method = (
                    len(journal_payment_methods) == 1
                    and journal_payment_methods[0].code == 'manual')

    @api.onchange('journal_id')
    def _onchange_journal(self):
        if self.journal_id:
            # Set default payment method (we consider the first to be the default one)
            payment_methods = self.journal_id.outbound_payment_method_ids
            self.payment_method_id = payment_methods and payment_methods[
                0] or False
            # Set payment method domain (restrict to methods enabled for the journal and to selected payment type)
            return {
                'domain': {
                    'payment_method_id': [('payment_type', '=', 'outbound'),
                                          ('id', 'in', payment_methods.ids)]
                }
            }
        return {}

    def _get_payment_vals(self):
        """ Hook for extension """
        return {
            'partner_type': 'supplier',
            'payment_type': 'outbound',
            'partner_id': self.partner_id.id,
            'partner_bank_account_id': self.partner_bank_account_id.id,
            'journal_id': self.journal_id.id,
            'company_id': self.company_id.id,
            'payment_method_id': self.payment_method_id.id,
            'amount': self.amount,
            'currency_id': self.currency_id.id,
            'payment_date': self.payment_date,
            'communication': self.communication
        }

    def expense_post_payment(self):
        self.ensure_one()
        context = dict(self._context or {})
        active_ids = context.get('active_ids', [])
        expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)

        # Create payment and post it
        payment = self.env['account.payment'].create(self._get_payment_vals())
        payment.post()

        # Log the payment in the chatter
        body = (_(
            "A payment of %s %s with the reference <a href='/mail/view?%s'>%s</a> related to your expense %s has been made."
        ) % (payment.amount, payment.currency_id.symbol,
             url_encode({
                 'model': 'account.payment',
                 'res_id': payment.id
             }), payment.name, expense_sheet.name))
        expense_sheet.message_post(body=body)

        # Reconcile the payment and the expense, i.e. lookup on the payable account move lines
        account_move_lines_to_reconcile = self.env['account.move.line']
        for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids:
            if line.account_id.internal_type == 'payable' and not line.reconciled:
                account_move_lines_to_reconcile |= line
        account_move_lines_to_reconcile.reconcile()

        return {'type': 'ir.actions.act_window_close'}
class CRMLeadMiningRequest(models.Model):
    _name = 'crm.iap.lead.mining.request'
    _description = 'CRM Lead Mining Request'

    def _default_lead_type(self):
        if self.env.user.has_group('crm.group_use_lead'):
            return 'lead'
        else:
            return 'opportunity'

    name = fields.Char(string='Request Number',
                       required=True,
                       readonly=True,
                       default=lambda self: _('New'),
                       copy=False)
    state = fields.Selection([('draft', 'Draft'), ('done', 'Done'),
                              ('error', 'Error')],
                             string='Status',
                             required=True,
                             default='draft')

    # Request Data
    lead_number = fields.Integer(string='Number of Leads',
                                 required=True,
                                 default=10)
    search_type = fields.Selection(
        [('companies', 'Companies'),
         ('people', 'Companies and their Contacts')],
        string='Target',
        required=True,
        default='companies')
    error = fields.Text(string='Error', readonly=True)

    # Lead / Opportunity Data
    lead_type = fields.Selection([('lead', 'Lead'),
                                  ('opportunity', 'Opportunity')],
                                 string='Type',
                                 required=True,
                                 default=_default_lead_type)
    team_id = fields.Many2one('crm.team',
                              string='Sales Team',
                              domain="[('use_opportunities', '=', True)]")
    user_id = fields.Many2one('res.users', string='Salesperson')
    tag_ids = fields.Many2many('crm.lead.tag', string='Tags')
    lead_ids = fields.One2many('crm.lead',
                               'lead_mining_request_id',
                               string='Generated Lead / Opportunity')
    leads_count = fields.Integer(compute='_compute_leads_count',
                                 string='Number of Generated Leads')

    # Company Criteria Filter
    filter_on_size = fields.Boolean(string='Filter on Size', default=False)
    company_size_min = fields.Integer(string='Size', default=1)
    company_size_max = fields.Integer(default=1000)
    country_ids = fields.Many2many('res.country', string='Countries')
    state_ids = fields.Many2many('res.country.state', string='States')
    industry_ids = fields.Many2many('crm.iap.lead.industry',
                                    string='Industries')

    # Contact Generation Filter
    contact_number = fields.Integer(string='Number of Contacts', default=1)
    contact_filter_type = fields.Selection([('role', 'Role'),
                                            ('seniority', 'Seniority')],
                                           string='Filter on',
                                           default='role')
    preferred_role_id = fields.Many2one('crm.iap.lead.role',
                                        string='Preferred Role')
    role_ids = fields.Many2many('crm.iap.lead.role', string='Other Roles')
    seniority_id = fields.Many2one('crm.iap.lead.seniority',
                                   string='Seniority')

    # Fields for the blue tooltip
    lead_credits = fields.Char(compute='_compute_tooltip', readonly=True)
    lead_contacts_credits = fields.Char(compute='_compute_tooltip',
                                        readonly=True)
    lead_total_credits = fields.Char(compute='_compute_tooltip', readonly=True)

    @api.onchange('lead_number', 'contact_number')
    def _compute_tooltip(self):
        for record in self:
            company_credits = CREDIT_PER_COMPANY * record.lead_number
            contact_credits = CREDIT_PER_CONTACT * record.contact_number
            total_contact_credits = contact_credits * record.lead_number
            record.lead_contacts_credits = _(
                "Up to %d additional credits will be consumed to identify %d contacts per company."
            ) % (contact_credits * company_credits, record.contact_number)
            record.lead_credits = _(
                '%d credits will be consumed to find %d companies.') % (
                    company_credits, record.lead_number)
            record.lead_total_credits = _(
                "This makes a total of %d credits for this request.") % (
                    total_contact_credits + company_credits)

    @api.depends('lead_ids')
    def _compute_leads_count(self):
        for req in self:
            req.leads_count = len(req.lead_ids)

    @api.onchange('lead_number')
    def _onchange_lead_number(self):
        if self.lead_number <= 0:
            self.lead_number = 1
        elif self.lead_number > MAX_LEAD:
            self.lead_number = MAX_LEAD

    @api.onchange('contact_number')
    def _onchange_contact_number(self):
        if self.contact_number <= 0:
            self.contact_number = 1
        elif self.contact_number > MAX_CONTACT:
            self.contact_number = MAX_CONTACT

    @api.onchange('country_ids')
    def _onchange_country_ids(self):
        self.state_ids = []

    @api.onchange('company_size_min')
    def _onchange_company_size_min(self):
        if self.company_size_min <= 0:
            self.company_size_min = 1
        elif self.company_size_min > self.company_size_max:
            self.company_size_min = self.company_size_max

    @api.onchange('company_size_max')
    def _onchange_company_size_max(self):
        if self.company_size_max < self.company_size_min:
            self.company_size_max = self.company_size_min

    def _prepare_iap_payload(self):
        """
        This will prepare the data to send to the server
        """
        self.ensure_one()
        payload = {
            'lead_number': self.lead_number,
            'search_type': self.search_type,
            'countries': self.country_ids.mapped('code')
        }
        if self.state_ids:
            payload['states'] = self.state_ids.mapped('code')
        if self.filter_on_size:
            payload.update({
                'company_size_min': self.company_size_min,
                'company_size_max': self.company_size_max
            })
        if self.industry_ids:
            payload['industry_ids'] = self.industry_ids.mapped('reveal_id')
        if self.search_type == 'people':
            payload.update({
                'contact_number': self.contact_number,
                'contact_filter_type': self.contact_filter_type
            })
            if self.contact_filter_type == 'role':
                payload.update({
                    'preferred_role': self.preferred_role_id.reveal_id,
                    'other_roles': self.role_ids.mapped('reveal_id')
                })
            elif self.contact_filter_type == 'seniority':
                payload['seniority'] = self.seniority_id.reveal_id
        return payload

    def _perform_request(self):
        """
        This will perform the request and create the corresponding leads.
        The user will be notified if he hasn't enough credits.
        """
        server_payload = self._prepare_iap_payload()
        reveal_account = self.env['iap.account'].get('reveal')
        dbuuid = self.env['ir.config_parameter'].sudo().get_param(
            'database.uuid')
        endpoint = self.env['ir.config_parameter'].sudo().get_param(
            'reveal.endpoint',
            DEFAULT_ENDPOINT) + '/iap/clearbit/1/lead_mining_request'
        params = {
            'account_token': reveal_account.account_token,
            'dbuuid': dbuuid,
            'data': server_payload
        }
        try:
            response = jsonrpc(endpoint, params=params, timeout=300)
            return response['data']
        except InsufficientCreditError as e:
            self.error = 'Insufficient credits. Recharge your account and retry.'
            self.state = 'error'
            self._cr.commit()
            raise e

    def _create_leads_from_response(self, result):
        """ This method will get the response from the service and create the leads accordingly """
        self.ensure_one()
        lead_vals = []
        messages_to_post = {}
        for data in result:
            lead_vals.append(self._lead_vals_from_response(data))
            messages_to_post[data['company_data']['clearbit_id']] = self.env[
                'crm.iap.lead.helpers'].format_data_for_message_post(
                    data['company_data'], data.get('people_data'))
        leads = self.env['crm.lead'].create(lead_vals)
        for lead in leads:
            if messages_to_post.get(lead.reveal_id):
                lead.message_post_with_view(
                    'crm_iap_lead.lead_message_template',
                    values=messages_to_post[lead.reveal_id],
                    subtype_id=self.env.ref('mail.mt_note').id)

    # Methods responsible for format response data into valid eagle lead data
    @api.model
    def _lead_vals_from_response(self, data):
        self.ensure_one()
        company_data = data.get('company_data')
        people_data = data.get('people_data')
        lead_vals = self.env['crm.iap.lead.helpers'].lead_vals_from_response(
            self.lead_type, self.team_id.id, self.tag_ids.ids, self.user_id.id,
            company_data, people_data)
        lead_vals['lead_mining_request_id'] = self.id
        return lead_vals

    @api.model
    def get_empty_list_help(self, help):
        help_title = _('Create a Lead Mining Request')
        sub_title = _(
            'Generate new leads based on their country, industry, size, etc.')
        return '<p class="o_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (
            help_title, sub_title)

    def action_draft(self):
        self.ensure_one()
        self.name = _('New')
        self.state = 'draft'

    def action_submit(self):
        self.ensure_one()
        if self.name == _('New'):
            self.name = self.env['ir.sequence'].next_by_code(
                'crm.iap.lead.mining.request') or _('New')
        results = self._perform_request()
        if results:
            self._create_leads_from_response(results)
            self.state = 'done'
        if self.lead_type == 'lead':
            return self.action_get_lead_action()
        elif self.lead_type == 'opportunity':
            return self.action_get_opportunity_action()

    def action_get_lead_action(self):
        self.ensure_one()
        action = self.env.ref('crm.crm_lead_all_leads').read()[0]
        action['domain'] = [('id', 'in', self.lead_ids.ids),
                            ('type', '=', 'lead')]
        action['help'] = _("""<p class="o_view_nocontent_empty_folder">
            No leads found
        </p><p>
            No leads could be generated according to your search criteria
        </p>""")
        return action

    def action_get_opportunity_action(self):
        self.ensure_one()
        action = self.env.ref('crm.crm_lead_opportunities').read()[0]
        action['domain'] = [('id', 'in', self.lead_ids.ids),
                            ('type', '=', 'opportunity')]
        action['help'] = _("""<p class="o_view_nocontent_empty_folder">
            No opportunities found
        </p><p>
            No opportunities could be generated according to your search criteria
        </p>""")
        return action
Beispiel #6
0
class ProductTemplate(models.Model):
    _inherit = "product.template"

    taxes_id = fields.Many2many(
        'account.tax',
        'product_taxes_rel',
        'prod_id',
        'tax_id',
        help="Default taxes used when selling the product.",
        string='Customer Taxes',
        domain=[('type_tax_use', '=', 'sale')],
        default=lambda self: self.env.user.company_id.account_sale_tax_id)
    supplier_taxes_id = fields.Many2many(
        'account.tax',
        'product_supplier_taxes_rel',
        'prod_id',
        'tax_id',
        string='Vendor Taxes',
        help='Default taxes used when buying the product.',
        domain=[('type_tax_use', '=', 'purchase')],
        default=lambda self: self.env.user.company_id.account_purchase_tax_id)
    property_account_income_id = fields.Many2one(
        'account.account',
        company_dependent=True,
        string="Income Account",
        oldname="property_account_income",
        domain=[('deprecated', '=', False)],
        help=
        "Keep this field empty to use the default value from the product category."
    )
    property_account_expense_id = fields.Many2one(
        'account.account',
        company_dependent=True,
        string="Expense Account",
        oldname="property_account_expense",
        domain=[('deprecated', '=', False)],
        help=
        "Keep this field empty to use the default value from the product category. If anglo-saxon accounting with automated valuation method is configured, the expense account on the product category will be used."
    )

    @api.multi
    def _get_product_accounts(self):
        return {
            'income':
            self.property_account_income_id
            or self.categ_id.property_account_income_categ_id,
            'expense':
            self.property_account_expense_id
            or self.categ_id.property_account_expense_categ_id
        }

    @api.multi
    def _get_asset_accounts(self):
        res = {}
        res['stock_input'] = False
        res['stock_output'] = False
        return res

    @api.multi
    def get_product_accounts(self, fiscal_pos=None):
        accounts = self._get_product_accounts()
        if not fiscal_pos:
            fiscal_pos = self.env['account.fiscal.position']
        return fiscal_pos.map_accounts(accounts)
Beispiel #7
0
class CrmLead(models.Model):
    _inherit = "crm.lead"
    partner_latitude = fields.Float('Geo Latitude', digits=(16, 5))
    partner_longitude = fields.Float('Geo Longitude', digits=(16, 5))
    partner_assigned_id = fields.Many2one('res.partner', 'Assigned Partner', tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Partner this case has been forwarded/assigned to.", index=True)
    partner_declined_ids = fields.Many2many(
        'res.partner',
        'crm_lead_declined_partner',
        'lead_id',
        'partner_id',
        string='Partner not interested')
    date_assign = fields.Date('Partner Assignation Date', help="Last date this case was forwarded/assigned to a partner")

    def _merge_data(self, fields):
        fields += ['partner_latitude', 'partner_longitude', 'partner_assigned_id', 'date_assign']
        return super(CrmLead, self)._merge_data(fields)

    @api.onchange("partner_assigned_id")
    def onchange_assign_id(self):
        """This function updates the "assignation date" automatically, when manually assign a partner in the geo assign tab
        """
        partner_assigned = self.partner_assigned_id
        if not partner_assigned:
            self.date_assign = False
        else:
            self.date_assign = fields.Date.context_today(self)
            self.user_id = partner_assigned.user_id

    def assign_salesman_of_assigned_partner(self):
        salesmans_leads = {}
        for lead in self:
            if (lead.probability > 0 and lead.probability < 100) or lead.stage_id.sequence == 1:
                if lead.partner_assigned_id and lead.partner_assigned_id.user_id != lead.user_id:
                    salesmans_leads.setdefault(lead.partner_assigned_id.user_id.id, []).append(lead.id)

        for salesman_id, leads_ids in salesmans_leads.items():
            leads = self.browse(leads_ids)
            leads.write({'user_id': salesman_id})

    def action_assign_partner(self):
        return self.assign_partner(partner_id=False)

    def assign_partner(self, partner_id=False):
        partner_dict = {}
        res = False
        if not partner_id:
            partner_dict = self.search_geo_partner()
        for lead in self:
            if not partner_id:
                partner_id = partner_dict.get(lead.id, False)
            if not partner_id:
                tag_to_add = self.env.ref('website_crm_partner_assign.tag_portal_lead_partner_unavailable', False)
                lead.write({'tag_ids': [(4, tag_to_add.id, False)]})
                continue
            lead.assign_geo_localize(lead.partner_latitude, lead.partner_longitude)
            partner = self.env['res.partner'].browse(partner_id)
            if partner.user_id:
                lead.allocate_salesman(partner.user_id.ids, team_id=partner.team_id.id)
            values = {'date_assign': fields.Date.context_today(lead), 'partner_assigned_id': partner_id}
            lead.write(values)
        return res

    def assign_geo_localize(self, latitude=False, longitude=False):
        if latitude and longitude:
            self.write({
                'partner_latitude': latitude,
                'partner_longitude': longitude
            })
            return True
        # Don't pass context to browse()! We need country name in english below
        for lead in self:
            if lead.partner_latitude and lead.partner_longitude:
                continue
            if lead.country_id:
                result = self.env['res.partner']._geo_localize(
                    lead.street, lead.zip, lead.city,
                    lead.state_id.name, lead.country_id.name
                )
                if result:
                    lead.write({
                        'partner_latitude': result[0],
                        'partner_longitude': result[1]
                    })
        return True

    def search_geo_partner(self):
        Partner = self.env['res.partner']
        res_partner_ids = {}
        self.assign_geo_localize()
        for lead in self:
            partner_ids = []
            if not lead.country_id:
                continue
            latitude = lead.partner_latitude
            longitude = lead.partner_longitude
            if latitude and longitude:
                # 1. first way: in the same country, small area
                partner_ids = Partner.search([
                    ('partner_weight', '>', 0),
                    ('partner_latitude', '>', latitude - 2), ('partner_latitude', '<', latitude + 2),
                    ('partner_longitude', '>', longitude - 1.5), ('partner_longitude', '<', longitude + 1.5),
                    ('country_id', '=', lead.country_id.id),
                    ('id', 'not in', lead.partner_declined_ids.mapped('id')),
                ])

                # 2. second way: in the same country, big area
                if not partner_ids:
                    partner_ids = Partner.search([
                        ('partner_weight', '>', 0),
                        ('partner_latitude', '>', latitude - 4), ('partner_latitude', '<', latitude + 4),
                        ('partner_longitude', '>', longitude - 3), ('partner_longitude', '<', longitude + 3),
                        ('country_id', '=', lead.country_id.id),
                        ('id', 'not in', lead.partner_declined_ids.mapped('id')),
                    ])

                # 3. third way: in the same country, extra large area
                if not partner_ids:
                    partner_ids = Partner.search([
                        ('partner_weight', '>', 0),
                        ('partner_latitude', '>', latitude - 8), ('partner_latitude', '<', latitude + 8),
                        ('partner_longitude', '>', longitude - 8), ('partner_longitude', '<', longitude + 8),
                        ('country_id', '=', lead.country_id.id),
                        ('id', 'not in', lead.partner_declined_ids.mapped('id')),
                    ])

                # 5. fifth way: anywhere in same country
                if not partner_ids:
                    # still haven't found any, let's take all partners in the country!
                    partner_ids = Partner.search([
                        ('partner_weight', '>', 0),
                        ('country_id', '=', lead.country_id.id),
                        ('id', 'not in', lead.partner_declined_ids.mapped('id')),
                    ])

                # 6. sixth way: closest partner whatsoever, just to have at least one result
                if not partner_ids:
                    # warning: point() type takes (longitude, latitude) as parameters in this order!
                    self._cr.execute("""SELECT id, distance
                                  FROM  (select id, (point(partner_longitude, partner_latitude) <-> point(%s,%s)) AS distance FROM res_partner
                                  WHERE active
                                        AND partner_longitude is not null
                                        AND partner_latitude is not null
                                        AND partner_weight > 0
                                        AND id not in (select partner_id from crm_lead_declined_partner where lead_id = %s)
                                        ) AS d
                                  ORDER BY distance LIMIT 1""", (longitude, latitude, lead.id))
                    res = self._cr.dictfetchone()
                    if res:
                        partner_ids = Partner.browse([res['id']])

                total_weight = 0
                toassign = []
                for partner in partner_ids:
                    total_weight += partner.partner_weight
                    toassign.append((partner.id, total_weight))

                random.shuffle(toassign)  # avoid always giving the leads to the first ones in db natural order!
                nearest_weight = random.randint(0, total_weight)
                for partner_id, weight in toassign:
                    if nearest_weight <= weight:
                        res_partner_ids[lead.id] = partner_id
                        break
        return res_partner_ids

    def partner_interested(self, comment=False):
        message = _('<p>I am interested by this lead.</p>')
        if comment:
            message += '<p>%s</p>' % comment
        for lead in self:
            lead.message_post(body=message)
            lead.sudo().convert_opportunity(lead.partner_id.id)  # sudo required to convert partner data

    def partner_desinterested(self, comment=False, contacted=False, spam=False):
        if contacted:
            message = '<p>%s</p>' % _('I am not interested by this lead. I contacted the lead.')
        else:
            message = '<p>%s</p>' % _('I am not interested by this lead. I have not contacted the lead.')
        partner_ids = self.env['res.partner'].search(
            [('id', 'child_of', self.env.user.partner_id.commercial_partner_id.id)])
        self.message_unsubscribe(partner_ids=partner_ids.ids)
        if comment:
            message += '<p>%s</p>' % comment
        self.message_post(body=message)
        values = {
            'partner_assigned_id': False,
        }

        if spam:
            tag_spam = self.env.ref('website_crm_partner_assign.tag_portal_lead_is_spam', False)
            if tag_spam and tag_spam not in self.tag_ids:
                values['tag_ids'] = [(4, tag_spam.id, False)]
        if partner_ids:
            values['partner_declined_ids'] = [(4, p, 0) for p in partner_ids.ids]
        self.sudo().write(values)

    def update_lead_portal(self, values):
        self.check_access_rights('write')
        for lead in self:
            lead_values = {
                'planned_revenue': values['planned_revenue'],
                'probability': values['probability'],
                'priority': values['priority'],
                'date_deadline': values['date_deadline'] or False,
            }
            # As activities may belong to several users, only the current portal user activity
            # will be modified by the portal form. If no activity exist we create a new one instead
            # that we assign to the portal user.

            user_activity = lead.sudo().activity_ids.filtered(lambda activity: activity.user_id == self.env.user)[:1]
            if values['activity_date_deadline']:
                if user_activity:
                    user_activity.sudo().write({
                        'activity_type_id': values['activity_type_id'],
                        'summary': values['activity_summary'],
                        'date_deadline': values['activity_date_deadline'],
                    })
                else:
                    self.env['mail.activity'].sudo().create({
                        'res_model_id': self.env.ref('crm.model_crm_lead').id,
                        'res_id': lead.id,
                        'user_id': self.env.user.id,
                        'activity_type_id': values['activity_type_id'],
                        'summary': values['activity_summary'],
                        'date_deadline': values['activity_date_deadline'],
                    })
            lead.write(lead_values)

    @api.model
    def create_opp_portal(self, values):
        if not (self.env.user.partner_id.grade_id or self.env.user.commercial_partner_id.grade_id):
            raise AccessDenied()
        user = self.env.user
        self = self.sudo()
        if not (values['contact_name'] and values['description'] and values['title']):
            return {
                'errors': _('All fields are required !')
            }
        tag_own = self.env.ref('website_crm_partner_assign.tag_portal_lead_own_opp', False)
        values = {
            'contact_name': values['contact_name'],
            'name': values['title'],
            'description': values['description'],
            'priority': '2',
            'partner_assigned_id': user.commercial_partner_id.id,
        }
        if tag_own:
            values['tag_ids'] = [(4, tag_own.id, False)]

        lead = self.create(values)
        lead.assign_salesman_of_assigned_partner()
        lead.convert_opportunity(lead.partner_id.id)
        return {
            'id': lead.id
        }

    #
    #   DO NOT FORWARD PORT IN MASTER
    #   instead, crm.lead should implement portal.mixin
    #
    def get_access_action(self, access_uid=None):
        """ Instead of the classic form view, redirect to the online document for
        portal users or if force_website=True in the context. """
        self.ensure_one()

        user, record = self.env.user, self
        if access_uid:
            try:
                record.check_access_rights('read')
                record.check_access_rule("read")
            except AccessError:
                return super(CrmLead, self).get_access_action(access_uid)
            user = self.env['res.users'].sudo().browse(access_uid)
            record = self.with_user(user)
        if user.share or self.env.context.get('force_website'):
            try:
                record.check_access_rights('read')
                record.check_access_rule('read')
            except AccessError:
                pass
            else:
                return {
                    'type': 'ir.actions.act_url',
                    'url': '/my/opportunity/%s' % record.id,
                }
        return super(CrmLead, self).get_access_action(access_uid)
Beispiel #8
0
class SaleOrder(models.Model):
    _inherit = "sale.order"

    website_id = fields.Many2one("website",
                                 "Online Order Website",
                                 readonly=True)
class Inventory(models.Model):
    _name = "stock.inventory"
    _description = "Inventory"
    _order = "date desc, id desc"

    name = fields.Char(
        'Inventory Reference', default="Inventory",
        readonly=True, required=True,
        states={'draft': [('readonly', False)]})
    date = fields.Datetime(
        'Inventory Date',
        readonly=True, required=True,
        default=fields.Datetime.now,
        help="If the inventory adjustment is not validated, date at which the theoritical quantities have been checked.\n"
             "If the inventory adjustment is validated, date at which the inventory adjustment has been validated.")
    line_ids = fields.One2many(
        'stock.inventory.line', 'inventory_id', string='Inventories',
        copy=False, readonly=False,
        states={'done': [('readonly', True)]})
    move_ids = fields.One2many(
        'stock.move', 'inventory_id', string='Created Moves',
        states={'done': [('readonly', True)]})
    state = fields.Selection(string='Status', selection=[
        ('draft', 'Draft'),
        ('cancel', 'Cancelled'),
        ('confirm', 'In Progress'),
        ('done', 'Validated')],
        copy=False, index=True, readonly=True,
        default='draft')
    company_id = fields.Many2one(
        'res.company', 'Company',
        readonly=True, index=True, required=True,
        states={'draft': [('readonly', False)]},
        default=lambda self: self.env.company)
    location_ids = fields.Many2many(
        'stock.location', string='Locations',
        readonly=True, check_company=True,
        states={'draft': [('readonly', False)]},
        domain="[('company_id', '=', company_id), ('usage', 'in', ['internal', 'transit'])]")
    product_ids = fields.Many2many(
        'product.product', string='Products', check_company=True,
        domain="[('type', '=', 'product'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", readonly=True,
        states={'draft': [('readonly', False)]},
        help="Specify Products to focus your inventory on particular Products.")
    start_empty = fields.Boolean('Empty Inventory',
        help="Allows to start with an empty inventory.")
    prefill_counted_quantity = fields.Selection(string='Counted Quantities',
        help="Allows to start with prefill counted quantity for each lines or "
        "with all counted quantity set to zero.", default='counted',
        selection=[('counted', 'Default to stock on hand'), ('zero', 'Default to zero')])

    @api.onchange('company_id')
    def _onchange_company_id(self):
        # If the multilocation group is not active, default the location to the one of the main
        # warehouse.
        if not self.user_has_groups('stock.group_stock_multi_locations'):
            warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.company_id.id)], limit=1)
            if warehouse:
                self.location_ids = warehouse.lot_stock_id

    def copy_data(self, default=None):
        name = _("%s (copy)") % (self.name)
        default = dict(default or {}, name=name)
        return super(Inventory, self).copy_data(default)

    def unlink(self):
        for inventory in self:
            if (inventory.state not in ('draft', 'cancel')
               and not self.env.context.get(MODULE_UNINSTALL_FLAG, False)):
                raise UserError(_('You can only delete a draft inventory adjustment. If the inventory adjustment is not done, you can cancel it.'))
        return super(Inventory, self).unlink()

    def action_validate(self):
        if not self.exists():
            return
        self.ensure_one()
        if not self.user_has_groups('stock.group_stock_manager'):
            raise UserError(_("Only a stock manager can validate an inventory adjustment."))
        if self.state != 'confirm':
            raise UserError(_(
                "You can't validate the inventory '%s', maybe this inventory " +
                "has been already validated or isn't ready.") % (self.name))
        inventory_lines = self.line_ids.filtered(lambda l: l.product_id.tracking in ['lot', 'serial'] and not l.prod_lot_id and l.theoretical_qty != l.product_qty)
        lines = self.line_ids.filtered(lambda l: float_compare(l.product_qty, 1, precision_rounding=l.product_uom_id.rounding) > 0 and l.product_id.tracking == 'serial' and l.prod_lot_id)
        if inventory_lines and not lines:
            wiz_lines = [(0, 0, {'product_id': product.id, 'tracking': product.tracking}) for product in inventory_lines.mapped('product_id')]
            wiz = self.env['stock.track.confirmation'].create({'inventory_id': self.id, 'tracking_line_ids': wiz_lines})
            return {
                'name': _('Tracked Products in Inventory Adjustment'),
                'type': 'ir.actions.act_window',
                'view_mode': 'form',
                'views': [(False, 'form')],
                'res_model': 'stock.track.confirmation',
                'target': 'new',
                'res_id': wiz.id,
            }
        self._action_done()
        self.line_ids._check_company()
        self._check_company()
        return True

    def _action_done(self):
        negative = next((line for line in self.mapped('line_ids') if line.product_qty < 0 and line.product_qty != line.theoretical_qty), False)
        if negative:
            raise UserError(_('You cannot set a negative product quantity in an inventory line:\n\t%s - qty: %s') % (negative.product_id.name, negative.product_qty))
        self.action_check()
        self.write({'state': 'done'})
        self.post_inventory()
        return True

    def post_inventory(self):
        # The inventory is posted as a single step which means quants cannot be moved from an internal location to another using an inventory
        # as they will be moved to inventory loss, and other quants will be created to the encoded quant location. This is a normal behavior
        # as quants cannot be reuse from inventory location (users can still manually move the products before/after the inventory if they want).
        self.mapped('move_ids').filtered(lambda move: move.state != 'done')._action_done()

    def action_check(self):
        """ Checks the inventory and computes the stock move to do """
        # tde todo: clean after _generate_moves
        for inventory in self.filtered(lambda x: x.state not in ('done','cancel')):
            # first remove the existing stock moves linked to this inventory
            inventory.mapped('move_ids').unlink()
            inventory.line_ids._generate_moves()

    def action_cancel_draft(self):
        self.mapped('move_ids')._action_cancel()
        self.line_ids.unlink()
        self.write({'state': 'draft'})

    def action_start(self):
        self.ensure_one()
        self._action_start()
        self._check_company()
        return self.action_open_inventory_lines()

    def _action_start(self):
        """ Confirms the Inventory Adjustment and generates its inventory lines
        if its state is draft and don't have already inventory lines (can happen
        with demo data or tests).
        """
        for inventory in self:
            if inventory.state != 'draft':
                continue
            vals = {
                'state': 'confirm',
                'date': fields.Datetime.now()
            }
            if not inventory.line_ids and not inventory.start_empty:
                self.env['stock.inventory.line'].create(inventory._get_inventory_lines_values())
            inventory.write(vals)

    def action_open_inventory_lines(self):
        self.ensure_one()
        action = {
            'type': 'ir.actions.act_window',
            'views': [(self.env.ref('stock.stock_inventory_line_tree2').id, 'tree')],
            'view_mode': 'tree',
            'name': _('Inventory Lines'),
            'res_model': 'stock.inventory.line',
        }
        context = {
            'default_is_editable': True,
            'default_inventory_id': self.id,
            'default_company_id': self.company_id.id,
        }
        # Define domains and context
        domain = [
            ('inventory_id', '=', self.id),
            ('location_id.usage', 'in', ['internal', 'transit'])
        ]
        if self.location_ids:
            context['default_location_id'] = self.location_ids[0].id
            if len(self.location_ids) == 1:
                if not self.location_ids[0].child_ids:
                    context['readonly_location_id'] = True

        if self.product_ids:
            if len(self.product_ids) == 1:
                context['default_product_id'] = self.product_ids[0].id

        action['context'] = context
        action['domain'] = domain
        return action

    def action_view_related_move_lines(self):
        self.ensure_one()
        domain = [('move_id', 'in', self.move_ids.ids)]
        action = {
            'name': _('Product Moves'),
            'type': 'ir.actions.act_window',
            'res_model': 'stock.move.line',
            'view_type': 'list',
            'view_mode': 'list,form',
            'domain': domain,
        }
        return action

    def _get_inventory_lines_values(self):
        # TDE CLEANME: is sql really necessary ? I don't think so
        locations = self.env['stock.location']
        if self.location_ids:
            locations = self.env['stock.location'].search([('id', 'child_of', self.location_ids.ids)])
        else:
            locations = self.env['stock.location'].search([('company_id', '=', self.company_id.id), ('usage', 'in', ['internal', 'transit'])])
        domain = ' location_id in %s AND quantity != 0 AND active = TRUE'
        args = (tuple(locations.ids),)

        vals = []
        Product = self.env['product.product']
        # Empty recordset of products available in stock_quants
        quant_products = self.env['product.product']

        # If inventory by company
        if self.company_id:
            domain += ' AND company_id = %s'
            args += (self.company_id.id,)
        if self.product_ids:
            domain += ' AND product_id in %s'
            args += (tuple(self.product_ids.ids),)

        self.env['stock.quant'].flush(['company_id', 'product_id', 'quantity', 'location_id', 'lot_id', 'package_id', 'owner_id'])
        self.env['product.product'].flush(['active'])
        self.env.cr.execute("""SELECT product_id, sum(quantity) as product_qty, location_id, lot_id as prod_lot_id, package_id, owner_id as partner_id
            FROM stock_quant
            LEFT JOIN product_product
            ON product_product.id = stock_quant.product_id
            WHERE %s
            GROUP BY product_id, location_id, lot_id, package_id, partner_id """ % domain, args)

        for product_data in self.env.cr.dictfetchall():
            product_data['company_id'] = self.company_id.id
            product_data['inventory_id'] = self.id
            # replace the None the dictionary by False, because falsy values are tested later on
            for void_field in [item[0] for item in product_data.items() if item[1] is None]:
                product_data[void_field] = False
            product_data['theoretical_qty'] = product_data['product_qty']
            if self.prefill_counted_quantity == 'zero':
                product_data['product_qty'] = 0
            if product_data['product_id']:
                product_data['product_uom_id'] = Product.browse(product_data['product_id']).uom_id.id
                quant_products |= Product.browse(product_data['product_id'])
            vals.append(product_data)
        return vals
Beispiel #10
0
class UtmCampaign(models.Model):
    _inherit = 'utm.campaign'
    _description = 'UTM Campaign'

    quotation_count = fields.Integer('Quotation Count',
                                     groups='sales_team.group_sale_salesman',
                                     compute="_compute_quotation_count")
    invoiced_amount = fields.Integer(
        default=0,
        compute="_compute_sale_invoiced_amount",
        string="Revenues generated by the campaign")
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True,
                                 states={
                                     'draft': [('readonly', False)],
                                     'refused': [('readonly', False)]
                                 },
                                 default=lambda self: self.env.company)
    currency_id = fields.Many2one('res.currency',
                                  related='company_id.currency_id',
                                  string='Currency')

    def _compute_quotation_count(self):
        quotation_data = self.env['sale.order'].read_group(
            [('campaign_id', 'in', self.ids)], ['campaign_id'],
            ['campaign_id'])
        data_map = {
            datum['campaign_id'][0]: datum['campaign_id_count']
            for datum in quotation_data
        }
        for campaign in self:
            campaign.quotation_count = data_map.get(campaign.id, 0)

    def _compute_sale_invoiced_amount(self):
        self.env['account.move.line'].flush(
            ['balance', 'move_id', 'account_id', 'exclude_from_invoice_tab'])
        self.env['account.move'].flush(['state', 'campaign_id', 'type'])
        query = """SELECT move.campaign_id, -SUM(line.balance) as price_subtotal
                    FROM account_move_line line
                    INNER JOIN account_move move ON line.move_id = move.id
                    WHERE move.state not in ('draft', 'cancel')
                        AND move.campaign_id IN %s
                        AND move.type IN ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')
                        AND line.account_id IS NOT NULL
                        AND NOT line.exclude_from_invoice_tab
                    GROUP BY move.campaign_id
                    """

        self._cr.execute(query, [tuple(self.ids)])
        query_res = self._cr.dictfetchall()

        campaigns = self.browse()
        for datum in query_res:
            campaign = self.browse(datum['campaign_id'])
            campaign.invoiced_amount = datum['price_subtotal']
            campaigns |= campaign
        for campaign in (self - campaigns):
            campaign.invoiced_amount = 0

    def action_redirect_to_quotations(self):
        action = self.env.ref(
            'sale.action_quotations_with_onboarding').read()[0]
        action['domain'] = [('campaign_id', '=', self.id)]
        action['context'] = {'default_campaign_id': self.id}
        return action

    def action_redirect_to_invoiced(self):
        action = self.env.ref('account.action_move_journal_line').read()[0]
        invoices = self.env['account.move'].search([('campaign_id', '=',
                                                     self.id)])
        action['context'] = {
            'create': False,
            'edit': False,
            'view_no_maturity': True
        }
        action['domain'] = [('id', 'in', invoices.ids),
                            ('type', 'in',
                             ('out_invoice', 'out_refund', 'in_invoice',
                              'in_refund', 'out_receipt', 'in_receipt')),
                            ('state', 'not in', ['draft', 'cancel'])]
        return action
Beispiel #11
0
class parent_payment_report_wiz(models.TransientModel):
    _name = 'parent.payment.report.wiz'

    parent_id = fields.Many2one('res.partner', string="Parent Name")
    date_from = fields.Date(string="Date From")
    date_to = fields.Date(sring="Date To")
    invoice_ids = fields.Many2many('account.invoice', string="Invoices")
    voucher_ids = fields.Many2many('account.voucher', string="Vouchers")
    date_today = fields.Date(string="Todays Date")
    past_balance = fields.Float(string="Past balance")
    user = fields.Many2one('res.users', string="Current User")
    #    running_balance_credit=fields.Float(string="Running balance after all creadit lines")
    current_running_balance = fields.Float(string="Current Running balance")
    total_credit_amount = fields.Float(string="Credit")
    total_debit_amount = fields.Float(string="Debit")

    _defaults = {'current_running_balance': 0.0}

    @api.multi
    def calc_current_running_balance(self, total, paid):
        self.current_running_balance = self.current_running_balance + total - paid
        return self.current_running_balance

    @api.multi
    def calc_credit(self, credit):
        if credit != 0.0:
            self.total_credit_amount += credit

    @api.multi
    def calc_debit(self, debit):
        if debit != 0.0:
            self.total_debit_amount += debit

    @api.multi
    def cal_total_balance(self, child, parent):
        total_balance = 0
        if len(child) > 0:
            for child_rec in child:
                total_balance += child_rec.credit
        if parent:
            total_balance += parent.credit
        return total_balance

    @api.multi
    def line_details(self, invoices, vouchers, from_date, to_date):
        invoice_voucher_list = []
        final_inv_vouch_list = []

        for inv in invoices:
            inv_date = datetime.datetime.strptime(
                inv.date_invoice, "%Y-%m-%d").strftime("%d/%m/%Y")
            invoice_voucher_list.append({
                'date': inv_date,
                'object': 'account.invoice',
                'rec_id': inv
            })

        for vouch in vouchers:
            vouch_date = datetime.datetime.strptime(
                vouch.date, "%Y-%m-%d").strftime("%d/%m/%Y")
            invoice_voucher_list.append({
                'date': vouch_date,
                'object': 'account.voucher',
                'rec_id': vouch
            })

        for data in invoice_voucher_list:
            if 'account.invoice' in data['object']:
                if data['rec_id'].type == 'out_refund':
                    for inv_line in data['rec_id'].invoice_line_ids:
                        final_inv_vouch_list.append({
                            'date':
                            data['date'],
                            'invoice_number':
                            data['rec_id'].number,
                            'student_name':
                            data['rec_id'].partner_id.name,
                            'description':
                            inv_line.product_id.name,
                            'debit':
                            0.0,
                            'credit':
                            inv_line.price_unit
                        })
                        self.calc_credit(inv_line.price_unit)
                else:
                    for inv_line in data['rec_id'].invoice_line_ids:
                        final_inv_vouch_list.append({
                            'date':
                            data['date'],
                            'invoice_number':
                            data['rec_id'].number,
                            'student_name':
                            data['rec_id'].partner_id.name,
                            'description':
                            inv_line.product_id.name,
                            'debit':
                            inv_line.price_unit,
                            'credit':
                            0.0
                        })
                        self.calc_debit(inv_line.price_unit)

                for pay_line in data['rec_id'].payment_ids:
                    pay_date = datetime.datetime.strptime(
                        pay_line.date, "%Y-%m-%d").strftime("%d/%m/%Y")
                    flag = False
                    for move_line in pay_line.move_id.line_id:
                        if move_line.debit == 0.00 and move_line.credit == 0.00:
                            flag = True
                    if flag:
                        for move_line in pay_line.move_id.line_id:
                            if pay_line.date >= from_date and pay_line.date <= to_date:
                                final_inv_vouch_list.append({
                                    'date':
                                    pay_date,
                                    'invoice_number':
                                    data['rec_id'].number,
                                    'student_name':
                                    data['rec_id'].partner_id.name,
                                    'description':
                                    pay_line.ref,
                                    'debit':
                                    move_line.debit,
                                    'credit':
                                    move_line.credit
                                })
                                if move_line.debit != 0.00:
                                    self.calc_debit(move_line.debit)
                                if move_line.credit != 0.00:
                                    self.calc_credit(move_line.credit)

                    if not flag:
                        voucher = False
                        for move_line in pay_line.move_id.line_id:
                            if move_line.reconcile_ref and move_line.name:
                                voucher = True
                        if voucher:
                            continue
                        else:
                            if pay_line.date >= from_date and pay_line.date <= to_date:
                                final_inv_vouch_list.append({
                                    'date':
                                    pay_date,
                                    'invoice_number':
                                    data['rec_id'].number,
                                    'description':
                                    pay_line.ref,
                                    'debit':
                                    0.0,
                                    'credit':
                                    pay_line.credit
                                })
                                self.calc_credit(pay_line.credit)

            elif 'account.voucher' in data['object']:
                if len(data['rec_id'].line_cr_ids) == 0 or (
                        len(data['rec_id'].line_cr_ids) != 0
                        and data['rec_id'].amount != 0.0):
                    if data['rec_id'].partner_id.is_student == True:
                        final_inv_vouch_list.append({
                            'date':
                            data['date'],
                            'invoice_number':
                            'Advance Payment',
                            'student_name':
                            data['rec_id'].partner_id.name,
                            'description':
                            data['rec_id'].number,
                            'debit':
                            0.0,
                            'credit':
                            data['rec_id'].amount
                        })
                    elif data['rec_id'].partner_id.is_parent == True:
                        final_inv_vouch_list.append({
                            'date':
                            data['date'],
                            'invoice_number':
                            'Advance Payment',
                            'student_name':
                            '',
                            'description':
                            data['rec_id'].number,
                            'debit':
                            0.0,
                            'credit':
                            data['rec_id'].amount
                        })
                    self.calc_credit(data['rec_id'].amount)
                    #code to add refund pdc entry on parent report
                    final_inv_vouch_list.append({
                        'date': data['date'],
                        'invoice_number': 'Refund Advance Payment',
                        'student_name': '',
                        'description': data['rec_id'].number,
                        'credit': 0.0,
                        'debit': data['rec_id'].amount
                    })
                    self.calc_debit(data['rec_id'].amount)
        final_inv_vouch_list.sort(
            key=lambda x: datetime.datetime.strptime(x['date'], '%d/%m/%Y'))
        return final_inv_vouch_list

    @api.multi
    def open_report(self):
        voucher_obj = self.env['account.voucher']
        invoice_obj = self.env['account.invoice']

        invoice_ids = []
        voucher_ids = []
        parent_vouchers = voucher_obj.search([('partner_id', '=',
                                               self.parent_id.id),
                                              ('state', '=', 'posted'),
                                              ('date', '>=', self.date_from),
                                              ('date', '<=', self.date_to)])
        if parent_vouchers:
            voucher_ids.append(parent_vouchers.id)
        for each1 in self.parent_id.chield1_ids.ids:
            invoices = invoice_obj.search([
                ('partner_id', '=', each1), ('state', 'in', ['open', 'paid']),
                ('date_invoice', '>=', self.date_from),
                ('date_invoice', '<=', self.date_to)
            ])
            vouchers = voucher_obj.search([('partner_id', '=', each1),
                                           ('state', '=', 'posted'),
                                           ('date', '>=', self.date_from),
                                           ('date', '<=', self.date_to)])
            for each in invoices:
                invoice_ids.append(each.id)
            for each in vouchers:
                voucher_ids.append(each.id)

        past_balance = 0.0
        move_line_ids = self.env['account.move.line'].search([
            '|', ('partner_id', '=', self.parent_id.id),
            ('partner_id', 'in', self.parent_id.chield1_ids.ids),
            ('date', '<', self.date_from),
            ('journal_id.type', '!=', 'situation'), '|',
            ('account_id.type', '=', 'receivable'),
            ('account_id.type', '=', 'payable')
        ])
        for move_line in move_line_ids:
            if move_line.credit == 0.0 and move_line.debit == 0.0:
                continue
            elif move_line.credit != 0.0:
                past_balance -= move_line.credit
            elif move_line.debit != 0.0:
                past_balance += move_line.debit

        self.user = self._uid
        self.past_balance = past_balance
        self.invoice_ids = [(6, 0, invoice_ids)]
        self.voucher_ids = [(6, 0, voucher_ids)]
        self.date_today = date.today()

        #        running_bal=0+self.past_balance
        #        for each in self.invoice_ids:
        #            for line in each.invoice_line:
        #                running_bal=running_bal+line.price_subtotal
        #
        #        self.running_balance_credit=running_bal

        value = {
            'type': 'ir.actions.report.xml',
            'report_name': 'edsys_edu_fee.report_parent_payment',
            'datas': {
                'model': 'parent.payment.report.wiz',
                'id': self.id,
                'ids': [self.id],
                'report_type': 'pdf',
                'report_file': 'edsys_edu_fee.report_parent_payment'
            },
            'name': self.parent_id.parent1_id + '_ Payment' + ' ' + ' Report',
            'nodestroy': True
        }
        return value

    @api.multi
    def send_parents_report(self):
        if self.parent_id:
            email = self.parent_id.email
            mail_obj = self.env['mail.mail']
            email_server = self.env['ir.mail_server']
            email_sender = email_server.search([])
            ir_model_data = self.env['ir.model.data']
            template_id = ir_model_data.get_object_reference(
                'edsys_edu_fee',
                'email_template_send_parent_report_by_email')[1]
            template_rec = self.env['mail.template'].browse(template_id)
            template_rec.write({
                'email_to': email,
                'email_from': email_sender.smtp_user,
                'email_cc': ''
            })
            template_rec.send_mail(self.id, force_send=True)
Beispiel #12
0
class EducationStudentClass(models.Model):
    _name = 'education.promotion'
    _description = 'Promote Student To Upper Class'
    _inherit = ['mail.thread']
    # _rec_name = 'class_assign_name'
    name = fields.Char('Promotion Register',
                       compute='get_promotion_register_name')
    assign_date = fields.Date(default=fields.Date.today)
    previous_batch = fields.Many2one("education.academic.year",
                                     'Previous Batch')
    class_id = fields.Many2one('education.class', string='Previous Class')
    promote_to = fields.Many2one('education.class',
                                 string='Promote To',
                                 compute='get_promoted_class')
    sequence = fields.Integer(related='class_id.sequence')
    student_list = fields.One2many('education.promotion.list',
                                   'connect_id',
                                   string="Students")
    admitted_class = fields.Many2one('education.class.division',
                                     string="From Section")
    new_batch = fields.Many2one("education.academic.year", 'New Batch')
    promote_section = fields.Many2one('education.class.division',
                                      string="To Section")
    assigned_by = fields.Many2one('res.users',
                                  string='Promoted By',
                                  default=lambda self: self.env.uid)
    state = fields.Selection([('draft', 'Draft'), ('done', 'Done')],
                             string='State',
                             required=True,
                             default='draft',
                             track_visibility='onchange')

    @api.onchange('class_id')
    def get_promoted_class(self):
        promote_class = self.env['education.class'].search([
            ('sequence', '=', (self.sequence + 1))
        ])
        for rec in promote_class:
            self.promote_to = rec.id

    @api.multi
    def get_promotion_register_name(self):
        for rec in self:
            rec.name = rec.admitted_class.name + '(assigned on ' + rec.assign_date + ')'

    @api.multi
    def promote_student(self):
        max_roll = self.env['education.class.history'].search(
            [('class_id', '=', self.promote_section.id)],
            order='roll_no desc',
            limit=1)
        if max_roll.roll_no:
            next_roll = max_roll.roll_no
        else:
            next_roll = 0

        for rec in self:

            if not self.student_list:
                raise ValidationError(_('No Student Lines'))
            com_sub = self.env['education.syllabus'].search([
                ('class_id', '=', rec.promote_to.id),
                ('academic_year', '=', rec.new_batch.id),
                ('divisional', '=', False),
                ('selection_type', '=', 'compulsory')
            ])
            elect_sub = self.env['education.syllabus'].search([
                ('class_id', '=', rec.promote_to.id),
                ('academic_year', '=', rec.new_batch.id),
                ('divisional', '=', True),
                ('division_id', '=', rec.promote_to.id),
                ('selection_type', '=', 'compulsory')
            ])
            com_subjects = []  # compulsory Subject List
            el_subjects = []  # Elective Subject List
            for sub in com_sub:
                com_subjects.append(sub.id)
            for sub in elect_sub:
                el_subjects.append(sub.id)
            for line in self.student_list:
                next_roll = next_roll + 1
                st = self.env['education.student'].search([
                    ('id', '=', line.student_id.id)
                ])
                st.roll_no = next_roll
                st.class_id = rec.promote_to.id
                line.roll_no = next_roll

                # create student history

                self.env['education.class.history'].create({
                    'academic_year_id':
                    rec.new_batch.id,
                    'class_id':
                    rec.promote_section.id,
                    'student_id':
                    line.student_id.id,
                    'roll_no':
                    next_roll,
                    'from_date':
                    rec.new_batch.ay_start_date,
                    'compulsory_subjects': [(6, 0, com_subjects)],
                    'selective_subjects': [(6, 0, el_subjects)]
                })

            self.write({'state': 'done'})

    @api.multi
    def unlink(self):
        """Return warning if the Record is in done state"""
        for rec in self:
            if rec.state == 'done':
                raise ValidationError(_("Cannot delete Record in Done state"))

    @api.multi
    def get_student_list(self):
        """returns the list of students applied to join the selected class"""
        for rec in self:
            for line in rec.student_list:
                line.unlink()
            # TODO apply filter not to get student assigned previously
            students = self.env['education.class.history'].search([
                ('class_id', '=', rec.admitted_class.id)
            ])
            if not students:
                raise ValidationError(_('No Students Available.. !'))
            values = []
            for stud in students:
                stud_line = {
                    'class_id': rec.class_id.id,
                    'student_id': stud.student_id.id,
                    'connect_id': rec.id,
                    'section_id': rec.admitted_class.id,
                    'roll_no': stud.roll_no
                }
                stud.assigned = True
                values.append(stud_line)
            for line in values:
                rec.student_line = self.env['education.promotion.list'].create(
                    line)
Beispiel #13
0
class MailMail(models.Model):
    """Add the mass mailing campaign data to mail"""
    _inherit = ['mail.mail']

    mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
    mailing_trace_ids = fields.One2many('mailing.trace', 'mail_mail_id', string='Statistics')

    @api.model
    def create(self, values):
        """ Override mail_mail creation to create an entry in mailing.trace """
        # TDE note: should be after 'all values computed', to have values (FIXME after merging other branch holding create refactoring)
        mail = super(MailMail, self).create(values)
        if values.get('mailing_trace_ids'):
            mail_sudo = mail.sudo()
            mail_sudo.mailing_trace_ids.write({'message_id': mail_sudo.message_id, 'state': 'outgoing'})
        return mail

    def _get_tracking_url(self):
        base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
        track_url = werkzeug.urls.url_join(
            base_url, 'mail/track/%(mail_id)s/blank.gif?%(params)s' % {
                'mail_id': self.id,
                'params': werkzeug.urls.url_encode({'db': self.env.cr.dbname})
            }
        )
        return '<img src="%s" alt=""/>' % track_url

    def _get_unsubscribe_url(self, email_to):
        base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
        url = werkzeug.urls.url_join(
            base_url, 'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % {
                'mailing_id': self.mailing_id.id,
                'params': werkzeug.urls.url_encode({
                    'db': self.env.cr.dbname,
                    'res_id': self.res_id,
                    'email': email_to,
                    'token': self.mailing_id._unsubscribe_token(
                        self.res_id, email_to),
                }),
            }
        )
        return url

    def _send_prepare_body(self):
        """ Override to add the tracking URL to the body and to add
        trace ID in shortened urls """
        # TDE: temporary addition (mail was parameter) due to semi-new-API
        self.ensure_one()
        body = super(MailMail, self)._send_prepare_body()

        if self.mailing_id and body and self.mailing_trace_ids:
            for match in re.findall(URL_REGEX, self.body_html):
                href = match[0]
                url = match[1]

                parsed = werkzeug.urls.url_parse(url, scheme='http')

                if parsed.scheme.startswith('http') and parsed.path.startswith('/r/'):
                    new_href = href.replace(url, url + '/m/' + str(self.mailing_trace_ids[0].id))
                    body = body.replace(href, new_href)

            # generate tracking URL
            tracking_url = self._get_tracking_url()
            if tracking_url:
                body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')

        body = self.env['mail.thread']._replace_local_links(body)

        return body

    def _send_prepare_values(self, partner=None):
        # TDE: temporary addition (mail was parameter) due to semi-new-API
        res = super(MailMail, self)._send_prepare_values(partner)
        base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url').rstrip('/')
        if self.mailing_id and res.get('body') and res.get('email_to'):
            emails = tools.email_split(res.get('email_to')[0])
            email_to = emails and emails[0] or False
            unsubscribe_url = self._get_unsubscribe_url(email_to)
            link_to_replace = base_url + '/unsubscribe_from_list'
            if link_to_replace in res['body']:
                res['body'] = res['body'].replace(link_to_replace, unsubscribe_url if unsubscribe_url else '#')
        return res

    def _postprocess_sent_message(self, success_pids, failure_reason=False, failure_type=None):
        mail_sent = not failure_type  # we consider that a recipient error is a failure with mass mailling and show them as failed
        for mail in self:
            if mail.mailing_id:
                if mail_sent is True and mail.mailing_trace_ids:
                    mail.mailing_trace_ids.write({'sent': fields.Datetime.now(), 'exception': False})
                elif mail_sent is False and mail.mailing_trace_ids:
                    mail.mailing_trace_ids.write({'exception': fields.Datetime.now(), 'failure_type': failure_type})
        return super(MailMail, self)._postprocess_sent_message(success_pids, failure_reason=failure_reason, failure_type=failure_type)
Beispiel #14
0
class SchoolClaim(models.Model):
    _name = 'school.claim'
    _inherit = ['portal.mixin', 'mail.thread', 'mail.activity.mixin']
    _description = 'Issue Report'

    school_issue_id = fields.Many2one(
        string='Issue', comodel_name='school.issue')
    school_issue_ids = fields.One2many(
        comodel_name='school.issue', string='Issues', inverse_name='claim_id')
    school_issue_count = fields.Integer(
        string='Issue Count', compute='_compute_issue_count')
    name = fields.Char(string='Description', required=True)
    issue_date = fields.Date(
        string='Date', required=True,
        default=lambda self: fields.Date.context_today(self))
    school_issue_type_id = fields.Many2one(
        string='School issue type', comodel_name='school.college.issue.type',
        required=True)
    school_id = fields.Many2one(
        comodel_name='res.partner', name='Education Center',
        related='school_issue_type_id.school_id', store=True)
    education_schedule_id = fields.Many2one(
        string='Class Schedule', comodel_name='education.schedule')
    education_group_id = fields.Many2one(
        string='Education Group', comodel_name='education.group')
    reported_id = fields.Many2one(
        string='Reported by', comodel_name='res.users', required=True,
        default=lambda self: self.env.user)
    student_id = fields.Many2one(
        string='Student', comodel_name='res.partner')
    description_facts = fields.Text(string='Description of the facts')
    sanction_specification = fields.Text(string='Sanction specification')
    educational_measure_ids = fields.Many2many(
        string="Educational measures",
        comodel_name='school.college.educational.measure',
        relation='rel_school_claims_educational_measures',
        column1='school_claim_id', column2='educational_measure_id',
        compute='_compute_educational_measure_ids', store=True)
    educational_measure_concretion = fields.Text(
        string='Educational measure concretion')
    state = fields.Selection(selection=[
        ('draft', 'Draft'),
        ('notified', 'Notified'),
        ('confirmed', 'Educational measure confirmed'),
        ('fulfill', 'Sanction fulfill'),
        ('closed', 'Closed'),
        ], string='Status', copy=False, index=True, default='draft',
        track_visibility='onchange')
    calendar_event_count = fields.Integer(
        string='Meetings Count', compute='_compute_calendar_events')

    @api.depends('school_issue_type_id',
                 'school_issue_type_id.educational_measure_ids')
    def _compute_educational_measure_ids(self):
        for claim in self.filtered(lambda c: c.school_issue_type_id):
            claim.educational_measure_ids = [
                (6, 0, claim.mapped(
                    'school_issue_type_id.educational_measure_ids').ids)]

    @api.multi
    def _compute_calendar_events(self):
        meeting_obj = self.env['calendar.event']
        res_model_id = self.env['ir.model']._get_id(self._name)
        for claim in self:
            claim.calendar_event_count = meeting_obj.search_count([
                ('res_id', '=', claim.id),
                ('res_model_id', '=', res_model_id),
            ])

    @api.model
    def create(self, values):
        claim = super(SchoolClaim, self).create(values)
        if claim.school_issue_type_id.notify_ids:
            claim.message_subscribe(
                list(claim.mapped('school_issue_type_id.notify_ids').ids))
        return claim

    @api.multi
    def open_calendar_event(self):
        action = self.env.ref('calendar.action_calendar_event')
        action_dict = action.read()[0] if action else {}
        res_model_id = self.env['ir.model']._get_id(self._name)
        domain = expression.AND([
            [('res_id', '=', self.id),
             ('res_model_id', '=', res_model_id)],
            safe_eval(action.domain or '[]')])
        action_dict.update({'domain': domain})
        return action_dict

    @api.multi
    def button_notified(self):
        self.ensure_one()
        gravity_scale = (
            self.school_issue_type_id.issue_type_id.gravity_scale_id)
        if int(gravity_scale.gravity_scale) <= 0:
            self.create_calendar_event()
        self.state = 'notified'

    @api.multi
    def button_confirmed(self):
        self.state = 'confirmed'

    @api.multi
    def button_fulfill(self):
        self.state = 'fulfill'

    @api.multi
    def button_closed(self):
        self.state = 'closed'

    def create_calendar_event(self):
        families = set(self.student_id.mapped(
            'child2_ids.family_id'))
        for family in families:
            lines = self.student_id.mapped(
                'child2_ids').filtered(
                lambda c: c.family_id.id == family.id)
            progenitors = lines.mapped('responsible_id')
            if progenitors:
                self._create_calendar_event_for_progenitor(family, progenitors)

    def _create_calendar_event_for_progenitor(self, family, progenitors):
        self.ensure_one()
        vals = self._catch_values_for_progenitor(family, progenitors)
        vals.update({
            'res_id': self.id,
            'res_model': self._name,
            'res_model_id': self.env['ir.model']._get_id(self._name),
        })
        self.env['calendar.event'].create(vals)

    def _catch_values_for_progenitor(self, family, progenitors):
        today = fields.Datetime.context_timestamp(
            self, fields.Datetime.today())
        label = self.env.ref(
            'issue_education.calendar_event_type_disciplinary')
        alarm = self.env.ref('calendar.alarm_notif_1')
        start = today.replace(hour=15) - today.utcoffset()
        stop = start + timedelta(minutes=30)
        progenitors |= self.reported_id.partner_id
        vals = {
            'name': self.name,
            'allday': False,
            'start': fields.Datetime.to_string(start),
            'stop': fields.Datetime.to_string(stop),
            'user_id': self.reported_id.id,
            'family_id': family.id,
            'student_id': self.student_id.id,
            'teacher_id': self.education_schedule_id.teacher_id.id,
            'alarm_ids': [(6, 0, alarm.ids)],
            'partner_ids': [(6, 0, progenitors.ids)],
            'categ_ids': [(6, 0, label.ids)],
        }
        return vals
Beispiel #15
0
class ResUsers(models.Model):
    _inherit = 'res.users'

    oauth_provider_id = fields.Many2one('auth.oauth.provider',
                                        string='OAuth Provider')
    oauth_uid = fields.Char(string='OAuth User ID',
                            help="Oauth Provider user_id",
                            copy=False)
    oauth_access_token = fields.Char(string='OAuth Access Token',
                                     readonly=True,
                                     copy=False)

    _sql_constraints = [
        ('uniq_users_oauth_provider_oauth_uid',
         'unique(oauth_provider_id, oauth_uid)',
         'OAuth UID must be unique per provider'),
    ]

    @api.model
    def _auth_oauth_rpc(self, endpoint, access_token):
        return requests.get(endpoint, params={
            'access_token': access_token
        }).json()

    @api.model
    def _auth_oauth_validate(self, provider, access_token):
        """ return the validation data corresponding to the access token """
        oauth_provider = self.env['auth.oauth.provider'].browse(provider)
        validation = self._auth_oauth_rpc(oauth_provider.validation_endpoint,
                                          access_token)
        if validation.get("error"):
            raise Exception(validation['error'])
        if oauth_provider.data_endpoint:
            data = self._auth_oauth_rpc(oauth_provider.data_endpoint,
                                        access_token)
            validation.update(data)
        return validation

    @api.model
    def _generate_signup_values(self, provider, validation, params):
        oauth_uid = validation['user_id']
        email = validation.get('email',
                               'provider_%s_user_%s' % (provider, oauth_uid))
        name = validation.get('name', email)
        return {
            'name': name,
            'login': email,
            'email': email,
            'oauth_provider_id': provider,
            'oauth_uid': oauth_uid,
            'oauth_access_token': params['access_token'],
            'active': True,
        }

    @api.model
    def _auth_oauth_signin(self, provider, validation, params):
        """ retrieve and sign in the user corresponding to provider and validated access token
            :param provider: oauth provider id (int)
            :param validation: result of validation of access token (dict)
            :param params: oauth parameters (dict)
            :return: user login (str)
            :raise: AccessDenied if signin failed

            This method can be overridden to add alternative signin methods.
        """
        oauth_uid = validation['user_id']
        try:
            oauth_user = self.search([("oauth_uid", "=", oauth_uid),
                                      ('oauth_provider_id', '=', provider)])
            if not oauth_user:
                raise AccessDenied()
            assert len(oauth_user) == 1
            oauth_user.write({'oauth_access_token': params['access_token']})
            return oauth_user.login
        except AccessDenied as access_denied_exception:
            if self.env.context.get('no_user_creation'):
                return None
            state = json.loads(params['state'])
            token = state.get('t')
            values = self._generate_signup_values(provider, validation, params)
            try:
                _, login, _ = self.signup(values, token)
                return login
            except (SignupError, UserError):
                raise access_denied_exception

    @api.model
    def auth_oauth(self, provider, params):
        # Advice by Google (to avoid Confused Deputy Problem)
        # if validation.audience != OUR_CLIENT_ID:
        #   abort()
        # else:
        #   continue with the process
        access_token = params.get('access_token')
        validation = self._auth_oauth_validate(provider, access_token)
        # required check
        if not validation.get('user_id'):
            # Workaround: facebook does not send 'user_id' in Open Graph Api
            if validation.get('id'):
                validation['user_id'] = validation['id']
            else:
                raise AccessDenied()

        # retrieve and sign in user
        login = self._auth_oauth_signin(provider, validation, params)
        if not login:
            raise AccessDenied()
        # return user credentials
        return (self.env.cr.dbname, login, access_token)

    def _check_credentials(self, password):
        try:
            return super(ResUsers, self)._check_credentials(password)
        except AccessDenied:
            res = self.sudo().search([('id', '=', self.env.uid),
                                      ('oauth_access_token', '=', password)])
            if not res:
                raise

    def _get_session_token_fields(self):
        return super(ResUsers, self)._get_session_token_fields() | {
            'oauth_access_token'
        }
class InventoryLine(models.Model):
    _name = "stock.inventory.line"
    _description = "Inventory Line"
    _order = "product_id, inventory_id, location_id, prod_lot_id"

    @api.model
    def _domain_location_id(self):
        if self.env.context.get('active_model') == 'stock.inventory':
            inventory = self.env['stock.inventory'].browse(self.env.context.get('active_id'))
            if inventory.exists() and inventory.location_ids:
                return "[('company_id', '=', company_id), ('usage', 'in', ['internal', 'transit']), ('id', 'child_of', %s)]" % inventory.location_ids.ids
        return "[('company_id', '=', company_id), ('usage', 'in', ['internal', 'transit'])]"

    @api.model
    def _domain_product_id(self):
        if self.env.context.get('active_model') == 'stock.inventory':
            inventory = self.env['stock.inventory'].browse(self.env.context.get('active_id'))
            if inventory.exists() and len(inventory.product_ids) > 1:
                return "[('type', '=', 'product'), '|', ('company_id', '=', False), ('company_id', '=', company_id), ('id', 'in', %s)]" % inventory.product_ids.ids
        return "[('type', '=', 'product'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]"

    is_editable = fields.Boolean(help="Technical field to restrict the edition.")
    inventory_id = fields.Many2one(
        'stock.inventory', 'Inventory', check_company=True,
        index=True, ondelete='cascade')
    partner_id = fields.Many2one('res.partner', 'Owner', check_company=True)
    product_id = fields.Many2one(
        'product.product', 'Product', check_company=True,
        domain=lambda self: self._domain_product_id(),
        index=True, required=True)
    product_uom_id = fields.Many2one(
        'uom.uom', 'Product Unit of Measure',
        required=True, readonly=True)
    product_qty = fields.Float(
        'Counted Quantity',
        digits='Product Unit of Measure', default=0)
    categ_id = fields.Many2one(related='product_id.categ_id', store=True)
    location_id = fields.Many2one(
        'stock.location', 'Location', check_company=True,
        domain=lambda self: self._domain_location_id(),
        index=True, required=True)
    package_id = fields.Many2one(
        'stock.quant.package', 'Pack', index=True, check_company=True,
        domain="[('location_id', '=', location_id)]",
    )
    prod_lot_id = fields.Many2one(
        'stock.production.lot', 'Lot/Serial Number', check_company=True,
        domain="[('product_id','=',product_id), ('company_id', '=', company_id)]")
    company_id = fields.Many2one(
        'res.company', 'Company', related='inventory_id.company_id',
        index=True, readonly=True, store=True)
    state = fields.Selection('Status', related='inventory_id.state')
    theoretical_qty = fields.Float(
        'Theoretical Quantity',
        digits='Product Unit of Measure', readonly=True)
    difference_qty = fields.Float('Difference', compute='_compute_difference',
        help="Indicates the gap between the product's theoretical quantity and its newest quantity.",
        readonly=True, digits='Product Unit of Measure', search="_search_difference_qty")
    inventory_date = fields.Datetime('Inventory Date', readonly=True,
        default=fields.Datetime.now,
        help="Last date at which the On Hand Quantity has been computed.")
    outdated = fields.Boolean(string='Quantity outdated',
        compute='_compute_outdated', search='_search_outdated')
    product_tracking = fields.Selection('Tracking', related='product_id.tracking', readonly=True)

    @api.depends('product_qty', 'theoretical_qty')
    def _compute_difference(self):
        for line in self:
            line.difference_qty = line.product_qty - line.theoretical_qty

    @api.depends('inventory_date', 'product_id.stock_move_ids', 'theoretical_qty', 'product_uom_id.rounding')
    def _compute_outdated(self):
        grouped_quants = self.env['stock.quant'].read_group(
            [('product_id', 'in', self.product_id.ids), ('location_id', 'in', self.location_id.ids)],
            ['product_id', 'location_id', 'lot_id', 'package_id', 'owner_id', 'quantity:sum'],
            ['product_id', 'location_id', 'lot_id', 'package_id', 'owner_id'],
            lazy=False)
        quants = {
            (quant['product_id'][0],
            quant['location_id'][0],
            quant['lot_id'] and quant['lot_id'][0],
            quant['package_id'] and quant['package_id'][0],
            quant['owner_id'] and quant['owner_id'][0]): quant['quantity']
            for quant in grouped_quants
        }
        for line in self:
            if line.state == 'done' or not line.id:
                line.outdated = False
                continue
            qty = quants.get((
                line.product_id.id,
                line.location_id.id,
                line.prod_lot_id.id,
                line.package_id.id,
                line.partner_id.id,
                ), 0
            )
            if float_compare(qty, line.theoretical_qty, precision_rounding=line.product_uom_id.rounding) != 0:
                line.outdated = True
            else:
                line.outdated = False

    @api.onchange('product_id', 'location_id', 'product_uom_id', 'prod_lot_id', 'partner_id', 'package_id')
    def _onchange_quantity_context(self):
        product_qty = False
        if self.product_id:
            self.product_uom_id = self.product_id.uom_id
        if self.product_id and self.location_id and self.product_id.uom_id.category_id == self.product_uom_id.category_id:  # TDE FIXME: last part added because crash
            theoretical_qty = self.product_id.get_theoretical_quantity(
                self.product_id.id,
                self.location_id.id,
                lot_id=self.prod_lot_id.id,
                package_id=self.package_id.id,
                owner_id=self.partner_id.id,
                to_uom=self.product_uom_id.id,
            )
        else:
            theoretical_qty = 0
        # Sanity check on the lot.
        if self.prod_lot_id:
            if self.product_id.tracking == 'none' or self.product_id != self.prod_lot_id.product_id:
                self.prod_lot_id = False

        if self.prod_lot_id and self.product_id.tracking == 'serial':
            # We force `product_qty` to 1 for SN tracked product because it's
            # the only relevant value aside 0 for this kind of product.
            self.product_qty = 1
        elif self.product_id and float_compare(self.product_qty, self.theoretical_qty, precision_rounding=self.product_uom_id.rounding) == 0:
            # We update `product_qty` only if it equals to `theoretical_qty` to
            # avoid to reset quantity when user manually set it.
            self.product_qty = theoretical_qty
        self.theoretical_qty = theoretical_qty

    @api.model_create_multi
    def create(self, vals_list):
        """ Override to handle the case we create inventory line without
        `theoretical_qty` because this field is usually computed, but in some
        case (typicaly in tests), we create inventory line without trigger the
        onchange, so in this case, we set `theoretical_qty` depending of the
        product's theoretical quantity.
        Handles the same problem with `product_uom_id` as this field is normally
        set in an onchange of `product_id`.
        Finally, this override checks we don't try to create a duplicated line.
        """
        for values in vals_list:
            if 'theoretical_qty' not in values:
                theoretical_qty = self.env['product.product'].get_theoretical_quantity(
                    values['product_id'],
                    values['location_id'],
                    lot_id=values.get('prod_lot_id'),
                    package_id=values.get('package_id'),
                    owner_id=values.get('partner_id'),
                    to_uom=values.get('product_uom_id'),
                )
                values['theoretical_qty'] = theoretical_qty
            if 'product_id' in values and 'product_uom_id' not in values:
                values['product_uom_id'] = self.env['product.product'].browse(values['product_id']).uom_id.id
        res = super(InventoryLine, self).create(vals_list)
        res._check_no_duplicate_line()
        return res

    def write(self, vals):
        res = super(InventoryLine, self).write(vals)
        self._check_no_duplicate_line()
        return res

    def _check_no_duplicate_line(self):
        for line in self:
            domain = [
                ('id', '!=', line.id),
                ('product_id', '=', line.product_id.id),
                ('location_id', '=', line.location_id.id),
                ('partner_id', '=', line.partner_id.id),
                ('package_id', '=', line.package_id.id),
                ('prod_lot_id', '=', line.prod_lot_id.id),
                ('inventory_id', '=', line.inventory_id.id)]
            existings = self.search_count(domain)
            if existings:
                raise UserError(_("There is already one inventory adjustment line for this product,"
                                  " you should rather modify this one instead of creating a new one."))

    @api.constrains('product_id')
    def _check_product_id(self):
        """ As no quants are created for consumable products, it should not be possible do adjust
        their quantity.
        """
        for line in self:
            if line.product_id.type != 'product':
                raise ValidationError(_("You can only adjust storable products.") + '\n\n%s -> %s' % (line.product_id.display_name, line.product_id.type))

    def _get_move_values(self, qty, location_id, location_dest_id, out):
        self.ensure_one()
        return {
            'name': _('INV:') + (self.inventory_id.name or ''),
            'product_id': self.product_id.id,
            'product_uom': self.product_uom_id.id,
            'product_uom_qty': qty,
            'date': self.inventory_id.date,
            'company_id': self.inventory_id.company_id.id,
            'inventory_id': self.inventory_id.id,
            'state': 'confirmed',
            'restrict_partner_id': self.partner_id.id,
            'location_id': location_id,
            'location_dest_id': location_dest_id,
            'move_line_ids': [(0, 0, {
                'product_id': self.product_id.id,
                'lot_id': self.prod_lot_id.id,
                'product_uom_qty': 0,  # bypass reservation here
                'product_uom_id': self.product_uom_id.id,
                'qty_done': qty,
                'package_id': out and self.package_id.id or False,
                'result_package_id': (not out) and self.package_id.id or False,
                'location_id': location_id,
                'location_dest_id': location_dest_id,
                'owner_id': self.partner_id.id,
            })]
        }

    def _get_virtual_location(self):
        return self.product_id.with_context(force_company=self.company_id.id).property_stock_inventory

    def _generate_moves(self):
        vals_list = []
        for line in self:
            virtual_location = line._get_virtual_location()
            rounding = line.product_id.uom_id.rounding
            if float_is_zero(line.difference_qty, precision_rounding=rounding):
                continue
            if line.difference_qty > 0:  # found more than expected
                vals = line._get_move_values(line.difference_qty, virtual_location.id, line.location_id.id, False)
            else:
                vals = line._get_move_values(abs(line.difference_qty), line.location_id.id, virtual_location.id, True)
            vals_list.append(vals)
        return self.env['stock.move'].create(vals_list)

    def _refresh_inventory(self):
        return self[0].inventory_id.action_open_inventory_lines()

    def action_refresh_quantity(self):
        filtered_lines = self.filtered(lambda l: l.state != 'done')
        for line in filtered_lines:
            if line.outdated:
                quants = self.env['stock.quant']._gather(line.product_id, line.location_id, lot_id=line.prod_lot_id, package_id=line.package_id, owner_id=line.partner_id, strict=True)
                if quants.exists():
                    quantity = sum(quants.mapped('quantity'))
                    if line.theoretical_qty != quantity:
                        line.theoretical_qty = quantity
                else:
                    line.theoretical_qty = 0
                line.inventory_date = fields.Datetime.now()

    def action_reset_product_qty(self):
        """ Write `product_qty` to zero on the selected records. """
        impacted_lines = self.env['stock.inventory.line']
        for line in self:
            if line.state == 'done':
                continue
            impacted_lines |= line
        impacted_lines.write({'product_qty': 0})

    def _search_difference_qty(self, operator, value):
        if operator == '=':
            result = True
        elif operator == '!=':
            result = False
        else:
            raise NotImplementedError()
        lines = self.search([('inventory_id', '=', self.env.context.get('default_inventory_id'))])
        line_ids = lines.filtered(lambda line: float_is_zero(line.difference_qty, line.product_id.uom_id.rounding) == result).ids
        return [('id', 'in', line_ids)]

    def _search_outdated(self, operator, value):
        if operator != '=':
            if operator == '!=' and isinstance(value, bool):
                value = not value
            else:
                raise NotImplementedError()
        lines = self.search([('inventory_id', '=', self.env.context.get('default_inventory_id'))])
        line_ids = lines.filtered(lambda line: line.outdated == value).ids
        return [('id', 'in', line_ids)]
Beispiel #17
0
class EagleeduApplication(models.Model):
    _name = 'eagleedu.application'
    _description = 'This is Student Application Form'
    # _order = 'id desc'
    _inherit = ['mail.thread']

    application_no = fields.Char(string='Application No.', required=True, copy=False, readonly=True,
                       index=True, default=lambda self: _('New'))

    application_date = fields.Datetime('Application Date', default=lambda self: fields.datetime.now())  # , default=fields.Datetime.now, required=True
    name = fields.Char(string='Student Name', required=True, help="Enter Name of Student")
    st_name_b = fields.Char(string='Student Bangla Name')
    st_image = fields.Binary(string='Image', help="Provide the image of the Student")
    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")
    date_of_birth = fields.Date(string="Date Of birth", help="Enter your DOB")
    age = fields.Char(compute="get_student_age", string="Age", store=True, help="Enter your DOB")

    st_gender = fields.Selection([('male', 'Male'), ('female', 'Female'), ('other', 'Other')],
                                string='Gender', required=False, track_visibility='onchange',
                                help="Your Gender is ")
    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',
                                   help="Your Blood Group is ")
    st_passport_no = fields.Char(string="Passport No.", help="Proud to say my father is", required=False)
    nationality = fields.Many2one('res.country', string='Nationality', ondelete='restrict',default=19,
                                help="Select the Nationality")
    academic_year = fields.Many2one('eagleedu.academic.year', string='Academic Year',
                                help="Choose Academic year for which the admission is choosing")
    register_id = fields.Many2one('eagleedu.register', string="Admission Register", required=True,
                                      help="Enter the admission register Name")
    import_id=fields.Many2one('eagleedu.import.previous.student', string="Import Student")

# todo all Name in bangla unicode should auto fill and translate to bangla language
    # for translate in unicode
    # @api.onchange('name')
    # def set_st_name_b(self):
    #     self.st_name_b = self.name
    #



    #
    # @api.model
    # def create(self, vals):
    #     record = super(IrTranslation, self).create(vals)
    #     name = vals.get('name', False)  # name of the field to translate
    #     lang = vals.get('lang', False)  # creating record for this language
    #     if 'context' in dir(self.env):
    #         cur_lang = self.env.context.get('lang', False)  # current used language
    #         if name == 'eagleedu.application,st_father_name_b' and lang == cur_lang:
    #             langs = self.env['ir.translation']._get_languages()
    #             langs = [l[0] for l in langs if l[0] != cur_lang]  # installed languages
    #
    #             for l in langs:
    #                 if self.env.eagleedu.application.st_father_name_b:
    #                     t = self.env['ir.translation'].search([
    #                         ('lang', '=', l),
    #                         ('type', '=', 'model'),
    #                         ('name', '=', 'eagleedu.application,st_father_name_b')
    #                     ])
    #                     if t:
    #                         self.env['ir.translation'].create({
    #                             'lang': l,
    #                             'type': 'model',
    #                             'name': 'eagleedu.application,st_father_name_b',
    #                             'res_id': record.res_id,
    #                             'src': record.src,
    #                             'value': t.value,
    #                             'state': 'translated',
    #                         })
    #









    # def _get_default_ay(self, cr, uid, context=None):
    #     res = self.pool.get('eagleedu.academic.year').search(cr, uid, [('name', '=', academic_year)], context=context)
    #     return
    #     return res and res[0] or False
    #
    #
    # _defaults = {
    #     'academic_year': _get_default_ay,
    # }


    # user_id = fields.Many2one('res.users', 'User', default=lambda self: self.env.user)
    # application_date = fields.Datetime('Application Date', default=lambda self: fields.datetime.now())  # , default=fields.Datetime.now, required=True

    # self.env.ref('module_name.reference_record_id').id

    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= 'State / Division')
    country_id = fields.Many2one('res.country', string='Country', ondelete='restrict',default=19,
                                 help="Select the Country")
    if_same_address = fields.Boolean(string="Permanent Address same as above", default=True,
                                     help="Tick the field if the Present and permanent address is same")
    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_bd_division_id = fields.Many2one('eagleedu.bddivision', string='State / Division', help="Enter the City of District name")
    per_country_id = fields.Many2one('res.country', string='Country', ondelete='restrict', default=19,
                                     help="Select the Country")
    guardian_name = fields.Char(string="Guardian's Name", help="Proud to say my guardian is")
    guardian_relation = fields.Many2one('eagleedu.guardian.relation', string="Relation to Guardian",  required=False,
                                        help="Tell us the Relation toyour guardian")
    guardian_mobile = fields.Char(string="guardian's Mobile No", help="guardian's Mobile No")

    religious_id = fields.Many2one('eagleedu.religious', string="Religious", help="My Religion is ")
    class_id = fields.Many2one('eagleedu.class')
    academic_year = fields.Many2one('eagleedu.academic.year', string='Academic Year')
    group_division = fields.Many2one('eagleedu.group_division')

    student_id=fields.Char('Student Id')
    roll_no = fields.Integer('Roll No')
    section=fields.Char('Section')
    state = fields.Selection([('draft', 'Draft'), ('verification', 'Verify'),
                               ('approve', 'Approve'), ('done', 'Done')],
                              string='Status', required=True, default='draft', track_visibility='onchange')

    company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id)
    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")

    # document_count = fields.Integer(compute='_document_count', string='# Documents')
    verified_by = fields.Many2one('res.users', string='Verified by', help="The Document is verified by")




    @api.onchange('guardian_relation')
    def guardian_relation_changed(self):
        for rec in self:
            if rec.guardian_relation.name:
                if  rec.guardian_relation.name=='Father':
                    rec.guardian_mobile=rec.father_mobile
                    rec.guardian_name=rec.st_father_name
                elif  rec.guardian_relation.name=='Mother':
                    rec.guardian_mobile = rec.mother_mobile
                    rec.guardian_name = rec.st_mother_name



    # @api.depends('application_no', 'application_no.birthday', 'application_date')
    @api.depends('date_of_birth', 'application_date')
    def get_student_age(self):
        for rec in self:
            age = ''
            if rec.date_of_birth:
                end_data = rec.application_date or fields.Datetime.now()
                delta = relativedelta(end_data, rec.date_of_birth)
                if delta.years <= 25:
                    age = str(delta.years) + _(" Years  ") + str(delta.months) + _(" Month  ") + str(delta.days) + _(
                        " Days")
                else:
                    age = str(delta.years) + _(" Year")
            rec.age = age



    @api.model
    def create(self, vals):
        """Overriding the create method and assigning the the sequence for the record"""
        if vals.get('application_no', _('New')) == _('New'):
            vals['application_no'] = self.env['ir.sequence'].next_by_code('eagleedu.application') or _('New')
        res = super(EagleeduApplication, self).create(vals)
        return res

#    @api.model
    def send_to_verify(self):
        """Return the state to done if the documents are perfect"""
        for rec in self:
            rec.write({
                'state': 'verification'
            })


#    @api.model
    def application_verify(self):
        """Return the state to done if the documents are perfect"""
        for rec in self:
            rec.write({
                'state': 'approve'
            })

#todo show group division when select the class 1 to 8 only
    # def group_division_show(self):
    #     """Return the state to done if the documents are perfect"""
    #     for rec in self:
    #         rec.write({
    #             'state': 'approve'
    #         })


    # def get_group_name_auto(self):
    #     for rec in self:
    #         rec.name = str(rec.admitted_class.name) + '(Assign on ' + str(rec.assign_date) +')'
    #         #rec.name = rec.admitted_class.name #+ '(assigned on '+ rec.assign_date +')'


#    @api.model
    def create_student(self):
        """Create student from the application and data and return the student"""
        for rec in self:
            values = {
                'name': rec.name,
                'st_name_b': rec.st_name_b,
                'st_image': rec.st_image,
                'application_no': rec.id,
                'st_father_name': rec.st_father_name,
                'st_father_name_b': rec.st_father_name_b,
                'father_mobile': rec.father_mobile,
                'st_father_occupation': rec.st_father_occupation,
                'st_mother_name': rec.st_mother_name,
                'st_mother_name_b': rec.st_mother_name_b,
                'mother_mobile': rec.mother_mobile,
                'st_mother_occupation': rec.st_mother_occupation,
                'st_gender': rec.st_gender,
                'date_of_birth': rec.date_of_birth,
                'st_blood_group': rec.st_blood_group,
                'st_passport_no': rec.st_passport_no,
                'nationality': rec.nationality.id,
                'academic_year': rec.academic_year.id,
                'class_id': rec.class_id.id,
                'admission_class': rec.register_id.standard.id,
                'group_division': rec.group_division.id,
                'house_no': rec.house_no,
                'road_no': rec.road_no,
                'post_office': rec.post_office,
                'city': rec.city,
                'bd_division_id': rec.bd_division_id.id,
                'country_id': rec.country_id.id,
                'per_village': rec.per_village,
                'per_po': rec.per_po,
                'per_ps': rec.per_ps,
                'per_dist_id': rec.per_dist_id.id,
                'per_bd_division_id': rec.per_bd_division_id.id,
                'per_country_id': rec.per_country_id.id,
                'guardian_name': rec.guardian_name,
                'religious_id': rec.religious_id.id,
                # 'is_student': True,
                'student_id': rec.student_id,
                'roll_no': rec.roll_no,
                'application_no': rec.application_no,
            }
            student = self.env['eagleedu.student'].create(values)
            rec.write({
                'state': 'done'
            })
            return {
                'name': _('Student'),
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'eagleedu.student',
                'type': 'ir.actions.act_window',
                'res_id': student.id,
                'context': self.env.context
            }
Beispiel #18
0
class MailMessageSubtype(models.Model):
    """ Class holding subtype definition for messages. Subtypes allow to tune
        the follower subscription, allowing only some subtypes to be pushed
        on the Wall. """
    _name = 'mail.message.subtype'
    _description = 'Message subtypes'
    _order = 'sequence, id'

    name = fields.Char(
        'Message Type',
        required=True,
        translate=True,
        help='Message subtype gives a more precise type on the message, '
        'especially for system notifications. For example, it can be '
        'a notification related to a new record (New), or to a stage '
        'change in a process (Stage change). Message subtypes allow to '
        'precisely tune the notifications the user want to receive on its wall.'
    )
    description = fields.Text(
        'Description',
        translate=True,
        help='Description that will be added in the message posted for this '
        'subtype. If void, the name will be added instead.')
    internal = fields.Boolean(
        'Internal Only',
        help=
        'Messages with internal subtypes will be visible only by employees, aka members of base_user group'
    )
    parent_id = fields.Many2one(
        'mail.message.subtype',
        string='Parent',
        ondelete='set null',
        help=
        'Parent subtype, used for automatic subscription. This field is not '
        'correctly named. For example on a project, the parent_id of project '
        'subtypes refers to task-related subtypes.')
    relation_field = fields.Char(
        'Relation field',
        help='Field used to link the related model to the subtype model when '
        'using automatic subscription on a related document. The field '
        'is used to compute getattr(related_document.relation_field).')
    res_model = fields.Char(
        'Model',
        help=
        "Model the subtype applies to. If False, this subtype applies to all models."
    )
    default = fields.Boolean('Default',
                             default=True,
                             help="Activated by default when subscribing.")
    sequence = fields.Integer('Sequence',
                              default=1,
                              help="Used to order subtypes.")
    hidden = fields.Boolean('Hidden',
                            help="Hide the subtype in the follower options")

    @api.model
    def create(self, vals):
        self.clear_caches()
        return super(MailMessageSubtype, self).create(vals)

    def write(self, vals):
        self.clear_caches()
        return super(MailMessageSubtype, self).write(vals)

    def unlink(self):
        self.clear_caches()
        return super(MailMessageSubtype, self).unlink()

    @tools.ormcache('model_name')
    def _get_auto_subscription_subtypes(self, model_name):
        """ Return data related to auto subscription based on subtype matching.
        Example with tasks and project :

         * generic: discussion, res_model = False
         * task: new, res_model = project.task
         * project: task_new, parent_id = new, res_model = project.project, field = project_id

        Returned data

          * all_ids: all subtypes that are generic or related to task and project
          * def_ids: for task, default subtypes ids
          * int_ids: for task, internal-only default subtypes ids
          * parent: dict(parent subtype id, child subtype id), i.e. {task_new.id: new.id}
          * relation: dict(parent_model, relation_fields), i.e. {'project.project': ['project_id']}
        """
        all_ids, def_ids, int_ids, parent, relation = list(), list(), list(
        ), dict(), dict()
        subtypes = self.sudo().search([
            '|', '|', ('res_model', '=', False),
            ('res_model', '=', model_name),
            ('parent_id.res_model', '=', model_name)
        ])
        for subtype in subtypes:
            if not subtype.res_model or subtype.res_model == model_name:
                all_ids += subtype.ids
                if subtype.default:
                    def_ids += subtype.ids
            elif subtype.relation_field:
                parent[subtype.id] = subtype.parent_id.id
                relation.setdefault(subtype.res_model,
                                    set()).add(subtype.relation_field)
            if subtype.internal:
                int_ids += subtype.ids
        return all_ids, def_ids, int_ids, parent, relation

    @api.model
    def default_subtypes(self, model_name):
        """ Retrieve the default subtypes (all, internal, external) for the given model. """
        subtype_ids, internal_ids, external_ids = self._default_subtypes(
            model_name)
        return self.browse(subtype_ids), self.browse(
            internal_ids), self.browse(external_ids)

    @tools.ormcache('self.env.uid', 'self.env.su', 'model_name')
    def _default_subtypes(self, model_name):
        domain = [('default', '=', True), '|', ('res_model', '=', model_name),
                  ('res_model', '=', False)]
        subtypes = self.search(domain)
        internal = subtypes.filtered('internal')
        return subtypes.ids, internal.ids, (subtypes - internal).ids
class AccrualAccountingWizard(models.TransientModel):
    _name = 'account.accrual.accounting.wizard'
    _description = 'Create accrual entry.'

    date = fields.Date(required=True)
    company_id = fields.Many2one('res.company', required=True)
    account_type = fields.Selection([('income', 'Revenue'),
                                     ('expense', 'Expense')])
    active_move_line_ids = fields.Many2many('account.move.line')
    journal_id = fields.Many2one(
        'account.journal',
        required=True,
        readonly=False,
        domain="[('company_id', '=', company_id), ('type', '=', 'general')]",
        related="company_id.accrual_default_journal_id")
    expense_accrual_account = fields.Many2one(
        'account.account',
        readonly=False,
        domain=
        "[('company_id', '=', company_id), ('internal_type', 'not in', ('receivable', 'payable')), ('internal_group', '=', 'liability'), ('reconcile', '=', True)]",
        related="company_id.expense_accrual_account_id")
    revenue_accrual_account = fields.Many2one(
        'account.account',
        readonly=False,
        domain=
        "[('company_id', '=', company_id), ('internal_type', 'not in', ('receivable', 'payable')), ('internal_group', '=', 'asset'), ('reconcile', '=', True)]",
        related="company_id.revenue_accrual_account_id")
    percentage = fields.Float("Percentage", default=100.0)
    total_amount = fields.Monetary(compute="_compute_total_amount",
                                   currency_field='company_currency_id')
    company_currency_id = fields.Many2one('res.currency',
                                          related='company_id.currency_id')

    @api.constrains('percentage')
    def _constraint_percentage(self):
        for record in self:
            if not (0.0 < record.percentage <= 100.0):
                raise UserError(_("Percentage must be between 0 and 100"))

    @api.depends('percentage', 'active_move_line_ids')
    def _compute_total_amount(self):
        for record in self:
            record.total_amount = sum(
                record.active_move_line_ids.mapped(
                    lambda l: record.percentage * (l.debit + l.credit) / 100))

    @api.model
    def default_get(self, fields):
        if self.env.context.get(
                'active_model'
        ) != 'account.move.line' or not self.env.context.get('active_ids'):
            raise UserError(_('This can only be used on journal items'))
        rec = super(AccrualAccountingWizard, self).default_get(fields)
        active_move_line_ids = self.env['account.move.line'].browse(
            self.env.context['active_ids'])
        rec['active_move_line_ids'] = active_move_line_ids.ids

        if any(move.state != 'posted'
               for move in active_move_line_ids.mapped('move_id')):
            raise UserError(
                _('You can only change the period for posted journal items.'))
        if any(move_line.reconciled for move_line in active_move_line_ids):
            raise UserError(
                _('You can only change the period for items that are not yet reconciled.'
                  ))
        if any(line.account_id.user_type_id !=
               active_move_line_ids[0].account_id.user_type_id
               for line in active_move_line_ids):
            raise UserError(
                _('All accounts on the lines must be from the same type.'))
        if any(line.company_id != active_move_line_ids[0].company_id
               for line in active_move_line_ids):
            raise UserError(_('All lines must be from the same company.'))
        rec['company_id'] = active_move_line_ids[0].company_id.id
        account_types_allowed = self.env.ref(
            'account.data_account_type_expenses') + self.env.ref(
                'account.data_account_type_revenue') + self.env.ref(
                    'account.data_account_type_other_income')
        if active_move_line_ids[
                0].account_id.user_type_id not in account_types_allowed:
            raise UserError(
                _('You can only change the period for items in these types of accounts: '
                  ) + ", ".join(account_types_allowed.mapped('name')))
        rec['account_type'] = active_move_line_ids[
            0].account_id.user_type_id.internal_group
        return rec

    def amend_entries(self):
        # set the accrual account on the selected journal items
        accrual_account = self.revenue_accrual_account if self.account_type == 'income' else self.expense_accrual_account

        # Generate journal entries.
        move_data = {}
        for aml in self.active_move_line_ids:
            ref1 = _('Accrual Adjusting Entry (%s recognized) for invoice: %s'
                     ) % (self.percentage, aml.move_id.name)
            ref2 = _('Accrual Adjusting Entry (%s recognized) for invoice: %s'
                     ) % (100 - self.percentage, aml.move_id.name)
            move_data.setdefault(
                aml.move_id,
                (
                    [
                        # Values to create moves.
                        {
                            'date': self.date,
                            'ref': ref1,
                            'journal_id': self.journal_id.id,
                            'line_ids': [],
                        },
                        {
                            'date': aml.move_id.date,
                            'ref': ref2,
                            'journal_id': self.journal_id.id,
                            'line_ids': [],
                        },
                    ],
                    [
                        # Messages to log on the chatter.
                        (_('Accrual Adjusting Entry ({percent}% recognized) for invoice:'
                           ) +
                         ' <a href=# data-oe-model=account.move data-oe-id={id}>{name}</a>'
                         ).format(
                             percent=self.percentage,
                             id=aml.move_id.id,
                             name=aml.move_id.name,
                         ),
                        (_('Accrual Adjusting Entry ({percent}% recognized) for invoice:'
                           ) +
                         ' <a href=# data-oe-model=account.move data-oe-id={id}>{name}</a>'
                         ).format(
                             percent=100 - self.percentage,
                             id=aml.move_id.id,
                             name=aml.move_id.name,
                         ),
                    ]))

            reported_debit = aml.company_id.currency_id.round(
                (self.percentage / 100) * aml.debit)
            reported_credit = aml.company_id.currency_id.round(
                (self.percentage / 100) * aml.credit)
            if aml.currency_id:
                reported_amount_currency = aml.currency_id.round(
                    (self.percentage / 100) * aml.amount_currency)
            else:
                reported_amount_currency = 0.0

            move_data[aml.move_id][0][0]['line_ids'] += [
                (0, 0, {
                    'name': aml.name,
                    'debit': reported_debit,
                    'credit': reported_credit,
                    'amount_currency': reported_amount_currency,
                    'currency_id': aml.currency_id.id,
                    'account_id': aml.account_id.id,
                    'partner_id': aml.partner_id.id,
                }),
                (0, 0, {
                    'name': ref1,
                    'debit': reported_credit,
                    'credit': reported_debit,
                    'amount_currency': -reported_amount_currency,
                    'currency_id': aml.currency_id.id,
                    'account_id': accrual_account.id,
                    'partner_id': aml.partner_id.id,
                }),
            ]

            move_data[aml.move_id][0][1]['line_ids'] += [
                (0, 0, {
                    'name': aml.name,
                    'debit': aml.debit - reported_debit,
                    'credit': aml.credit - reported_credit,
                    'amount_currency':
                    aml.amount_currency - reported_amount_currency,
                    'currency_id': aml.currency_id.id,
                    'account_id': aml.account_id.id,
                    'partner_id': aml.partner_id.id,
                }),
                (0, 0, {
                    'name': ref2,
                    'debit': aml.credit - reported_credit,
                    'credit': aml.debit - reported_debit,
                    'amount_currency':
                    reported_amount_currency - aml.amount_currency,
                    'currency_id': aml.currency_id.id,
                    'account_id': accrual_account.id,
                    'partner_id': aml.partner_id.id,
                }),
            ]

        # Update the account of selected journal items.
        self.active_move_line_ids.write({'account_id': accrual_account.id})

        # When the percentage is 100%, the second move is not needed.
        if self.percentage < 100:
            move_vals = []
            log_messages = []
            for v in move_data.values():
                move_vals += v[0]
                log_messages += v[1]
        else:
            move_vals = [v[0][0] for k, v in move_data.items()]
            log_messages = [v[1][0] for k, v in move_data.items()]

        created_moves = self.env['account.move'].create(move_vals)
        created_moves.post()

        # Reconcile.
        index = 0
        for move in self.active_move_line_ids.mapped('move_id'):
            if self.percentage < 100:
                accrual_moves = created_moves[index:index + 2]
                index += 2
            else:
                accrual_moves = created_moves[index:index + 1]
                index += 1

            to_reconcile = self.active_move_line_ids.filtered(
                lambda line: line.move_id == move)
            to_reconcile += accrual_moves.mapped(
                'line_ids').filtered(lambda line: line.account_id ==
                                     accrual_account and not line.reconciled)
            to_reconcile.reconcile()

        # Log messages.
        for created_move, log_message in zip(created_moves, log_messages):
            created_move.message_post(body=log_message)

        # open the generated entries
        action = {
            'name':
            _('Generated Entries'),
            'domain': [('id', 'in', created_moves.ids)],
            'res_model':
            'account.move',
            'view_mode':
            'tree,form',
            'type':
            'ir.actions.act_window',
            'views': [(self.env.ref('account.view_move_tree').id, 'tree'),
                      (False, 'form')],
        }
        if len(created_moves) == 1:
            action.update({'view_mode': 'form', 'res_id': created_moves.id})
        return action
Beispiel #20
0
class ProductTemplate(models.Model):
    _inherit = 'product.template'

    service_policy = fields.Selection([
        ('ordered_timesheet', 'Ordered quantities'),
        ('delivered_timesheet', 'Timesheets on tasks'),
        ('delivered_manual', 'Milestones (manually set quantities on order)')
    ],
                                      string="Service Invoicing Policy",
                                      compute='_compute_service_policy',
                                      inverse='_inverse_service_policy')
    service_type = fields.Selection(selection_add=[
        ('timesheet', 'Timesheets on project (one fare per SO/Project)'),
    ])
    service_tracking = fields.Selection(
        [
            ('no', 'Don\'t create task'),
            ('task_global_project', 'Create a task in an existing project'),
            ('task_new_project', 'Create a task in a new project'),
            ('project_only', 'Create a new project but no task'),
        ],
        string="Service Tracking",
        default="no",
        help=
        "On Sales order confirmation, this product can generate a project and/or task. From those, you can track the service you are selling."
    )
    project_id = fields.Many2one(
        'project.project',
        'Project',
        company_dependent=True,
        domain=[('billable_type', '=', 'no')],
        help=
        'Select a non billable project on which tasks can be created. This setting must be set for each company.'
    )
    project_template_id = fields.Many2one(
        'project.project',
        'Project Template',
        company_dependent=True,
        domain=[('billable_type', '=', 'no')],
        copy=True,
        help=
        'Select a non billable project to be the skeleton of the new created project when selling the current product. Its stages and tasks will be duplicated.'
    )

    @api.depends('invoice_policy', 'service_type')
    def _compute_service_policy(self):
        for product in self:
            policy = None
            if product.invoice_policy == 'delivery':
                policy = 'delivered_manual' if product.service_type == 'manual' else 'delivered_timesheet'
            elif product.invoice_policy == 'order' and product.service_type == 'timesheet':
                policy = 'ordered_timesheet'
            product.service_policy = policy

    def _inverse_service_policy(self):
        for product in self:
            policy = product.service_policy
            if not policy and not product.invoice_policy == 'delivery':
                product.invoice_policy = 'order'
                product.service_type = 'manual'
            elif policy == 'ordered_timesheet':
                product.invoice_policy = 'order'
                product.service_type = 'timesheet'
            else:
                product.invoice_policy = 'delivery'
                product.service_type = 'manual' if policy == 'delivered_manual' else 'timesheet'

    @api.constrains('project_id', 'project_template_id')
    def _check_project_and_template(self):
        """ NOTE 'service_tracking' should be in decorator parameters but since ORM check constraints twice (one after setting
            stored fields, one after setting non stored field), the error is raised when company-dependent fields are not set.
            So, this constraints does cover all cases and inconsistent can still be recorded until the ORM change its behavior.
        """
        for product in self:
            if product.service_tracking == 'no' and (
                    product.project_id or product.project_template_id):
                raise ValidationError(
                    _('The product %s should not have a project nor a project template since it will not generate project.'
                      ) % (product.name, ))
            elif product.service_tracking == 'task_global_project' and product.project_template_id:
                raise ValidationError(
                    _('The product %s should not have a project template since it will generate a task in a global project.'
                      ) % (product.name, ))
            elif product.service_tracking in [
                    'task_new_project', 'project_only'
            ] and product.project_id:
                raise ValidationError(
                    _('The product %s should not have a global project since it will generate a project.'
                      ) % (product.name, ))

    @api.onchange('service_tracking')
    def _onchange_service_tracking(self):
        if self.service_tracking == 'no':
            self.project_id = False
            self.project_template_id = False
        elif self.service_tracking == 'task_global_project':
            self.project_template_id = False
        elif self.service_tracking in ['task_new_project', 'project_only']:
            self.project_id = False

    @api.onchange('type')
    def _onchange_type(self):
        super(ProductTemplate, self)._onchange_type()
        if self.type == 'service' and not self.invoice_policy:
            self.invoice_policy = 'order'
            self.service_type = 'timesheet'
        elif self.type == 'consu' and not self.invoice_policy and self.service_policy == 'ordered_timesheet':
            self.invoice_policy = 'order'
Beispiel #21
0
class OpStudent(models.Model):
    _name = "op.student"
    _description = "Student"
    _inherit = "mail.thread"
    _inherits = {"res.partner": "partner_id"}

    first_name = fields.Char('First Name', size=128, translate=True)
    middle_name = fields.Char('Middle Name', size=128, translate=True)
    last_name = fields.Char('Last Name', size=128, translate=True)
    birth_date = fields.Date('Birth Date')
    blood_group = fields.Selection([('A+', 'A+ve'), ('B+', 'B+ve'),
                                    ('O+', 'O+ve'), ('AB+', 'AB+ve'),
                                    ('A-', 'A-ve'), ('B-', 'B-ve'),
                                    ('O-', 'O-ve'), ('AB-', 'AB-ve')],
                                   string='Blood Group')
    gender = fields.Selection([('m', 'Male'), ('f', 'Female'), ('o', 'Other')],
                              'Gender',
                              required=True,
                              default='m')
    nationality = fields.Many2one('res.country', 'Nationality')
    emergency_contact = fields.Many2one('res.partner', 'Emergency Contact')
    visa_info = fields.Char('Visa Info', size=64)
    id_number = fields.Char('ID Card Number', size=64)
    partner_id = fields.Many2one('res.partner',
                                 'Partner',
                                 required=True,
                                 ondelete="cascade")
    gr_no = fields.Char("GR Number", size=20)
    category_id = fields.Many2one('op.category', 'Category')
    course_detail_ids = fields.One2many('op.student.course',
                                        'student_id',
                                        'Course Details',
                                        track_visibility='onchange')

    _sql_constraints = [('unique_gr_no', 'unique(gr_no)',
                         'GR Number must be unique per student!')]

    @api.onchange('first_name', 'middle_name', 'last_name')
    def _onchange_name(self):
        if not self.middle_name:
            self.name = str(self.first_name) + " " + str(self.last_name)
        else:
            self.name = str(self.first_name) + " " + str(
                self.middle_name) + " " + str(self.last_name)

    @api.constrains('birth_date')
    def _check_birthdate(self):
        for record in self:
            if record.birth_date > fields.Date.today():
                raise ValidationError(
                    _("Birth Date can't be greater than current date!"))

    @api.model
    def get_import_templates(self):
        return [{
            'label': _('Import Template for Students'),
            'template': '/openeagleedu_core/static/xls/op_student.xls'
        }]

    def create_student_user(self):
        user_group = self.env.ref("base.group_portal") or False
        users_res = self.env['res.users']
        for record in self:
            if not record.user_id:
                user_id = users_res.create({
                    'name': record.name,
                    'partner_id': record.partner_id.id,
                    'login': record.email,
                    'groups_id': user_group,
                    'is_student': True,
                    'tz': self._context.get('tz')
                })
                record.user_id = user_id
Beispiel #22
0
class CrmTeam(models.Model):
    _name = "crm.team"
    _inherit = ['mail.thread']
    _description = "Sales Team"
    _order = "name"

    @api.model
    @api.returns('self', lambda value: value.id if value else False)
    def _get_default_team_id(self, user_id=None):
        if not user_id:
            user_id = self.env.uid
        company_id = self.sudo(user_id).env.user.company_id.id
        team_id = self.env['crm.team'].sudo().search([
            '|', ('user_id', '=', user_id), ('member_ids', '=', user_id), '|',
            ('company_id', '=', False),
            ('company_id', 'child_of', [company_id])
        ],
                                                     limit=1)
        if not team_id and 'default_team_id' in self.env.context:
            team_id = self.env['crm.team'].browse(
                self.env.context.get('default_team_id'))
        if not team_id:
            default_team_id = self.env.ref('sales_team.team_sales_department',
                                           raise_if_not_found=False)
            if default_team_id:
                try:
                    default_team_id.check_access_rule('read')
                except AccessError:
                    return self.env['crm.team']
                if (self.env.context.get('default_type') != 'lead' or
                        default_team_id.use_leads) and default_team_id.active:
                    team_id = default_team_id
        return team_id

    def _get_default_favorite_user_ids(self):
        return [(6, 0, [self.env.uid])]

    name = fields.Char('Sales Team', required=True, translate=True)
    active = fields.Boolean(
        default=True,
        help=
        "If the active field is set to false, it will allow you to hide the Sales Team without removing it."
    )
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env['res.company'].
                                 _company_default_get('crm.team'))
    currency_id = fields.Many2one("res.currency",
                                  related='company_id.currency_id',
                                  string="Currency",
                                  readonly=True)
    user_id = fields.Many2one('res.users', string='Team Leader')
    member_ids = fields.One2many('res.users',
                                 'sale_team_id',
                                 string='Channel Members')
    favorite_user_ids = fields.Many2many(
        'res.users',
        'team_favorite_user_rel',
        'team_id',
        'user_id',
        string='Favorite Members',
        default=_get_default_favorite_user_ids)
    is_favorite = fields.Boolean(
        string='Show on dashboard',
        compute='_compute_is_favorite',
        inverse='_inverse_is_favorite',
        help=
        "Favorite teams to display them in the dashboard and access them easily."
    )
    reply_to = fields.Char(
        string='Reply-To',
        help=
        "The email address put in the 'Reply-To' of all emails sent by Eagle about cases in this Sales Team"
    )
    color = fields.Integer(string='Color Index',
                           help="The color of the channel")
    team_type = fields.Selection(
        [('sales', 'Sales'), ('website', 'Website')],
        string='Team Type',
        default='sales',
        required=True,
        help=
        "The type of this channel, it will define the resources this channel uses."
    )
    dashboard_button_name = fields.Char(
        string="Dashboard Button", compute='_compute_dashboard_button_name')
    dashboard_graph_data = fields.Text(compute='_compute_dashboard_graph')
    dashboard_graph_type = fields.Selection(
        [
            ('line', 'Line'),
            ('bar', 'Bar'),
        ],
        string='Type',
        compute='_compute_dashboard_graph',
        help='The type of graph this channel will display in the dashboard.')
    dashboard_graph_model = fields.Selection(
        [],
        string="Content",
        help='The graph this channel will display in the Dashboard.\n')
    dashboard_graph_group = fields.Selection(
        [
            ('day', 'Day'),
            ('week', 'Week'),
            ('month', 'Month'),
            ('user', 'Salesperson'),
        ],
        string='Group by',
        default='day',
        help="How this channel's dashboard graph will group the results.")
    dashboard_graph_period = fields.Selection(
        [
            ('week', 'Last Week'),
            ('month', 'Last Month'),
            ('year', 'Last Year'),
        ],
        string='Scale',
        default='month',
        help="The time period this channel's dashboard graph will consider.")

    @api.depends('dashboard_graph_group', 'dashboard_graph_model',
                 'dashboard_graph_period')
    def _compute_dashboard_graph(self):
        for team in self.filtered('dashboard_graph_model'):
            if team.dashboard_graph_group in (False, 'user') or team.dashboard_graph_period == 'week' and team.dashboard_graph_group != 'day' \
                    or team.dashboard_graph_period == 'month' and team.dashboard_graph_group != 'day':
                team.dashboard_graph_type = 'bar'
            else:
                team.dashboard_graph_type = 'line'
            team.dashboard_graph_data = json.dumps(team._get_graph())

    def _compute_is_favorite(self):
        for team in self:
            team.is_favorite = self.env.user in team.favorite_user_ids

    def _inverse_is_favorite(self):
        sudoed_self = self.sudo()
        to_fav = sudoed_self.filtered(
            lambda team: self.env.user not in team.favorite_user_ids)
        to_fav.write({'favorite_user_ids': [(4, self.env.uid)]})
        (sudoed_self - to_fav).write(
            {'favorite_user_ids': [(3, self.env.uid)]})
        return True

    def _graph_get_dates(self, today):
        """ return a coherent start and end date for the dashboard graph according to the graph settings.
        """
        if self.dashboard_graph_period == 'week':
            start_date = today - relativedelta(weeks=1)
        elif self.dashboard_graph_period == 'year':
            start_date = today - relativedelta(years=1)
        else:
            start_date = today - relativedelta(months=1)

        # we take the start of the following month/week/day if we group by month/week/day
        # (to avoid having twice the same month/week/day from different years/month/week)
        if self.dashboard_graph_group == 'month':
            start_date = date(start_date.year + start_date.month // 12,
                              start_date.month % 12 + 1, 1)
            # handle period=week, grouping=month for silly managers
            if self.dashboard_graph_period == 'week':
                start_date = today.replace(day=1)
        elif self.dashboard_graph_group == 'week':
            start_date += relativedelta(days=8 - start_date.isocalendar()[2])
            # add a week to make sure no overlapping is possible in case of year period (will display max 52 weeks, avoid case of 53 weeks in a year)
            if self.dashboard_graph_period == 'year':
                start_date += relativedelta(weeks=1)
        else:
            start_date += relativedelta(days=1)

        return [start_date, today]

    def _graph_date_column(self):
        return 'create_date'

    def _graph_x_query(self):
        if self.dashboard_graph_group == 'user':
            return 'user_id'
        elif self.dashboard_graph_group == 'week':
            return 'EXTRACT(WEEK FROM %s)' % self._graph_date_column()
        elif self.dashboard_graph_group == 'month':
            return 'EXTRACT(MONTH FROM %s)' % self._graph_date_column()
        else:
            return 'DATE(%s)' % self._graph_date_column()

    def _graph_y_query(self):
        raise UserError(
            _('Undefined graph model for Sales Team: %s') % self.name)

    def _extra_sql_conditions(self):
        return ''

    def _graph_title_and_key(self):
        """ Returns an array containing the appropriate graph title and key respectively.

            The key is for lineCharts, to have the on-hover label.
        """
        return ['', '']

    def _graph_data(self, start_date, end_date):
        """ return format should be an iterable of dicts that contain {'x_value': ..., 'y_value': ...}
            x_values should either be dates, weeks, months or user_ids depending on the self.dashboard_graph_group value.
            y_values are floats.
        """
        query = """SELECT %(x_query)s as x_value, %(y_query)s as y_value
                     FROM %(table)s
                    WHERE team_id = %(team_id)s
                      AND DATE(%(date_column)s) >= %(start_date)s
                      AND DATE(%(date_column)s) <= %(end_date)s
                      %(extra_conditions)s
                    GROUP BY x_value;"""

        # apply rules
        if not self.dashboard_graph_model:
            raise UserError(
                _('Undefined graph model for Sales Team: %s') % self.name)
        GraphModel = self.env[self.dashboard_graph_model]
        graph_table = GraphModel._table
        extra_conditions = self._extra_sql_conditions()
        where_query = GraphModel._where_calc([])
        GraphModel._apply_ir_rules(where_query, 'read')
        from_clause, where_clause, where_clause_params = where_query.get_sql()
        if where_clause:
            extra_conditions += " AND " + where_clause

        query = query % {
            'x_query': self._graph_x_query(),
            'y_query': self._graph_y_query(),
            'table': graph_table,
            'team_id': "%s",
            'date_column': self._graph_date_column(),
            'start_date': "%s",
            'end_date': "%s",
            'extra_conditions': extra_conditions
        }
        self._cr.execute(query,
                         [self.id, start_date, end_date] + where_clause_params)
        return self.env.cr.dictfetchall()

    def _get_graph(self):
        def get_week_name(start_date, locale):
            """ Generates a week name (string) from a datetime according to the locale:
                E.g.: locale    start_date (datetime)      return string
                      "en_US"      November 16th           "16-22 Nov"
                      "en_US"      December 28th           "28 Dec-3 Jan"
            """
            if (start_date + relativedelta(days=6)).month == start_date.month:
                short_name_from = format_date(start_date, 'd', locale=locale)
            else:
                short_name_from = format_date(start_date,
                                              'd MMM',
                                              locale=locale)
            short_name_to = format_date(start_date + relativedelta(days=6),
                                        'd MMM',
                                        locale=locale)
            return short_name_from + '-' + short_name_to

        self.ensure_one()
        values = []
        today = fields.Date.from_string(fields.Date.context_today(self))
        start_date, end_date = self._graph_get_dates(today)
        graph_data = self._graph_data(start_date, end_date)

        # line graphs and bar graphs require different labels
        if self.dashboard_graph_type == 'line':
            x_field = 'x'
            y_field = 'y'
        else:
            x_field = 'label'
            y_field = 'value'

        # generate all required x_fields and update the y_values where we have data for them
        locale = self._context.get('lang') or 'en_US'
        if self.dashboard_graph_group == 'day':
            for day in range(0, (end_date - start_date).days + 1):
                short_name = format_date(start_date + relativedelta(days=day),
                                         'd MMM',
                                         locale=locale)
                values.append({x_field: short_name, y_field: 0})
            for data_item in graph_data:
                index = (data_item.get('x_value') - start_date).days
                values[index][y_field] = data_item.get('y_value')

        elif self.dashboard_graph_group == 'week':
            weeks_in_start_year = int(
                date(start_date.year, 12, 28).isocalendar()
                [1])  # This date is always in the last week of ISO years
            for week in range(
                    0,
                (end_date.isocalendar()[1] - start_date.isocalendar()[1]) %
                    weeks_in_start_year + 1):
                short_name = get_week_name(
                    start_date + relativedelta(days=7 * week), locale)
                values.append({x_field: short_name, y_field: 0})

            for data_item in graph_data:
                index = int(
                    (data_item.get('x_value') - start_date.isocalendar()[1]) %
                    weeks_in_start_year)
                values[index][y_field] = data_item.get('y_value')

        elif self.dashboard_graph_group == 'month':
            for month in range(0,
                               (end_date.month - start_date.month) % 12 + 1):
                short_name = format_date(start_date +
                                         relativedelta(months=month),
                                         'MMM',
                                         locale=locale)
                values.append({x_field: short_name, y_field: 0})

            for data_item in graph_data:
                index = int((data_item.get('x_value') - start_date.month) % 12)
                values[index][y_field] = data_item.get('y_value')

        elif self.dashboard_graph_group == 'user':
            for data_item in graph_data:
                values.append({
                    x_field:
                    self.env['res.users'].browse(data_item.get('x_value')).name
                    or _('Not Defined'),
                    y_field:
                    data_item.get('y_value')
                })

        else:
            for data_item in graph_data:
                values.append({
                    x_field: data_item.get('x_value'),
                    y_field: data_item.get('y_value')
                })

        [graph_title, graph_key] = self._graph_title_and_key()
        color = '#875A7B' if '+e' in version else '#7c7bad'
        return [{
            'values': values,
            'area': True,
            'title': graph_title,
            'key': graph_key,
            'color': color
        }]

    def _compute_dashboard_button_name(self):
        """ Sets the adequate dashboard button name depending on the Sales Team's options
        """
        for team in self:
            team.dashboard_button_name = _(
                "Big Pretty Button :)")  # placeholder

    def action_primary_channel_button(self):
        """ skeleton function to be overloaded
            It will return the adequate action depending on the Sales Team's options
        """
        return False

    def _onchange_team_type(self):
        """ skeleton function defined here because it'll be called by crm and/or sale
        """
        self.ensure_one()

    @api.model
    def create(self, values):
        team = super(
            CrmTeam,
            self.with_context(mail_create_nosubscribe=True)).create(values)
        if values.get('member_ids'):
            team._add_members_to_favorites()
        return team

    @api.multi
    def write(self, values):
        res = super(CrmTeam, self).write(values)
        if values.get('member_ids'):
            self._add_members_to_favorites()
        return res

    def _add_members_to_favorites(self):
        for team in self:
            team.favorite_user_ids = [(4, member.id)
                                      for member in team.member_ids]
class EagleeduDocuments(models.Model):
    _name = 'eagleedu.documents'
    _description = "Student Documents"
    _inherit = ['mail.thread']

    @api.model
    def create(self, vals):
        """Over riding the create method to assign
        the sequence for newly creating records"""
        if vals.get('name', _('New')) == _('New'):
            vals['name'] = self.env['ir.sequence'].next_by_code('eagleedu.documents') or _('New')
        res = super(EagleeduDocuments, self).create(vals)
        return res

    #@api.multi
    def verify_document(self):
        """Return the state to done if the documents are perfect"""
        for rec in self:
            rec.write({
                'verified_by': self.env.uid,
                'verified_date': datetime.datetime.now().strftime("%Y-%m-%d"),
                'state': 'done'
            })

    #@api.multi
    def need_correction(self):
        """Return the state to correction if the documents are not perfect"""
        for rec in self:
            rec.write({
                'state': 'correction'
            })

    #@api.multi
    def hard_copy_returned(self):
        """Records who return the documents and when is it returned"""
        for rec in self:
            if rec.state == 'done':
                rec.write({
                    'state': 'returned',
                    'returned_by': self.env.uid,
                    'returned_date': datetime.datetime.now().strftime("%Y-%m-%d")
                })

    name = fields.Char(string='Serial Number', copy=False, default=lambda self: _('New'))
    # document_name = fields.Many2one('documentedu.documentedu', string='Document Type', required=True,
    #                                 help="Choose the type of the Document")
    description = fields.Text(string='Description', copy=False,
                              help="Enter a description about the document")
    has_hard_copy = fields.Boolean(string="Hard copy Received",
                                   help="Tick the field if the hard copy is provided")
    location_id = fields.Many2one('stock.location', 'Location', domain="[('usage', '=', 'internal')]",
                                  help="Location where which the hard copy is stored")
    location_note = fields.Char(string="Location Note", help="Enter some notes about the location")
    submitted_date = fields.Date(string="Submitted Date", default=date.today(),
                                 help="Documents are submitted on")
    received_by = fields.Many2one('hr.employee', string="Received By",
                                  help="The Documents are received by")
    returned_by = fields.Many2one('hr.employee', string="Returned By",
                                  help="The Documents are returned by")
    verified_date = fields.Date(string="Verified Date", help="Date at the verification is done")
    returned_date = fields.Date(string="Returned Date", help="Returning date")
    reference = fields.Char(string='Document Number', required=True, copy=False)
    responsible_verified = fields.Many2one('hr.employee', string="Responsible")
    responsible_returned = fields.Many2one('hr.employee', string="Responsible")

    verified_by = fields.Many2one('res.users', string='Verified by')
    application_ref = fields.Many2one('eagleedu.application', invisible=1, copy=False)
    # doc_attachment_id = fields.Many2many('ir.attachment', 'doc_attach_rel', 'doc_id', 'attach_id3', string="Attachment",
    #                                      help='You can attach the copy of your document', copy=False)
    state = fields.Selection([('draft', 'Draft'), ('correction', 'Correction'), ('done', 'Done'),
                              ('returned', 'Returned')],
                             string='State', required=True, default='draft', track_visibility='onchange')
Beispiel #24
0
class EagleeduHuman(models.Model):
    _name = 'eagleedu.student'
    # _inherit = 'res.partner'
    # _inherits = {'res.partner': 'image_1920'}
    _inherits = {'res.partner': 'partner_id'}
    _inherit = 'image.mixin'
    _description = 'This the application for Human'
    _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(EagleeduHuman, 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(EagleeduHuman, self).create(vals)
        return res

    # @api.model
    # def create_partener(self, partner):
    #     if partner.get('image_1920'):
    #         partner['image_1920'] = partner['image_1920']
    #     partner_id = partner.pop('id', False)
    #     if partner_id:  # Modifying existing partner
    #         self.browse(partner_id).write(partner)
    #     else:
    #         partner['lang'] = self.env.user.lang
    #         partner_id = self.create(partner).id
    #     return partner_id

    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 ondelete="cascade")
    adm_no = fields.Char(string="Admission No.", readonly=True)
    image_1920 = fields.Image(string='Image',
                              help="Provide the image of the Human")

    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)
    academic_year = fields.Many2one('eagleedu.academic.year',
                                    string="Year Information",
                                    help="Select Year")

    st_name_b = fields.Char(string='Human 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_bd_division_id = fields.Many2one('eagleedu.bddivision',
                                         string='Division/Province',
                                         help="Enter the Division 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_mobile = fields.Char(string="Guardian's Mobile")

    religious_id = fields.Many2one('eagleedu.religious',
                                   string="Religious",
                                   help="My Religion is ")
    student_id = fields.Char('Human Id')
    email = fields.Char(string="Email",
                        help="Enter E-mail id for contact purpose")
    phone = fields.Char(string="Phone",
                        help="Enter Phone no. for contact purpose")
    mobile = fields.Char(string="Mobile",
                         help="Enter Mobile num for contact purpose")
    nationality = fields.Many2one('res.country',
                                  string='Nationality',
                                  ondelete='restrict',
                                  default=19,
                                  help="Select the Nationality")
    state = fields.Selection([('draft', 'Draft'), ('approve', 'Approve'),
                              ('done', 'Done')],
                             string='Status',
                             required=True,
                             default='draft',
                             track_visibility='onchange')
    description_sale = fields.Text(string="Description",
                                   help="Enter description purpose")

    def send_to_publish(self):
        """Return the state to done if the documents are perfect"""
        for rec in self:
            rec.write({'state': 'approve'})

    def create_human(self):
        """Create student from the application and data and return the student"""
        for rec in self:
            values = {
                'name': rec.name,
                'image_1920': rec.image_1920,
                'application_no': rec.id,
                'st_father_name': rec.st_father_name,
                'st_mother_name': rec.st_mother_name,
                'mobile': rec.mobile,
                'email': rec.email,
                'st_gender': rec.st_gender,
                'date_of_birth': rec.date_of_birth,
                'st_blood_group': rec.st_blood_group,
                'nationality': rec.nationality.id,
                'house_no': rec.house_no,
                'road_no': rec.road_no,
                'post_office': rec.post_office,
                'city': rec.city,
                'bd_division_id': rec.bd_division_id.id,
                'country_id': rec.country_id.id,
                'per_village': rec.per_village,
                'per_po': rec.per_po,
                'per_ps': rec.per_ps,
                'per_dist_id': rec.per_dist_id.id,
                'per_bd_division_id': rec.per_bd_division_id.id,
                'per_country_id': rec.per_country_id.id,
                'religious_id': rec.religious_id.id,
                'application_no': rec.application_no,
                'description_sale': rec.description_sale,
            }
            student = self.env['product.template'].create(values)
            rec.write({
                'state': 'done',
            })
            return {
                'name': _('Human'),
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'product.template',
                'type': 'ir.actions.act_window',
                'res_id': student.id,
                'context': self.env.context
            }
Beispiel #25
0
class ReportStockForecat(models.Model):
    _name = 'report.stock.forecast'
    _auto = False
    _description = 'Stock Forecast Report'

    date = fields.Date(string='Date')
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 readonly=True)
    product_tmpl_id = fields.Many2one('product.template',
                                      string='Product Template',
                                      related='product_id.product_tmpl_id',
                                      readonly=True)
    cumulative_quantity = fields.Float(string='Cumulative Quantity',
                                       readonly=True)
    quantity = fields.Float(readonly=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True)

    @api.model_cr
    def init(self):
        tools.drop_view_if_exists(self._cr, 'report_stock_forecast')
        self._cr.execute(
            """CREATE or REPLACE VIEW report_stock_forecast AS (SELECT
        MIN(id) as id,
        product_id as product_id,
        date as date,
        sum(product_qty) AS quantity,
        sum(sum(product_qty)) OVER (PARTITION BY product_id ORDER BY date) AS cumulative_quantity,
        company_id
        FROM
        (SELECT
        MIN(id) as id,
        MAIN.product_id as product_id,
        SUB.date as date,
        CASE WHEN MAIN.date = SUB.date THEN sum(MAIN.product_qty) ELSE 0 END as product_qty,
        MAIN.company_id as company_id
        FROM
        (SELECT
            MIN(sq.id) as id,
            sq.product_id,
            date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) as date,
            SUM(sq.quantity) AS product_qty,
            sq.company_id
            FROM
            stock_quant as sq
            LEFT JOIN
            product_product ON product_product.id = sq.product_id
            LEFT JOIN
            stock_location location_id ON sq.location_id = location_id.id
            WHERE
            location_id.usage = 'internal'
            GROUP BY date, sq.product_id, sq.company_id
            UNION ALL
            SELECT
            MIN(-sm.id) as id,
            sm.product_id,
            CASE WHEN sm.date_expected > CURRENT_DATE
            THEN date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD'))
            ELSE date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) END
            AS date,
            SUM(sm.product_qty) AS product_qty,
            sm.company_id
            FROM
               stock_move as sm
            LEFT JOIN
               product_product ON product_product.id = sm.product_id
            LEFT JOIN
            stock_location dest_location ON sm.location_dest_id = dest_location.id
            LEFT JOIN
            stock_location source_location ON sm.location_id = source_location.id
            WHERE
            sm.state IN ('confirmed','partially_available','assigned','waiting') and
            source_location.usage != 'internal' and dest_location.usage = 'internal'
            GROUP BY sm.date_expected,sm.product_id, sm.company_id
            UNION ALL
            SELECT
                MIN(-sm.id) as id,
                sm.product_id,
                CASE WHEN sm.date_expected > CURRENT_DATE
                    THEN date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD'))
                    ELSE date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) END
                AS date,
                SUM(-(sm.product_qty)) AS product_qty,
                sm.company_id
            FROM
               stock_move as sm
            LEFT JOIN
               product_product ON product_product.id = sm.product_id
            LEFT JOIN
               stock_location source_location ON sm.location_id = source_location.id
            LEFT JOIN
               stock_location dest_location ON sm.location_dest_id = dest_location.id
            WHERE
                sm.state IN ('confirmed','partially_available','assigned','waiting') and
            source_location.usage = 'internal' and dest_location.usage != 'internal'
            GROUP BY sm.date_expected,sm.product_id, sm.company_id)
         as MAIN
     LEFT JOIN
     (SELECT DISTINCT date
      FROM
      (
             SELECT date_trunc('week', CURRENT_DATE) AS DATE
             UNION ALL
             SELECT date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD')) AS date
             FROM stock_move sm
             LEFT JOIN
             stock_location source_location ON sm.location_id = source_location.id
             LEFT JOIN
             stock_location dest_location ON sm.location_dest_id = dest_location.id
             WHERE
             sm.state IN ('confirmed','assigned','waiting') and sm.date_expected > CURRENT_DATE and
             ((dest_location.usage = 'internal' AND source_location.usage != 'internal')
              or (source_location.usage = 'internal' AND dest_location.usage != 'internal'))) AS DATE_SEARCH)
             SUB ON (SUB.date IS NOT NULL)
    GROUP BY MAIN.product_id,SUB.date, MAIN.date, MAIN.company_id
    ) AS FINAL
    GROUP BY product_id,date,company_id)""")
Beispiel #26
0
class IrActionsReport(models.Model):
    _inherit = 'ir.actions.report'

    property_printing_action_id = fields.Many2one(
        comodel_name='printing.action',
        string='Default Behaviour',
        company_dependent=True,
    )
    printing_printer_id = fields.Many2one(comodel_name='printing.printer',
                                          string='Default Printer')
    printer_tray_id = fields.Many2one(
        comodel_name='printing.tray',
        string='Paper Source',
        domain="[('printer_id', '=', printing_printer_id)]",
    )
    printing_action_ids = fields.One2many(
        comodel_name='printing.report.xml.action',
        inverse_name='report_id',
        string='Actions',
        help='This field allows configuring action and printer on a per '
        'user basis')

    @api.onchange('printing_printer_id')
    def onchange_printing_printer_id(self):
        """ Reset the tray when the printer is changed """
        self.printer_tray_id = False

    @api.model
    def print_action_for_report_name(self, report_name):
        """ Returns if the action is a direct print or pdf

        Called from js
        """
        report = self._get_report_from_name(report_name)
        if not report:
            return {}
        result = report.behaviour()
        serializable_result = {
            'action': result['action'],
            'printer_name': result['printer'].name,
        }
        return serializable_result

    @api.multi
    def _get_user_default_print_behaviour(self):
        printer_obj = self.env['printing.printer']
        user = self.env.user
        return dict(action=user.printing_action or 'client',
                    printer=user.printing_printer_id
                    or printer_obj.get_default(),
                    tray=str(user.printer_tray_id.system_name)
                    if user.printer_tray_id else False)

    @api.multi
    def _get_report_default_print_behaviour(self):
        result = {}
        report_action = self.property_printing_action_id
        if report_action and report_action.action_type != 'user_default':
            result['action'] = report_action.action_type
        if self.printing_printer_id:
            result['printer'] = self.printing_printer_id
        if self.printer_tray_id:
            result['tray'] = self.printer_tray_id.system_name
        return result

    @api.multi
    def behaviour(self):
        self.ensure_one()
        printing_act_obj = self.env['printing.report.xml.action']

        result = self._get_user_default_print_behaviour()
        result.update(self._get_report_default_print_behaviour())

        # Retrieve report-user specific values
        print_action = printing_act_obj.search([
            ('report_id', '=', self.id),
            ('user_id', '=', self.env.uid),
            ('action', '!=', 'user_default'),
        ],
                                               limit=1)
        if print_action:
            # For some reason design takes report defaults over
            # False action entries so we must allow for that here
            result.update(
                {k: v
                 for k, v in print_action.behaviour().items() if v})
        return result

    @api.multi
    def print_document(self, record_ids, data=None):
        """ Print a document, do not return the document file """
        document, doc_format = self.with_context(
            must_skip_send_to_printer=True).render_qweb_pdf(record_ids,
                                                            data=data)
        behaviour = self.behaviour()
        printer = behaviour.pop('printer', None)

        if not printer:
            raise exceptions.Warning(
                _('No printer configured to print this report.'))
        # TODO should we use doc_format instead of report_type
        return printer.print_document(self,
                                      document,
                                      doc_format=self.report_type,
                                      **behaviour)

    @api.multi
    def _can_print_report(self, behaviour, printer, document):
        """Predicate that decide if report can be sent to printer

        If you want to prevent `render_qweb_pdf` to send report you can set
        the `must_skip_send_to_printer` key to True in the context
        """
        if self.env.context.get('must_skip_send_to_printer'):
            return False
        if behaviour['action'] == 'server' and printer and document:
            return True
        return False

    @api.noguess
    def report_action(self, docids, data=None, config=True):
        res = super().report_action(docids, data=data, config=config)
        if not res.get('id'):
            res['id'] = self.id
        return res

    def render_qweb_pdf(self, res_ids=None, data=None):
        """ Generate a PDF and returns it.

        If the action configured on the report is server, it prints the
        generated document as well.
        """
        document, doc_format = super(IrActionsReport,
                                     self).render_qweb_pdf(res_ids=res_ids,
                                                           data=data)

        behaviour = self.behaviour()
        printer = behaviour.pop('printer', None)
        can_print_report = self._can_print_report(behaviour, printer, document)

        if can_print_report:
            printer.print_document(self,
                                   document,
                                   doc_format=self.report_type,
                                   **behaviour)

        return document, doc_format
class OpMarksheetLine(models.Model):
    _name = "op.marksheet.line"
    _rec_name = "student_id"
    _description = "Marksheet Line"

    marksheet_reg_id = fields.Many2one('op.marksheet.register',
                                       'Marksheet Register')
    evaluation_type = fields.Selection(
        related='marksheet_reg_id.exam_session_id.evaluation_type', store=True)
    student_id = fields.Many2one('op.student', 'Student', required=True)
    result_line = fields.One2many('op.result.line', 'marksheet_line_id',
                                  'Results')
    total_marks = fields.Integer("Total Marks",
                                 compute='_compute_total_marks',
                                 store=True)
    percentage = fields.Float("Percentage",
                              compute='_compute_percentage',
                              store=True)
    generated_date = fields.Date('Generated Date',
                                 required=True,
                                 default=fields.Date.today(),
                                 track_visibility='onchange')
    grade = fields.Char('Grade', readonly=True, compute='_compute_grade')
    status = fields.Selection([('pass', 'Pass'), ('fail', 'Fail')],
                              'Status',
                              compute='_compute_status')

    @api.constrains('total_marks', 'percentage')
    def _check_marks(self):
        for record in self:
            if (record.total_marks < 0.0) or (record.percentage < 0.0):
                raise ValidationError(_("Enter proper marks or percentage!"))

    @api.depends('result_line.marks')
    def _compute_total_marks(self):
        for record in self:
            record.total_marks = sum(
                [int(x.marks) for x in record.result_line])

    @api.depends('total_marks')
    def _compute_percentage(self):
        for record in self:
            total_exam_marks = sum(
                [int(x.exam_id.total_marks) for x in record.result_line])
            record.percentage = record.total_marks and (
                100 * record.total_marks) / total_exam_marks or 0.0

    @api.depends('percentage')
    def _compute_grade(self):
        for record in self:
            if record.evaluation_type == 'grade':
                grades = record.marksheet_reg_id.result_template_id.grade_ids
                for grade in grades:
                    if grade.min_per <= record.percentage and \
                            grade.max_per >= record.percentage:
                        record.grade = grade.result
                    else:
                        record.grade = None
            else:
                record.grade = None

    @api.depends('result_line.status')
    def _compute_status(self):
        for record in self:
            if record.status == 'fail':
                for records in record.result_line:
                    records.status = 'fail'
            else:
                record.status = 'pass'
Beispiel #28
0
class defaultAbsentSms(models.Model):
    _name = 'default.absent.sms'
    name = fields.Char("Name")
    from_number = fields.Many2one('sms.number', 'From Number')
    template_id = fields.Many2one('sms.template', "SMS template")
class Discount_Category_line(models.Model):

    _name = 'discount.category.line'

    product_id = fields.Many2one('product.template',
                                 string='Discount for Fees')
    discount_type = fields.Selection([('amount', 'Amount'),
                                      ('persentage', 'Persentage')],
                                     string='Discount Type')
    discount_amount = fields.Float(string='Discount Amount')
    discount_persentage = fields.Float('Discount Persentage')
    discount_category_id = fields.Many2one('discount.category',
                                           string='Discount Category')
    update_discount = fields.Float()

    @api.model
    def create(self, vals):
        if 'discount_type' in vals and vals['discount_type']:
            if vals['discount_type'] == 'amount':
                vals['discount_persentage'] = 0.00
            if vals['discount_type'] == 'persentage':
                vals['discount_amount'] = 0.00
        return super(Discount_Category_line, self).create(vals)

    @api.multi
    def write(self, vals):
        if 'discount_persentage' in vals and vals['discount_persentage']:
            if vals['discount_persentage'] > 100.0:
                raise except_orm(_('Warning!'),
                                 _("please enter valid discount(%)."))
        if 'discount_type' in vals and vals['discount_type']:
            if vals['discount_type'] == 'amount':
                vals['discount_persentage'] = 0.00
            if vals['discount_type'] == 'persentage':
                vals['discount_amount'] = 0.00
        if 'update_discount' not in vals:
            if 'discount_amount' in vals or 'discount_persentage' in vals:
                raise except_orm(
                    _('Warning!'),
                    _("You can not update discount direcaly from hear, use update button"
                      ))
        return super(Discount_Category_line, self).write(vals)

    @api.multi
    def discount_update(self):
        if self.update_discount >= 0.00:
            student_obj = self.env['registration']
            for stud_rec in student_obj.search([
                ('discount_on_fee', '=', self.discount_category_id.id),
                ('fee_structure_confirm', '=', True)
            ]):
                for fee_line in stud_rec.student_fee_line:
                    if fee_line.name.fees_discount.id == self.product_id.id:
                        if self.discount_type == 'amount':
                            fee_line.discount_amount = self.update_discount
                        elif self.discount_type == 'persentage':
                            fee_line.discount = self.update_discount

            history_data = {
                'updated_discount_id': self.product_id.id,
                'discount_type': self.discount_type,
                'discount_category_id': self.discount_category_id.id,
                'update_date': date.today(),
            }

            if self.discount_type == 'amount':
                history_data.update({
                    'old_discount_amount':
                    self.discount_amount,
                    'new_discount_amount':
                    self.update_discount,
                })
                self.discount_category_id.discount_history_line = [
                    (0, 0, history_data)
                ]
                self.write({
                    'discount_amount': self.update_discount,
                    'discount_persentage': 0.00,
                    'update_discount': 0.00,
                })

            elif self.discount_type == 'persentage':
                history_data.update({
                    'old_discount_persentage':
                    self.discount_persentage,
                    'new_discount_persentage':
                    self.update_discount,
                })
                self.discount_category_id.discount_history_line = [
                    (0, 0, history_data)
                ]
                self.write({
                    'discount_persentage': self.update_discount,
                    'discount_amount': 0.00,
                    'update_discount': 0.00,
                })
Beispiel #30
0
class Module(models.Model):
    _name = "ir.module.module"
    _rec_name = "shortdesc"
    _description = "Module"
    _order = 'sequence,name'

    @api.model
    def fields_view_get(self,
                        view_id=None,
                        view_type='form',
                        toolbar=False,
                        submenu=False):
        res = super(Module, self).fields_view_get(view_id,
                                                  view_type,
                                                  toolbar=toolbar,
                                                  submenu=False)
        if view_type == 'form' and res.get('toolbar', False):
            install_id = self.env.ref(
                'base.action_server_module_immediate_install').id
            action = [
                rec for rec in res['toolbar']['action']
                if rec.get('id', False) != install_id
            ]
            res['toolbar'] = {'action': action}
        return res

    @classmethod
    def get_module_info(cls, name):
        try:
            return modules.load_information_from_description_file(name)
        except Exception:
            _logger.debug(
                'Error when trying to fetch information for module %s',
                name,
                exc_info=True)
            return {}

    @api.depends('name', 'description')
    def _get_desc(self):
        for module in self:
            path = modules.get_module_resource(
                module.name, 'static/description/index.html')
            if path:
                with tools.file_open(path, 'rb') as desc_file:
                    doc = desc_file.read()
                    html = lxml.html.document_fromstring(doc)
                    for element, attribute, link, pos in html.iterlinks():
                        if element.get('src') and not '//' in element.get(
                                'src') and not 'static/' in element.get('src'):
                            element.set(
                                'src', "/%s/static/description/%s" %
                                (module.name, element.get('src')))
                    module.description_html = tools.html_sanitize(
                        lxml.html.tostring(html))
            else:
                overrides = {
                    'embed_stylesheet': False,
                    'doctitle_xform': False,
                    'output_encoding': 'unicode',
                    'xml_declaration': False,
                    'file_insertion_enabled': False,
                }
                output = publish_string(
                    source=module.description
                    if not module.application and module.description else '',
                    settings_overrides=overrides,
                    writer=MyWriter())
                module.description_html = tools.html_sanitize(output)

    @api.depends('name')
    def _get_latest_version(self):
        default_version = modules.adapt_version('1.0')
        for module in self:
            module.installed_version = self.get_module_info(module.name).get(
                'version', default_version)

    @api.depends('name', 'state')
    def _get_views(self):
        IrModelData = self.env['ir.model.data'].with_context(active_test=True)
        dmodels = ['ir.ui.view', 'ir.actions.report', 'ir.ui.menu']

        for module in self:
            # Skip uninstalled modules below, no data to find anyway.
            if module.state not in ('installed', 'to upgrade', 'to remove'):
                module.views_by_module = ""
                module.reports_by_module = ""
                module.menus_by_module = ""
                continue

            # then, search and group ir.model.data records
            imd_models = defaultdict(list)
            imd_domain = [('module', '=', module.name),
                          ('model', 'in', tuple(dmodels))]
            for data in IrModelData.sudo().search(imd_domain):
                imd_models[data.model].append(data.res_id)

            def browse(model):
                # as this method is called before the module update, some xmlid
                # may be invalid at this stage; explictly filter records before
                # reading them
                return self.env[model].browse(imd_models[model]).exists()

            def format_view(v):
                return '%s%s (%s)' % (v.inherit_id and '* INHERIT '
                                      or '', v.name, v.type)

            module.views_by_module = "\n".join(
                sorted(format_view(v) for v in browse('ir.ui.view')))
            module.reports_by_module = "\n".join(
                sorted(r.name for r in browse('ir.actions.report')))
            module.menus_by_module = "\n".join(
                sorted(m.complete_name for m in browse('ir.ui.menu')))

    @api.depends('icon')
    def _get_icon_image(self):
        for module in self:
            module.icon_image = ''
            if module.icon:
                path_parts = module.icon.split('/')
                path = modules.get_module_resource(path_parts[1],
                                                   *path_parts[2:])
            else:
                path = modules.module.get_module_icon(module.name)
            if path:
                with tools.file_open(path, 'rb') as image_file:
                    module.icon_image = base64.b64encode(image_file.read())

    name = fields.Char('Technical Name',
                       readonly=True,
                       required=True,
                       index=True)
    category_id = fields.Many2one('ir.module.category',
                                  string='Category',
                                  readonly=True,
                                  index=True)
    shortdesc = fields.Char('Module Name', readonly=True, translate=True)
    summary = fields.Char('Summary', readonly=True, translate=True)
    description = fields.Text('Description', readonly=True, translate=True)
    description_html = fields.Html('Description HTML', compute='_get_desc')
    author = fields.Char("Author", readonly=True)
    maintainer = fields.Char('Maintainer', readonly=True)
    contributors = fields.Text('Contributors', readonly=True)
    website = fields.Char("Website", readonly=True)

    # attention: Incorrect field names !!
    #   installed_version refers the latest version (the one on disk)
    #   latest_version refers the installed version (the one in database)
    #   published_version refers the version available on the repository
    installed_version = fields.Char('Latest Version',
                                    compute='_get_latest_version')
    latest_version = fields.Char('Installed Version', readonly=True)
    published_version = fields.Char('Published Version', readonly=True)

    url = fields.Char('URL', readonly=True)
    sequence = fields.Integer('Sequence', default=100)
    dependencies_id = fields.One2many('ir.module.module.dependency',
                                      'module_id',
                                      string='Dependencies',
                                      readonly=True)
    exclusion_ids = fields.One2many('ir.module.module.exclusion',
                                    'module_id',
                                    string='Exclusions',
                                    readonly=True)
    auto_install = fields.Boolean(
        'Automatic Installation',
        help='An auto-installable module is automatically installed by the '
        'system when all its dependencies are satisfied. '
        'If the module has no dependency, it is always installed.')
    state = fields.Selection(STATES,
                             string='Status',
                             default='uninstallable',
                             readonly=True,
                             index=True)
    demo = fields.Boolean('Demo Data', default=False, readonly=True)
    license = fields.Selection(
        [('GPL-2', 'GPL Version 2'),
         ('GPL-2 or any later version', 'GPL-2 or later version'),
         ('GPL-3', 'GPL Version 3'),
         ('GPL-3 or any later version', 'GPL-3 or later version'),
         ('AGPL-3', 'Affero GPL-3'), ('LGPL-3', 'LGPL Version 3'),
         ('Other OSI approved licence', 'Other OSI Approved Licence'),
         ('OEEL-1', 'Eagle Enterprise Edition License v1.0'),
         ('OPL-1', 'Eagle Proprietary License v1.0'),
         ('Other proprietary', 'Other Proprietary')],
        string='License',
        default='LGPL-3',
        readonly=True)
    menus_by_module = fields.Text(string='Menus',
                                  compute='_get_views',
                                  store=True)
    reports_by_module = fields.Text(string='Reports',
                                    compute='_get_views',
                                    store=True)
    views_by_module = fields.Text(string='Views',
                                  compute='_get_views',
                                  store=True)
    application = fields.Boolean('Application', readonly=True)
    icon = fields.Char('Icon URL')
    icon_image = fields.Binary(string='Icon', compute='_get_icon_image')
    to_buy = fields.Boolean('Eagle Enterprise Module', default=False)

    _sql_constraints = [
        ('name_uniq', 'UNIQUE (name)',
         'The name of the module must be unique!'),
    ]

    @api.multi
    def unlink(self):
        if not self:
            return True
        for module in self:
            if module.state in ('installed', 'to upgrade', 'to remove',
                                'to install'):
                raise UserError(
                    _('You are trying to remove a module that is installed or will be installed.'
                      ))
        self.clear_caches()
        return super(Module, self).unlink()

    @staticmethod
    def _check_external_dependencies(terp):
        depends = terp.get('external_dependencies')
        if not depends:
            return
        for pydep in depends.get('python', []):
            try:
                importlib.import_module(pydep)
            except ImportError:
                raise ImportError('No module named %s' % (pydep, ))

        for binary in depends.get('bin', []):
            try:
                tools.find_in_path(binary)
            except IOError:
                raise Exception('Unable to find %r in path' % (binary, ))

    @classmethod
    def check_external_dependencies(cls, module_name, newstate='to install'):
        terp = cls.get_module_info(module_name)
        try:
            cls._check_external_dependencies(terp)
        except Exception as e:
            if newstate == 'to install':
                msg = _(
                    'Unable to install module "%s" because an external dependency is not met: %s'
                )
            elif newstate == 'to upgrade':
                msg = _(
                    'Unable to upgrade module "%s" because an external dependency is not met: %s'
                )
            else:
                msg = _(
                    'Unable to process module "%s" because an external dependency is not met: %s'
                )
            raise UserError(msg % (module_name, e.args[0]))

    @api.multi
    def _state_update(self, newstate, states_to_update, level=100):
        if level < 1:
            raise UserError(_('Recursion error in modules dependencies !'))

        # whether some modules are installed with demo data
        demo = False

        for module in self:
            # determine dependency modules to update/others
            update_mods, ready_mods = self.browse(), self.browse()
            for dep in module.dependencies_id:
                if dep.state == 'unknown':
                    raise UserError(
                        _("You try to install module '%s' that depends on module '%s'.\nBut the latter module is not available in your system."
                          ) % (
                              module.name,
                              dep.name,
                          ))
                if dep.depend_id.state == newstate:
                    ready_mods += dep.depend_id
                else:
                    update_mods += dep.depend_id

            # update dependency modules that require it, and determine demo for module
            update_demo = update_mods._state_update(newstate,
                                                    states_to_update,
                                                    level=level - 1)
            module_demo = module.demo or update_demo or any(
                mod.demo for mod in ready_mods)
            demo = demo or module_demo

            # check dependencies and update module itself
            self.check_external_dependencies(module.name, newstate)
            if module.state in states_to_update:
                module.write({'state': newstate, 'demo': module_demo})

        return demo

    @assert_log_admin_access
    @api.multi
    def button_install(self):
        # domain to select auto-installable (but not yet installed) modules
        auto_domain = [('state', '=', 'uninstalled'),
                       ('auto_install', '=', True)]

        # determine whether an auto-install module must be installed:
        #  - all its dependencies are installed or to be installed,
        #  - at least one dependency is 'to install'
        install_states = frozenset(('installed', 'to install', 'to upgrade'))

        def must_install(module):
            states = set(dep.state for dep in module.dependencies_id)
            return states <= install_states and 'to install' in states

        modules = self
        while modules:
            # Mark the given modules and their dependencies to be installed.
            modules._state_update('to install', ['uninstalled'])

            # Determine which auto-installable modules must be installed.
            modules = self.search(auto_domain).filtered(must_install)

        # the modules that are installed/to install/to upgrade
        install_mods = self.search([('state', 'in', list(install_states))])

        # check individual exclusions
        install_names = {module.name for module in install_mods}
        for module in install_mods:
            for exclusion in module.exclusion_ids:
                if exclusion.name in install_names:
                    msg = _('Modules "%s" and "%s" are incompatible.')
                    raise UserError(
                        msg %
                        (module.shortdesc, exclusion.exclusion_id.shortdesc))

        # check category exclusions
        def closure(module):
            todo = result = module
            while todo:
                result |= todo
                todo = todo.mapped('dependencies_id.depend_id')
            return result

        exclusives = self.env['ir.module.category'].search([('exclusive', '=',
                                                             True)])
        for category in exclusives:
            # retrieve installed modules in category and sub-categories
            categories = category.search([('id', 'child_of', category.ids)])
            modules = install_mods.filtered(
                lambda mod: mod.category_id in categories)
            # the installation is valid if all installed modules in categories
            # belong to the transitive dependencies of one of them
            if modules and not any(modules <= closure(module)
                                   for module in modules):
                msg = _(
                    'You are trying to install incompatible modules in category "%s":'
                )
                labels = dict(self.fields_get(['state'])['state']['selection'])
                raise UserError("\n".join([msg % category.name] + [
                    "- %s (%s)" % (module.shortdesc, labels[module.state])
                    for module in modules
                ]))

        return dict(ACTION_DICT, name=_('Install'))

    @assert_log_admin_access
    @api.multi
    def button_immediate_install(self):
        """ Installs the selected module(s) immediately and fully,
        returns the next res.config action to execute

        :returns: next res.config item to execute
        :rtype: dict[str, object]
        """
        _logger.info('User #%d triggered module installation', self.env.uid)
        return self._button_immediate_function(type(self).button_install)

    @assert_log_admin_access
    @api.multi
    def button_install_cancel(self):
        self.write({'state': 'uninstalled', 'demo': False})
        return True

    @assert_log_admin_access
    @api.multi
    def module_uninstall(self):
        """ Perform the various steps required to uninstall a module completely
        including the deletion of all database structures created by the module:
        tables, columns, constraints, etc.
        """
        modules_to_remove = self.mapped('name')
        self.env['ir.model.data']._module_data_uninstall(modules_to_remove)
        # we deactivate prefetching to not try to read a column that has been deleted
        self.with_context(prefetch_fields=False).write({
            'state': 'uninstalled',
            'latest_version': False
        })
        return True

    @api.multi
    def _remove_copied_views(self):
        """ Remove the copies of the views installed by the modules in `self`.

        Those copies do not have an external id so they will not be cleaned by
        `_module_data_uninstall`. This is why we rely on `key` instead.

        It is important to remove these copies because using them will crash if
        they rely on data that don't exist anymore if the module is removed.
        """
        domain = expression.OR([[('key', '=like', m.name + '.%')]
                                for m in self])
        orphans = self.env['ir.ui.view'].with_context(
            **{
                'active_test': False,
                MODULE_UNINSTALL_FLAG: True
            }).search(domain)
        orphans.unlink()

    @api.multi
    @api.returns('self')
    def downstream_dependencies(self,
                                known_deps=None,
                                exclude_states=('uninstalled', 'uninstallable',
                                                'to remove')):
        """ Return the modules that directly or indirectly depend on the modules
        in `self`, and that satisfy the `exclude_states` filter.
        """
        if not self:
            return self
        known_deps = known_deps or self.browse()
        query = """ SELECT DISTINCT m.id
                    FROM ir_module_module_dependency d
                    JOIN ir_module_module m ON (d.module_id=m.id)
                    WHERE
                        d.name IN (SELECT name from ir_module_module where id in %s) AND
                        m.state NOT IN %s AND
                        m.id NOT IN %s """
        self._cr.execute(query, (tuple(self.ids), tuple(exclude_states),
                                 tuple(known_deps.ids or self.ids)))
        new_deps = self.browse([row[0] for row in self._cr.fetchall()])
        missing_mods = new_deps - known_deps
        known_deps |= new_deps
        if missing_mods:
            known_deps |= missing_mods.downstream_dependencies(
                known_deps, exclude_states)
        return known_deps

    @api.multi
    @api.returns('self')
    def upstream_dependencies(self,
                              known_deps=None,
                              exclude_states=('installed', 'uninstallable',
                                              'to remove')):
        """ Return the dependency tree of modules of the modules in `self`, and
        that satisfy the `exclude_states` filter.
        """
        if not self:
            return self
        known_deps = known_deps or self.browse()
        query = """ SELECT DISTINCT m.id
                    FROM ir_module_module_dependency d
                    JOIN ir_module_module m ON (d.module_id=m.id)
                    WHERE
                        m.name IN (SELECT name from ir_module_module_dependency where module_id in %s) AND
                        m.state NOT IN %s AND
                        m.id NOT IN %s """
        self._cr.execute(query, (tuple(self.ids), tuple(exclude_states),
                                 tuple(known_deps.ids or self.ids)))
        new_deps = self.browse([row[0] for row in self._cr.fetchall()])
        missing_mods = new_deps - known_deps
        known_deps |= new_deps
        if missing_mods:
            known_deps |= missing_mods.upstream_dependencies(
                known_deps, exclude_states)
        return known_deps

    def next(self):
        """
        Return the action linked to an ir.actions.todo is there exists one that
        should be executed. Otherwise, redirect to /web
        """
        Todos = self.env['ir.actions.todo']
        _logger.info('getting next %s', Todos)
        active_todo = Todos.search([('state', '=', 'open')], limit=1)
        if active_todo:
            _logger.info('next action is "%s"', active_todo.name)
            return active_todo.action_launch()
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/web',
        }

    @api.multi
    def _button_immediate_function(self, function):
        try:
            # This is done because the installation/uninstallation/upgrade can modify a currently
            # running cron job and prevent it from finishing, and since the ir_cron table is locked
            # during execution, the lock won't be released until timeout.
            self._cr.execute("SELECT * FROM ir_cron FOR UPDATE NOWAIT")
        except psycopg2.OperationalError:
            raise UserError(
                _("The server is busy right now, module operations are not possible at"
                  " this time, please try again later."))
        function(self)

        self._cr.commit()
        api.Environment.reset()
        modules.registry.Registry.new(self._cr.dbname, update_module=True)

        self._cr.commit()
        env = api.Environment(self._cr, self._uid, self._context)
        # pylint: disable=next-method-called
        config = env['ir.module.module'].next() or {}
        if config.get('type') not in ('ir.actions.act_window_close', ):
            return config

        # reload the client; open the first available root menu
        menu = env['ir.ui.menu'].search([('parent_id', '=', False)])[:1]
        return {
            'type': 'ir.actions.client',
            'tag': 'reload',
            'params': {
                'menu_id': menu.id
            },
        }

    @assert_log_admin_access
    @api.multi
    def button_immediate_uninstall(self):
        """
        Uninstall the selected module(s) immediately and fully,
        returns the next res.config action to execute
        """
        _logger.info('User #%d triggered module uninstallation', self.env.uid)
        return self._button_immediate_function(type(self).button_uninstall)

    @assert_log_admin_access
    @api.multi
    def button_uninstall(self):
        if 'base' in self.mapped('name'):
            raise UserError(_("The `base` module cannot be uninstalled"))
        deps = self.downstream_dependencies()
        (self + deps).write({'state': 'to remove'})
        return dict(ACTION_DICT, name=_('Uninstall'))

    @assert_log_admin_access
    @api.multi
    def button_uninstall_wizard(self):
        """ Launch the wizard to uninstall the given module. """
        return {
            'type': 'ir.actions.act_window',
            'target': 'new',
            'name': _('Uninstall module'),
            'view_mode': 'form',
            'res_model': 'base.module.uninstall',
            'context': {
                'default_module_id': self.id
            },
        }

    @api.multi
    def button_uninstall_cancel(self):
        self.write({'state': 'installed'})
        return True

    @assert_log_admin_access
    @api.multi
    def button_immediate_upgrade(self):
        """
        Upgrade the selected module(s) immediately and fully,
        return the next res.config action to execute
        """
        return self._button_immediate_function(type(self).button_upgrade)

    @assert_log_admin_access
    @api.multi
    def button_upgrade(self):
        Dependency = self.env['ir.module.module.dependency']
        self.update_list()

        todo = list(self)
        i = 0
        while i < len(todo):
            module = todo[i]
            i += 1
            if module.state not in ('installed', 'to upgrade'):
                raise UserError(
                    _("Can not upgrade module '%s'. It is not installed.") %
                    (module.name, ))
            self.check_external_dependencies(module.name, 'to upgrade')
            for dep in Dependency.search([('name', '=', module.name)]):
                if dep.module_id.state == 'installed' and dep.module_id not in todo:
                    todo.append(dep.module_id)

        self.browse(module.id
                    for module in todo).write({'state': 'to upgrade'})

        to_install = []
        for module in todo:
            for dep in module.dependencies_id:
                if dep.state == 'unknown':
                    raise UserError(
                        _('You try to upgrade the module %s that depends on the module: %s.\nBut this module is not available in your system.'
                          ) % (
                              module.name,
                              dep.name,
                          ))
                if dep.state == 'uninstalled':
                    to_install += self.search([('name', '=', dep.name)]).ids

        self.browse(to_install).button_install()
        return dict(ACTION_DICT, name=_('Apply Schedule Upgrade'))

    @assert_log_admin_access
    @api.multi
    def button_upgrade_cancel(self):
        self.write({'state': 'installed'})
        return True

    @staticmethod
    def get_values_from_terp(terp):
        return {
            'description': terp.get('description', ''),
            'shortdesc': terp.get('name', ''),
            'author': terp.get('author', 'Unknown'),
            'maintainer': terp.get('maintainer', False),
            'contributors': ', '.join(terp.get('contributors', [])) or False,
            'website': terp.get('website', ''),
            'license': terp.get('license', 'LGPL-3'),
            'sequence': terp.get('sequence', 100),
            'application': terp.get('application', False),
            'auto_install': terp.get('auto_install', False),
            'icon': terp.get('icon', False),
            'summary': terp.get('summary', ''),
            'url': terp.get('url') or terp.get('live_test_url', ''),
            'to_buy': False
        }

    @api.model
    def create(self, vals):
        new = super(Module, self).create(vals)
        module_metadata = {
            'name': 'module_%s' % vals['name'],
            'model': 'ir.module.module',
            'module': 'base',
            'res_id': new.id,
            'noupdate': True,
        }
        self.env['ir.model.data'].create(module_metadata)
        return new

    # update the list of available packages
    @assert_log_admin_access
    @api.model
    def update_list(self):
        res = [0, 0]  # [update, add]

        default_version = modules.adapt_version('1.0')
        known_mods = self.with_context(lang=None).search([])
        known_mods_names = {mod.name: mod for mod in known_mods}

        # iterate through detected modules and update/create them in db
        for mod_name in modules.get_modules():
            mod = known_mods_names.get(mod_name)
            terp = self.get_module_info(mod_name)
            values = self.get_values_from_terp(terp)

            if mod:
                updated_values = {}
                for key in values:
                    old = getattr(mod, key)
                    updated = tools.ustr(values[key]) if isinstance(
                        values[key], pycompat.string_types) else values[key]
                    if (old or updated) and updated != old:
                        updated_values[key] = values[key]
                if terp.get('installable',
                            True) and mod.state == 'uninstallable':
                    updated_values['state'] = 'uninstalled'
                if parse_version(terp.get(
                        'version', default_version)) > parse_version(
                            mod.latest_version or default_version):
                    res[0] += 1
                if updated_values:
                    mod.write(updated_values)
            else:
                mod_path = modules.get_module_path(mod_name)
                if not mod_path or not terp:
                    continue
                state = "uninstalled" if terp.get('installable',
                                                  True) else "uninstallable"
                mod = self.create(dict(name=mod_name, state=state, **values))
                res[1] += 1

            mod._update_dependencies(terp.get('depends', []))
            mod._update_exclusions(terp.get('excludes', []))
            mod._update_category(terp.get('category', 'Uncategorized'))

        return res

    @assert_log_admin_access
    @api.multi
    def download(self, download=True):
        return []

    @assert_log_admin_access
    @api.model
    def install_from_urls(self, urls):
        if not self.env.user.has_group('base.group_system'):
            raise AccessDenied()

        # One-click install is opt-in - cfr Issue #15225
        ad_dir = tools.config.addons_data_dir
        if not os.access(ad_dir, os.W_OK):
            msg = (_(
                "Automatic install of downloaded Apps is currently disabled."
            ) + "\n\n" + _(
                "To enable it, make sure this directory exists and is writable on the server:"
            ) + "\n%s" % ad_dir)
            _logger.warning(msg)
            raise UserError(msg)

        apps_server = urls.url_parse(self.get_apps_server())

        OPENERP = eagle.release.product_name.lower()
        tmp = tempfile.mkdtemp()
        _logger.debug('Install from url: %r', urls)
        try:
            # 1. Download & unzip missing modules
            for module_name, url in urls.items():
                if not url:
                    continue  # nothing to download, local version is already the last one

                up = urls.url_parse(url)
                if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc:
                    raise AccessDenied()

                try:
                    _logger.info('Downloading module `%s` from OpenERP Apps',
                                 module_name)
                    response = requests.get(url)
                    response.raise_for_status()
                    content = response.content
                except Exception:
                    _logger.exception('Failed to fetch module %s', module_name)
                    raise UserError(
                        _('The `%s` module appears to be unavailable at the moment, please try again later.'
                          ) % module_name)
                else:
                    zipfile.ZipFile(io.BytesIO(content)).extractall(tmp)
                    assert os.path.isdir(os.path.join(tmp, module_name))

            # 2a. Copy/Replace module source in addons path
            for module_name, url in urls.items():
                if module_name == OPENERP or not url:
                    continue  # OPENERP is special case, handled below, and no URL means local module
                module_path = modules.get_module_path(module_name,
                                                      downloaded=True,
                                                      display_warning=False)
                bck = backup(module_path, False)
                _logger.info('Copy downloaded module `%s` to `%s`',
                             module_name, module_path)
                shutil.move(os.path.join(tmp, module_name), module_path)
                if bck:
                    shutil.rmtree(bck)

            # 2b.  Copy/Replace server+base module source if downloaded
            if urls.get(OPENERP):
                # special case. it contains the server and the base module.
                # extract path is not the same
                base_path = os.path.dirname(modules.get_module_path('base'))

                # copy all modules in the SERVER/eagle/addons directory to the new "eagle" module (except base itself)
                for d in os.listdir(base_path):
                    if d != 'base' and os.path.isdir(os.path.join(
                            base_path, d)):
                        destdir = os.path.join(tmp, OPENERP, 'addons',
                                               d)  # XXX 'eagle' subdirectory ?
                        shutil.copytree(os.path.join(base_path, d), destdir)

                # then replace the server by the new "base" module
                server_dir = tools.config['root_path']  # XXX or dirname()
                bck = backup(server_dir)
                _logger.info('Copy downloaded module `eagle` to `%s`',
                             server_dir)
                shutil.move(os.path.join(tmp, OPENERP), server_dir)
                #if bck:
                #    shutil.rmtree(bck)

            self.update_list()

            with_urls = [
                module_name for module_name, url in urls.items() if url
            ]
            downloaded = self.search([('name', 'in', with_urls)])
            installed = self.search([('id', 'in', downloaded.ids),
                                     ('state', '=', 'installed')])

            to_install = self.search([('name', 'in', list(urls)),
                                      ('state', '=', 'uninstalled')])
            post_install_action = to_install.button_immediate_install()

            if installed or to_install:
                # in this case, force server restart to reload python code...
                self._cr.commit()
                eagle.service.server.restart()
                return {
                    'type': 'ir.actions.client',
                    'tag': 'home',
                    'params': {
                        'wait': True
                    },
                }
            return post_install_action

        finally:
            shutil.rmtree(tmp)

    @api.model
    def get_apps_server(self):
        return tools.config.get('apps_server',
                                'https://apps.eagle-erp.com/apps')

    def _update_dependencies(self, depends=None):
        existing = set(dep.name for dep in self.dependencies_id)
        needed = set(depends or [])
        for dep in (needed - existing):
            self._cr.execute(
                'INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)',
                (self.id, dep))
        for dep in (existing - needed):
            self._cr.execute(
                'DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s',
                (self.id, dep))
        self.invalidate_cache(['dependencies_id'], self.ids)

    def _update_exclusions(self, excludes=None):
        existing = set(excl.name for excl in self.exclusion_ids)
        needed = set(excludes or [])
        for name in (needed - existing):
            self._cr.execute(
                'INSERT INTO ir_module_module_exclusion (module_id, name) VALUES (%s, %s)',
                (self.id, name))
        for name in (existing - needed):
            self._cr.execute(
                'DELETE FROM ir_module_module_exclusion WHERE module_id=%s AND name=%s',
                (self.id, name))
        self.invalidate_cache(['exclusion_ids'], self.ids)

    def _update_category(self, category='Uncategorized'):
        current_category = self.category_id
        current_category_path = []
        while current_category:
            current_category_path.insert(0, current_category.name)
            current_category = current_category.parent_id

        categs = category.split('/')
        if categs != current_category_path:
            cat_id = modules.db.create_categories(self._cr, categs)
            self.write({'category_id': cat_id})

    @api.multi
    def _update_translations(self, filter_lang=None):
        if not filter_lang:
            langs = self.env['res.lang'].search([('translatable', '=', True)])
            filter_lang = [lang.code for lang in langs]
        elif not isinstance(filter_lang, (list, tuple)):
            filter_lang = [filter_lang]

        update_mods = self.filtered(lambda r: r.state in
                                    ('installed', 'to install', 'to upgrade'))
        mod_dict = {
            mod.name: mod.dependencies_id.mapped('name')
            for mod in update_mods
        }
        mod_names = topological_sort(mod_dict)
        self.env['ir.translation'].load_module_terms(mod_names, filter_lang)

    @api.multi
    def _check(self):
        for module in self:
            if not module.description_html:
                _logger.warning('module %s: description is empty !',
                                module.name)

    @api.model
    @tools.ormcache()
    def _installed(self):
        """ Return the set of installed modules as a dictionary {name: id} """
        return {
            module.name: module.id
            for module in self.sudo().search([('state', '=', 'installed')])
        }