From fcf785081c22d72037eb3f5a4b23a101afd8ad82 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 4 Jan 2026 17:49:04 +0100 Subject: [PATCH] Skill editieren un neu erstellen --- backend/gsmaster/routes.go | 3 +- backend/gsmaster/skill_create_test.go | 149 ++++++++++++++ backend/gsmaster/skill_enhanced_handlers.go | 94 ++++++++- .../gsmaster/skill_enhanced_handlers_test.go | 67 +++++++ .../src/components/maintenance/SkillView.vue | 185 +++++++++++++++++- frontend/src/locales/de | 4 + frontend/src/locales/en | 6 +- 7 files changed, 503 insertions(+), 5 deletions(-) create mode 100644 backend/gsmaster/skill_create_test.go diff --git a/backend/gsmaster/routes.go b/backend/gsmaster/routes.go index 778c0cc..02d8e5a 100644 --- a/backend/gsmaster/routes.go +++ b/backend/gsmaster/routes.go @@ -12,7 +12,8 @@ func RegisterRoutes(r *gin.RouterGroup) { { maintGrp.GET("", GetMasterData) maintGrp.GET("/skills", GetMDSkills) - maintGrp.GET("/skills-enhanced", GetEnhancedMDSkills) // New enhanced endpoint + maintGrp.GET("/skills-enhanced", GetEnhancedMDSkills) // New enhanced endpoint + maintGrp.POST("/skills-enhanced", CreateEnhancedMDSkill) // Create new skill maintGrp.GET("/skills/:id", GetMDSkill) maintGrp.GET("/skills-enhanced/:id", GetEnhancedMDSkill) // New enhanced endpoint maintGrp.PUT("/skills/:id", UpdateMDSkill) diff --git a/backend/gsmaster/skill_create_test.go b/backend/gsmaster/skill_create_test.go new file mode 100644 index 0000000..4b5d3a3 --- /dev/null +++ b/backend/gsmaster/skill_create_test.go @@ -0,0 +1,149 @@ +package gsmaster + +import ( + "bamort/database" + "bamort/models" + "testing" +) + +func TestCreateSkillWithCategories(t *testing.T) { + setupTestEnvironment(t) + database.SetupTestDB() + + // Create test dependencies + source := getOrCreateSource("TSTCRT", "TestCreate") + category := getOrCreateCategory("Alltag", source.ID) + difficulty := getOrCreateDifficulty("normal") + + // Prepare create request + req := SkillUpdateRequest{ + Skill: models.Skill{ + Name: "Neue Fertigkeit", + GameSystem: "midgard", + Beschreibung: "Test Fertigkeit", + Initialwert: 5, + BasisWert: 0, + Bonuseigenschaft: "In", + Improvable: true, + InnateSkill: false, + SourceID: source.ID, + PageNumber: 42, + }, + CategoryDifficulties: []CategoryDifficultyPair{ + { + CategoryID: category.ID, + DifficultyID: difficulty.ID, + }, + }, + } + + // Test creating new skill + skillID, err := CreateSkillWithCategories(req) + if err != nil { + t.Fatalf("CreateSkillWithCategories failed: %v", err) + } + + if skillID == 0 { + t.Fatalf("Expected non-zero skill ID, got 0") + } + + // Verify skill was created + var skill models.Skill + if err := database.DB.First(&skill, skillID).Error; err != nil { + t.Fatalf("Failed to retrieve created skill: %v", err) + } + + if skill.Name != "Neue Fertigkeit" { + t.Errorf("Expected name 'Neue Fertigkeit', got '%s'", skill.Name) + } + + if skill.Initialwert != 5 { + t.Errorf("Expected initialwert 5, got %d", skill.Initialwert) + } + + if skill.BasisWert != 0 { + t.Errorf("Expected basiswert 0, got %d", skill.BasisWert) + } + + // Verify category-difficulty relationship + var scd models.SkillCategoryDifficulty + if err := database.DB.Where("skill_id = ?", skillID).First(&scd).Error; err != nil { + t.Fatalf("Failed to retrieve skill category difficulty: %v", err) + } + + if scd.SkillCategoryID != category.ID { + t.Errorf("Expected category ID %d, got %d", category.ID, scd.SkillCategoryID) + } + + if scd.SkillDifficultyID != difficulty.ID { + t.Errorf("Expected difficulty ID %d, got %d", difficulty.ID, scd.SkillDifficultyID) + } +} + +func TestCreateSkillWithMultipleCategories(t *testing.T) { + setupTestEnvironment(t) + database.SetupTestDB() + + // Create test dependencies + source := getOrCreateSource("TSTMLT", "TestMultiple") + category1 := getOrCreateCategory("Körper", source.ID) + category2 := getOrCreateCategory("Geist", source.ID) + difficulty1 := getOrCreateDifficulty("leicht") + difficulty2 := getOrCreateDifficulty("schwer") + + // Prepare create request with multiple categories + req := SkillUpdateRequest{ + Skill: models.Skill{ + Name: "Multi-Kategorie Fertigkeit", + GameSystem: "midgard", + Initialwert: 10, + Improvable: true, + SourceID: source.ID, + }, + CategoryDifficulties: []CategoryDifficultyPair{ + { + CategoryID: category1.ID, + DifficultyID: difficulty1.ID, + }, + { + CategoryID: category2.ID, + DifficultyID: difficulty2.ID, + }, + }, + } + + // Test creating skill with multiple categories + skillID, err := CreateSkillWithCategories(req) + if err != nil { + t.Fatalf("CreateSkillWithCategories failed: %v", err) + } + + // Verify both category-difficulty relationships exist + var scds []models.SkillCategoryDifficulty + if err := database.DB.Where("skill_id = ?", skillID).Find(&scds).Error; err != nil { + t.Fatalf("Failed to retrieve skill category difficulties: %v", err) + } + + if len(scds) != 2 { + t.Fatalf("Expected 2 category-difficulty relationships, got %d", len(scds)) + } +} + +func TestCreateSkillValidation(t *testing.T) { + setupTestEnvironment(t) + database.SetupTestDB() + + // Test creating skill without name + req := SkillUpdateRequest{ + Skill: models.Skill{ + GameSystem: "midgard", + Initialwert: 5, + }, + CategoryDifficulties: []CategoryDifficultyPair{}, + } + + _, err := CreateSkillWithCategories(req) + if err == nil { + t.Error("Expected error when creating skill without name, got nil") + } +} diff --git a/backend/gsmaster/skill_enhanced_handlers.go b/backend/gsmaster/skill_enhanced_handlers.go index a26e109..a0a31fd 100644 --- a/backend/gsmaster/skill_enhanced_handlers.go +++ b/backend/gsmaster/skill_enhanced_handlers.go @@ -95,12 +95,77 @@ type CategoryDifficultyPair struct { LearnCost int `json:"learn_cost,omitempty"` } +// CreateSkillWithCategories creates a new skill with category-difficulty relationships +func CreateSkillWithCategories(req SkillUpdateRequest) (uint, error) { + // Validate required fields + if req.Skill.Name == "" { + return 0, fmt.Errorf("skill name is required") + } + + var skillID uint + + // Start transaction + err := database.DB.Transaction(func(tx *gorm.DB) error { + // Create skill + if err := tx.Create(&req.Skill).Error; err != nil { + return err + } + + skillID = req.Skill.ID + + // Create category-difficulty relationships + for _, cd := range req.CategoryDifficulties { + // Get category and difficulty names for denormalized fields + var category models.SkillCategory + if err := tx.First(&category, cd.CategoryID).Error; err != nil { + return fmt.Errorf("category not found: %w", err) + } + + var difficulty models.SkillDifficulty + if err := tx.First(&difficulty, cd.DifficultyID).Error; err != nil { + return fmt.Errorf("difficulty not found: %w", err) + } + + learnCost := cd.LearnCost + if learnCost == 0 { + // Use default based on difficulty + learnCost = getDefaultLearnCost(difficulty.Name) + } + + scd := models.SkillCategoryDifficulty{ + SkillID: skillID, + SkillCategoryID: cd.CategoryID, + SkillDifficultyID: cd.DifficultyID, + LearnCost: learnCost, + SCategory: category.Name, + SDifficulty: difficulty.Name, + } + + if err := tx.Create(&scd).Error; err != nil { + return err + } + } + + return nil + }) + + if err != nil { + return 0, err + } + + return skillID, nil +} + // UpdateSkillWithCategories updates a skill and its category-difficulty relationships func UpdateSkillWithCategories(skillID uint, req SkillUpdateRequest) error { // Start transaction return database.DB.Transaction(func(tx *gorm.DB) error { - // Update skill basic info - if err := tx.Model(&models.Skill{}).Where("id = ?", skillID).Updates(req.Skill).Error; err != nil { + // Update skill basic info - use Select to explicitly include boolean fields + // This ensures false values are also updated (GORM skips zero values by default in Updates) + if err := tx.Model(&models.Skill{}).Where("id = ?", skillID). + Select("name", "beschreibung", "game_system", "initialwert", "basis_wert", + "bonuseigenschaft", "improvable", "innate_skill", "source_id", "page_number"). + Updates(req.Skill).Error; err != nil { return err } @@ -240,3 +305,28 @@ func UpdateEnhancedMDSkill(c *gin.Context) { c.JSON(http.StatusOK, skill) } + +// CreateEnhancedMDSkill creates a new skill with categories +func CreateEnhancedMDSkill(c *gin.Context) { + var req SkillUpdateRequest + if err := c.ShouldBindJSON(&req); err != nil { + respondWithError(c, http.StatusBadRequest, "Invalid request: "+err.Error()) + return + } + + // Create the skill + skillID, err := CreateSkillWithCategories(req) + if err != nil { + respondWithError(c, http.StatusInternalServerError, "Failed to create skill: "+err.Error()) + return + } + + // Return created skill + skill, err := GetSkillWithCategories(skillID) + if err != nil { + respondWithError(c, http.StatusInternalServerError, "Failed to retrieve created skill") + return + } + + c.JSON(http.StatusCreated, skill) +} diff --git a/backend/gsmaster/skill_enhanced_handlers_test.go b/backend/gsmaster/skill_enhanced_handlers_test.go index 8358c5f..b380183 100644 --- a/backend/gsmaster/skill_enhanced_handlers_test.go +++ b/backend/gsmaster/skill_enhanced_handlers_test.go @@ -292,6 +292,73 @@ func TestUpdateSkillWithCategories(t *testing.T) { t.Error("Expected to find 'Alltag/normal' category after update") } } +func TestUpdateSkillBooleanFields(t *testing.T) { + setupTestEnvironment(t) + database.SetupTestDB() + + // Create test data with improvable=true and innateskill=false + source := getOrCreateSource("TSTBOOL", "TestBoolean") + skill := models.Skill{ + Name: "TestBooleanSkill", + GameSystem: "midgard", + Initialwert: 5, + Improvable: true, + InnateSkill: false, + SourceID: source.ID, + } + database.DB.Create(&skill) + + category := getOrCreateCategory("Alltag", source.ID) + difficulty := getOrCreateDifficulty("normal") + + scd := models.SkillCategoryDifficulty{ + SkillID: skill.ID, + SkillCategoryID: category.ID, + SkillDifficultyID: difficulty.ID, + LearnCost: 10, + SCategory: category.Name, + SDifficulty: difficulty.Name, + } + database.DB.Create(&scd) + + // Update to set improvable=false and innateskill=true + updateReq := SkillUpdateRequest{ + Skill: models.Skill{ + ID: skill.ID, + Name: "TestBooleanSkill", + GameSystem: "midgard", + Initialwert: 5, + Improvable: false, // Change to false + InnateSkill: true, // Change to true + SourceID: source.ID, + }, + CategoryDifficulties: []CategoryDifficultyPair{ + { + CategoryID: category.ID, + DifficultyID: difficulty.ID, + }, + }, + } + + err := UpdateSkillWithCategories(skill.ID, updateReq) + if err != nil { + t.Fatalf("UpdateSkillWithCategories failed: %v", err) + } + + // Verify boolean fields were updated correctly + var updatedSkill models.Skill + if err := database.DB.First(&updatedSkill, skill.ID).Error; err != nil { + t.Fatalf("Failed to retrieve updated skill: %v", err) + } + + if updatedSkill.Improvable != false { + t.Errorf("Expected improvable to be false, got %v", updatedSkill.Improvable) + } + + if updatedSkill.InnateSkill != true { + t.Errorf("Expected innateskill to be true, got %v", updatedSkill.InnateSkill) + } +} func TestGetDefaultLearnCost(t *testing.T) { tests := []struct { diff --git a/frontend/src/components/maintenance/SkillView.vue b/frontend/src/components/maintenance/SkillView.vue index 09e3386..2923101 100644 --- a/frontend/src/components/maintenance/SkillView.vue +++ b/frontend/src/components/maintenance/SkillView.vue @@ -7,6 +7,7 @@ v-model="searchTerm" :placeholder="`${$t('search')} ${$t('Skill')}...`" /> + @@ -80,6 +81,117 @@ + + + New + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+
+
+ +
+
+ +
+
+ {{ getCategoryName(catId) }}: + +
+
+
+
+ +
+ + +
+
+ + +