삭제처리 수정

This commit is contained in:
2025-10-23 00:50:52 +09:00
parent 4a3a24e327
commit 76f5b572e8
3 changed files with 153 additions and 11 deletions
+50 -5
View File
@@ -687,7 +687,7 @@ exports.updateEvent = async (req, res) => {
}
};
// 이벤트 삭제 (소프트 삭제)
// 이벤트 삭제 (소프트 삭제: 숨김 처리)
exports.deleteEvent = async (req, res) => {
try {
const eventId = req.params.id;
@@ -716,9 +716,9 @@ exports.deleteEvent = async (req, res) => {
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
}
// 참가자 정보도 함께 삭제 처리
// 참가자 소프트 삭제(취소 상태로 표시)
await EventParticipant.update(
{ status: '취소' },
{ status: 'canceled' },
{
where: {
eventId,
@@ -727,10 +727,11 @@ exports.deleteEvent = async (req, res) => {
}
);
await event.update({ status: '삭제' });
// 이벤트 숨김 처리(표준 상태값 사용)
await event.update({ status: 'deleted' });
res.json({
message: '이벤트가 삭제되었습니다.',
message: '이벤트가 숨김(소프트 삭제)되었습니다.',
event: {
id: event.id,
title: event.title,
@@ -744,6 +745,50 @@ exports.deleteEvent = async (req, res) => {
}
};
// 이벤트 완전 삭제 (하드 삭제): 팀배정, 점수, 참가자, 팀, 이벤트 순서로 물리 삭제
exports.deleteEventPermanently = async (req, res) => {
try {
const eventId = Number(req.params.id);
const { clubId } = req.body || {};
const cid = Number(clubId);
if (!eventId || !cid) {
return res.status(400).json({ message: '이벤트 ID와 클럽 ID가 필요합니다.' });
}
await sequelize.transaction(async (t) => {
const event = await Event.findOne({ where: { id: eventId, clubId: cid }, transaction: t });
if (!event) {
throw new Error('이벤트를 찾을 수 없습니다.');
}
// 팀멤버 삭제
const teams = await EventTeam.findAll({ where: { eventId }, transaction: t });
const teamIds = teams.map((tm) => tm.id);
if (teamIds.length > 0) {
await EventTeamMember.destroy({ where: { eventTeamId: teamIds }, transaction: t });
}
// 점수 삭제
await EventScore.destroy({ where: { eventId }, transaction: t });
// 참가자 삭제
await EventParticipant.destroy({ where: { eventId }, transaction: t });
// 팀 삭제
await EventTeam.destroy({ where: { eventId }, transaction: t });
// 이벤트 삭제
await Event.destroy({ where: { id: eventId, clubId: cid }, transaction: t });
});
res.json({ message: '이벤트 및 관련 데이터가 완전히 삭제되었습니다.' });
} catch (error) {
console.error('이벤트 완전 삭제 오류:', error);
res.status(500).json({ message: '이벤트 완전 삭제 중 오류가 발생했습니다.' });
}
};
// 이벤트 점수 조회
exports.getEventScores = async (req, res) => {
try {
+99 -6
View File
@@ -50,6 +50,79 @@ const fileFilter = (req, file, cb) => {
}
};
// 업로드 저장 롤백: 이벤트 및 관련 데이터 삭제 (옵션: 게스트 정리)
exports.rollbackEventImport = async (req, res) => {
try {
const { eventId, clubId, includeGuests } = req.body || {};
const eid = Number(eventId);
const cid = Number(clubId);
if (!eid || !cid) {
return res.status(400).json({ message: '유효한 eventId, clubId가 필요합니다.' });
}
await sequelize.transaction(async (t) => {
// 이벤트 검증 (동일 클럽)
const event = await Event.findOne({ where: { id: eid, clubId: cid }, transaction: t });
if (!event) {
throw new Error('대상 이벤트를 찾을 수 없습니다.');
}
// 관련 팀멤버 삭제
const teams = await EventTeam.findAll({ where: { eventId: eid }, transaction: t });
const teamIds = teams.map((tm) => tm.id);
if (teamIds.length > 0) {
await EventTeamMember.destroy({ where: { eventTeamId: teamIds }, transaction: t });
}
// 점수 삭제
await EventScore.destroy({ where: { eventId: eid }, transaction: t });
// 참가자 삭제 전, 게스트 후보 수집
let guestMemberIds = [];
if (includeGuests) {
const participants = await EventParticipant.findAll({ where: { eventId: eid }, transaction: t });
const memberIds = participants.map((p) => p.memberId).filter(Boolean);
if (memberIds.length > 0) {
const members = await Member.findAll({ where: { id: memberIds, clubId: cid, memberType: 'guest' }, transaction: t });
guestMemberIds = members.map((m) => m.id);
}
}
// 참가자 삭제
await EventParticipant.destroy({ where: { eventId: eid }, transaction: t });
// 팀 삭제
await EventTeam.destroy({ where: { eventId: eid }, transaction: t });
// 이벤트 삭제
await Event.destroy({ where: { id: eid, clubId: cid }, transaction: t });
// 게스트 정리: 다른 참가 기록이 없고, 동일 클럽의 조인만 있는 경우 제거
if (includeGuests && guestMemberIds.length > 0) {
// 다른 참가 기록이 없는 멤버만 필터
const others = await EventParticipant.findAll({ where: { memberId: guestMemberIds }, transaction: t });
const stillUsed = new Set(others.map((p) => p.memberId));
const deletable = guestMemberIds.filter((id) => !stillUsed.has(id));
if (deletable.length > 0) {
// 클럽 조인 제거
const club = await Club.findOne({ where: { id: cid }, transaction: t });
if (club) {
// removeMember 지원 여부에 따라 destroy 직접 수행
await sequelize.models.ClubMembers?.destroy?.({ where: { clubId: cid, memberId: deletable }, transaction: t }).catch(() => {});
}
// 멤버 삭제
await Member.destroy({ where: { id: deletable, clubId: cid, memberType: 'guest' }, transaction: t });
}
}
});
res.json({ message: '롤백이 완료되었습니다.' });
} catch (error) {
console.error('롤백 오류:', error);
res.status(500).json({ message: `롤백 중 오류가 발생했습니다: ${error.message}` });
}
};
const upload = multer({
storage: storage,
fileFilter: fileFilter,
@@ -531,6 +604,12 @@ exports.saveEventFileData = async (req, res) => {
try {
// 트랜잭션 시작
const result = await sequelize.transaction(async (t) => {
// 프런트에서 전달할 수 있는 핸디 포함 여부 플래그 (기본: true)
const scoresIncludeHandicap = (
(typeof req.body.scoresIncludeHandicap === 'boolean') ? req.body.scoresIncludeHandicap
: (eventData && typeof eventData.scoresIncludeHandicap === 'boolean') ? eventData.scoresIncludeHandicap
: true
);
// 이벤트 타입/상태 정규화 (모델 enum 호환)
const normalizeEventType = (v) => {
if (!v) return 'regular';
@@ -569,13 +648,14 @@ exports.saveEventFileData = async (req, res) => {
await event.update({
title: eventData.title,
description: eventData.description || '',
eventType: normalizeEventType(eventData.eventType),
// 비즈니스 규칙: 이벤트 유형은 정기전, 상태는 완료로 고정
eventType: 'regular',
startDate: new Date(eventData.startDate),
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
location: eventData.location || '',
gameCount: eventData.gameCount || 3,
maxParticipants: eventData.maxParticipants || 0,
status: normalizeStatus(eventData.status)
status: 'completed'
}, { transaction: t });
} else {
// 새 이벤트 생성
@@ -583,13 +663,15 @@ exports.saveEventFileData = async (req, res) => {
clubId: cid,
title: eventData.title,
description: eventData.description || '',
eventType: normalizeEventType(eventData.eventType),
// 비즈니스 규칙: 이벤트 유형은 정기전으로 저장
eventType: 'regular',
startDate: new Date(eventData.startDate),
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
location: eventData.location || '',
gameCount: eventData.gameCount || 3,
maxParticipants: eventData.maxParticipants || 0,
status: normalizeStatus(eventData.status),
// 비즈니스 규칙: 상태는 완료로 저장
status: 'completed',
publicHash: generateRandomHash()
}, { transaction: t });
}
@@ -702,6 +784,7 @@ exports.saveEventFileData = async (req, res) => {
eventId: event.id,
memberId: newMember.id,
status: 'confirmed',
paymentStatus: 'paid',
teamId: teamId,
comment: ''
}, { transaction: t });
@@ -750,9 +833,15 @@ exports.saveEventFileData = async (req, res) => {
for (const s of scoreItems) {
const gameNumber = Number(s.gameNumber ?? 0) || 0;
const baseScore = Number(s.score ?? 0) || 0;
const handicap = s.handicap !== undefined ? Number(s.handicap) : Number(participant.handicap || 0);
const totalScore = s.totalScore !== undefined ? Number(s.totalScore) : baseScore;
// scoresIncludeHandicap 플래그에 따라 저장 방식 결정
const provided = s.totalScore !== undefined ? Number(s.totalScore) : Number(s.score ?? 0) || 0;
const totalScore = scoresIncludeHandicap
? provided
: Math.max(0, provided + (Number.isFinite(handicap) ? handicap : 0));
const baseScore = scoresIncludeHandicap
? Math.max(0, provided - (Number.isFinite(handicap) ? handicap : 0))
: provided;
if (gameNumber <= 0) continue;
await EventScore.create({
@@ -778,6 +867,10 @@ exports.saveEventFileData = async (req, res) => {
title: result.title,
eventType: result.eventType,
startDate: result.startDate
},
rollbackToken: {
eventId: result.id,
clubId: Number(result.clubId)
}
});
} catch (error) {
+4
View File
@@ -11,6 +11,8 @@ router.get('/:id', authenticateJWT, requireFeature('event_management'), eventCon
router.post('/create', authenticateJWT, requireFeature('event_management'), eventController.createEvent);
router.put('/:id', authenticateJWT, requireFeature('event_management'), eventController.updateEvent);
router.delete('/:id', authenticateJWT, requireFeature('event_management'), eventController.deleteEvent);
// 이벤트 완전 삭제 (하드 삭제)
router.delete('/:id/permanent', authenticateJWT, requireFeature('event_management'), eventController.deleteEventPermanently);
// 이벤트 참가자 관리
router.post('/:id/participants/list', authenticateJWT, requireFeature('event_management'), eventController.getEventParticipants);
@@ -42,5 +44,7 @@ router.post('/upload', authenticateJWT, requireFeature('event_management'), file
router.post('/parse-preview', authenticateJWT, requireFeature('event_management'), fileUploadController.parseEventFile);
router.post('/save-file-data', authenticateJWT, requireFeature('event_management'), fileUploadController.saveEventFileData);
router.post('/delete-file', authenticateJWT, requireFeature('event_management'), fileUploadController.deleteUploadedFile);
// 업로드 저장 롤백
router.post('/rollback-import', authenticateJWT, requireFeature('event_management'), fileUploadController.rollbackEventImport);
module.exports = router;