"""Game-neutral models for supported TCG systems.""" import re from odoo import _, api, fields, models from odoo.exceptions import UserError class MvdTcgGame(models.Model): """Represent a supported trading card game within the suite.""" _name = "mvd.tcg.game" _description = "TCG Game" _order = "sequence, name, id" name = fields.Char(required=True, translate=True, index="trigram") code = fields.Char(required=True, index=True) active = fields.Boolean(default=True) sequence = fields.Integer(default=10) description = fields.Text(translate=True) card_ids = fields.One2many("mvd.tcg.card", "game_id") card_count = fields.Integer(compute="_compute_card_count") _code_unique = models.Constraint( "UNIQUE (code)", "The game code must be unique.", ) @api.model def _mvd_tcg_generate_game_code(self, name): """Return a slug-like technical code for one game name.""" return re.sub(r"[^a-z0-9]+", "_", (name or "").strip().lower()).strip("_") or "game" @api.model def _mvd_tcg_get_unique_game_code(self, name): """Return a globally unique technical code for one game name.""" base_code = self._mvd_tcg_generate_game_code(name) existing_codes = set(self.search([]).mapped("code")) if base_code not in existing_codes: return base_code suffix = 2 while f"{base_code}_{suffix}" in existing_codes: suffix += 1 return f"{base_code}_{suffix}" def _mvd_tcg_can_edit_game_code(self): """Return whether the current user may edit technical game codes.""" return self.env.is_superuser() or any( self.env.user.has_group(xmlid) for xmlid in ( "mvd_tcg_base.mvd_tcg_base_group_administrator", "base.group_system", ) ) @api.model_create_multi def create(self, vals_list): """Generate missing technical game codes on manual creates.""" prepared_vals_list = [] for vals in vals_list: prepared_vals = dict(vals) if ( prepared_vals.get("code") and not self.env.context.get("mvd_tcg_bypass_game_code_write") and not self._mvd_tcg_can_edit_game_code() ): raise UserError( _( "Game codes are technical identifiers and can only be set " "by TCG administrators." ) ) if not prepared_vals.get("code"): prepared_vals["code"] = self._mvd_tcg_get_unique_game_code( prepared_vals.get("name") ) prepared_vals_list.append(prepared_vals) return super().create(prepared_vals_list) def write(self, vals): """Protect technical game codes from normal business edits.""" if "code" in vals and not self.env.context.get("mvd_tcg_bypass_game_code_write"): if not self._mvd_tcg_can_edit_game_code(): raise UserError( _( "Game codes are technical identifiers and can only be changed " "by TCG administrators." ) ) return super().write(vals) @api.depends("card_ids") def _compute_card_count(self): """Compute the number of base cards linked to each game.""" card_data = self.env["mvd.tcg.card"]._read_group( [("game_id", "in", self.ids)], ["game_id"], ["__count"], ) counts_by_game = {game.id: count for game, count in card_data} for game in self: game.card_count = counts_by_game.get(game.id, 0) def action_open_cards(self): """Open the cards catalog filtered on the selected game.""" self.ensure_one() action = self.env["ir.actions.actions"]._for_xml_id( "mvd_tcg_base.mvd_tcg_card_action" ) action["domain"] = [("game_id", "=", self.id)] action["context"] = {"default_game_id": self.id} return action def _mvd_tcg_get_default_deck_board_templates(self): """Return default board definitions for new decks of this game. Returns: list[dict[str, object]]: Ordered board configuration dictionaries. """ self.ensure_one() return [ { "name": _("Mainboard"), "code": "mainboard", "sequence": 10, "include_in_total": True, }, { "name": _("Sideboard"), "code": "sideboard", "sequence": 20, "include_in_total": True, }, { "name": _("Maybeboard"), "code": "maybeboard", "sequence": 30, "include_in_total": False, }, ]