|
| 1 | +# Copyright 2026 Hunki Enterprises BV |
| 2 | +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). |
| 3 | + |
| 4 | +import re |
| 5 | + |
| 6 | +from openupgradelib import openupgrade |
| 7 | + |
| 8 | +from odoo.orm.commands import Command |
| 9 | + |
| 10 | + |
| 11 | +def account_reconcile_model(env): |
| 12 | + """ |
| 13 | + Apply changes to account.reconcile.model |
| 14 | + """ |
| 15 | + env.cr.execute( |
| 16 | + """ |
| 17 | + UPDATE account_reconcile_model |
| 18 | + SET trigger='auto_reconcile' |
| 19 | + WHERE auto_reconcile |
| 20 | + """ |
| 21 | + ) |
| 22 | + |
| 23 | + |
| 24 | +def account_reconcile_model_partner_mapping(env): |
| 25 | + """ |
| 26 | + Model account.reconcile.model.partner.mapping is folded into |
| 27 | + account.reconcile.model.line, so create a new model per partner |
| 28 | + mapping, merge regexes from partner mapping and model if any |
| 29 | + """ |
| 30 | + link_column = openupgrade.get_legacy_name("partner_mapping_id") |
| 31 | + env.cr.execute( |
| 32 | + "ALTER TABLE account_reconcile_model " |
| 33 | + f"ADD COLUMN IF NOT EXISTS {link_column} int " |
| 34 | + ) |
| 35 | + env.cr.execute( |
| 36 | + """ |
| 37 | + SELECT |
| 38 | + model_id, |
| 39 | + array_agg(id), |
| 40 | + array_agg(partner_id), |
| 41 | + array_agg(payment_ref_regex), |
| 42 | + array_agg(narration_regex) |
| 43 | + FROM account_reconcile_model_partner_mapping mapping |
| 44 | + GROUP BY model_id |
| 45 | + """ |
| 46 | + ) |
| 47 | + AccountReconcileModel = env["account.reconcile.model"] |
| 48 | + ResPartner = env["res.partner"] |
| 49 | + |
| 50 | + for ( |
| 51 | + reconcile_model_id, |
| 52 | + mapping_ids, |
| 53 | + partner_ids, |
| 54 | + payment_ref_regexes, |
| 55 | + narration_regexes, |
| 56 | + ) in env.cr.fetchall(): |
| 57 | + reconcile_model = AccountReconcileModel.browse(reconcile_model_id) |
| 58 | + match_regex_lookahead = "" |
| 59 | + if reconcile_model.match_label_param: |
| 60 | + if reconcile_model.match_label == "contains": |
| 61 | + match_regex_lookahead = ( |
| 62 | + f"(?=.*{re.escape(reconcile_model.match_label_param)}.*)" |
| 63 | + ) |
| 64 | + elif reconcile_model.match_label == "not_contains": |
| 65 | + match_regex_lookahead = ( |
| 66 | + f"(?!{re.escape(reconcile_model.match_label_param)})" |
| 67 | + ) |
| 68 | + elif reconcile_model.match_label == "match_regex": |
| 69 | + match_regex_lookahead = f"(?={reconcile_model.match_label_param})" |
| 70 | + |
| 71 | + for mapping_id, partner_id, payment_ref_regex, narration_regex in zip( |
| 72 | + mapping_ids, |
| 73 | + partner_ids, |
| 74 | + payment_ref_regexes, |
| 75 | + narration_regexes, |
| 76 | + strict=True, |
| 77 | + ): |
| 78 | + partner = ResPartner.browse(partner_id) |
| 79 | + match_regex = "|".join( |
| 80 | + map( |
| 81 | + lambda x: f"({x})", |
| 82 | + filter(None, [payment_ref_regex, narration_regex]), |
| 83 | + ) |
| 84 | + ) |
| 85 | + new_reconcile_model = reconcile_model.copy( |
| 86 | + { |
| 87 | + "name": reconcile_model.name + f" ({partner.name})", |
| 88 | + "match_label": "match_regex", |
| 89 | + "match_label_param": match_regex_lookahead + match_regex, |
| 90 | + "line_ids": [ |
| 91 | + Command.create( |
| 92 | + { |
| 93 | + "partner_id": partner_id, |
| 94 | + } |
| 95 | + ), |
| 96 | + ], |
| 97 | + } |
| 98 | + ) |
| 99 | + env.cr.execute( |
| 100 | + f""" |
| 101 | + UPDATE account_reconcile_model |
| 102 | + SET {link_column} = {mapping_id} |
| 103 | + WHERE id = {new_reconcile_model.id} |
| 104 | + """ |
| 105 | + ) |
| 106 | + |
| 107 | + |
| 108 | +def account_account_active(env): |
| 109 | + """ |
| 110 | + Set active flag from deprecated |
| 111 | + """ |
| 112 | + env.cr.execute("UPDATE account_account SET active = FALSE where deprecated") |
| 113 | + |
| 114 | + |
| 115 | +def fiscal_position_tax_ids(env): |
| 116 | + """ |
| 117 | + Fill account.fiscal.position#tax_ids from previous account.fiscal.position.tax |
| 118 | + table |
| 119 | + """ |
| 120 | + env.cr.execute( |
| 121 | + """ |
| 122 | + INSERT INTO account_fiscal_position_account_tax_rel |
| 123 | + (account_fiscal_position_id, account_tax_id) |
| 124 | + SELECT DISTINCT position_id, tax_dest_id FROM account_fiscal_position_tax |
| 125 | + WHERE tax_dest_id IS NOT NULL |
| 126 | + """ |
| 127 | + ) |
| 128 | + |
| 129 | + |
| 130 | +def account_tax_original_tax_ids(env): |
| 131 | + """ |
| 132 | + Fill account.tax#original_tax_ids from previous account.fiscal.position.tax table |
| 133 | + """ |
| 134 | + env.cr.execute( |
| 135 | + """ |
| 136 | + INSERT INTO account_tax_alternatives |
| 137 | + (dest_tax_id, src_tax_id) |
| 138 | + SELECT DISTINCT tax_dest_id, tax_src_id FROM account_fiscal_position_tax |
| 139 | + WHERE tax_dest_id IS NOT NULL AND tax_src_id IS NOT NULL |
| 140 | + """ |
| 141 | + ) |
| 142 | + |
| 143 | + |
| 144 | +def account_full_reconcile_exchange_move_id(env): |
| 145 | + """ |
| 146 | + account.full.reconcile#exchange_move_id has been scrapped, clone a partial |
| 147 | + reconciliation and link the exchange move to it |
| 148 | + """ |
| 149 | + env.cr.execute( |
| 150 | + """ |
| 151 | + SELECT id, exchange_move_id |
| 152 | + FROM account_full_reconcile |
| 153 | + WHERE exchange_move_id IS NOT NULL |
| 154 | + """ |
| 155 | + ) |
| 156 | + AccountFullReconcile = env["account.full.reconcile"] |
| 157 | + |
| 158 | + for full_reconcile_id, exchange_move_id in env.cr.fetchall(): |
| 159 | + AccountFullReconcile.browse(full_reconcile_id).partial_reconcile_ids[-1:].copy( |
| 160 | + { |
| 161 | + "exchange_move_id": exchange_move_id, |
| 162 | + "full_reconcile_id": full_reconcile_id, |
| 163 | + } |
| 164 | + ) |
| 165 | + |
| 166 | + |
| 167 | +def account_move_line_is_storno(env): |
| 168 | + """ |
| 169 | + Compute is_storno on lines of companies with storno accounting |
| 170 | + """ |
| 171 | + lines_to_recompute = env["account.move.line"].search( |
| 172 | + [("company_id.account_storno", "=", True)] |
| 173 | + ) |
| 174 | + lines_to_recompute._compute_is_storno() |
| 175 | + |
| 176 | + |
| 177 | +def account_move_line_no_followup(env): |
| 178 | + """ |
| 179 | + Set no_followup = True on lines of journals of type 'general' |
| 180 | + """ |
| 181 | + env.cr.execute( |
| 182 | + """ |
| 183 | + UPDATE account_move_line |
| 184 | + SET no_followup=True |
| 185 | + FROM account_journal |
| 186 | + WHERE |
| 187 | + account_move_line.journal_id=account_journal.id AND |
| 188 | + account_journal.type = 'general' |
| 189 | + """ |
| 190 | + ) |
| 191 | + |
| 192 | + |
| 193 | +@openupgrade.migrate() |
| 194 | +def migrate(env, version): |
| 195 | + openupgrade.load_data(env, "account", "19.0.1.4/noupdate_changes.xml") |
| 196 | + openupgrade.delete_record_translations( |
| 197 | + env.cr, |
| 198 | + "account", |
| 199 | + [ |
| 200 | + "email_template_edi_credit_note", |
| 201 | + "email_template_edi_invoice", |
| 202 | + "mail_template_data_payment_receipt", |
| 203 | + ], |
| 204 | + ["body_html"], |
| 205 | + ) |
| 206 | + account_reconcile_model(env) |
| 207 | + account_reconcile_model_partner_mapping(env) |
| 208 | + account_account_active(env) |
| 209 | + fiscal_position_tax_ids(env) |
| 210 | + account_tax_original_tax_ids(env) |
| 211 | + account_full_reconcile_exchange_move_id(env) |
| 212 | + account_move_line_is_storno(env) |
| 213 | + account_move_line_no_followup(env) |
0 commit comments