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.

265 lines
10 KiB

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