삭제처리 수정
This commit is contained in:
@@ -687,7 +687,7 @@ exports.updateEvent = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 삭제 (소프트 삭제)
|
// 이벤트 삭제 (소프트 삭제: 숨김 처리)
|
||||||
exports.deleteEvent = async (req, res) => {
|
exports.deleteEvent = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const eventId = req.params.id;
|
const eventId = req.params.id;
|
||||||
@@ -716,9 +716,9 @@ exports.deleteEvent = async (req, res) => {
|
|||||||
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 참가자 정보도 함께 삭제 처리
|
// 참가자 소프트 삭제(취소 상태로 표시)
|
||||||
await EventParticipant.update(
|
await EventParticipant.update(
|
||||||
{ status: '취소' },
|
{ status: 'canceled' },
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
eventId,
|
eventId,
|
||||||
@@ -727,10 +727,11 @@ exports.deleteEvent = async (req, res) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await event.update({ status: '삭제' });
|
// 이벤트 숨김 처리(표준 상태값 사용)
|
||||||
|
await event.update({ status: 'deleted' });
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: '이벤트가 삭제되었습니다.',
|
message: '이벤트가 숨김(소프트 삭제)되었습니다.',
|
||||||
event: {
|
event: {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
title: event.title,
|
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) => {
|
exports.getEventScores = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -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({
|
const upload = multer({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
fileFilter: fileFilter,
|
fileFilter: fileFilter,
|
||||||
@@ -531,6 +604,12 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
// 트랜잭션 시작
|
// 트랜잭션 시작
|
||||||
const result = await sequelize.transaction(async (t) => {
|
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 호환)
|
// 이벤트 타입/상태 정규화 (모델 enum 호환)
|
||||||
const normalizeEventType = (v) => {
|
const normalizeEventType = (v) => {
|
||||||
if (!v) return 'regular';
|
if (!v) return 'regular';
|
||||||
@@ -569,13 +648,14 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
await event.update({
|
await event.update({
|
||||||
title: eventData.title,
|
title: eventData.title,
|
||||||
description: eventData.description || '',
|
description: eventData.description || '',
|
||||||
eventType: normalizeEventType(eventData.eventType),
|
// 비즈니스 규칙: 이벤트 유형은 정기전, 상태는 완료로 고정
|
||||||
|
eventType: 'regular',
|
||||||
startDate: new Date(eventData.startDate),
|
startDate: new Date(eventData.startDate),
|
||||||
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
|
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
|
||||||
location: eventData.location || '',
|
location: eventData.location || '',
|
||||||
gameCount: eventData.gameCount || 3,
|
gameCount: eventData.gameCount || 3,
|
||||||
maxParticipants: eventData.maxParticipants || 0,
|
maxParticipants: eventData.maxParticipants || 0,
|
||||||
status: normalizeStatus(eventData.status)
|
status: 'completed'
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
} else {
|
} else {
|
||||||
// 새 이벤트 생성
|
// 새 이벤트 생성
|
||||||
@@ -583,13 +663,15 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
clubId: cid,
|
clubId: cid,
|
||||||
title: eventData.title,
|
title: eventData.title,
|
||||||
description: eventData.description || '',
|
description: eventData.description || '',
|
||||||
eventType: normalizeEventType(eventData.eventType),
|
// 비즈니스 규칙: 이벤트 유형은 정기전으로 저장
|
||||||
|
eventType: 'regular',
|
||||||
startDate: new Date(eventData.startDate),
|
startDate: new Date(eventData.startDate),
|
||||||
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
|
endDate: eventData.endDate ? new Date(eventData.endDate) : null,
|
||||||
location: eventData.location || '',
|
location: eventData.location || '',
|
||||||
gameCount: eventData.gameCount || 3,
|
gameCount: eventData.gameCount || 3,
|
||||||
maxParticipants: eventData.maxParticipants || 0,
|
maxParticipants: eventData.maxParticipants || 0,
|
||||||
status: normalizeStatus(eventData.status),
|
// 비즈니스 규칙: 상태는 완료로 저장
|
||||||
|
status: 'completed',
|
||||||
publicHash: generateRandomHash()
|
publicHash: generateRandomHash()
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
}
|
}
|
||||||
@@ -702,6 +784,7 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
eventId: event.id,
|
eventId: event.id,
|
||||||
memberId: newMember.id,
|
memberId: newMember.id,
|
||||||
status: 'confirmed',
|
status: 'confirmed',
|
||||||
|
paymentStatus: 'paid',
|
||||||
teamId: teamId,
|
teamId: teamId,
|
||||||
comment: ''
|
comment: ''
|
||||||
}, { transaction: t });
|
}, { transaction: t });
|
||||||
@@ -750,9 +833,15 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
|
|
||||||
for (const s of scoreItems) {
|
for (const s of scoreItems) {
|
||||||
const gameNumber = Number(s.gameNumber ?? 0) || 0;
|
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 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;
|
if (gameNumber <= 0) continue;
|
||||||
|
|
||||||
await EventScore.create({
|
await EventScore.create({
|
||||||
@@ -778,6 +867,10 @@ exports.saveEventFileData = async (req, res) => {
|
|||||||
title: result.title,
|
title: result.title,
|
||||||
eventType: result.eventType,
|
eventType: result.eventType,
|
||||||
startDate: result.startDate
|
startDate: result.startDate
|
||||||
|
},
|
||||||
|
rollbackToken: {
|
||||||
|
eventId: result.id,
|
||||||
|
clubId: Number(result.clubId)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ router.get('/:id', authenticateJWT, requireFeature('event_management'), eventCon
|
|||||||
router.post('/create', authenticateJWT, requireFeature('event_management'), eventController.createEvent);
|
router.post('/create', authenticateJWT, requireFeature('event_management'), eventController.createEvent);
|
||||||
router.put('/:id', authenticateJWT, requireFeature('event_management'), eventController.updateEvent);
|
router.put('/:id', authenticateJWT, requireFeature('event_management'), eventController.updateEvent);
|
||||||
router.delete('/:id', authenticateJWT, requireFeature('event_management'), eventController.deleteEvent);
|
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);
|
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('/parse-preview', authenticateJWT, requireFeature('event_management'), fileUploadController.parseEventFile);
|
||||||
router.post('/save-file-data', authenticateJWT, requireFeature('event_management'), fileUploadController.saveEventFileData);
|
router.post('/save-file-data', authenticateJWT, requireFeature('event_management'), fileUploadController.saveEventFileData);
|
||||||
router.post('/delete-file', authenticateJWT, requireFeature('event_management'), fileUploadController.deleteUploadedFile);
|
router.post('/delete-file', authenticateJWT, requireFeature('event_management'), fileUploadController.deleteUploadedFile);
|
||||||
|
// 업로드 저장 롤백
|
||||||
|
router.post('/rollback-import', authenticateJWT, requireFeature('event_management'), fileUploadController.rollbackEventImport);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
Reference in New Issue
Block a user