diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 9b05e8f..1b16a18 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -2462,12 +2462,82 @@ class FalukantService extends BaseService { try { const threeWeeksAgo = new Date(Date.now() - 21 * 24 * 60 * 60 * 1000); const proposalCount = Math.floor(Math.random() * 3) + 3; - for (let i = 0; i < proposalCount; i++) { - const directorCharacter = await FalukantCharacter.findOne({ - where: { - regionId, - createdAt: { [Op.lt]: threeWeeksAgo }, + + // Hole bereits existierende Proposals, um diese Charaktere auszuschließen + const existingProposals = await DirectorProposal.findAll({ + where: { employerUserId: falukantUserId }, + attributes: ['directorCharacterId'], + raw: true + }); + const proposalCharacterIds = existingProposals.map(p => p.directorCharacterId); + + // Hole alle Charaktere, die bereits als Direktor arbeiten (egal für welchen User) + const existingDirectors = await Director.findAll({ + attributes: ['directorCharacterId'], + raw: true + }); + const directorCharacterIds = existingDirectors.map(d => d.directorCharacterId); + + // Kombiniere beide Listen + const excludedCharacterIds = [...new Set([...proposalCharacterIds, ...directorCharacterIds])]; + + console.log(`[generateProposals] Excluding ${excludedCharacterIds.length} characters (${proposalCharacterIds.length} proposals + ${directorCharacterIds.length} active directors)`); + console.log(`[generateProposals] Region ID: ${regionId}, Proposal count needed: ${proposalCount}`); + + // Versuche zuerst Charaktere, die mindestens 3 Wochen alt sind + let whereClause = { + regionId, + userId: null, // Nur NPCs + }; + + if (excludedCharacterIds.length > 0) { + whereClause.id = { [Op.notIn]: excludedCharacterIds }; + } + whereClause.createdAt = { [Op.lt]: threeWeeksAgo }; + + // Erstelle Query-Objekt für Logging + const queryOptions = { + where: whereClause, + include: [ + { + model: TitleOfNobility, + as: 'nobleTitle', + attributes: ['level'], }, + ], + order: sequelize.literal('RANDOM()'), + limit: proposalCount, + }; + + // Logge die SQL-Query + try { + const query = FalukantCharacter.findAll(queryOptions); + const sqlQuery = query.toSQL ? query.toSQL() : query; + console.log(`[generateProposals] SQL Query (older than 3 weeks):`, JSON.stringify(sqlQuery, null, 2)); + } catch (e) { + // Fallback: Logge die Query-Optionen direkt + console.log(`[generateProposals] Query Options (older than 3 weeks):`, JSON.stringify(queryOptions, null, 2)); + } + console.log(`[generateProposals] WHERE clause:`, JSON.stringify(whereClause, null, 2)); + console.log(`[generateProposals] Excluded character IDs:`, excludedCharacterIds); + + let directorCharacters = await FalukantCharacter.findAll(queryOptions); + + // Fallback: Wenn nicht genug ältere Charaktere gefunden werden, verwende auch neuere + if (directorCharacters.length < proposalCount) { + console.log(`[generateProposals] Only found ${directorCharacters.length} characters older than 3 weeks, trying all NPCs...`); + + const fallbackWhereClause = { + regionId, + userId: null, // Nur NPCs + }; + + if (excludedCharacterIds.length > 0) { + fallbackWhereClause.id = { [Op.notIn]: excludedCharacterIds }; + } + + const fallbackQueryOptions = { + where: fallbackWhereClause, include: [ { model: TitleOfNobility, @@ -2476,22 +2546,81 @@ class FalukantService extends BaseService { }, ], order: sequelize.literal('RANDOM()'), - }); - if (!directorCharacter) { - throw new Error('No directors available for the region'); + limit: proposalCount, + }; + + // Logge die Fallback-SQL-Query + try { + const fallbackQuery = FalukantCharacter.findAll(fallbackQueryOptions); + const fallbackSqlQuery = fallbackQuery.toSQL ? fallbackQuery.toSQL() : fallbackQuery; + console.log(`[generateProposals] SQL Query (all NPCs):`, JSON.stringify(fallbackSqlQuery, null, 2)); + } catch (e) { + console.log(`[generateProposals] Fallback Query Options:`, JSON.stringify(fallbackQueryOptions, null, 2)); } - const avgKnowledge = await this.calculateAverageKnowledge(directorCharacter.id); + console.log(`[generateProposals] Fallback WHERE clause:`, JSON.stringify(fallbackWhereClause, null, 2)); + + const fallbackCharacters = await FalukantCharacter.findAll(fallbackQueryOptions); + + // Kombiniere beide Listen und entferne Duplikate + const allCharacterIds = new Set(directorCharacters.map(c => c.id)); + fallbackCharacters.forEach(c => { + if (!allCharacterIds.has(c.id)) { + directorCharacters.push(c); + allCharacterIds.add(c.id); + } + }); + + // Limitiere auf proposalCount + directorCharacters = directorCharacters.slice(0, proposalCount); + } + + if (directorCharacters.length === 0) { + console.error(`[generateProposals] No NPCs found in region ${regionId} at all`); + throw new Error('No directors available for the region'); + } + + console.log(`[generateProposals] Found ${directorCharacters.length} available NPCs`); + + // Batch-Berechnung der Knowledge-Werte + const characterIds = directorCharacters.map(c => c.id); + const allKnowledges = await Knowledge.findAll({ + where: { characterId: { [Op.in]: characterIds } }, + attributes: ['characterId', 'knowledge'], + raw: true + }); + + // Gruppiere Knowledge nach characterId und berechne Durchschnitt + const knowledgeMap = new Map(); + characterIds.forEach(id => knowledgeMap.set(id, [])); + allKnowledges.forEach(k => { + const list = knowledgeMap.get(k.characterId) || []; + list.push(k.knowledge); + knowledgeMap.set(k.characterId, list); + }); + + // Erstelle alle Proposals in einem Batch + const proposalsToCreate = directorCharacters.map(character => { + const knowledges = knowledgeMap.get(character.id) || []; + const avgKnowledge = knowledges.length > 0 + ? knowledges.reduce((sum, k) => sum + k, 0) / knowledges.length + : 0; + const proposedIncome = Math.round( - directorCharacter.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5) + character.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5) ); - await DirectorProposal.create({ - directorCharacterId: directorCharacter.id, + + return { + directorCharacterId: character.id, employerUserId: falukantUserId, proposedIncome, - }); - } + }; + }); + + await DirectorProposal.bulkCreate(proposalsToCreate); + + console.log(`[generateProposals] Created ${proposalsToCreate.length} director proposals for region ${regionId}`); } catch (error) { - console.log(error.message, error.stack); + console.error('[generateProposals] Error:', error.message, error.stack); throw new Error(error.message); } }