From cc8782c3cdb64cc504584e587f829fdc9188097c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20V=C3=A1squez?= <ramon.vasquez@eynes.com.ar> Date: Thu, 16 May 2024 17:44:25 -0300 Subject: [PATCH] [FIX][T4221] Purchase order manual importer; rejected files --- models/customer_purchase_order_importer.py | 211 +++++------------- ...customer_purchase_order_importer_wizard.py | 46 ++-- wizard/rejected_import_wizard.py | 3 +- 3 files changed, 84 insertions(+), 176 deletions(-) diff --git a/models/customer_purchase_order_importer.py b/models/customer_purchase_order_importer.py index efad48c..f187cde 100644 --- a/models/customer_purchase_order_importer.py +++ b/models/customer_purchase_order_importer.py @@ -6,10 +6,8 @@ # ############################################################################## -import base64 -import glob +from base64 import b64decode, b64encode import logging -import os import pytz from datetime import datetime, timedelta from ftplib import FTP @@ -31,20 +29,6 @@ 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/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 - - Within the folder <db_name>, create a folder named 'import' and within it, another one named - 'purchase_orders'. This folder is the mount point. - - IMPORTANT: The folder TEST_ORDERS is a copy of the original folder ORDERS. This folder is - 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') @@ -421,14 +405,8 @@ class CustomerPurchaseOrderImporter(models.Model): 'modified': additional_line_expense, }, 'box_qty': {'original': box_qty, 'modified': box_qty}, - 'box_qty_ean_set': { - 'original': box_qty_ean_set, - 'modified': box_qty_ean_set, - }, - 'box_qty_pallets': { - 'original': box_qty_pallets, - 'modified': box_qty_pallets, - }, + 'box_qty_ean_set': {'original': box_qty_ean_set, 'modified': box_qty_ean_set}, + 'box_qty_pallets': {'original': box_qty_pallets, 'modified': box_qty_pallets}, 'comeback': {'original': comeback, 'modified': comeback}, 'discount1': {'original': discount1, 'modified': discount1}, 'discount2': {'original': discount2, 'modified': discount2}, @@ -438,63 +416,30 @@ class CustomerPurchaseOrderImporter(models.Model): 'discount6': {'original': discount6, 'modified': discount6}, 'ean13_code': {'original': ean13_code, 'modified': ean13_code}, 'errors': [], - 'gross_price_unit': { - 'original': gross_price_unit, - 'modified': gross_price_unit, - }, - 'internal_tax': { - 'original': internal_tax, - 'modified': internal_tax, - }, + 'gross_price_unit': {'original': gross_price_unit, 'modified': gross_price_unit}, + 'internal_tax': {'original': internal_tax, 'modified': internal_tax}, 'item_colour_description': { 'original': item_colour_description, 'modified': item_colour_description, }, - 'item_description1': { - 'original': item_description1, - 'modified': item_description1, - }, - 'item_description2': { - 'original': item_description2, - 'modified': item_description2, - }, - 'item_description3': { - 'original': item_description3, - 'modified': item_description3, - }, - 'item_description4': { - 'original': item_description4, - 'modified': item_description4, - }, + 'item_description1': {'original': item_description1, 'modified': item_description1}, + 'item_description2': {'original': item_description2, 'modified': item_description2}, + 'item_description3': {'original': item_description3, 'modified': item_description3}, + 'item_description4': {'original': item_description4, 'modified': item_description4}, 'item_short_description': { 'original': item_short_description, 'modified': item_short_description, }, - 'item_size_description': { - 'original': item_size_description, - 'modified': item_size_description, - }, - 'line_number': { - 'original': line_number, - 'modified': line_number, - }, - 'line_total_amount': { - 'original': line_total_amount, - 'modified': line_total_amount, - }, - 'net_price_unit': { - 'original': net_price_unit, - 'modified': net_price_unit, - }, + 'item_size_description': {'original': item_size_description, 'modified': item_size_description}, + 'line_number': {'original': line_number, 'modified': line_number}, + 'line_total_amount': {'original': line_total_amount, 'modified': line_total_amount}, + 'net_price_unit': {'original': net_price_unit, 'modified': net_price_unit}, 'ordered_qty': {'original': ordered_qty, 'modified': ordered_qty}, 'supplier_product_internal_code': { 'original': supplier_product_internal_code, 'modified': supplier_product_internal_code, }, - 'units_per_box': { - 'original': units_per_box, - 'modified': units_per_box, - }, + 'units_per_box': {'original': units_per_box, 'modified': units_per_box}, 'uom': {'original': uom, 'modified': uom}, 'use_unit': {'original': use_unit, 'modified': use_unit}, 'vat_tax': {'original': vat_tax, 'modified': vat_tax}, @@ -503,22 +448,16 @@ class CustomerPurchaseOrderImporter(models.Model): def get_partner(self, ean_code): ResPartner = self.env['res.partner'] - partner = ResPartner.search( - [ - ('partner_ean_code', '=', ean_code), - ] - ) + partner = ResPartner.search([('partner_ean_code', '=', ean_code)]) return partner def get_branch(self, ean_code, product_type): ResPartner = self.env['res.partner'] - branch = ResPartner.search( - [ - ('partner_ean_code', '=', ean_code), - ] - ).filtered(lambda x: x.zone_accounts_line_ids.rp_za_product_type_id == product_type) + branch = ResPartner.search([('partner_ean_code', '=', ean_code)]).filtered( + lambda x: x.zone_accounts_line_ids.rp_za_product_type_id == product_type + ) return branch @@ -526,10 +465,7 @@ class CustomerPurchaseOrderImporter(models.Model): PreSaleOrder = self.env['pre.sale.order'] purchase_order = PreSaleOrder.search_count( - [ - ('name', '=', 'ORD' + po_num), - ('partner_shipping_id', '=', branch.id), - ] + [('name', '=', 'ORD' + po_num), ('partner_shipping_id', '=', branch.id)] ) return purchase_order @@ -590,10 +526,7 @@ class CustomerPurchaseOrderImporter(models.Model): if _due_date and not due_date: errors.append(_('~ {} is not valid.\n').format(_('Due date'))) - start_date_is_earlier = self.start_date_is_earlier( - date_order, - due_date, - ) + start_date_is_earlier = self.start_date_is_earlier(date_order, due_date) if not start_date_is_earlier: errors.append(_('~ {} cannot be earlier than {}.\n').format(_('Due date'), _('order date'))) @@ -660,23 +593,18 @@ class CustomerPurchaseOrderImporter(models.Model): if len(product_types) > 1: errors.append(_('~ Some products have different product types.\n')) else: - product_type = ProductType.browse( - list(product_types)[0], - ) + product_type = ProductType.browse(list(product_types)[0]) if not product_type: errors.append( _( - '~ Product type not found. This is because no product has been found in the ' - 'database with its corresponding code. Without a defined product type, the ' - "branch will not be found, even when the branch's EAN code was correct.\n" + '~ Product type not found. This is because no product has been found in the database ' + 'with its corresponding code. Without a defined product type, the branch will not be ' + "found, even when the branch's EAN code was correct.\n" ) ) - return { - 'product_type': product_type, - 'errors': errors, - } + return {'product_type': product_type, 'errors': errors} def get_product(self, config, code, is_ean_code=False, partner=None, description=''): CustomerPurchaseOrderConfigLine = self.env['customer.purchase.order.config.line'] @@ -754,16 +682,11 @@ class CustomerPurchaseOrderImporter(models.Model): 'product_id': product.id, 'product_uom': product.uom_id.id, 'pricelist_price': ( - PreSaleOrder._get_pricelist_price( - product, - pricelist, - ) + PreSaleOrder._get_pricelist_price(product, pricelist) if pricelist else 0.0 ), - 'tax_id': [ - (6, 0, product.taxes_id.ids), - ], + 'tax_id': [(6, 0, product.taxes_id.ids)], } ) @@ -782,21 +705,11 @@ class CustomerPurchaseOrderImporter(models.Model): BranchEANCode = self.env['branch.ean.code'] branch_ean_code = BranchEANCode.search( - [ - ('branch_id', '=', branch.id), - ('ean13_code', '=', ean13_code), - ('product_id', '=', product.id), - ] + [('branch_id', '=', branch.id), ('ean13_code', '=', ean13_code), ('product_id', '=', product.id)] ) if not branch_ean_code: - BranchEANCode.create( - { - 'branch_id': branch.id, - 'ean13_code': ean13_code, - 'product_id': product.id, - } - ) + BranchEANCode.create({'branch_id': branch.id, 'ean13_code': ean13_code, 'product_id': product.id}) def get_config(self, partner): CustomerPurchaseOrderConfig = self.env['customer.purchase.order.config'] @@ -1046,10 +959,8 @@ class CustomerPurchaseOrderImporter(models.Model): processed_path = paths['processed'] rejected_path = paths['rejected'] - # if not manual_import: - # _logger.info('Importing PO files from ' + import_path) - - # files = glob.glob('{}*.*'.format(import_path)) + if not manual_import: + _logger.info('Importing PO files from ' + import_path) ftp = connect_ftp() @@ -1076,21 +987,20 @@ class CustomerPurchaseOrderImporter(models.Model): file_vals = self.get_full_file_data(lines) if isinstance(file_vals, list): - partner_name = file_vals.pop(-1) + rejected_file = '' + partner_name = file_vals.pop(-1).replace(' ', '_') if partner_name not in _file: - rejected_file = _file.replace('ORD_', 'ORD_{}_'.format(partner_name)) - - ftp.rename(import_path + _file, import_path + rejected_file) - - _file = rejected_file - - msg_list.insert(0, _('FILE: {}\n').format(_file)) + rejected_file = _file.replace('ORD_', 'ORD_%s_' % partner_name) + msg_list.insert(0, _('FILE: %s\n') % rejected_file) msg_list.extend(file_vals) not_imported.append(''.join(msg_list) + '\n') - ftp.rename(import_path + _file, rejected_path + _file) + if rejected_file in ftp.nlst(rejected_path): + ftp.delete(rejected_path + rejected_file) + + ftp.rename(import_path + _file, rejected_path + rejected_file) continue else: @@ -1115,10 +1025,10 @@ class CustomerPurchaseOrderImporter(models.Model): 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' + '~ No method has been defined for %s. Please contact the developer ' + 'to fix this issue.\n' + ) + % (_('configuration type %s\n') % config_type_name) ) not_imported.append(''.join(msg_list)) continue @@ -1131,17 +1041,17 @@ class CustomerPurchaseOrderImporter(models.Model): ftp.rename(import_path + _file, processed_path + _file) + if len(imported) > 0: + msg += _('The following files have been imported successfully:\n') + ', '.join(sorted(imported)) + if len(not_imported) > 0: - msg += ( - _('The following files have errors that need to be corrected before importing ' 'them:\n\n') - if manual_import - else '' - ) + ''.join(sorted(not_imported)) + not_imported_msg = ''.join(sorted(not_imported)) - # self._create_error_file(rejected_path, msg, ftp) + self._create_error_file(rejected_path, not_imported_msg, ftp) - if len(imported) > 0: - msg += _('The following files have been imported successfully:\n') + ', '.join(sorted(imported)) + msg += ( + _('The following files have errors that need to be corrected before importing them:\n\n') + ) + not_imported_msg self.env.cr.commit() @@ -1150,31 +1060,28 @@ class CustomerPurchaseOrderImporter(models.Model): if manual_import: raise UserError(msg) - def _create_error_file(self, path, datas, ftp): + def _create_error_file(self, path, msg, ftp): IrAttachment = self.env['ir.attachment'] + datas = bytes(msg, 'utf-8') + today = str(datetime.today().date()) name = _('[{}] Rejected Purchase Order Files').format(today) attachment_id = IrAttachment.search([('name', '=', name)]) if attachment_id: - datas += base64.b64decode(attachment_id.datas) + datas += b64decode(attachment_id.datas) - attachment_id.write({'datas': base64.b64encode(datas.encode('utf-8')), 'name': name}) + attachment_id.write({'datas': b64encode(datas), 'name': name}) file_name = '_not_imported_{}.txt'.format(today.replace('-', '')) - file = BytesIO(bytes(datas, 'utf-8')) + + file = BytesIO(datas) ftp.storbinary('STOR ' + file_name, file) - file.seek(0) if not attachment_id: IrAttachment.create( - { - 'datas_fname': file_name, - 'datas': base64.b64encode(file.read()), - 'name': name, - 'type': 'binary', - } + {'datas_fname': file_name, 'datas': b64encode(datas), 'name': name, 'type': 'binary'} ) file.close() diff --git a/wizard/customer_purchase_order_importer_wizard.py b/wizard/customer_purchase_order_importer_wizard.py index 3b3ace3..5f41b1b 100644 --- a/wizard/customer_purchase_order_importer_wizard.py +++ b/wizard/customer_purchase_order_importer_wizard.py @@ -1,45 +1,45 @@ import logging from base64 import b64decode +from io import BytesIO from odoo import _, fields, models +from odoo.addons.customer_purchase_order.models.customer_purchase_order_importer import connect_ftp from odoo.exceptions import UserError -from shutil import move -from tempfile import mkstemp _logger = logging.getLogger(__name__) class CustomerPurchaseOrderImporterWizard(models.TransientModel): - _name = "customer.purchase.order.importer.wizard" - _description = "Customer Purchase Order Importer Wizard" + _name = 'customer.purchase.order.importer.wizard' + _description = 'Customer Purchase Order Importer Wizard' name = fields.Char() files = fields.Many2many( - "ir.attachment", - "class_ir_attachments_rel", - "class_id", - "attachment_id", - string="Files to Import", + 'ir.attachment', 'class_ir_attachments_rel', 'class_id', 'attachment_id', string='Files to Import' ) + def prevent_dups(self, files): + no_dups = [] + + for file in files: + if file.name not in [f.name for f in no_dups]: + no_dups.append(file) + + return no_dups + def create_tmp_and_move_file(self, file, new_name, path='/tmp/'): if file: - tmp_file = mkstemp()[1] - with open(tmp_file, "wb+") as tmp: - tmp.write(b64decode(file.datas)) - tmp.close() + ftp = connect_ftp() + ftp.cwd(path) - move(tmp_file, new_name) + tmp_file = BytesIO(b64decode(file.datas)) - def prevent_dups(self, files): - no_dups = [] - for file in files: - if file.name not in [f[1] for f in no_dups]: - no_dups.append((file, file.name)) + ftp.storbinary('STOR %s' % file.name, tmp_file) - return [f[0] for f in no_dups] + tmp_file.seek(0) + tmp_file.close() def do_import(self): - CustomerPurchaseOrderImporter = self.env["customer.purchase.order.importer"] + CustomerPurchaseOrderImporter = self.env['customer.purchase.order.importer'] file_list = [] @@ -48,12 +48,12 @@ class CustomerPurchaseOrderImporterWizard(models.TransientModel): files = self.prevent_dups(self.files) for file in files: try: - filename = '{}{}'.format(import_path, file.name) + filename = '%s%s' % (import_path, file.name) self.create_tmp_and_move_file(file, filename, import_path) file_list.append(filename) except UserError: - raise UserError(_("Error creating file %s") % file.name) + raise UserError(_('Error creating file %s') % file.name) CustomerPurchaseOrderImporter.do_import(files=file_list, manual_import=True) diff --git a/wizard/rejected_import_wizard.py b/wizard/rejected_import_wizard.py index 3136506..9562c0a 100644 --- a/wizard/rejected_import_wizard.py +++ b/wizard/rejected_import_wizard.py @@ -23,7 +23,8 @@ class RejectedImportWizard(models.Model): 'ir.attachment', string='File', default=lambda self: self._default_file_id(), - domain="[('datas_fname', 'ilike', '_not_imported_')]") + domain="[('datas_fname', 'ilike', '_not_imported_')]", + ) def _default_file_id(self): attachment = self.env['ir.attachment'].search( -- GitLab