def _domain_move_lines(self, search_str): """ Returns the domain from the search_str search :param search_str: search string """ if not search_str: return [] str_domain = self._str_domain_for_mv_line(search_str) if search_str[0] in ['-', '+']: try: amounts_str = search_str.split('|') for amount_str in amounts_str: amount = amount_str[0] == '-' and float( amount_str) or float(amount_str[1:]) amount_domain = [ '|', ('amount_residual', '=', amount), '|', ('amount_residual_currency', '=', amount), '|', (amount_str[0] == '-' and 'credit' or 'debit', '=', float(amount_str[1:])), ('amount_currency', '=', amount), ] str_domain = expression.OR([str_domain, amount_domain]) except: pass else: try: amount = float(search_str) amount_domain = [ '|', ('amount_residual', '=', amount), '|', ('amount_residual_currency', '=', amount), '|', ('amount_residual', '=', -amount), '|', ('amount_residual_currency', '=', -amount), '&', ('account_id.internal_type', '=', 'liquidity'), '|', '|', '|', ('debit', '=', amount), ('credit', '=', amount), ('amount_currency', '=', amount), ('amount_currency', '=', -amount), ] str_domain = expression.OR([str_domain, amount_domain]) except: pass return str_domain
def _domain_move_lines_for_reconciliation(self, st_line, aml_accounts, partner_id, excluded_ids=None, 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: """ domain_reconciliation = [ '&', '&', ('statement_line_id', '=', False), ('account_id', 'in', aml_accounts), ('payment_id', '<>', False) ] # default domain matching domain_matching = expression.AND([ [('reconciled', '=', False)], [('account_id.reconcile', '=', True)] ]) 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 _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()
def _search_ids(model, values, force_company_id): ids = set() domain = [] for item in values: if isinstance(item, int): ids.add(item) else: domain = expression.OR([[('name', 'ilike', item)], domain]) if domain: if force_company_id: domain = expression.AND([[('company_id', '=', force_company_id)], domain]) ids |= set(self.env[model].search(domain).ids) return ids
def _search_available_today(self, operator, value): if (not operator in ['=', '!=']) or (not value in [True, False]): return [] searching_for_true = (operator == '=' and value) or (operator == '!=' and not value) today = fields.Date.context_today(self) fieldname = 'recurrency_%s' % (WEEKDAY_TO_NAME[today.weekday()]) return expression.AND([[(fieldname, operator, value)], expression.OR([ [('until', '=', False)], [('until', '>' if searching_for_true else '<', today) ], ])])
def _compute_domain(self, model_name, mode="read"): if mode not in self._MODES: raise ValueError("Invalid mode: {!r}".format(mode)) if self._uid == SUPERUSER_ID: return None query = """ SELECT r.id FROM ir_rule r JOIN ir_model m ON (r.model_id=m.id) WHERE m.model=%s AND r.active AND r.perm_{mode} AND (r.id IN (SELECT rule_group_id FROM rule_group_rel rg JOIN res_groups_users_rel gu ON (rg.group_id=gu.gid) WHERE gu.uid=%s) OR r.global) """.format(mode=mode) self._cr.execute(query, (model_name, self._uid)) rule_ids = [row[0] for row in self._cr.fetchall()] if not rule_ids: return [] # browse user and rules as SUPERUSER_ID to avoid access errors! eval_context = self._eval_context() user_groups = self.env.user.groups_id global_domains = [] # list of domains group_domains = [] # list of domains for rule in self.browse(rule_ids).sudo(): # BEGIN redefined part of original _compute_domain of eagle/base/addons/ir/ir_rule. # have to redefine all method to take in account new ir.rule ``backend_behaviour`` setting dom = [] if not eval_context.get("website_id") and rule.backend_behaviour: dom = ([(1, "=", 1)] if rule.backend_behaviour == "true" else [(0, "=", 1)]) else: # evaluate the domain for the current user dom = (safe_eval(rule.domain_force, eval_context) if rule.domain_force else []) dom = expression.normalize_domain(dom) # END redefined part of original _compute_domain if not rule.groups: global_domains.append(dom) elif rule.groups & user_groups: group_domains.append(dom) # combine global domains and group domains if not group_domains: return expression.AND(global_domains) return expression.AND(global_domains + [expression.OR(group_domains)])
def _compute_debit_credit_balance(self): Curr = self.env['res.currency'] analytic_line_obj = self.env['account.analytic.line'] domain = [('account_id', 'in', self.ids)] if self._context.get('from_date', False): domain.append(('date', '>=', self._context['from_date'])) if self._context.get('to_date', False): domain.append(('date', '<=', self._context['to_date'])) if self._context.get('tag_ids'): tag_domain = expression.OR([[('tag_ids', 'in', [tag])] for tag in self._context['tag_ids']]) domain = expression.AND([domain, tag_domain]) if self._context.get('company_ids'): domain.append(('company_id', 'in', self._context['company_ids'])) user_currency = self.env.company.currency_id credit_groups = analytic_line_obj.read_group( domain=domain + [('amount', '>=', 0.0)], fields=['account_id', 'currency_id', 'amount'], groupby=['account_id', 'currency_id'], lazy=False, ) data_credit = defaultdict(float) for l in credit_groups: data_credit[l['account_id'][0]] += Curr.browse( l['currency_id'][0])._convert(l['amount'], user_currency, self.env.company, fields.Date.today()) debit_groups = analytic_line_obj.read_group( domain=domain + [('amount', '<', 0.0)], fields=['account_id', 'currency_id', 'amount'], groupby=['account_id', 'currency_id'], lazy=False, ) data_debit = defaultdict(float) for l in debit_groups: data_debit[l['account_id'][0]] += Curr.browse( l['currency_id'][0])._convert(l['amount'], user_currency, self.env.company, fields.Date.today()) for account in self: account.debit = abs(data_debit.get(account.id, 0.0)) account.credit = data_credit.get(account.id, 0.0) account.balance = account.credit - account.debit
def _search_available_today(self, operator, value): if (not operator in ['=', '!=']) or (not value in [True, False]): return [] searching_for_true = (operator == '=' and value) or (operator == '!=' and not value) now = fields.Datetime.now().replace(tzinfo=pytz.UTC).astimezone( pytz.timezone(self.env.user.tz or 'UTC')) fieldname = 'recurrency_%s' % (WEEKDAY_TO_NAME[now.weekday()]) recurrency_domain = expression.OR([[ ('recurrency_end_date', '=', False) ], [('recurrency_end_date', '>' if searching_for_true else '<', now)]]) return expression.AND( [recurrency_domain, [(fieldname, operator, value)]])
def unlink(self): """ Override unlink method : Remove attendee from a channel, then also remove slide.slide.partner related to. """ removed_slide_partner_domain = [] for channel_partner in self: # find all slide link to the channel and the partner removed_slide_partner_domain = expression.OR([ removed_slide_partner_domain, [('partner_id', '=', channel_partner.partner_id.id), ('slide_id', 'in', channel_partner.channel_id.slide_ids.ids)] ]) if removed_slide_partner_domain: self.env['slide.slide.partner'].search( removed_slide_partner_domain).unlink() return super(ChannelUsersRelation, self).unlink()
def _search_is_available_at(self, operator, value): supported_operators = ['in', 'not in', '=', '!='] if not operator in supported_operators: return expression.TRUE_DOMAIN if isinstance(value, int): value = [value] if operator in expression.NEGATIVE_TERM_OPERATORS: return expression.AND([[ ('supplier_id.available_location_ids', 'not in', value) ], [('supplier_id.available_location_ids', '!=', False)]]) return expression.OR([[ ('supplier_id.available_location_ids', 'in', value) ], [('supplier_id.available_location_ids', '=', False)]])
def _remove_membership(self, partner_ids): """ Unlink (!!!) the relationships between the passed partner_ids and the channels and their slides (done in the unlink of slide.channel.partner model). """ if not partner_ids: raise ValueError( "Do not use this method with an empty partner_id recordset") removed_channel_partner_domain = [] for channel in self: removed_channel_partner_domain = expression.OR([ removed_channel_partner_domain, [('partner_id', 'in', partner_ids), ('channel_id', '=', channel.id)] ]) if removed_channel_partner_domain: self.env['slide.channel.partner'].sudo().search( removed_channel_partner_domain).unlink()
def get_mention_suggestions(self, search, limit=8): """ Return 'limit'-first partners' id, name and email such that the name or email matches a 'search' string. Prioritize users, and then extend the research to all partners. """ search_dom = expression.OR([[('name', 'ilike', search)], [('email', 'ilike', search)]]) fields = ['id', 'name', 'email'] # Search users domain = expression.AND([[('user_ids.id', '!=', False)], search_dom]) users = self.search_read(domain, fields, limit=limit) # Search partners if less than 'limit' users found partners = [] if len(users) < limit: partners = self.search_read(search_dom, fields, limit=limit) # Remove duplicates partners = [ p for p in partners if not len([u for u in users if u['id'] == p['id']]) ] return [users, partners]
def _get_failing(self, for_records, mode='read'): """ Returns the rules for the mode for the current user which fail on the specified records. Can return any global rule and/or all local rules (since local rules are OR-ed together, the entire group succeeds or fails, while global rules get AND-ed and can each fail) """ Model = for_records.browse(()).sudo() eval_context = self._eval_context() all_rules = self._get_rules(Model._name, mode=mode).sudo() # first check if the group rules fail for any record (aka if # searching on (records, group_rules) filters out some of the records) group_rules = all_rules.filtered( lambda r: r.groups and r.groups & self.env.user.groups_id) group_domains = expression.OR([ safe_eval(r.domain_force, eval_context) if r.domain_force else [] for r in group_rules ]) # if all records get returned, the group rules are not failing if Model.search_count( expression.AND([[('id', 'in', for_records.ids)], group_domains])) == len(for_records): group_rules = self.browse(()) # failing rules are previously selected group rules or any failing global rule def is_failing(r, ids=for_records.ids): dom = safe_eval(r.domain_force, eval_context) if r.domain_force else [] return Model.search_count( expression.AND([[('id', 'in', ids)], expression.normalize_domain(dom)])) < len(ids) return all_rules.filtered(lambda r: r in group_rules or (not r.groups and is_failing(r))).with_user( self.env.user)
def _compute_domain(self, model_name, mode="read"): if mode not in self._MODES: raise ValueError('Invalid mode: %r' % (mode,)) if self._uid == SUPERUSER_ID: return None query = """ SELECT r.id FROM ir_rule r JOIN ir_model m ON (r.model_id=m.id) WHERE m.model=%s AND r.active AND r.perm_{mode} AND (r.id IN (SELECT rule_group_id FROM rule_group_rel rg JOIN res_groups_users_rel gu ON (rg.group_id=gu.gid) WHERE gu.uid=%s) OR r.global) """.format(mode=mode) self._cr.execute(query, (model_name, self._uid)) rule_ids = [row[0] for row in self._cr.fetchall()] if not rule_ids: return [] # browse user and rules as SUPERUSER_ID to avoid access errors! eval_context = self._eval_context() user_groups = self.env.user.groups_id global_domains = [] # list of domains group_domains = [] # list of domains for rule in self.browse(rule_ids).sudo(): # evaluate the domain for the current user dom = safe_eval(rule.domain_force, eval_context) if rule.domain_force else [] dom = expression.normalize_domain(dom) if not rule.groups: global_domains.append(dom) elif rule.groups & user_groups: group_domains.append(dom) # combine global domains and group domains if not group_domains: return expression.AND(global_domains) return expression.AND(global_domains + [expression.OR(group_domains)])
def _compute_domain(self, model_name, mode="read"): rules = self._get_rules(model_name, mode=mode) if not rules: return # browse user and rules as SUPERUSER_ID to avoid access errors! eval_context = self._eval_context() user_groups = self.env.user.groups_id global_domains = [] # list of domains group_domains = [] # list of domains for rule in rules.sudo(): # evaluate the domain for the current user dom = safe_eval(rule.domain_force, eval_context) if rule.domain_force else [] dom = expression.normalize_domain(dom) if not rule.groups: global_domains.append(dom) elif rule.groups & user_groups: group_domains.append(dom) # combine global domains and group domains if not group_domains: return expression.AND(global_domains) return expression.AND(global_domains + [expression.OR(group_domains)])
def _default_sale_line_domain(self): domain = super(AccountAnalyticLine, self)._default_sale_line_domain() return expression.OR( [domain, [('qty_delivered_method', '=', 'timesheet')]])
def _get_translation_frontend_modules_domain(cls): domain = super(IrHttp, cls)._get_translation_frontend_modules_domain() return expression.OR([domain, [('name', '=', 'im_livechat')]])
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()
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
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 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: attachments |= self.env['ir.attachment'].create({ 'name': attachment_name[0].text, 'res_id': self.id, 'res_model': 'account.move', 'datas': attachment_data[0].text, 'type': 'binary', }) if attachments: self.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: # 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()