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.

286 lines
12 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_PY': 'ES', # Spanish (PY)
  62. 'es_ES': 'ES', # Spanish
  63. 'fi_FI': 'FI', # Finnish
  64. 'fr_BE': 'FR', # French (FR)
  65. 'fr_CA': 'FR', # French (CA)
  66. 'fr_CH': 'FR', # French (CH)
  67. 'fr_FR': 'FR', # French
  68. 'hr_HR': 'HR', # Croatian
  69. 'hu_HU': 'HU', # Hungarian
  70. 'it_IT': 'IT', # Italian
  71. 'lt_LT': 'LT', # Lithuanian
  72. 'lv_LV': 'LV', # Latvian
  73. # '': 'MT', # Il-Malti, has no language in Odoo
  74. 'nl_BE': 'NL', # Dutch (BE)
  75. 'nl_NL': 'NL', # Dutch
  76. 'nb_NO': 'NO', # Norwegian Bokmål
  77. 'pl_PL': 'PL', # Polish
  78. 'pt_BR': 'PT', # Portuguese (BR)
  79. 'pt_PT': 'PT', # Portuguese
  80. 'ro_RO': 'RO', # Romanian
  81. 'ru_RU': 'RU', # Russian
  82. 'sl_SI': 'SI', # Slovenian
  83. 'sk_SK': 'SK', # Slovak
  84. 'sv_SE': 'SV', # Swedish
  85. 'tr_TR': 'TR', # Turkish
  86. }
  87. _map = OrderedDict([
  88. ('level', {'xpath': '', 'attrib': 'idLevel', 'type': 'integer',
  89. 'translate': False, 'required': True}),
  90. ('code', {'xpath': '', 'attrib': 'id', 'type': 'string',
  91. 'translate': False, 'required': True}),
  92. ('name', {'xpath': './Label/LabelText', 'type': 'string',
  93. 'translate': True, 'required': True}),
  94. ('generic', {
  95. 'xpath': './Property[@name="Generic"]'
  96. '/PropertyQualifier[@name="Value"]/PropertyText',
  97. 'type': 'string', 'translate': False, 'required': False}),
  98. ('rules', {
  99. 'xpath': './Property[@name="ExplanatoryNote"]'
  100. '/PropertyQualifier[@name="Rules"]/PropertyText',
  101. 'type': 'string', 'translate': False, 'required': False}),
  102. ('central_content', {
  103. 'xpath': './Property[@name="ExplanatoryNote"]'
  104. '/PropertyQualifier[@name="CentralContent"]/PropertyText',
  105. 'type': 'string', 'translate': True, 'required': False}),
  106. ('limit_content', {
  107. 'xpath': './Property[@name="ExplanatoryNote"]'
  108. '/PropertyQualifier[@name="LimitContent"]/PropertyText',
  109. 'type': 'string', 'translate': True, 'required': False}),
  110. ('exclusions', {
  111. 'xpath': './Property[@name="ExplanatoryNote"]'
  112. '/PropertyQualifier[@name="Exclusions"]/PropertyText',
  113. 'type': 'string', 'translate': True, 'required': False}),
  114. ])
  115. _url_base = 'http://ec.europa.eu'
  116. _url_path = '/eurostat/ramon/nomenclatures/index.cfm'
  117. _url_params = {
  118. 'TargetUrl': 'ACT_OTH_CLS_DLD',
  119. 'StrNom': 'NACE_REV2',
  120. 'StrFormat': 'XML',
  121. 'StrLanguageCode': 'EN',
  122. # 'IntKey': '',
  123. # 'IntLevel': '',
  124. # 'TxtDelimiter': ';',
  125. # 'bExport': '',
  126. }
  127. def _check_node(self, node):
  128. if node.get('id') and node.get('idLevel'):
  129. return True
  130. return False
  131. def _mapping(self, node, translate):
  132. item = {}
  133. for k, v in self._map.iteritems():
  134. field_translate = v.get('translate', False)
  135. field_xpath = v.get('xpath', '')
  136. field_attrib = v.get('attrib', False)
  137. field_type = v.get('type', 'string')
  138. field_required = v.get('required', False)
  139. if field_translate == translate:
  140. value = ''
  141. if field_xpath:
  142. n = node.find(field_xpath)
  143. else:
  144. n = node
  145. if n is not None:
  146. if field_attrib:
  147. value = n.get(field_attrib, '')
  148. else:
  149. value = n.text
  150. if field_type == 'integer':
  151. try:
  152. value = int(value)
  153. except:
  154. value = 0
  155. else:
  156. logger.debug("xpath = '%s', not found" % field_xpath)
  157. if field_required and not value:
  158. raise Warning(
  159. _('Value not found for mandatory field %s' % k))
  160. item[k] = value
  161. return item
  162. def _download_nace(self, lang_code):
  163. params = self._url_params.copy()
  164. params['StrLanguageCode'] = lang_code
  165. url = self._url_base + self._url_path + '?'
  166. url += '&'.join([k + '=' + v for k, v in params.iteritems()])
  167. logger.info('Starting to download %s' % url)
  168. try:
  169. res_request = requests.get(url)
  170. except Exception, e:
  171. raise Warning(
  172. _('Got an error when trying to download the file: %s.') %
  173. str(e))
  174. if res_request.status_code != requests.codes.ok:
  175. raise Warning(
  176. _('Got an error %d when trying to download the file %s.')
  177. % (res_request.status_code, url))
  178. logger.info('Download successfully %d bytes' %
  179. len(res_request.content))
  180. # Workaround XML: Remove all characters before <?xml
  181. pattern = re.compile(r'^.*<\?xml', re.DOTALL)
  182. content_fixed = re.sub(pattern, '<?xml', res_request.content)
  183. if not re.match(r'<\?xml', content_fixed):
  184. raise Warning(_('Downloaded file is not a valid XML file'))
  185. return content_fixed
  186. @api.model
  187. def create_or_update_nace(self, node):
  188. if not self._check_node(node):
  189. return False
  190. nace_model = self.env['res.partner.nace']
  191. data = self._mapping(node, False)
  192. data.update(self._mapping(node, True))
  193. level = data.get('level', 0)
  194. if level >= 2 and level <= 5:
  195. data['parent_id'] = self._parents[level - 2]
  196. nace = nace_model.search([('level', '=', data['level']),
  197. ('code', '=', data['code'])])
  198. if nace:
  199. nace.write(data)
  200. else:
  201. nace = nace_model.create(data)
  202. if level >= 1 and level <= 4:
  203. self._parents[level - 1] = nace.id
  204. return nace
  205. @api.model
  206. def translate_nace(self, node, lang):
  207. translation_model = self.env['ir.translation']
  208. nace_model = self.env['res.partner.nace']
  209. index = self._mapping(node, False)
  210. trans = self._mapping(node, True)
  211. nace = nace_model.search([('level', '=', index['level']),
  212. ('code', '=', index['code'])])
  213. if nace:
  214. for field, value in trans.iteritems():
  215. name = 'res.partner.nace,' + field
  216. query = [('res_id', '=', nace.id),
  217. ('type', '=', 'model'),
  218. ('name', '=', name),
  219. ('lang', '=', lang.code)]
  220. translation = translation_model.search(query)
  221. data = {
  222. 'value': value,
  223. 'state': 'translated'
  224. }
  225. if translation:
  226. translation_model.write(data)
  227. else:
  228. data['res_id'] = nace.id
  229. data['type'] = 'model'
  230. data['name'] = name
  231. data['lang'] = lang.code
  232. translation_model.create(data)
  233. @api.one
  234. def run_import(self):
  235. nace_model = self.env['res.partner.nace'].\
  236. with_context(defer_parent_store_computation=True)
  237. lang_model = self.env['res.lang']
  238. # Available lang list
  239. langs = lang_model.search(
  240. [('code', 'in', self._available_langs.keys()),
  241. ('active', '=', True)])
  242. # All current NACEs, delete if not found above
  243. naces_to_delete = nace_model.search([])
  244. # Download NACEs in english, create or update
  245. logger.info('Import NACE Rev.2 English')
  246. xmlcontent = self._download_nace('EN')
  247. dom = etree.fromstring(xmlcontent)
  248. for node in dom.iter('Item'):
  249. logger.debug('Reading level=%s, code=%s' %
  250. (node.get('idLevel', 'N/A'),
  251. node.get('id', 'N/A')))
  252. nace = self.create_or_update_nace(node)
  253. if nace and nace in naces_to_delete:
  254. naces_to_delete -= nace
  255. # Download NACEs in other languages, translate them
  256. for lang in langs:
  257. logger.info('Import NACE Rev.2 %s' % lang.code)
  258. nace_lang = self._available_langs[lang.code]
  259. xmlcontent = self._download_nace(nace_lang)
  260. dom = etree.fromstring(xmlcontent)
  261. for node in dom.iter('Item'):
  262. logger.debug('Reading lang=%s, level=%s, code=%s' %
  263. (nace_lang, node.get('idLevel', 'N/A'),
  264. node.get('id', 'N/A')))
  265. self.translate_nace(node, lang)
  266. # Delete obsolete NACEs
  267. if naces_to_delete:
  268. naces_to_delete.unlink()
  269. logger.info('%d NACEs entries deleted' % len(naces_to_delete))
  270. logger.info(
  271. 'The wizard to create NACEs entries from RAMON '
  272. 'has been successfully completed.')
  273. return True