diff --git a/account_chart_update/security/ir.model.access.csv b/account_chart_update/security/ir.model.access.csv
index 38346742..6ce9de54 100644
--- a/account_chart_update/security/ir.model.access.csv
+++ b/account_chart_update/security/ir.model.access.csv
@@ -7,3 +7,5 @@ access_wizard_account_matching,wizard.account.matching,model_wizard_account_matc
access_wizard_update_charts_accounts_tax,wizard.update.charts.accounts.tax,model_wizard_update_charts_accounts_tax,base.group_erp_manager,1,1,1,1
access_wizard_matching,wizard.matching,model_wizard_matching,base.group_erp_manager,1,1,1,1
access_wizard_update_charts_accounts_fiscal_position,wizard.update.charts.accounts.fiscal.position,model_wizard_update_charts_accounts_fiscal_position,base.group_erp_manager,1,1,1,1
+access_wizard_account_groupo_matching,wizard.account.group.matching,model_wizard_account_group_matching,base.group_erp_manager,1,1,1,1
+access_wizard_update_charts_accounts_account_group,wizard.update.charts.accounts.account.group,model_wizard_update_charts_accounts_account_group,base.group_erp_manager,1,1,1,1
diff --git a/account_chart_update/tests/test_account_chart_update.py b/account_chart_update/tests/test_account_chart_update.py
index 567761d0..f08826d9 100644
--- a/account_chart_update/tests/test_account_chart_update.py
+++ b/account_chart_update/tests/test_account_chart_update.py
@@ -457,6 +457,66 @@ class TestAccountChartUpdate(common.HttpCase):
self.assertEqual(wizard.new_accounts, 0)
wizard.unlink()
+ def test_chart_update_groups(self):
+ self.wizard_vals.update(
+ {
+ "update_account": False,
+ "update_tax": False,
+ "update_fiscal_position": False,
+ "recreate_xml_ids": True,
+ }
+ )
+ template_1 = self.env["account.group.template"].create(
+ {
+ "name": "TEST",
+ "code_prefix_start": "TESTZ",
+ "chart_template_id": self.chart_template.id,
+ }
+ )
+ self._create_xml_id(template_1)
+ template_2 = self.env["account.group.template"].create(
+ {
+ "name": "TEST",
+ "code_prefix_start": "TESTY",
+ "chart_template_id": self.chart_template.id,
+ }
+ )
+ self._create_xml_id(template_2)
+ group_1 = self.env["account.group"].create(
+ {
+ "name": "TEST",
+ "code_prefix_start": "TESTZ",
+ "code_prefix_end": "TESZZ",
+ "company_id": self.company.id,
+ }
+ )
+ wizard = self.wizard_obj.create(self.wizard_vals)
+ wizard.action_find_records()
+ self.assertEqual(2, len(wizard.account_group_ids))
+ self.assertEqual(
+ template_1,
+ wizard.account_group_ids.filtered(
+ lambda r: r.type == "updated"
+ ).account_group_id,
+ )
+ self.assertEqual(
+ group_1,
+ wizard.account_group_ids.filtered(
+ lambda r: r.type == "updated"
+ ).update_account_group_id,
+ )
+ self.assertEqual(
+ template_2,
+ wizard.account_group_ids.filtered(
+ lambda r: r.type == "new"
+ ).account_group_id,
+ )
+ wizard.action_update_records()
+ self.assertEqual(1, wizard.updated_account_groups)
+ self.assertEqual(1, wizard.new_account_groups)
+ self.assertEqual("TESTZ", group_1.code_prefix_end)
+ self.assertTrue(list(group_1.get_xml_id().values())[0])
+
def test_matching(self):
# Test XML-ID matching
self.tax_template.name = "Test 1 tax name changed"
diff --git a/account_chart_update/wizard/wizard_chart_update.py b/account_chart_update/wizard/wizard_chart_update.py
index 0e937f35..7db646df 100644
--- a/account_chart_update/wizard/wizard_chart_update.py
+++ b/account_chart_update/wizard/wizard_chart_update.py
@@ -84,6 +84,12 @@ class WizardUpdateChartsAccounts(models.TransientModel):
default=True,
help="Existing accounts are updated. Accounts are searched by code.",
)
+ update_account_group = fields.Boolean(
+ string="Update account groups",
+ default=True,
+ help="Existing account groups are updated. "
+ "Account groups are searched by prefix_code_start.",
+ )
update_fiscal_position = fields.Boolean(
string="Update fiscal positions",
default=True,
@@ -106,6 +112,11 @@ class WizardUpdateChartsAccounts(models.TransientModel):
inverse_name="update_chart_wizard_id",
string="Accounts",
)
+ account_group_ids = fields.One2many(
+ comodel_name="wizard.update.charts.accounts.account.group",
+ inverse_name="update_chart_wizard_id",
+ string="Account Groups",
+ )
fiscal_position_ids = fields.One2many(
comodel_name="wizard.update.charts.accounts.fiscal.position",
inverse_name="update_chart_wizard_id",
@@ -113,6 +124,7 @@ class WizardUpdateChartsAccounts(models.TransientModel):
)
new_taxes = fields.Integer(compute="_compute_new_taxes_count")
new_accounts = fields.Integer(compute="_compute_new_accounts_count")
+ new_account_groups = fields.Integer(compute="_compute_new_account_groups_count")
rejected_new_account_number = fields.Integer()
new_fps = fields.Integer(
string="New fiscal positions", compute="_compute_new_fps_count"
@@ -120,6 +132,9 @@ class WizardUpdateChartsAccounts(models.TransientModel):
updated_taxes = fields.Integer(compute="_compute_updated_taxes_count")
rejected_updated_account_number = fields.Integer()
updated_accounts = fields.Integer(compute="_compute_updated_accounts_count")
+ updated_account_groups = fields.Integer(
+ compute="_compute_updated_account_groups_count"
+ )
updated_fps = fields.Integer(
string="Updated fiscal positions", compute="_compute_updated_fps_count"
)
@@ -141,6 +156,13 @@ class WizardUpdateChartsAccounts(models.TransientModel):
domain=lambda self: self._domain_account_field_ids(),
default=lambda self: self._default_account_field_ids(),
)
+ account_group_field_ids = fields.Many2many(
+ comodel_name="ir.model.fields",
+ relation="wizard_update_charts_account_group_fields_rel",
+ string="Account fields",
+ domain=lambda self: self._domain_account_group_field_ids(),
+ default=lambda self: self._default_account_group_field_ids(),
+ )
fp_field_ids = fields.Many2many(
comodel_name="ir.model.fields",
relation="wizard_update_charts_fp_fields_rel",
@@ -160,6 +182,12 @@ class WizardUpdateChartsAccounts(models.TransientModel):
string="Accounts matching",
default=lambda self: self._default_account_matching_ids(),
)
+ account_group_matching_ids = fields.One2many(
+ comodel_name="wizard.account.group.matching",
+ inverse_name="update_chart_wizard_id",
+ string="Account groups matching",
+ default=lambda self: self._default_account_group_matching_ids(),
+ )
fp_matching_ids = fields.One2many(
comodel_name="wizard.fp.matching",
inverse_name="update_chart_wizard_id",
@@ -179,6 +207,9 @@ class WizardUpdateChartsAccounts(models.TransientModel):
def _domain_account_field_ids(self):
return self._domain_per_name("account.account.template")
+ def _domain_account_group_field_ids(self):
+ return self._domain_per_name("account.group.template")
+
def _domain_fp_field_ids(self):
return self._domain_per_name("account.fiscal.position.template")
@@ -196,6 +227,14 @@ class WizardUpdateChartsAccounts(models.TransientModel):
)
]
+ def _default_account_group_field_ids(self):
+ return [
+ (4, x.id)
+ for x in self.env["ir.model.fields"].search(
+ self._domain_account_group_field_ids()
+ )
+ ]
+
def _default_fp_field_ids(self):
return [
(4, x.id)
@@ -206,14 +245,12 @@ class WizardUpdateChartsAccounts(models.TransientModel):
vals = []
for seq, opt in enumerate(ordered_opts, 1):
vals.append((0, False, {"sequence": seq, "matching_value": opt}))
-
all_options = self.env[model_name]._get_matching_selection()
all_options = map(lambda x: x[0], all_options)
all_options = list(set(all_options) - set(ordered_opts))
for seq, opt in enumerate(all_options, len(ordered_opts) + 1):
vals.append((0, False, {"sequence": seq, "matching_value": opt}))
-
return vals
def _default_fp_matching_ids(self):
@@ -228,6 +265,10 @@ class WizardUpdateChartsAccounts(models.TransientModel):
ordered_opts = ["xml_id", "code", "name"]
return self._get_matching_ids("wizard.account.matching", ordered_opts)
+ def _default_account_group_matching_ids(self):
+ ordered_opts = ["xml_id", "code_prefix_start"]
+ return self._get_matching_ids("wizard.account.group.matching", ordered_opts)
+
@api.model
def _get_lang_selection_options(self):
"""Gets the available languages for the selection."""
@@ -250,6 +291,12 @@ class WizardUpdateChartsAccounts(models.TransientModel):
- self.rejected_new_account_number
)
+ @api.depends("account_group_ids")
+ def _compute_new_account_groups_count(self):
+ self.new_account_groups = len(
+ self.account_group_ids.filtered(lambda x: x.type == "new")
+ )
+
@api.depends("fiscal_position_ids")
def _compute_new_fps_count(self):
self.new_fps = len(self.fiscal_position_ids.filtered(lambda x: x.type == "new"))
@@ -265,6 +312,12 @@ class WizardUpdateChartsAccounts(models.TransientModel):
- self.rejected_updated_account_number
)
+ @api.depends("account_group_ids")
+ def _compute_updated_account_groups_count(self):
+ self.updated_account_groups = len(
+ self.account_group_ids.filtered(lambda x: x.type == "updated")
+ )
+
@api.depends("fiscal_position_ids")
def _compute_updated_fps_count(self):
self.updated_fps = len(
@@ -315,6 +368,8 @@ class WizardUpdateChartsAccounts(models.TransientModel):
self._find_taxes()
if self.update_account:
self._find_accounts()
+ if self.update_account_group:
+ self._find_account_groups()
if self.update_fiscal_position:
self._find_fiscal_positions()
# Write the results, and go to the next step.
@@ -372,6 +427,8 @@ class WizardUpdateChartsAccounts(models.TransientModel):
# Clear this cache for avoiding incorrect account hits (as it was
# queried before account creation)
self.find_account_by_templates.clear_cache(self)
+ if self.update_account_group and perform_rest:
+ self._update_account_groups()
if self.update_tax and perform_rest:
self._update_taxes_pending_for_accounts(todo_dict)
if self.update_fiscal_position and perform_rest:
@@ -538,6 +595,39 @@ class WizardUpdateChartsAccounts(models.TransientModel):
for matching in self.account_matching_ids.sorted("sequence"):
if matching.matching_value == "xml_id":
real = self.env["account.account"]
+ for template in templates:
+ try:
+ real |= self.env.ref(self._get_real_xml_name(template))
+ except BaseException:
+ _logger.info("Is not real xml Name")
+ if not real:
+ continue
+ criteria = ("id", "in", real.ids)
+ elif matching.matching_value == "code":
+ codes = templates.mapped("code")
+ if not codes:
+ continue
+ criteria = ("code", "in", list(map(self.padded_code, codes)))
+ else:
+ field_name = matching.matching_value
+ field_values = templates.mapped(field_name)
+ if not field_values:
+ continue
+ criteria = (field_name, "in", field_values)
+ result = account_model.search(
+ [criteria, ("company_id", "=", self.company_id.id)]
+ )
+ if result:
+ return result.id
+ return False
+
+ @tools.ormcache("templates")
+ def find_account_group_by_templates(self, templates):
+ """Find an account that matches the template."""
+ account_model = self.env["account.group"]
+ for matching in self.account_group_matching_ids.sorted("sequence"):
+ if matching.matching_value == "xml_id":
+ real = self.env["account.group"]
for template in templates:
try:
real |= self.env.ref(self._get_real_xml_name(template))
@@ -680,6 +770,11 @@ class WizardUpdateChartsAccounts(models.TransientModel):
"account.account.template": set(self.env["mail.thread"]._fields)
| {"chart_template_id", "root_id", "nocreate"},
"account.fiscal.position.template": {"chart_template_id"},
+ "account.group.template": {
+ "chart_template_id",
+ "parent_id",
+ "code_prefix_end",
+ },
}
specials = {
"display_name",
@@ -706,6 +801,7 @@ class WizardUpdateChartsAccounts(models.TransientModel):
"account.tax.template": self.tax_field_ids,
"account.account.template": self.account_field_ids,
"account.fiscal.position.template": self.fp_field_ids,
+ "account.group.template": self.account_group_field_ids,
}
to_include = template_field_mapping[template._name].mapped("name")
for key, field in template._fields.items():
@@ -902,6 +998,57 @@ class WizardUpdateChartsAccounts(models.TransientModel):
}
)
+ def _find_account_groups(self):
+ """Load account templates to create/update."""
+ self.account_group_ids.unlink()
+ for template in self.env["account.group.template"].search(
+ [("chart_template_id", "in", self.chart_template_ids.ids)]
+ ):
+ # Search for a real account that matches the template
+ account_group_id = self.find_account_group_by_templates(template)
+ if not account_group_id:
+ # Account to be created
+ self.account_group_ids.create(
+ {
+ "account_group_id": template.id,
+ "update_chart_wizard_id": self.id,
+ "type": "new",
+ "notes": _("No account found with this code."),
+ }
+ )
+ else:
+ # Check the account for changes
+ account_group = self.env["account.group"].browse(account_group_id)
+ notes = self.diff_notes(template, account_group)
+ code_prefix_end = (
+ template.code_prefix_end
+ if template.code_prefix_end
+ and template.code_prefix_end < template.code_prefix_start
+ else template.code_prefix_start
+ )
+ if code_prefix_end != account_group.code_prefix_end:
+ notes += (notes and "\n" or "") + _(
+ "Differences in these fields: %s."
+ ) % template._fields["code_prefix_end"].get_description(self.env)[
+ "string"
+ ]
+ if self.recreate_xml_ids and self.missing_xml_id(
+ template, account_group
+ ):
+ notes += (notes and "\n" or "") + _("Missing XML-ID.")
+
+ if notes:
+ # Account to be updated
+ self.account_group_ids.create(
+ {
+ "account_group_id": template.id,
+ "update_chart_wizard_id": self.id,
+ "type": "updated",
+ "update_account_group_id": account_group_id,
+ "notes": notes,
+ }
+ )
+
def _find_fiscal_positions(self):
"""Load fiscal position templates to create/update."""
wiz_fp = self.env["wizard.update.charts.accounts.fiscal.position"]
@@ -1168,6 +1315,52 @@ class WizardUpdateChartsAccounts(models.TransientModel):
"account_ids": [(0, 0, x) for x in account_mapping],
}
+ def _prepare_account_group_vals(self, template):
+ return {
+ "name": template.name,
+ "code_prefix_start": template.code_prefix_start,
+ "code_prefix_end": template.code_prefix_end,
+ "company_id": self.company_id.id,
+ }
+
+ def _update_account_groups(self):
+ """Process fiscal position templates to create/update."""
+ for wiz_account_group in self.account_group_ids:
+ account_group, template = (
+ wiz_account_group.update_account_group_id,
+ wiz_account_group.account_group_id,
+ )
+ if wiz_account_group.type == "new":
+ # Create a new fiscal position
+ self.chart_template_id.create_record_with_xmlid(
+ self.company_id,
+ template,
+ "account.group",
+ self._prepare_account_group_vals(template),
+ )
+ _logger.info(_("Created account group %s."), "'%s'" % template.name)
+ else:
+ for key, value in self.diff_fields(template, account_group).items():
+ account_group[key] = value
+ _logger.info(_("Updated account group %s."), "'%s'" % template.name)
+ code_prefix_end = (
+ template.code_prefix_end
+ if template.code_prefix_end
+ and template.code_prefix_end < template.code_prefix_start
+ else template.code_prefix_start
+ )
+ if code_prefix_end != account_group.code_prefix_end:
+ account_group.code_prefix_end = code_prefix_end
+ _logger.info(_("Updated account group %s."), "'%s'" % template.name)
+ if self.recreate_xml_ids and self.missing_xml_id(
+ template, account_group
+ ):
+ self.recreate_xml_id(template, account_group)
+ _logger.info(
+ _("Updated account group %s. (Recreated XML-ID)"),
+ "'%s'" % template.name,
+ )
+
def _update_fiscal_positions(self):
"""Process fiscal position templates to create/update."""
for wiz_fp in self.fiscal_position_ids:
@@ -1352,3 +1545,50 @@ class WizardFpMatching(models.TransientModel):
vals = super(WizardFpMatching, self)._get_matching_selection()
vals += self._selection_from_files("account.fiscal.position.template", ["name"])
return vals
+
+
+class WizardAccountGroupMatching(models.TransientModel):
+ _name = "wizard.account.group.matching"
+ _description = "Wizard Account Group Matching"
+ _inherit = "wizard.matching"
+
+ def _get_matching_selection(self):
+ vals = super()._get_matching_selection()
+ vals += self._selection_from_files(
+ "account.group.template", ["code_prefix_start"]
+ )
+ return vals
+
+
+class WizardUpdateChartsAccountsAccountGroup(models.TransientModel):
+ _name = "wizard.update.charts.accounts.account.group"
+ _description = (
+ "Account group that needs to be updated (new or updated in the template)."
+ )
+
+ account_group_id = fields.Many2one(
+ comodel_name="account.group.template",
+ string="Account group template",
+ required=True,
+ )
+ update_chart_wizard_id = fields.Many2one(
+ comodel_name="wizard.update.charts.accounts",
+ string="Update chart wizard",
+ required=True,
+ ondelete="cascade",
+ )
+ type = fields.Selection(
+ selection=[("new", "New template"), ("updated", "Updated template")],
+ readonly=False,
+ )
+ update_account_group_id = fields.Many2one(
+ comodel_name="account.group",
+ string="Account group to update",
+ required=False,
+ ondelete="set null",
+ )
+ notes = fields.Text(readonly=True)
+ recreate_xml_ids = fields.Boolean(
+ string="Recreate missing XML-IDs",
+ related="update_chart_wizard_id.recreate_xml_ids",
+ )
diff --git a/account_chart_update/wizard/wizard_chart_update_view.xml b/account_chart_update/wizard/wizard_chart_update_view.xml
index 7640fb61..087df635 100644
--- a/account_chart_update/wizard/wizard_chart_update_view.xml
+++ b/account_chart_update/wizard/wizard_chart_update_view.xml
@@ -49,6 +49,7 @@
+
@@ -119,6 +121,17 @@
context="{'account_chart_update': True}"
/>
+
+
+
@@ -182,6 +196,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -331,6 +398,7 @@
+