diff --git a/backend/controllers/eventController.js b/backend/controllers/eventController.js index 45ea92f..e0a4495 100644 --- a/backend/controllers/eventController.js +++ b/backend/controllers/eventController.js @@ -1020,8 +1020,27 @@ exports.updateEventScore = async (req, res) => { totalScore: score + handicap }); - // 캐시 무효화 추가 - await averageService.invalidateMemberAverageCache(scoreRecord.participantId); + // 캐시 무효화: participantId -> memberId로 변환 후 무효화 및 워밍업 + try { + const ep = await EventParticipant.findOne({ + where: { id: scoreRecord.participantId }, + attributes: ['memberId'], + }); + if (ep && ep.memberId) { + await averageService.invalidateMemberAverageCache(ep.memberId); + // clubId 조회 후 워밍업 + try { + const ev = await Event.findOne({ where: { id: eventId }, attributes: ['clubId'] }); + if (ev && ev.clubId) { + await averageService.calculateMemberAverageByClubSetting(ep.memberId, ev.clubId); + } + } catch (e2) { + console.error('에버리지 워밍업 실패(updateEventScore):', e2); + } + } + } catch (e) { + console.error('에버리지 캐시 무효화 실패(updateEventScore):', e); + } // 업데이트된 레코드 조회 const updatedScore = await EventScore.findOne({ @@ -1093,14 +1112,23 @@ exports.deleteEventScore = async (req, res) => { await score.destroy(); - // 점수 삭제 시 해당 회원의 에버리지 캡시 무효화 - if (score.participant && score.participant.Member) { - try { - await averageService.invalidateMemberAverageCache(score.participant.Member.id); - console.log(`회원 ${score.participant.Member.id}의 에버리지 캡시 무효화 완료`); - } catch (error) { - console.error(`회원 ${score.participant.Member.id}의 에버리지 캡시 무효화 오류:`, error); + // 점수 삭제 시 해당 회원의 에버리지 캐시 무효화 및 워밍업 + try { + const memberId = score.EventParticipant?.Member?.id; + if (memberId) { + await averageService.invalidateMemberAverageCache(memberId); + console.log(`회원 ${memberId}의 에버리지 캐시 무효화 완료`); + // clubId는 상단 검증 단계의 req.body에서 확보됨 + if (clubId) { + try { + await averageService.calculateMemberAverageByClubSetting(memberId, clubId); + } catch (e2) { + console.error('에버리지 워밍업 실패(deleteEventScore):', e2); + } + } } + } catch (error) { + console.error('에버리지 캐시 무효화 오류(deleteEventScore):', error); } res.json({ @@ -1131,6 +1159,12 @@ exports.deleteParticipantScores = async (req, res) => { }); } + // 삭제 전 memberId 조회 (캐시 무효화용) + const ep = await EventParticipant.findOne({ + where: { id: participantId }, + attributes: ['memberId'] + }); + // 점수 삭제 await EventScore.destroy({ where: { @@ -1139,6 +1173,23 @@ exports.deleteParticipantScores = async (req, res) => { } }); + // 캐시 무효화: 해당 참가자의 회원 평균 캐시 제거 및 워밍업 + try { + if (ep && ep.memberId) { + await averageService.invalidateMemberAverageCache(ep.memberId); + try { + const ev = await Event.findOne({ where: { id: eventId }, attributes: ['clubId'] }); + if (ev && ev.clubId) { + await averageService.calculateMemberAverageByClubSetting(ep.memberId, ev.clubId); + } + } catch (e2) { + console.error('에버리지 워밍업 오류(deleteParticipantScores):', e2); + } + } + } catch (e) { + console.error('에버리지 캐시 무효화 오류(deleteParticipantScores):', e); + } + res.json({ message: '점수가 삭제되었습니다.' }); diff --git a/backend/controllers/fileUploadController.js b/backend/controllers/fileUploadController.js index af4a151..38484d4 100644 --- a/backend/controllers/fileUploadController.js +++ b/backend/controllers/fileUploadController.js @@ -718,6 +718,7 @@ exports.saveEventFileData = async (req, res) => { } // 2. 참가자 및 점수 처리 + const affectedMemberIds = new Set(); for (const participant of participants) { let eventParticipant; @@ -754,15 +755,39 @@ exports.saveEventFileData = async (req, res) => { comment: '' }, { transaction: t }); } + affectedMemberIds.add(Number(participant.memberId)); } else { // 새 회원 생성 및 참가자 등록 + // 클럽 여성 기본 핸디캡 기준으로 성별/핸디캡 보정 + const femaleBase = Number(clubInstance?.femaleHandicap ?? 0) || 0; + let parsedHandicap = null; + if (participant.handicap !== undefined && participant.handicap !== null) { + const ph = Number(participant.handicap); + if (Number.isFinite(ph)) parsedHandicap = ph; + } + // 점수 객체에 handicap이 개별 제공되는 경우 최댓값을 사용 + if (parsedHandicap == null && Array.isArray(participant.scores)) { + for (const s of participant.scores) { + if (s && typeof s === 'object' && s.handicap !== undefined) { + const sh = Number(s.handicap); + if (Number.isFinite(sh)) { + parsedHandicap = parsedHandicap == null ? sh : Math.max(parsedHandicap, sh); + } + } + } + } + const isFemaleByRule = (parsedHandicap != null && parsedHandicap >= femaleBase) || + (Array.isArray(participant.scores) && participant.scores.some((s) => Number(s?.handicap) >= femaleBase)); + const genderNormalized = (participant.gender === 'female' || participant.gender === '여성') ? 'female' : 'male'; + const finalGender = isFemaleByRule ? 'female' : genderNormalized; + const newMember = await Member.create({ clubId: effectiveClubId, // required by Member model name: participant.name, - // normalize enum values to match Member model - gender: (participant.gender === 'female' || participant.gender === '여성') ? 'female' : 'male', + gender: finalGender, memberType: 'guest', - status: 'active' + status: 'active', + handicap: parsedHandicap ?? 0 }, { transaction: t }); // 클럽 회원으로 등록 (조인 테이블을 association으로 처리) @@ -788,6 +813,7 @@ exports.saveEventFileData = async (req, res) => { teamId: teamId, comment: '' }, { transaction: t }); + affectedMemberIds.add(Number(newMember.id)); } // 팀에 속한 참가자인 경우 팀 멤버로 등록 @@ -856,21 +882,33 @@ exports.saveEventFileData = async (req, res) => { } } } - - return event; + + // 트랜잭션 내부에서는 목록만 반환 (커밋 후 워밍업) + return { event, affectedMemberIds: Array.from(affectedMemberIds), effectiveClubId }; }); - + // 커밋 이후 평균 캐시 무효화 및 워밍업 (신규 점수가 커밋된 뒤 계산되도록) + if (result && Array.isArray(result.affectedMemberIds)) { + for (const mid of result.affectedMemberIds) { + try { + await averageService.invalidateMemberAverageCache(mid); + await averageService.calculateMemberAverageByClubSetting(mid, result.effectiveClubId); + } catch (e) { + console.error('평균 캐시 갱신 실패:', e); + } + } + } + res.json({ message: '이벤트 데이터가 성공적으로 저장되었습니다.', event: { - id: result.id, - title: result.title, - eventType: result.eventType, - startDate: result.startDate + id: result.event.id, + title: result.event.title, + eventType: result.event.eventType, + startDate: result.event.startDate }, rollbackToken: { - eventId: result.id, - clubId: Number(result.clubId) + eventId: result.event.id, + clubId: Number(result.event.clubId) } }); } catch (error) {