diff --git a/src/l10n_do_banks/17.0.1.0.0/end-migration.py b/src/l10n_do_banks/17.0.1.0.0/end-migration.py index 789cbefac..8fb33cc74 100644 --- a/src/l10n_do_banks/17.0.1.0.0/end-migration.py +++ b/src/l10n_do_banks/17.0.1.0.0/end-migration.py @@ -5,15 +5,45 @@ _logger = logging.getLogger(__name__) -def delete_advanced_web_domain_widget_assets(cr): +def delete_custom_assets(cr): """ - Script to delete advanced_web_domain_widget assets. + Script to delete several custom backend assets: + - advanced_web_domain_widget + - ks_dashboard_ninja + - report_xlsx + - web_m2x_options + - alan_customize + - qztray_base + - ncf_manager + - interface_invoicing + - protocol_message + - dgii_reports + - l10n_do_ecommerce + """ env = api.Environment(cr, SUPERUSER_ID, {}) - assets = env['ir.asset'].search([('name', 'like', 'advanced_web_domain_widget.assets_backend%')]) - for asset in assets: - asset.unlink() - _logger.info("Advanced Web Domain Widget assets deleted") + + assets_to_delete = [ + ('advanced_web_domain_widget.assets_backend%', "Advanced Web Domain Widget assets deleted"), + ('ks_dashboard_ninja.assets_backend%', "Dashboard Ninja assets deleted"), + ('report_xlsx.assets_backend%', "Report xlsx assets deleted"), + ('web_m2x_options.assets_backend%', "Web m2x options assets deleted"), + ('alan_customize%', "Alan Customize assets deleted"), + ('qztray_base.assets_backend%', "QZ Tray Base assets deleted"), + ('ncf_manager.assets_backend%', "NCF Manager assets deleted"), + ('interface_invoicing.%', "Interface Invoicing assets deleted"), + ('protocol_message.%', "Protocol Message assets deleted"), + ('dgii_reports%', "DGII Reports assets deleted"), + ('l10n_do_ecommerce%', "Ecommerce assets deleted"), + ('web_editor%', "Custom assets deleted"), + ] + + for name_pattern, log_message in assets_to_delete: + assets = env['ir.asset'].search([('name', 'like', name_pattern)]) + for asset in assets: + asset.unlink() + _logger.info(log_message) + def deactivate_studio_views(cr): """ @@ -47,23 +77,24 @@ def deactivate_studio_views(cr): for view in studio_views: try: view_obj = env['ir.ui.view'].browse(view['id']) - + inherited_views = env['ir.ui.view'].search([ ('inherit_id', '=', view_obj.id), ('active', '=', True) ]) - + for inherited in inherited_views: inherited.write({'active': False, 'inherit_id': False}) _logger.info(f"Inherited View Deactivated: {inherited.name} (ID: {inherited.id})") - + view_obj.write({'active': False, 'inherit_id': False}) - _logger.info(f"View Deactivated and Inherited Deactivated: {view['name']} (ID: {view['id']})") + _logger.info(f"View Deactivated and Inherited Deactivated: {view['name']} (ID: {view['id']})") env.cr.commit() - + except Exception as e: _logger.warning(f"Error deactivating view {view['name']} (ID: {view['id']}): {e}") - + + def deactivate_automated_actions(cr): """ Script on end-migration to deactivate automated actions. @@ -73,10 +104,10 @@ def deactivate_automated_actions(cr): """ env = api.Environment(cr, SUPERUSER_ID, {}) - + # Get all active automated actions automated_actions = env['base.automation'].search([('active', '=', True)]) - + # Deactivate each automated action for action in automated_actions: try: @@ -84,11 +115,10 @@ def deactivate_automated_actions(cr): _logger.info(f"Automated action deactivated: {action.name} (ID: {action.id})") except Exception as e: _logger.error(f"Error deactivating automated action {action.name} (ID: {action.id}): {e}") - - def migrate(cr, version): - delete_advanced_web_domain_widget_assets(cr) + delete_custom_assets(cr) deactivate_studio_views(cr) deactivate_automated_actions(cr) + diff --git a/src/l10n_do_banks/17.0.1.0.0/end-views-activation.py b/src/l10n_do_banks/17.0.1.0.0/end-views-activation.py index 35a0d9c6e..9f61497bf 100644 --- a/src/l10n_do_banks/17.0.1.0.0/end-views-activation.py +++ b/src/l10n_do_banks/17.0.1.0.0/end-views-activation.py @@ -168,6 +168,84 @@ def migrate(cr, version): 'website_stock_availability.res_config_settings_view_form_inherit', 'operating_unit.view_user_form', 'sale_discount_display_amount.sale_order_view_form_display_discount', + 'operating_unit.view_users_form' + 'sale_order_transit_notification.res_config_settings_view_form_transit_inherit' + 'whatsapp_connector.res_config_settings_view_form' + 'sale_order_rate.res_config_settings_view_form' + 'l10n_do_hr_payroll.res_config_settings_view_form' + 'product_sequence.res_config_settings_view_form' + 'bi_warranty_registration.res_config_settings_view_form_stock' + 'bi_warranty_registration.res_config_settings_view_form' + 'product_price_checker.res_config_settings_view_form_checker' + 'purchase_request.purchase_order_line_form2_sub' + 'product_stock_qty_date_widgets.product_product_tree_view_inherit_widget_qty' + 'product_brand.view_product_variant_kanban_brand' + 'recurring_sale_order_app.recurring_order_view_form' + 'stock.stock_location_view_form_editable' + 'stock.stock_location_view_tree2_editable' + 'sale_pos_backend_card_bin_promotion.view_order_form_inherited' + 'sale_pos_session_link.view_order_sales_pos_backend_inherit_form' + 'sale_pos_backend_advance_payment.sale_pos_backend_order_form_advance_inherit' + 'l10n_do_sale_pos_backend.view_order_form_inherited' + 'sale_pos_backend_warranty_reports.view_order_form_warranty_inherit' + 'cecomsa_sale_control.view_order_sales_pos_backend_form_inherit' + 'sale_pos_backend.view_partner_form_sale_cashier' + 'l10n_do_account_bank_payment_se.account_payment_line_mixed_view_form', + 'l10n_do_account_bank_payment_se.account_payment_line_view_form', + 'l10n_do_chase.account_move_form_inherit_view', + 'l10n_do_chase.journal_payment_view_form', + 'l10n_do_electronic_invoice.autorized_xml_file_4e_view_form', + 'l10n_do_fiscal_printer.ir_sequence_view_form', + 'l10n_do_fiscal_printer.res_config_settings_view_form', + 'l10n_do_fiscal_printer_ir_series_move_form', + 'l10n_do_fleet_vehicle_journal.inherit_fleet_vehicle_view_form', + 'l10n_do_fleet_vehicle_journal.journal_entry_view_form', + 'l10n_do_hr_payroll_inherit.res_config_settings_view_form', + 'l10n_do_hr_payroll_inherit.sheet_import_line_form_view', + 'l10n_do_hr_payroll_inherit.sheet_import_view_form', + 'l10n_do_hr_payroll_inherit.sheet_import_view_tree', + 'l10n_do_income_statement.report_line_form_view', + 'l10n_do_income_statement.wizard_income_statement_config_view', + 'l10n_do_isr_retention.wizard_view_form', + 'l10n_do_pos_bank_statement.l10n_do_pos_bank_statement_form_view', + 'l10n_do_pos_return.order_form_view', + 'l10n_do_project_sale.project_project_view_form_inherit', + 'l10n_do_purchase.document_type_wizard_view_form', + 'l10n_do_purchase.requisition_view_form', + 'l10n_do_sale.purchase_order_line_inherit_view', + 'l10n_do_sale_order_extension.res_config_settings_view_form', + 'l10n_do_sale_order_view.line_inherit_view_form', + 'l10n_do_sale_order_view.order_view_form', + 'l10n_do_sale_payment_method.form_view_payment_inherit', + 'l10n_do_sale_payment_method.sale_order_form_view_payment_inherit', + 'l10n_do_supplier_invoice.res_config_settings_view_form_supplier_invoice', + 'l10n_do_supplier_invoice.view_account_move_supplier_invoice_form', + 'l10n_do_supplier_payment.advance_payment_inherit_form_view', + 'l10n_do_supplier_payment.res_partner_form_inherit_view', + 'l10n_do_supplier_payment.view_account_move_supplier_form_inherit', + 'l10n_do_supplier_payment.wizard_supplier_payment_view_form', + 'l10n_do_supplier_withholding.account_move_form_supplier_withholding', + 'l10n_do_supplier_withholding.partner_supplier_withholding_view_form', + 'l10n_do_supplier_withholding.res_config_settings_supplier_withholding_view_form', + 'pm_bank_transfer_order.view_bank_transfer_order_form', + 'pm_bank_transfer_order.view_res_partner_bank_inherit_form', + 'pm_bank_transfer_order.wizard_view_form', + 'pm_bank_transfer_order_line.view_bank_transfer_order_line_form', + 'pm_bank_transfer_order_line_inherit.view_bank_transfer_order_line_form_inherit', + 'pos_backend_order.line_inherit_view_form', + 'pos_backend_order.order_view_form', + 'pos_journal_control.wizard_view_form', + 'pos_journal_control.view_session_journal_control_form', + 'pos_payment_method_extension.payment_method_inherit_form_view', + 'pos_payment_method_extension.pos_payment_method_view_form', + 'pos_payment_method_extension.wizard_view_form', + 'pos_sale_report.order_sale_report_wizard_form_view', + 'sale_cashier_inherit.view_sale_cashier_form', + 'sale_cashier_inherit.view_sale_cashier_session_form', + 'sale_cashier_inherit.view_sale_cashier_session_inherit_form', + 'sale_cashier_inherit.view_sale_cashier_session_return_form', + 'sale_cashier_inherit.view_users_form', + ] remove_inherit_views_list = [ diff --git a/src/l10n_do_banks/17.0.1.0.0/pre-modules-uninstall.py b/src/l10n_do_banks/17.0.1.0.0/pre-modules-uninstall.py index bb757a9d8..788267532 100644 --- a/src/l10n_do_banks/17.0.1.0.0/pre-modules-uninstall.py +++ b/src/l10n_do_banks/17.0.1.0.0/pre-modules-uninstall.py @@ -5,6 +5,47 @@ _logger = logging.getLogger(__name__) +def _cleanup_qztray_data(cr): + """Remove qztray printer records that block keypair deletion. + + Some databases have a NOT NULL constraint on ``qztray_printer.keypair_id`` + while the foreign key uses ``ON DELETE SET NULL``. When uninstalling the + qztray modules, the upgrade utility tries to delete records from + ``qztray_keypair``, which triggers ``SET NULL`` on ``qztray_printer`` and + crashes on the NOT NULL constraint. + + We proactively delete the printer records that reference any keypair so the + module uninstall can proceed. + """ + # Solo ejecutamos esta limpieza si el módulo qztray_printer (o qztray) + # está instalado en la base de datos. + if not ( + util.module_installed(cr, "qztray_printer") + or util.module_installed(cr, "qztray") + ): + _logger.debug( + "qztray_printer/qztray no instalados; se omite la limpieza previa." + ) + return + + try: + cr.execute( + """ + DELETE FROM qztray_printer + WHERE keypair_id IN (SELECT id FROM qztray_keypair) + """ + ) + _logger.info("Deleted qztray_printer records referencing qztray_keypair.") + except Exception: + # If tables don't exist or deletion fails, ignore and let normal + # uninstall logic handle it. + _logger.debug( + "qztray_printer/qztray_keypair tables not found or deletion failed, " + "continuing with module uninstall.", + exc_info=True, + ) + + def uninstall_modules(cr): """ Script to uninstall modules that are no longer needed or compatible with version 17.0. @@ -81,6 +122,33 @@ def uninstall_modules(cr): 'stock_account_product_cost_security', 'product_code_unique', 'account_ecf_auto_post', + 'qztray_base', + 'professional_templates', + 'stock_available_unreserved', + 'product_warehouse_quantity', + 'sale_discount_limit', + 'sales_product_warehouse_quantity', + 'account_invoice_migration_scripts', + 'alan_customize', + 'config_interface', + 'database_cleanup', + 'dev_sale_product_stock_restrict', + 'interface_invoicing', + 'ncf_sale', + 'negative_stock_sale', + 'non_moving_product_ept', + 'payment_backend_refund', + 'product_hide_sale_cost_price', + 'protocol_message', + 'qztray', + 'qztray_base', + 'qztray_location_labels', + 'qztray_partner_labels', + 'qztray_product_inventory', + 'qztray_product_labels', + 'qztray_product_purchase', + 'required_requested_date', + 'stock_inventory_chatter' 'cecomsa_account_followup', ] @@ -91,4 +159,5 @@ def uninstall_modules(cr): def migrate(cr, version): + _cleanup_qztray_data(cr) uninstall_modules(cr) diff --git a/src/l10n_do_banks/17.0.1.0.0/pre-view-delete.py b/src/l10n_do_banks/17.0.1.0.0/pre-view-delete.py index dfdc80d5f..01bedfc36 100644 --- a/src/l10n_do_banks/17.0.1.0.0/pre-view-delete.py +++ b/src/l10n_do_banks/17.0.1.0.0/pre-view-delete.py @@ -25,6 +25,11 @@ def migrate(cr, version): 'studio_customization.odoo_studio_warranty_5e101e7c-e388-4589-b9b8-628b03ca43f1', 'studio_customization.odoo_studio_warranty_64540003-0f78-4e15-8bba-6d3475418fed', 'product_product_price_widget.product_product_tree_view_inherit_widget', + 'website_livechat.channel_list_page', + '__export__.ir_ui_view_4053_9a82b3d7', + 'professional_templates.view_sale_order_inherit_customized', + 'professional_templates.view_rfq_inherit_customized', + 'professional_templates.purchase_order_inherited_customized', ] for xml_id in views_to_delete: diff --git a/src/product_sequence/17.0.1.0.0/end-migration.py b/src/product_sequence/17.0.1.0.0/end-migration.py new file mode 100644 index 000000000..cf15d30ba --- /dev/null +++ b/src/product_sequence/17.0.1.0.0/end-migration.py @@ -0,0 +1,45 @@ +import logging + +_logger = logging.getLogger(__name__) + +CONSTRAINT_NAME = 'product_product_default_code_uniq' +TABLE_NAME = 'product_product' +CONSTRAINT_EXISTS_SQL = """ + SELECT 1 + FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + JOIN pg_namespace n ON t.relnamespace = n.oid + WHERE c.conname = %s AND t.relname = %s AND n.nspname = 'public' +""" +DUPLICATES_SQL = """ + SELECT default_code, COUNT(*) AS qty + FROM product_product + WHERE default_code IS NOT NULL AND default_code != '' + GROUP BY default_code + HAVING COUNT(*) > 1 +""" + + +def migrate(cr, version): + """Recreate the unique constraint on product_product.default_code.""" + _logger.info('POST-MIGRATION: Restoring %s on %s.default_code', CONSTRAINT_NAME, TABLE_NAME) + + cr.execute(CONSTRAINT_EXISTS_SQL, (CONSTRAINT_NAME, TABLE_NAME)) + if cr.fetchone(): + _logger.info('Constraint %s already exists. Nothing to do.', CONSTRAINT_NAME) + return + + cr.execute(DUPLICATES_SQL) + duplicates = cr.fetchall() + if duplicates: + sample = ', '.join(f'"{code}" ({qty}x)' for code, qty in duplicates[:5]) + if len(duplicates) > 5: + sample += f', ... (+{len(duplicates) - 5})' + _logger.error('Cannot recreate %s; duplicate default_code values detected: %s', CONSTRAINT_NAME, sample) + raise Exception('Resolve duplicate product_product.default_code values before rerunning the migration.') + + _logger.info('Dropping constraint %s (if it exists) and recreating it.', CONSTRAINT_NAME) + cr.execute(f"ALTER TABLE {TABLE_NAME} DROP CONSTRAINT IF EXISTS {CONSTRAINT_NAME}") + cr.execute(f"ALTER TABLE {TABLE_NAME} ADD CONSTRAINT {CONSTRAINT_NAME} UNIQUE (default_code)") + _logger.info('Constraint %s restored successfully.', CONSTRAINT_NAME) + diff --git a/src/product_sequence/17.0.1.0.0/pre-remove_constraint.py b/src/product_sequence/17.0.1.0.0/pre-remove_constraint.py new file mode 100644 index 000000000..79e0d7017 --- /dev/null +++ b/src/product_sequence/17.0.1.0.0/pre-remove_constraint.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +from odoo.addons.base.maintenance.migrations import util +import logging + +_logger = logging.getLogger(__name__) + +CONSTRAINT_NAME = 'product_product_default_code_uniq' + + +def migrate(cr, version): + """Drop the product default_code unique constraint when needed.""" + _logger.info('PRE-MIGRATION: Dropping %s constraint', CONSTRAINT_NAME) + + if not util.module_installed(cr, 'product_code_unique'): + _logger.info('Module product_code_unique is not installed. Nothing to do.') + return + + cr.execute( + """ + SELECT COUNT(*) + FROM product_product + WHERE default_code IS NULL OR default_code = '' + """ + ) + missing_codes = cr.fetchone()[0] + + if not missing_codes: + _logger.info('No products without default_code found. Skipping constraint drop.') + return + + _logger.info('Dropping constraint %s to allow temporary duplicates.', CONSTRAINT_NAME) + cr.execute( + """ + ALTER TABLE product_product + DROP CONSTRAINT IF EXISTS product_product_default_code_uniq + """ + ) + _logger.info('Constraint %s dropped (if it existed).', CONSTRAINT_NAME)