Odoo migration from 11 EE to 14 CE

    In this article, I tell you how to use standard export and import and a few small scripts to migrate a database from incompatible versions of odoo without using open upgrade.
ATTENTION!!!
    In the process of manual migration, I still suggest that you familiarize yourself with the automated migration of open upgrade OCA. because migrating manually using scripts is quite expensive. And I migrated all the data 2 times 1 time during the migration process and the second time after everything was ready, I added new data, because the campaign works non-stop. In the case of automatic scripts, this is much easier!

 


0.Companies usuall export and import)

1.Contacts

a)First part,  only info fields.

Fields email,city, ИНН (custom), КПП(custom), ОГРН(custom), ОКПО(custom), name, company_name, наименование(custom) , передан другим менеджером(custom), phone, street,street2, is a company, is a customer, is a vendor, mobile, address_type,salesperson
attention!when you import contacts odoo 14 auto set salesperson admin and when create sale order and select custome salesperson will set admin. Dont forger reset this field and import from old database again


b)Second part, relation childs_ids and parent_id (related company)

Contacts/ID


2.Users and passwords and groups

1.Passwords store in flectra and odoo 11EE in password_crypt field as hash we need disable auto convert hash for import is



2.Contracts (custom)

1.Export import fields

 

3.Lead (crm.lead)

1.Export import fields. Rename planned_revenue to expected_revenue

   

4.Bank (res.bank)

1.Export import fields

  

5.Bank partner (res.partner.bank)

1.Export import fields


6.Sale.order

1.Export import fields (without lines) dont forget export archive sale.order and your custom fields in my case (1c_date)

 

6.1.Product.product (dont forget archived records)

1.Import product.product without company_id

2.Import product template with links to product.product and without company_id 

3.Delete auto create product.template

4.Also if you use multi companies export tax customers and vendors in prooduct.product, because from it will set default tax in sale order lines 

 

7.Спецтехника (custom)

1.Export import fields

Our custom fields

8.Sale.order.line

1.Export import fields

2.Dont forget set unit of measure from product, i use this script

    _inherit='sale.order.line'
    def recompute_product_uom(self):
        """
        миграция
        проставляет единицы измерения из товара
        """
        for s in self:
            s.product_uom = s.product_id.uom_id


9.Details.line (custom)

1.Export import fields

Our custom fields 

10.Account.move 

1.Export import fields (without lines), in excel duplicate column date and rename invoice_date, because in version 14 have 3 dates in move date,invoice_date (number do from it field) and invoice_due_date. If use multi company export parts company by company and in this case company will detect auto from default

 

2.commit sequence check if have error with number

def _constrains_date_sequence(self):
in sequence_mixin.py file. temporaly after import uncommit

3.move_type field to readonly = False, for import capability

11.Account.move.line

1.Change currency_id field to required=False, move_id field to readonly=False, full_reconcile_id to readonly=false for capability with import

2.Change core, commit this in create func, if we get error not valid balance on import our lines, because in odoo 11 credit can be not compare debit.

        # if self._context.get('check_move_validity', True):
        #     moves._check_balanced()

3.Fields.


12.Account.move part 2

1.Export account.invoice and import to move(replace column) and save only column with move_id is set

 

it will save type of invoice customer or vendor bills and link to sale.order, by invoice_origin field for customer invoice - invoice_date not imported.

Also in odoo 14 vendor bills only 1 can be per sale.order with this date and reference

2.Start script for link move to sale.order (because in 11 version only account.invoice link to sale.order)
    def migration_link_move_saleorder(self):
        for s in self:
            if s.invoice_origin and (s.move_type == 'in_invoice' or s.move_type == 'out_invoice'):
                # sale_order = self.env['sale.order'].search([('name','=',s.ref)], limit=1)
                sale_order = self.env['sale.order'].search([('name','=',s.invoice_origin)], limit=1)
                if sale_order:
                    if s.move_type == 'in_invoice':
                        if s.id not in sale_order.invoice_supp_ids.ids:
                            sale_order.invoice_supp_ids = [(4, s.id)]

                    if s.move_type == 'out_invoice':
                        if s.id not in sale_order.invoice_ids.ids:
                            sale_order.invoice_ids = [(4, s.id)]
                        for lines in sale_order.order_line:
                            for linem in s.line_ids:
                                # if lines.price_unit*lines.product_uom_qty == linem.debit:
                                if lines.product_id.id == linem.product_id.id:
                                    lines.invoice_lines = [(4, linem.id)]
                                    break

3.After it we can calc price_unit because price store in account.invoice.line in 11 and in account.moce.line in 14
      def compute_price_unit_debit_credit(self):
        """
        функция вычисляет стоимость единицы на основе дебита или кредита, необходима для миграции
        так как в 11 версии оду ЕЕ цена хранится в account.invoice приходится пересчитывать
        """
        self = self.with_context(check_move_validity=False)
        for s in self:
            if s.state != 'posted':
                try:
                    if s.move_type == 'out_invoice':
                        for ml in s.line_ids:
                            if ml.credit:
                                # our tax always 20% = 1.2
                                if ml.tax_ids:
                                    ml.price_unit = round(ml.credit * 1.2 / (ml.quantity or 1.0), 2)
                                else:
                                    ml.price_unit = round(ml.credit / (ml.quantity or 1.0), 2)
                                ml._onchange_price_subtotal()
                                ml._onchange_mark_recompute_taxes()
                            else:
                                ml.exclude_from_invoice_tab = True
                        if s.line_ids:
                                s.action_post()
                    if s.move_type == 'in_invoice':
                        for ml in s.line_ids:
                            if ml.debit:
                                if ml.tax_ids:
                                    # our tax always 20% = 1.2
                                    ml.price_unit = round(ml.debit * 1.2 / (ml.quantity or 1.0), 2)
                                else:
                                    ml.price_unit = round(ml.debit / (ml.quantity or 1.0), 2)
                                ml._onchange_price_subtotal()
                                ml._onchange_mark_recompute_taxes()
                            else:
                                ml.exclude_from_invoice_tab = True
                        if s.line_ids:
                                s.action_post()
                except:
                    pass
             

3.After it we should recompute amount residual
    def tree_button_compute_amount_residual(self):
        count = 0
        for s in self:
            if s.state == 'posted':
                for ml in s.line_ids:
                    try:
                        ml._compute_amount_residual()
                    except:
                        count +=1

13.Account.payment

1.Commit core code temporaly, in create function account.payment

# vals['move_type'] = 'entry'
and
# TODO
# if 'line_ids' not in vals_list[i]:
#     to_write['line_ids'] = [(0, 0, line_vals) for line_vals in pay._prepare_move_line_default_vals(write_off_line_vals=write_off_line_vals)]

2.Export and import with move_id


3. Delete duplicate in excel by 2 click for field move_line_ids/move_id/id

.4. Import in odoo and post all payments


14.Account.full.reconcicle

1.Export and import, for payments widget display paid info

15.Account.partial.reconcicle

1.Export and import, for payments widget

2.Script, because in odoo 14 partial have amount and debit+credit. in odoo 11 only amount

    def compute_amount(self):
        for s in self:
            if s.debit_move_id.debit:
                s.debit_amount_currency = s.amount
                # if s.debit_move_id.move_id.state == 'posted':
                #     s.debit_move_id._compute_amount_residual()
            if s.credit_move_id.credit:
                s.credit_amount_currency = s.amount

16.Import ir.attachment or attachments)

1.Copy filestore

linux

/opt/flectra/.local/share/Flectra/filestore


windows C:\Users\Artem\AppData\Local\OpenERP S.A\Odoo\filestore

2.Export and import attachment. Disable readonly fields and export data without binary fields

import as usually. And comment write func fields compute pop

3. Non admins not see attachments now. Need Link old ids of attachmetns with new i use this. File to upload (input) its export External identificators model from settings. odoo

class mta_attachments(models.TransientModel):
    _name='ir.attachment.wizard.mig'
    _description = "ir.attachment.wizard.mig"

    file_to_upload = fields.Binary('File')
    def compute_new_res_id(self):
        """
        миграция
        функция находит res_id в новой базе данных, на основе external_id
        """
        from odoo.exceptions import ValidationError
        import logging
        _logger = logging.getLogger(__name__)
        import io
        try:
            import csv
        except ImportError:
            _logger.debug('Cannot `import csv`.')
        try:
            import base64
        except ImportError:
            _logger.debug('Cannot `import base64`.')
        keys = ["id","name","model","res_id"]
        try:
            csv_data = base64.b64decode(self.file_to_upload)
            data_file = io.StringIO(csv_data.decode("utf-8"))
            data_file.seek(0)
            file_reader = []
            csv_reader = csv.reader(data_file, delimiter=',')
            file_reader.extend(csv_reader)
        except Exception:
            raise ValidationError("Invalid file!")
        values = {}
        for i in range(len(file_reader)):
            field = list(map(str, file_reader[i]))
            count = 1
            count_keys = len(keys)
            if len(field) > count_keys:
                for new_fields in field:
                    if count > count_keys :
                        keys.append(new_fields)
                    count+=1
            values = dict(zip(keys, field))
            if values:
                if i == 0:
                    continue
                else:
                    # ищем вложение с такой моделью и ид
                    rec = self.env['ir.attachment'].sudo().search([
                        ('res_id', '=', int(values['res_id'])),
                        ('res_model', '=', values['model']),
                    ])
                    if rec:
                        # и если находим то получаем соответствие нашего ид и переписываем его
                        # тем самым привязываем вложение к рекорду в новом инстансе
                        rec2 = self.env['ir.model.data'].search([('name','=',values['name'])])
                        rec.res_id = rec2.res_id

 

Sale order line product tax default multi companies