Merge branch 'main' into yoon
All checks were successful
LOCALNET-DEV/pipeline/head This commit looks good

This commit is contained in:
yoon 2025-03-13 12:14:10 +09:00
commit 214f4ec8a0
6 changed files with 104 additions and 37 deletions

View File

@ -49,6 +49,7 @@ import io.company.localhost.common.dto.ApiResponse;
import io.company.localhost.common.dto.MapDto; import io.company.localhost.common.dto.MapDto;
import io.company.localhost.common.exception.InvalidPasswordException; import io.company.localhost.common.exception.InvalidPasswordException;
import io.company.localhost.common.exception.NotFoundHandler; import io.company.localhost.common.exception.NotFoundHandler;
import io.company.localhost.common.exception.code.UserErrorCode;
import io.company.localhost.service.commoncodService; import io.company.localhost.service.commoncodService;
import io.company.localhost.service.localbordService; import io.company.localhost.service.localbordService;
import io.company.localhost.utils.AuthUtil; import io.company.localhost.utils.AuthUtil;
@ -121,7 +122,9 @@ public class BoardController {
public ApiResponse<MapDto> getBoardDetail(@PathVariable("boardId") Long boardId) { public ApiResponse<MapDto> getBoardDetail(@PathVariable("boardId") Long boardId) {
MapDto board = boardService.selectBoardDetail(boardId); MapDto board = boardService.selectBoardDetail(boardId);
if (board == null) { if (board == null) {
throw new NotFoundHandler("게시물 ID " + boardId + "을(를) 찾을 수 없습니다."); //throw new NotFoundHandler("게시물 ID " + boardId + "을(를) 찾을 수 없습니다.");
String errMessage = "게시물 ID " + boardId + "을(를) 찾을 수 없습니다.";
ApiResponse.error(HttpStatus.NOT_FOUND, errMessage);
} }
// 📌 첨부파일 목록 추가 // 📌 첨부파일 목록 추가
List<MapDto> attachments = boardService.selectAttachments(boardId); List<MapDto> attachments = boardService.selectAttachments(boardId);
@ -296,9 +299,10 @@ public class BoardController {
@Member @Member
@ParameterCheck @ParameterCheck
@DeleteMapping("/comment/{commentId}") @DeleteMapping("/comment/{commentId}")
public ApiResponse<String> deleteComment(@PathVariable("commentId") Long commentId) { public ApiResponse<String> deleteComment(@PathVariable("commentId") Long commentId, @RequestParam(value = "LOCCMTPNT") Long parentId) {
MapDto map = new MapDto(); MapDto map = new MapDto();
map.put("LOCCMTSEQ", commentId); map.put("LOCCMTSEQ", commentId);
map.put("LOCCMTPNT", parentId);
boardService.deleteComment(map); boardService.deleteComment(map);
return ApiResponse.ok("댓글이 삭제되었습니다."); return ApiResponse.ok("댓글이 삭제되었습니다.");
@ -328,11 +332,13 @@ public class BoardController {
String storedHashedPassword = boardService.selectBoardPassword(boardId); String storedHashedPassword = boardService.selectBoardPassword(boardId);
if (storedHashedPassword == null) { if (storedHashedPassword == null) {
throw new NotFoundHandler("해당 게시물이 존재하지 않습니다."); throw new NotFoundHandler("해당 게시물이 존재하지 않습니다.");
} }
boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword); boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword);
if (!isMatch) { if (!isMatch) {
throw new InvalidPasswordException("비밀번호가 일치하지 않습니다."); //throw new InvalidPasswordException("비밀번호가 일치하지 않습니다.");
return ApiResponse.error(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
} }
return ApiResponse.ok(true); return ApiResponse.ok(true);
@ -365,7 +371,8 @@ public class BoardController {
boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword); boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword);
if (!isMatch) { if (!isMatch) {
throw new InvalidPasswordException("비밀번호가 일치하지 않습니다."); //throw new InvalidPasswordException("비밀번호가 일치하지 않습니다.");
return ApiResponse.error(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
} }
return ApiResponse.ok(true); return ApiResponse.ok(true);

View File

@ -13,6 +13,7 @@
* *
*************************************************************/ *************************************************************/
package io.company.localhost.controller.api; package io.company.localhost.controller.api;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -54,25 +55,36 @@ public class VacationController {
@PostMapping @PostMapping
public ApiResponse<?> insertVacations(@RequestBody List<MapDto> list) { public ApiResponse<?> insertVacations(@RequestBody List<MapDto> list) {
Long user = AuthUtil.getUser().getId(); Long user = AuthUtil.getUser().getId();
List<MapDto> savedVacations = new ArrayList<>();
for (MapDto request : list) { for (MapDto request : list) {
String date = request.getString("date"); String date = request.getString("date");
String type = request.getString("type"); String type = request.getString("type");
Object receiverId = request.get("receiverId"); Object receiverId = request.get("receiverId");
request.put("employeeId", user);
if (date == null || type == null) { if (date == null || type == null) {
throw new IllegalArgumentException("요청 데이터에 누락된 값이 있습니다: " + request); throw new IllegalArgumentException("요청 데이터에 누락된 값이 있습니다: " + request);
} }
Integer count = request.getInt("count"); Integer count = request.getInt("count");
if (count == null || count < 1) { if (count == null || count < 1) {
count = 1; count = 1;
} }
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
localVacaService.insertVacation(request); MapDto vacationRequest = new MapDto();
vacationRequest.put("date", date);
vacationRequest.put("type", type);
vacationRequest.put("receiverId", receiverId);
vacationRequest.put("employeeId", user);
// 실제 저장
localVacaService.insertVacation(vacationRequest);
savedVacations.add(vacationRequest);
} }
} }
return ApiResponse.ok("모든 휴가가 성공적으로 저장되었습니다.");
return ApiResponse.ok(savedVacations);
} }
/** /**

View File

@ -66,13 +66,13 @@ public interface localbordMapper {
void updateComment(MapDto map); void updateComment(MapDto map);
// 댓글에 대댓글이 있는지 확인 // 댓글에 대댓글이 있는지 확인
int selectHasReplies(MapDto map); int selectReplyCount(Long parentId);
// 댓글 내용만 삭제 처리 (대댓글 유지) // 댓글 내용만 삭제 처리 (대댓글 유지)
void updateSoftDeleteComment(MapDto map); void updateSoftDeleteComment(Long commentId);
// 댓글 삭제 (대댓글 없음) // 댓글 삭제 (대댓글 없음)
void deleteComment(MapDto map); void deleteComment(Long commentId);
// 댓글 비밀번호 조회 // 댓글 비밀번호 조회
String selectCommentPassword(int commentId); String selectCommentPassword(int commentId);
@ -106,6 +106,8 @@ public interface localbordMapper {
List<String> selectDelFileInfo(String[] array); List<String> selectDelFileInfo(String[] array);
void deleteFileInfo(String[] array); void deleteFileInfo(String[] array);
String selectUserProfileImg(String userId);
} }

View File

@ -36,9 +36,11 @@ import io.company.localhost.utils.BlobUtil;
import io.company.localhost.utils.PageUtil; import io.company.localhost.utils.PageUtil;
import io.company.localhost.vo.UploadFile; import io.company.localhost.vo.UploadFile;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class localbordService { public class localbordService {
private final localbordMapper boardMapper; private final localbordMapper boardMapper;
private final FileService fileService; private final FileService fileService;
@ -148,8 +150,7 @@ public class localbordService {
} }
public List<MapDto> selectReply(MapDto map) { public List<MapDto> selectReply(MapDto map) {
return boardMapper.selectReply(map);
return boardMapper.selectReply(map);
} }
public void insertCommentOrReply(MapDto map) { public void insertCommentOrReply(MapDto map) {
@ -173,20 +174,33 @@ public class localbordService {
} }
public void deleteComment(MapDto map) { public void deleteComment(MapDto map) {
Long commentId = (Long) map.get("LOCCMTSEQ");
// 댓글이 대댓글이 있는지 확인 // 댓글이 대댓글이 있는지 확인
boolean hasReplies = boardMapper.selectHasReplies(map) > 0; boolean hasReplies = boardMapper.selectReplyCount(commentId) > 0;
if (hasReplies) { if (hasReplies) {
// 대댓글이 있는 경우, '삭제된 댓글입니다.' 변경 (소프트 삭제) // 대댓글이 있는 경우, '삭제된 댓글입니다.' 변경 (소프트 삭제)
boardMapper.updateSoftDeleteComment(map); boardMapper.updateSoftDeleteComment(commentId);
} else { } else {
// 대댓글이 없는 경우, 완전 삭제 // 대댓글이 없는 경우, 완전 삭제
boardMapper.deleteComment(map); boardMapper.deleteComment(commentId);
} }
checkAndDeleteParentComment(map);
} }
public String selectCommentPassword(int commentId) { private void checkAndDeleteParentComment(MapDto map) {
Long parentId = (Long) map.get("LOCCMTPNT");
if (parentId == null) return; // 부모가 없으면 종료
// 부모 댓글의 남아있는 대댓글 개수 확인
int remainingReplies = boardMapper.selectReplyCount(parentId);
if (remainingReplies == 0) {
// 남은 대댓글이 없으면 부모 댓글도 삭제
boardMapper.deleteComment(parentId);
}
}
public String selectCommentPassword(int commentId) {
return boardMapper.selectCommentPassword(commentId); return boardMapper.selectCommentPassword(commentId);
} }
@ -325,6 +339,16 @@ public class localbordService {
private void enrichCommentsWithAdditionalData(List<MapDto> comments) { private void enrichCommentsWithAdditionalData(List<MapDto> comments) {
for (MapDto comment : comments) { for (MapDto comment : comments) {
Object idObject = comment.get("LOCCMTSEQ"); Object idObject = comment.get("LOCCMTSEQ");
String userId = "";
// 프로필 이미지 추가
if(comment.containsKey("authorId")) {
userId = String.valueOf(comment.get("authorId"));
String profileImg = boardMapper.selectUserProfileImg(userId);
comment.put("profileImg", profileImg);
}
if (idObject instanceof Number) { if (idObject instanceof Number) {
long commentId = ((Number) idObject).longValue(); long commentId = ((Number) idObject).longValue();

View File

@ -99,7 +99,8 @@
b.LOCBRDTYP AS type, b.LOCBRDTYP AS type,
b.LOCBRDCNT AS cnt, b.LOCBRDCNT AS cnt,
m.MEMBERNAM AS author, m.MEMBERNAM AS author,
m.MEMBERSEQ AS authorId m.MEMBERSEQ AS authorId,
m.MEMBERPRF AS profileImg
FROM localbord b FROM localbord b
LEFT JOIN netmember m ON b.MEMBERSEQ = m.MEMBERSEQ LEFT JOIN netmember m ON b.MEMBERSEQ = m.MEMBERSEQ
WHERE b.LOCBRDSEQ = #{boardId} WHERE b.LOCBRDSEQ = #{boardId}
@ -122,7 +123,7 @@
<!-- 게시물 삭제 시 댓글/대댓글 삭제 --> <!-- 게시물 삭제 시 댓글/대댓글 삭제 -->
<delete id="deleteCommentsByBoardId"> <delete id="deleteCommentsByBoardId">
DELETE FROM localcomt DELETE FROM localcomt
WHERE LOCBRDSEQ = #{LOCBRDSEQ} WHERE LOCBRDSEQ = #{LOCBRDSEQ}
</delete> </delete>
@ -176,7 +177,8 @@
c.LOCCMTSEQ,c.LOCBRDSEQ,c.LOCCMTPNT,c.LOCCMTRPY, c.LOCCMTSEQ,c.LOCBRDSEQ,c.LOCCMTPNT,c.LOCCMTRPY,
c.LOCCMTUDT,c.LOCCMTPWD,c.LOCCMTRDT,c.LOCCMTPNT, c.LOCCMTUDT,c.LOCCMTPWD,c.LOCCMTRDT,c.LOCCMTPNT,
m.MEMBERNAM AS author, m.MEMBERNAM AS author,
m.MEMBERSEQ AS authorId m.MEMBERSEQ AS authorId,
m.MEMBERPRF as profileImg
FROM localcomt c FROM localcomt c
LEFT JOIN netmember m ON c.MEMBERSEQ = m.MEMBERSEQ LEFT JOIN netmember m ON c.MEMBERSEQ = m.MEMBERSEQ
WHERE LOCCMTPNT = #{LOCCMTPNT} and LOCCMTPNT != 1 WHERE LOCCMTPNT = #{LOCCMTPNT} and LOCCMTPNT != 1
@ -207,7 +209,6 @@
) )
</update> </update>
<!-- 댓글 삭제 (대댓글 없을 경우) --> <!-- 댓글 삭제 (대댓글 없을 경우) -->
<delete id="deleteComment"> <delete id="deleteComment">
DELETE FROM localcomt DELETE FROM localcomt
@ -216,10 +217,13 @@
SELECT 1 FROM localcomt WHERE LOCCMTPNT = #{LOCCMTSEQ} SELECT 1 FROM localcomt WHERE LOCCMTPNT = #{LOCCMTSEQ}
) )
</delete> </delete>
<!-- 댓글에 대댓글이 있는지 확인 --> <!-- 특정 댓글에 달린 대댓글 개수 조회 -->
<select id="selectHasReplies" resultType="int"> <select id="selectReplyCount" resultType="int">
SELECT COUNT(1) FROM localcomt WHERE LOCCMTPNT = #{LOCCMTSEQ} SELECT COUNT(*)
FROM localcomt
WHERE LOCCMTPNT = #{LOCCMTSEQ}
AND LOCCMTPNT IS NOT NULL
</select> </select>
<!-- 댓글 비밀번호 조회 --> <!-- 댓글 비밀번호 조회 -->
@ -247,7 +251,7 @@
<select id="selectCountComments" parameterType="long" resultType="int"> <select id="selectCountComments" parameterType="long" resultType="int">
SELECT COUNT(*) SELECT COUNT(*)
FROM localcomt FROM localcomt
WHERE LOCBRDSEQ = #{boardId} WHERE LOCBRDSEQ = #{boardId} and LOCCMTPNT = 1
</select> </select>
<!-- 첨부파일 유무 --> <!-- 첨부파일 유무 -->
@ -302,4 +306,14 @@
</foreach> </foreach>
</delete> </delete>
<!-- 프로파일 이미지 조회 -->
<select id="selectUserProfileImg">
SELECT
MEMBERPRF
FROM
NETMEMBER
WHERE
MEMBERSEQ = ${userId}
</select>
</mapper> </mapper>

View File

@ -33,23 +33,31 @@
FROM localvaca FROM localvaca
WHERE MEMBERSEQ = #{userId} WHERE MEMBERSEQ = #{userId}
AND YEAR(LOCVACUDT) = #{year} AND YEAR(LOCVACUDT) = #{year}
AND DATE_FORMAT(LOCVACUDT, '%Y') = DATE_FORMAT(CURDATE(), '%Y')
GROUP BY LOCVACUDT, LOCVACTYP, LOCVACRMM GROUP BY LOCVACUDT, LOCVACTYP, LOCVACRMM
ORDER BY LOCVACUDT DESC ORDER BY LOCVACUDT DESC
</select> </select>
<!-- 사용자가 받은 연차 목록 조회 --> <!-- 사용자가 받은 연차 목록 조회 -->
<select id="selectReceivedVacations" resultType="io.company.localhost.common.dto.MapDto"> <select id="selectReceivedVacations" resultType="io.company.localhost.common.dto.MapDto">
SELECT LOCVACUDT AS date, LOCVACTYP AS type, MEMBERSEQ AS senderId SELECT
LOCVACUDT AS date,
LOCVACTYP AS type,
MEMBERSEQ AS senderId,
-- 반차(700101, 700102)는 0.5, 연차(700103)는 1로 계산하여 받은 연차 수량 저장
SUM(
CASE
WHEN LOCVACTYP IN ('700101', '700102') THEN 0.5
WHEN LOCVACTYP = '700103' THEN 1
ELSE 0
END
) AS received_quota
FROM localvaca FROM localvaca
WHERE LOCVACRMM = #{userId} WHERE LOCVACRMM = #{userId} -- 현재 로그인한 사용자가 받은 연차
AND YEAR(LOCVACUDT) = #{year} AND YEAR(LOCVACUDT) = #{year} -- 해당 연도의 데이터만 가져옴
AND DATE_FORMAT(LOCVACUDT, '%Y') = DATE_FORMAT(CURDATE(), '%Y') GROUP BY LOCVACUDT, LOCVACTYP, MEMBERSEQ -- 연차를 보낸 사람별로 그룹화
GROUP BY LOCVACUDT, LOCVACTYP, MEMBERSEQ ORDER BY LOCVACUDT DESC;
ORDER BY LOCVACUDT DESC
</select> </select>
<!-- 전체 직원 남은 연차 조회 --> <!-- 전체 직원 남은 연차 조회 -->
<select id="selectEmployeeRemainingVacation" resultType="io.company.localhost.common.dto.MapDto"> <select id="selectEmployeeRemainingVacation" resultType="io.company.localhost.common.dto.MapDto">
<![CDATA[ <![CDATA[