Esempio n. 1
0
    def get_move_lines_for_reconciliation(self,
                                          partner_id=None,
                                          excluded_ids=None,
                                          str=False,
                                          offset=0,
                                          limit=None,
                                          additional_domain=None,
                                          overlook_partner=False):
        """ Return account.move.line records which can be used for bank statement reconciliation.
            :param partner_id:
            :param excluded_ids:
            :param str:
            :param offset:
            :param limit:
            :param additional_domain:
            :param overlook_partner:
        """
        _logger.debug('PAsa por aquí')
        if partner_id is None:
            partner_id = self.partner_id.id

        # Blue lines = payment on bank account not assigned to a statement yet
        reconciliation_aml_accounts = [
            self.journal_id.default_credit_account_id.id,
            self.journal_id.default_debit_account_id.id
        ]
        #domain_reconciliation = ['&', '&', ('statement_line_id', '=', False), ('account_id', 'in', reconciliation_aml_accounts), ('payment_id','<>', False)]
        domain_reconciliation = [
            '&', ('statement_line_id', '=', False),
            ('account_id', 'in', reconciliation_aml_accounts)
        ]

        # Black lines = unreconciled & (not linked to a payment or open balance created by statement
        domain_matching = [('reconciled', '=', False)]
        if partner_id or overlook_partner:
            domain_matching = expression.AND([
                domain_matching,
                [('account_id.internal_type', 'in', ['payable', 'receivable'])]
            ])
        else:
            # TODO : find out what use case this permits (match a check payment, registered on a journal whose account type is other instead of liquidity)
            domain_matching = expression.AND(
                [domain_matching, [('account_id.reconcile', '=', True)]])

        # Let's add what applies to both
        domain = expression.OR([domain_reconciliation, domain_matching])
        if partner_id and not overlook_partner:
            domain = expression.AND(
                [domain, [('partner_id', '=', partner_id)]])

        # Domain factorized for all reconciliation use cases
        if str:
            str_domain = self.env[
                'account.move.line'].domain_move_lines_for_reconciliation(
                    str=str)
            if not partner_id:
                str_domain = expression.OR(
                    [str_domain, [('partner_id.name', 'ilike', str)]])
            domain = expression.AND([domain, str_domain])
        if excluded_ids:
            domain = expression.AND([[('id', 'not in', excluded_ids)], domain])

        # Domain from caller
        if additional_domain is None:
            additional_domain = []
        else:
            additional_domain = expression.normalize_domain(additional_domain)
        domain = expression.AND([domain, additional_domain])
        #domain = expression.AND([additional_domain])
        _logger.debug('DOMAIN')
        _logger.debug(domain)
        return self.env['account.move.line'].search(
            domain,
            offset=offset,
            limit=limit,
            order="date_maturity desc, id desc")
Esempio n. 2
0
 def _name_search(self,
                  name,
                  args=None,
                  operator='ilike',
                  limit=100,
                  name_get_uid=None):
     if not args:
         args = []
     if name:
         positive_operators = ['=', 'ilike', '=ilike', 'like', '=like']
         product_ids = []
         if operator in positive_operators:
             product_ids = self._search([('default_code', '=', name)] +
                                        args,
                                        limit=limit,
                                        access_rights_uid=name_get_uid)
             if not product_ids:
                 product_ids = self._search([('barcode', '=', name)] + args,
                                            limit=limit,
                                            access_rights_uid=name_get_uid)
         if not product_ids and operator not in expression.NEGATIVE_TERM_OPERATORS:
             # Do not merge the 2 next lines into one single search, SQL search performance would be abysmal
             # on a database with thousands of matching products, due to the huge merge+unique needed for the
             # OR operator (and given the fact that the 'name' lookup results come from the ir.translation table
             # Performing a quick memory merge of ids in Python will give much better performance
             product_ids = self._search(args +
                                        [('default_code', operator, name)],
                                        limit=limit)
             if not limit or len(product_ids) < limit:
                 # we may underrun the limit because of dupes in the results, that's fine
                 limit2 = (limit - len(product_ids)) if limit else False
                 product2_ids = self._search(
                     args + [('name', operator, name),
                             ('id', 'not in', product_ids)],
                     limit=limit2,
                     access_rights_uid=name_get_uid)
                 product_ids.extend(product2_ids)
         elif not product_ids and operator in expression.NEGATIVE_TERM_OPERATORS:
             domain = expression.OR([
                 [
                     '&', ('default_code', operator, name),
                     ('name', operator, name)
                 ],
                 [
                     '&', ('default_code', '=', False),
                     ('name', operator, name)
                 ],
             ])
             domain = expression.AND([args, domain])
             product_ids = self._search(domain,
                                        limit=limit,
                                        access_rights_uid=name_get_uid)
         if not product_ids and operator in positive_operators:
             ptrn = re.compile('(\[(.*?)\])')
             res = ptrn.search(name)
             if res:
                 product_ids = self._search(
                     [('default_code', '=', res.group(2))] + args,
                     limit=limit,
                     access_rights_uid=name_get_uid)
         # still no results, partner in context: search on supplier info as last hope to find something
         if not product_ids and self._context.get('partner_id'):
             suppliers_ids = self.env['product.supplierinfo']._search(
                 [('name', '=', self._context.get('partner_id')), '|',
                  ('product_code', operator, name),
                  ('product_name', operator, name)],
                 access_rights_uid=name_get_uid)
             if suppliers_ids:
                 product_ids = self._search(
                     [('product_tmpl_id.seller_ids', 'in', suppliers_ids)],
                     limit=limit,
                     access_rights_uid=name_get_uid)
     else:
         product_ids = self._search(args,
                                    limit=limit,
                                    access_rights_uid=name_get_uid)
     return self.browse(product_ids).name_get()
Esempio n. 3
0
 def _default_sale_line_domain(self):
     domain = super(AccountAnalyticLine, self)._default_sale_line_domain()
     return expression.OR([domain, [('qty_delivered_method', '=', 'timesheet')]])
Esempio n. 4
0
    def _domain_move_lines_for_reconciliation(self,
                                              st_line,
                                              aml_accounts,
                                              partner_id,
                                              excluded_ids=[],
                                              search_str=False,
                                              mode='rp'):
        """ Return the domain for account.move.line records which can be used for bank statement reconciliation.

            :param aml_accounts:
            :param partner_id:
            :param excluded_ids:
            :param search_str:
            :param mode: 'rp' for receivable/payable or 'other'
        """
        AccountMoveLine = self.env['account.move.line']

        #Always exclude the journal items that have been marked as 'to be checked' in a former bank statement reconciliation
        to_check_excluded = AccountMoveLine.search(
            AccountMoveLine._get_suspense_moves_domain()).ids
        excluded_ids.extend(to_check_excluded)

        domain_reconciliation = [
            '&',
            '&',
            '&',
            ('statement_line_id', '=', False),
            ('account_id', 'in', aml_accounts),
            ('payment_id', '<>', False),
            ('balance', '!=', 0.0),
        ]

        # default domain matching
        domain_matching = [
            '&',
            '&',
            ('reconciled', '=', False),
            ('account_id.reconcile', '=', True),
            ('balance', '!=', 0.0),
        ]

        domain = expression.OR([domain_reconciliation, domain_matching])
        if partner_id:
            domain = expression.AND(
                [domain, [('partner_id', '=', partner_id)]])
        if mode == 'rp':
            domain = expression.AND([
                domain,
                [('account_id.internal_type', 'in',
                  ['receivable', 'payable', 'liquidity'])]
            ])
        else:
            domain = expression.AND([
                domain,
                [('account_id.internal_type', 'not in',
                  ['receivable', 'payable', 'liquidity'])]
            ])

        # Domain factorized for all reconciliation use cases
        if search_str:
            str_domain = self._domain_move_lines(search_str=search_str)
            str_domain = expression.OR(
                [str_domain, [('partner_id.name', 'ilike', search_str)]])
            domain = expression.AND([domain, str_domain])

        if excluded_ids:
            domain = expression.AND([[('id', 'not in', excluded_ids)], domain])
        # filter on account.move.line having the same company as the statement line
        domain = expression.AND(
            [domain, [('company_id', '=', st_line.company_id.id)]])

        # take only moves in valid state. Draft is accepted only when "Post At" is set
        # to "Bank Reconciliation" in the associated journal
        domain_post_at = [
            '|',
            '&',
            ('move_id.state', '=', 'draft'),
            ('journal_id.post_at', '=', 'bank_rec'),
            ('move_id.state', 'not in', ['draft', 'cancel']),
        ]
        domain = expression.AND([domain, domain_post_at])

        if st_line.company_id.account_bank_reconciliation_start:
            domain = expression.AND([
                domain,
                [('date', '>=',
                  st_line.company_id.account_bank_reconciliation_start)]
            ])
        return domain
Esempio n. 5
0
    def name_search(self, name, args=None, operator='ilike', limit=100):
        all_loai_record = self._context.get('loai_record_more', [])
        default_loai_record = self._context.get('default_loai_record')
        if default_loai_record:
            all_loai_record.append(default_loai_record)
        if u'Công Việc' not in all_loai_record:
            return super(TVCV, self).name_search(name,
                                                 args=args,
                                                 operator=operator,
                                                 limit=limit)

        limit = 500
        try:
            id_int = int(name)
            ma_tvcv_domain = [
                '|', ('code', 'ilike', name), ('id', '=', id_int)
            ]
        except:
            ma_tvcv_domain = [('code', 'ilike', name)]
        ma_tvcv_domain = expression.OR([[
            '|', ('name_khong_dau', 'ilike', name),
            ('name_viet_tat', 'ilike', name)
        ], ma_tvcv_domain])

        #nên giữ lại
        #         thu_vien_id_of_gd_parent_id = self._context.get('thu_vien_id_of_gd_parent_id')
        #         if thu_vien_id_of_gd_parent_id and self._context.get('you_at_gd_form'):#self._context.get('you_search_at_gd_form'):
        #             thu_vien_da_chon_list_txt = self._context.get('thu_vien_da_chon_list')
        #             if thu_vien_da_chon_list_txt==False:
        #                 thu_vien_da_chon_list = []
        #             else:
        #                 thu_vien_da_chon_list = json.loads(thu_vien_da_chon_list_txt)
        # #             thu_vien_id_of_gd_parent_id = self._context.get('thu_vien_id_of_gd_parent_id')
        #             gd_children_or_not_gd_children_domain = [('id','!=',thu_vien_da_chon_list),'|',('parent_id', '=',thu_vien_id_of_gd_parent_id),('id', '=',self.env.ref('dai_tgg.loaisuvu_viec_con_lai').id)]
        #         else:
        #             gd_children_or_not_gd_children_domain = [('parent_id','=',False),('id','!=',self.env.ref('dai_tgg.loaisuvu_viec_con_lai').id)]

        if not args:
            args = []
        if name:
            category_names = name.split(' / ')
            parents = list(category_names)
            child = parents[0]
            parents = parents[1:]
            domain = [('name', operator, child)]
            if parents:
                names_ids = self.name_search(' / '.join(parents),
                                             args=args,
                                             operator='ilike',
                                             limit=limit)
                category_ids = [name_id[0] for name_id in names_ids]
                if operator in expression.NEGATIVE_TERM_OPERATORS:
                    categories = self.search([('id', 'not in', category_ids)])
                    domain = expression.OR([[('parent_id', 'in',
                                              categories.ids)], domain])
                else:
                    domain = expression.AND([[('parent_id', 'in', category_ids)
                                              ], domain])
                for i in range(1, len(category_names)):
                    domain = [[('name', operator,
                                ' / '.join(category_names[-1 - i:]))], domain]
                    if operator in expression.NEGATIVE_TERM_OPERATORS:
                        domain = expression.AND(domain)
                    else:
                        domain = expression.OR(domain)

            name_or_code_domain = expression.OR([domain, ma_tvcv_domain])
            last_domain = expression.AND([name_or_code_domain, args])
        else:
            last_domain = args

        rs = self.search(last_domain, limit=limit)

        return rs.name_get(from_name_search=True)
Esempio n. 6
0
    def create_tax_settlement_entry(self, journal):
        """
        Funcion que crea asiento de liquidación a partir de información del
        reporte y devuelve browse del asiento generado
        * from_report_id
        * force_context
        * context: periods_number, cash_basis, date_filter_cmp, date_filter,
        date_to, date_from, hierarchy_3, company_ids, date_to_cmp,
        date_from_cmp, all_entries
        * search_disable_custom_filters
        * from_report_model
        * active_id
        """
        self.ensure_one()

        # obtenemos lineas de este reporte que tengan revert (sin importar
        # dominio o no porque en realidad puede estar seteado en linea padre
        # sin dominio)
        revert_lines = self.line_ids.search([
            ('id', 'child_of', self.line_ids.ids),
            ('settlement_type', '=', 'revert'),
        ])

        # obtenemos todas las lineas hijas de las que obtuvimos que tengan
        # dominio (esto es para evitar tener que
        # configurar revert en cada linea hija)
        revert_lines = self.line_ids.search([
            ('id', 'child_of', revert_lines.ids),
            ('domain', '!=', False),
            ('settlement_type', 'in', ['revert', False])
        ])

        move_lines = self.env['account.move.line']
        # TODO podriamos en vez de usar el report_move_lines_action para
        # obtener domain, usar directamente el "_compute_line" o el "_get_sum"
        # pero deberiamos luego cambiar la logica del grouped move lines
        # o en realidad estariamos repidiento casi dos veces lo mismo
        domains = []
        for line in revert_lines:
            domains.append(line.report_move_lines_action()['domain'])
        domain = expression.OR(domains)

        lines_vals = journal._get_tax_settlement_entry_lines_vals(domain)

        # agregamos otrs lineas ẗipo "new line"
        new_lines = self.line_ids.search([
            ('id', 'child_of', self.line_ids.ids),
            ('settlement_type', 'in', ['new_line', 'new_line_negative'])])

        # pasamos por contexto lo que viene adentro del contetxo como contexto
        # porque asi lo interpreta get_balance (en vez aparentemente
        # report_move_lines_action busca dentro del contexto)
        new_lines = new_lines.with_context(
            new_lines._context.get('context'))
        for new_line in new_lines:
            account = self.env['account.account'].search([
                ('company_id', '=', journal.company_id.id),
                ('tag_ids', '=', new_line.settement_account_tag_id.id)],
                limit=1)
            if not account:
                raise ValidationError(_(
                    'No account found with tag "%s" (id: %s) for company "%s".'
                    ' Check report and accounts configuration.') % (
                    new_line.settement_account_tag_id.name,
                    new_line.settement_account_tag_id.id,
                    journal.company_id.name))
            balance = sum(
                [x['balance'] for x in new_line.get_balance(
                    {}, {}, self, field_names=['balance'])])
            if journal.company_id.currency_id.is_zero(balance):
                continue
            balance = new_line.settlement_type == 'new_line' \
                and balance or balance * -1.0
            lines_vals.append({
                'name': self.name,
                'debit': balance < 0.0 and -balance,
                'credit': balance >= 0.0 and balance,
                'account_id': account.id,
            })

        vals = journal._get_tax_settlement_entry_vals(lines_vals)
        move = self.env['account.move'].with_context(
            allow_no_partner=True).create(vals)

        if self._context.get('tax_settlement_link', True):
            move_lines.write({'tax_settlement_move_id': move.id})
        return move
    def _domain_move_lines_for_reconciliation(self,
                                              st_line,
                                              aml_accounts,
                                              partner_id,
                                              excluded_ids=[],
                                              search_str=False):
        """ Return the domain for account.move.line records which can be used for bank statement reconciliation.

            :param aml_accounts:
            :param partner_id:
            :param excluded_ids:
            :param search_str:
        """
        AccountMoveLine = self.env['account.move.line']

        #Always exclude the journal items that have been marked as 'to be checked' in a former bank statement reconciliation
        to_check_excluded = AccountMoveLine.search(
            AccountMoveLine._get_domain_for_edition_mode()).ids
        excluded_ids.extend(to_check_excluded)

        domain_reconciliation = [
            '&', '&', ('statement_line_id', '=', False),
            ('account_id', 'in', aml_accounts), ('payment_id', '<>', False)
        ]

        # Black lines = unreconciled & (not linked to a payment or open balance created by statement
        domain_matching = [('reconciled', '=', False)]
        if partner_id:
            domain_matching = expression.AND([
                domain_matching,
                [('account_id.internal_type', 'in', ['payable', 'receivable'])]
            ])
        else:
            # TODO : find out what use case this permits (match a check payment, registered on a journal whose account type is other instead of liquidity)
            domain_matching = expression.AND(
                [domain_matching, [('account_id.reconcile', '=', True)]])

        # Let's add what applies to both
        domain = expression.OR([domain_reconciliation, domain_matching])
        if partner_id:
            domain = expression.AND(
                [domain, [('partner_id', '=', partner_id)]])

        # Domain factorized for all reconciliation use cases
        if search_str:
            str_domain = self._domain_move_lines(search_str=search_str)
            if not partner_id:
                str_domain = expression.OR(
                    [str_domain, [('partner_id.name', 'ilike', search_str)]])
            domain = expression.AND([domain, str_domain])

        if excluded_ids:
            domain = expression.AND([[('id', 'not in', excluded_ids)], domain])
        # filter on account.move.line having the same company as the statement line
        domain = expression.AND(
            [domain, [('company_id', '=', st_line.company_id.id)]])

        if st_line.company_id.account_bank_reconciliation_start:
            domain = expression.AND([
                domain,
                [('date', '>=',
                  st_line.company_id.account_bank_reconciliation_start)]
            ])

        return domain
    def name_search(self, name, args=None, operator='ilike', limit=100):
        def log_query(msg, id=False):
            # Use a new cursor to avoid rollback that could be caused by
            # an upper method
            try:
                db_registry = registry(self._cr.dbname)
                with db_registry.cursor() as cr:
                    env = api.Environment(cr, SUPERUSER_ID, {})
                    path = 'autofill_name_search by %s' % (self.env.user.name)
                    data = {
                        'name': self._name,
                        'type': 'server',
                        'dbname': self._cr.dbname,
                        'level': 'DEBUG',
                        'message': msg,
                        'path': path,
                        'func': 'name_search',
                        'line': 1
                    }
                    if id:
                        env['ir.logging'].browse(id).sudo().write(data)
                        return id
                    else:
                        data['func'] += ' in progress ...'
                        ir_logging = env['ir.logging'].sudo().create(data)
                        return ir_logging.id
            except psycopg2.Error:
                pass

        # Make a search for all autofill fields and clear default name arg to
        # avoid `expression.AND` collision
        if self.env.context.get("autofill_name_search"):
            bench = Bench().start()
            # To avoid long-waiting query, we first search for all lines owned
            # by this user. It has better performance than making a long AND
            # query including user_id
            domain = [
                ('user_id', '=', self.env.uid),
                ('project_id', '!=', False),
            ]
            owned_ids = self.env['account.analytic.line'].search(domain)
            args.append(('id', 'in', owned_ids.ids))
            # Execute normal search
            autofill_fields = self.get_autofill_fields()
            if len(name) > 2:
                extra_args = []
                for value in name.split():
                    if len(value) > 2:
                        value_args = []
                        for fname in autofill_fields:
                            value_args = expression.OR(
                                [value_args, [(fname, 'ilike', value)]])
                        extra_args = expression.AND([extra_args, value_args])
                if extra_args:
                    args = expression.AND([args, extra_args])
                    name = ''
            log_id = log_query("Autofill query: {} in progress".format(args))

        # Make a search with default criteria
        names = super().name_search(name=name,
                                    args=args,
                                    operator=operator,
                                    limit=limit)

        if _logger.isEnabledFor(logging.DEBUG):
            if self.env.context.get("autofill_name_search"):
                duration = bench.stop().duration()
                log_query("Autofill query: {} in {}s".format(args, duration),
                          log_id)

        if self.env.context.get("autofill_name_search"):
            autofill_fields = self.get_autofill_fields()
            # Add line details to quickly identify its content
            autofill_fields.remove('name')
            result = []
            for item in names:
                rec = self.browse(item[0])[0]
                name = str(item[1])
                extra_name = []
                for fname in autofill_fields:
                    fvalue = rec[fname]
                    if hasattr(fvalue, 'display_name'):
                        val = fvalue.display_name or ''
                    elif fvalue:
                        val = str(fvalue)
                    else:
                        val = False
                    if val and val not in extra_name:
                        extra_name.append(val)
                name = '{}: {}'.format(' / '.join(extra_name), name)
                result.append((item[0], name))
            return result
        else:
            return names
Esempio n. 9
0
 def _any_is_configured(self, company_id):
     domain = expression.OR([[('property_tax_payable_account_id', '!=', False)],
                             [('property_tax_receivable_account_id', '!=', False)],
                             [('property_advance_tax_payment_account_id', '!=', False)]])
     group_with_config = self.with_company(company_id).search_count(domain)
     return group_with_config > 0
Esempio n. 10
0
 def _timesheet_get_portal_domain(self):
     domain = super(AccountAnalyticLine, self)._timesheet_get_portal_domain()
     return expression.OR([domain, self._timesheet_in_helpdesk_get_portal_domain()])
Esempio n. 11
0
    def _get_bank_rec_report_data(self, options, line_id=None):
        # General data + setup
        rslt = {}

        journal_id = self._context.get('active_id') or options.get('active_id')
        journal = self.env['account.journal'].browse(journal_id)
        selected_companies = self.env['res.company'].browse(
            self.env.context['company_ids'])

        rslt['use_foreign_currency'] = \
                journal.currency_id != journal.company_id.currency_id \
                if journal.currency_id else False
        rslt['account_ids'] = list(
            set([
                journal.default_debit_account_id.id,
                journal.default_credit_account_id.id
            ]))
        rslt['line_currency'] = journal.currency_id if rslt[
            'use_foreign_currency'] else False
        self = self.with_context(line_currency=rslt['line_currency'])
        lines_already_accounted = self.env['account.move.line'].search([
            ('account_id', 'in', rslt['account_ids']),
            ('date', '<=', self.env.context['date_to']),
            ('company_id', 'in', self.env.context['company_ids'])
        ])
        rslt['odoo_balance'] = sum([
            line.amount_currency
            if rslt['use_foreign_currency'] else line.balance
            for line in lines_already_accounted
        ])

        # Payments not reconciled with a bank statement line
        aml_domain = [
            '|',
            '&',
            ('move_id.journal_id.type', 'in', ('cash', 'bank')),
            ('move_id.journal_id', '=', journal_id),
            '&',
            ('move_id.journal_id.type', 'not in', ('cash', 'bank')),
            ('payment_id.journal_id', '=', journal_id),
            '|',
            ('statement_line_id', '=', False),
            ('statement_line_id.date', '>', self.env.context['date_to']),
            ('user_type_id.type', '=', 'liquidity'),
            ('full_reconcile_id', '=', False),
            ('date', '<=', self.env.context['date_to']),
        ]
        companies_unreconciled_selection_domain = []
        for company in selected_companies:
            company_domain = [('company_id', '=', company.id)]
            if company.account_bank_reconciliation_start:
                company_domain = expression.AND([
                    company_domain,
                    [('date', '>=', company.account_bank_reconciliation_start)]
                ])
            companies_unreconciled_selection_domain = expression.OR(
                [companies_unreconciled_selection_domain, company_domain])
        aml_domain += companies_unreconciled_selection_domain

        move_lines = self.env['account.move.line'].search(aml_domain)

        if move_lines:
            rslt['not_reconciled_pmts'] = move_lines

        # Bank statement lines not reconciled with a payment
        rslt['not_reconciled_st_positive'] = self.env[
            'account.bank.statement.line'].search([
                ('statement_id.journal_id', '=', journal_id),
                ('date', '<=', self.env.context['date_to']),
                ('journal_entry_ids', '=', False), ('amount', '>', 0),
                ('company_id', 'in', self.env.context['company_ids'])
            ])

        rslt['not_reconciled_st_negative'] = self.env[
            'account.bank.statement.line'].search([
                ('statement_id.journal_id', '=', journal_id),
                ('date', '<=', self.env.context['date_to']),
                ('journal_entry_ids', '=', False), ('amount', '<', 0),
                ('company_id', 'in', self.env.context['company_ids'])
            ])

        # Final
        last_statement = self.env['account.bank.statement'].search(
            [('journal_id', '=', journal_id),
             ('date', '<=', self.env.context['date_to']),
             ('company_id', 'in', self.env.context['company_ids'])],
            order="date desc, id desc",
            limit=1)
        rslt['last_st_balance'] = last_statement.balance_end
        rslt['last_st_end_date'] = last_statement.date

        return rslt
Esempio n. 12
0
    def _compute_get_invoiced(self):
        """
        Compute the invoice status of a SO. Possible statuses:
        - no: if the SO is not in status 'sale' or 'done', we consider that there is
          nothing to invoice. This is also the default value if the conditions of no
          other status is met.
        - to invoice: if any SO line is 'to invoice', the whole SO is 'to invoice'
        - invoiced: if all SO lines are invoiced, the SO is invoiced.
        - upselling: if all SO lines are invoiced or upselling, the status is
          upselling.

        The invoice_ids are obtained thanks to the invoice lines of the SO lines, and
        we also search for possible refunds created directly from existing
        invoices. This is necessary since such a refund is not directly linked to
        the SO.
        """
        # Ignore the status of the deposit product
        # deposit_product_id = self.env['sale.advance.payment.inv']\
        #     ._default_product_id()

        for order in self:
            invoice_ids = order.operations.mapped("invoice_line_id").mapped(
                "invoice_id").filtered(
                    lambda r: r.type in ["out_invoice", "out_refund"]
                ) + order.fees_lines.mapped("invoice_line_id").mapped(
                    "invoice_id").filtered(
                        lambda r: r.type in ["out_invoice", "out_refund"])
            # Search for invoices which have been
            # 'cancelled' (filter_refund = 'modify' in account.invoice.refund')
            # use like as origin may contains multiple
            # references (e.g. 'SO01, SO02')
            refunds = invoice_ids.search([
                ("origin", "like", order.name),
                ("company_id", "=", order.company_id.id),
                ("type", "in", ("out_invoice", "out_refund")),
            ])

            invoice_ids |= refunds.filtered(
                lambda r: order.name in
                [origin.strip() for origin in r.origin.split(",")])

            # Search for refunds as well
            domain_inv = expression.OR([[
                "&",
                ("origin", "=", inv.number),
                ("journal_id", "=", inv.journal_id.id),
            ] for inv in invoice_ids if inv.number])

            if domain_inv:
                refund_ids = self.env["account.invoice"].search(
                    expression.AND([
                        [
                            "&", ("type", "=", "out_refund"),
                            ("origin", "!=", False)
                        ],
                        domain_inv,
                    ]))
            else:
                refund_ids = self.env["account.invoice"].browse()

            order.update({
                "invoice_count":
                len(set(invoice_ids.ids + refund_ids.ids)),
                "invoice_ids":
                invoice_ids.ids + refund_ids.ids,
            })
Esempio n. 13
0
    def _domain_move_lines_for_reconciliation(
        self,
        st_line,
        aml_accounts,
        partner_id,
        excluded_ids=None,
        search_str=False,
        mode="rp",
    ):
        """Return the domain for account.move.line records which can be used for
        bank statement reconciliation.

        :param aml_accounts:
        :param partner_id:
        :param excluded_ids:
        :param search_str:
        :param mode: 'rp' for receivable/payable or 'other'
        """
        AccountMoveLine = self.env["account.move.line"]

        # Always exclude the journal items that have been marked as
        # 'to be checked' in a former bank statement reconciliation
        to_check_excluded = AccountMoveLine.search(
            AccountMoveLine._get_suspense_moves_domain()).ids
        if excluded_ids is None:
            excluded_ids = []
        excluded_ids.extend(to_check_excluded)

        domain_reconciliation = [
            "&",
            "&",
            "&",
            ("statement_line_id", "=", False),
            ("account_id", "in", aml_accounts),
            ("payment_id", "<>", False),
            ("balance", "!=", 0.0),
        ]

        # default domain matching
        domain_matching = [
            "&",
            "&",
            ("reconciled", "=", False),
            ("account_id.reconcile", "=", True),
            ("balance", "!=", 0.0),
        ]

        domain = expression.OR([domain_reconciliation, domain_matching])
        if partner_id:
            domain = expression.AND(
                [domain, [("partner_id", "=", partner_id)]])
        if mode == "rp":
            domain = expression.AND([
                domain,
                [(
                    "account_id.internal_type",
                    "in",
                    ["receivable", "payable", "liquidity"],
                )],
            ])
        else:
            domain = expression.AND([
                domain,
                [(
                    "account_id.internal_type",
                    "not in",
                    ["receivable", "payable", "liquidity"],
                )],
            ])

        # Domain factorized for all reconciliation use cases
        if search_str:
            str_domain = self._domain_move_lines(search_str=search_str)
            str_domain = expression.OR(
                [str_domain, [("partner_id.name", "ilike", search_str)]])
            domain = expression.AND([domain, str_domain])

        if excluded_ids:
            domain = expression.AND([[("id", "not in", excluded_ids)], domain])
        # filter on account.move.line having the same company as the statement
        # line
        domain = expression.AND(
            [domain, [("company_id", "=", st_line.company_id.id)]])

        if st_line.company_id.account_bank_reconciliation_start:
            domain = expression.AND([
                domain,
                [(
                    "date",
                    ">=",
                    st_line.company_id.account_bank_reconciliation_start,
                )],
            ])
        return domain
Esempio n. 14
0
 def _get_domain_matching_suspense_moves(self):
     # OVERRIDE to handle the document requests in the suspense accounts domain.
     domain = super(AccountMove, self)._get_domain_matching_suspense_moves()
     return expression.OR(
         [domain, [('reconciliation_invoice_id', '=', self.id)]])
Esempio n. 15
0
    def _get_invoiced(self):
        """
        Compute the invoice status of a SO. Possible statuses:
        - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
          invoice. This is also the default value if the conditions of no other status is met.
        - to invoice: if any SO line is 'to invoice', the whole SO is 'to invoice'
        - invoiced: if all SO lines are invoiced, the SO is invoiced.
        - upselling: if all SO lines are invoiced or upselling, the status is upselling.

        The invoice_ids are obtained thanks to the invoice lines of the SO lines, and we also search
        for possible refunds created directly from existing invoices. This is necessary since such a
        refund is not directly linked to the SO.
        """
        # Ignore the status of the deposit product
        deposit_product_id = self.env[
            'sale.advance.payment.inv']._default_product_id()
        line_invoice_status_all = [
            (d['order_id'][0], d['invoice_status'])
            for d in self.env['sub.sale.order'].read_group(
                [('order_id', 'in',
                  self.ids), ('product_id', '!=', deposit_product_id.id)],
                ['order_id', 'invoice_status'], ['order_id', 'invoice_status'],
                lazy=False)
        ]
        for order in self:
            invoice_ids = order.sub_sale_order_ids.mapped(
                'invoice_lines').mapped('invoice_id').filtered(
                    lambda r: r.type in ['out_invoice', 'out_refund'])
            # Search for invoices which have been 'cancelled' (filter_refund = 'modify' in
            # 'account.invoice.refund')
            # use like as origin may contains multiple references (e.g. 'SO01, SO02')
            refunds = invoice_ids.search([
                ('origin', 'like', order.name),
                ('company_id', '=', order.company_id.id),
                ('type', 'in', ('out_invoice', 'out_refund'))
            ])
            invoice_ids |= refunds.filtered(
                lambda r: order.name in
                [origin.strip() for origin in r.origin.split(',')])
            # Search for refunds as well
            domain_inv = expression.OR([[
                '&', ('origin', '=', inv.number),
                ('journal_id', '=', inv.journal_id.id)
            ] for inv in invoice_ids if inv.number])
            if domain_inv:
                refund_ids = self.env['account.invoice'].search(
                    expression.AND([[
                        '&', ('type', '=', 'out_refund'),
                        ('origin', '!=', False)
                    ], domain_inv]))
            else:
                refund_ids = self.env['account.invoice'].browse()

            line_invoice_status = [
                d[1] for d in line_invoice_status_all if d[0] == order.id
            ]

            if order.state not in ('sale', 'done'):
                invoice_status = 'no'
            elif any(invoice_status == 'to invoice'
                     for invoice_status in line_invoice_status):
                invoice_status = 'to invoice'
            elif all(invoice_status == 'invoiced'
                     for invoice_status in line_invoice_status):
                invoice_status = 'invoiced'
            elif all(invoice_status in ['invoiced', 'upselling']
                     for invoice_status in line_invoice_status):
                invoice_status = 'upselling'
            else:
                invoice_status = 'no'

            order.update({
                'invoice_count':
                len(set(invoice_ids.ids + refund_ids.ids)),
                'invoice_ids':
                invoice_ids.ids + refund_ids.ids,
                'invoice_status':
                invoice_status
            })
Esempio n. 16
0
 def _analytic_compute_delivered_quantity_domain(self):
     domain = super(SaleOrderLine,
                    self)._analytic_compute_delivered_quantity_domain()
     domain = expression.AND([domain, [('project_id', '=', False)]])
     timesheet_domain = self._timesheet_compute_delivered_quantity_domain()
     return expression.OR([domain, timesheet_domain])
Esempio n. 17
0
    def _decode_ubl_2_1(self, tree):
        self.ensure_one()
        namespaces = self._get_ubl_namespaces(tree)

        elements = tree.xpath('//cbc:InvoiceTypeCode', namespaces=namespaces)
        if elements:
            type_code = elements[0].text
            type = 'in_refund' if type_code == '381' else 'in_invoice'
        else:
            type = 'in_invoice'

        default_journal = self.with_context(
            default_type=type)._get_default_journal()

        with Form(
                self.with_context(
                    default_type=type,
                    default_journal_id=default_journal.id)) as invoice_form:
            # Reference
            elements = tree.xpath('//cbc:ID', namespaces=namespaces)
            if elements:
                invoice_form.ref = elements[0].text
            elements = tree.xpath('//cbc:InstructionID', namespaces=namespaces)
            if elements:
                invoice_form.invoice_payment_ref = elements[0].text

            # Dates
            elements = tree.xpath('//cbc:IssueDate', namespaces=namespaces)
            if elements:
                invoice_form.invoice_date = elements[0].text
            elements = tree.xpath('//cbc:PaymentDueDate',
                                  namespaces=namespaces)
            if elements:
                invoice_form.invoice_date_due = elements[0].text
            # allow both cbc:PaymentDueDate and cbc:DueDate
            elements = tree.xpath('//cbc:DueDate', namespaces=namespaces)
            invoice_form.invoice_date_due = invoice_form.invoice_date_due or elements and elements[
                0].text

            # Currency
            elements = tree.xpath('//cbc:DocumentCurrencyCode',
                                  namespaces=namespaces)
            currency_code = elements and elements[0].text or ''
            currency = self.env['res.currency'].search(
                [('name', '=', currency_code.upper())], limit=1)
            if elements:
                invoice_form.currency_id = currency

            # Incoterm
            elements = tree.xpath(
                '//cbc:TransportExecutionTerms/cac:DeliveryTerms/cbc:ID',
                namespaces=namespaces)
            if elements:
                invoice_form.invoice_incoterm_id = self.env[
                    'account.incoterms'].search(
                        [('code', '=', elements[0].text)], limit=1)

            # Partner
            partner_element = tree.xpath(
                '//cac:AccountingSupplierParty/cac:Party',
                namespaces=namespaces)
            if partner_element:
                domains = []
                partner_element = partner_element[0]
                elements = partner_element.xpath(
                    '//cac:AccountingSupplierParty/cac:Party//cbc:Name',
                    namespaces=namespaces)
                if elements:
                    partner_name = elements[0].text
                    domains.append([('name', 'ilike', partner_name)])
                elements = partner_element.xpath(
                    '//cac:AccountingSupplierParty/cac:Party//cbc:Telephone',
                    namespaces=namespaces)
                if elements:
                    partner_telephone = elements[0].text
                    domains.append([('phone', '=', partner_telephone),
                                    ('mobile', '=', partner_telephone)])
                elements = partner_element.xpath(
                    '//cac:AccountingSupplierParty/cac:Party//cbc:ElectronicMail',
                    namespaces=namespaces)
                if elements:
                    partner_mail = elements[0].text
                    domains.append([('email', '=', partner_mail)])
                elements = partner_element.xpath(
                    '//cac:AccountingSupplierParty/cac:Party//cbc:ID',
                    namespaces=namespaces)
                if elements:
                    partner_id = elements[0].text
                    domains.append([('vat', 'like', partner_id)])

                if domains:
                    partner = self.env['res.partner'].search(
                        expression.OR(domains), limit=1)
                    if partner:
                        invoice_form.partner_id = partner
                    else:
                        invoice_form.partner_id = self.env['res.partner']

            # Regenerate PDF
            elements = tree.xpath('//cac:AdditionalDocumentReference',
                                  namespaces=namespaces)
            if elements:
                attachment_name = elements[0].xpath(
                    'cbc:ID', namespaces=namespaces)[0].text
                attachment_data = elements[0].xpath(
                    'cac:Attachment//cbc:EmbeddedDocumentBinaryObject',
                    namespaces=namespaces)[0].text
                attachment_id = self.env['ir.attachment'].create({
                    'name':
                    attachment_name,
                    'res_id':
                    self.id,
                    'res_model':
                    'account.move',
                    'datas':
                    attachment_data,
                    'type':
                    'binary',
                })
                self.with_context(no_new_invoice=True).message_post(
                    attachment_ids=[attachment_id.id])

            # Lines
            lines_elements = tree.xpath('//cac:InvoiceLine',
                                        namespaces=namespaces)
            for eline in lines_elements:
                with invoice_form.invoice_line_ids.new() as invoice_line_form:
                    # Quantity
                    elements = eline.xpath('cbc:InvoicedQuantity',
                                           namespaces=namespaces)
                    quantity = elements and float(elements[0].text) or 1.0
                    invoice_line_form.quantity = quantity

                    # Price Unit
                    elements = eline.xpath('cac:Price/cbc:PriceAmount',
                                           namespaces=namespaces)
                    invoice_line_form.price_unit = elements and float(
                        elements[0].text) or 0.0

                    # Name
                    elements = eline.xpath('cac:Item/cbc:Description',
                                           namespaces=namespaces)
                    invoice_line_form.name = elements and elements[0].text or ''
                    invoice_line_form.name = invoice_line_form.name.replace(
                        '%month%',
                        str(
                            fields.Date.to_date(invoice_form.invoice_date).
                            month))  # TODO: full name in locale
                    invoice_line_form.name = invoice_line_form.name.replace(
                        '%year%',
                        str(
                            fields.Date.to_date(
                                invoice_form.invoice_date).year))

                    # Product
                    elements = eline.xpath(
                        'cac:Item/cac:SellersItemIdentification/cbc:ID',
                        namespaces=namespaces)
                    domains = []
                    if elements:
                        product_code = elements[0].text
                        domains.append([('default_code', '=', product_code)])
                    elements = eline.xpath(
                        'cac:Item/cac:StandardItemIdentification/cbc:ID[@schemeID=\'GTIN\']',
                        namespaces=namespaces)
                    if elements:
                        product_ean13 = elements[0].text
                        domains.append([('ean13', '=', product_ean13)])
                    if domains:
                        product = self.env['product.product'].search(
                            expression.OR(domains), limit=1)
                        if product:
                            invoice_line_form.product_id = product

                    # Taxes
                    taxes_elements = eline.xpath(
                        'cac:TaxTotal/cac:TaxSubtotal', namespaces=namespaces)
                    invoice_line_form.tax_ids.clear()
                    for etax in taxes_elements:
                        elements = etax.xpath('cbc:Percent',
                                              namespaces=namespaces)
                        if elements:
                            tax = self.env['account.tax'].search(
                                [
                                    ('company_id', '=', self.env.company.id),
                                    ('amount', '=', float(elements[0].text)),
                                    ('type_tax_use', '=',
                                     invoice_form.journal_id.type),
                                ],
                                order='sequence ASC',
                                limit=1)
                            if tax:
                                invoice_line_form.tax_ids.add(tax)

        return invoice_form.save()
Esempio n. 18
0
 def _search_parent_path_names(self, operator, operand):
     domain = []
     for value in operand.split('/'):
         args = [(self._rec_name_fallback(), operator, value)]
         domain = expression.OR([args, domain]) if domain else args
     return domain if domain else [(self._rec_name_fallback(), operator, "")]
Esempio n. 19
0
 def action_view_opportunity(self):
     action = super().action_view_opportunity()
     action['domain'] = expression.OR([
         action.get('domain', []), [('partner_assigned_id', '=', self.id)]
     ])
     return action
Esempio n. 20
0
    def _get_search_domain(self, search, category, attrib_values):
        """
            Modifica el domain original para mostrar los productos en funcion de los nuevos subdominios.
            domain_swp: controla que no se muestren los productos sin stock en conjunto con el cron act_stock_published
            de product.py. El resto de domains solo busca dentro de esos ids.
            attr_domain: para buscar productos dentro del contexto por filtros de atributos.
            search: pasa del contexto y realiza la busqueda por los terminos introducidos en el search box.

        """
        # Set fuzzy search for more results
        request.env.cr.execute("SELECT set_limit(0.2);")
        domain_origin = super(WebsiteSaleExtended, self)._get_search_domain(search, category, attrib_values)
        attr_domain = []
        has_att_filter = False
        filter_args = request.httprequest.args

        # Search and filters work together
        if search and search != 0:
            for srch in shlex.split(search):
                if len(srch) > 2:
                    domain_search = ['|', '|', '|', '|', '|', '|',
                                     ('name', '%', srch),
                                     ('ref_template', 'ilike', srch),
                                     ('product_color', 'ilike', srch),
                                     ('product_variant_ids.attribute_value_ids', 'ilike', srch),
                                     ('public_categ_ids.complete_name', 'ilike', srch),
                                     ('public_categ_ids.public_categ_tag_ids', 'ilike', srch),
                                     # ('product_variant_ids', 'ilike', srch),
                                     ('product_variant_ids.product_brand_id', 'ilike', srch)]
                    domain_origin = expression.normalize_domain(domain_origin)
                    domain_origin = expression.OR([domain_origin, domain_search])

        if filter_args:
            brand = int(filter_args.get('brand', False))
            context = dict(request.env.context)
            if context.get('brand_id') == 0:
                context.pop('brand_id')
                domain_origin.remove([d for d in domain_origin if 'product_brand_id' in d][0])
            if brand and brand != 0:
                domain_origin.append(('product_brand_id', '=', brand))

            tags = request.env['product.attribute.tag'].sudo()

            gender = filter_args.get('gender', False)
            if gender:
                gender_domain = ['&', ('type', '=', 'gender'), ('value', '=', gender)]
                if brand and brand != 0:
                    gender_domain += [('product_brand_id', '=', brand)]
                tag_gender = tags.search(gender_domain)
                if tag_gender:
                    attr_domain += [('product_gender_id', 'in', tag_gender.ids)]
                    has_att_filter = True

            age = filter_args.get('age', False)
            if age:
                tag_domain = ['&', ('type', '=', 'age'), ('value', '=', age)]
                if brand and brand != 0:
                    tag_domain += [('product_brand_id', '=', brand)]
                tag_age = tags.search(tag_domain)
                if tag_age:
                    attr_domain += [('product_age_id', 'in', tag_age.ids)]
                    has_att_filter = True

            if has_att_filter:
                product_attributes = request.env['product.attribute'].sudo().search(attr_domain)
                product_attribute_lines = request.env['product.attribute.line'].sudo().search([
                    ('attribute_id', 'in', product_attributes.ids)
                ])
                domain_origin += [('attribute_line_ids', 'in', product_attribute_lines.ids)]

        # Only can put on context products with stock > 0 or with stock = 0 but published
        # search and filters and all domain have to respect this. So that we need add this like an AND
        website = request.website
        domain_swp = [('website_id', '=', website.id), ('stock_website_published', '=', True)]
        product_ids = request.env['template.stock.web'].sudo().search(domain_swp).mapped('product_id').ids
        domain_origin += [('id', 'in', product_ids)]

        return domain_origin
Esempio n. 21
0
 def _analytic_compute_delivered_quantity_domain(self):
     domain = super(SaleOrderLine,
                    self)._analytic_compute_delivered_quantity_domain()
     timesheet_domain = self._timesheet_compute_delivered_quantity_domain()
     return expression.OR([domain, timesheet_domain])
Esempio n. 22
0
 def _get_stage_id_domain(self):
     domain = [('job_ids', '=', False)]
     if self.job_id:
         domain = expression.OR([domain, [('job_ids', 'in', self.job_id)]])
     return domain
Esempio n. 23
0
 def _check_barcode_uniqueness(self):
     """ With GS1 nomenclature, products and packagins use the same pattern. Therefore, we need
     to ensure the uniqueness between products' barcodes and packagings' ones"""
     condition = expression.OR([[('barcode', '=', b)] for b in self.mapped('barcode') if b])
     if self.env['product.product'].search(condition, limit=1):
         raise ValidationError(_("A product already uses the barcode"))
Esempio n. 24
0
 def _get_translation_frontend_modules_domain(cls):
     domain = super(IrHttp, cls)._get_translation_frontend_modules_domain()
     return expression.OR([domain, [('name', '=', 'web_editor')]])
Esempio n. 25
0
 def _get_invoice_basis_domain(self, domain_params):
     domain_company = [("company_id", "in", domain_params["company_ids"])]
     domain_tax_date = self._get_tax_date_domain(domain_params)
     domain_account_date = self._get_accounting_date_domain(domain_params)
     domain_dates = expression.OR([domain_tax_date, domain_account_date])
     return expression.AND([domain_company, domain_dates])
 def _name_search(self,
                  name,
                  args=None,
                  operator='ilike',
                  limit=100,
                  name_get_uid=None):
     if not args:
         args = []
     if name:
         positive_operators = ['=', 'ilike', '=ilike', 'like', '=like']
         category_ids = []
         if operator in positive_operators:
             category_ids = self._search([('code', '=', name)] + args,
                                         limit=limit,
                                         access_rights_uid=name_get_uid)
         if not category_ids and operator not in expression.NEGATIVE_TERM_OPERATORS:
             # Do not merge the 2 next lines into one single search, SQL
             # search performance would be abysmal on a database with
             # thousands of matching products, due to the huge merge+unique
             # needed for the OR operator (and given the fact that the
             # 'name' lookup results come from the ir.translation table
             # Performing a quick memory merge of ids in Python will give
             # much better performance
             category_ids = self._search(args + [('code', operator, name)],
                                         limit=limit)
             if not limit or len(category_ids) < limit:
                 # we may underrun the limit because of dupes in the
                 # results, that's fine
                 limit2 = (limit - len(category_ids)) if limit else False
                 product2_ids = self._search(
                     args + [('name', operator, name),
                             ('id', 'not in', category_ids)],
                     limit=limit2,
                     access_rights_uid=name_get_uid)
                 category_ids.extend(product2_ids)
         elif not category_ids and operator in expression.NEGATIVE_TERM_OPERATORS:
             domain = expression.OR([
                 [
                     '&',
                     ('code', operator, name),
                     ('name', operator, name),
                 ],
                 [
                     '&',
                     ('code', '=', False),
                     ('name', operator, name),
                 ],
             ])
             domain = expression.AND([args, domain])
             category_ids = self._search(domain,
                                         limit=limit,
                                         access_rights_uid=name_get_uid)
         if not category_ids and operator in positive_operators:
             ptrn = re.compile('(\[(.*?)\])')
             res = ptrn.search(name)
             if res:
                 category_ids = self._search([('code', '=', res.group(2))] +
                                             args,
                                             limit=limit,
                                             access_rights_uid=name_get_uid)
     else:
         category_ids = self._search(args,
                                     limit=limit,
                                     access_rights_uid=name_get_uid)
     return self.browse(category_ids).name_get()
Esempio n. 27
0
 def _domain_move_lines(self, search_str):
     str_domain = super()._domain_move_lines(search_str)
     account_code_domain = [('account_id.code', '=ilike', search_str + '%')]
     str_domain = expression.OR([str_domain, account_code_domain])
     return str_domain
Esempio n. 28
0
    def _import_ubl(self, tree, invoice):
        """ Decodes an UBL invoice into an invoice.

        :param tree:    the UBL tree to decode.
        :param invoice: the invoice to update or an empty recordset.
        :returns:       the invoice where the UBL data was imported.
        """
        def _get_ubl_namespaces():
            ''' If the namespace is declared with xmlns='...', the namespaces map contains the 'None' key that causes an
            TypeError: empty namespace prefix is not supported in XPath
            Then, we need to remap arbitrarily this key.

            :param tree: An instance of etree.
            :return: The namespaces map without 'None' key.
            '''
            namespaces = tree.nsmap
            namespaces['inv'] = namespaces.pop(None)
            return namespaces

        namespaces = _get_ubl_namespaces()
        if not invoice:
            invoice = self.env['account.move'].create({})

        elements = tree.xpath('//cbc:InvoiceTypeCode', namespaces=namespaces)
        if elements:
            type_code = elements[0].text
            move_type = 'in_refund' if type_code == '381' else 'in_invoice'
        else:
            move_type = 'in_invoice'

        default_journal = invoice.with_context(
            default_move_type=move_type)._get_default_journal()

        with Form(
                invoice.with_context(
                    default_move_type=move_type,
                    default_journal_id=default_journal.id)) as invoice_form:
            # Reference
            elements = tree.xpath('//cbc:ID', namespaces=namespaces)
            if elements:
                invoice_form.ref = elements[0].text
            elements = tree.xpath('//cbc:InstructionID', namespaces=namespaces)
            if elements:
                invoice_form.payment_reference = elements[0].text

            # Dates
            elements = tree.xpath('//cbc:IssueDate', namespaces=namespaces)
            if elements:
                invoice_form.invoice_date = elements[0].text
            elements = tree.xpath('//cbc:PaymentDueDate',
                                  namespaces=namespaces)
            if elements:
                invoice_form.invoice_date_due = elements[0].text
            # allow both cbc:PaymentDueDate and cbc:DueDate
            elements = tree.xpath('//cbc:DueDate', namespaces=namespaces)
            invoice_form.invoice_date_due = invoice_form.invoice_date_due or elements and elements[
                0].text

            # Currency
            elements = tree.xpath('//cbc:DocumentCurrencyCode',
                                  namespaces=namespaces)
            currency_code = elements and elements[0].text or ''
            currency = self.env['res.currency'].search(
                [('name', '=', currency_code.upper())], limit=1)
            if elements:
                invoice_form.currency_id = currency

            # Incoterm
            elements = tree.xpath(
                '//cbc:TransportExecutionTerms/cac:DeliveryTerms/cbc:ID',
                namespaces=namespaces)
            if elements:
                invoice_form.invoice_incoterm_id = self.env[
                    'account.incoterms'].search(
                        [('code', '=', elements[0].text)], limit=1)

            # Partner
            partner_element = tree.xpath(
                '//cac:AccountingSupplierParty/cac:Party',
                namespaces=namespaces)
            if partner_element:
                domains = []
                partner_element = partner_element[0]
                elements = partner_element.xpath(
                    '//cac:AccountingSupplierParty/cac:Party//cbc:Name',
                    namespaces=namespaces)
                if elements:
                    partner_name = elements[0].text
                    domains.append([('name', 'ilike', partner_name)])
                else:
                    partner_name = ''
                elements = partner_element.xpath(
                    '//cac:AccountingSupplierParty/cac:Party//cbc:Telephone',
                    namespaces=namespaces)
                if elements:
                    partner_telephone = elements[0].text
                    domains.append([('phone', '=', partner_telephone),
                                    ('mobile', '=', partner_telephone)])
                elements = partner_element.xpath(
                    '//cac:AccountingSupplierParty/cac:Party//cbc:ElectronicMail',
                    namespaces=namespaces)
                if elements:
                    partner_mail = elements[0].text
                    domains.append([('email', '=', partner_mail)])
                elements = partner_element.xpath(
                    '//cac:AccountingSupplierParty/cac:Party//cbc:ID',
                    namespaces=namespaces)
                if elements:
                    partner_id = elements[0].text
                    domains.append([('vat', 'like', partner_id)])

                if domains:
                    partner = self.env['res.partner'].search(
                        expression.OR(domains), limit=1)
                    if partner:
                        invoice_form.partner_id = partner
                        partner_name = partner.name
                    else:
                        invoice_form.partner_id = self.env['res.partner']

            # Regenerate PDF
            attachments = self.env['ir.attachment']
            elements = tree.xpath('//cac:AdditionalDocumentReference',
                                  namespaces=namespaces)
            for element in elements:
                attachment_name = element.xpath('cbc:ID',
                                                namespaces=namespaces)
                attachment_data = element.xpath(
                    'cac:Attachment//cbc:EmbeddedDocumentBinaryObject',
                    namespaces=namespaces)
                if attachment_name and attachment_data:
                    text = attachment_data[0].text
                    attachments |= self.env['ir.attachment'].create({
                        'name':
                        attachment_name[0].text,
                        'res_id':
                        invoice.id,
                        'res_model':
                        'account.move',
                        'datas':
                        text + '=' * (len(text) % 3),  # Fix incorrect padding
                        'type':
                        'binary',
                    })
            if attachments:
                invoice.with_context(no_new_invoice=True).message_post(
                    attachment_ids=attachments.ids)

            # Lines
            lines_elements = tree.xpath('//cac:InvoiceLine',
                                        namespaces=namespaces)
            for eline in lines_elements:
                with invoice_form.invoice_line_ids.new() as invoice_line_form:
                    # Product
                    elements = eline.xpath(
                        'cac:Item/cac:SellersItemIdentification/cbc:ID',
                        namespaces=namespaces)
                    domains = []
                    if elements:
                        product_code = elements[0].text
                        domains.append([('default_code', '=', product_code)])
                    elements = eline.xpath(
                        'cac:Item/cac:StandardItemIdentification/cbc:ID[@schemeID=\'GTIN\']',
                        namespaces=namespaces)
                    if elements:
                        product_ean13 = elements[0].text
                        domains.append([('barcode', '=', product_ean13)])
                    if domains:
                        product = self.env['product.product'].search(
                            expression.OR(domains), limit=1)
                        if product:
                            invoice_line_form.product_id = product

                    # Quantity
                    elements = eline.xpath('cbc:InvoicedQuantity',
                                           namespaces=namespaces)
                    quantity = elements and float(elements[0].text) or 1.0
                    invoice_line_form.quantity = quantity

                    # Price Unit
                    elements = eline.xpath('cac:Price/cbc:PriceAmount',
                                           namespaces=namespaces)
                    price_unit = elements and float(elements[0].text) or 0.0
                    elements = eline.xpath('cbc:LineExtensionAmount',
                                           namespaces=namespaces)
                    line_extension_amount = elements and float(
                        elements[0].text) or 0.0
                    invoice_line_form.price_unit = price_unit or line_extension_amount / invoice_line_form.quantity or 0.0

                    # Name
                    elements = eline.xpath('cac:Item/cbc:Description',
                                           namespaces=namespaces)
                    if elements and elements[0].text:
                        invoice_line_form.name = elements[0].text
                        invoice_line_form.name = invoice_line_form.name.replace(
                            '%month%',
                            str(
                                fields.Date.to_date(invoice_form.invoice_date).
                                month))  # TODO: full name in locale
                        invoice_line_form.name = invoice_line_form.name.replace(
                            '%year%',
                            str(
                                fields.Date.to_date(
                                    invoice_form.invoice_date).year))
                    else:
                        invoice_line_form.name = "%s (%s)" % (
                            partner_name or '', invoice_form.invoice_date)

                    # Taxes
                    taxes_elements = eline.xpath(
                        'cac:TaxTotal/cac:TaxSubtotal', namespaces=namespaces)
                    invoice_line_form.tax_ids.clear()
                    for etax in taxes_elements:
                        elements = etax.xpath('cbc:Percent',
                                              namespaces=namespaces)
                        if elements:
                            tax = self.env['account.tax'].search(
                                [
                                    ('company_id', '=', self.env.company.id),
                                    ('amount', '=', float(elements[0].text)),
                                    ('type_tax_use', '=',
                                     invoice_form.journal_id.type),
                                ],
                                order='sequence ASC',
                                limit=1)
                            if tax:
                                invoice_line_form.tax_ids.add(tax)

        return invoice_form.save()
Esempio n. 29
0
 def get_rule_ids(self, test_record_ids, model_name, mode="read"):
     if self._uid == SUPERUSER_ID:
         return []
     res_ids = []
     model_env = self.env[model_name]
     self._cr.execute(
         """
             SELECT r.id
             FROM ir_rule r
             JOIN ir_model m ON (r.model_id = m.id)
             WHERE m.model = %s
             AND r.active is True
             AND r.perm_""" + mode + """
             AND (r.id IN (
                 SELECT rule_group_id FROM rule_group_rel g_rel
                 JOIN res_groups_users_rel u_rel
                     ON (g_rel.group_id = u_rel.gid)
                 WHERE u_rel.uid = %s) OR r.global)
             """, (model_name, self._uid))
     rule_ids = [x[0] for x in self._cr.fetchall()]
     if rule_ids:
         # browse user as super-admin root to avoid access errors!
         user_obj = self.env['res.users'].sudo().browse(self._uid)
         rule_data_objs = self.env['ir.rule'].browse(rule_ids)
         for rule in rule_data_objs:
             # list of domains
             global_domains = []
             # map: group -> list of domains
             group_domains = {}
             # read 'domain' as UID to have the correct eval context
             # for the rule.
             rule_domain = rule.domain
             # rule_domain = rule_domain['domain']
             dom = expression.normalize_domain(rule_domain)
             for group in rule.groups:
                 if group in user_obj.groups_id:
                     group_domains.setdefault(group, []).append(dom)
             if not rule.groups:
                 global_domains.append(dom)
             # combine global domains and group domains
             if group_domains:
                 group_domain = expression.OR(
                     map(expression.OR, group_domains.values()))
             else:
                 group_domain = []
             domain = expression.AND(global_domains + [group_domain])
             if domain:
                 # _where_calc is called as superuser.
                 # This means that rules can involve objects on
                 # which the real uid has no acces rights.
                 # This means also there is no implicit restriction
                 # (e.g. an object references another object
                 # the user can't see).
                 query = self.env[model_name].sudo()._where_calc(
                     domain, active_test=False)
                 where_clause, where_params, tables = query.where_clause,\
                     query.where_clause_params, query.tables
                 if where_clause:
                     where_clause = ' and ' + ' and '.join(where_clause)
                     self._cr.execute(
                         'SELECT ' + model_env._table + '.id FROM ' +
                         ','.join(tables) + ' WHERE ' + model_env._table +
                         '.id IN %s' + where_clause,
                         ([tuple(test_record_ids)] + where_params))
                     returned_ids = [
                         x['id'] for x in self._cr.dictfetchall()
                     ]
                     check_rs = self.sudo(
                         self._uid).profile_check_record_rules_result_count(
                             test_record_ids, returned_ids, mode, model_env)
                     if not check_rs:
                         res_ids.append(rule.id)
     return res_ids
Esempio n. 30
0
 def _get_translation_frontend_modules_domain(cls):
     domain = super(IrHttp, cls)._get_translation_frontend_modules_domain()
     return expression.OR([domain, [('name', 'ilike', 'crnd_wsd')]])