From c679a207d4138a37f0be0db15fa392cb9824bce7 Mon Sep 17 00:00:00 2001 From: Marc Wempe Date: Fri, 3 Apr 2026 23:08:57 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Initialize=20module=20repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 + __init__.py | 1 + __manifest__.py | 31 ++ i18n/de.po | 472 ++++++++++++++++++++++++++++ models/__init__.py | 4 + models/ir_http.py | 13 + models/mvd_tcg_card.py | 150 +++++++++ models/mvd_tcg_game.py | 140 +++++++++ models/res_config_settings.py | 12 + security/ir.model.access.csv | 8 + security/security.xml | 43 +++ static/description/icon.png | Bin 0 -> 51676 bytes views/menu_views.xml | 104 ++++++ views/mvd_tcg_card_views.xml | 200 ++++++++++++ views/mvd_tcg_game_views.xml | 143 +++++++++ views/res_config_settings_views.xml | 65 ++++ 16 files changed, 1394 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 i18n/de.po create mode 100644 models/__init__.py create mode 100644 models/ir_http.py create mode 100644 models/mvd_tcg_card.py create mode 100644 models/mvd_tcg_game.py create mode 100644 models/res_config_settings.py create mode 100644 security/ir.model.access.csv create mode 100644 security/security.xml create mode 100644 static/description/icon.png create mode 100644 views/menu_views.xml create mode 100644 views/mvd_tcg_card_views.xml create mode 100644 views/mvd_tcg_game_views.xml create mode 100644 views/res_config_settings_views.xml 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 0000000000000000000000000000000000000000..8b76dd32a99c1de5df1748922cda7898d9f096eb GIT binary patch literal 51676 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 o + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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} + +