You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

338 lines
12 KiB

  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
  5. # All Rights Reserved
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as published
  9. # by the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. from openerp.tools.translate import _
  22. from openerp.addons.account_banking import sepa
  23. from openerp.addons.account_banking.struct import struct
  24. __all__ = [
  25. 'get_period',
  26. 'get_bank_accounts',
  27. 'get_partner',
  28. 'get_country_id',
  29. 'get_company_bank_account',
  30. 'create_bank_account',
  31. ]
  32. def get_period(pool, cr, uid, date, company, log=None):
  33. '''
  34. Wrapper over account_period.find() to log exceptions of
  35. missing periods instead of raising.
  36. '''
  37. context = {'account_period_prefer_normal': True}
  38. if company:
  39. context['company_id'] = company.id
  40. try:
  41. period_ids = pool.get('account.period').find(
  42. cr, uid, dt=date, context=context)
  43. except Exception as e:
  44. if log is None:
  45. raise
  46. else:
  47. log.append(e)
  48. return False
  49. return period_ids[0]
  50. def get_bank_accounts(pool, cr, uid, account_number, log, fail=False):
  51. '''
  52. Get the bank account with account number account_number
  53. '''
  54. # No need to search for nothing
  55. if not account_number:
  56. return []
  57. partner_bank_obj = pool.get('res.partner.bank')
  58. bank_account_ids = partner_bank_obj.search(cr, uid, [
  59. ('acc_number', '=', account_number)
  60. ])
  61. if not bank_account_ids:
  62. if not fail:
  63. log.append(
  64. _('Bank account %(account_no)s was not found in the database')
  65. % dict(account_no=account_number)
  66. )
  67. return []
  68. return partner_bank_obj.browse(cr, uid, bank_account_ids)
  69. def _has_attr(obj, attr):
  70. # Needed for dangling addresses and a weird exception scheme in
  71. # OpenERP's orm.
  72. try:
  73. return bool(getattr(obj, attr))
  74. except KeyError:
  75. return False
  76. def get_partner(pool, cr, uid, name, address, postal_code, city,
  77. country_id, log, context=None):
  78. '''
  79. Get the partner belonging to the account holders name <name>
  80. If multiple partners are found with the same name, select the first and
  81. add a warning to the import log.
  82. TODO: revive the search by lines from the address argument
  83. '''
  84. partner_obj = pool.get('res.partner')
  85. partner_ids = partner_obj.search(
  86. cr, uid, [
  87. '|', ('is_company', '=', True), ('parent_id', '=', False),
  88. ('name', 'ilike', name),
  89. ], context=context)
  90. if not partner_ids:
  91. # Try brute search on address and then match reverse
  92. criteria = []
  93. if country_id:
  94. criteria.append(('country_id', '=', country_id))
  95. if city:
  96. criteria.append(('city', 'ilike', city))
  97. if postal_code:
  98. criteria.append(('zip', 'ilike', postal_code))
  99. partner_search_ids = partner_obj.search(
  100. cr, uid, criteria, context=context)
  101. if (not partner_search_ids and country_id):
  102. # Try again with country_id = False
  103. criteria[0] = ('country_id', '=', False)
  104. partner_search_ids = partner_obj.search(
  105. cr, uid, criteria, context=context)
  106. key = name.lower()
  107. partners = []
  108. for partner in partner_obj.read(
  109. cr, uid, partner_search_ids, ['name', 'commercial_partner_id'],
  110. context=context):
  111. if (len(partner['name']) > 3 and partner['name'].lower() in key):
  112. partners.append(partner)
  113. partners.sort(key=lambda x: len(x['name']), reverse=True)
  114. partner_ids = [x['commercial_partner_id'][0] for x in partners]
  115. if len(partner_ids) > 1:
  116. log.append(
  117. _('More than one possible match found for partner with '
  118. 'name %(name)s') % {'name': name})
  119. return partner_ids and partner_ids[0] or False
  120. def get_company_bank_account(pool, cr, uid, account_number, currency,
  121. company, log):
  122. '''
  123. Get the matching bank account for this company. Currency is the ISO code
  124. for the requested currency.
  125. '''
  126. results = struct()
  127. bank_accounts = get_bank_accounts(pool, cr, uid, account_number, log,
  128. fail=True)
  129. if not bank_accounts:
  130. return False
  131. elif len(bank_accounts) != 1:
  132. log.append(
  133. _('More than one bank account was found with the same number '
  134. '%(account_no)s') % dict(account_no=account_number)
  135. )
  136. return False
  137. if bank_accounts[0].partner_id.id != company.partner_id.id:
  138. log.append(
  139. _('Account %(account_no)s is not owned by %(partner)s')
  140. % dict(account_no=account_number,
  141. partner=company.partner_id.name,
  142. ))
  143. return False
  144. results.account = bank_accounts[0]
  145. bank_settings_obj = pool.get('account.banking.account.settings')
  146. criteria = [('partner_bank_id', '=', bank_accounts[0].id)]
  147. # Find matching journal for currency
  148. journal_obj = pool.get('account.journal')
  149. journal_ids = journal_obj.search(cr, uid, [
  150. ('type', '=', 'bank'),
  151. ('currency.name', '=', currency or company.currency_id.name)
  152. ])
  153. if currency == company.currency_id.name:
  154. journal_ids_no_curr = journal_obj.search(cr, uid, [
  155. ('type', '=', 'bank'), ('currency', '=', False)
  156. ])
  157. journal_ids.extend(journal_ids_no_curr)
  158. if journal_ids:
  159. criteria.append(('journal_id', 'in', journal_ids))
  160. # Find bank account settings
  161. bank_settings_ids = bank_settings_obj.search(cr, uid, criteria)
  162. if bank_settings_ids:
  163. settings = bank_settings_obj.browse(cr, uid, bank_settings_ids)[0]
  164. results.company_id = company
  165. results.journal_id = settings.journal_id
  166. # Take currency from settings or from company
  167. if settings.journal_id.currency.id:
  168. results.currency_id = settings.journal_id.currency
  169. else:
  170. results.currency_id = company.currency_id
  171. # Rest just copy/paste from settings. Why am I doing this?
  172. results.default_debit_account_id = settings.default_debit_account_id
  173. results.default_credit_account_id = settings.default_credit_account_id
  174. results.costs_account_id = settings.costs_account_id
  175. results.invoice_journal_id = settings.invoice_journal_id
  176. results.bank_partner_id = settings.bank_partner_id
  177. return results
  178. def get_or_create_bank(pool, cr, uid, bic, online=False, code=None,
  179. name=None, context=None):
  180. '''
  181. Find or create the bank with the provided BIC code.
  182. When online, the SWIFT database will be consulted in order to
  183. provide for missing information.
  184. '''
  185. # UPDATE: Free SWIFT databases are since 2/22/2010 hidden behind an
  186. # image challenge/response interface.
  187. bank_obj = pool.get('res.bank')
  188. # Self generated key?
  189. if len(bic) < 8:
  190. # search key
  191. bank_ids = bank_obj.search(
  192. cr, uid, [
  193. ('bic', '=', bic[:6])
  194. ])
  195. if not bank_ids:
  196. bank_ids = bank_obj.search(
  197. cr, uid, [
  198. ('bic', 'ilike', bic + '%')
  199. ])
  200. else:
  201. bank_ids = bank_obj.search(
  202. cr, uid, [
  203. ('bic', '=', bic)
  204. ])
  205. if bank_ids and len(bank_ids) == 1:
  206. banks = bank_obj.browse(cr, uid, bank_ids)
  207. return banks[0].id, banks[0].country.id
  208. country_obj = pool.get('res.country')
  209. country_ids = country_obj.search(
  210. cr, uid, [('code', '=', bic[4:6])]
  211. )
  212. country_id = country_ids and country_ids[0] or False
  213. bank_id = False
  214. if online:
  215. info, address = bank_obj.online_bank_info(
  216. cr, uid, bic, context=context
  217. )
  218. if info:
  219. bank_id = bank_obj.create(cr, uid, dict(
  220. code=info.code,
  221. name=info.name,
  222. street=address.street,
  223. street2=address.street2,
  224. zip=address.zip,
  225. city=address.city,
  226. country=country_id,
  227. bic=info.bic[:8],
  228. ))
  229. else:
  230. info = struct(name=name, code=code)
  231. if not online or not bank_id:
  232. bank_id = bank_obj.create(cr, uid, dict(
  233. code=info.code or 'UNKNOW', # FIXME: Typo?
  234. name=info.name or _('Unknown Bank'),
  235. country=country_id,
  236. bic=bic,
  237. ))
  238. return bank_id, country_id
  239. def get_country_id(pool, cr, uid, transaction, context=None):
  240. """
  241. Derive a country id from the info on the transaction.
  242. :param transaction: browse record of a transaction
  243. :returns: res.country id or False
  244. """
  245. country_code = False
  246. iban = sepa.IBAN(transaction.remote_account)
  247. if iban.valid:
  248. country_code = iban.countrycode
  249. elif transaction.remote_owner_country_code:
  250. country_code = transaction.remote_owner_country_code
  251. # fallback on the import parsers country code
  252. elif transaction.bank_country_code:
  253. country_code = transaction.bank_country_code
  254. if country_code:
  255. country_ids = pool.get('res.country').search(
  256. cr, uid, [('code', '=', country_code.upper())],
  257. context=context)
  258. country_id = country_ids and country_ids[0] or False
  259. if not country_id:
  260. company = transaction.statement_line_id.company_id
  261. if company.partner_id.country:
  262. country_id = company.partner_id.country.id
  263. return country_id
  264. def create_bank_account(pool, cr, uid, partner_id,
  265. account_number, holder_name, address, city,
  266. country_id, bic=False,
  267. context=None):
  268. '''
  269. Create a matching bank account with this holder for this partner.
  270. '''
  271. values = struct(
  272. partner_id=partner_id,
  273. owner_name=holder_name,
  274. country_id=country_id,
  275. )
  276. # Are we dealing with IBAN?
  277. iban = sepa.IBAN(account_number)
  278. if iban.valid:
  279. # Take as much info as possible from IBAN
  280. values.state = 'iban'
  281. values.acc_number = str(iban)
  282. else:
  283. # No, try to convert to IBAN
  284. values.state = 'bank'
  285. values.acc_number = account_number
  286. if country_id:
  287. country_code = pool.get('res.country').read(
  288. cr, uid, country_id, ['code'], context=context)['code']
  289. if country_code in sepa.IBAN.countries:
  290. account_info = pool['res.partner.bank'].online_account_info(
  291. cr, uid, country_code, values.acc_number, context=context)
  292. if account_info:
  293. values.acc_number = iban = account_info.iban
  294. values.state = 'iban'
  295. bic = account_info.bic
  296. if bic:
  297. values.bank = get_or_create_bank(pool, cr, uid, bic)[0]
  298. values.bank_bic = bic
  299. # Create bank account and return
  300. return pool.get('res.partner.bank').create(
  301. cr, uid, values, context=context)