1# -*- coding: utf-8 -*- 2 3import re 4 5from collections.abc import Iterable 6 7from odoo import api, fields, models, _ 8from odoo.osv import expression 9from odoo.exceptions import UserError 10 11import werkzeug.urls 12 13def sanitize_account_number(acc_number): 14 if acc_number: 15 return re.sub(r'\W+', '', acc_number).upper() 16 return False 17 18 19class Bank(models.Model): 20 _description = 'Bank' 21 _name = 'res.bank' 22 _order = 'name' 23 24 name = fields.Char(required=True) 25 street = fields.Char() 26 street2 = fields.Char() 27 zip = fields.Char() 28 city = fields.Char() 29 state = fields.Many2one('res.country.state', 'Fed. State', domain="[('country_id', '=?', country)]") 30 country = fields.Many2one('res.country') 31 email = fields.Char() 32 phone = fields.Char() 33 active = fields.Boolean(default=True) 34 bic = fields.Char('Bank Identifier Code', index=True, help="Sometimes called BIC or Swift.") 35 36 def name_get(self): 37 result = [] 38 for bank in self: 39 name = bank.name + (bank.bic and (' - ' + bank.bic) or '') 40 result.append((bank.id, name)) 41 return result 42 43 @api.model 44 def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): 45 args = args or [] 46 domain = [] 47 if name: 48 domain = ['|', ('bic', '=ilike', name + '%'), ('name', operator, name)] 49 if operator in expression.NEGATIVE_TERM_OPERATORS: 50 domain = ['&'] + domain 51 return self._search(domain + args, limit=limit, access_rights_uid=name_get_uid) 52 53 @api.onchange('country') 54 def _onchange_country_id(self): 55 if self.country and self.country != self.state.country_id: 56 self.state = False 57 58 @api.onchange('state') 59 def _onchange_state(self): 60 if self.state.country_id: 61 self.country = self.state.country_id 62 63 64class ResPartnerBank(models.Model): 65 _name = 'res.partner.bank' 66 _rec_name = 'acc_number' 67 _description = 'Bank Accounts' 68 _order = 'sequence, id' 69 70 @api.model 71 def get_supported_account_types(self): 72 return self._get_supported_account_types() 73 74 @api.model 75 def _get_supported_account_types(self): 76 return [('bank', _('Normal'))] 77 78 active = fields.Boolean(default=True) 79 acc_type = fields.Selection(selection=lambda x: x.env['res.partner.bank'].get_supported_account_types(), compute='_compute_acc_type', string='Type', help='Bank account type: Normal or IBAN. Inferred from the bank account number.') 80 acc_number = fields.Char('Account Number', required=True) 81 sanitized_acc_number = fields.Char(compute='_compute_sanitized_acc_number', string='Sanitized Account Number', readonly=True, store=True) 82 acc_holder_name = fields.Char(string='Account Holder Name', help="Account holder name, in case it is different than the name of the Account Holder") 83 partner_id = fields.Many2one('res.partner', 'Account Holder', ondelete='cascade', index=True, domain=['|', ('is_company', '=', True), ('parent_id', '=', False)], required=True) 84 bank_id = fields.Many2one('res.bank', string='Bank') 85 bank_name = fields.Char(related='bank_id.name', readonly=False) 86 bank_bic = fields.Char(related='bank_id.bic', readonly=False) 87 sequence = fields.Integer(default=10) 88 currency_id = fields.Many2one('res.currency', string='Currency') 89 company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.company, ondelete='cascade', readonly=True) 90 91 _sql_constraints = [ 92 ('unique_number', 'unique(sanitized_acc_number, company_id)', 'Account Number must be unique'), 93 ] 94 95 @api.depends('acc_number') 96 def _compute_sanitized_acc_number(self): 97 for bank in self: 98 bank.sanitized_acc_number = sanitize_account_number(bank.acc_number) 99 100 @api.depends('acc_number') 101 def _compute_acc_type(self): 102 for bank in self: 103 bank.acc_type = self.retrieve_acc_type(bank.acc_number) 104 105 @api.model 106 def retrieve_acc_type(self, acc_number): 107 """ To be overridden by subclasses in order to support other account_types. 108 """ 109 return 'bank' 110 111 @api.model 112 def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): 113 pos = 0 114 while pos < len(args): 115 # DLE P14 116 if args[pos][0] == 'acc_number': 117 op = args[pos][1] 118 value = args[pos][2] 119 if not isinstance(value, str) and isinstance(value, Iterable): 120 value = [sanitize_account_number(i) for i in value] 121 else: 122 value = sanitize_account_number(value) 123 if 'like' in op: 124 value = '%' + value + '%' 125 args[pos] = ('sanitized_acc_number', op, value) 126 pos += 1 127 return super(ResPartnerBank, self)._search(args, offset, limit, order, count=count, access_rights_uid=access_rights_uid) 128