diff --git a/backend/gsmaster/export_import.go b/backend/gsmaster/export_import.go index ac906fd..0750bdf 100644 --- a/backend/gsmaster/export_import.go +++ b/backend/gsmaster/export_import.go @@ -140,9 +140,10 @@ type ExportableClassSpellSchoolEPCost struct { // ExportableSpellLevelLECost represents spell level LE costs for export type ExportableSpellLevelLECost struct { - Level int `json:"level"` - LERequired int `json:"le_required"` - GameSystem string `json:"game_system"` + Level int `json:"level"` + LERequired int `json:"le_required"` + GameSystem string `json:"game_system"` + GameSystemId uint `json:"game_system_id"` } // ExportableSkillImprovementCost represents skill improvement costs for export @@ -1204,9 +1205,10 @@ func ExportSpellLevelLECosts(outputDir string) error { exportable := make([]ExportableSpellLevelLECost, len(costs)) for i, cost := range costs { exportable[i] = ExportableSpellLevelLECost{ - Level: cost.Level, - LERequired: cost.LERequired, - GameSystem: cost.GameSystem, + Level: cost.Level, + LERequired: cost.LERequired, + GameSystem: cost.GameSystem, + GameSystemId: cost.GameSystemId, } } @@ -1221,23 +1223,27 @@ func ImportSpellLevelLECosts(inputDir string) error { } for _, exp := range exportable { + gs := models.GetGameSystem(exp.GameSystemId, exp.GameSystem) var cost models.SpellLevelLECost - result := database.DB.Where("level = ? AND game_system = ?", exp.Level, exp.GameSystem).First(&cost) + result := database.DB.Where("level = ? AND (game_system = ? OR game_system_id = ?)", exp.Level, gs.Name, gs.ID).First(&cost) if result.Error == gorm.ErrRecordNotFound { cost = models.SpellLevelLECost{ - Level: exp.Level, - LERequired: exp.LERequired, - GameSystem: exp.GameSystem, + Level: exp.Level, + LERequired: exp.LERequired, + GameSystem: gs.Name, + GameSystemId: gs.ID, } - if err := database.DB.Create(&cost).Error; err != nil { + if err := cost.Create(); err != nil { return fmt.Errorf("failed to create spell level LE cost: %w", err) } } else if result.Error != nil { return fmt.Errorf("failed to query spell level LE cost: %w", result.Error) } else { cost.LERequired = exp.LERequired - if err := database.DB.Save(&cost).Error; err != nil { + cost.GameSystem = gs.Name + cost.GameSystemId = gs.ID + if err := cost.Save(); err != nil { return fmt.Errorf("failed to update spell level LE cost: %w", err) } } diff --git a/backend/models/model_learning_costs.go b/backend/models/model_learning_costs.go index 1692a11..1ad2d9f 100644 --- a/backend/models/model_learning_costs.go +++ b/backend/models/model_learning_costs.go @@ -95,10 +95,11 @@ type ClassSpellSchoolEPCost struct { // SpellLevelLECost definiert LE-Kosten pro Zauber-Stufe type SpellLevelLECost struct { - ID uint `gorm:"primaryKey" json:"id"` - Level int `gorm:"uniqueIndex;not null" json:"level"` // Zauber-Stufe (1-12) - LERequired int `gorm:"not null" json:"le_required"` // Benötigte Lerneinheiten - GameSystem string `gorm:"index;default:midgard" json:"game_system"` + ID uint `gorm:"primaryKey" json:"id"` + Level int `gorm:"uniqueIndex;not null" json:"level"` // Zauber-Stufe (1-12) + LERequired int `gorm:"not null" json:"le_required"` // Benötigte Lerneinheiten + GameSystem string `gorm:"index;default:midgard" json:"game_system"` + GameSystemId uint `json:"game_system_id,omitempty"` } // SkillCategoryDifficulty definiert die Schwierigkeit einer Fertigkeit in einer bestimmten Kategorie @@ -155,6 +156,8 @@ type SkillLearningInfo struct { ClassCode string `json:"class_code"` ClassName string `json:"class_name"` EPPerTE int `json:"ep_per_te"` + GameSystem string `json:"game_system"` + GameSystemId uint `json:"game_system_id,omitempty"` } // SpellLearningInfo kombiniert alle Informationen für einen Zauber @@ -169,6 +172,8 @@ type SpellLearningInfo struct { ClassName string `json:"class_name"` EPPerLE int `json:"ep_per_le"` LERequired int `json:"le_required"` + GameSystem string `json:"game_system"` + GameSystemId uint `json:"game_system_id,omitempty"` } // AuditLogEntry repräsentiert einen Eintrag im Audit-Log @@ -232,6 +237,27 @@ func (SkillImprovementCost) TableName() string { return "learning_skill_improvement_costs" } +func (sllc *SpellLevelLECost) ensureGameSystem() { + gs := GetGameSystem(sllc.GameSystemId, sllc.GameSystem) + sllc.GameSystemId = gs.ID + sllc.GameSystem = gs.Name +} + +func (sllc *SpellLevelLECost) BeforeCreate(tx *gorm.DB) error { + sllc.ensureGameSystem() + return nil +} + +func (sllc *SpellLevelLECost) BeforeSave(tx *gorm.DB) error { + sllc.ensureGameSystem() + return nil +} + +func (sllc *SpellLevelLECost) Save() error { + sllc.ensureGameSystem() + return database.DB.Save(sllc).Error +} + func (s *Source) ensureGameSystem() { gs := GetGameSystem(s.GameSystemId, s.GameSystem) s.GameSystemId = gs.ID @@ -411,6 +437,7 @@ func (cssec *ClassSpellSchoolEPCost) Create() error { } func (sllc *SpellLevelLECost) Create() error { + sllc.ensureGameSystem() return database.DB.Create(sllc).Error } @@ -457,11 +484,14 @@ func GetEPPerLEForClassAndSpellSchool(classCode string, schoolName string) (int, // GetSkillCategoryAndDifficultyNewSystem findet die beste Kategorie für eine Fertigkeit basierend auf niedrigsten EP-Kosten func GetSkillCategoryAndDifficultyNewSystem(skillName string, classCode string) (*SkillLearningInfo, error) { var results []SkillLearningInfo + gs := GetGameSystem(0, "midgard") err := database.DB.Raw(` SELECT scd.skill_id, s.name as skill_name, + s.game_system as game_system, + s.game_system_id as game_system_id, scd.skill_category as category_name, scd.skill_difficulty as difficulty_name, scd.learn_cost, @@ -472,14 +502,20 @@ func GetSkillCategoryAndDifficultyNewSystem(skillName string, classCode string) FROM learning_skill_category_difficulties scd JOIN learning_class_category_ep_costs ccec ON scd.skill_category = ccec.skill_category JOIN gsm_skills s ON scd.skill_id = s.id - WHERE s.name = ? AND ccec.character_class = ? + WHERE s.name = ? AND ccec.character_class = ? AND (s.game_system = ? OR s.game_system_id = ?) ORDER BY total_cost ASC - `, skillName, classCode).Scan(&results).Error + `, skillName, classCode, gs.Name, gs.ID).Scan(&results).Error if err != nil { return nil, err } + for i := range results { + gs := GetGameSystem(results[i].GameSystemId, results[i].GameSystem) + results[i].GameSystemId = gs.ID + results[i].GameSystem = gs.Name + } + if len(results) == 0 { return nil, gorm.ErrRecordNotFound } @@ -490,11 +526,14 @@ func GetSkillCategoryAndDifficultyNewSystem(skillName string, classCode string) // GetSkillInfoCategoryAndDifficultyNewSystem holt die Informationen für eine spezifische Kategorie/Schwierigkeit func GetSkillInfoCategoryAndDifficultyNewSystem(skillName, category, difficulty, classCode string) (*SkillLearningInfo, error) { var result SkillLearningInfo + gs := GetGameSystem(0, "midgard") err := database.DB.Raw(` SELECT scd.skill_id, s.name as skill_name, + s.game_system as game_system, + s.game_system_id as game_system_id, scd.skill_category as category_name, scd.skill_difficulty as difficulty_name, scd.learn_cost, @@ -505,13 +544,17 @@ func GetSkillInfoCategoryAndDifficultyNewSystem(skillName, category, difficulty, FROM learning_skill_category_difficulties scd JOIN learning_class_category_ep_costs ccec ON scd.skill_category = ccec.skill_category JOIN gsm_skills s ON scd.skill_id = s.id - WHERE s.name = ? AND scd.skill_category = ? AND scd.skill_difficulty = ? AND ccec.character_class = ? - `, skillName, category, difficulty, classCode).Scan(&result).Error + WHERE s.name = ? AND scd.skill_category = ? AND scd.skill_difficulty = ? AND ccec.character_class = ? AND (s.game_system = ? OR s.game_system_id = ?) + `, skillName, category, difficulty, classCode, gs.Name, gs.ID).Scan(&result).Error if err != nil { return nil, err } + gs = GetGameSystem(result.GameSystemId, result.GameSystem) + result.GameSystemId = gs.ID + result.GameSystem = gs.Name + return &result, nil } @@ -523,6 +566,8 @@ func GetSpellLearningInfoNewSystem(spellName string, classCode string) (*SpellLe SELECT s.id as spell_id, s.name as spell_name, + s.game_system as game_system, + s.game_system_id as game_system_id, s.stufe as spell_level, s.learning_category as school_name, cssec.character_class as class_code, @@ -531,7 +576,7 @@ func GetSpellLearningInfoNewSystem(spellName string, classCode string) (*SpellLe COALESCE(sllc.le_required, 0) as le_required FROM gsm_spells s JOIN learning_class_spell_school_ep_costs cssec ON s.learning_category = cssec.spell_school - LEFT JOIN learning_spell_level_le_costs sllc ON s.stufe = sllc.level + LEFT JOIN learning_spell_level_le_costs sllc ON s.stufe = sllc.level AND (sllc.game_system = s.game_system OR sllc.game_system_id = s.game_system_id OR sllc.game_system_id IS NULL) WHERE s.name = ? AND cssec.character_class = ? `, spellName, classCode).Scan(&result).Error @@ -539,15 +584,19 @@ func GetSpellLearningInfoNewSystem(spellName string, classCode string) (*SpellLe return nil, err } + // Validate that we found a spell (spell_id should be > 0) + if result.SpellID == 0 { + return nil, gorm.ErrRecordNotFound + } + // Validate spell level - level 0 is not a valid spell level if result.SpellLevel <= 0 { return nil, fmt.Errorf("invalid spell level %d for spell '%s' - spell levels must be 1 or higher", result.SpellLevel, spellName) } - // Validate that we found a spell (spell_id should be > 0) - if result.SpellID == 0 { - return nil, gorm.ErrRecordNotFound - } + gs := GetGameSystem(result.GameSystemId, result.GameSystem) + result.GameSystemId = gs.ID + result.GameSystem = gs.Name return &result, nil } diff --git a/backend/models/model_learning_costs_test.go b/backend/models/model_learning_costs_test.go index 9f635d6..6321518 100644 --- a/backend/models/model_learning_costs_test.go +++ b/backend/models/model_learning_costs_test.go @@ -225,6 +225,28 @@ func TestSkillDifficulty_Create_SetsGameSystem(t *testing.T) { assert.NotZero(t, difficulty.GameSystemId) } +// ============================================================================= +// Tests for SpellLevelLECost struct and methods +// ============================================================================= + +func TestSpellLevelLECost_Create_SetsGameSystem(t *testing.T) { + setupLearningCostsTestDB(t) + + gs := GetGameSystem(0, "midgard") + + cost := SpellLevelLECost{ + Level: 99, + LERequired: 1, + } + + err := cost.Create() + + require.NoError(t, err) + assert.NotZero(t, cost.ID) + assert.Equal(t, gs.Name, cost.GameSystem) + assert.Equal(t, gs.ID, cost.GameSystemId) +} + // ============================================================================= // Tests for SpellSchool struct and methods // ============================================================================= @@ -336,6 +358,19 @@ func TestGetSkillCategoryAndDifficultyNewSystem_Success(t *testing.T) { } } +func TestGetSkillCategoryAndDifficultyNewSystem_IncludesGameSystem(t *testing.T) { + setupLearningCostsTestDB(t) + + gs := GetGameSystem(0, "midgard") + + skillInfo, err := GetSkillCategoryAndDifficultyNewSystem("Schwimmen", "Bb") + + require.NoError(t, err) + require.NotNil(t, skillInfo) + assert.Equal(t, gs.Name, skillInfo.GameSystem) + assert.Equal(t, gs.ID, skillInfo.GameSystemId) +} + func TestGetSkillCategoryAndDifficultyNewSystem_InvalidSkill(t *testing.T) { setupLearningCostsTestDB(t) @@ -373,6 +408,53 @@ func TestGetSpellLearningInfoNewSystem_Success(t *testing.T) { } } +func TestGetSpellLearningInfoNewSystem_IncludesGameSystem(t *testing.T) { + setupLearningCostsTestDB(t) + + gs := GetGameSystem(0, "midgard") + require.NotNil(t, gs) + + var class CharacterClass + require.NoError(t, class.FirstByCode("Ma")) + + var school SpellSchool + require.NoError(t, school.FirstByName("Dweomer")) + + cost := ClassSpellSchoolEPCost{ + CharacterClassID: class.ID, + SpellSchoolID: school.ID, + EPPerLE: 3, + } + require.NoError(t, cost.Create()) + + var spellLevelCost SpellLevelLECost + err := database.DB.Where("level = ?", 1).First(&spellLevelCost).Error + if err != nil { + spellLevelCost = SpellLevelLECost{Level: 1, LERequired: 1, GameSystem: gs.Name, GameSystemId: gs.ID} + require.NoError(t, spellLevelCost.Create()) + } else { + spellLevelCost.GameSystem = gs.Name + spellLevelCost.GameSystemId = gs.ID + require.NoError(t, spellLevelCost.Save()) + } + + spell := Spell{ + Name: "TestSpellGS", + GameSystem: gs.Name, + GameSystemId: gs.ID, + Stufe: spellLevelCost.Level, + LearningCategory: school.Name, + } + require.NoError(t, spell.Create()) + + spellInfo, err := GetSpellLearningInfoNewSystem(spell.Name, class.Code) + + require.NoError(t, err) + require.NotNil(t, spellInfo) + assert.Equal(t, gs.Name, spellInfo.GameSystem) + assert.Equal(t, gs.ID, spellInfo.GameSystemId) +} + func TestGetSpellLearningInfoNewSystem_InvalidSpell(t *testing.T) { setupLearningCostsTestDB(t)