diff --git a/src/main/java/com/itn/admin/cmn/config/SecurityConfig.java b/src/main/java/com/itn/admin/cmn/config/SecurityConfig.java index 4e542e5..c0285b4 100644 --- a/src/main/java/com/itn/admin/cmn/config/SecurityConfig.java +++ b/src/main/java/com/itn/admin/cmn/config/SecurityConfig.java @@ -86,12 +86,13 @@ public class SecurityConfig { .failureHandler(customAuthenticationFailureHandler()) // 로그인 실패 시 핸들러 .permitAll() // 로그인 페이지는 누구나 접근 가능 ) + // 로그아웃 설정 .logout((logoutConfig) -> logoutConfig .logoutUrl("/logout") // 로그아웃 요청 URL .logoutSuccessUrl("/user/login") // 로그아웃 성공 시 리다이렉트 URL .invalidateHttpSession(true) // 로그아웃 시 세션 무효화 - .deleteCookies("JSESSIONID") // JSESSIONID 쿠키 삭제 +// .deleteCookies("JSESSIONID") // JSESSIONID 쿠키 삭제 .permitAll() // 로그아웃은 누구나 요청 가능 ) // 세션 관리 설정 @@ -122,15 +123,31 @@ public class SecurityConfig { // 인증된 사용자 정보를 가져옴 CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + // session 설정 + // 보안이슈로인해 필요한 데이터만 + UserVO loginVO = new UserVO(); + loginVO.setUserName( userDetails.getUser().getUserName()); + request.getSession().setAttribute("loginVO", loginVO); + // 요청 URL의 호스트를 추출 (디버깅용) String host_url = new URL(request.getRequestURL().toString()).getHost(); - System.out.println("host_url : " + host_url); +// System.out.println("host_url : " + host_url); // 로컬 환경이 아닌 경우 로그인 로그를 기록 (2024-05-22 수정) if (!"localhost".equals(host_url)) { + String id = userDetails.getId(); userService.loginLog(id); // 로그인 기록 저장 } + +// +// var rememberMeServices = new org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices( +// "secureAndRandomKey", customUserDetailsService +// ); +// rememberMeServices.setAlwaysRemember(true); +// rememberMeServices.onLoginSuccess(request, response, authentication); + + response.setStatus(HttpStatus.OK.value()); // HTTP 상태 코드 200 설정 response.sendRedirect("/"); // 루트 경로로 리다이렉트 }; diff --git a/src/main/java/com/itn/admin/cmn/util/thymeleafUtils/TCodeUtils.java b/src/main/java/com/itn/admin/cmn/util/thymeleafUtils/TCodeUtils.java index 8e6b4c8..09cd5b0 100644 --- a/src/main/java/com/itn/admin/cmn/util/thymeleafUtils/TCodeUtils.java +++ b/src/main/java/com/itn/admin/cmn/util/thymeleafUtils/TCodeUtils.java @@ -1,11 +1,11 @@ package com.itn.admin.cmn.util.thymeleafUtils; import com.itn.admin.itn.code.mapper.domain.CodeDetailVO; -import com.itn.admin.itn.code.mapper.domain.CodeVO; import com.itn.admin.itn.code.server.CodeDetailService; +import com.itn.admin.itn.user.mapper.UserMapper; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.thymeleaf.util.StringUtils; import java.util.List; @@ -16,6 +16,8 @@ public class TCodeUtils { @Autowired private CodeDetailService codeDetailService; + @Autowired + private UserMapper userMapper; public String getCodeName(String codeGroupId, String codeValue) { if(StringUtils.isEmpty(codeValue)){ @@ -23,9 +25,42 @@ public class TCodeUtils { }; return codeDetailService.getCodeName(codeGroupId, codeValue); } + public List getCodeList(String codeGroupId) { return codeDetailService.getDetailsByGroupId(codeGroupId); } + public String getUserName(String uniqId) { + if(StringUtils.isEmpty(uniqId)){ + return uniqId; + } + String userName = userMapper.findById(uniqId).getUserName(); + return StringUtils.isEmpty(userName) ? uniqId : userName ; + } + public String getUserDeptTxt(String uniqId) { + String deptCd = userMapper.findById(uniqId).getDeptCd(); + String dept = ""; + if(StringUtils.isNotEmpty(deptCd)){ + dept = codeDetailService.getCodeName("DEPT", deptCd); + } + return StringUtils.isEmpty(dept) ? "-" : dept ; + } + + public String getUserRankTxt(String uniqId) { + String rankCd = userMapper.findById(uniqId).getRankCd(); + String rank = ""; + if(StringUtils.isNotEmpty(rankCd)){ + rank = codeDetailService.getCodeName("RANK", rankCd); + } + return StringUtils.isEmpty(rank) ? "-" : rank ; + } + + public String getUserMobilePhone(String uniqId) { + String mobilePhone = userMapper.findById(uniqId).getMobilePhone(); + + return StringUtils.isEmpty(mobilePhone) ? uniqId : mobilePhone ; + } + + } diff --git a/src/main/java/com/itn/admin/init/web/DashboardController.java b/src/main/java/com/itn/admin/init/web/DashboardController.java index 005e3b3..529a58e 100644 --- a/src/main/java/com/itn/admin/init/web/DashboardController.java +++ b/src/main/java/com/itn/admin/init/web/DashboardController.java @@ -1,17 +1,45 @@ package com.itn.admin.init.web; +import com.itn.admin.cmn.config.CustomUserDetails; +import com.itn.admin.itn.bizTrip.mapper.domain.BizTripVO; +import com.itn.admin.itn.bizTrip.service.BizTripService; +import com.itn.admin.itn.user.mapper.domain.UserVO; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import java.util.List; + +@Slf4j @Controller public class DashboardController { + @Autowired + private BizTripService bizTripService; + @GetMapping("/") - public String index() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - System.out.println("authentication.getAuthorities(); : "+ authentication.getAuthorities()); + public String index(HttpServletRequest request + , Model model + , @AuthenticationPrincipal CustomUserDetails loginUser) { + + List myApprovalslist = bizTripService.getMyPendingApprovals(loginUser.getUser().getUniqId()); + + + model.addAttribute("myApprovalslist", myApprovalslist); + +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// System.out.println("authentication.getAuthorities(); : "+ authentication.getAuthorities()); +// HttpSession session = request.getSession(); +// Object loginVO = session.getAttribute("loginVO"); +// log.info("세션에 저장된 사용자 정보: {}", loginVO instanceof UserVO); +// log.info("세션에 저장된 사용자 정보: {}", loginVO); return "dashboard/index"; } diff --git a/src/main/java/com/itn/admin/itn/bizTrip/mapper/BizTripMapper.java b/src/main/java/com/itn/admin/itn/bizTrip/mapper/BizTripMapper.java index 674d325..f8db501 100644 --- a/src/main/java/com/itn/admin/itn/bizTrip/mapper/BizTripMapper.java +++ b/src/main/java/com/itn/admin/itn/bizTrip/mapper/BizTripMapper.java @@ -1,9 +1,11 @@ package com.itn.admin.itn.bizTrip.mapper; +import com.itn.admin.cmn.msg.RestResponse; import com.itn.admin.itn.bizTrip.mapper.domain.BizTripApprovalVO; import com.itn.admin.itn.bizTrip.mapper.domain.BizTripMemberVO; import com.itn.admin.itn.bizTrip.mapper.domain.BizTripVO; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; import java.util.List; @@ -16,4 +18,20 @@ public interface BizTripMapper { void insertApprovalLine(BizTripApprovalVO approval); List selectTripList(); + + BizTripVO getBizTripWithDetail(String tripId); + + List getMyPendingApprovals(String uniqId); + + RestResponse saveApproval(BizTripApprovalVO bizTripApprovalVO); + + void updateTripStatus(BizTripVO bizTripVO); + + String getNextApproverId(int tripId, String approverId); + + void updateBizTrip(BizTripVO trip); + + void deleteTripMembers(Integer tripId); + + void deleteApprovalLines(Integer tripId); } diff --git a/src/main/java/com/itn/admin/itn/bizTrip/mapper/domain/BizTripApprovalVO.java b/src/main/java/com/itn/admin/itn/bizTrip/mapper/domain/BizTripApprovalVO.java index 5341545..9090ef9 100644 --- a/src/main/java/com/itn/admin/itn/bizTrip/mapper/domain/BizTripApprovalVO.java +++ b/src/main/java/com/itn/admin/itn/bizTrip/mapper/domain/BizTripApprovalVO.java @@ -7,6 +7,7 @@ import lombok.experimental.SuperBuilder; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.Date; @Getter @@ -22,7 +23,7 @@ public class BizTripApprovalVO extends CmnVO { private String approverId; // 결재자 uniq_id private Integer orderNo; // 결재 순서 private String approveStatus; // 결재 상태 (WAIT, APPROVED, REJECTED) - private LocalDateTime approveDt; // 결재 일시 + private Date approveDt; // 결재 일시 private String comment; // 결재 의견 diff --git a/src/main/java/com/itn/admin/itn/bizTrip/mapper/domain/BizTripVO.java b/src/main/java/com/itn/admin/itn/bizTrip/mapper/domain/BizTripVO.java index 936ae6b..bacab73 100644 --- a/src/main/java/com/itn/admin/itn/bizTrip/mapper/domain/BizTripVO.java +++ b/src/main/java/com/itn/admin/itn/bizTrip/mapper/domain/BizTripVO.java @@ -6,6 +6,7 @@ import lombok.experimental.SuperBuilder; import java.time.LocalDate; import java.time.LocalTime; +import java.util.List; @Getter @@ -26,8 +27,14 @@ public class BizTripVO extends CmnVO { private LocalTime startTime; // 출장 시작시간 private LocalTime endTime; // 출장 종료시간 private String status; // 결재 상태 (ING, DONE 등) + private String useYn; // 사용여부 private String latestApproveStatus; // 최신 결재 상태 + private String currentApproverId; // 현재 결제 대기자 + + private List memberList; + private List approvalList; + } diff --git a/src/main/java/com/itn/admin/itn/bizTrip/service/BizTripService.java b/src/main/java/com/itn/admin/itn/bizTrip/service/BizTripService.java index def276e..294d1c4 100644 --- a/src/main/java/com/itn/admin/itn/bizTrip/service/BizTripService.java +++ b/src/main/java/com/itn/admin/itn/bizTrip/service/BizTripService.java @@ -1,13 +1,26 @@ package com.itn.admin.itn.bizTrip.service; import com.itn.admin.cmn.msg.RestResponse; +import com.itn.admin.itn.bizTrip.mapper.domain.BizTripApprovalVO; import com.itn.admin.itn.bizTrip.mapper.domain.BizTripRequestDTO; import com.itn.admin.itn.bizTrip.mapper.domain.BizTripVO; +import com.itn.admin.itn.user.mapper.domain.UserVO; import java.util.List; +import java.util.Map; public interface BizTripService { RestResponse register(BizTripRequestDTO dto); List selectTripList(); + + Map getBizTripWithDetail(String tripId); + + List getMyPendingApprovals(String uniqId); + + RestResponse approval(BizTripApprovalVO bizTripApprovalVO, UserVO userVO); + + Map getBizTripWithEdit(String tripId); + + RestResponse update(BizTripRequestDTO dto); } diff --git a/src/main/java/com/itn/admin/itn/bizTrip/service/impl/BizTripServiceImpl.java b/src/main/java/com/itn/admin/itn/bizTrip/service/impl/BizTripServiceImpl.java index d0e585e..95d3592 100644 --- a/src/main/java/com/itn/admin/itn/bizTrip/service/impl/BizTripServiceImpl.java +++ b/src/main/java/com/itn/admin/itn/bizTrip/service/impl/BizTripServiceImpl.java @@ -10,11 +10,14 @@ import com.itn.admin.itn.bizTrip.service.BizTripService; import com.itn.admin.itn.code.mapper.CodeMapper; import com.itn.admin.itn.code.mapper.domain.CodeVO; import com.itn.admin.itn.code.server.CodeService; +import com.itn.admin.itn.user.mapper.domain.UserVO; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import java.util.List; +import java.util.*; @Service public class BizTripServiceImpl implements BizTripService { @@ -49,12 +52,146 @@ public class BizTripServiceImpl implements BizTripService { } - return new RestResponse(HttpStatus.OK, "등록되었습니다"); + return new RestResponse(HttpStatus.OK, "등록되었습니다",tripId); } + + @Override + public RestResponse update(BizTripRequestDTO dto) { + BizTripVO trip = dto.getTripInfo(); + Integer tripId = trip.getTripId(); + + // 1. 출장 기본 정보 수정 + bizTripMapper.updateBizTrip(trip); // tripId 포함되어 있어야 함 + + // 2. 기존 출장 인원 삭제 후 재등록 + bizTripMapper.deleteTripMembers(tripId); + List memberList = dto.getTripMembers(); + if (memberList != null && !memberList.isEmpty()) { + for (BizTripMemberVO member : memberList) { + member.setTripId(tripId); + bizTripMapper.insertTripMember(member); + } + } + + // 3. 기존 결재라인 삭제 후 재등록 + bizTripMapper.deleteApprovalLines(tripId); + List approvalList = dto.getApprovalLines(); + if (approvalList != null && !approvalList.isEmpty()) { + for (BizTripApprovalVO approval : approvalList) { + approval.setTripId(tripId); + bizTripMapper.insertApprovalLine(approval); + } + } + + return new RestResponse(HttpStatus.OK, "수정되었습니다"); + } + + + @Override public List selectTripList() { return bizTripMapper.selectTripList(); } + + @Override + public Map getBizTripWithDetail(String tripId) { + Map returnMap = new HashMap<>(); + BizTripVO bizTrip = bizTripMapper.getBizTripWithDetail(tripId); + + // 상세화면 + String firstWaitingApproverId = bizTrip.getApprovalList().stream() + .filter(a -> "10".equals(a.getApproveStatus())) + .map(BizTripApprovalVO::getApproverId) + .findFirst() + .orElse(null); + + + /*더 필요한 데이터있으면 추가해서 returnMap에 put 해서 넘겨주기*/ + returnMap.put("bizTrip", bizTrip); + + returnMap.put("firstWaitingApproverId", firstWaitingApproverId); + return returnMap; + } + + @Override + public Map getBizTripWithEdit(String tripId) { + Map returnMap = new HashMap<>(); + BizTripVO bizTrip = bizTripMapper.getBizTripWithDetail(tripId); + + // 검토1, 검토2, 결제를 구현하기 위한 로직 + List finalList = new ArrayList<>(); + for (int i = 1; i <= 3; i++) { + // 복사해서 final 변수로 만듦 + // 불변이라고 선언을 해야 람다에서 사용가능 + final int order = i; + Optional found = bizTrip.getApprovalList().stream() + .filter(vo -> vo.getOrderNo() == order) + .findFirst(); + + if (found.isPresent()) { + finalList.add(found.get()); + } else { + BizTripApprovalVO empty = new BizTripApprovalVO(); + empty.setOrderNo(i); + finalList.add(empty); + } + } + bizTrip.setApprovalList(finalList); + // -- 검토1, 검토2, 결제를 구현하기 위한 로직 + + /*더 필요한 데이터있으면 추가해서 returnMap에 put 해서 넘겨주기*/ + returnMap.put("bizTrip", bizTrip); + + return returnMap; + } + + @Override + public List getMyPendingApprovals(String uniqId) { + return bizTripMapper.getMyPendingApprovals(uniqId); + } + + @Override + @Transactional + public RestResponse approval(BizTripApprovalVO bizTripApprovalVO, UserVO userVO) { + + + // '결재 상태(APPR_STS - 대기:10, 승인:30, 거절:40)' + String approveStatus = bizTripApprovalVO.getApproveStatus(); + int tripId = bizTripApprovalVO.getTripId(); + + bizTripMapper.saveApproval(bizTripApprovalVO); + + + // 다음 결제자 있는지 확인하는 로직 + // 있으면 uniqId 가져옴 + String nextApproverId = bizTripMapper.getNextApproverId(tripId, bizTripApprovalVO.getApproverId()); + BizTripVO bizTripVO = BizTripVO.builder() + .tripId(tripId) + .status(approveStatus) + .lastUpdusrId(userVO.getUniqId()) + .build(); + + if (StringUtils.isNotEmpty(nextApproverId) && "30".equals(approveStatus)) { + bizTripVO.setStatus("20"); + // 다음 결재자 있음 & 현재 승인 → '진행중' + bizTripMapper.updateTripStatus(bizTripVO); + + //TODO [slack] nextApproverId를 참고해서 알려줘야함 + + + } else { + + // 마지막 결재자 or 반려 → 본인 상태 그대로 반영 + bizTripMapper.updateTripStatus(bizTripVO); + + //TODO [slack] 결제 완료되었다는걸 알려줘야함 + + } + + + return new RestResponse(HttpStatus.OK,"결제가 완료되었습니다."); + + } } diff --git a/src/main/java/com/itn/admin/itn/bizTrip/web/BizTripController.java b/src/main/java/com/itn/admin/itn/bizTrip/web/BizTripController.java index 4710e5c..441703e 100644 --- a/src/main/java/com/itn/admin/itn/bizTrip/web/BizTripController.java +++ b/src/main/java/com/itn/admin/itn/bizTrip/web/BizTripController.java @@ -1,6 +1,7 @@ package com.itn.admin.itn.bizTrip.web; import com.itn.admin.cmn.config.CustomUserDetails; +import com.itn.admin.itn.bizTrip.mapper.domain.BizTripMemberVO; import com.itn.admin.itn.bizTrip.mapper.domain.BizTripVO; import com.itn.admin.itn.bizTrip.service.BizTripService; import com.itn.admin.itn.user.mapper.domain.UserVO; @@ -12,8 +13,11 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Slf4j @Controller @@ -32,11 +36,43 @@ public class BizTripController { return "itn/bizTrip/reg"; } + + @GetMapping("/itn/bizTrip/edit/{tripId}") + public String edit(@PathVariable String tripId + , @AuthenticationPrincipal CustomUserDetails loginUser + ,Model model + ) { + + Map returnMap = bizTripService.getBizTripWithEdit(tripId); + model.addAttribute("trip", returnMap.get("bizTrip")); + model.addAttribute("loginUser", loginUser.getUser()); + + return "itn/bizTrip/edit"; + } @GetMapping("/itn/bizTrip/list") - public String bizTripList(Model model) { + public String bizTripList(Model model + ,@AuthenticationPrincipal CustomUserDetails loginUser) { List list = bizTripService.selectTripList(); model.addAttribute("list", list); + for (BizTripVO vo : list) { + log.info(" + vo :: [{}]", vo.getCurrentApproverId()); + } + model.addAttribute("loginUser", loginUser.getUser()); + log.info(" + loginUser :: [{}]", loginUser.getUser()); return "itn/bizTrip/list"; // Thymeleaf HTML 파일 경로 } + @GetMapping("/itn/bizTrip/detail/{tripId}") + public String tripDetail(@PathVariable String tripId + ,@AuthenticationPrincipal CustomUserDetails loginUser + , Model model + ) { + Map returnMap = bizTripService.getBizTripWithDetail(tripId); + model.addAttribute("trip", returnMap.get("bizTrip")); + model.addAttribute("loginUser", loginUser.getUser()); + model.addAttribute("firstWaitingApproverId", returnMap.get("firstWaitingApproverId")); + return "itn/bizTrip/tripDetail"; + } + + } diff --git a/src/main/java/com/itn/admin/itn/bizTrip/web/BizTripRestController.java b/src/main/java/com/itn/admin/itn/bizTrip/web/BizTripRestController.java index bb40d52..a6ff968 100644 --- a/src/main/java/com/itn/admin/itn/bizTrip/web/BizTripRestController.java +++ b/src/main/java/com/itn/admin/itn/bizTrip/web/BizTripRestController.java @@ -2,6 +2,7 @@ package com.itn.admin.itn.bizTrip.web; import com.itn.admin.cmn.config.CustomUserDetails; import com.itn.admin.cmn.msg.RestResponse; +import com.itn.admin.itn.bizTrip.mapper.domain.BizTripApprovalVO; import com.itn.admin.itn.bizTrip.mapper.domain.BizTripRequestDTO; import com.itn.admin.itn.bizTrip.service.BizTripService; import com.itn.admin.itn.code.mapper.domain.CodeDetailVO; @@ -27,11 +28,24 @@ public class BizTripRestController { , @AuthenticationPrincipal CustomUserDetails loginUser) { dto.getTripInfo().setFrstRegisterId(loginUser.getUser().getUniqId()); log.info("dto: [{}]", dto); -// bizTripService.register(dto); return ResponseEntity.ok().body(bizTripService.register(dto)); } + @PostMapping("/api/bizTrip/approval") + public ResponseEntity approval(@RequestBody BizTripApprovalVO bizTripApprovalVO + , @AuthenticationPrincipal CustomUserDetails loginUser) { + log.info("bizTripApprovalVO: [{}]", bizTripApprovalVO); +// bizTripService.register(dto); + return ResponseEntity.ok().body(bizTripService.approval(bizTripApprovalVO, loginUser.getUser())); + } + + @PutMapping("/api/bizTrip/update") + public ResponseEntity updateBizTrip(@RequestBody BizTripRequestDTO dto, + @AuthenticationPrincipal CustomUserDetails loginUser) { + dto.getTripInfo().setLastUpdusrId(loginUser.getUser().getUniqId()); + return ResponseEntity.ok().body(bizTripService.update(dto)); + } + - } diff --git a/src/main/java/com/itn/admin/itn/user/web/UserRestController.java b/src/main/java/com/itn/admin/itn/user/web/UserRestController.java index 2879e87..587f63b 100644 --- a/src/main/java/com/itn/admin/itn/user/web/UserRestController.java +++ b/src/main/java/com/itn/admin/itn/user/web/UserRestController.java @@ -38,12 +38,13 @@ public class UserRestController { } - // 특정 코드 그룹을 ID로 가져오는 메서드 + // @GetMapping("/api/admin/user/{uniqId}") public ResponseEntity findByUniqId(@PathVariable String uniqId) { return ResponseEntity.ok(userService.findByUniqId(uniqId)); } - // 특정 코드 그룹을 ID로 가져오는 메서드 + + // @GetMapping("/api/admin/user/search/name") public ResponseEntity findByUniqUserName(@RequestParam String userName) { log.info("userName: {}", userName); diff --git a/src/main/resources/mapper/itn/bizTrip/BizTripMapper.xml b/src/main/resources/mapper/itn/bizTrip/BizTripMapper.xml index 1cd56f1..dd70a96 100644 --- a/src/main/resources/mapper/itn/bizTrip/BizTripMapper.xml +++ b/src/main/resources/mapper/itn/bizTrip/BizTripMapper.xml @@ -67,40 +67,118 @@ ELSE '10' /* 전부 대기 */ END AS status + /* 현재 결재 대기자 추가 */ + ,( + SELECT a.approver_id + FROM biz_trip_approval a + WHERE a.trip_id = bt.trip_id + AND a.approve_status = '10' + ORDER BY a.order_no ASC + LIMIT 1 + ) AS current_approver_id + FROM biz_trip bt + WHERE bt.use_yn = 'Y' ORDER BY bt.trip_dt DESC ]]> - + + + + + + + + + + + + + - <!– 특정 코드 그룹을 ID로 조회하는 쿼리 –> - + SELECT * FROM biz_trip_member WHERE trip_id = #{tripId} - <!– 코드 그룹을 추가하는 쿼리 –> - - INSERT INTO common_code (code_group_id, code_group_name, description, frst_register_id, frst_regist_pnttm, last_updusr_id, last_updt_pnttm) - VALUES (#{codeGroupId}, #{codeGroupName}, #{description}, #{frstRegisterId}, #{frstRegistPnttm}, #{lastUpdusrId}, #{lastUpdtPnttm}) - + + - <!– 코드 그룹을 수정하는 쿼리 –> - - UPDATE common_code - SET code_group_name = #{codeGroupName}, - description = #{description}, + + + + + UPDATE biz_trip + SET status = #{status}, last_updusr_id = #{lastUpdusrId}, - last_updt_pnttm = #{lastUpdtPnttm} - WHERE code_group_id = #{codeGroupId} + last_updt_pnttm = NOW() + WHERE trip_id = #{tripId} - <!– 코드 그룹을 삭제하는 쿼리 –> - - DELETE FROM common_code WHERE code_group_id = #{codeGroupId} - --> + + + + UPDATE biz_trip + SET + trip_type_cd = #{tripTypeCd}, + location_cd = #{locationCd}, + location_txt = #{locationTxt}, + purpose = #{purpose}, + move_cd = #{moveCd}, + trip_dt = #{tripDt}, + start_time = #{startTime}, + end_time = #{endTime}, + status = #{status}, + last_updusr_id = #{lastUpdusrId}, + last_updt_pnttm = NOW() + WHERE trip_id = #{tripId} + + + + DELETE FROM biz_trip_member + WHERE trip_id = #{tripId} + + + + DELETE FROM biz_trip_approval + WHERE trip_id = #{tripId} + + \ No newline at end of file diff --git a/src/main/resources/mapper/itn/commute/CommuteMapper.xml b/src/main/resources/mapper/itn/commute/CommuteMapper.xml index e08079b..f40f7dc 100644 --- a/src/main/resources/mapper/itn/commute/CommuteMapper.xml +++ b/src/main/resources/mapper/itn/commute/CommuteMapper.xml @@ -54,7 +54,7 @@ ,ic.holi_code -- ,ic.pstn ,us.user_name - ,us.user_rank + ,us.rank_cd from itn_commute ic left join itn_commute_group icg on ic.commute_group_id = icg.commute_group_id diff --git a/src/main/resources/static/cmn/js/bizTrip/event.js b/src/main/resources/static/cmn/js/bizTrip/edit/event.js similarity index 100% rename from src/main/resources/static/cmn/js/bizTrip/event.js rename to src/main/resources/static/cmn/js/bizTrip/edit/event.js diff --git a/src/main/resources/static/cmn/js/bizTrip/init.js b/src/main/resources/static/cmn/js/bizTrip/edit/init.js similarity index 100% rename from src/main/resources/static/cmn/js/bizTrip/init.js rename to src/main/resources/static/cmn/js/bizTrip/edit/init.js diff --git a/src/main/resources/static/cmn/js/bizTrip/edit/service.js b/src/main/resources/static/cmn/js/bizTrip/edit/service.js new file mode 100644 index 0000000..e0eae80 --- /dev/null +++ b/src/main/resources/static/cmn/js/bizTrip/edit/service.js @@ -0,0 +1,74 @@ +/** + * @discription 수정 액션 + * @param tripId + */ +function updateTripData(tripId) { + if (!validateTripForm()) return; + + // 값 생성 + const tripInfo = { + tripId: tripId, + tripTypeCd: $('#tripType').val(), + locationCd: $('#tripLocation').val(), + locationTxt: $('#locationTxt').val(), + purpose: $('#purpose').val(), + moveCd: $('#tripMove').val(), + tripDt: $('#tripDate').val(), + startTime: $('#startTimePicker input').val(), + endTime: $('#endTimePicker input').val(), + status: '10' + }; + + const tripMembers = []; + $('#tripMemberTbody tr').each(function () { + const uniqId = $(this).data('uniqid'); + if (!uniqId) return; + const role = $(this).find('td').eq(3).text().trim() === '기안자' ? '0' : '1'; + tripMembers.push({ uniqId, role }); + }); + + const approvalLines = []; + ['approver1', 'approver2', 'approver3'].forEach((id, idx) => { + const uniqId = $(`#${id}`).val(); + if (uniqId) { + approvalLines.push({ + approverId: uniqId, + orderNo: idx + 1, + approveStatus: '10' + }); + } + }); + + const payload = { tripInfo, tripMembers, approvalLines }; + console.log("update payload:", payload); + + // Ajax 전송 + $.ajax({ + url: '/api/bizTrip/update', + method: 'PUT', + contentType: 'application/json', + data: JSON.stringify(payload), + success: function(data) { + console.log('data : ', data); + // Toast 먼저 띄움 (바로 표시됨) + // fn_successAlert("수정 성공", data.msg); + + Swal.fire({ + title: data.msg, + text: '목록으로 이동하시겠습니까?', + icon: 'success', + showCancelButton: true, + confirmButtonText: '이동', + width: 300, + cancelButtonText: '취소' + }).then((result) => { + if (result.isConfirmed) { + location.href = "/itn/bizTrip/list"; + } else if (result.isDismissed) { + location.reload(); + } + }); + }, + error: () => fn_failedAlert("수정 실패", "오류가 발생했습니다.") + }); +} diff --git a/src/main/resources/static/cmn/js/bizTrip/validation.js b/src/main/resources/static/cmn/js/bizTrip/edit/validation.js similarity index 100% rename from src/main/resources/static/cmn/js/bizTrip/validation.js rename to src/main/resources/static/cmn/js/bizTrip/edit/validation.js diff --git a/src/main/resources/static/cmn/js/bizTrip/reg/event.js b/src/main/resources/static/cmn/js/bizTrip/reg/event.js new file mode 100644 index 0000000..5ca8500 --- /dev/null +++ b/src/main/resources/static/cmn/js/bizTrip/reg/event.js @@ -0,0 +1,247 @@ +/** + * 페이지 로드 완료 후 실행되는 초기화 함수 (비워둠) + * @function + */ +$(function () { + // 초기화 코드 필요 시 여기에 작성 +}); + +/** + * Enter 키 입력 시 사용자 검색 수행 + * @event keydown + * @param {KeyboardEvent} e + */ +document.getElementById("userSearchKeyword").addEventListener("keydown", function (e) { + if (e.key === "Enter") { + e.preventDefault(); + searchUser(); + } +}); +document.getElementById("approvalSearchKeyword").addEventListener("keydown", function (e) { + if (e.key === "Enter") { + e.preventDefault(); + searchApproval(); + } +}); + +/** + * 모달이 열릴 때 사용자 목록을 불러오고 포커스를 맞춤 + * @event shown.bs.modal + */ +$('#userSearchModal').on('shown.bs.modal', function () { + loadUserList(); + $('#userSearchKeyword').trigger('focus'); +}); +$('#approvalSearchModal').on('shown.bs.modal', function () { + loadApprovalList(); + $('#approvalSearchKeyword').trigger('focus'); +}); + +/** + * 날짜 입력 필드 클릭 시 브라우저 기본 날짜 선택기 표시 + * @event click + */ +document.querySelector('input[id="tripDate"]').addEventListener('click', function () { + this.showPicker && this.showPicker(); +}); + +/** + * 사용자 목록을 불러오는 Ajax 요청 함수 + * @function + * @param {string} [keyword=""] - 검색 키워드 + * @returns {void} + */ +function loadUserList(keyword = "") { + $.ajax({ + url: '/api/admin/user/search/name', + type: 'GET', + data: { userName: keyword }, + success: function (result) { + const tbody = document.getElementById("userSearchResult"); + tbody.innerHTML = ""; + + const data = result.data; + if (data && data.length > 0) { + data.forEach(user => { + const row = document.createElement("tr"); + row.innerHTML = ` + ${user.userName} + ${user.deptNm || "-"} + ${user.rankCd || "-"} + + + + `; + tbody.appendChild(row); + }); + } else { + tbody.innerHTML = `검색 결과가 없습니다.`; + } + }, + error: function () { + alert("사용자 목록을 불러오는 데 실패했습니다."); + } + }); +} + +function loadApprovalList(keyword = "") { + $.ajax({ + url: '/api/admin/approval/search/name', + type: 'GET', + data: { userName: keyword }, + success: function (result) { + const tbody = document.getElementById("approvalSearchResult"); + const data = result.data; + + tbody.innerHTML = ""; + if (data && data.length > 0) { + data.forEach(user => { + const row = document.createElement("tr"); + row.innerHTML = ` + ${user.userName} + ${user.deptNm || "-"} + ${user.rankCd || "-"} + + + + `; + tbody.appendChild(row); + }); + } else { + tbody.innerHTML = `검색 결과가 없습니다.`; + } + }, + error: function () { + alert("사용자 목록을 불러오는 데 실패했습니다."); + } + }); +} + +/** + * 사용자를 선택하여 출장 인원 또는 결재라인에 추가 + * @function + * @param {string} name - 사용자 이름 + * @param {string} dept - 부서명 + * @param {string} phone - 전화번호 + * @param {string} uniqId - 사용자 고유 ID + * @returns {void} + */ +function selectUser(name, dept, phone, uniqId) { + + const tbody = document.getElementById("tripMemberTbody"); + const newRow = document.createElement("tr"); + newRow.setAttribute("data-uniqid", uniqId); + newRow.innerHTML = ` + ${name} + ${dept} + ${phone} + + + 삭제 + + + `; + tbody.appendChild(newRow); + $('#userSearchModal').modal('hide'); +} + + +function selectApproval(name, dept, phone, uniqId) { + const stageId = $('#approvalSearchModal').data('stageId'); + + const rowMap = { + approval1: { index: 0, inputId: "approver1" }, + approval2: { index: 1, inputId: "approver2" }, + approval3: { index: 2, inputId: "approver3" } + }; + + const { index, inputId } = rowMap[stageId]; + const tbody = document.getElementById("approvalLineTbody"); + const targetRow = tbody.rows[index]; + if (!targetRow) return; + + targetRow.innerHTML = ` + ${targetRow.cells[0].textContent} + ${name} + ${dept} + ${phone} + + + 삭제 + + + `; + + // hidden input에 uniqId 저장 + document.getElementById(inputId).value = uniqId; + + $('#approvalSearchModal').modal('hide'); + $('#approvalSearchModal').data('stageId', null); + +} + +/** + * 검색창 버튼 클릭 시 사용자 검색 실행 + * @function + */ +function searchUser() { + const keyword = document.getElementById("userSearchKeyword").value.trim(); + loadUserList(keyword); +} +function searchApproval() { + const keyword = document.getElementById("userSearchKeyword").value.trim(); + loadApprovalList(keyword); +} + +/** + * 출장 인원 행 삭제 + * @function + * @param {HTMLElement} el - 삭제 버튼 요소 + */ +function removeMemberRow(el) { + if (confirm("정말 삭제하시겠습니까?")) { + const row = el.closest("tr"); + if (row) row.remove(); + } +} + + +/** + * 결재자 지정 모달 열기 + * @function + * @param {string} stageId - 결재 단계 ID (approval1, approval2, approval3) + */ +function openApprovalModal(stageId) { + $('#approvalSearchModal').data('stageId', stageId).modal('show'); +} + +/** + * 결재자 삭제 및 단계 초기화 + * @function + * @param {string} stageId - 결재 단계 ID + */ +function resetApproval(stageId) { + const rowIndex = { + approval1: 0, + approval2: 1, + approval3: 2 + }[stageId]; + + const tbody = document.getElementById("approvalLineTbody"); + const label = ['검토 1', '검토 2', '결제'][rowIndex]; + + tbody.rows[rowIndex].innerHTML = ` + ${label} + + + + `; +} diff --git a/src/main/resources/static/cmn/js/bizTrip/reg/init.js b/src/main/resources/static/cmn/js/bizTrip/reg/init.js new file mode 100644 index 0000000..665c22e --- /dev/null +++ b/src/main/resources/static/cmn/js/bizTrip/reg/init.js @@ -0,0 +1,26 @@ + +$(function () { + + /** + * 시작 시간 선택기 초기화 + * @function + */ + $('#startTimePicker').datetimepicker({ + format: 'HH:mm', + stepping: 10 + }); + + /** + * 종료 시간 선택기 초기화 + * @function + */ + $('#endTimePicker').datetimepicker({ + format: 'HH:mm', + stepping: 10, + icons: { + time: 'far fa-clock', + up: 'fas fa-chevron-up', + down: 'fas fa-chevron-down' + } + }); +}); \ No newline at end of file diff --git a/src/main/resources/static/cmn/js/bizTrip/service.js b/src/main/resources/static/cmn/js/bizTrip/reg/service.js similarity index 70% rename from src/main/resources/static/cmn/js/bizTrip/service.js rename to src/main/resources/static/cmn/js/bizTrip/reg/service.js index 6ec90f1..3808d0c 100644 --- a/src/main/resources/static/cmn/js/bizTrip/service.js +++ b/src/main/resources/static/cmn/js/bizTrip/reg/service.js @@ -46,7 +46,25 @@ function collectAndSubmitTripData() { method: 'POST', contentType: 'application/json', data: JSON.stringify(payload), - success: () => fn_successAlert("등록 성공", "출장 정보가 저장되었습니다."), + success: function(data) { + + Swal.fire({ + title: "출장 정보가 저장되었습니다.", + text: '상세페이지로 이동하시겠습니까?', + icon: 'success', + showCancelButton: true, + confirmButtonText: '상세', + cancelButtonText: '목록' + }).then((result) => { + if (result.isConfirmed) { + location.href = "/itn/bizTrip/detail/"+data.data; + } else if (result.isDismissed) { + location.href = "/itn/bizTrip/list"; + } + }); + }, + + error: () => fn_failedAlert("등록 실패", "오류가 발생했습니다.") }); } \ No newline at end of file diff --git a/src/main/resources/static/cmn/js/bizTrip/reg/validation.js b/src/main/resources/static/cmn/js/bizTrip/reg/validation.js new file mode 100644 index 0000000..acffa11 --- /dev/null +++ b/src/main/resources/static/cmn/js/bizTrip/reg/validation.js @@ -0,0 +1,65 @@ +/** + * 출장 등록 입력값 유효성 검사 함수 + * - 필수 항목 누락 여부 확인 + * - 출장일자가 오늘 이전인지 확인 + * - 시작 시간이 종료 시간보다 늦은지 확인 + * + * @returns {boolean} 유효하면 true, 유효하지 않으면 false + */ +function validateTripForm() { + // ===== [1] 입력값 가져오기 ===== + const tripType = $('#tripType'); + const location = $('#tripLocation'); + const locationTxt = $('#locationTxt'); + const purpose = $('#purpose'); + const move = $('#tripMove'); + const date = $('#tripDate'); + const start = $('#startTimePicker input'); + const end = $('#endTimePicker input'); + const approver = $('#approver3'); + + const tripTypeCd = tripType.val(); + const locationCd = location.val(); + const locationTxtVal = locationTxt.val(); + const purposeVal = purpose.val(); + const moveCd = move.val(); + const tripDt = date.val(); + const startTime = start.val(); + const endTime = end.val(); + const approver3 = approver.val(); + + // ===== [2] 필수값 체크 + focus ===== + if (!tripTypeCd) return fn_failedAlert("입력 오류", "출장 구분을 선택해주세요.", 1000), tripType.focus(), false; + if (!locationCd) return fn_failedAlert("입력 오류", "출장지를 선택해주세요.", 1000), location.focus(), false; + if (!locationTxtVal || locationTxtVal.trim() === "") return fn_failedAlert("입력 오류", "목적지를 입력해주세요.", 1000), locationTxt.focus(), false; + if (!purposeVal || purposeVal.trim() === "") return fn_failedAlert("입력 오류", "출장 목적을 입력해주세요.", 1000), purpose.focus(), false; + if (!moveCd) return fn_failedAlert("입력 오류", "이동 수단을 선택해주세요.", 1000), move.focus(), false; + if (!tripDt) return fn_failedAlert("입력 오류", "출장일자를 선택해주세요.", 1000), date.focus(), false; + if (!startTime || !endTime) return fn_failedAlert("입력 오류", "시작/종료 시간을 입력해주세요.", 1000), start.focus(), false; + if (!approver3) return fn_failedAlert("입력 오류", "최종 결재자를 지정해주세요.", 1000), approver.focus(), false; + + // ===== [3] 출장일자가 오늘보다 과거일 경우 ===== + const today = new Date(); + const inputDate = new Date(tripDt); + today.setHours(0, 0, 0, 0); + inputDate.setHours(0, 0, 0, 0); + if (inputDate < today) { + fn_failedAlert("입력 오류", "출장일자는 오늘보다 빠를 수 없습니다."); + date.focus(); + return false; + } + + // ===== [4] 시간 순서 확인 (종료 > 시작) ===== + const [sH, sM] = startTime.split(':').map(Number); + const [eH, eM] = endTime.split(':').map(Number); + const startDate = new Date(); startDate.setHours(sH, sM, 0, 0); + const endDate = new Date(); endDate.setHours(eH, eM, 0, 0); + if (endDate <= startDate) { + fn_failedAlert("입력 오류", "종료 시간은 시작 시간보다 이후여야 합니다."); + end.focus(); + return false; + } + + // ===== [5] 유효성 통과 ===== + return true; +} diff --git a/src/main/resources/templates/accessDenied.html b/src/main/resources/templates/accessDenied.html index d810732..a3731a6 100644 --- a/src/main/resources/templates/accessDenied.html +++ b/src/main/resources/templates/accessDenied.html @@ -6,8 +6,10 @@ layout:decorate="layout"> - 401 + + 401 + diff --git a/src/main/resources/templates/dashboard/index.html b/src/main/resources/templates/dashboard/index.html index c4ccd74..829c588 100644 --- a/src/main/resources/templates/dashboard/index.html +++ b/src/main/resources/templates/dashboard/index.html @@ -111,512 +111,52 @@
-
- -
-
-

- - Sales -

-
- +
+
+
+
+
+ + 내 결재 대기 출장 +
-
-
-
- -
- -
-
- -
+
+ + + + + + + + + + + + + + + + + + + + +
출장일자목적지목적상태
결재할 출장 없음
+ + +
-
-
- - - -
-
-

Direct Chat

- -
- 3 - - - -
-
- -
- -
- -
-
- Alexander Pierce - 23 Jan 2:00 pm -
- - message user image - -
- Is this template really for free? That's unbelievable! -
- -
- - - -
-
- Sarah Bullock - 23 Jan 2:05 pm -
- - message user image - -
- You better believe it! -
- -
- - - -
-
- Alexander Pierce - 23 Jan 5:37 pm -
- - message user image - -
- Working with AdminLTE on a great new app! Wanna join? -
- -
- - - -
-
- Sarah Bullock - 23 Jan 6:10 pm -
- - message user image - -
- I would love to. -
- -
- - -
- - - - - -
- - - -
- - - -
-
-

- - To Do List -

- -
- -
-
- -
-
    -
  • - - - - - - -
    - - -
    - - Design a nice theme - - 2 mins - -
    - - -
    -
  • -
  • - - - - -
    - - -
    - Make the theme responsive - 4 hours -
    - - -
    -
  • -
  • - - - - -
    - - -
    - Let theme shine like a star - 1 day -
    - - -
    -
  • -
  • - - - - -
    - - -
    - Let theme shine like a star - 3 days -
    - - -
    -
  • -
  • - - - - -
    - - -
    - Check your messages and notifications - 1 week -
    - - -
    -
  • -
  • - - - - -
    - - -
    - Let theme shine like a star - 1 month -
    - - -
    -
  • -
-
- -
- -
- - -
- -
-
-

- - Visitors -

- -
- - -
- -
-
-
-
- - -
- - -
-
-

- - Sales Graph -

-
- - -
-
-
- -
- - - -
- - - -
-
- -

- - Calendar -

- -
- - - - -
- -
- -
- -
-
- -
-
diff --git a/src/main/resources/templates/fragments/header.html b/src/main/resources/templates/fragments/header.html index 7b70e42..c1754ea 100644 --- a/src/main/resources/templates/fragments/header.html +++ b/src/main/resources/templates/fragments/header.html @@ -1,6 +1,6 @@ - - + + @@ -37,6 +37,16 @@ + + + + + + + + + + @@ -78,6 +88,8 @@ + + - + + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/mainsidebar.html b/src/main/resources/templates/fragments/mainsidebar.html index 42d68fb..f96eb01 100644 --- a/src/main/resources/templates/fragments/mainsidebar.html +++ b/src/main/resources/templates/fragments/mainsidebar.html @@ -92,11 +92,11 @@ diff --git a/src/main/resources/templates/fragments/top_nav.html b/src/main/resources/templates/fragments/top_nav.html index aea51e2..2544e69 100644 --- a/src/main/resources/templates/fragments/top_nav.html +++ b/src/main/resources/templates/fragments/top_nav.html @@ -136,12 +136,22 @@ - + diff --git a/src/main/resources/templates/itn/bizTrip/edit.html b/src/main/resources/templates/itn/bizTrip/edit.html new file mode 100644 index 0000000..ed2e619 --- /dev/null +++ b/src/main/resources/templates/itn/bizTrip/edit.html @@ -0,0 +1,334 @@ + + + + + 출장 수정 + + + + + + + + +
+
+ +
+
+
+
+
+

출장 수정

+
+
+
+
+
+
+
+
출장정보
+
+ + + + + + + + + + + + + + + + + + + + + +
출장구분 + + 출장지 + + +
출장목적이동사항 + +
출장일자시간 +
+
+ +
+
+
+
+ ~ +
+ +
+
+
+
+
+
+ + + +
+
출장 인원
+
+ + + + + + + + + + + + + + +
이름부서명연락처 + +
+ + + + + + + 삭제 + +
+
+
+ + +
+
+ 결제라인 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
결재 단계이름부서명연락처관리
결재 단계이름부서연락처 + + 삭제 + + + +
+
+
+ +
+ +
+
+
+
+
+
+ + + + + + + +
+
+ + + + +
+ + + + + + + + diff --git a/src/main/resources/templates/itn/bizTrip/list.html b/src/main/resources/templates/itn/bizTrip/list.html index 88c2c64..47fdfda 100644 --- a/src/main/resources/templates/itn/bizTrip/list.html +++ b/src/main/resources/templates/itn/bizTrip/list.html @@ -5,15 +5,13 @@ xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="layout"> - - 사용자 관리 - - - - + + 출장 목록 + + + @@ -42,12 +41,12 @@
-

사용자 관리

+

출장 목록

@@ -80,46 +79,54 @@ 목적 이동수단 상태 - 결재상태 - 작성자 + + 결재대기자 + 기안자 + 관리 - - - - - - - - - - - - - 대기 - 진행 - 승인 - 반려 - - - - - - - 대기 - 진행 - 승인 - 반려 - - - - - - + + + + + + + + + + + + + 대기 + 진행 + 승인 + 반려 + - + + + + - + + +
+ 수정 + +
+ +
+
@@ -207,7 +214,7 @@ exportOptions: commonExportOptions }, "colvis"] - }).buttons().container().appendTo('#commuteTb_wrapper .col-md-6:eq(0)'); + }).buttons().container().appendTo('#tripTb_wrapper .col-md-6:eq(0)'); diff --git a/src/main/resources/templates/itn/bizTrip/reg.html b/src/main/resources/templates/itn/bizTrip/reg.html index e5ca5de..98937f7 100644 --- a/src/main/resources/templates/itn/bizTrip/reg.html +++ b/src/main/resources/templates/itn/bizTrip/reg.html @@ -10,9 +10,12 @@ - - - + + 출장 등록 + + + + + @@ -68,13 +72,8 @@
- - -
-
- 출장정보 -
+
출장정보
@@ -146,7 +145,7 @@ - +
@@ -352,10 +351,10 @@
- - - - + + + + diff --git a/src/main/resources/templates/itn/bizTrip/tripDetail.html b/src/main/resources/templates/itn/bizTrip/tripDetail.html new file mode 100644 index 0000000..c63e43e --- /dev/null +++ b/src/main/resources/templates/itn/bizTrip/tripDetail.html @@ -0,0 +1,260 @@ + + + + + + 출장 상세 + + + + + + + + +
+
+ +
+
+
+
+
+

출장 상세

+
+
+ +
+
+
+
+ +
+
+
+
출장정보
+
+ + + + + + + + + + + + + + + + + +
출장구분출장지 + - + +
출장목적 + 이동사항 +
출장일자 + 시간 +
+ +
+
출장 인원
+
+ + + + + + + + + + + + + +
이름부서명연락처역할
+ + + +
+
+
+ +
+
결제라인
+
+ + + + + + + + + + + + + + + + + +
결재 단계이름부서명(직급)연락처상태
결제검토 + + + + + 대기 + + +
+ + + +
+ + +
+ 승인 + + + + + +
+ + +
+ 반려 + + + + +
+ + + - +
+
+
+ + +
+
+
+
+
+
+ + + + + diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html index 3db0c60..5a7c582 100644 --- a/src/main/resources/templates/layout.html +++ b/src/main/resources/templates/layout.html @@ -1,8 +1,21 @@ - - - - + + + + + ITN ADMIN + + + + + + + + + + + diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index 723c371..c3d0aba 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -74,7 +74,7 @@
- +