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.

285 lines
11 KiB

10 years ago
10 years ago
  1. # -*- coding: utf-8 -*-
  2. # Python source code encoding : https://www.python.org/dev/peps/pep-0263/
  3. ##############################################################################
  4. #
  5. # OpenERP, Open Source Management Solution
  6. # This module copyright :
  7. # (c) 2015 Antiun Ingenieria, SL (Madrid, Spain, http://www.antiun.com)
  8. # Antonio Espinosa <antonioea@antiun.com>
  9. #
  10. # This program is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU Affero General Public License as
  12. # published by the Free Software Foundation, either version 3 of the
  13. # License, or (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU Affero General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU Affero General Public License
  21. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. ##############################################################################
  24. from openerp import models, api, _
  25. from openerp.exceptions import Warning
  26. import requests
  27. import re
  28. import logging
  29. from lxml import etree
  30. from collections import OrderedDict
  31. logger = logging.getLogger(__name__)
  32. class NaceImport(models.TransientModel):
  33. _name = 'nace.import'
  34. _description = 'Import NACE activities from European RAMON service'
  35. _parents = [False, False, False, False]
  36. _available_langs = {
  37. 'bg_BG': 'BG', # Bulgarian
  38. 'cs_CZ': 'CZ', # Czech
  39. 'da_DK': 'DA', # Danish
  40. 'de_DE': 'DE', # German
  41. 'et_EE': 'EE', # Estonian
  42. 'el_GR': 'EL', # Greek
  43. 'es_AR': 'ES', # Spanish (AR)
  44. 'es_BO': 'ES', # Spanish (BO)
  45. 'es_CL': 'ES', # Spanish (CL)
  46. 'es_CO': 'ES', # Spanish (CO)
  47. 'es_CR': 'ES', # Spanish (CR)
  48. 'es_DO': 'ES', # Spanish (DO)
  49. 'es_EC': 'ES', # Spanish (EC)
  50. 'es_GT': 'ES', # Spanish (GT)
  51. 'es_HN': 'ES', # Spanish (HN)
  52. 'es_MX': 'ES', # Spanish (MX)
  53. 'es_NI': 'ES', # Spanish (NI)
  54. 'es_PA': 'ES', # Spanish (PA)
  55. 'es_PE': 'ES', # Spanish (PE)
  56. 'es_PR': 'ES', # Spanish (PR)
  57. 'es_PY': 'ES', # Spanish (PY)
  58. 'es_SV': 'ES', # Spanish (SV)
  59. 'es_UY': 'ES', # Spanish (UY)
  60. 'es_VE': 'ES', # Spanish (VE)
  61. 'es_ES': 'ES', # Spanish
  62. 'fi_FI': 'FI', # Finnish
  63. 'fr_BE': 'FR', # French (FR)
  64. 'fr_CA': 'FR', # French (CA)
  65. 'fr_CH': 'FR', # French (CH)
  66. 'fr_FR': 'FR', # French
  67. 'hr_HR': 'HR', # Croatian
  68. 'hu_HU': 'HU', # Hungarian
  69. 'it_IT': 'IT', # Italian
  70. 'lt_LT': 'LT', # Lithuanian
  71. 'lv_LV': 'LV', # Latvian
  72. # '': 'MT', # Il-Malti, has no language in Odoo
  73. 'nl_BE': 'NL', # Dutch (BE)
  74. 'nl_NL': 'NL', # Dutch
  75. 'nb_NO': 'NO', # Norwegian Bokmål
  76. 'pl_PL': 'PL', # Polish
  77. 'pt_BR': 'PT', # Portuguese (BR)
  78. 'pt_PT': 'PT', # Portuguese
  79. 'ro_RO': 'RO', # Romanian
  80. 'ru_RU': 'RU', # Russian
  81. 'sl_SI': 'SI', # Slovenian
  82. 'sk_SK': 'SK', # Slovak
  83. 'sv_SE': 'SV', # Swedish
  84. 'tr_TR': 'TR', # Turkish
  85. }
  86. _map = OrderedDict([
  87. ('level', {'xpath': '', 'attrib': 'idLevel', 'type': 'integer',
  88. 'translate': False, 'required': True}),
  89. ('code', {'xpath': '', 'attrib': 'id', 'type': 'string',
  90. 'translate': False, 'required': True}),
  91. ('name', {'xpath': './Label/LabelText', 'type': 'string',
  92. 'translate': True, 'required': True}),
  93. ('generic', {
  94. 'xpath': './Property[@name="Generic"]'
  95. '/PropertyQualifier[@name="Value"]/PropertyText',
  96. 'type': 'string', 'translate': False, 'required': False}),
  97. ('rules', {
  98. 'xpath': './Property[@name="ExplanatoryNote"]'
  99. '/PropertyQualifier[@name="Rules"]/PropertyText',
  100. 'type': 'string', 'translate': False, 'required': False}),
  101. ('central_content', {
  102. 'xpath': './Property[@name="ExplanatoryNote"]'
  103. '/PropertyQualifier[@name="CentralContent"]/PropertyText',
  104. 'type': 'string', 'translate': True, 'required': False}),
  105. ('limit_content', {
  106. 'xpath': './Property[@name="ExplanatoryNote"]'
  107. '/PropertyQualifier[@name="LimitContent"]/PropertyText',
  108. 'type': 'string', 'translate': True, 'required': False}),
  109. ('exclusions', {
  110. 'xpath': './Property[@name="ExplanatoryNote"]'
  111. '/PropertyQualifier[@name="Exclusions"]/PropertyText',
  112. 'type': 'string', 'translate': True, 'required': False}),
  113. ])
  114. _url_base = 'http://ec.europa.eu'
  115. _url_path = '/eurostat/ramon/nomenclatures/index.cfm'
  116. _url_params = {
  117. 'TargetUrl': 'ACT_OTH_CLS_DLD',
  118. 'StrNom': 'NACE_REV2',
  119. 'StrFormat': 'XML',
  120. 'StrLanguageCode': 'EN',
  121. # 'IntKey': '',
  122. # 'IntLevel': '',
  123. # 'TxtDelimiter': ';',
  124. # 'bExport': '',
  125. }
  126. def _check_node(self, node):
  127. if node.get('id') and node.get('idLevel'):
  128. return True
  129. return False
  130. def _mapping(self, node, translate):
  131. item = {}
  132. for k, v in self._map.iteritems():
  133. field_translate = v.get('translate', False)
  134. field_xpath = v.get('xpath', '')
  135. field_attrib = v.get('attrib', False)
  136. field_type = v.get('type', 'string')
  137. field_required = v.get('required', False)
  138. if field_translate == translate:
  139. value = ''
  140. if field_xpath:
  141. n = node.find(field_xpath)
  142. else:
  143. n = node
  144. if n is not None:
  145. if field_attrib:
  146. value = n.get(field_attrib, '')
  147. else:
  148. value = n.text
  149. if field_type == 'integer':
  150. try:
  151. value = int(value)
  152. except:
  153. value = 0
  154. else:
  155. logger.debug("xpath = '%s', not found" % field_xpath)
  156. if field_required and not value:
  157. raise Warning(
  158. _('Value not found for mandatory field %s' % k))
  159. item[k] = value
  160. return item
  161. def _download_nace(self, lang_code):
  162. params = self._url_params.copy()
  163. params['StrLanguageCode'] = lang_code
  164. url = self._url_base + self._url_path + '?'
  165. url += '&'.join([k + '=' + v for k, v in params.iteritems()])
  166. logger.info('Starting to download %s' % url)
  167. try:
  168. res_request = requests.get(url)
  169. except Exception, e:
  170. raise Warning(
  171. _('Got an error when trying to download the file: %s.') %
  172. str(e))
  173. if res_request.status_code != requests.codes.ok:
  174. raise Warning(
  175. _('Got an error %d when trying to download the file %s.')
  176. % (res_request.status_code, url))
  177. logger.info('Download successfully %d bytes' %
  178. len(res_request.content))
  179. # Workaround XML: Remove all characters before <?xml
  180. pattern = re.compile(r'^.*<\?xml', re.DOTALL)
  181. content_fixed = re.sub(pattern, '<?xml', res_request.content)
  182. if not re.match(r'<\?xml', content_fixed):
  183. raise Warning(_('Downloaded file is not a valid XML file'))
  184. return content_fixed
  185. @api.model
  186. def create_or_update_nace(self, node):
  187. if not self._check_node(node):
  188. return False
  189. nace_model = self.env['res.partner.nace']
  190. data = self._mapping(node, False)
  191. data.update(self._mapping(node, True))
  192. level = data.get('level', 0)
  193. if level >= 2 and level <= 5:
  194. data['parent_id'] = self._parents[level - 2]
  195. nace = nace_model.search([('level', '=', data['level']),
  196. ('code', '=', data['code'])])
  197. if nace:
  198. nace.write(data)
  199. else:
  200. nace = nace_model.create(data)
  201. if level >= 1 and level <= 4:
  202. self._parents[level - 1] = nace.id
  203. return nace
  204. @api.model
  205. def translate_nace(self, node, lang):
  206. translation_model = self.env['ir.translation']
  207. nace_model = self.env['res.partner.nace']
  208. index = self._mapping(node, False)
  209. trans = self._mapping(node, True)
  210. nace = nace_model.search([('level', '=', index['level']),
  211. ('code', '=', index['code'])])
  212. if nace:
  213. for field, value in trans.iteritems():
  214. name = 'res.partner.nace,' + field
  215. query = [('res_id', '=', nace.id),
  216. ('type', '=', 'model'),
  217. ('name', '=', name),
  218. ('lang', '=', lang.code)]
  219. translation = translation_model.search(query)
  220. data = {
  221. 'value': value,
  222. 'state': 'translated'
  223. }
  224. if translation:
  225. translation_model.write(data)
  226. else:
  227. data['res_id'] = nace.id
  228. data['type'] = 'model'
  229. data['name'] = name
  230. data['lang'] = lang.code
  231. translation_model.create(data)
  232. @api.one
  233. def run_import(self):
  234. nace_model = self.env['res.partner.nace'].\
  235. with_context(defer_parent_store_computation=True)
  236. lang_model = self.env['res.lang']
  237. # Available lang list
  238. langs = lang_model.search(
  239. [('code', 'in', self._available_langs.keys()),
  240. ('active', '=', True)])
  241. # All current NACEs, delete if not found above
  242. naces_to_delete = nace_model.search([])
  243. # Download NACEs in english, create or update
  244. logger.info('Import NACE Rev.2 English')
  245. xmlcontent = self._download_nace('EN')
  246. dom = etree.fromstring(xmlcontent)
  247. for node in dom.iter('Item'):
  248. logger.debug('Reading level=%s, code=%s' %
  249. (node.get('idLevel', 'N/A'),
  250. node.get('id', 'N/A')))
  251. nace = self.create_or_update_nace(node)
  252. if nace and nace in naces_to_delete:
  253. naces_to_delete -= nace
  254. # Download NACEs in other languages, translate them
  255. for lang in langs:
  256. logger.info('Import NACE Rev.2 %s' % lang.code)
  257. nace_lang = self._available_langs[lang.code]
  258. xmlcontent = self._download_nace(nace_lang)
  259. dom = etree.fromstring(xmlcontent)
  260. for node in dom.iter('Item'):
  261. logger.debug('Reading lang=%s, level=%s, code=%s' %
  262. (nace_lang, node.get('idLevel', 'N/A'),
  263. node.get('id', 'N/A')))
  264. self.translate_nace(node, lang)
  265. # Delete obsolete NACEs
  266. if naces_to_delete:
  267. naces_to_delete.unlink()
  268. logger.info('%d NACEs entries deleted' % len(naces_to_delete))
  269. logger.info(
  270. 'The wizard to create NACEs entries from RAMON '
  271. 'has been successfully completed.')
  272. return True