commit c679a207d4138a37f0be0db15fa392cb9824bce7 Author: Marc Wempe Date: Fri Apr 3 23:08:57 2026 +0200 🎉 Initialize module repository diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c5f867 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +*.py[cod] +.DS_Store +.pytest_cache/ +.ruff_cache/ +*.log +*.swp +*~ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..df5af2b --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,31 @@ +{ + "name": "MVD TCG Base", + "summary": "Game-neutral card and game foundation for the MVD TCG suite", + "version": "19.0.5.7.0", + "description": """ +Game-neutral foundation for the MVD trading card game suite. + +This module provides the shared reference models and base navigation used by +all game-specific adapters and higher-level features: +- supported games +- neutral card records with images and validation state +- shared menus, access rights, and core views + +It is intentionally kept game-agnostic so that other TCG adapters can build +on the same abstraction layer later. +""", + "category": "Tools", + "author": "Mantjeverse Digital", + "license": "LGPL-3", + "depends": ["base", "base_setup"], + "data": [ + "security/security.xml", + "security/ir.model.access.csv", + "views/res_config_settings_views.xml", + "views/mvd_tcg_game_views.xml", + "views/mvd_tcg_card_views.xml", + "views/menu_views.xml", + ], + "application": True, + "installable": True, +} diff --git a/i18n/de.po b/i18n/de.po new file mode 100644 index 0000000..1b0224e --- /dev/null +++ b/i18n/de.po @@ -0,0 +1,472 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mvd_tcg_base +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e-20260324\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-04-03 05:05+0000\n" +"PO-Revision-Date: 2026-04-03 05:05+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Language: de\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mvd_tcg_base +#: model:ir.module.category,description:mvd_tcg_base.mvd_tcg_base_module_category +msgid "Access rights for the Mantjeverse Digital TCG suite." +msgstr "Zugriffsrechte für die Mantjeverse-Digital-TCG-Suite." + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__active +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__active +msgid "Active" +msgstr "Aktiv" + +#. module: mvd_tcg_base +#: model:ir.model.fields,help:mvd_tcg_base.field_mvd_tcg_card__external_ref +msgid "Adapter-specific identifier for this card reference." +msgstr "Adapterspezifische Kennung für diese Kartenreferenz." + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +msgid "Add a shared card reference description." +msgstr "Füge eine gemeinsame Beschreibung für diese Kartenreferenz hinzu." + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +msgid "Add internal neutral notes for this card reference." +msgstr "Füge interne, spielneutrale Notizen für diese Kartenreferenz hinzu." + +#. module: mvd_tcg_base +#: model:res.groups,name:mvd_tcg_base.mvd_tcg_base_group_administrator +msgid "Administrator" +msgstr "Administrator" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_search +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_form +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_search +msgid "Archived" +msgstr "Archiviert" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__card_ids +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_kanban +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_search +msgid "Card" +msgstr "Karte" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__card_count +msgid "Card Count" +msgstr "Kartenanzahl" + +#. module: mvd_tcg_base +#: model_terms:ir.actions.act_window,help:mvd_tcg_base.mvd_tcg_card_action +msgid "" +"Card references in the base module stay game-neutral and can be\n" +" enriched later by game-specific adapters such as MTG." +msgstr "" +"Kartenreferenzen bleiben im Basismodul spielneutral und können später durch " +"spielspezifische Adapter wie MTG ergänzt werden." + +#. module: mvd_tcg_base +#: model:ir.actions.act_window,name:mvd_tcg_base.mvd_tcg_card_action +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_list +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_search +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_form +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_kanban +msgid "Cards" +msgstr "Karten" + +#. module: mvd_tcg_base +#: model:ir.ui.menu,name:mvd_tcg_base.mvd_tcg_cards_menu +msgid "All Cards" +msgstr "Alle Karten" + +#. module: mvd_tcg_base +#: model:ir.ui.menu,name:mvd_tcg_base.mvd_tcg_catalog_menu +msgid "Reference" +msgstr "Referenz" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__code +msgid "Code" +msgstr "Code" + +#. module: mvd_tcg_base +#: model:ir.ui.menu,name:mvd_tcg_base.mvd_tcg_configuration_menu +msgid "Configuration" +msgstr "Konfiguration" + +#. module: mvd_tcg_base +#: model:ir.ui.menu,name:mvd_tcg_base.mvd_tcg_operations_menu +msgid "Deck Builder" +msgstr "Deckbuilder" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "" +"Configure connector and analysis defaults here. Technical integration\n" +" details stay in settings instead of user-facing data forms." +msgstr "" +"Konfiguriere hier Connector- und Analyse-Standards. Technische " +"Integrationsdetails bleiben in den Einstellungen statt in " +"benutzerorientierten Datenformularen." + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "Connector and AI defaults for the MVD TCG suite." +msgstr "Connector- und KI-Standards für die MVD-TCG-Suite." + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "Uploads" +msgstr "Uploads" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "Maximum File Upload Size" +msgstr "Maximale Datei-Upload-Größe" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "Controls the file size limit exposed to the Odoo web client for TCG-related uploads and imports." +msgstr "Steuert die Dateigrenze, die der Odoo-Webclient für TCG-bezogene Uploads und Importe anzeigt." + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "Increase this value if large CSV, XLSX, ZIP, or image archives should be uploaded through the browser." +msgstr "Erhöhe diesen Wert, wenn große CSV-, XLSX-, ZIP- oder Bildarchive über den Browser hochgeladen werden sollen." + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_res_config_settings__mvd_tcg_max_file_upload_size_mb +msgid "Maximum File Upload Size (MB)" +msgstr "Maximale Datei-Upload-Größe (MB)" + +#. module: mvd_tcg_base +#: model:ir.model.fields,help:mvd_tcg_base.field_res_config_settings__mvd_tcg_max_file_upload_size_mb +msgid "Maximum file size exposed to the Odoo web client for uploads in TCG workflows." +msgstr "Maximale Dateigröße, die dem Odoo-Webclient für Uploads in TCG-Workflows signalisiert wird." + +#. module: mvd_tcg_base +#: model_terms:ir.actions.act_window,help:mvd_tcg_base.mvd_tcg_card_action +msgid "Create a new card reference" +msgstr "Neue Kartenreferenz anlegen" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__create_uid +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__create_uid +msgid "Created by" +msgstr "Erstellt von" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__create_date +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__create_date +msgid "Created on" +msgstr "Erstellt am" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_form +msgid "Describe the supported game system." +msgstr "Beschreibe das unterstützte Spielsystem." + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__description +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__description +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_form +msgid "Description" +msgstr "Beschreibung" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__display_name +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__display_name +msgid "Display Name" +msgstr "Anzeigename" + +#. module: mvd_tcg_base +#: model:ir.model.fields.selection,name:mvd_tcg_base.selection__mvd_tcg_card__state__draft +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_search +msgid "Draft" +msgstr "Entwurf" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__external_ref +msgid "External Ref" +msgstr "Externe Referenz" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__game_id +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_search +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_form +msgid "Game" +msgstr "Spiel" + +#. module: mvd_tcg_base +#. odoo-python +#: code:addons/mvd_tcg_base/models/mvd_tcg_game.py:0 +msgid "" +"Game codes are technical identifiers and can only be changed by TCG " +"administrators." +msgstr "" +"Spielcodes sind technische Kennungen und können nur von TCG-Administratoren " +"geändert werden." + +#. module: mvd_tcg_base +#: model:ir.actions.act_window,name:mvd_tcg_base.mvd_tcg_game_action +#: model:ir.ui.menu,name:mvd_tcg_base.mvd_tcg_games_menu +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_list +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_search +msgid "Games" +msgstr "Spiele" + +#. module: mvd_tcg_base +#: model_terms:ir.actions.act_window,help:mvd_tcg_base.mvd_tcg_game_action +msgid "" +"Games define the neutral root taxonomy that later adapters and\n" +" commerce modules can build on." +msgstr "" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_form +msgid "General Information" +msgstr "Allgemeine Informationen" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__id +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__id +msgid "ID" +msgstr "ID" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__image_1920 +msgid "Image" +msgstr "Bild" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__image_1024 +msgid "Image 1024" +msgstr "" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__image_128 +msgid "Image 128" +msgstr "" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__image_256 +msgid "Image 256" +msgstr "" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__image_512 +msgid "Image 512" +msgstr "" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +msgid "Internal Notes" +msgstr "Interne Notizen" + +#. module: mvd_tcg_base +#: model:ir.model.fields,help:mvd_tcg_base.field_mvd_tcg_card__note +msgid "Internal neutral note stored on the card reference." +msgstr "" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__write_uid +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__write_uid +msgid "Last Updated by" +msgstr "Zuletzt aktualisiert von" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__write_date +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__write_date +msgid "Last Updated on" +msgstr "Zuletzt aktualisiert am" + +#. module: mvd_tcg_base +#: model:ir.module.category,name:mvd_tcg_base.mvd_tcg_base_module_category +msgid "MVD TCG" +msgstr "" + +#. module: mvd_tcg_base +#. odoo-python +#: code:addons/mvd_tcg_base/models/mvd_tcg_game.py:0 +msgid "Mainboard" +msgstr "Mainboard" + +#. module: mvd_tcg_base +#: model:res.groups,name:mvd_tcg_base.mvd_tcg_base_group_manager +msgid "Manager" +msgstr "Manager" + +#. module: mvd_tcg_base +#. odoo-python +#: code:addons/mvd_tcg_base/models/mvd_tcg_game.py:0 +msgid "Maybeboard" +msgstr "Maybeboard" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__name +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__name +msgid "Name" +msgstr "Name" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__note +msgid "Note" +msgstr "" + +#. module: mvd_tcg_base +msgid "Operations" +msgstr "Operationen" + +#. module: mvd_tcg_base +#: model:res.groups,name:mvd_tcg_base.mvd_tcg_base_group_operator +msgid "Operator" +msgstr "Operator" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "Overview" +msgstr "Übersicht" + +#. module: mvd_tcg_base +#: model_terms:ir.actions.act_window,help:mvd_tcg_base.mvd_tcg_game_action +msgid "Register a supported trading card game" +msgstr "" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +msgid "Reset to Draft" +msgstr "Auf Entwurf zurücksetzen" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__sequence +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_game__sequence +msgid "Sequence" +msgstr "Reihenfolge" + +#. module: mvd_tcg_base +#: model:ir.actions.act_window,name:mvd_tcg_base.mvd_tcg_action_configuration +#: model:ir.ui.menu,name:mvd_tcg_base.mvd_tcg_settings_menu +msgid "TCG Settings" +msgstr "TCG-Einstellungen" + +#. module: mvd_tcg_base +#. odoo-python +#: code:addons/mvd_tcg_base/models/mvd_tcg_game.py:0 +msgid "Sideboard" +msgstr "Sideboard" + +#. module: mvd_tcg_base +#: model:ir.model.fields,field_description:mvd_tcg_base.field_mvd_tcg_card__state +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_search +msgid "State" +msgstr "Status" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "Suite Configuration" +msgstr "Suite-Konfiguration" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "" +"Scryfall Connector:\n" +" import defaults and API behavior for MTG reference sync." +msgstr "" +"Scryfall-Connector:\n" +" Import-Standards und API-Verhalten für den MTG-Referenzabgleich." + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "" +"OpenAI Analysis:\n" +" model and batching defaults for deck analysis, role tagging,\n" +" alternatives, and deck fill." +msgstr "" +"OpenAI-Analyse:\n" +" Modell- und Batch-Standards für Deckanalyse, Rollentags,\n" +" Alternativen und das Auffüllen von Decks." + +#. module: mvd_tcg_base +#: model:ir.ui.menu,name:mvd_tcg_base.mvd_tcg_root_menu +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_res_config_settings_view_form +msgid "TCG" +msgstr "TCG" + +#. module: mvd_tcg_base +#: model:res.groups.privilege,name:mvd_tcg_base.mvd_tcg_base_privilege_access +msgid "TCG Access" +msgstr "TCG-Zugriff" + +#. module: mvd_tcg_base +#: model:ir.model,name:mvd_tcg_base.model_mvd_tcg_card +msgid "TCG Card" +msgstr "" + +#. module: mvd_tcg_base +#: model:ir.model,name:mvd_tcg_base.model_mvd_tcg_game +msgid "TCG Game" +msgstr "" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_form +msgid "Technical" +msgstr "Technisch" + +#. module: mvd_tcg_base +#: model:ir.model.constraint,message:mvd_tcg_base.constraint_mvd_tcg_card_external_ref_unique +msgid "The external reference must be unique per game." +msgstr "Die externe Referenz muss pro Spiel eindeutig sein." + +#. module: mvd_tcg_base +#: model:ir.model.constraint,message:mvd_tcg_base.constraint_mvd_tcg_game_code_unique +msgid "The game code must be unique." +msgstr "Der Spielcode muss eindeutig sein." + +#. module: mvd_tcg_base +#: model:res.groups,name:mvd_tcg_base.mvd_tcg_base_group_user +msgid "User" +msgstr "Benutzer" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +msgid "Validate" +msgstr "Validieren" + +#. module: mvd_tcg_base +#: model:ir.model.fields.selection,name:mvd_tcg_base.selection__mvd_tcg_card__state__validated +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_search +msgid "Validated" +msgstr "Validiert" + +#. module: mvd_tcg_base +#. odoo-python +#: code:addons/mvd_tcg_base/models/mvd_tcg_card.py:0 +msgid "" +"Validated cards are read-only. Reset the card to Draft first if you really " +"want to edit it." +msgstr "" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_card_view_form +msgid "e.g. Black Lotus" +msgstr "" + +#. module: mvd_tcg_base +#: model_terms:ir.ui.view,arch_db:mvd_tcg_base.mvd_tcg_game_view_form +msgid "e.g. Magic: The Gathering" +msgstr "" diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..92e9a00 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,4 @@ +from . import mvd_tcg_card +from . import mvd_tcg_game +from . import ir_http +from . import res_config_settings diff --git a/models/ir_http.py b/models/ir_http.py new file mode 100644 index 0000000..4ea3ec5 --- /dev/null +++ b/models/ir_http.py @@ -0,0 +1,13 @@ +from odoo import models + + +class IrHttp(models.AbstractModel): + _inherit = "ir.http" + + def session_info(self): + """Expose the configured max upload size to the web client.""" + info = super().session_info() + config = self.env["ir.config_parameter"].sudo() + upload_limit_mb = int(config.get_param("mvd_tcg_base.max_file_upload_size_mb", "256") or 256) + info["max_file_upload_size"] = max(upload_limit_mb, 1) * 1024 * 1024 + return info diff --git a/models/mvd_tcg_card.py b/models/mvd_tcg_card.py new file mode 100644 index 0000000..d8aaec6 --- /dev/null +++ b/models/mvd_tcg_card.py @@ -0,0 +1,150 @@ +"""Game-neutral card reference models.""" + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class MvdTcgCard(models.Model): + """Represent a game-neutral card reference entry.""" + + _name = "mvd.tcg.card" + _description = "TCG Card" + _inherit = "image.mixin" + _order = "game_id, sequence, name, id" + + active = fields.Boolean(default=True) + sequence = fields.Integer(default=10) + state = fields.Selection( + selection=[ + ("draft", "Draft"), + ("validated", "Validated"), + ], + default="draft", + required=True, + index=True, + copy=False, + ) + name = fields.Char(required=True, translate=True, index="trigram") + game_id = fields.Many2one( + "mvd.tcg.game", + required=True, + index=True, + ondelete="restrict", + ) + external_ref = fields.Char( + index=True, + help="Adapter-specific identifier for this card reference.", + ) + description = fields.Html( + translate=True, + ) + note = fields.Text( + translate=True, + help="Internal neutral note stored on the card reference.", + ) + + _external_ref_unique = models.Constraint( + "UNIQUE (game_id, external_ref)", + "The external reference must be unique per game.", + ) + + def _mvd_tcg_can_edit_external_ref(self): + """Return whether the current user may edit adapter reference ids. + + Returns: + bool: ``True`` for TCG administrators and system users. + """ + 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): + """Protect technical external references during card creation. + + Args: + vals_list: Standard ORM payloads for new cards. + + Returns: + Model: Created card records. + """ + prepared_vals_list = [] + for vals in vals_list: + prepared_vals = dict(vals) + if ( + prepared_vals.get("external_ref") + and not self.env.context.get("mvd_tcg_bypass_external_ref_write") + and not self._mvd_tcg_can_edit_external_ref() + ): + raise UserError( + _( + "Adapter reference ids can only be set by TCG administrators." + ) + ) + prepared_vals_list.append(prepared_vals) + return super().create(prepared_vals_list) + + def write(self, vals): + """Prevent accidental edits on validated reference cards. + + Args: + vals: Field values to update. + + Returns: + bool: Result of the underlying ORM write. + + Raises: + UserError: If a validated card is modified outside the allowed paths. + """ + if ( + "external_ref" in vals + and not self.env.context.get("mvd_tcg_bypass_external_ref_write") + and not self._mvd_tcg_can_edit_external_ref() + ): + raise UserError( + _( + "Adapter reference ids can only be changed by TCG administrators." + ) + ) + if not self.env.context.get("mvd_tcg_bypass_validated_write"): + validated_cards = self.filtered(lambda card: card.state == "validated") + is_reset_to_draft = set(vals) == {"state"} and vals.get("state") == "draft" + if validated_cards and not is_reset_to_draft: + raise UserError( + _( + "Validated cards are read-only. Reset the card to Draft first " + "if you really want to edit it." + ) + ) + return super().write(vals) + + def action_validate(self): + """Lock the current cards as validated reference data. + + Returns: + bool: ``True`` when the operation completed. + """ + self.write({"state": "validated"}) + return True + + def action_reset_to_draft(self): + """Move the current cards back into editable draft state. + + Returns: + bool: ``True`` when the operation completed. + """ + self.write({"state": "draft"}) + return True + + def _mvd_tcg_get_deck_image_binary(self): + """Return the preferred card image for deck previews. + + Returns: + str | bool: Base64 image data for deck-related previews. + """ + self.ensure_one() + return self.image_512 or self.image_1920 or False diff --git a/models/mvd_tcg_game.py b/models/mvd_tcg_game.py new file mode 100644 index 0000000..4816c2b --- /dev/null +++ b/models/mvd_tcg_game.py @@ -0,0 +1,140 @@ +"""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, + }, + ] diff --git a/models/res_config_settings.py b/models/res_config_settings.py new file mode 100644 index 0000000..6564499 --- /dev/null +++ b/models/res_config_settings.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + mvd_tcg_max_file_upload_size_mb = fields.Integer( + string="Maximum File Upload Size (MB)", + config_parameter="mvd_tcg_base.max_file_upload_size_mb", + default=256, + help="Maximum file size exposed to the Odoo web client for uploads in TCG workflows.", + ) diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..cda3db7 --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,8 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_mvd_tcg_game_user,mvd.tcg.game.user,model_mvd_tcg_game,mvd_tcg_base.mvd_tcg_base_group_user,1,0,0,0 +access_mvd_tcg_game_operator,mvd.tcg.game.operator,model_mvd_tcg_game,mvd_tcg_base.mvd_tcg_base_group_operator,1,0,0,0 +access_mvd_tcg_game_manager,mvd.tcg.game.manager,model_mvd_tcg_game,mvd_tcg_base.mvd_tcg_base_group_manager,1,1,1,1 +access_mvd_tcg_game_system,mvd.tcg.game.system,model_mvd_tcg_game,base.group_system,1,1,1,1 +access_mvd_tcg_card_user,mvd.tcg.card.user,model_mvd_tcg_card,mvd_tcg_base.mvd_tcg_base_group_user,1,0,0,0 +access_mvd_tcg_card_manager,mvd.tcg.card.manager,model_mvd_tcg_card,mvd_tcg_base.mvd_tcg_base_group_manager,1,1,1,1 +access_mvd_tcg_card_system,mvd.tcg.card.system,model_mvd_tcg_card,base.group_system,1,1,1,1 diff --git a/security/security.xml b/security/security.xml new file mode 100644 index 0000000..3d1a1dc --- /dev/null +++ b/security/security.xml @@ -0,0 +1,43 @@ + + + + MVD TCG + Access rights for the Mantjeverse Digital TCG suite. + 42 + + + + TCG Access + + + + + User + + + + + + Operator + + + + + + Manager + + + + + + Administrator + + + + diff --git a/static/description/icon.png b/static/description/icon.png new file mode 100644 index 0000000..8b76dd3 Binary files /dev/null and b/static/description/icon.png differ diff --git a/views/menu_views.xml b/views/menu_views.xml new file mode 100644 index 0000000..44faabf --- /dev/null +++ b/views/menu_views.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/views/mvd_tcg_card_views.xml b/views/mvd_tcg_card_views.xml new file mode 100644 index 0000000..c58bfb4 --- /dev/null +++ b/views/mvd_tcg_card_views.xml @@ -0,0 +1,200 @@ + + + + mvd.tcg.card.view.search + mvd.tcg.card + + + + + + + + + + + + + + + + + + + + mvd.tcg.card.view.list + mvd.tcg.card + + + + + + + + + + + + + mvd.tcg.card.view.kanban + mvd.tcg.card + + + + + + + +
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ + + mvd.tcg.card.view.form + mvd.tcg.card + +
+
+
+ +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Cards + mvd.tcg.card + kanban,list,form + + + +

+ Create a new card reference +

+

+ Card references in the base module stay game-neutral and can be + enriched later by game-specific adapters such as MTG. +

+
+
+ diff --git a/views/mvd_tcg_game_views.xml b/views/mvd_tcg_game_views.xml new file mode 100644 index 0000000..21748de --- /dev/null +++ b/views/mvd_tcg_game_views.xml @@ -0,0 +1,143 @@ + + + + mvd.tcg.game.view.search + mvd.tcg.game + + + + + + + + + + + + mvd.tcg.game.view.list + mvd.tcg.game + + + + + + + + + + + + + mvd.tcg.game.view.kanban + mvd.tcg.game + + + + + + +
+
+
+ +
+
+ + Cards + +
+
+
+
+
+
+ + + mvd.tcg.game.view.form + mvd.tcg.game + +
+ +
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + Games + mvd.tcg.game + kanban,list,form + + + +

+ Register a supported trading card game +

+

+ Games define the neutral root taxonomy that later adapters and + commerce modules can build on. +

+
+
+
diff --git a/views/res_config_settings_views.xml b/views/res_config_settings_views.xml new file mode 100644 index 0000000..9d05554 --- /dev/null +++ b/views/res_config_settings_views.xml @@ -0,0 +1,65 @@ + + + + res.config.settings.view.form.inherit.mvd.tcg + res.config.settings + + + + + + +
+ Configure connector and analysis defaults here. Technical integration + details stay in settings instead of user-facing data forms. +
+
+
    +
  • + Scryfall Connector: + import defaults and API behavior for MTG reference sync. +
  • +
  • + OpenAI Analysis: + model and batching defaults for deck analysis, role tagging, + alternatives, and deck fill. +
  • +
+
+
+
+ + + +
+ Increase this value if large CSV, XLSX, ZIP, or image archives should be uploaded through the browser. +
+
+
+
+
+
+
+ + + TCG Settings + res.config.settings + form + current + {'module': 'mvd_tcg', 'bin_size': False} + +