# © 2019 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import csv import io import datetime import logging from ftplib import FTP from odoo import models, fields, exceptions _logger = logging.getLogger(__name__) class CgscopRigaOdooImport(models.Model): """ Ce modèle permet de logguer les imports de RIGA vers Odoo """ _name = 'riga.odoo.import' _description = 'Synchronisation RIGA vers Odoo' _rec_name = 'model_id' _order = 'create_date desc' model_id = fields.Many2one( comodel_name='ir.model', string='Modèle') is_sync = fields.Boolean('Synchronisé', default=False) is_warning = fields.Boolean('Warning', default=False) log = fields.Text('Log') is_error = fields.Boolean('Erreur Synchro', default=False) is_resync = fields.Boolean('Re-Synchronisé', default=False) filename = fields.Char('Nom du fichier') # ------------------------------------------------------ # Fonctions génériques # ------------------------------------------------------ def _get_ftp_file(self, filename, ftp_channel): """ Fonction de récupértion d'ufichier sur un FTP. Cette fonction prend en paramètre un nom de fichier et recherche dans le fichier de conf Odoo les login et password, puis dans les paramètres système l'url et le path pour se connecter et retourner le fichier binaire souhaité. : param filename: (string) nom du fichier @return csv_file: (binary) fichier binaire à traiter """ param = ftp_channel ftp_login = param.login ftp_pass = param.password # Connexion FTP try: # Connexion ftp ftp = FTP(param.url, ftp_login, ftp_pass) # Changement de path ftp.cwd(param.path) # Lecture du fichier csv_file = io.BytesIO() ftp.retrbinary( "RETR " + filename, csv_file.write) _logger.info( "Connecté à l'URL : %s - Path : %s", param.url, param.path ) return csv_file except Exception as e: raise exceptions.Warning(e) def _read_file(self, model, file, table, primary_key, header_key, filename, resync=False, log_id=None): """ Cette fonction prend en paramètre un fichier CSV et lit chaque ligne pour mettre à jour la ligne de la table correspondante si elle existe ou la créer si l'organisme existe :param model (obj): modèle pour importer les données :param file (binary): fichier CSV à traiter :param table (list): liste de tuples de correspondance ('champ_odoo', 'champ_csv') :param primary_key (string): header du fichier CSV correspondant à la clé :param parent_id (string): header du fichier CSV pour lequel on va rechercher l'ID RIGA de l'organisme :param filename (string): nom du fichier :param resync (bool): le fichier a été synchronisé manuellement :param log_id (obj): ojjet à mettre à jour si pas de création """ try: file.seek(0) # Création du lecteur CSV. reader = csv.DictReader( io.TextIOWrapper(file, encoding="utf-8-sig", newline=None), delimiter=';') model_obj = self.env[model.model] crea_nb = 0 maj_nb = 0 log = "" # Lecture de chaque ligne for row in reader: # Création de l'objet vals = {} for field in table: vals.update({ field[0]: self._cast_type( model=model, field=field[0], value=row[field[1]], relation=field[2]) }) # Vérification de la ligne dans la base line = model_obj.search([ ['id_riga', '=', row[primary_key]]]) # Si il y a un enregistrement, on met à jour if line: line.write(vals) maj_nb += 1 else: # Check d'un organisme dans la base partner = self.env['res.partner'].search([ ['id_riga', '=', row[header_key]], ['is_cooperative', '=', True]]) # Si l'organisme existe, on crée un enregistrement if partner: vals.update({ 'partner_id': partner.id }) model_obj.create(vals) crea_nb += 1 else: log += ("Pas d'enredistrement trouvé pour l'id " + row[primary_key] + " avec l'id riga " + row[header_key] + "\n") # Création du log vals = { 'model_id': model.id, 'is_sync': True, 'filename': filename, 'log': ("Import du fichier " + filename + date_value.strftime(data.suffix) + '.' + data.riga_extension + " : \n" + " - Création de " + str(crea_nb) + " lignes\n" + " - Mise à jour de " + str(maj_nb) + " lignes\n\n" + log), 'is_warning': True if log else False, 'is_resync': resync, } if not log_id: self.create(vals) else: log_id.update(vals) except Exception as e: _logger.error(e.__str__()) # Création du log vals = { 'model_id': model.id, 'is_sync': False, 'is_error': True, 'log': str(e) } if not log_id: self.create(vals) else: log_id.update(vals) def _cast_type(self, model, field, value, relation=None): """ Détermine en fonction du champ la valeur typée à renvoyer pour l'ORM :param model : objet modèle de donnée Odoo :param field : nom du champ :param value : valeur à caster @return valeur castée """ odoo_field = self.env['ir.model.fields'].search([ ['model', '=', model.model], ['name', '=', field]]) field_type = odoo_field.ttype if value: if field_type == 'integer': return int(float(value.replace(',', '.'))) elif field_type == 'float': return float(value.replace(',', '.')) elif field_type == 'date' or field_type == 'datetime': date_value = datetime.datetime.strptime( value, '%d/%m/%Y %H:%M:%S') return date_value elif field_type == 'selection': selection = self.env[model.model]._fields.get( odoo_field.name).selection for item in selection: if item[1] == value: return item[0] elif field_type == 'many2one': m2o_value = self.env[odoo_field.relation].search([ [relation, '=', value]]).id return m2o_value else: return value else: return False # ------------------------------------------------------ # Import données # ------------------------------------------------------ def odoo_sync(self): model_sync = self.env['riga.files.matching'].sudo().search([ ['is_active', '=', True]]) self.sync_file(model_sync) def sync_file(self, model_sync): """ Fonction de synchronisation des fichiers CSV RIGA avec les tables associées Odoo. La fonction recherche l'ensembles des synchronisations actives dans la table riga.files.matching, elle transforme la table de correspondance de string à list puis appelle la fonction _read_file qui permet d'intégrer ou de metre à jour les valeurs """ for model in model_sync: table = [] # Calcul nom du fichier if model.params_active: date_value = fields.Datetime.now() + datetime.timedelta(days=model.day) filename = (model.riga_filename + date_value.strftime(model.suffix) + '.' + model.riga_extension) else: filename = (model.riga_filename + '.' + model.riga_extension) _logger.info( "Lecture Fichier %s pour le modèle %s", filename, model.model_id.name ) for header in model.matching_table_ids: table.append([header.name, header.riga_name, header.relation]) self._read_file( model=model.model_id, file=self._get_ftp_file(filename, model.ftp_channel_id), table=table, primary_key=model.primary_key, header_key=model.header_key, filename=filename) def resync_file(self): """ Fonction appelée pour la resynchronisation d'un log en erreur """ model = self.env['riga.files.matching'].sudo().search([ ['model_id', '=', self.model_id.id]]) table = [] for header in model.matching_table_ids: table.append([header.name, header.riga_name, header.relation]) self._read_file( model=self.model_id, file=self._get_ftp_file(self.filename, model.ftp_channel_id), table=table, primary_key=model.primary_key, header_key=model.header_key, resync=True, log_id=self, filename=self.filename)