From abecc08334225bf7d45e45be23ffdaf5f20b7fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20V=C3=A1squez?= <ramon.vasquez@eynes.com.ar> Date: Wed, 15 May 2024 16:18:11 -0300 Subject: [PATCH] [ADD][T4221] Import purchase orders via FTP --- models/customer_purchase_order_importer.py | 192 +++++++++++---------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/models/customer_purchase_order_importer.py b/models/customer_purchase_order_importer.py index 71ef261..efad48c 100644 --- a/models/customer_purchase_order_importer.py +++ b/models/customer_purchase_order_importer.py @@ -9,26 +9,31 @@ import base64 import glob import logging -import re import os import pytz from datetime import datetime, timedelta +from ftplib import FTP +from io import BytesIO from odoo import _, models from odoo.exceptions import UserError -from pathlib import Path _logger = logging.getLogger(__name__) +def connect_ftp(): + ftp = FTP('10.10.15.134') + ftp.login(user='odoo', passwd='Odoo123!') + return ftp + + class CustomerPurchaseOrderImporter(models.Model): _name = 'customer.purchase.order.importer' _description = 'Customer Purchase Order Importer' """The command to mount the remote folder where the import files are stored is: sudo mount -t cifs //10.10.15.134/KrikosDMS/TEST_ORDERS/Documentos/Download/ORDERS \ - /home/docker/volumes/<filestore_volume_name>/_data/filestore/<db_name>/import/purchase_orders \ - -o vers=3.0,uid=1000,gid=1000,forceuid,forcegid,username=odoo,domain=lario.net,password=<password> + /home/Downloads -o vers=3.0,uid=1000,gid=1000,forceuid,forcegid,username=odoo,domain=lario.net,password=Odoo123! To get the filestore volume name, run: docker volume ls @@ -40,6 +45,14 @@ class CustomerPurchaseOrderImporter(models.Model): used for testing purposes only. The original folder will be used in production. """ + def get_lines(self, file_txt): + lines = file_txt.split('\r\n') + + if lines[-1] == '': + lines.pop() + + return lines + def get_full_file_data(self, lines): msg_list = [] address = None @@ -1033,83 +1046,90 @@ class CustomerPurchaseOrderImporter(models.Model): processed_path = paths['processed'] rejected_path = paths['rejected'] - if not manual_import: - _logger.info('Importing PO files from ' + import_path) + # if not manual_import: + # _logger.info('Importing PO files from ' + import_path) + + # files = glob.glob('{}*.*'.format(import_path)) + + ftp = connect_ftp() - files = glob.glob('{}*.*'.format(import_path)) + ftp.cwd(import_path) + files = self.standarize_file_names(import_path, ftp) for _file in files: - _file = self.standarize_file_name(import_path, _file) if not _file: continue po_vals = {} - with open(import_path + _file, 'r', encoding='utf-8') as file: - msg_list = [] + file = BytesIO() + ftp.retrbinary('RETR ' + _file, file.write) + msg_list = [] - with self.env.cr.savepoint(): - file_txt = file.read() - lines = file_txt.split('\n') - file.close() + with self.env.cr.savepoint(): + file_txt = file.getvalue().decode('utf-8') - file_vals = self.get_full_file_data(lines) + lines = self.get_lines(file_txt) - if isinstance(file_vals, list): - partner_name = file_vals.pop(-1) + file.close() - if partner_name not in _file: - rejected_file = _file.replace('ORD_', 'ORD_{}_'.format(partner_name)) + file_vals = self.get_full_file_data(lines) - os.rename(import_path + _file, import_path + rejected_file) + if isinstance(file_vals, list): + partner_name = file_vals.pop(-1) - _file = rejected_file + if partner_name not in _file: + rejected_file = _file.replace('ORD_', 'ORD_{}_'.format(partner_name)) - msg_list.insert(0, _('FILE: {}\n').format(_file)) + ftp.rename(import_path + _file, import_path + rejected_file) - msg_list.extend(file_vals) - not_imported.append(''.join(msg_list) + '\n') + _file = rejected_file - os.rename(import_path + _file, rejected_path + _file) + msg_list.insert(0, _('FILE: {}\n').format(_file)) - continue - else: - partner = file_vals['partner'] - config = self.get_config(partner) - config_type = 'basic' - - if config: - for c in config: - if c.config_type: - if c.config_type != 'internal_code': - config_type = c.config_type - - try: - po_vals.update( - getattr(self, 'get_config_{}_vals'.format(config_type))(file_vals) - ) - except AttributeError: - _type = CustomerPurchaseOrderConfig._fields['config_type'].selection - config_type_name = dict(_type).get(config_type) - - msg_list.insert(0, _('FILE: {}\n').format(_file)) - msg_list.append( - _( - '~ No method has been defined for {}. Please contact ' - 'the developer to fix this issue.\n' - ).format(_('configuration type {}').format(config_type_name)) - + '\n' - ) - not_imported.append(''.join(msg_list)) - continue - - imported.append(_file) + msg_list.extend(file_vals) + not_imported.append(''.join(msg_list) + '\n') - po_vals.update(self._update_file_vals(file_vals)) - po_vals = self._refactor_product_vals_before_import(po_vals) - PreSaleOrder.create(po_vals) + ftp.rename(import_path + _file, rejected_path + _file) - os.rename(import_path + _file, processed_path + _file) + continue + else: + partner = file_vals['partner'] + config = self.get_config(partner) + config_type = 'basic' + + if config: + for c in config: + if c.config_type: + if c.config_type != 'internal_code': + config_type = c.config_type + + try: + po_vals.update( + getattr(self, 'get_config_{}_vals'.format(config_type))(file_vals) + ) + except AttributeError: + _type = CustomerPurchaseOrderConfig._fields['config_type'].selection + config_type_name = dict(_type).get(config_type) + + msg_list.insert(0, _('FILE: {}\n').format(_file)) + msg_list.append( + _( + '~ No method has been defined for {}. Please contact ' + 'the developer to fix this issue.\n' + ).format(_('configuration type {}').format(config_type_name)) + + '\n' + ) + not_imported.append(''.join(msg_list)) + continue + + imported.append(_file) + + po_vals.update(self._update_file_vals(file_vals)) + po_vals = self._refactor_product_vals_before_import(po_vals) + PreSaleOrder.create(po_vals) + + ftp.rename(import_path + _file, processed_path + _file) if len(not_imported) > 0: msg += ( @@ -1118,7 +1138,7 @@ class CustomerPurchaseOrderImporter(models.Model): else '' ) + ''.join(sorted(not_imported)) - self._create_error_file(rejected_path, msg) + # self._create_error_file(rejected_path, msg, ftp) if len(imported) > 0: msg += _('The following files have been imported successfully:\n') + ', '.join(sorted(imported)) @@ -1130,58 +1150,56 @@ class CustomerPurchaseOrderImporter(models.Model): if manual_import: raise UserError(msg) - def _create_error_file(self, path, msg): + def _create_error_file(self, path, datas, ftp): IrAttachment = self.env['ir.attachment'] today = str(datetime.today().date()) - file_name = '_not_imported_{}.txt'.format(today.replace('-', '')) - file = open(path + file_name, 'w+') - file.write(msg) - file.seek(0) - - datas = base64.b64encode((file.read()).encode('utf-8')) - name = _('[{}] Rejected Purchase Order Files').format(today) attachment_id = IrAttachment.search([('name', '=', name)]) + if attachment_id: + datas += base64.b64decode(attachment_id.datas) + + attachment_id.write({'datas': base64.b64encode(datas.encode('utf-8')), 'name': name}) + + file_name = '_not_imported_{}.txt'.format(today.replace('-', '')) + file = BytesIO(bytes(datas, 'utf-8')) + ftp.storbinary('STOR ' + file_name, file) + file.seek(0) if not attachment_id: IrAttachment.create( { 'datas_fname': file_name, - 'datas': datas, + 'datas': base64.b64encode(file.read()), 'name': name, 'type': 'binary', } ) - else: - attachment_id.write({'datas': datas, 'name': name}) file.close() - def standarize_file_name(self, path, old_name): - if '_not_imported_' not in old_name: - name = old_name.replace(path, '') - if name[:4].upper() == 'ORD_' and old_name[-4:].lower() == '.txt': - new_name = 'ORD_{}.txt'.format(name[4:-4]) + def standarize_file_names(self, path, ftp): + files = [] - os.rename(path + name, path + new_name) + old_file_names = ftp.nlst() + for old_name in old_file_names: + if '_not_imported_' not in old_name: + if old_name[:4].upper() == 'ORD_' and old_name[-4:].lower() == '.txt': + new_name = 'ORD_%s.txt' % old_name[4:-4] - return new_name + ftp.rename(path + old_name, path + new_name) - return False + files.append(new_name) - return False + return files def _get_paths(self): IrConfigParameter = self.env['ir.config_parameter'] import_path = IrConfigParameter.sudo().get_param('po_importer_path') - if import_path[-1] != '/': - import_path += '/' processed_path = IrConfigParameter.sudo().get_param('po_importer_processed_path') - if processed_path[-1] != '/': - processed_path += '/' - rejected_path = import_path + 'RECHAZADOS/' + rejected_path = import_path + 'RECHAZADOS\\' + return {'import': import_path, 'processed': processed_path, 'rejected': rejected_path} -- GitLab