Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09d12d3d8c | |||
| 6ac04b2ae1 |
@@ -147,7 +147,17 @@ func UpdateCharacter(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, character)
|
||||
// Reload character to get updated data
|
||||
var updatedCharacter models.Char
|
||||
err = updatedCharacter.FirstID(id)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to reload character")
|
||||
return
|
||||
}
|
||||
|
||||
// Return as FeChar with categorized skills
|
||||
feChar := ToFeChar(&updatedCharacter)
|
||||
c.JSON(http.StatusOK, feChar)
|
||||
}
|
||||
func DeleteCharacter(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
@@ -47,6 +47,7 @@ func RegisterRoutes(r *gin.RouterGroup) {
|
||||
maintGrp.PUT("/spells/:id", UpdateMDSpell)
|
||||
maintGrp.PUT("/spells-enhanced/:id", UpdateEnhancedMDSpell) // New enhanced endpoint
|
||||
maintGrp.POST("/spells", AddSpell)
|
||||
maintGrp.POST("/spells-enhanced", CreateEnhancedMDSpell) // New enhanced endpoint
|
||||
maintGrp.DELETE("/spells/:id", DeleteMDSpell)
|
||||
|
||||
maintGrp.PUT("/equipment/:id", UpdateMDEquipment)
|
||||
|
||||
@@ -66,6 +66,15 @@ func UpdateSpellWithCategories(spellID uint, req SpellUpdateRequest) error {
|
||||
})
|
||||
}
|
||||
|
||||
// CreateSpellWithCategories creates a new spell
|
||||
func CreateSpellWithCategories(req SpellUpdateRequest) (*models.Spell, error) {
|
||||
spell := req.Spell
|
||||
if err := database.DB.Create(&spell).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &spell, nil
|
||||
}
|
||||
|
||||
// ===== Handler Functions =====
|
||||
|
||||
// GetEnhancedMDSpells returns spells with enhanced information
|
||||
@@ -87,11 +96,18 @@ func GetEnhancedMDSpells(c *gin.Context) {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve spell categories: "+err.Error())
|
||||
return
|
||||
}
|
||||
// Get spell Learn_categories
|
||||
learnCategories, err := spell.GetSpellLearnCategories()
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve spell learn categories: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"spells": spells,
|
||||
"sources": sources,
|
||||
"categories": categories,
|
||||
"spells": spells,
|
||||
"sources": sources,
|
||||
"categories": categories,
|
||||
"learnCategories": learnCategories,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -145,3 +161,27 @@ func UpdateEnhancedMDSpell(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, spell)
|
||||
}
|
||||
|
||||
// CreateEnhancedMDSpell creates a new spell
|
||||
func CreateEnhancedMDSpell(c *gin.Context) {
|
||||
var req SpellUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondWithError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
spell, err := CreateSpellWithCategories(req)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to create spell: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Return created spell with enhanced information
|
||||
spellWithCats, err := GetSpellWithCategories(spell.ID)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve created spell")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, spellWithCats)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package gsmaster
|
||||
|
||||
import (
|
||||
"bamort/database"
|
||||
"bamort/models"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUpdateSpellWithLearningCategory(t *testing.T) {
|
||||
// Set test environment
|
||||
setupTestEnvironment(t)
|
||||
|
||||
database.SetupTestDB(true)
|
||||
defer database.ResetTestDB()
|
||||
|
||||
// Create a test spell
|
||||
spell := models.Spell{
|
||||
Name: "Testzauber",
|
||||
Category: "Wunder",
|
||||
LearningCategory: "",
|
||||
Stufe: 1,
|
||||
GameSystem: "midgard",
|
||||
}
|
||||
err := database.DB.Create(&spell).Error
|
||||
require.NoError(t, err, "Failed to create test spell")
|
||||
|
||||
// Test 1: Update learning_category
|
||||
spell.LearningCategory = "Wundertat"
|
||||
updateReq := SpellUpdateRequest{Spell: spell}
|
||||
err = UpdateSpellWithCategories(spell.ID, updateReq)
|
||||
assert.NoError(t, err, "Failed to update spell with learning_category")
|
||||
|
||||
// Verify update
|
||||
var updated models.Spell
|
||||
err = database.DB.First(&updated, spell.ID).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Wundertat", updated.LearningCategory, "Learning category not updated correctly")
|
||||
assert.Equal(t, "Wunder", updated.Category, "Category should remain unchanged")
|
||||
|
||||
// Test 2: Update both categories
|
||||
updated.Category = "Verändern"
|
||||
updated.LearningCategory = "Zauberlied"
|
||||
updateReq2 := SpellUpdateRequest{Spell: updated}
|
||||
err = UpdateSpellWithCategories(updated.ID, updateReq2)
|
||||
assert.NoError(t, err, "Failed to update both categories")
|
||||
|
||||
// Verify both were updated
|
||||
var final models.Spell
|
||||
err = database.DB.First(&final, updated.ID).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Verändern", final.Category, "Category not updated")
|
||||
assert.Equal(t, "Zauberlied", final.LearningCategory, "Learning category not updated")
|
||||
}
|
||||
|
||||
func TestCreateSpellWithLearningCategory(t *testing.T) {
|
||||
setupTestEnvironment(t)
|
||||
|
||||
database.SetupTestDB(true)
|
||||
defer database.ResetTestDB()
|
||||
|
||||
// Test: Create spell with learning_category
|
||||
spell := models.Spell{
|
||||
Name: "Neuer Zauber",
|
||||
Category: "Beherrschen",
|
||||
LearningCategory: "Runenstab",
|
||||
Stufe: 2,
|
||||
GameSystem: "midgard",
|
||||
AP: "3",
|
||||
}
|
||||
|
||||
err := database.DB.Create(&spell).Error
|
||||
require.NoError(t, err, "Failed to create spell with learning_category")
|
||||
|
||||
// Verify creation
|
||||
var created models.Spell
|
||||
err = database.DB.Where("name = ?", "Neuer Zauber").First(&created).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Beherrschen", created.Category)
|
||||
assert.Equal(t, "Runenstab", created.LearningCategory)
|
||||
assert.Equal(t, 2, created.Stufe)
|
||||
}
|
||||
|
||||
func TestLearningCategoryDefaultEmpty(t *testing.T) {
|
||||
setupTestEnvironment(t)
|
||||
|
||||
database.SetupTestDB(true)
|
||||
defer database.ResetTestDB()
|
||||
|
||||
// Test: Create spell without learning_category
|
||||
spell := models.Spell{
|
||||
Name: "Zauber ohne LearningCategory",
|
||||
Category: "Normal",
|
||||
Stufe: 1,
|
||||
GameSystem: "midgard",
|
||||
}
|
||||
|
||||
err := database.DB.Create(&spell).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify learning_category is empty (not nil)
|
||||
var created models.Spell
|
||||
err = database.DB.First(&created, spell.ID).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "", created.LearningCategory, "Learning category should be empty string by default")
|
||||
assert.Equal(t, "Normal", created.Category)
|
||||
}
|
||||
|
||||
func TestCreateEnhancedMDSpellEndpoint(t *testing.T) {
|
||||
setupTestEnvironment(t)
|
||||
database.SetupTestDB(true)
|
||||
defer database.ResetTestDB()
|
||||
|
||||
// Test: Create spell with learning_category via endpoint
|
||||
spell := models.Spell{
|
||||
Name: "Test Endpoint Zauber",
|
||||
Category: "Erkennen",
|
||||
LearningCategory: "Erkenntnismagie",
|
||||
Stufe: 3,
|
||||
GameSystem: "midgard",
|
||||
AP: "2",
|
||||
}
|
||||
|
||||
req := SpellUpdateRequest{Spell: spell}
|
||||
created, err := CreateSpellWithCategories(req)
|
||||
require.NoError(t, err, "Failed to create spell")
|
||||
assert.NotZero(t, created.ID, "Created spell should have an ID")
|
||||
assert.Equal(t, "Erkennen", created.Category)
|
||||
assert.Equal(t, "Erkenntnismagie", created.LearningCategory)
|
||||
assert.Equal(t, 3, created.Stufe)
|
||||
}
|
||||
|
||||
func TestGetSpellLearningCategories(t *testing.T) {
|
||||
setupTestEnvironment(t)
|
||||
database.SetupTestDB(true)
|
||||
defer database.ResetTestDB()
|
||||
var spell models.Spell
|
||||
|
||||
learningCategories, err := spell.GetSpellLearnCategories()
|
||||
require.NoError(t, err, "Failed to get spell learning categories")
|
||||
assert.Contains(t, learningCategories, "Spruch", "Learning categories should include 'Spruch'")
|
||||
assert.Contains(t, learningCategories, "Runenstab", "Learning categories should include 'Runenstab'")
|
||||
assert.Contains(t, learningCategories, "Zauberlied", "Learning categories should include 'Zauberlied'")
|
||||
assert.Contains(t, learningCategories, "Wundertat", "Learning categories should include 'Wundertat'")
|
||||
assert.Contains(t, learningCategories, "Dweomer", "Learning categories should include 'Dweomer'")
|
||||
assert.Contains(t, learningCategories, "Thaumatherapie", "Learning categories should include 'Thaumatherapie'")
|
||||
assert.Contains(t, learningCategories, "Zaubersalz", "Learning categories should include 'Zaubersalz'")
|
||||
assert.Contains(t, learningCategories, "Rune", "Learning categories should include 'Rune'")
|
||||
assert.Contains(t, learningCategories, "Siegel", "Learning categories should include 'Siegel'")
|
||||
|
||||
}
|
||||
@@ -438,6 +438,22 @@ func (object *Spell) GetSpellCategories() ([]string, error) {
|
||||
return categories, nil
|
||||
}
|
||||
|
||||
func (object *Spell) GetSpellLearnCategories() ([]string, error) {
|
||||
var categories []string
|
||||
gs := GetGameSystem(object.GameSystemId, object.GameSystem)
|
||||
|
||||
result := database.DB.Model(&Spell{}).
|
||||
Where("game_system = ? OR game_system_id = ?", gs.Name, gs.ID).
|
||||
Distinct().
|
||||
Pluck("learning_category", &categories)
|
||||
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return categories, nil
|
||||
}
|
||||
|
||||
func (object *Spell) ensureGameSystem() {
|
||||
gs := GetGameSystem(object.GameSystemId, object.GameSystem)
|
||||
object.GameSystemId = gs.ID
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<!-- Submenu Content -->
|
||||
<!-- <div class="character-aspect"> -->
|
||||
<component :is="currentView" :character="character" :isOwner="isOwner" @character-updated="refreshCharacter"/>
|
||||
<component :is="currentView" :character="character" :isOwner="isOwner" @character-updated="refreshCharacter" @character-data-updated="updateCharacterData"/>
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- Submenu -->
|
||||
@@ -156,6 +156,12 @@ export default {
|
||||
alert('Fehler beim Aktualisieren der Charakterdaten: ' + (error.response?.data?.error || error.message));
|
||||
}
|
||||
},
|
||||
|
||||
updateCharacterData(updatedData) {
|
||||
// Update character data directly without reloading from server
|
||||
this.character = updatedData;
|
||||
console.log('Character data updated directly from response');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -55,6 +55,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Warning message when editing skill name -->
|
||||
<div v-if="showNameEditWarning" class="warning-message">
|
||||
<span class="warning-icon">⚠️</span>
|
||||
<span class="warning-text">
|
||||
{{ $t('characters.datasheet.editnamewarning') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -384,6 +393,29 @@
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.warning-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
margin: 10px 0;
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 6px;
|
||||
color: #856404;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -431,6 +463,7 @@ export default {
|
||||
editingSkillId: null,
|
||||
editingField: null,
|
||||
editValue: '',
|
||||
showNameEditWarning: false,
|
||||
|
||||
isLoading: false
|
||||
};
|
||||
@@ -752,6 +785,11 @@ export default {
|
||||
this.editingField = field;
|
||||
this.editValue = skill[field] || '';
|
||||
|
||||
// Show warning when editing skill name
|
||||
if (field === 'name') {
|
||||
this.showNameEditWarning = true;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.editInput;
|
||||
if (input) {
|
||||
@@ -779,19 +817,50 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// Update local character object
|
||||
skill[field] = newValue;
|
||||
// Find and update the original skill in character.fertigkeiten or character.waffenfertigkeiten
|
||||
let originalSkillFound = false;
|
||||
|
||||
// Check in fertigkeiten
|
||||
if (this.character.fertigkeiten) {
|
||||
const originalSkill = this.character.fertigkeiten.find(s => s.name === skill.name);
|
||||
if (originalSkill) {
|
||||
originalSkill[field] = newValue;
|
||||
originalSkillFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check in waffenfertigkeiten if not found in fertigkeiten
|
||||
if (!originalSkillFound && this.character.waffenfertigkeiten) {
|
||||
const originalWeaponSkill = this.character.waffenfertigkeiten.find(s => s.name === skill.name);
|
||||
if (originalWeaponSkill) {
|
||||
originalWeaponSkill[field] = newValue;
|
||||
originalSkillFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!originalSkillFound) {
|
||||
console.warn('Original skill not found in character data:', skill.name);
|
||||
alert('Warnung: Originalfertigkeit nicht gefunden.');
|
||||
this.cancelEditSkill();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Save to backend
|
||||
await API.put(`/api/characters/${this.character.id}`, this.character);
|
||||
const response = await API.put(`/api/characters/${this.character.id}`, this.character);
|
||||
|
||||
console.log('Skill updated successfully:', skill.name, field, newValue);
|
||||
|
||||
// Update the parent component with the response data
|
||||
// The backend now returns the full FeChar with categorizedskills
|
||||
this.$emit('character-data-updated', response.data);
|
||||
|
||||
this.$emit('character-updated');
|
||||
this.cancelEditSkill();
|
||||
} catch (error) {
|
||||
console.error('Failed to update skill:', error);
|
||||
alert('Fehler beim Speichern: ' + (error.response?.data?.error || error.message));
|
||||
this.cancelEditSkill();
|
||||
// Revert changes on error by reloading
|
||||
this.$emit('character-updated');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -799,6 +868,7 @@ export default {
|
||||
this.editingSkillId = null;
|
||||
this.editingField = null;
|
||||
this.editValue = '';
|
||||
this.showNameEditWarning = false;
|
||||
},
|
||||
|
||||
isEditingSkill(skill, field) {
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
{{ $t('spell.category') }}
|
||||
<button @click="sortBy('category')">{{ sortField === 'category' ? (sortAsc ? '↑' : '↓') : '-' }}</button>
|
||||
</th>
|
||||
<th class="cd-table-header">{{ $t('spell.learning_category') || 'Learning Category' }}</th>
|
||||
<th class="cd-table-header">
|
||||
{{ $t('spell.name') }}
|
||||
<button @click="sortBy('name')">{{ sortField === 'name' ? (sortAsc ? '↑' : '↓') : '-' }}</button>
|
||||
@@ -150,7 +151,7 @@
|
||||
<tbody>
|
||||
<tr v-if="creatingNew">
|
||||
<td>New</td>
|
||||
<td colspan="14">
|
||||
<td colspan="15">
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<div class="edit-field">
|
||||
@@ -165,6 +166,15 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('spell.learning_category') || 'Learning Category' }}:</label>
|
||||
<select v-model="newItem.learning_category" style="width:150px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="category in availableLearnCategories" :key="category" :value="category">
|
||||
{{ category }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('spell.level') }}:</label>
|
||||
<input v-model.number="newItem.level" type="number" style="width:60px;" />
|
||||
@@ -248,6 +258,7 @@
|
||||
<tr v-if="editingIndex !== index">
|
||||
<td>{{ dtaItem.id || '' }}</td>
|
||||
<td>{{ dtaItem.category|| '-' }}</td>
|
||||
<td>{{ dtaItem.learning_category || '-' }}</td>
|
||||
<td>{{ dtaItem.name || '-' }}</td>
|
||||
<td>{{ dtaItem.level || '0' }}</td>
|
||||
<td>{{ dtaItem.ap || '0' }}</td>
|
||||
@@ -267,7 +278,7 @@
|
||||
<!-- Edit Mode -->
|
||||
<tr v-else>
|
||||
<td><input v-model="editedItem.id" style="width:20px;" disabled /></td>
|
||||
<td colspan="14">
|
||||
<td colspan="15">
|
||||
<!-- Expanded edit form -->
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
@@ -283,6 +294,15 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('spell.learning_category') || 'Learning Category' }}:</label>
|
||||
<select v-model="editedItem.learning_category" style="width:150px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="category in availableLearnCategories" :key="category" :value="category">
|
||||
{{ category }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('spell.level') }}:</label>
|
||||
<input v-model.number="editedItem.level" type="number" style="width:60px;" />
|
||||
@@ -519,6 +539,7 @@ export default {
|
||||
filterQuelle: '',
|
||||
enhancedSpells: [],
|
||||
availableSources: [],
|
||||
availableLearnCategories: [],
|
||||
gameSystems: [],
|
||||
selectedSystemId: null,
|
||||
creatingNew: false,
|
||||
@@ -663,6 +684,7 @@ export default {
|
||||
const response = await API.get('/api/maintenance/spells-enhanced')
|
||||
this.enhancedSpells = response.data.spells || []
|
||||
this.availableSources = response.data.sources || []
|
||||
this.availableLearnCategories = response.data.learnCategories || []
|
||||
// Also update mdata for compatibility
|
||||
if (response.data.categories) {
|
||||
this.mdata.spellcategories = response.data.categories
|
||||
@@ -725,6 +747,7 @@ export default {
|
||||
this.newItem = {
|
||||
name: '',
|
||||
category: this.mdata.spellcategories?.[0] || '',
|
||||
learning_category: '',
|
||||
level: 0,
|
||||
ap: '',
|
||||
zauberdauer: '',
|
||||
|
||||
@@ -177,6 +177,7 @@ export default {
|
||||
spell:{
|
||||
id:'ID',
|
||||
category:'Kategorie',
|
||||
learning_category:'Lernkategorie',
|
||||
name:'Name',
|
||||
description:'Beschreibung',
|
||||
level:'Stufe',
|
||||
@@ -563,7 +564,8 @@ export default {
|
||||
bRollTooltip: 'B würfeln: 1d6 + Modifikator je nach Rasse'
|
||||
},
|
||||
datasheet: {
|
||||
editHelp: 'Doppelklicken auf ein Feld, um es zu bearbeiten.'
|
||||
editHelp: 'Doppelklicken auf ein Feld, um es zu bearbeiten.',
|
||||
editnamewarning: 'Wenn der Name der Fertigkeit geändert wird kann diese nicht mehr nach den Regeln verbessert werden. Auch beim Export können Boni und andere Werte fehlen!'
|
||||
}
|
||||
},
|
||||
audit: {
|
||||
|
||||
@@ -173,6 +173,7 @@ export default {
|
||||
spell:{
|
||||
id:'ID',
|
||||
category:'Category',
|
||||
learning_category:'Learning Category',
|
||||
name:'Name',
|
||||
description:'Description',
|
||||
level:'Level',
|
||||
@@ -559,7 +560,8 @@ export default {
|
||||
bRollTooltip: 'Roll B: 1d6 + modifier based on race'
|
||||
},
|
||||
datasheet: {
|
||||
editHelp: 'Click twice on a field to edit it.'
|
||||
editHelp: 'Click twice on a field to edit it.',
|
||||
editnamewarning: 'If the name of the skill is changed, it can no longer be improved according to the rules. Also, bonuses and other values may be missing when exporting!'
|
||||
}
|
||||
},
|
||||
audit: {
|
||||
|
||||
Reference in New Issue
Block a user