share job

This commit is contained in:
JAE SIK CHO
2026-04-09 11:12:12 +09:00
commit f8427ee1d0
193 changed files with 23830 additions and 0 deletions

10
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
.gradle/
build/
*.class
*.jar
!gradle/wrapper/gradle-wrapper.jar
.idea/
*.iml
out/
.env
application-local.yml

9
backend/Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM openjdk:21-jdk-slim AS builder
WORKDIR /app
COPY . .
RUN ./gradlew bootJar -x test
FROM openjdk:21-jdk-slim
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

92
backend/build.gradle Normal file
View File

@@ -0,0 +1,92 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.1'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.company'
version = '1.0.0-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', '2024.0.0')
set('mybatisVersion', '3.0.4')
set('jjwtVersion', '0.12.6')
}
dependencies {
// Web
implementation 'org.springframework.boot:spring-boot-starter-web'
// Actuator (헬스체크)
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
// JWT
implementation "io.jsonwebtoken:jjwt-api:${jjwtVersion}"
runtimeOnly "io.jsonwebtoken:jjwt-impl:${jjwtVersion}"
runtimeOnly "io.jsonwebtoken:jjwt-jackson:${jjwtVersion}"
// MyBatis
implementation "org.mybatis.spring.boot:mybatis-spring-boot-starter:${mybatisVersion}"
// MS SQL Server JDBC (현재 사용) - 9.4.1: encrypt=false 시 TLS 미사용 (TLS1.0 서버 호환)
runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc:9.4.1.jre11'
// MariaDB JDBC (추후 전환용)
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// RabbitMQ
implementation 'org.springframework.boot:spring-boot-starter-amqp'
// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// Excel (Apache POI)
implementation 'org.apache.poi:poi-ooxml:5.3.0'
// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
bootRun {
args '--spring.profiles.active=mssql,local'
jvmArgs "-Djava.security.properties=${projectDir}/tls-local.security"
}

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
backend/gradlew vendored Normal file
View File

@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
backend/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
backend/settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'gw-backend'

View File

@@ -0,0 +1,16 @@
package com.company.gw;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@EnableCaching
public class GwBackendApplication {
public static void main(String[] args) {
SpringApplication.run(GwBackendApplication.class, args);
}
}

View File

@@ -0,0 +1,115 @@
package com.company.gw.board.controller;
import com.company.gw.board.service.BoardService;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/board")
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
/** 게시물 목록 */
@GetMapping("/{untyBbsCd}")
public ApiResponse<Map<String, Object>> getPostList(
@PathVariable String untyBbsCd,
@RequestParam(defaultValue = "") String searchText,
@RequestParam(defaultValue = "1") int pageNo,
@RequestParam(defaultValue = "20") int pageSize,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(
boardService.getPostList(currentUser.getCorpNo(), untyBbsCd, searchText, pageNo, pageSize));
}
/** 게시물 상세 (조회수 증가) */
@GetMapping("/{untyBbsCd}/{untyBbsSno}")
public ApiResponse<Map<String, Object>> getPostInfo(
@PathVariable String untyBbsCd,
@PathVariable Long untyBbsSno,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(
boardService.getPostInfoForView(currentUser.getCorpNo(), untyBbsCd, untyBbsSno));
}
/** 게시물 상세 (수정용, 조회수 미증가) */
@GetMapping("/{untyBbsCd}/{untyBbsSno}/edit")
public ApiResponse<Map<String, Object>> getPostInfoForEdit(
@PathVariable String untyBbsCd,
@PathVariable Long untyBbsSno,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(
boardService.getPostInfoForEdit(currentUser.getCorpNo(), untyBbsCd, untyBbsSno));
}
/** 게시물 등록 */
@PostMapping("/{untyBbsCd}")
public ApiResponse<Map<String, Object>> insertPost(
@PathVariable String untyBbsCd,
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
body.put("untyBbsCd", untyBbsCd);
return ApiResponse.ok(boardService.insertPost(currentUser.getCorpNo(), body));
}
/** 게시물 수정 */
@PutMapping("/{untyBbsCd}/{untyBbsSno}")
public ApiResponse<Void> updatePost(
@PathVariable String untyBbsCd,
@PathVariable Long untyBbsSno,
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
body.put("untyBbsCd", untyBbsCd);
boardService.updatePost(currentUser.getCorpNo(), untyBbsSno, body);
return ApiResponse.ok(null);
}
/** 게시물 삭제 */
@DeleteMapping("/{untyBbsCd}/{untyBbsSno}")
public ApiResponse<Void> deletePost(
@PathVariable String untyBbsCd,
@PathVariable Long untyBbsSno,
@AuthenticationPrincipal CurrentUser currentUser) {
boardService.deletePost(currentUser.getCorpNo(), untyBbsCd, untyBbsSno);
return ApiResponse.ok(null);
}
/** 댓글 목록 */
@GetMapping("/{untyBbsCd}/{untyBbsSno}/comments")
public ApiResponse<List<Map<String, Object>>> getCommentList(
@PathVariable String untyBbsCd,
@PathVariable Long untyBbsSno,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(
boardService.getCommentList(currentUser.getCorpNo(), untyBbsCd, untyBbsSno));
}
/** 댓글 등록 */
@PostMapping("/{untyBbsCd}/{untyBbsSno}/comments")
public ApiResponse<Void> insertComment(
@PathVariable String untyBbsCd,
@PathVariable Long untyBbsSno,
@RequestBody Map<String, String> body,
@AuthenticationPrincipal CurrentUser currentUser) {
boardService.insertComment(currentUser.getCorpNo(), untyBbsCd, untyBbsSno, body.get("cmmtCn"));
return ApiResponse.ok(null);
}
/** 댓글 삭제 */
@DeleteMapping("/{untyBbsCd}/{untyBbsSno}/comments/{cmmtSno}")
public ApiResponse<Void> deleteComment(
@PathVariable String untyBbsCd,
@PathVariable Long untyBbsSno,
@PathVariable Long cmmtSno,
@AuthenticationPrincipal CurrentUser currentUser) {
boardService.deleteComment(currentUser.getCorpNo(), untyBbsCd, untyBbsSno, cmmtSno);
return ApiResponse.ok(null);
}
}

View File

@@ -0,0 +1,40 @@
package com.company.gw.board.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface BoardMapper {
List<Map<String, Object>> getPostList(Map<String, Object> param);
long getPostListCount(Map<String, Object> param);
Map<String, Object> getPostInfo(Map<String, Object> param);
void insertPost(Map<String, Object> param);
void updatePost(Map<String, Object> param);
void increaseInqrCnt(Map<String, Object> param);
void updateEtcAtchNo(Map<String, Object> param);
void updatePhotoAtchNo(Map<String, Object> param);
void updateCmmtCnt(Map<String, Object> param);
void deletePost(Map<String, Object> param);
List<Map<String, Object>> getCommentList(Map<String, Object> param);
Map<String, Object> getCommentInfo(Map<String, Object> param);
void insertComment(Map<String, Object> param);
void deleteComment(Map<String, Object> param);
void deleteCommentsByPost(Map<String, Object> param);
}

View File

@@ -0,0 +1,228 @@
package com.company.gw.board.service;
import com.company.gw.board.mapper.BoardMapper;
import com.company.gw.common.exception.BizException;
import com.company.gw.common.service.AttachService;
import com.company.gw.common.service.SequenceService;
import com.company.gw.common.util.SecurityUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardMapper boardMapper;
private final SequenceService sequenceService;
private final AttachService attachService;
// ──────────────────────────────────────────────
// 게시물 목록
// ──────────────────────────────────────────────
public Map<String, Object> getPostList(String corpNo, String untyBbsCd,
String searchText, int pageNo, int pageSize) {
if (pageSize > 200) pageSize = 200;
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("untyBbsCd", untyBbsCd);
param.put("searchText", searchText);
param.put("pageNo", pageNo);
param.put("pageSize", pageSize);
List<Map<String, Object>> list = boardMapper.getPostList(param);
long total = boardMapper.getPostListCount(param);
Map<String, Object> result = new HashMap<>();
result.put("list", list);
result.put("total", total);
result.put("pageNo", pageNo);
result.put("pageSize", pageSize);
return result;
}
// ──────────────────────────────────────────────
// 게시물 상세 (조회수 증가 포함)
// ──────────────────────────────────────────────
@Transactional
public Map<String, Object> getPostInfoForView(String corpNo, String untyBbsCd, Long untyBbsSno) {
Map<String, Object> param = buildPostParam(corpNo, untyBbsCd, untyBbsSno);
boardMapper.increaseInqrCnt(param);
Map<String, Object> info = boardMapper.getPostInfo(param);
if (info == null) throw new BizException("게시물이 존재하지 않습니다.", HttpStatus.NOT_FOUND);
// 첨부파일 목록 조회
String etcAtchNo = (String) info.get("ETC_ATCH_NO");
String photoAtchNo = (String) info.get("PHOTO_ATCH_NO");
if (etcAtchNo != null) info.put("etcFileList", attachService.getFileList(etcAtchNo));
if (photoAtchNo != null) info.put("photoFileList", attachService.getFileList(photoAtchNo));
return info;
}
// 수정화면용 (조회수 증가 없음)
@Transactional(readOnly = true)
public Map<String, Object> getPostInfoForEdit(String corpNo, String untyBbsCd, Long untyBbsSno) {
Map<String, Object> info = boardMapper.getPostInfo(buildPostParam(corpNo, untyBbsCd, untyBbsSno));
if (info == null) throw new BizException("게시물이 존재하지 않습니다.", HttpStatus.NOT_FOUND);
String etcAtchNo = (String) info.get("ETC_ATCH_NO");
String photoAtchNo = (String) info.get("PHOTO_ATCH_NO");
if (etcAtchNo != null) info.put("etcFileList", attachService.getFileList(etcAtchNo));
if (photoAtchNo != null) info.put("photoFileList", attachService.getFileList(photoAtchNo));
return info;
}
// ──────────────────────────────────────────────
// 게시물 등록
// ──────────────────────────────────────────────
@Transactional
public Map<String, Object> insertPost(String corpNo, Map<String, Object> body) {
String usrId = SecurityUtil.getUsrId();
Integer sno = Integer.parseInt(
sequenceService.getNextSeqString("SX_GW0020.UNTY_BBS_SNO", 10));
Map<String, Object> param = new HashMap<>(body);
param.put("corpNo", corpNo);
param.put("usrId", usrId);
param.put("untyBbsSno", sno);
boardMapper.insertPost(param);
// 첨부 확정
String etcAtchNo = (String) body.get("etcAtchNo");
String photoAtchNo = (String) body.get("photoAtchNo");
if (etcAtchNo != null) {
param.put("etcAtchNo", etcAtchNo);
boardMapper.updateEtcAtchNo(param);
attachService.confirmAtchNo(etcAtchNo, "SX_GW0020.ETC_ATCH_NO");
}
if (photoAtchNo != null) {
param.put("photoAtchNo", photoAtchNo);
boardMapper.updatePhotoAtchNo(param);
attachService.confirmAtchNo(photoAtchNo, "SX_GW0020.PHOTO_ATCH_NO");
}
Map<String, Object> result = new HashMap<>();
result.put("untyBbsSno", sno);
return result;
}
// ──────────────────────────────────────────────
// 게시물 수정
// ──────────────────────────────────────────────
@Transactional
public void updatePost(String corpNo, Long untyBbsSno, Map<String, Object> body) {
String usrId = SecurityUtil.getUsrId();
Map<String, Object> param = buildPostParam(corpNo, (String) body.get("untyBbsCd"), untyBbsSno);
Map<String, Object> existing = boardMapper.getPostInfo(param);
if (existing == null) throw new BizException("게시물이 존재하지 않습니다.", HttpStatus.NOT_FOUND);
if (!usrId.equals(existing.get("CTUSR_ID"))) {
throw new BizException("작성자만 수정할 수 있습니다.", HttpStatus.FORBIDDEN);
}
param.putAll(body);
param.put("usrId", usrId);
boardMapper.updatePost(param);
// 첨부 처리 (새 파일이 업로드된 경우)
String etcAtchNo = (String) body.get("etcAtchNo");
if (etcAtchNo != null) {
param.put("etcAtchNo", etcAtchNo);
boardMapper.updateEtcAtchNo(param);
attachService.confirmAtchNo(etcAtchNo, "SX_GW0020.ETC_ATCH_NO");
}
}
// ──────────────────────────────────────────────
// 게시물 삭제
// ──────────────────────────────────────────────
@Transactional
public void deletePost(String corpNo, String untyBbsCd, Long untyBbsSno) {
String usrId = SecurityUtil.getUsrId();
Map<String, Object> param = buildPostParam(corpNo, untyBbsCd, untyBbsSno);
Map<String, Object> info = boardMapper.getPostInfo(param);
if (info == null) throw new BizException("게시물이 존재하지 않습니다.", HttpStatus.NOT_FOUND);
if (!usrId.equals(info.get("CTUSR_ID"))) {
throw new BizException("작성자만 삭제할 수 있습니다.", HttpStatus.FORBIDDEN);
}
boardMapper.deleteCommentsByPost(param);
boardMapper.deletePost(param);
// 첨부파일 삭제
String etcAtchNo = (String) info.get("ETC_ATCH_NO");
String photoAtchNo = (String) info.get("PHOTO_ATCH_NO");
if (etcAtchNo != null) attachService.deleteFilesByAtchNo(etcAtchNo, true);
if (photoAtchNo != null) attachService.deleteFilesByAtchNo(photoAtchNo, true);
}
// ──────────────────────────────────────────────
// 댓글
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getCommentList(String corpNo, String untyBbsCd, Long untyBbsSno) {
return boardMapper.getCommentList(buildPostParam(corpNo, untyBbsCd, untyBbsSno));
}
@Transactional
public void insertComment(String corpNo, String untyBbsCd, Long untyBbsSno, String cmmtCn) {
String usrId = SecurityUtil.getUsrId();
Integer cmmtSno = Integer.parseInt(
sequenceService.getNextSeqString("SX_GW0030.CMMT_SNO", 10));
Map<String, Object> param = buildPostParam(corpNo, untyBbsCd, untyBbsSno);
param.put("cmmtSno", cmmtSno);
param.put("cmmtCn", cmmtCn);
param.put("usrId", usrId);
boardMapper.insertComment(param);
boardMapper.updateCmmtCnt(param);
}
@Transactional
public void deleteComment(String corpNo, String untyBbsCd, Long untyBbsSno, Long cmmtSno) {
String usrId = SecurityUtil.getUsrId();
Map<String, Object> param = buildPostParam(corpNo, untyBbsCd, untyBbsSno);
param.put("cmmtSno", cmmtSno);
param.put("usrId", usrId);
Map<String, Object> info = boardMapper.getCommentInfo(param);
if (info == null) throw new BizException("댓글이 존재하지 않습니다.", HttpStatus.NOT_FOUND);
if (!usrId.equals(info.get("CMMT_CTUSR_ID"))) {
throw new BizException("작성자만 삭제할 수 있습니다.", HttpStatus.FORBIDDEN);
}
boardMapper.deleteComment(param);
boardMapper.updateCmmtCnt(param);
}
// ──────────────────────────────────────────────
// 내부 유틸
// ──────────────────────────────────────────────
private Map<String, Object> buildPostParam(String corpNo, String untyBbsCd, Long untyBbsSno) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("untyBbsCd", untyBbsCd);
p.put("untyBbsSno", untyBbsSno);
return p;
}
}

View File

@@ -0,0 +1,69 @@
package com.company.gw.common.config;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.util.Map;
/**
* MyBatis resultType="map" 은 DB 컬럼명을 그대로 Map 키로 반환합니다 (e.g. WORK_START_DT).
* 프론트엔드는 camelCase(e.g. workStartDt)를 기대하므로, JSON 직렬화 시 변환합니다.
* UPPER_CASE_SNAKE 패턴만 변환하고, 이미 camelCase인 키는 그대로 유지합니다.
*/
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer mapKeyCamelCaseCustomizer() {
return builder -> {
SimpleModule module = new SimpleModule();
module.addSerializer(Map.class, new MapCamelCaseSerializer());
builder.modules(module);
};
}
@SuppressWarnings({"rawtypes", "unchecked"})
static class MapCamelCaseSerializer extends JsonSerializer<Map> {
@Override
public void serialize(Map map, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
String key = entry.getKey() != null ? convertKey(entry.getKey().toString()) : "null";
gen.writeFieldName(key);
serializers.defaultSerializeValue(entry.getValue(), gen);
}
gen.writeEndObject();
}
/**
* UPPER_CASE_SNAKE 패턴인 경우에만 camelCase로 변환.
* 이미 camelCase 이거나 소문자인 키는 그대로 반환.
*/
private String convertKey(String key) {
// 모두 대문자+언더스코어 패턴인 경우만 변환
if (!key.matches("[A-Z][A-Z0-9_]*")) return key;
StringBuilder result = new StringBuilder();
boolean nextUpper = false;
for (int i = 0; i < key.length(); i++) {
char c = key.charAt(i);
if (c == '_') {
nextUpper = true;
} else if (nextUpper) {
result.append(Character.toUpperCase(c));
nextUpper = false;
} else {
result.append(Character.toLowerCase(c));
}
}
return result.toString();
}
}
}

View File

@@ -0,0 +1,53 @@
package com.company.gw.common.config;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@MapperScan("com.company.gw.**.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml")
);
// databaseId 벤더 매핑
// SQL Server → "mssql" (기본 구문은 databaseId 없거나 "mssql")
// MySQL → "mariadb" (MariaDB JDBC 2.x가 MySQL로 리포트)
// MariaDB → "mariadb" (MariaDB JDBC 3.x+)
VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
Properties vendorProps = new Properties();
vendorProps.setProperty("SQL Server", "mssql");
vendorProps.setProperty("MySQL", "mariadb");
vendorProps.setProperty("MariaDB", "mariadb");
databaseIdProvider.setProperties(vendorProps);
factoryBean.setDatabaseIdProvider(databaseIdProvider);
// application.yml의 mybatis.configuration이 수동 Bean 생성 시 무시되므로 직접 설정
org.apache.ibatis.session.Configuration mybatisConfig = new org.apache.ibatis.session.Configuration();
mybatisConfig.setMapUnderscoreToCamelCase(true);
mybatisConfig.setDefaultFetchSize(100);
mybatisConfig.setDefaultStatementTimeout(30);
factoryBean.setConfiguration(mybatisConfig);
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}

View File

@@ -0,0 +1,61 @@
package com.company.gw.common.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.Map;
@Slf4j
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
try {
// Redis 연결 가능 여부 확인
connectionFactory.getConnection().ping();
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
Map<String, RedisCacheConfiguration> cacheConfigs = Map.of(
"code", defaultConfig.entryTtl(Duration.ofHours(6)),
"workCd", defaultConfig.entryTtl(Duration.ofHours(6))
);
log.info("CacheManager: Redis 사용");
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(cacheConfigs)
.build();
} catch (Exception e) {
log.warn("Redis 연결 실패 → In-Memory CacheManager 사용 ({})", e.getMessage());
return new ConcurrentMapCacheManager("code", "workCd");
}
}
}

View File

@@ -0,0 +1,85 @@
package com.company.gw.common.config;
import com.company.gw.common.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/actuator/health").permitAll()
.anyRequest().authenticated()
)
.exceptionHandling(ex -> ex
// 인증 안 된 요청 → 401 (프론트 interceptor가 refresh 시도)
.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write("{\"message\":\"Unauthorized\"}");
})
// 인증은 됐지만 권한 없음 → 403
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write("{\"message\":\"Forbidden\"}");
})
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of("*"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}

View File

@@ -0,0 +1,133 @@
package com.company.gw.common.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.AttachDto;
import com.company.gw.common.exception.BizException;
import com.company.gw.common.service.AttachService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@RestController
@RequestMapping("/api/attach")
@RequiredArgsConstructor
public class AttachController {
private final AttachService attachService;
/** 단일 파일 업로드 */
@PostMapping("/upload")
public ApiResponse<AttachDto> upload(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "atchNo", required = false) String atchNo,
@RequestParam(value = "division", required = false) String division) {
return ApiResponse.ok(attachService.uploadFile(file, atchNo, division));
}
/** 다중 파일 업로드 */
@PostMapping("/upload/multi")
public ApiResponse<List<AttachDto>> uploadMulti(
@RequestParam("files") List<MultipartFile> files,
@RequestParam(value = "atchNo", required = false) String atchNo,
@RequestParam(value = "division", required = false) String division) {
return ApiResponse.ok(attachService.uploadFiles(files, atchNo, division));
}
/** 에디터 이미지 업로드 */
@PostMapping("/upload/image")
public ApiResponse<Map<String, String>> uploadImage(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "division", required = false) String division) {
return ApiResponse.ok(attachService.uploadEditorImage(file, division));
}
/** 첨부번호로 파일 목록 조회 */
@GetMapping("/list/{atchNo}")
public ApiResponse<List<Map<String, Object>>> getFileList(@PathVariable String atchNo) {
return ApiResponse.ok(attachService.getFileList(atchNo));
}
/** 단일 파일 다운로드 */
@GetMapping("/download/{atchfileNo}")
public ResponseEntity<Resource> download(@PathVariable String atchfileNo) {
Map<String, Object> info = attachService.getFileInfo(atchfileNo);
File file = attachService.getPhysicalFile(atchfileNo);
String fileName = (String) info.get("ATCH_FILE_NM");
String encodedName;
try {
encodedName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
.replace("+", "%20");
} catch (UnsupportedEncodingException e) {
encodedName = fileName;
}
String contentType = (String) info.getOrDefault("ATCH_TYPE_NM",
MediaType.APPLICATION_OCTET_STREAM_VALUE);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename*=UTF-8''" + encodedName)
.contentType(MediaType.parseMediaType(contentType))
.contentLength(file.length())
.body(new FileSystemResource(file));
}
/** 전체 파일 ZIP 다운로드 */
@GetMapping("/download-all/{atchNo}")
public ResponseEntity<byte[]> downloadAll(@PathVariable String atchNo) throws IOException {
List<Map<String, Object>> fileList = attachService.getFileList(atchNo);
if (fileList == null || fileList.isEmpty()) {
throw new BizException("다운로드할 파일이 없습니다.", HttpStatus.NOT_FOUND);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(baos, StandardCharsets.UTF_8)) {
for (Map<String, Object> info : fileList) {
String atchfileNo = (String) info.get("ATCHFILE_NO");
String fileName = (String) info.get("ATCH_FILE_NM");
try {
File physicalFile = attachService.getPhysicalFile(atchfileNo);
zos.putNextEntry(new ZipEntry(fileName != null ? fileName : atchfileNo));
try (FileInputStream fis = new FileInputStream(physicalFile)) {
fis.transferTo(zos);
}
zos.closeEntry();
} catch (Exception e) {
// 개별 파일 오류 시 건너뜀
}
}
}
String encodedName = URLEncoder.encode("첨부파일.zip", StandardCharsets.UTF_8)
.replace("+", "%20");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename*=UTF-8''" + encodedName)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(baos.toByteArray());
}
/** 파일 삭제 */
@DeleteMapping("/{atchfileNo}")
public ApiResponse<Void> delete(@PathVariable String atchfileNo) {
attachService.deleteFile(atchfileNo, false);
return ApiResponse.ok(null);
}
}

View File

@@ -0,0 +1,88 @@
package com.company.gw.common.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.dto.LoginRequestDto;
import com.company.gw.common.dto.LoginResponseDto;
import com.company.gw.common.dto.RefreshRequestDto;
import com.company.gw.common.service.AuthService;
import com.company.gw.common.util.JwtUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
private final JwtUtil jwtUtil;
/**
* 로그인
* POST /api/auth/login
*/
@PostMapping("/login")
public ResponseEntity<ApiResponse<LoginResponseDto>> login(
@Valid @RequestBody LoginRequestDto req) {
LoginResponseDto result = authService.login(req);
return ResponseEntity.ok(ApiResponse.ok(result));
}
/**
* 로그아웃
* POST /api/auth/logout
*/
@PostMapping("/logout")
public ResponseEntity<ApiResponse<Void>> logout(
@AuthenticationPrincipal UserDetails userDetails) {
authService.logout(userDetails.getUsername());
return ResponseEntity.ok(ApiResponse.ok(null));
}
/**
* Access Token 재발급
* POST /api/auth/refresh
*/
@PostMapping("/refresh")
public ResponseEntity<ApiResponse<String>> refresh(
@Valid @RequestBody RefreshRequestDto req) {
LoginResponseDto.UserInfo userInfo = authService.refreshToken(req.getRefreshToken());
String roleCode = (userInfo.getRoles() != null && !userInfo.getRoles().isEmpty())
? userInfo.getRoles().get(0) : "USER";
String newAccessToken = jwtUtil.generateAccessToken(userInfo.getUsrId(), userInfo.getCorpNo(), roleCode);
return ResponseEntity.ok(ApiResponse.ok(newAccessToken));
}
/**
* 현재 로그인 사용자 정보
* GET /api/auth/me
*/
@GetMapping("/me")
public ResponseEntity<ApiResponse<String>> me(
@AuthenticationPrincipal UserDetails userDetails) {
return ResponseEntity.ok(ApiResponse.ok(userDetails.getUsername()));
}
/**
* 비밀번호 변경
* POST /api/auth/change-pw
*/
@PostMapping("/change-pw")
public ResponseEntity<ApiResponse<Void>> changePassword(
@AuthenticationPrincipal CurrentUser currentUser,
@RequestBody Map<String, String> body) {
authService.changePassword(
currentUser.getUsrId(),
body.get("oldPw"),
body.get("newPw")
);
return ResponseEntity.ok(ApiResponse.ok(null));
}
}

View File

@@ -0,0 +1,78 @@
package com.company.gw.common.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.service.CodeService;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.CacheManager;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@RestController
@RequestMapping("/api/code")
@RequiredArgsConstructor
public class CodeController {
private final CodeService codeService;
private final CacheManager cacheManager;
/**
* 공통코드 목록 조회
* GET /api/code?commClCd=SX001&useYn=Y
*/
@GetMapping
public ApiResponse<List<Map<String, Object>>> getCodeList(
@RequestParam String commClCd,
@RequestParam(defaultValue = "") String useYn,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(codeService.getCodeList(currentUser.getCorpNo(), commClCd, useYn));
}
/**
* 공통코드 전체 정보 조회 (PROP_CD 포함)
*/
@GetMapping("/full")
public ApiResponse<List<Map<String, Object>>> getCodeListFull(
@RequestParam String commClCd,
@RequestParam(defaultValue = "") String useYn,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(codeService.getCodeListFull(currentUser.getCorpNo(), commClCd, useYn));
}
/**
* 직원 검색 (결재자 팝업 등)
* GET /api/code/users?searchText=홍&apprOnly=Y
*/
@GetMapping("/users")
public ApiResponse<List<Map<String, Object>>> searchUser(
@RequestParam(defaultValue = "") String searchText,
@RequestParam(defaultValue = "N") String apprOnly,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(codeService.searchUser(
currentUser.getCorpNo(), searchText, "Y".equals(apprOnly)));
}
/**
* 근무코드 목록 조회
*/
@GetMapping("/workcd")
public ApiResponse<List<Map<String, Object>>> getWorkCdList(
@RequestParam(defaultValue = "") String useYn,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(codeService.getWorkCdList(currentUser.getCorpNo(), useYn));
}
/**
* 캐시 초기화 (코드 변경 후 호출)
*/
@PostMapping("/cache/evict")
public ApiResponse<Void> evictCache() {
Objects.requireNonNull(cacheManager.getCache("code")).clear();
Objects.requireNonNull(cacheManager.getCache("workCd")).clear();
return ApiResponse.ok(null);
}
}

View File

@@ -0,0 +1,116 @@
package com.company.gw.common.controller;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.util.ExcelUtil;
import com.company.gw.tam.mapper.TamMapper;
import com.company.gw.wtime.mapper.WtimeMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Excel 내보내기 공통 컨트롤러
*/
@RestController
@RequestMapping("/api/excel")
@RequiredArgsConstructor
public class ExcelController {
private final WtimeMapper wtimeMapper;
private final TamMapper tamMapper;
/** 근무시간 목록 엑셀 */
@GetMapping("/wtime")
public ResponseEntity<byte[]> wtimeExcel(
@RequestParam Map<String, Object> params,
@AuthenticationPrincipal CurrentUser cu) throws IOException {
params.put("corpNo", cu.getCorpNo());
setDefault(params, "staYmd", "00000000");
setDefault(params, "endYmd", "99999999");
setDefault(params, "includeRetireYn", "N");
setDefault(params, "includeWorkYn", "N");
List<Map<String, Object>> data = wtimeMapper.getWtimeList(params);
return ExcelUtil.toExcel("근무시간",
new String[]{"날짜", "이름", "", "직위", "근무코드", "출근", "퇴근", "총근무(분)", "지각(분)", "OT(분)", "결근"},
new String[]{"WORK_PLAN_YYMMDD", "USR_NM", "TEAM_CD", "DUTY_CD",
"REAL_WORK_CD", "WORK_START_DT", "WORK_END_DT",
"TOT_WORK_MIN", "LATE_MIN", "OT_WORK_MIN", "ABTI_YN"},
data);
}
/** 월별 근무통계 엑셀 */
@GetMapping("/wstat")
public ResponseEntity<byte[]> wstatExcel(
@RequestParam Map<String, Object> params,
@AuthenticationPrincipal CurrentUser cu) throws IOException {
params.put("corpNo", cu.getCorpNo());
setDefault(params, "staYmd", "00000000");
setDefault(params, "endYmd", "99999999");
setDefault(params, "includeRetireYn", "N");
List<Map<String, Object>> data = wtimeMapper.getWstatList(params);
return ExcelUtil.toExcel("근무통계",
new String[]{"이름", "", "직위", "연차", "근무일", "결근", "지각일", "조퇴일", "총근무(분)", "지각(분)", "OT(분)"},
new String[]{"USR_NM", "TEAM_CD", "DUTY_CD", "YYVCT_CNT",
"WORK_DCNT", "ABTI_DCNT", "LATE_DCNT", "SKIPOFF_DCNT",
"TOT_WORK_MIN", "LATE_MIN", "OT_WORK_MIN"},
data);
}
/** 결재 신청 목록 엑셀 (TAM0020) */
@GetMapping("/tam-apvreq")
public ResponseEntity<byte[]> tamApvreqExcel(
@RequestParam Map<String, Object> params,
@AuthenticationPrincipal CurrentUser cu) throws IOException {
params.put("corpNo", cu.getCorpNo());
params.put("usrId", cu.getUsrId());
setDefault(params, "staYmd", "00000000");
setDefault(params, "endYmd", "99999999");
setDefault(params, "pageNo", 1);
setDefault(params, "pageSize", 10000);
List<Map<String, Object>> data = tamMapper.getApvreqList(params);
return ExcelUtil.toExcel("결재신청목록",
new String[]{"문서ID", "상태", "종류", "상신일", "완료일"},
new String[]{"APRVL_DOC_ID", "APRVL_STUS_CD", "APRVL_KIND_CD", "OFFER_DT", "CMPL_DT"},
data);
}
/** 근태 현황 엑셀 (TAM0040) */
@GetMapping("/tam-status")
public ResponseEntity<byte[]> tamStatusExcel(
@RequestParam Map<String, Object> params,
@AuthenticationPrincipal CurrentUser cu) throws IOException {
params.put("corpNo", cu.getCorpNo());
setDefault(params, "staYmd", "00000000");
setDefault(params, "endYmd", "99999999");
setDefault(params, "includeRetireYn", "N");
List<Map<String, Object>> data = tamMapper.getTamStatusList(params);
return ExcelUtil.toExcel("근태현황",
new String[]{"이름", "", "직위", "연차", "결재건수"},
new String[]{"USR_NM", "TEAM_CD", "DUTY_CD", "YYVCT_CNT", "APRVL_CNT"},
data);
}
private void setDefault(Map<String, Object> params, String key, Object defaultVal) {
if (!params.containsKey(key) || params.get(key) == null) {
params.put(key, defaultVal);
}
}
}

View File

@@ -0,0 +1,46 @@
package com.company.gw.common.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse<T> {
private boolean success;
private T data;
private String message;
private PaginationInfo pagination;
public static <T> ApiResponse<T> ok(T data) {
return ApiResponse.<T>builder()
.success(true)
.data(data)
.build();
}
public static <T> ApiResponse<T> ok(T data, PaginationInfo pagination) {
return ApiResponse.<T>builder()
.success(true)
.data(data)
.pagination(pagination)
.build();
}
public static <T> ApiResponse<T> fail(String message) {
return ApiResponse.<T>builder()
.success(false)
.message(message)
.build();
}
@Getter
@Builder
public static class PaginationInfo {
private int page;
private int size;
private long total;
}
}

View File

@@ -0,0 +1,16 @@
package com.company.gw.common.dto;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class AttachDto {
private String atchfileNo;
private String atchNo;
private String atchFilePathNm;
private String atchFileNm;
private String atchTypeNm;
private Long atchFileMg;
private String tblColNm;
}

View File

@@ -0,0 +1,12 @@
package com.company.gw.common.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ControllerRoleDto {
private String controller;
private String role;
private String funcAuthCn;
}

View File

@@ -0,0 +1,24 @@
package com.company.gw.common.dto;
import lombok.Builder;
import lombok.Getter;
import java.security.Principal;
/**
* 기존 Grails의 SS_CORP_NO, SS_USER_ID, ROLE_CD를 대체하는 현재 로그인 사용자 정보.
* JWT 클레임에서 추출하여 컨트롤러/서비스에서 사용.
* Principal 구현 → auth.getName()이 usrId를 반환하도록 보장.
*/
@Getter
@Builder
public class CurrentUser implements Principal {
private final String usrId;
private final String corpNo;
private final String roleCode;
@Override
public String getName() {
return usrId;
}
}

View File

@@ -0,0 +1,17 @@
package com.company.gw.common.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
@Getter
public class LoginRequestDto {
@NotBlank(message = "로그인 아이디를 입력하세요.")
private String loginId;
@NotBlank(message = "비밀번호를 입력하세요.")
private String loginPw;
@NotBlank(message = "권한을 선택하세요.")
private String menuAuthCd;
}

View File

@@ -0,0 +1,27 @@
package com.company.gw.common.dto;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
@Builder
public class LoginResponseDto {
private String accessToken;
private String refreshToken;
private UserInfo userInfo;
private List<MenuDto> menuList;
@Getter
@Builder
public static class UserInfo {
private String usrId;
private String usrNm;
private String loginId;
private String corpNo;
private String dutyCd;
private String roleCode; // 선택한 MENU_AUTH_CD
private List<String> roles; // 보유 전체 role 목록
}
}

View File

@@ -0,0 +1,18 @@
package com.company.gw.common.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MenuDto {
private String menuNo;
private String upperMenuNo;
private String menuNm;
private String url;
private String rm;
private String menuProp;
private Integer menuOrdr;
private String menuUseYn;
private Integer lvl;
}

View File

@@ -0,0 +1,11 @@
package com.company.gw.common.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
@Getter
public class RefreshRequestDto {
@NotBlank(message = "refreshToken이 필요합니다.")
private String refreshToken;
}

View File

@@ -0,0 +1,19 @@
package com.company.gw.common.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserDto {
private String corpNo;
private String usrId;
private String usrNm;
private String loginId;
@JsonIgnore
private String pw;
private String dutyCd;
@JsonIgnore
private String retirementDate;
}

View File

@@ -0,0 +1,20 @@
package com.company.gw.common.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
public class BizException extends RuntimeException {
private final HttpStatus status;
public BizException(String message) {
super(message);
this.status = HttpStatus.BAD_REQUEST;
}
public BizException(String message, HttpStatus status) {
super(message);
this.status = status;
}
}

View File

@@ -0,0 +1,42 @@
package com.company.gw.common.exception;
import com.company.gw.common.dto.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public ResponseEntity<ApiResponse<Void>> handleBizException(BizException e) {
log.warn("BizException: {}", e.getMessage());
return ResponseEntity
.status(e.getStatus())
.body(ApiResponse.fail(e.getMessage()));
}
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public ResponseEntity<ApiResponse<Void>> handleValidationException(BindException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
.findFirst()
.orElse("입력값이 올바르지 않습니다.");
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.fail(message));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException(Exception e) {
log.error("Unhandled exception", e);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.fail("서버 오류가 발생했습니다."));
}
}

View File

@@ -0,0 +1,71 @@
package com.company.gw.common.filter;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = resolveToken(request);
if (StringUtils.hasText(token) && jwtUtil.isValid(token)) {
try {
String usrId = jwtUtil.getUserId(token);
String corpNo = jwtUtil.getCorpNo(token);
String roleCode = jwtUtil.getRoleCode(token);
if (StringUtils.hasText(usrId)) {
CurrentUser currentUser = CurrentUser.builder()
.usrId(usrId)
.corpNo(corpNo)
.roleCode(roleCode)
.build();
var authorities = StringUtils.hasText(roleCode)
? List.of(new SimpleGrantedAuthority("ROLE_" + roleCode))
: List.<SimpleGrantedAuthority>of();
var authentication = new UsernamePasswordAuthenticationToken(
currentUser, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
log.debug("JWT 인증 실패 - 인증 없이 계속 진행: {}", e.getMessage());
SecurityContextHolder.clearContext();
}
}
filterChain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

View File

@@ -0,0 +1,24 @@
package com.company.gw.common.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface AttachMapper {
Map<String, Object> getFileInfo(String atchfileNo);
List<Map<String, Object>> getFileInfoListByAtchNo(String atchNo);
void insertFileInfo(Map<String, Object> param);
void deleteFileInfo(String atchfileNo);
void updateTblColNmByAtchNo(Map<String, Object> param);
void updateTblColNmByAtchfileNo(Map<String, Object> param);
List<Map<String, Object>> getNeedDeleteFileList();
}

View File

@@ -0,0 +1,18 @@
package com.company.gw.common.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface CodeMapper {
List<Map<String, Object>> getCodeList(Map<String, Object> param);
List<Map<String, Object>> getCodeListFull(Map<String, Object> param);
List<Map<String, Object>> searchUser(Map<String, Object> param);
List<Map<String, Object>> getWorkCdList(Map<String, Object> param);
}

View File

@@ -0,0 +1,12 @@
package com.company.gw.common.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface CommonMapper {
void increaseSequence(@Param("sequenceNm") String sequenceNm);
Long getSequenceVal(@Param("sequenceNm") String sequenceNm);
}

View File

@@ -0,0 +1,17 @@
package com.company.gw.common.mapper;
import com.company.gw.common.dto.ControllerRoleDto;
import com.company.gw.common.dto.MenuDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface MenuMapper {
List<MenuDto> getMenuList(@Param("corpNo") String corpNo,
@Param("menuAuthCd") String menuAuthCd);
List<ControllerRoleDto> getControllerRoleList(@Param("corpNo") String corpNo);
}

View File

@@ -0,0 +1,13 @@
package com.company.gw.common.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface SecurityMapper {
List<String> getUserRoleList(@Param("corpNo") String corpNo,
@Param("usrId") String usrId);
}

View File

@@ -0,0 +1,15 @@
package com.company.gw.common.mapper;
import com.company.gw.common.dto.UserDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
UserDto getUserInfo(@Param("usrId") String usrId);
UserDto getUserInfoWithPwd(@Param("loginId") String loginId);
int updatePassword(@Param("usrId") String usrId, @Param("newPw") String newPw);
}

View File

@@ -0,0 +1,226 @@
package com.company.gw.common.service;
import com.company.gw.common.dto.AttachDto;
import com.company.gw.common.exception.BizException;
import com.company.gw.common.mapper.AttachMapper;
import com.company.gw.common.util.SecurityUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Service
@RequiredArgsConstructor
public class AttachService {
private static final List<String> NOT_ALLOWED_EXT =
Arrays.asList("php", "jsp", "asp", "gsp", "sh", "exe", "bat");
private static final List<String> IMAGE_EXT =
Arrays.asList("jpg", "jpeg", "png", "gif", "tif", "tiff", "bmp", "webp");
private static final long MAX_FILE_SIZE = 30L * 1024 * 1024; // 30MB
private final AttachMapper attachMapper;
@Value("${file.upload-path}")
private String uploadPath;
// ──────────────────────────────────────────────
// 업로드
// ──────────────────────────────────────────────
@Transactional
public AttachDto uploadFile(MultipartFile file, String atchNo, String division) {
validateFile(file);
if (!org.springframework.util.StringUtils.hasText(atchNo)) {
atchNo = UUID.randomUUID().toString().replace("-", "");
}
String ext = FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase();
String atchfileNo = UUID.randomUUID().toString().replace("-", "");
String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String relativePath = (division != null ? division : "common") + "/" + today + "/" + atchfileNo + "." + ext;
File dest = new File(uploadPath, relativePath);
dest.getParentFile().mkdirs();
try {
file.transferTo(dest);
} catch (IOException e) {
throw new BizException("파일 저장 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
}
Map<String, Object> param = new HashMap<>();
param.put("atchfileNo", atchfileNo);
param.put("atchNo", atchNo);
param.put("atchFilePathNm", relativePath);
param.put("atchFileNm", file.getOriginalFilename());
param.put("atchTypeNm", file.getContentType());
param.put("atchFileMg", file.getSize());
param.put("tblColNm", null);
param.put("rgstrId", SecurityUtil.getUsrId());
attachMapper.insertFileInfo(param);
return AttachDto.builder()
.atchfileNo(atchfileNo)
.atchNo(atchNo)
.atchFilePathNm(relativePath)
.atchFileNm(file.getOriginalFilename())
.atchTypeNm(file.getContentType())
.atchFileMg(file.getSize())
.build();
}
@Transactional
public List<AttachDto> uploadFiles(List<MultipartFile> files, String atchNo, String division) {
if (!org.springframework.util.StringUtils.hasText(atchNo)) {
atchNo = UUID.randomUUID().toString().replace("-", "");
}
List<AttachDto> result = new ArrayList<>();
for (MultipartFile file : files) {
if (file != null && !file.isEmpty()) {
result.add(uploadFile(file, atchNo, division));
}
}
return result;
}
// 이미지 에디터용 업로드 (DB 저장 없이 파일만 저장)
public Map<String, String> uploadEditorImage(MultipartFile file, String division) {
String ext = FilenameUtils.getExtension(
Objects.requireNonNull(file.getOriginalFilename())).toLowerCase();
if (!IMAGE_EXT.contains(ext)) {
throw new BizException("이미지 파일만 업로드할 수 있습니다.");
}
if (file.getSize() > MAX_FILE_SIZE) {
throw new BizException("파일 크기가 초과되었습니다. (최대 30MB)");
}
String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String fileId = UUID.randomUUID().toString().replace("-", "");
String relativePath = (division != null ? division : "editor") + "/" + today + "/" + fileId + "." + ext;
File dest = new File(uploadPath + "/images", relativePath);
dest.getParentFile().mkdirs();
try {
file.transferTo(dest);
} catch (IOException e) {
throw new BizException("이미지 저장 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
}
Map<String, String> result = new HashMap<>();
result.put("url", "/api/attach/image/" + relativePath.replace("/", "_") + "." + ext);
return result;
}
// ──────────────────────────────────────────────
// 조회
// ──────────────────────────────────────────────
public Map<String, Object> getFileInfo(String atchfileNo) {
Map<String, Object> info = attachMapper.getFileInfo(atchfileNo);
if (info == null) {
throw new BizException("파일 정보가 존재하지 않습니다.", HttpStatus.NOT_FOUND);
}
return info;
}
public List<Map<String, Object>> getFileList(String atchNo) {
return attachMapper.getFileInfoListByAtchNo(atchNo);
}
public File getPhysicalFile(String atchfileNo) {
Map<String, Object> info = getFileInfo(atchfileNo);
File file = new File(uploadPath, (String) info.get("ATCH_FILE_PATH_NM"));
if (!file.exists()) {
throw new BizException("파일이 존재하지 않습니다.", HttpStatus.NOT_FOUND);
}
return file;
}
// ──────────────────────────────────────────────
// 삭제
// ──────────────────────────────────────────────
@Transactional
public void deleteFile(String atchfileNo, boolean forceDelete) {
Map<String, Object> info = getFileInfo(atchfileNo);
if (!forceDelete) {
String rgstrId = (String) info.get("RGSTR_ID");
if (!SecurityUtil.getUsrId().equals(rgstrId)) {
throw new BizException("등록자만 삭제할 수 있습니다.", HttpStatus.FORBIDDEN);
}
}
attachMapper.deleteFileInfo(atchfileNo);
// 물리 파일 삭제 (DB 성공 후)
new File(uploadPath, (String) info.get("ATCH_FILE_PATH_NM")).delete();
}
@Transactional
public void deleteFilesByAtchNo(String atchNo, boolean forceDelete) {
List<Map<String, Object>> list = attachMapper.getFileInfoListByAtchNo(atchNo);
for (Map<String, Object> info : list) {
deleteFile((String) info.get("ATCHFILE_NO"), forceDelete);
}
}
// ──────────────────────────────────────────────
// TBL_COL_NM 확정 처리 (게시판/결재 저장 완료 후 호출)
// ──────────────────────────────────────────────
@Transactional
public void confirmAtchNo(String atchNo, String tblColNm) {
if (!org.springframework.util.StringUtils.hasText(atchNo)) return;
Map<String, Object> param = new HashMap<>();
param.put("atchNo", atchNo);
param.put("tblColNm", tblColNm);
attachMapper.updateTblColNmByAtchNo(param);
}
// ──────────────────────────────────────────────
// 미확정 파일 정기 삭제 (6시간 이상 TBL_COL_NM 없는 파일)
// ──────────────────────────────────────────────
@Scheduled(cron = "0 0 3 * * *") // 매일 새벽 3시
@Transactional
public void cleanUpUnnecessaryFiles() {
List<Map<String, Object>> list = attachMapper.getNeedDeleteFileList();
for (Map<String, Object> item : list) {
try {
attachMapper.deleteFileInfo((String) item.get("ATCHFILE_NO"));
new File(uploadPath, (String) item.get("ATCH_FILE_PATH_NM")).delete();
} catch (Exception ignored) {
}
}
}
// ──────────────────────────────────────────────
// 검증
// ──────────────────────────────────────────────
private void validateFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new BizException("파일이 없습니다.");
}
String ext = FilenameUtils.getExtension(
Objects.requireNonNull(file.getOriginalFilename())).toLowerCase();
if (NOT_ALLOWED_EXT.contains(ext)) {
throw new BizException("업로드할 수 없는 확장자입니다. (" + ext + ")");
}
if (file.getSize() > MAX_FILE_SIZE) {
throw new BizException("파일 크기가 초과되었습니다. (최대 30MB)");
}
}
}

View File

@@ -0,0 +1,189 @@
package com.company.gw.common.service;
import com.company.gw.common.dto.*;
import com.company.gw.common.exception.BizException;
import com.company.gw.common.mapper.MenuMapper;
import com.company.gw.common.mapper.SecurityMapper;
import com.company.gw.common.mapper.UserMapper;
import com.company.gw.common.util.JwtUtil;
import com.company.gw.common.util.PasswordUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {
private static final String ROLE_ADMIN = "BIDS_0001";
private static final String ROLE_FEDEX = "FEDE";
private static final String ROLE_USER = "USER";
private static final String REFRESH_TOKEN_PREFIX = "refresh:";
private final UserMapper userMapper;
private final SecurityMapper securityMapper;
private final MenuMapper menuMapper;
private final JwtUtil jwtUtil;
private final RedisTemplate<String, String> redisTemplate;
/**
* 로그인
* 기존 IndexController.do_login() 로직과 동일
*/
public LoginResponseDto login(LoginRequestDto req) {
// 1. 사용자 조회 (PW 포함)
UserDto user = userMapper.getUserInfoWithPwd(req.getLoginId());
if (user == null) {
throw new BizException("로그인 아이디 또는 비밀번호를 확인하세요.");
}
// 2. 비밀번호 검증 (SHA-256 + Base64)
if (!PasswordUtil.matches(req.getLoginPw(), user.getPw())) {
throw new BizException("로그인 아이디 또는 비밀번호를 확인하세요.");
}
// 3. 퇴직자 체크
if (StringUtils.hasText(user.getRetirementDate())) {
throw new BizException("로그인 아이디 또는 비밀번호를 확인하세요.");
}
// 4. 보유 롤 조회
List<String> roles = securityMapper.getUserRoleList(user.getCorpNo(), user.getUsrId());
// 5. 선택한 MENU_AUTH_CD 보유 여부 검증
// 구 시스템(SecurityService.groovy) 로직과 동일:
// - FEDE 사용자이거나, USER 이외의 권한을 선택한 경우에만 DB 체크
// - USER 권한은 FEDE 사용자가 아니면 누구나 사용 가능 (기본값)
boolean isFedexUser = roles.contains(ROLE_FEDEX);
String menuAuthCd = req.getMenuAuthCd();
if (isFedexUser || !ROLE_USER.equals(menuAuthCd)) {
if (!roles.contains(menuAuthCd)) {
throw new BizException("해당 메뉴 권한이 없습니다.");
}
}
// 6. JWT 발급 (usrId + corpNo + roleCode 포함)
String accessToken = jwtUtil.generateAccessToken(user.getUsrId(), user.getCorpNo(), menuAuthCd);
String refreshToken = jwtUtil.generateRefreshToken(user.getUsrId());
// 7. Redis에 Refresh Token 저장 (7일) — Redis 미사용 환경에서는 건너뜀
try {
redisTemplate.opsForValue().set(
REFRESH_TOKEN_PREFIX + user.getUsrId(),
refreshToken,
7, TimeUnit.DAYS
);
} catch (Exception e) {
log.warn("Redis 미사용 환경: Refresh Token 저장 건너뜀 ({})", e.getMessage());
}
// 8. 메뉴 조회 - 선택한 역할 코드로 SX_CO0090 기반 필터링
List<MenuDto> menuList = menuMapper.getMenuList(user.getCorpNo(), menuAuthCd);
return LoginResponseDto.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.userInfo(LoginResponseDto.UserInfo.builder()
.usrId(user.getUsrId())
.usrNm(user.getUsrNm())
.loginId(user.getLoginId())
.corpNo(user.getCorpNo())
.dutyCd(user.getDutyCd())
.roleCode(menuAuthCd)
.roles(roles)
.build())
.menuList(menuList)
.build();
}
/**
* Access Token 재발급 (Refresh Token 검증)
*/
public LoginResponseDto.UserInfo refreshToken(String refreshToken) {
if (!jwtUtil.isValid(refreshToken)) {
throw new BizException("유효하지 않은 토큰입니다.", HttpStatus.UNAUTHORIZED);
}
String usrId = jwtUtil.getUserId(refreshToken);
try {
String stored = redisTemplate.opsForValue().get(REFRESH_TOKEN_PREFIX + usrId);
if (stored != null && !refreshToken.equals(stored)) {
throw new BizException("만료된 토큰입니다. 다시 로그인하세요.", HttpStatus.UNAUTHORIZED);
}
// stored == null: Redis 미사용 환경 → JWT 유효성만으로 통과
} catch (BizException e) {
throw e;
} catch (Exception e) {
log.warn("Redis 미사용 환경: Refresh Token 검증 건너뜀 ({})", e.getMessage());
}
// 사용자 정보 재조회
UserDto user = userMapper.getUserInfo(usrId);
if (user == null) {
throw new BizException("사용자 정보를 찾을 수 없습니다.", HttpStatus.UNAUTHORIZED);
}
List<String> roles = securityMapper.getUserRoleList(user.getCorpNo(), usrId);
return LoginResponseDto.UserInfo.builder()
.usrId(user.getUsrId())
.usrNm(user.getUsrNm())
.loginId(user.getLoginId())
.corpNo(user.getCorpNo())
.dutyCd(user.getDutyCd())
.roles(roles)
.build();
}
/**
* 로그아웃 - Redis에서 Refresh Token 삭제
*/
public void logout(String usrId) {
try {
redisTemplate.delete(REFRESH_TOKEN_PREFIX + usrId);
} catch (Exception e) {
log.warn("Redis 미사용 환경: 로그아웃 토큰 삭제 건너뜀 ({})", e.getMessage());
}
log.debug("로그아웃 처리 완료: {}", usrId);
}
/**
* 비밀번호 변경
*/
@org.springframework.transaction.annotation.Transactional
public void changePassword(String usrId, String oldPw, String newPw) {
UserDto user = userMapper.getUserInfo(usrId);
if (user == null) {
throw new BizException("사용자 정보를 찾을 수 없습니다.", HttpStatus.UNAUTHORIZED);
}
// 현재 비밀번호 확인 (loginId로 재조회하여 PW 포함)
UserDto userWithPw = userMapper.getUserInfoWithPwd(user.getLoginId());
if (userWithPw == null || !PasswordUtil.matches(oldPw, userWithPw.getPw())) {
throw new BizException("현재 비밀번호가 올바르지 않습니다.");
}
if (!StringUtils.hasText(newPw) || newPw.length() < 4) {
throw new BizException("새 비밀번호는 4자 이상이어야 합니다.");
}
userMapper.updatePassword(usrId, PasswordUtil.encode(newPw));
try {
redisTemplate.delete(REFRESH_TOKEN_PREFIX + usrId);
} catch (Exception e) {
log.warn("Redis 미사용 환경: 비밀번호 변경 후 토큰 삭제 건너뜀 ({})", e.getMessage());
}
}
/**
* 관리자 여부
*/
public boolean isAdmin(List<String> roles) {
return roles.contains(ROLE_ADMIN);
}
}

View File

@@ -0,0 +1,72 @@
package com.company.gw.common.service;
import com.company.gw.common.mapper.CodeMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 공통코드 조회 서비스
* - @Cacheable("code") 로 Redis 캐시 적용 (기본 TTL은 RedisConfig에서 설정)
* - 공통코드 변경 시 /api/code/cache/evict 호출로 캐시 초기화
*/
@Service
@RequiredArgsConstructor
public class CodeService {
private final CodeMapper codeMapper;
// ──────────────────────────────────────────────
// 공통코드 목록 (드롭다운용)
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
@Cacheable(value = "code", key = "#corpNo + ':' + #commClCd + ':' + #useYn")
public List<Map<String, Object>> getCodeList(String corpNo, String commClCd, String useYn) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("commClCd", commClCd);
p.put("useYn", useYn);
return codeMapper.getCodeList(p);
}
@Transactional(readOnly = true)
public List<Map<String, Object>> getCodeListFull(String corpNo, String commClCd, String useYn) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("commClCd", commClCd);
p.put("useYn", useYn);
return codeMapper.getCodeListFull(p);
}
// ──────────────────────────────────────────────
// 직원 검색 (결재자 선택 팝업 등)
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> searchUser(String corpNo, String searchText, boolean apprOnly) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("searchText", searchText);
p.put("apprOnly", apprOnly ? "Y" : "N");
return codeMapper.searchUser(p);
}
// ──────────────────────────────────────────────
// 근무코드 목록
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
@Cacheable(value = "workCd", key = "#corpNo + ':' + #useYn")
public List<Map<String, Object>> getWorkCdList(String corpNo, String useYn) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("useYn", useYn);
return codeMapper.getWorkCdList(p);
}
}

View File

@@ -0,0 +1,41 @@
package com.company.gw.common.service;
import com.company.gw.common.dto.UserDto;
import com.company.gw.common.mapper.SecurityMapper;
import com.company.gw.common.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserMapper userMapper;
private final SecurityMapper securityMapper;
@Override
public UserDetails loadUserByUsername(String usrId) throws UsernameNotFoundException {
UserDto user = userMapper.getUserInfo(usrId);
if (user == null) {
throw new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + usrId);
}
List<String> roles = securityMapper.getUserRoleList(user.getCorpNo(), usrId);
List<SimpleGrantedAuthority> authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList();
return User.builder()
.username(usrId)
.password("") // JWT 방식이므로 password 불필요
.authorities(authorities)
.build();
}
}

View File

@@ -0,0 +1,24 @@
package com.company.gw.common.service;
import com.company.gw.common.mapper.CommonMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 기존 CommonService.getNextSeqString() 대체.
* SX_CO0060 테이블의 MERGE 기반 시퀀스.
*/
@Service
@RequiredArgsConstructor
public class SequenceService {
private final CommonMapper commonMapper;
@Transactional
public String getNextSeqString(String sequenceNm, int padLength) {
commonMapper.increaseSequence(sequenceNm);
Long val = commonMapper.getSequenceVal(sequenceNm);
return String.format("%0" + padLength + "d", val);
}
}

View File

@@ -0,0 +1,86 @@
package com.company.gw.common.util;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
/**
* Excel 내보내기 유틸리티
*/
public class ExcelUtil {
private ExcelUtil() {}
/**
* Map 리스트를 Excel로 변환하여 ResponseEntity 반환
* @param fileName 다운로드 파일명 (확장자 없이)
* @param headers 컬럼 헤더 배열
* @param keys Map에서 읽을 키 배열 (headers와 1:1 대응)
* @param data 데이터 리스트
*/
public static ResponseEntity<byte[]> toExcel(String fileName,
String[] headers,
String[] keys,
List<Map<String, Object>> data) throws IOException {
try (Workbook wb = new XSSFWorkbook()) {
Sheet sheet = wb.createSheet("Sheet1");
// 헤더 스타일
CellStyle headerStyle = wb.createCellStyle();
Font headerFont = wb.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setBorderBottom(BorderStyle.THIN);
// 헤더 행
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
sheet.setColumnWidth(i, 4000);
}
// 데이터 행
int rowIdx = 1;
for (Map<String, Object> row : data) {
Row dataRow = sheet.createRow(rowIdx++);
for (int i = 0; i < keys.length; i++) {
Cell cell = dataRow.createCell(i);
Object val = row.get(keys[i]);
if (val == null) {
cell.setCellValue("");
} else if (val instanceof Number) {
cell.setCellValue(((Number) val).doubleValue());
} else {
cell.setCellValue(val.toString());
}
}
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
wb.write(baos);
String encodedName = URLEncoder.encode(fileName + ".xlsx", StandardCharsets.UTF_8)
.replace("+", "%20");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename*=UTF-8''" + encodedName)
.contentType(MediaType.parseMediaType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(baos.toByteArray());
}
}
}

View File

@@ -0,0 +1,82 @@
package com.company.gw.common.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Slf4j
@Component
public class JwtUtil {
private static final String CLAIM_CORP_NO = "corpNo";
private static final String CLAIM_ROLE_CODE = "roleCode";
private final SecretKey secretKey;
private final long accessTokenExpiry;
private final long refreshTokenExpiry;
public JwtUtil(
@Value("${jwt.secret}") String secret,
@Value("${jwt.access-token-expiry}") long accessTokenExpiry,
@Value("${jwt.refresh-token-expiry}") long refreshTokenExpiry) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.accessTokenExpiry = accessTokenExpiry;
this.refreshTokenExpiry = refreshTokenExpiry;
}
public String generateAccessToken(String usrId, String corpNo, String roleCode) {
return Jwts.builder()
.subject(usrId)
.claim(CLAIM_CORP_NO, corpNo)
.claim(CLAIM_ROLE_CODE, roleCode)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + accessTokenExpiry))
.signWith(secretKey)
.compact();
}
public String generateRefreshToken(String usrId) {
return Jwts.builder()
.subject(usrId)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + refreshTokenExpiry))
.signWith(secretKey)
.compact();
}
public String getUserId(String token) {
return getClaims(token).getSubject();
}
public String getCorpNo(String token) {
return (String) getClaims(token).get(CLAIM_CORP_NO);
}
public String getRoleCode(String token) {
return (String) getClaims(token).get(CLAIM_ROLE_CODE);
}
public boolean isValid(String token) {
try {
getClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
log.debug("Invalid JWT token: {}", e.getMessage());
return false;
}
}
private Claims getClaims(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
}

View File

@@ -0,0 +1,30 @@
package com.company.gw.common.util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* 기존 Grails EncryptUtil.get_enc_password() 와 동일한 방식
* SHA-256 → Base64 인코딩
* 기존 Oracle DB의 PW 컬럼 값과 호환 필수
*/
public class PasswordUtil {
private PasswordUtil() {}
public static String encode(String rawPassword) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(rawPassword.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-256 알고리즘을 찾을 수 없습니다.", e);
}
}
public static boolean matches(String rawPassword, String encodedPassword) {
return encode(rawPassword).equals(encodedPassword);
}
}

View File

@@ -0,0 +1,52 @@
package com.company.gw.common.util;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.exception.BizException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* JWT에서 현재 사용자 정보를 추출하는 유틸.
* 기존 CommonUtil.getLoginCorpNo(), getLoginUserID() 역할.
*/
public class SecurityUtil {
private SecurityUtil() {}
public static CurrentUser getCurrentUser(JwtUtil jwtUtil) {
String token = resolveToken();
if (!StringUtils.hasText(token) || !jwtUtil.isValid(token)) {
throw new BizException("인증 정보가 없습니다.", HttpStatus.UNAUTHORIZED);
}
return CurrentUser.builder()
.usrId(jwtUtil.getUserId(token))
.corpNo(jwtUtil.getCorpNo(token))
.roleCode(jwtUtil.getRoleCode(token))
.build();
}
public static String getUsrId() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.isAuthenticated()) {
throw new BizException("인증 정보가 없습니다.", HttpStatus.UNAUTHORIZED);
}
return auth.getName();
}
private static String resolveToken() {
ServletRequestAttributes attrs =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs == null) return null;
HttpServletRequest request = attrs.getRequest();
String bearer = request.getHeader("Authorization");
if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) {
return bearer.substring(7);
}
return null;
}
}

View File

@@ -0,0 +1,62 @@
package com.company.gw.envset.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.util.JwtUtil;
import com.company.gw.common.util.SecurityUtil;
import com.company.gw.envset.dto.CodeDto;
import com.company.gw.envset.dto.CodeIndexDto;
import com.company.gw.envset.service.CodeManageService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 환경설정 > 공통코드 관리 (Envset0020/0030)
* GET /api/envset/codes 코드인덱스 목록
* POST /api/envset/codes 코드인덱스 배치 저장
* GET /api/envset/codes/{commClCd} 코드 목록
* POST /api/envset/codes/{commClCd} 코드 배치 저장
*/
@RestController
@RequestMapping("/api/envset/codes")
@RequiredArgsConstructor
public class CodeManageController {
private final CodeManageService codeManageService;
private final JwtUtil jwtUtil;
@GetMapping
public ResponseEntity<ApiResponse<List<CodeIndexDto>>> getCdidxList(
@RequestParam(required = false) String searchText) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
return ResponseEntity.ok(ApiResponse.ok(codeManageService.getCdidxList(cu, searchText)));
}
@PostMapping
public ResponseEntity<ApiResponse<Void>> saveCdidxList(@RequestBody List<CodeIndexDto> list) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
codeManageService.saveCdidxList(cu, list);
return ResponseEntity.ok(ApiResponse.ok(null));
}
@GetMapping("/{commClCd}")
public ResponseEntity<ApiResponse<List<CodeDto>>> getCodeList(
@PathVariable String commClCd,
@RequestParam(required = false) String searchText) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
return ResponseEntity.ok(ApiResponse.ok(
codeManageService.getCodeList(cu, commClCd, searchText)));
}
@PostMapping("/{commClCd}")
public ResponseEntity<ApiResponse<Void>> saveCodeList(
@PathVariable String commClCd,
@RequestBody List<CodeDto> list) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
codeManageService.saveCodeList(cu, commClCd, list);
return ResponseEntity.ok(ApiResponse.ok(null));
}
}

View File

@@ -0,0 +1,89 @@
package com.company.gw.envset.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.util.JwtUtil;
import com.company.gw.common.util.SecurityUtil;
import com.company.gw.envset.dto.AuthMenuDto;
import com.company.gw.envset.dto.MenuManageDto;
import com.company.gw.envset.dto.UserAuthDto;
import com.company.gw.envset.service.MenuManageService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 환경설정 > 메뉴관리 / 사용자권한 (Envset0050)
*
* GET /api/envset/menus 메뉴 목록
* POST /api/envset/menus 메뉴 배치 저장
* GET /api/envset/menus/auth?menuAuthCd= 권한별 메뉴 목록
* POST /api/envset/menus/auth?menuAuthCd= 권한-메뉴 배치 저장
* GET /api/envset/menus/user-auth 사용자-권한 목록
* POST /api/envset/menus/user-auth 사용자-권한 배치 저장
* GET /api/envset/menus/search-user 사용자 검색 (autocomplete)
*/
@RestController
@RequestMapping("/api/envset/menus")
@RequiredArgsConstructor
public class MenuManageController {
private final MenuManageService menuManageService;
private final JwtUtil jwtUtil;
@GetMapping
public ResponseEntity<ApiResponse<List<MenuManageDto>>> getMenuList() {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
return ResponseEntity.ok(ApiResponse.ok(menuManageService.getMenuList(cu)));
}
@PostMapping
public ResponseEntity<ApiResponse<Void>> saveMenuList(@RequestBody List<MenuManageDto> list) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
menuManageService.saveMenuList(cu, list);
return ResponseEntity.ok(ApiResponse.ok(null));
}
@GetMapping("/auth")
public ResponseEntity<ApiResponse<List<AuthMenuDto>>> getAuthMenuList(
@RequestParam String menuAuthCd) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
return ResponseEntity.ok(ApiResponse.ok(
menuManageService.getAuthMenuList(cu, menuAuthCd)));
}
@PostMapping("/auth")
public ResponseEntity<ApiResponse<Void>> saveAuthMenuList(
@RequestParam String menuAuthCd,
@RequestBody List<AuthMenuDto> list) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
menuManageService.saveAuthMenuList(cu, menuAuthCd, list);
return ResponseEntity.ok(ApiResponse.ok(null));
}
@GetMapping("/user-auth")
public ResponseEntity<ApiResponse<List<UserAuthDto>>> getUserMauthList(
@RequestParam(required = false) String menuAuthCd,
@RequestParam(required = false) String usrNm) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
return ResponseEntity.ok(ApiResponse.ok(
menuManageService.getUserMauthList(cu, menuAuthCd, usrNm)));
}
@PostMapping("/user-auth")
public ResponseEntity<ApiResponse<Void>> saveUserMauthList(@RequestBody List<UserAuthDto> list) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
menuManageService.saveUserMauthList(cu, list);
return ResponseEntity.ok(ApiResponse.ok(null));
}
@GetMapping("/search-user")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> searchUser(
@RequestParam(required = false) String keyword) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
return ResponseEntity.ok(ApiResponse.ok(menuManageService.searchUser(cu, keyword)));
}
}

View File

@@ -0,0 +1,95 @@
package com.company.gw.envset.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.util.JwtUtil;
import com.company.gw.common.util.SecurityUtil;
import com.company.gw.envset.dto.UserDetailDto;
import com.company.gw.envset.dto.UserSaveDto;
import com.company.gw.envset.service.UserManageService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 환경설정 > 직원정보 (Envset0010)
* GET /api/envset/users 직원 목록 (페이징)
* GET /api/envset/users/{usrId} 직원 상세
* POST /api/envset/users 직원 등록
* PUT /api/envset/users/{usrId} 직원 수정
* DELETE /api/envset/users/{usrId} 직원 삭제
*/
@RestController
@RequestMapping("/api/envset/users")
@RequiredArgsConstructor
public class UserManageController {
private final UserManageService userManageService;
private final JwtUtil jwtUtil;
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getUserList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "100") int size,
@RequestParam(required = false) String usrNm,
@RequestParam(required = false) String teamCd,
@RequestParam(required = false) String dutyCd,
@RequestParam(required = false) String apprYn,
@RequestParam(defaultValue = "N") String includeRetireYn,
@RequestParam(defaultValue = "USR_ID") String userOrderBy) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
Map<String, Object> params = new HashMap<>();
params.put("page", page);
params.put("size", size);
params.put("usrNm", usrNm);
params.put("teamCd", teamCd);
params.put("dutyCd", dutyCd);
params.put("apprYn", apprYn);
params.put("includeRetireYn", includeRetireYn);
params.put("userOrderBy", userOrderBy);
Map<String, Object> result = userManageService.getUserList(cu, params);
long total = (long) result.get("total");
return ResponseEntity.ok(ApiResponse.ok(
result,
ApiResponse.PaginationInfo.builder().page(page).size(size).total(total).build()
));
}
@GetMapping("/{usrId}")
public ResponseEntity<ApiResponse<UserDetailDto>> getUserDetail(@PathVariable String usrId) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
return ResponseEntity.ok(ApiResponse.ok(userManageService.getUserDetail(cu, usrId)));
}
@PostMapping
public ResponseEntity<ApiResponse<Void>> insertUser(@Valid @RequestBody UserSaveDto dto) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
userManageService.insertUser(cu, dto);
return ResponseEntity.ok(ApiResponse.ok(null));
}
@PutMapping("/{usrId}")
public ResponseEntity<ApiResponse<Void>> updateUser(
@PathVariable String usrId,
@Valid @RequestBody UserSaveDto dto) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
dto.setUsrId(usrId);
userManageService.updateUser(cu, dto);
return ResponseEntity.ok(ApiResponse.ok(null));
}
@DeleteMapping("/{usrId}")
public ResponseEntity<ApiResponse<Void>> deleteUser(@PathVariable String usrId) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
userManageService.deleteUser(cu, usrId);
return ResponseEntity.ok(ApiResponse.ok(null));
}
}

View File

@@ -0,0 +1,58 @@
package com.company.gw.envset.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.util.JwtUtil;
import com.company.gw.common.util.SecurityUtil;
import com.company.gw.envset.service.WorkCdService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 환경설정 > 근무코드 관리 (Envset0040, SX_CO0070)
* GET /api/envset/workcd 목록
* POST /api/envset/workcd 등록
* PUT /api/envset/workcd/{workCd} 수정
* DELETE /api/envset/workcd/{workCd} 삭제
*/
@RestController
@RequestMapping("/api/envset/workcd")
@RequiredArgsConstructor
public class WorkCdController {
private final WorkCdService workCdService;
private final JwtUtil jwtUtil;
@GetMapping
public ApiResponse<List<Map<String, Object>>> getWorkCdList(
@RequestParam(defaultValue = "") String searchText) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
return ApiResponse.ok(workCdService.getWorkCdList(cu.getCorpNo(), searchText));
}
@PostMapping
public ApiResponse<Void> insertWorkCd(@RequestBody Map<String, Object> body) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
workCdService.insertWorkCd(cu.getCorpNo(), cu.getUsrId(), body);
return ApiResponse.ok(null);
}
@PutMapping("/{workCd}")
public ApiResponse<Void> updateWorkCd(
@PathVariable String workCd,
@RequestBody Map<String, Object> body) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
workCdService.updateWorkCd(cu.getCorpNo(), cu.getUsrId(), workCd, body);
return ApiResponse.ok(null);
}
@DeleteMapping("/{workCd}")
public ApiResponse<Void> deleteWorkCd(@PathVariable String workCd) {
CurrentUser cu = SecurityUtil.getCurrentUser(jwtUtil);
workCdService.deleteWorkCd(cu.getCorpNo(), workCd);
return ApiResponse.ok(null);
}
}

View File

@@ -0,0 +1,23 @@
package com.company.gw.envset.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class AuthMenuDto {
private String corpNo;
private String menuNo;
private String upperMenuNo;
private String menuNm;
private String url;
private String menuProp;
private Integer menuOrdr;
private String menuUseYn;
private Integer lvl;
private String menuAuthYn; // Y/N
private String menuAuthCd;
private String funcAuthCn;
private String rgstrId;
private String modId;
private String rowStatus; // U만 존재 (Y이면 merge, N이면 delete)
}

View File

@@ -0,0 +1,18 @@
package com.company.gw.envset.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class CodeDto {
private String corpNo;
private String commClCd;
private String commCd;
private String commCdNm;
private String commCdUseYn;
private String commCdDscrpt;
private Integer commCdDsplyOrdr;
private String rgstrId;
private String modId;
private String rowStatus; // I/U/D
}

View File

@@ -0,0 +1,18 @@
package com.company.gw.envset.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class CodeIndexDto {
private String corpNo;
private String commClCd;
private String commClCdNm;
private String commClCdUseYn;
private String commClCdDscrpt;
private Integer dsplyOrdr;
private String rgstrId;
private String modId;
// 배치 저장용 상태 플래그
private String rowStatus; // I/U/D
}

View File

@@ -0,0 +1,22 @@
package com.company.gw.envset.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class MenuManageDto {
private String corpNo;
private String menuNo;
private String upperMenuNo;
private String menuNm;
private String url;
private String rm;
private String menuProp;
private Integer menuOrdr;
private String menuUseYn;
private String controller;
private Integer menuLvl;
private String rgstrId;
private String modId;
private String rowStatus; // I/U/D
}

View File

@@ -0,0 +1,17 @@
package com.company.gw.envset.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class UserAuthDto {
private String corpNo;
private String usrId;
private String usrNm;
private String menuAuthCd;
private String teamCd;
private String dutyCd;
private String retirementDate;
private String rgstrId;
private String rowStatus; // I/D
}

View File

@@ -0,0 +1,31 @@
package com.company.gw.envset.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class UserDetailDto {
private String corpNo;
private String usrId;
private String usrSortOrdr;
private String usrNm;
private String loginId;
private String dutyCd;
private String brthDyDate;
private String usrTelno;
private String mtelNo;
private String email;
private String baseAdrs;
private String gusoAdrs;
private String gusoTelno;
private String joinCpDate;
private String teamCd;
private String dismlDate;
private String retirementDate;
private String spkltArtcCn;
private String photoAtchfileNo;
private String spmtEmail;
private String spmtMtelNo;
private String apprYn;
private String finalSchspNm;
}

View File

@@ -0,0 +1,15 @@
package com.company.gw.envset.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class UserListDto {
private String usrId;
private String usrNm;
private String teamCd;
private String dutyCd;
private String usrTelno;
private String mtelNo;
private String retirementDate;
}

View File

@@ -0,0 +1,39 @@
package com.company.gw.envset.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class UserSaveDto {
// 공통 (insert/update 구분용)
private String corpNo;
private String usrId;
private String rgstrId;
private String modId;
@NotBlank(message = "직원명은 필수입니다.")
private String usrNm;
private String loginId;
private String pw; // 평문 → 서비스에서 암호화
private String usrSortOrdr;
private String dutyCd;
private String brthDyDate;
private String usrTelno;
private String mtelNo;
private String email;
private String baseAdrs;
private String gusoAdrs;
private String gusoTelno;
private String joinCpDate;
private String teamCd;
private String dismlDate;
private String retirementDate;
private String spkltArtcCn;
private String spmtEmail;
private String spmtMtelNo;
private String rrno;
private String apprYn;
private String finalSchspNm;
}

View File

@@ -0,0 +1,25 @@
package com.company.gw.envset.mapper;
import com.company.gw.envset.dto.CodeDto;
import com.company.gw.envset.dto.CodeIndexDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface CodeManageMapper {
List<CodeIndexDto> getCdidxList(Map<String, Object> params);
CodeIndexDto getCdidxInfo(Map<String, Object> params);
void insertCdidx(CodeIndexDto dto);
void updateCdidx(CodeIndexDto dto);
void deleteCdidx(@Param("corpNo") String corpNo, @Param("commClCd") String commClCd);
List<CodeDto> getCodeList(Map<String, Object> params);
void insertCode(CodeDto dto);
void updateCode(CodeDto dto);
void deleteCode(@Param("corpNo") String corpNo,
@Param("commClCd") String commClCd,
@Param("commCd") String commCd);
}

View File

@@ -0,0 +1,33 @@
package com.company.gw.envset.mapper;
import com.company.gw.envset.dto.AuthMenuDto;
import com.company.gw.envset.dto.MenuManageDto;
import com.company.gw.envset.dto.UserAuthDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface MenuManageMapper {
List<MenuManageDto> getMenuList(@Param("corpNo") String corpNo);
void insertMenu(MenuManageDto dto);
void updateMenu(MenuManageDto dto);
void deleteMauthByMenu(@Param("corpNo") String corpNo, @Param("menuNo") String menuNo);
void deleteMenu(@Param("corpNo") String corpNo, @Param("menuNo") String menuNo);
List<AuthMenuDto> getAuthMenuList(Map<String, Object> params);
void mergeMenuAuth(AuthMenuDto dto);
void deleteMenuAuth(@Param("corpNo") String corpNo,
@Param("menuNo") String menuNo,
@Param("menuAuthCd") String menuAuthCd);
List<UserAuthDto> getUserMauthList(Map<String, Object> params);
void insertUserMauth(UserAuthDto dto);
void deleteUserMauth(@Param("corpNo") String corpNo,
@Param("usrId") String usrId,
@Param("menuAuthCd") String menuAuthCd);
List<Map<String, Object>> searchUser(Map<String, Object> params);
}

View File

@@ -0,0 +1,26 @@
package com.company.gw.envset.mapper;
import com.company.gw.envset.dto.UserDetailDto;
import com.company.gw.envset.dto.UserListDto;
import com.company.gw.envset.dto.UserSaveDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface UserManageMapper {
List<UserListDto> getUserList(Map<String, Object> params);
long getUserListCount(Map<String, Object> params);
UserDetailDto getUserDetail(Map<String, Object> params);
int getLoginIdCount(Map<String, Object> params);
void insertUser(UserSaveDto dto);
void updateUser(UserSaveDto dto);
void updateUserPw(@Param("corpNo") String corpNo,
@Param("usrId") String usrId,
@Param("pw") String pw,
@Param("modId") String modId);
void deleteUser(@Param("corpNo") String corpNo, @Param("usrId") String usrId);
void updateUserPhoto(Map<String, Object> params);
}

View File

@@ -0,0 +1,13 @@
package com.company.gw.envset.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface WorkCdMapper {
List<Map<String, Object>> getWorkCdList(Map<String, Object> param);
void insertWorkCd(Map<String, Object> param);
void updateWorkCd(Map<String, Object> param);
void deleteWorkCd(Map<String, Object> param);
}

View File

@@ -0,0 +1,71 @@
package com.company.gw.envset.service;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.exception.BizException;
import com.company.gw.envset.dto.CodeDto;
import com.company.gw.envset.dto.CodeIndexDto;
import com.company.gw.envset.mapper.CodeManageMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class CodeManageService {
private final CodeManageMapper codeManageMapper;
public List<CodeIndexDto> getCdidxList(CurrentUser cu, String searchText) {
return codeManageMapper.getCdidxList(Map.of(
"corpNo", cu.getCorpNo(),
"searchText", searchText != null ? searchText : ""
));
}
@Transactional
public void saveCdidxList(CurrentUser cu, List<CodeIndexDto> list) {
for (CodeIndexDto item : list) {
item.setCorpNo(cu.getCorpNo());
item.setRgstrId(cu.getUsrId());
item.setModId(cu.getUsrId());
switch (item.getRowStatus()) {
case "I" -> codeManageMapper.insertCdidx(item);
case "U" -> codeManageMapper.updateCdidx(item);
case "D" -> codeManageMapper.deleteCdidx(cu.getCorpNo(), item.getCommClCd());
default -> throw new BizException("잘못된 rowStatus: " + item.getRowStatus());
}
}
}
public List<CodeDto> getCodeList(CurrentUser cu, String commClCd, String searchText) {
if (!StringUtils.hasText(commClCd)) throw new BizException("코드분류가 필요합니다.");
return codeManageMapper.getCodeList(Map.of(
"corpNo", cu.getCorpNo(),
"commClCd", commClCd,
"searchText", searchText != null ? searchText : ""
));
}
@Transactional
public void saveCodeList(CurrentUser cu, String commClCd, List<CodeDto> list) {
if (!StringUtils.hasText(commClCd)) throw new BizException("코드분류가 필요합니다.");
for (CodeDto item : list) {
item.setCorpNo(cu.getCorpNo());
item.setCommClCd(commClCd);
item.setRgstrId(cu.getUsrId());
item.setModId(cu.getUsrId());
switch (item.getRowStatus()) {
case "I" -> codeManageMapper.insertCode(item);
case "U" -> codeManageMapper.updateCode(item);
case "D" -> codeManageMapper.deleteCode(cu.getCorpNo(), commClCd, item.getCommCd());
default -> throw new BizException("잘못된 rowStatus: " + item.getRowStatus());
}
}
}
}

View File

@@ -0,0 +1,102 @@
package com.company.gw.envset.service;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.exception.BizException;
import com.company.gw.envset.dto.AuthMenuDto;
import com.company.gw.envset.dto.MenuManageDto;
import com.company.gw.envset.dto.UserAuthDto;
import com.company.gw.envset.mapper.MenuManageMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class MenuManageService {
private final MenuManageMapper menuManageMapper;
public List<MenuManageDto> getMenuList(CurrentUser cu) {
return menuManageMapper.getMenuList(cu.getCorpNo());
}
@Transactional
public void saveMenuList(CurrentUser cu, List<MenuManageDto> list) {
for (MenuManageDto item : list) {
item.setCorpNo(cu.getCorpNo());
item.setRgstrId(cu.getUsrId());
item.setModId(cu.getUsrId());
switch (item.getRowStatus()) {
case "I" -> menuManageMapper.insertMenu(item);
case "U" -> menuManageMapper.updateMenu(item);
case "D" -> {
menuManageMapper.deleteMauthByMenu(cu.getCorpNo(), item.getMenuNo());
menuManageMapper.deleteMenu(cu.getCorpNo(), item.getMenuNo());
}
default -> throw new BizException("잘못된 rowStatus: " + item.getRowStatus());
}
}
}
public List<AuthMenuDto> getAuthMenuList(CurrentUser cu, String menuAuthCd) {
if (!StringUtils.hasText(menuAuthCd)) throw new BizException("권한코드가 필요합니다.");
return menuManageMapper.getAuthMenuList(Map.of(
"corpNo", cu.getCorpNo(),
"menuAuthCd", menuAuthCd
));
}
@Transactional
public void saveAuthMenuList(CurrentUser cu, String menuAuthCd, List<AuthMenuDto> list) {
if (!StringUtils.hasText(menuAuthCd)) throw new BizException("권한코드가 필요합니다.");
for (AuthMenuDto item : list) {
if (!"U".equals(item.getRowStatus())) continue;
item.setCorpNo(cu.getCorpNo());
item.setMenuAuthCd(menuAuthCd);
item.setRgstrId(cu.getUsrId());
item.setModId(cu.getUsrId());
if ("Y".equals(item.getMenuAuthYn())) {
menuManageMapper.mergeMenuAuth(item);
} else {
menuManageMapper.deleteMenuAuth(cu.getCorpNo(), item.getMenuNo(), menuAuthCd);
}
}
}
public List<UserAuthDto> getUserMauthList(CurrentUser cu, String menuAuthCd, String usrNm) {
Map<String, Object> params = new HashMap<>();
params.put("corpNo", cu.getCorpNo());
params.put("menuAuthCd", StringUtils.hasText(menuAuthCd) ? menuAuthCd : null);
params.put("usrNm", StringUtils.hasText(usrNm) ? usrNm : null);
return menuManageMapper.getUserMauthList(params);
}
@Transactional
public void saveUserMauthList(CurrentUser cu, List<UserAuthDto> list) {
for (UserAuthDto item : list) {
item.setCorpNo(cu.getCorpNo());
item.setRgstrId(cu.getUsrId());
switch (item.getRowStatus()) {
case "I" -> menuManageMapper.insertUserMauth(item);
case "D" -> menuManageMapper.deleteUserMauth(
cu.getCorpNo(), item.getUsrId(), item.getMenuAuthCd());
default -> throw new BizException("잘못된 rowStatus: " + item.getRowStatus());
}
}
}
public List<Map<String, Object>> searchUser(CurrentUser cu, String keyword) {
return menuManageMapper.searchUser(Map.of(
"corpNo", cu.getCorpNo(),
"keyword", keyword != null ? keyword : ""
));
}
}

View File

@@ -0,0 +1,117 @@
package com.company.gw.envset.service;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.common.exception.BizException;
import com.company.gw.common.service.SequenceService;
import com.company.gw.common.util.PasswordUtil;
import com.company.gw.envset.dto.UserDetailDto;
import com.company.gw.envset.dto.UserListDto;
import com.company.gw.envset.dto.UserSaveDto;
import com.company.gw.envset.mapper.UserManageMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class UserManageService {
private static final List<String> ALLOWED_ORDER_BY =
List.of("USR_ID", "USR_NM", "TEAM_CD", "DUTY_CD");
private final UserManageMapper userManageMapper;
private final SequenceService sequenceService;
public Map<String, Object> getUserList(CurrentUser cu, Map<String, Object> params) {
String orderBy = (String) params.getOrDefault("userOrderBy", "USR_ID");
if (!ALLOWED_ORDER_BY.contains(orderBy)) {
throw new BizException("정렬 조건이 올바르지 않습니다.");
}
params.put("corpNo", cu.getCorpNo());
params.put("userOrderBy", orderBy);
long total = userManageMapper.getUserListCount(params);
List<UserListDto> list = userManageMapper.getUserList(params);
Map<String, Object> result = new HashMap<>();
result.put("list", list);
result.put("total", total);
return result;
}
public UserDetailDto getUserDetail(CurrentUser cu, String usrId) {
Map<String, Object> params = Map.of("corpNo", cu.getCorpNo(), "usrId", usrId);
UserDetailDto detail = userManageMapper.getUserDetail(params);
if (detail == null) {
throw new BizException("사용자 정보가 없습니다.");
}
return detail;
}
@Transactional
public void insertUser(CurrentUser cu, UserSaveDto dto) {
if (!StringUtils.hasText(dto.getPw())) {
throw new BizException("비밀번호는 필수입니다.");
}
dto.setCorpNo(cu.getCorpNo());
dto.setRgstrId(cu.getUsrId());
// 시퀀스로 USR_ID 생성 (기존 getNextSeqString('SX_GW0010.USR_ID', 5))
String usrId = sequenceService.getNextSeqString("SX_GW0010.USR_ID", 5);
dto.setUsrId(usrId);
// 로그인ID 중복 체크
checkLoginIdDuplicate(cu.getCorpNo(), dto.getLoginId(), usrId);
// 비밀번호 암호화 (SHA-256 + Base64)
dto.setPw(PasswordUtil.encode(dto.getPw()));
userManageMapper.insertUser(dto);
}
@Transactional
public void updateUser(CurrentUser cu, UserSaveDto dto) {
if (!StringUtils.hasText(dto.getUsrId())) {
throw new BizException("USR_ID는 필수입니다.");
}
dto.setCorpNo(cu.getCorpNo());
dto.setModId(cu.getUsrId());
// 로그인ID 중복 체크 (본인 제외)
checkLoginIdDuplicate(cu.getCorpNo(), dto.getLoginId(), dto.getUsrId());
userManageMapper.updateUser(dto);
// 비밀번호 입력 시에만 변경
if (StringUtils.hasText(dto.getPw())) {
userManageMapper.updateUserPw(
cu.getCorpNo(), dto.getUsrId(),
PasswordUtil.encode(dto.getPw()), cu.getUsrId());
}
}
@Transactional
public void deleteUser(CurrentUser cu, String usrId) {
// 사용자 존재 확인
getUserDetail(cu, usrId);
userManageMapper.deleteUser(cu.getCorpNo(), usrId);
}
private void checkLoginIdDuplicate(String corpNo, String loginId, String usrId) {
if (!StringUtils.hasText(loginId)) return;
Map<String, Object> params = new HashMap<>();
params.put("corpNo", corpNo);
params.put("loginId", loginId);
params.put("usrId", usrId != null ? usrId : "");
int cnt = userManageMapper.getLoginIdCount(params);
if (cnt > 0) {
throw new BizException("이미 사용 중인 로그인 아이디입니다.");
}
}
}

View File

@@ -0,0 +1,47 @@
package com.company.gw.envset.service;
import com.company.gw.envset.mapper.WorkCdMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class WorkCdService {
private final WorkCdMapper workCdMapper;
public List<Map<String, Object>> getWorkCdList(String corpNo, String searchText) {
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("searchText", searchText);
return workCdMapper.getWorkCdList(param);
}
@Transactional
public void insertWorkCd(String corpNo, String regUsrId, Map<String, Object> data) {
data.put("corpNo", corpNo);
data.put("regUsrId", regUsrId);
workCdMapper.insertWorkCd(data);
}
@Transactional
public void updateWorkCd(String corpNo, String regUsrId, String workCd, Map<String, Object> data) {
data.put("corpNo", corpNo);
data.put("regUsrId", regUsrId);
data.put("workCd", workCd);
workCdMapper.updateWorkCd(data);
}
@Transactional
public void deleteWorkCd(String corpNo, String workCd) {
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("workCd", workCd);
workCdMapper.deleteWorkCd(param);
}
}

View File

@@ -0,0 +1,58 @@
package com.company.gw.fedex.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.fedex.service.FedexService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/fedex")
@RequiredArgsConstructor
public class FedexController {
private final FedexService fedexService;
/** 목록 */
@GetMapping("/0010")
public ApiResponse<Map<String, Object>> getFedexList(
@RequestParam(defaultValue = "") String searchText,
@RequestParam(defaultValue = "1") int pageNo,
@RequestParam(defaultValue = "100") int pageSize) {
return ApiResponse.ok(fedexService.getFedexList(searchText, pageNo, pageSize));
}
/** 상세 */
@GetMapping("/0010/{sq}")
public ApiResponse<Map<String, Object>> getFedexDetail(@PathVariable long sq) {
return ApiResponse.ok(fedexService.getFedexDetail(sq));
}
/** 등록 */
@PostMapping("/0010")
public ApiResponse<Long> insertFedex(
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser cu) {
body.put("regUserId", cu.getUsrId());
long sq = fedexService.insertFedex(body);
return ApiResponse.ok(sq);
}
/** 첨부파일 번호 업데이트 */
@PatchMapping("/0010/{sq}/attach")
public ApiResponse<Void> updateFedexAttach(
@PathVariable long sq,
@RequestBody Map<String, Object> body) {
fedexService.updateFedexAttach(sq, (String) body.get("attachNo"));
return ApiResponse.ok(null);
}
/** 코드 목록 */
@GetMapping("/codes")
public ApiResponse<Map<String, Object>> getCodes() {
return ApiResponse.ok(fedexService.getCodes());
}
}

View File

@@ -0,0 +1,17 @@
package com.company.gw.fedex.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface FedexMapper {
int countFedexList(Map<String, Object> param);
List<Map<String, Object>> getFedexList(Map<String, Object> param);
Map<String, Object> getFedexDetail(Map<String, Object> param);
void insertFedex(Map<String, Object> param);
void updateFedexAttach(Map<String, Object> param);
List<Map<String, Object>> getFstList();
List<Map<String, Object>> getGwiList();
List<Map<String, Object>> getFjjList();
}

View File

@@ -0,0 +1,60 @@
package com.company.gw.fedex.service;
import com.company.gw.fedex.mapper.FedexMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class FedexService {
private final FedexMapper fedexMapper;
public Map<String, Object> getFedexList(String searchText, int pageNo, int pageSize) {
Map<String, Object> param = new HashMap<>();
param.put("searchText", searchText);
param.put("pageSize", pageSize);
param.put("offset", (pageNo - 1) * pageSize);
int total = fedexMapper.countFedexList(param);
List<Map<String, Object>> list = fedexMapper.getFedexList(param);
Map<String, Object> result = new HashMap<>();
result.put("list", list);
result.put("total", total);
return result;
}
public Map<String, Object> getFedexDetail(long sq) {
Map<String, Object> param = new HashMap<>();
param.put("sq", sq);
return fedexMapper.getFedexDetail(param);
}
@Transactional
public long insertFedex(Map<String, Object> param) {
fedexMapper.insertFedex(param);
return ((Number) param.get("sq")).longValue();
}
@Transactional
public void updateFedexAttach(long sq, String attachNo) {
Map<String, Object> param = new HashMap<>();
param.put("sq", sq);
param.put("attachNo", attachNo);
fedexMapper.updateFedexAttach(param);
}
public Map<String, Object> getCodes() {
Map<String, Object> result = new HashMap<>();
result.put("fstList", fedexMapper.getFstList());
result.put("gwiList", fedexMapper.getGwiList());
result.put("fjjList", fedexMapper.getFjjList());
return result;
}
}

View File

@@ -0,0 +1,69 @@
package com.company.gw.main.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.main.service.MainService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/main")
@RequiredArgsConstructor
public class MainController {
private final MainService mainService;
/** 대시보드 전체 데이터 */
@GetMapping("/dashboard")
public ApiResponse<Map<String, Object>> getDashboard(
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(mainService.getDashboard(
currentUser.getCorpNo(), currentUser.getUsrId()));
}
/** 출근 */
@PostMapping("/workstart")
public ApiResponse<Map<String, Object>> clockIn(
@AuthenticationPrincipal CurrentUser currentUser,
@RequestBody Map<String, String> body,
HttpServletRequest request) {
String date = body.get("workPlanYymmdd");
String ip = getClientIp(request);
return ApiResponse.ok(mainService.clockIn(
currentUser.getCorpNo(), currentUser.getUsrId(), date, ip));
}
/** 퇴근 */
@PostMapping("/workend")
public ApiResponse<Map<String, Object>> clockOut(
@AuthenticationPrincipal CurrentUser currentUser,
@RequestBody Map<String, String> body,
HttpServletRequest request) {
String date = body.get("workPlanYymmdd");
String ip = getClientIp(request);
return ApiResponse.ok(mainService.clockOut(
currentUser.getCorpNo(), currentUser.getUsrId(), date, ip));
}
/** 오늘 지각자 목록 (관리자용) */
@GetMapping("/late-list")
public ApiResponse<List<Map<String, Object>>> getLateList(
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(mainService.getTodayLateList(currentUser.getCorpNo()));
}
/** X-Forwarded-For → remoteAddr 순으로 클라이언트 IP 추출 */
private String getClientIp(HttpServletRequest request) {
String xff = request.getHeader("X-Forwarded-For");
if (StringUtils.hasText(xff)) {
return xff.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}

View File

@@ -0,0 +1,23 @@
package com.company.gw.main.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface MainMapper {
List<Map<String, Object>> getTopPostList(Map<String, Object> param);
List<Map<String, Object>> getWorkRangeList(Map<String, Object> param);
Map<String, Object> getTodayWorkRecord(Map<String, Object> param);
Map<String, Object> getPendingApprCount(Map<String, Object> param);
List<Map<String, Object>> getMyDocSentSummary(Map<String, Object> param);
List<Map<String, Object>> getMyDocReceivedSummary(Map<String, Object> param);
List<Map<String, Object>> getTodayWorkSummary(Map<String, Object> param);
// 출퇴근
Map<String, Object> getWorkplanInfo(Map<String, Object> param);
List<String> getAllowedIpList(Map<String, Object> param);
int updateWorkStart(Map<String, Object> param);
int updateWorkEnd(Map<String, Object> param);
List<Map<String, Object>> getTodayLateList(Map<String, Object> param);
}

View File

@@ -0,0 +1,221 @@
package com.company.gw.main.service;
import com.company.gw.common.exception.BizException;
import com.company.gw.main.mapper.MainMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class MainService {
private final MainMapper mainMapper;
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyyMMdd");
/** 대시보드 전체 데이터 */
public Map<String, Object> getDashboard(String corpNo, String usrId) {
LocalDate today = LocalDate.now();
String todayStr = today.format(FMT);
String fromDate = today.minusDays(4).format(FMT);
String toDate = today.plusDays(4).format(FMT);
Map<String, Object> result = new HashMap<>();
// 공지사항 최신 5건 (UNTY_BBS_CD='0002')
result.put("noticeList", getTopPostList(corpNo, "0002", 5));
// 자유게시판 최신 5건 (UNTY_BBS_CD='0001')
result.put("boardList", getTopPostList(corpNo, "0001", 5));
// 업무메뉴얼 최신 5건 (UNTY_BBS_CD='0004')
result.put("manualList", getTopPostList(corpNo, "0004", 5));
// 9일 근무 범위 (오늘±4일)
Map<String, Object> wp = new HashMap<>();
wp.put("corpNo", corpNo);
wp.put("usrId", usrId);
wp.put("fromDate", fromDate);
wp.put("toDate", toDate);
result.put("workRangeList", mainMapper.getWorkRangeList(wp));
// 오늘 근무 기록
result.put("todayWork", getTodayWorkRecord(corpNo, usrId, todayStr));
// 결재 대기 건수
result.put("pendingAppr", getPendingApprCount(corpNo, usrId));
// 내가 올린 결재 문서 상태별 카운트
Map<String, Object> sp = new HashMap<>();
sp.put("corpNo", corpNo);
sp.put("usrId", usrId);
result.put("myDocSent", mainMapper.getMyDocSentSummary(sp));
// 내가 받은 결재 문서 상태별 카운트
result.put("myDocReceived", mainMapper.getMyDocReceivedSummary(sp));
// 오늘 전체 근무 현황 (근무코드별 그룹)
Map<String, Object> ts = new HashMap<>();
ts.put("corpNo", corpNo);
ts.put("today", todayStr);
result.put("todayWorkSummary", mainMapper.getTodayWorkSummary(ts));
// 오늘 지각자
result.put("todayLateList", getTodayLateList(corpNo));
return result;
}
private List<Map<String, Object>> getTopPostList(String corpNo, String bbsCd, int top) {
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("bbsCd", bbsCd);
param.put("top", top);
return mainMapper.getTopPostList(param);
}
private Map<String, Object> getTodayWorkRecord(String corpNo, String usrId, String today) {
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("usrId", usrId);
param.put("today", today);
return mainMapper.getTodayWorkRecord(param);
}
private Map<String, Object> getPendingApprCount(String corpNo, String usrId) {
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("usrId", usrId);
return mainMapper.getPendingApprCount(param);
}
// ─────────────────────────────────────────────────────
// 출퇴근
// ─────────────────────────────────────────────────────
/** 근무계획 단건 조회 */
public Map<String, Object> getWorkplanInfo(String corpNo, String usrId, String workPlanYymmdd) {
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("usrId", usrId);
param.put("workPlanYymmdd", workPlanYymmdd);
return mainMapper.getWorkplanInfo(param);
}
/** IP 허용 여부 검사 (SX014 코드에 IP 등록 없으면 전체 허용) */
public void checkAllowedIp(String corpNo, String clientIp) {
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
List<String> ipList = mainMapper.getAllowedIpList(param);
if (ipList == null || ipList.isEmpty()) return; // 설정 없으면 전체 허용
for (String allowed : ipList) {
if (StringUtils.hasText(allowed) && clientIp.startsWith(allowed.trim())) return;
}
throw new BizException("접속 IP(" + clientIp + ")가 허용 IP가 아닙니다.", HttpStatus.FORBIDDEN);
}
/** 출근 처리 */
@Transactional
public Map<String, Object> clockIn(String corpNo, String usrId, String workPlanYymmdd, String clientIp) {
checkAllowedIp(corpNo, clientIp);
Map<String, Object> wp = getWorkplanInfo(corpNo, usrId, workPlanYymmdd);
if (wp == null || !StringUtils.hasText((String) wp.get("REAL_WORK_CD"))) {
throw new BizException(workPlanYymmdd + " 일의 근무 계획이 없습니다.");
}
if (StringUtils.hasText((String) wp.get("WORK_START_DT"))) {
throw new BizException(workPlanYymmdd + " 에 이미 출근 기록이 있습니다.");
}
// 지각 여부 계산
int lateMin = calcLateMin((String) wp.get("GOTOWORK_TM_NM"));
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("usrId", usrId);
param.put("workPlanYymmdd", workPlanYymmdd);
mainMapper.updateWorkStart(param);
Map<String, Object> result = new HashMap<>();
result.put("lateMin", lateMin);
return result;
}
/** 퇴근 처리 */
@Transactional
public Map<String, Object> clockOut(String corpNo, String usrId, String workPlanYymmdd, String clientIp) {
checkAllowedIp(corpNo, clientIp);
Map<String, Object> wp = getWorkplanInfo(corpNo, usrId, workPlanYymmdd);
if (wp == null || !StringUtils.hasText((String) wp.get("REAL_WORK_CD"))) {
throw new BizException(workPlanYymmdd + " 일의 근무 계획이 없습니다.");
}
if (!StringUtils.hasText((String) wp.get("WORK_START_DT"))) {
throw new BizException(workPlanYymmdd + " 에 출근 기록이 없습니다.");
}
if (StringUtils.hasText((String) wp.get("WORK_END_DT"))) {
throw new BizException(workPlanYymmdd + " 에 이미 퇴근 기록이 있습니다.");
}
// 조퇴 여부 계산
int earlyMin = calcEarlyMin((String) wp.get("GETOFFWORK_TM_NM"));
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("usrId", usrId);
param.put("workPlanYymmdd", workPlanYymmdd);
mainMapper.updateWorkEnd(param);
Map<String, Object> result = new HashMap<>();
result.put("earlyMin", earlyMin);
return result;
}
/** 오늘 지각자 목록 */
@Transactional(readOnly = true)
public List<Map<String, Object>> getTodayLateList(String corpNo) {
String today = LocalDate.now().format(FMT);
Map<String, Object> param = new HashMap<>();
param.put("corpNo", corpNo);
param.put("today", today);
return mainMapper.getTodayLateList(param);
}
/** 지각 분 계산 (GOTOWORK_TM_NM 예: "0900") */
private int calcLateMin(String gotoworkTmNm) {
if (!StringUtils.hasText(gotoworkTmNm) || gotoworkTmNm.length() < 4) return 0;
try {
LocalTime scheduled = LocalTime.of(
Integer.parseInt(gotoworkTmNm.substring(0, 2)),
Integer.parseInt(gotoworkTmNm.substring(2, 4))
);
LocalTime now = LocalTime.now();
if (now.isAfter(scheduled)) {
return (int) java.time.Duration.between(scheduled, now).toMinutes();
}
} catch (Exception ignored) {}
return 0;
}
/** 조퇴 분 계산 (GETOFFWORK_TM_NM 예: "1800") */
private int calcEarlyMin(String getoffworkTmNm) {
if (!StringUtils.hasText(getoffworkTmNm) || getoffworkTmNm.length() < 4) return 0;
try {
LocalTime scheduled = LocalTime.of(
Integer.parseInt(getoffworkTmNm.substring(0, 2)),
Integer.parseInt(getoffworkTmNm.substring(2, 4))
);
LocalTime now = LocalTime.now();
if (now.isBefore(scheduled)) {
return (int) java.time.Duration.between(now, scheduled).toMinutes();
}
} catch (Exception ignored) {}
return 0;
}
}

View File

@@ -0,0 +1,193 @@
package com.company.gw.tam.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.tam.service.TamService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/tam")
@RequiredArgsConstructor
public class TamController {
private final TamService tamService;
// ──────────────────────────────────────────────
// TAM0010 - 연차 관리
// ──────────────────────────────────────────────
@GetMapping("/yyvct")
public ApiResponse<List<Map<String, Object>>> getYyvctList(
@RequestParam String yyvctYy,
@RequestParam(defaultValue = "") String usrNm,
@RequestParam(defaultValue = "") String teamCd,
@RequestParam(defaultValue = "N") String includeRetireYn,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(tamService.getYyvctList(
currentUser.getCorpNo(), yyvctYy, usrNm, teamCd, includeRetireYn));
}
@PostMapping("/yyvct")
public ApiResponse<Void> saveYyvct(
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
tamService.saveYyvct(
currentUser.getCorpNo(),
(String) body.get("yyvctYy"),
(String) body.get("usrId"),
Integer.parseInt(body.get("yyvctCnt").toString()));
return ApiResponse.ok(null);
}
@DeleteMapping("/yyvct")
public ApiResponse<Void> deleteYyvct(
@RequestParam String yyvctYy,
@RequestParam String usrId,
@AuthenticationPrincipal CurrentUser currentUser) {
tamService.deleteYyvct(currentUser.getCorpNo(), yyvctYy, usrId);
return ApiResponse.ok(null);
}
// ──────────────────────────────────────────────
// TAM0020 - 결재 신청
// ──────────────────────────────────────────────
@GetMapping("/apvreq")
public ApiResponse<Map<String, Object>> getApvreqList(
@RequestParam Map<String, Object> params,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(tamService.getApvreqList(currentUser.getCorpNo(), params));
}
@GetMapping("/apvreq/{aprvlDocId}")
public ApiResponse<Map<String, Object>> getApvreqDetail(
@PathVariable String aprvlDocId,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(tamService.getApvreqDetail(currentUser.getCorpNo(), aprvlDocId));
}
@PostMapping("/apvreq")
public ApiResponse<Map<String, String>> createApvreq(
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
String aprvlDocId = tamService.createApvreq(currentUser.getCorpNo(), body);
return ApiResponse.ok(Map.of("aprvlDocId", aprvlDocId));
}
@PutMapping("/apvreq/{aprvlDocId}")
public ApiResponse<Void> updateApvreq(
@PathVariable String aprvlDocId,
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
tamService.updateApvreq(currentUser.getCorpNo(), aprvlDocId, body);
return ApiResponse.ok(null);
}
@PostMapping("/apvreq/{aprvlDocId}/request")
public ApiResponse<Void> requestApvreq(
@PathVariable String aprvlDocId,
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> apprList = (List<Map<String, Object>>) body.get("apprList");
tamService.requestApvreq(currentUser.getCorpNo(), aprvlDocId, apprList);
return ApiResponse.ok(null);
}
@DeleteMapping("/apvreq/{aprvlDocId}")
public ApiResponse<Void> deleteApvreq(
@PathVariable String aprvlDocId,
@AuthenticationPrincipal CurrentUser currentUser) {
tamService.deleteApvreq(currentUser.getCorpNo(), aprvlDocId);
return ApiResponse.ok(null);
}
@GetMapping("/apvreq/latest-appr")
public ApiResponse<List<Map<String, Object>>> getLatestApprList(
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(tamService.getLatestApprList(currentUser.getCorpNo()));
}
// ──────────────────────────────────────────────
// TAM0030 - 결재 처리
// ──────────────────────────────────────────────
@GetMapping("/apvapp")
public ApiResponse<Map<String, Object>> getApvappList(
@RequestParam Map<String, Object> params,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(tamService.getApvappList(currentUser.getCorpNo(), params));
}
@GetMapping("/apvapp/{aprvlDocId}")
public ApiResponse<Map<String, Object>> getApvappDetail(
@PathVariable String aprvlDocId,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(tamService.getApvappDetail(currentUser.getCorpNo(), aprvlDocId));
}
@PostMapping("/apvapp/{aprvlDocId}/approve")
public ApiResponse<Void> approve(
@PathVariable String aprvlDocId,
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
tamService.approveApvdoc(currentUser.getCorpNo(), aprvlDocId,
Integer.parseInt(body.get("aprvlSno").toString()),
(String) body.get("apprCn"));
return ApiResponse.ok(null);
}
@PostMapping("/apvapp/{aprvlDocId}/reject")
public ApiResponse<Void> reject(
@PathVariable String aprvlDocId,
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
tamService.rejectApvdoc(currentUser.getCorpNo(), aprvlDocId,
Integer.parseInt(body.get("aprvlSno").toString()),
(String) body.get("apprCn"));
return ApiResponse.ok(null);
}
/** 일괄 승인 */
@PostMapping("/apvapp/multi-approve")
public ApiResponse<Void> multiApprove(
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> items = (List<Map<String, Object>>) body.get("items");
tamService.multiApprove(currentUser.getCorpNo(), items, (String) body.get("apprCn"));
return ApiResponse.ok(null);
}
/** 일괄 반려 */
@PostMapping("/apvapp/multi-reject")
public ApiResponse<Void> multiReject(
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> items = (List<Map<String, Object>>) body.get("items");
tamService.multiReject(currentUser.getCorpNo(), items, (String) body.get("apprCn"));
return ApiResponse.ok(null);
}
// ──────────────────────────────────────────────
// TAM0040 - 근태 현황
// ──────────────────────────────────────────────
@GetMapping("/status")
public ApiResponse<List<Map<String, Object>>> getTamStatusList(
@RequestParam String yyvctYy,
@RequestParam(defaultValue = "") String teamCd,
@RequestParam(defaultValue = "00000000") String staYmd,
@RequestParam(defaultValue = "99999999") String endYmd,
@RequestParam(defaultValue = "N") String includeRetireYn,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(tamService.getTamStatusList(
currentUser.getCorpNo(), yyvctYy, teamCd, staYmd, endYmd, includeRetireYn));
}
}

View File

@@ -0,0 +1,52 @@
package com.company.gw.tam.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface ApvdocMapper {
Map<String, Object> getApvdocInfo(Map<String, Object> param);
List<Map<String, Object>> getApprList(Map<String, Object> param);
Map<String, Object> getApprInfo(Map<String, Object> param);
void insertApvdoc(Map<String, Object> param);
void updateApvdocContent(Map<String, Object> param);
void updateApvdocAtchNo(Map<String, Object> param);
void updateApvdocStatusRequest(Map<String, Object> param);
void updateApvdocStatusApprove(Map<String, Object> param);
void updateApvdocStatusReject(Map<String, Object> param);
void updateFinalAprvlSno(Map<String, Object> param);
void updateAprvlCmplSno(Map<String, Object> param);
void deleteApprAll(Map<String, Object> param);
void insertAppr(Map<String, Object> param);
void updateApprStatus(Map<String, Object> param);
void apprApproveOrReject(Map<String, Object> param);
void updateNextApprStatusAppring(Map<String, Object> param);
void deleteApvdocSx0080(Map<String, Object> param);
void deleteApvdocSx0110(Map<String, Object> param);
void deleteApvdocSx0090(Map<String, Object> param);
void updateApvdocDocFlagAplnt(Map<String, Object> param);
void resetWorkEndDt(Map<String, Object> param);
}

View File

@@ -0,0 +1,43 @@
package com.company.gw.tam.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface TamMapper {
// TAM0010 - 연차
List<Map<String, Object>> getYyvctList(Map<String, Object> param);
void upsertYyvct(Map<String, Object> param);
void deleteYyvct(Map<String, Object> param);
// TAM0020 - 결재 신청
List<Map<String, Object>> getApvreqList(Map<String, Object> param);
long getApvreqListCount(Map<String, Object> param);
List<Map<String, Object>> getWorkChangeList(Map<String, Object> param);
Map<String, Object> getOtInfo(Map<String, Object> param);
void insertSxGw0110(Map<String, Object> param);
void insertSxGw0080(Map<String, Object> param);
void deleteSxGw0110(Map<String, Object> param);
void deleteSxGw0080(Map<String, Object> param);
List<Map<String, Object>> getLatestApprList(Map<String, Object> param);
void updateDocFlagAplnt(Map<String, Object> param);
// TAM0030 - 결재 처리
List<Map<String, Object>> getApvappList(Map<String, Object> param);
long getApvappListCount(Map<String, Object> param);
void updateDocFlagAppr(Map<String, Object> param);
void updateOutingTime(Map<String, Object> param);
// TAM0040 - 현황/통계
List<Map<String, Object>> getTamStatusList(Map<String, Object> param);
// 결재 후처리
int afterProcessOt(Map<String, Object> param);
int afterProcessLate(Map<String, Object> param);
int afterProcessEarlyDep(Map<String, Object> param);
int afterProcessAbsence(Map<String, Object> param);
int afterProcessWorkChange(Map<String, Object> param);
}

View File

@@ -0,0 +1,262 @@
package com.company.gw.tam.service;
import com.company.gw.common.exception.BizException;
import com.company.gw.common.service.AttachService;
import com.company.gw.common.service.SequenceService;
import com.company.gw.common.util.SecurityUtil;
import com.company.gw.tam.mapper.ApvdocMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 결재 공통 서비스
* 결재 상태코드: 0001=작성중, 0002=결재중, 0003=결재완료, 0004=반려
*/
@Service
@RequiredArgsConstructor
public class ApvdocService {
public static final String STATUS_WRITING = "0001";
public static final String STATUS_APPRING = "0002";
public static final String STATUS_APPROVED = "0003";
public static final String STATUS_REJECTED = "0004";
private final ApvdocMapper apvdocMapper;
private final AttachService attachService;
private final SequenceService sequenceService;
// ──────────────────────────────────────────────
// 결재문서 조회
// ──────────────────────────────────────────────
public Map<String, Object> getApvdocInfo(String corpNo, String aprvlDocId) {
Map<String, Object> info = apvdocMapper.getApvdocInfo(param(corpNo, aprvlDocId));
if (info == null) throw new BizException("결재문서가 존재하지 않습니다.", HttpStatus.NOT_FOUND);
return info;
}
public List<Map<String, Object>> getApprList(String corpNo, String aprvlDocId) {
return apvdocMapper.getApprList(param(corpNo, aprvlDocId));
}
/** 결재문서 + 결재자목록 + 첨부파일 통합 조회 */
@Transactional(readOnly = true)
public Map<String, Object> getApvdocInfoAll(String corpNo, String aprvlDocId) {
Map<String, Object> info = getApvdocInfo(corpNo, aprvlDocId);
info.put("apprList", getApprList(corpNo, aprvlDocId));
String atchNo = (String) info.get("ATCH_NO");
if (atchNo != null) {
info.put("attachFileList", attachService.getFileList(atchNo));
}
return info;
}
// ──────────────────────────────────────────────
// 결재문서 생성
// ──────────────────────────────────────────────
@Transactional
public String createApvdoc(String corpNo, String aprvlKindCd,
String aplntCn, String bzCn, String offerCn, String bzDeputyId,
String atchNo) {
String usrId = SecurityUtil.getUsrId();
String aprvlDocId = sequenceService.getNextSeqString("SX_GW0090.APRVL_DOC_ID", 10);
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("aprvlDocId", aprvlDocId);
p.put("aprvlKindCd", aprvlKindCd);
p.put("aplntCn", aplntCn);
p.put("bzCn", bzCn);
p.put("offerCn", offerCn);
p.put("bzDeputyId", bzDeputyId);
p.put("atchNo", atchNo);
p.put("usrId", usrId);
apvdocMapper.insertApvdoc(p);
if (org.springframework.util.StringUtils.hasText(atchNo)) {
attachService.confirmAtchNo(atchNo, "SX_GW0090.ATCH_NO");
}
return aprvlDocId;
}
@Transactional
public void updateApvdocContent(String corpNo, String aprvlDocId,
String aplntCn, String bzCn, String offerCn, String bzDeputyId,
String atchNo) {
String usrId = SecurityUtil.getUsrId();
Map<String, Object> info = getApvdocInfo(corpNo, aprvlDocId);
checkAplnt(info);
checkBeforeRequest(info);
Map<String, Object> p = param(corpNo, aprvlDocId);
p.put("aplntCn", aplntCn);
p.put("bzCn", bzCn);
p.put("offerCn", offerCn);
p.put("bzDeputyId", bzDeputyId);
p.put("usrId", usrId);
apvdocMapper.updateApvdocContent(p);
if (org.springframework.util.StringUtils.hasText(atchNo)) {
Map<String, Object> ap = param(corpNo, aprvlDocId);
ap.put("atchNo", atchNo);
apvdocMapper.updateApvdocAtchNo(ap);
attachService.confirmAtchNo(atchNo, "SX_GW0090.ATCH_NO");
}
}
// ──────────────────────────────────────────────
// 결재자 등록
// ──────────────────────────────────────────────
@Transactional
public void insertApprList(String corpNo, String aprvlDocId, List<Map<String, Object>> apprList) {
String usrId = SecurityUtil.getUsrId();
Map<String, Object> info = getApvdocInfo(corpNo, aprvlDocId);
checkBeforeRequest(info);
apvdocMapper.deleteApprAll(param(corpNo, aprvlDocId));
int sno = 1;
for (Map<String, Object> appr : apprList) {
String apprId = (String) appr.get("apprId");
if (apprId == null || apprId.isEmpty()) {
throw new BizException("결재자가 지정되지 않았습니다.");
}
Map<String, Object> p = param(corpNo, aprvlDocId);
p.put("aprvlSno", sno++);
p.put("apprId", apprId);
p.put("usrId", usrId);
apvdocMapper.insertAppr(p);
}
apvdocMapper.updateFinalAprvlSno(param(corpNo, aprvlDocId));
}
// ──────────────────────────────────────────────
// 결재 상신
// ──────────────────────────────────────────────
@Transactional
public void requestApvdoc(String corpNo, String aprvlDocId) {
String usrId = SecurityUtil.getUsrId();
Map<String, Object> info = getApvdocInfo(corpNo, aprvlDocId);
checkBeforeRequest(info);
List<Map<String, Object>> apprList = getApprList(corpNo, aprvlDocId);
if (apprList.isEmpty()) throw new BizException("결재자가 지정되지 않았습니다.");
// 1차 결재자 → 결재중
Map<String, Object> p1 = param(corpNo, aprvlDocId);
p1.put("aprvlSno", 1);
p1.put("apprStusCd", STATUS_APPRING);
p1.put("usrId", usrId);
apvdocMapper.updateApprStatus(p1);
// 문서 → 결재중
apvdocMapper.updateApvdocStatusRequest(param(corpNo, aprvlDocId));
}
// ──────────────────────────────────────────────
// 승인 / 반려
// ──────────────────────────────────────────────
@Transactional
public void approveApvdoc(String corpNo, String aprvlDocId, int aprvlSno, String apprCn) {
processApproveOrReject(corpNo, aprvlDocId, aprvlSno, apprCn, true);
}
@Transactional
public void rejectApvdoc(String corpNo, String aprvlDocId, int aprvlSno, String apprCn) {
processApproveOrReject(corpNo, aprvlDocId, aprvlSno, apprCn, false);
}
private void processApproveOrReject(String corpNo, String aprvlDocId,
int aprvlSno, String apprCn, boolean isApprove) {
String usrId = SecurityUtil.getUsrId();
Map<String, Object> docInfo = getApvdocInfo(corpNo, aprvlDocId);
if (!STATUS_APPRING.equals(docInfo.get("APRVL_STUS_CD"))) {
throw new BizException("결재중 상태가 아닙니다.");
}
Map<String, Object> apprParam = param(corpNo, aprvlDocId);
apprParam.put("aprvlSno", aprvlSno);
Map<String, Object> apprInfo = apvdocMapper.getApprInfo(apprParam);
if (apprInfo == null) throw new BizException("결재자 정보가 없습니다. (" + aprvlSno + "차)");
if (!STATUS_APPRING.equals(apprInfo.get("APPR_STUS_CD"))) throw new BizException("결재중인 상태가 아닙니다.");
if (!usrId.equals(apprInfo.get("APPR_ID"))) throw new BizException("결재자가 아닙니다.");
String apprStusCd = isApprove ? STATUS_APPROVED : STATUS_REJECTED;
Map<String, Object> p = param(corpNo, aprvlDocId);
p.put("aprvlSno", aprvlSno);
p.put("apprStusCd", apprStusCd);
p.put("apprCn", apprCn);
p.put("usrId", usrId);
apvdocMapper.apprApproveOrReject(p);
if (isApprove) {
if ("Y".equals(apprInfo.get("FINAL_APPR_YN"))) {
apvdocMapper.updateApvdocStatusApprove(param(corpNo, aprvlDocId));
} else {
Map<String, Object> nextP = param(corpNo, aprvlDocId);
nextP.put("aprvlSno", aprvlSno);
nextP.put("usrId", usrId);
apvdocMapper.updateNextApprStatusAppring(nextP);
}
} else {
apvdocMapper.updateApvdocStatusReject(param(corpNo, aprvlDocId));
apvdocMapper.resetWorkEndDt(param(corpNo, aprvlDocId));
}
apvdocMapper.updateAprvlCmplSno(param(corpNo, aprvlDocId));
}
// ──────────────────────────────────────────────
// 결재문서 삭제
// ──────────────────────────────────────────────
@Transactional
public void deleteApvdoc(String corpNo, String aprvlDocId) {
Map<String, Object> info = getApvdocInfo(corpNo, aprvlDocId);
checkAplnt(info);
Map<String, Object> p = param(corpNo, aprvlDocId);
apvdocMapper.deleteApvdocSx0080(p);
apvdocMapper.deleteApvdocSx0110(p);
apvdocMapper.deleteApvdocSx0090(p);
String atchNo = (String) info.get("ATCH_NO");
if (atchNo != null) attachService.deleteFilesByAtchNo(atchNo, true);
}
// ──────────────────────────────────────────────
// 내부 유틸
// ──────────────────────────────────────────────
private Map<String, Object> param(String corpNo, String aprvlDocId) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("aprvlDocId", aprvlDocId);
return p;
}
private void checkBeforeRequest(Map<String, Object> docInfo) {
if (!STATUS_WRITING.equals(docInfo.get("APRVL_STUS_CD"))) {
throw new BizException("작성중 상태가 아닙니다.");
}
}
private void checkAplnt(Map<String, Object> docInfo) {
String usrId = SecurityUtil.getUsrId();
if (!usrId.equals(docInfo.get("APLNT_ID"))) {
throw new BizException("신청자가 아닙니다.", HttpStatus.FORBIDDEN);
}
}
}

View File

@@ -0,0 +1,315 @@
package com.company.gw.tam.service;
import com.company.gw.common.util.SecurityUtil;
import com.company.gw.tam.mapper.TamMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class TamService {
private final TamMapper tamMapper;
private final ApvdocService apvdocService;
// ──────────────────────────────────────────────
// TAM0010 - 연차 관리
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getYyvctList(String corpNo, String yyvctYy,
String usrNm, String teamCd,
String includeRetireYn) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("yyvctYy", yyvctYy);
p.put("usrNm", usrNm);
p.put("teamCd", teamCd);
p.put("includeRetireYn", includeRetireYn != null ? includeRetireYn : "N");
return tamMapper.getYyvctList(p);
}
@Transactional
public void saveYyvct(String corpNo, String yyvctYy, String usrId, int yyvctCnt) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("yyvctYy", yyvctYy);
p.put("usrId", usrId);
p.put("yyvctCnt", yyvctCnt);
p.put("loginUsrId", SecurityUtil.getUsrId());
tamMapper.upsertYyvct(p);
}
@Transactional
public void deleteYyvct(String corpNo, String yyvctYy, String usrId) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("yyvctYy", yyvctYy);
p.put("usrId", usrId);
tamMapper.deleteYyvct(p);
}
// ──────────────────────────────────────────────
// TAM0020 - 결재 신청 목록
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public Map<String, Object> getApvreqList(String corpNo, Map<String, Object> filter) {
filter.put("corpNo", corpNo);
filter.put("usrId", SecurityUtil.getUsrId());
filter.put("pageNo", Integer.parseInt(filter.getOrDefault("pageNo", "1").toString()));
filter.put("pageSize", Integer.parseInt(filter.getOrDefault("pageSize", "20").toString()));
if (!filter.containsKey("staYmd") || filter.get("staYmd") == null || filter.get("staYmd").toString().isEmpty())
filter.put("staYmd", "00000000");
if (!filter.containsKey("endYmd") || filter.get("endYmd") == null || filter.get("endYmd").toString().isEmpty())
filter.put("endYmd", "99999999");
List<Map<String, Object>> list = tamMapper.getApvreqList(filter);
long total = tamMapper.getApvreqListCount(filter);
Map<String, Object> result = new HashMap<>();
result.put("list", list);
result.put("total", total);
return result;
}
/** 결재 신청 상세 (문서 + 결재자 + 첨부 + 세부항목) */
@Transactional(readOnly = true)
public Map<String, Object> getApvreqDetail(String corpNo, String aprvlDocId) {
Map<String, Object> info = apvdocService.getApvdocInfoAll(corpNo, aprvlDocId);
// 변경근무 목록
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("aprvlDocId", aprvlDocId);
info.put("workChangeList", tamMapper.getWorkChangeList(p));
info.put("otInfo", tamMapper.getOtInfo(p));
return info;
}
/** 결재 신청 생성 */
@Transactional
public String createApvreq(String corpNo, Map<String, Object> body) {
String aprvlDocId = apvdocService.createApvdoc(
corpNo,
(String) body.get("aprvlKindCd"),
(String) body.get("aplntCn"),
(String) body.get("bzCn"),
(String) body.get("offerCn"),
(String) body.get("bzDeputyId"),
(String) body.get("atchNo")
);
// 세부 항목 등록
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("aprvlDocId", aprvlDocId);
p.put("usrId", SecurityUtil.getUsrId());
@SuppressWarnings("unchecked")
List<Map<String, Object>> workChangeList = (List<Map<String, Object>>) body.get("workChangeList");
if (workChangeList != null) {
for (Map<String, Object> item : workChangeList) {
Map<String, Object> ip = new HashMap<>(p);
ip.putAll(item);
tamMapper.insertSxGw0110(ip);
}
}
Map<String, Object> otInfo = (Map<String, Object>) body.get("otInfo");
if (otInfo != null) {
Map<String, Object> ip = new HashMap<>(p);
ip.putAll(otInfo);
tamMapper.insertSxGw0080(ip);
}
return aprvlDocId;
}
/** 결재 신청 수정 */
@Transactional
public void updateApvreq(String corpNo, String aprvlDocId, Map<String, Object> body) {
apvdocService.updateApvdocContent(corpNo, aprvlDocId,
(String) body.get("aplntCn"), (String) body.get("bzCn"),
(String) body.get("offerCn"), (String) body.get("bzDeputyId"),
(String) body.get("atchNo"));
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("aprvlDocId", aprvlDocId);
p.put("usrId", SecurityUtil.getUsrId());
tamMapper.deleteSxGw0110(p);
tamMapper.deleteSxGw0080(p);
@SuppressWarnings("unchecked")
List<Map<String, Object>> workChangeList = (List<Map<String, Object>>) body.get("workChangeList");
if (workChangeList != null) {
for (Map<String, Object> item : workChangeList) {
Map<String, Object> ip = new HashMap<>(p);
ip.putAll(item);
tamMapper.insertSxGw0110(ip);
}
}
Map<String, Object> otInfo = (Map<String, Object>) body.get("otInfo");
if (otInfo != null) {
Map<String, Object> ip = new HashMap<>(p);
ip.putAll(otInfo);
tamMapper.insertSxGw0080(ip);
}
}
/** 결재 상신 (결재자 등록 + 상신) */
@Transactional
public void requestApvreq(String corpNo, String aprvlDocId, List<Map<String, Object>> apprList) {
apvdocService.insertApprList(corpNo, aprvlDocId, apprList);
apvdocService.requestApvdoc(corpNo, aprvlDocId);
}
/** 결재문서 삭제 */
@Transactional
public void deleteApvreq(String corpNo, String aprvlDocId) {
apvdocService.deleteApvdoc(corpNo, aprvlDocId);
}
/** 최근 결재자 목록 조회 */
@Transactional(readOnly = true)
public List<Map<String, Object>> getLatestApprList(String corpNo) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("usrId", SecurityUtil.getUsrId());
return tamMapper.getLatestApprList(p);
}
// ──────────────────────────────────────────────
// TAM0030 - 결재 처리
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public Map<String, Object> getApvappList(String corpNo, Map<String, Object> filter) {
filter.put("corpNo", corpNo);
filter.put("usrId", SecurityUtil.getUsrId());
filter.put("pageNo", Integer.parseInt(filter.getOrDefault("pageNo", "1").toString()));
filter.put("pageSize", Integer.parseInt(filter.getOrDefault("pageSize", "20").toString()));
List<Map<String, Object>> list = tamMapper.getApvappList(filter);
long total = tamMapper.getApvappListCount(filter);
Map<String, Object> result = new HashMap<>();
result.put("list", list);
result.put("total", total);
return result;
}
/** 결재 처리 상세 */
@Transactional(readOnly = true)
public Map<String, Object> getApvappDetail(String corpNo, String aprvlDocId) {
return apvdocService.getApvdocInfoAll(corpNo, aprvlDocId);
}
/** 승인 처리 (최종 승인 시 executeAfterProcess 호출) */
@Transactional
public void approveApvdoc(String corpNo, String aprvlDocId, int aprvlSno, String apprCn) {
// 승인 전 문서 정보 (aplntId, aprvlKindCd 필요)
Map<String, Object> docBefore = apvdocService.getApvdocInfo(corpNo, aprvlDocId);
apvdocService.approveApvdoc(corpNo, aprvlDocId, aprvlSno, apprCn);
// 최종 승인 여부 확인
Map<String, Object> docAfter = apvdocService.getApvdocInfo(corpNo, aprvlDocId);
if (ApvdocService.STATUS_APPROVED.equals(docAfter.get("APRVL_STUS_CD"))) {
executeAfterProcess(corpNo, aprvlDocId,
(String) docBefore.get("APRVL_KIND_CD"),
(String) docBefore.get("APLNT_ID"));
}
}
/** 반려 처리 */
@Transactional
public void rejectApvdoc(String corpNo, String aprvlDocId, int aprvlSno, String apprCn) {
apvdocService.rejectApvdoc(corpNo, aprvlDocId, aprvlSno, apprCn);
}
/** 일괄 승인 */
@Transactional
public void multiApprove(String corpNo, List<Map<String, Object>> items, String apprCn) {
for (Map<String, Object> item : items) {
String aprvlDocId = (String) item.get("aprvlDocId");
int aprvlSno = Integer.parseInt(item.get("aprvlSno").toString());
try {
approveApvdoc(corpNo, aprvlDocId, aprvlSno, apprCn);
} catch (Exception e) {
// 일괄 처리 중 개별 실패는 무시하고 계속 진행
}
}
}
/** 일괄 반려 */
@Transactional
public void multiReject(String corpNo, List<Map<String, Object>> items, String apprCn) {
for (Map<String, Object> item : items) {
String aprvlDocId = (String) item.get("aprvlDocId");
int aprvlSno = Integer.parseInt(item.get("aprvlSno").toString());
try {
rejectApvdoc(corpNo, aprvlDocId, aprvlSno, apprCn);
} catch (Exception e) {
// 일괄 처리 중 개별 실패는 무시하고 계속 진행
}
}
}
/** 최종 승인 후처리 - 결재 종류별 근무 데이터 업데이트 */
private void executeAfterProcess(String corpNo, String aprvlDocId,
String aprvlKindCd, String aplntId) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("aprvlDocId", aprvlDocId);
p.put("aplntId", aplntId);
switch (aprvlKindCd != null ? aprvlKindCd : "") {
case "0001": // 시간외 → OT_RCTN_YN='Y'
tamMapper.afterProcessOt(p);
break;
case "0002": // 지각 → WORK_START_DT 보정
tamMapper.afterProcessLate(p);
break;
case "0003": // 조퇴 → WORK_END_DT 보정 + EL_RCTN_YN='Y'
tamMapper.afterProcessEarlyDep(p);
break;
case "0004": // 결근 → 출퇴근 모두 보정
tamMapper.afterProcessAbsence(p);
break;
case "0005": // 연차 → WORK_CD 변경
case "0006": // 근무변경 → WORK_CD 변경
tamMapper.afterProcessWorkChange(p);
break;
case "0008": // 외출 → OUTING_MIN/CNT 업데이트
tamMapper.updateOutingTime(p);
break;
default:
break;
}
}
// ──────────────────────────────────────────────
// TAM0040 - 근태 현황
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getTamStatusList(String corpNo, String yyvctYy,
String teamCd, String staYmd,
String endYmd, String includeRetireYn) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("yyvctYy", yyvctYy);
p.put("teamCd", teamCd);
p.put("staYmd", staYmd);
p.put("endYmd", endYmd);
p.put("includeRetireYn", includeRetireYn != null ? includeRetireYn : "N");
return tamMapper.getTamStatusList(p);
}
}

View File

@@ -0,0 +1,81 @@
package com.company.gw.wplan.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.wplan.service.WplanService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/wplan")
@RequiredArgsConstructor
public class WplanController {
private final WplanService wplanService;
// ──────────────────────────────────────────────
// 근무코드 목록
// ──────────────────────────────────────────────
@GetMapping("/workcode")
public ApiResponse<List<Map<String, Object>>> getWorkCodeList(
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(wplanService.getWorkCodeList(currentUser.getCorpNo()));
}
// ──────────────────────────────────────────────
// Wplan0010 - 근무계획관리
// ──────────────────────────────────────────────
@GetMapping("/0010")
public ApiResponse<List<Map<String, Object>>> getWplanList(
@RequestParam String workPlanYymm,
@RequestParam(defaultValue = "") String searchText,
@RequestParam(defaultValue = "") String teamCd,
@RequestParam(defaultValue = "N") String includeRetireYn,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(wplanService.getWplanList(
currentUser.getCorpNo(), workPlanYymm, searchText, teamCd, includeRetireYn));
}
@PostMapping("/0010")
public ApiResponse<Void> saveWplanList(
@RequestBody Map<String, Object> body,
@AuthenticationPrincipal CurrentUser currentUser) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> saveList = (List<Map<String, Object>>) body.get("saveList");
wplanService.saveWplanList(currentUser.getCorpNo(), saveList);
return ApiResponse.ok(null);
}
// ──────────────────────────────────────────────
// Wplan0020 - 나의 근무계획
// ──────────────────────────────────────────────
@GetMapping("/0020")
public ApiResponse<List<Map<String, Object>>> getMyWplanList(
@RequestParam String workPlanYymm,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(wplanService.getMyWplanList(currentUser.getCorpNo(), workPlanYymm));
}
// ──────────────────────────────────────────────
// Wplan0030 - 전체 근무계획
// ──────────────────────────────────────────────
@GetMapping("/0030")
public ApiResponse<List<Map<String, Object>>> getAllWplanList(
@RequestParam String workPlanYymm,
@RequestParam(defaultValue = "") String teamCd,
@RequestParam(defaultValue = "") String dutyCd,
@RequestParam(defaultValue = "") String workCd,
@RequestParam(defaultValue = "WORK_CD") String workCdType,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(wplanService.getAllWplanList(
currentUser.getCorpNo(), workPlanYymm, teamCd, dutyCd, workCd, workCdType));
}
}

View File

@@ -0,0 +1,27 @@
package com.company.gw.wplan.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface WplanMapper {
// 근무코드 목록
List<Map<String, Object>> getWorkCodeList(Map<String, Object> param);
// Wplan0010 - 근무계획관리
List<Map<String, Object>> getWplanList(Map<String, Object> param);
// Wplan0020 - 나의 근무계획
List<Map<String, Object>> getMyWplanList(Map<String, Object> param);
// Wplan0030 - 전체 근무계획
List<Map<String, Object>> getAllWplanList(Map<String, Object> param);
// 저장/삭제
void upsertWorkplan(Map<String, Object> param);
void deleteWorkplan(Map<String, Object> param);
void deleteWorkplanMonth(Map<String, Object> param);
}

View File

@@ -0,0 +1,97 @@
package com.company.gw.wplan.service;
import com.company.gw.common.util.SecurityUtil;
import com.company.gw.wplan.mapper.WplanMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class WplanService {
private final WplanMapper wplanMapper;
// ──────────────────────────────────────────────
// 근무코드 목록
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getWorkCodeList(String corpNo) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
return wplanMapper.getWorkCodeList(p);
}
// ──────────────────────────────────────────────
// Wplan0010 - 근무계획관리
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getWplanList(String corpNo, String workPlanYymm,
String searchText, String teamCd,
String includeRetireYn) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("workPlanYymm", workPlanYymm);
p.put("searchText", searchText);
p.put("teamCd", teamCd);
p.put("includeRetireYn", includeRetireYn != null ? includeRetireYn : "N");
return wplanMapper.getWplanList(p);
}
/**
* 근무계획 일괄 저장
* saveList: [{usrId, workPlanYymmdd, planWorkCd, sortOdr}]
*/
@Transactional
public void saveWplanList(String corpNo, List<Map<String, Object>> saveList) {
String loginUsrId = SecurityUtil.getUsrId();
for (Map<String, Object> item : saveList) {
Map<String, Object> p = new HashMap<>(item);
p.put("corpNo", corpNo);
p.put("loginUsrId", loginUsrId);
String planWorkCd = (String) p.get("planWorkCd");
if (planWorkCd == null || planWorkCd.isEmpty()) {
wplanMapper.deleteWorkplan(p);
} else {
wplanMapper.upsertWorkplan(p);
}
}
}
// ──────────────────────────────────────────────
// Wplan0020 - 나의 근무계획
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getMyWplanList(String corpNo, String workPlanYymm) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("usrId", SecurityUtil.getUsrId());
p.put("workPlanYymm", workPlanYymm);
return wplanMapper.getMyWplanList(p);
}
// ──────────────────────────────────────────────
// Wplan0030 - 전체 근무계획
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getAllWplanList(String corpNo, String workPlanYymm,
String teamCd, String dutyCd,
String workCd, String workCdType) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("workPlanYymm", workPlanYymm);
p.put("teamCd", teamCd);
p.put("dutyCd", dutyCd);
p.put("workCd", workCd);
p.put("workCdType", workCdType != null ? workCdType : "WORK_CD");
return wplanMapper.getAllWplanList(p);
}
}

View File

@@ -0,0 +1,58 @@
package com.company.gw.wtime.controller;
import com.company.gw.common.dto.ApiResponse;
import com.company.gw.common.dto.CurrentUser;
import com.company.gw.wtime.service.WtimeService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/wtime")
@RequiredArgsConstructor
public class WtimeController {
private final WtimeService wtimeService;
// ──────────────────────────────────────────────
// Wtime0010 - 개인별 근무시간
// ──────────────────────────────────────────────
@GetMapping("/0010")
public ApiResponse<List<Map<String, Object>>> getWtimeList(
@RequestParam String staYmd,
@RequestParam String endYmd,
@RequestParam(defaultValue = "") String usrId,
@RequestParam(defaultValue = "") String usrNm,
@RequestParam(defaultValue = "") String teamCd,
@RequestParam(defaultValue = "") String dutyCd,
@RequestParam(defaultValue = "N") String includeRetireYn,
@RequestParam(defaultValue = "") String includeWorkYn,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(wtimeService.getWtimeList(
currentUser.getCorpNo(), staYmd, endYmd,
usrId, usrNm, teamCd, dutyCd, includeRetireYn, includeWorkYn));
}
// ──────────────────────────────────────────────
// Wtime0030 - 월별 근무시간 집계
// ──────────────────────────────────────────────
@GetMapping("/0030")
public ApiResponse<List<Map<String, Object>>> getWstatList(
@RequestParam String staYmd,
@RequestParam String endYmd,
@RequestParam(defaultValue = "") String usrId,
@RequestParam(defaultValue = "") String usrNm,
@RequestParam(defaultValue = "") String teamCd,
@RequestParam(defaultValue = "") String dutyCd,
@RequestParam(defaultValue = "N") String includeRetireYn,
@AuthenticationPrincipal CurrentUser currentUser) {
return ApiResponse.ok(wtimeService.getWstatList(
currentUser.getCorpNo(), staYmd, endYmd,
usrId, usrNm, teamCd, dutyCd, includeRetireYn));
}
}

View File

@@ -0,0 +1,16 @@
package com.company.gw.wtime.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface WtimeMapper {
// Wtime0010 - 개인별 근무시간
List<Map<String, Object>> getWtimeList(Map<String, Object> param);
// Wtime0030 - 월별 근무시간 집계
List<Map<String, Object>> getWstatList(Map<String, Object> param);
}

View File

@@ -0,0 +1,59 @@
package com.company.gw.wtime.service;
import com.company.gw.wtime.mapper.WtimeMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class WtimeService {
private final WtimeMapper wtimeMapper;
// ──────────────────────────────────────────────
// Wtime0010 - 개인별 근무시간
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getWtimeList(String corpNo, String staYmd, String endYmd,
String usrId, String usrNm, String teamCd,
String dutyCd, String includeRetireYn,
String includeWorkYn) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("staYmd", staYmd);
p.put("endYmd", endYmd);
p.put("usrId", usrId);
p.put("usrNm", usrNm);
p.put("teamCd", teamCd);
p.put("dutyCd", dutyCd);
p.put("includeRetireYn", includeRetireYn != null ? includeRetireYn : "N");
p.put("includeWorkYn", includeWorkYn);
return wtimeMapper.getWtimeList(p);
}
// ──────────────────────────────────────────────
// Wtime0030 - 월별 근무시간 집계
// ──────────────────────────────────────────────
@Transactional(readOnly = true)
public List<Map<String, Object>> getWstatList(String corpNo, String staYmd, String endYmd,
String usrId, String usrNm, String teamCd,
String dutyCd, String includeRetireYn) {
Map<String, Object> p = new HashMap<>();
p.put("corpNo", corpNo);
p.put("staYmd", staYmd);
p.put("endYmd", endYmd);
p.put("usrId", usrId);
p.put("usrNm", usrNm);
p.put("teamCd", teamCd);
p.put("dutyCd", dutyCd);
p.put("includeRetireYn", includeRetireYn != null ? includeRetireYn : "N");
return wtimeMapper.getWstatList(p);
}
}

View File

@@ -0,0 +1,17 @@
# MariaDB 설정 (추후 전환용)
spring:
datasource:
url: ${DB_URL:jdbc:mariadb://localhost:3306/gw}
username: ${DB_USERNAME:}
password: ${DB_PASSWORD:}
driver-class-name: org.mariadb.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
mybatis:
configuration:
database-id: mariadb

View File

@@ -0,0 +1,13 @@
# MS SQL Server 설정 (현재 운영)
spring:
datasource:
url: ${DB_URL:jdbc:sqlserver://121.156.116.136:52785;databaseName=logins_test;encrypt=false;trustServerCertificate=true;sendStringParametersAsUnicode=false}
username: ${DB_USERNAME:logins}
password: ${DB_PASSWORD:ghkfkdahrfh40-8}
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000

View File

@@ -0,0 +1,17 @@
# Oracle DB 설정 (현재 운영)
spring:
datasource:
url: ${DB_URL:jdbc:oracle:thin:@localhost:1521:ORCL}
username: ${DB_USERNAME:}
password: ${DB_PASSWORD:}
driver-class-name: oracle.jdbc.OracleDriver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
mybatis:
configuration:
database-id: oracle

View File

@@ -0,0 +1,62 @@
spring:
application:
name: gw-backend
# Active Profile: mssql | mariadb
profiles:
active: mssql
# Redis
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
# RabbitMQ
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: ${RABBITMQ_PORT:5672}
username: ${RABBITMQ_USER:guest}
password: ${RABBITMQ_PASSWORD:guest}
# File Upload
servlet:
multipart:
max-file-size: 50MB
max-request-size: 100MB
# MyBatis
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 30
# JWT
jwt:
secret: ${JWT_SECRET:your-secret-key-must-be-at-least-256-bits-long-for-hs256}
access-token-expiry: 1800000 # 30분 (ms)
refresh-token-expiry: 604800000 # 7일 (ms)
# File Storage
file:
upload-path: ${FILE_UPLOAD_PATH:/data/uploads}
# Actuator
management:
endpoints:
web:
exposure:
include: health,info
base-path: /actuator
endpoint:
health:
show-details: when-authorized
# Logging
logging:
level:
com.company.gw: DEBUG
org.mybatis: DEBUG

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="gw-backend"/>
<!-- ── 패턴 ── -->
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- ── 콘솔 ── -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- ── 파일 (Rolling) ── -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/${APP_NAME}/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 날짜별 롤링, 최대 30일 보관 -->
<fileNamePattern>/var/log/${APP_NAME}/app.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- ── 에러 전용 파일 ── -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/${APP_NAME}/error.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/var/log/${APP_NAME}/error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- ── 레벨 설정 ── -->
<!-- 로컬 개발: 콘솔만, DEBUG (mssql/mariadb는 DB 프로파일이므로 local 여부로 판단) -->
<springProfile name="local">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.company.gw" level="DEBUG"/>
<logger name="org.mybatis" level="DEBUG"/>
<logger name="jdbc.sqltiming" level="DEBUG"/>
</springProfile>
<!-- 운영: 파일 + 콘솔, INFO -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.company.gw" level="INFO"/>
<logger name="org.mybatis" level="WARN"/>
<logger name="org.apache.ibatis" level="WARN"/>
</springProfile>
<!-- 기본 (local/prod 아닐 때) -->
<springProfile name="!local &amp;&amp; !prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.company.gw" level="INFO"/>
</springProfile>
</configuration>

View File

@@ -0,0 +1,339 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 원본: src/java/sql/biz/board/board0010_sql.xml
테이블: SX_GW0020(게시물), SX_GW0030(댓글), SX_GW0010(사용자) -->
<mapper namespace="com.company.gw.board.mapper.BoardMapper">
<!-- ===================== MSSQL (default) ===================== -->
<!-- 게시물 목록 (페이징) -->
<select id="getPostList" parameterType="map" resultType="map">
SELECT A.UNTY_BBS_SNO,
A.BBS_TITLE_NM,
A.INQR_CNT,
A.CMMT_CNT,
A.CTUSR_ID,
B.USR_NM AS CTUSR_NM,
CONVERT(VARCHAR, A.RGST_DT, 112) AS RGST_DT
FROM SX_GW0020 A
LEFT JOIN SX_GW0010 B ON B.CORP_NO = A.CORP_NO AND B.USR_ID = A.CTUSR_ID
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND (#{searchText} IS NULL OR #{searchText} = ''
OR A.BBS_TITLE_NM LIKE CONCAT('%', #{searchText}, '%')
OR CAST(A.BBS_CN AS VARCHAR(MAX)) LIKE CONCAT('%', #{searchText}, '%'))
ORDER BY A.RGST_DT DESC, A.UNTY_BBS_SNO DESC
OFFSET (#{pageNo} - 1) * #{pageSize} ROWS
FETCH NEXT #{pageSize} ROWS ONLY
</select>
<!-- 게시물 목록 건수 -->
<select id="getPostListCount" parameterType="map" resultType="long">
SELECT COUNT(1)
FROM SX_GW0020 A
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND (#{searchText} IS NULL OR #{searchText} = ''
OR A.BBS_TITLE_NM LIKE CONCAT('%', #{searchText}, '%')
OR CAST(A.BBS_CN AS VARCHAR(MAX)) LIKE CONCAT('%', #{searchText}, '%'))
</select>
<!-- 게시물 상세 -->
<select id="getPostInfo" parameterType="map" resultType="map">
SELECT A.UNTY_BBS_CD,
A.UNTY_BBS_SNO,
A.BBS_TITLE_NM,
A.BBS_CN,
A.INQR_CNT,
A.CMMT_CNT,
A.CTUSR_ID,
A.ETC_ATCH_NO,
A.PHOTO_ATCH_NO,
A.RGSTR_ID,
CONVERT(VARCHAR, A.RGST_DT, 120) AS RGST_DATE,
CONVERT(VARCHAR, A.UPD_DT, 120) AS UPD_DATE,
B.USR_NM AS CTUSR_NM
FROM SX_GW0020 A
LEFT JOIN SX_GW0010 B ON B.CORP_NO = A.CORP_NO AND B.USR_ID = A.CTUSR_ID
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND A.UNTY_BBS_SNO = #{untyBbsSno}
</select>
<!-- 게시물 등록 -->
<insert id="insertPost" parameterType="map">
INSERT INTO SX_GW0020 (
CORP_NO, UNTY_BBS_CD, UNTY_BBS_SNO,
BBS_TITLE_NM, BBS_CN, INQR_CNT,
CTUSR_ID, RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{untyBbsCd}, #{untyBbsSno},
#{bbsTitleNm}, #{bbsCn}, 0,
#{usrId}, #{usrId}, GETDATE(), #{usrId}, GETDATE()
)
</insert>
<!-- 게시물 수정 -->
<update id="updatePost" parameterType="map">
UPDATE SX_GW0020
SET BBS_TITLE_NM = #{bbsTitleNm},
BBS_CN = #{bbsCn},
MODID = #{usrId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<!-- 조회수 증가 -->
<update id="increaseInqrCnt" parameterType="map">
UPDATE SX_GW0020
SET INQR_CNT = ISNULL(INQR_CNT, 0) + 1
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<!-- 기타첨부 번호 업데이트 -->
<update id="updateEtcAtchNo" parameterType="map">
UPDATE SX_GW0020
SET ETC_ATCH_NO = #{etcAtchNo},
MODID = #{usrId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<!-- 사진첨부 번호 업데이트 -->
<update id="updatePhotoAtchNo" parameterType="map">
UPDATE SX_GW0020
SET PHOTO_ATCH_NO = #{photoAtchNo},
MODID = #{usrId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<!-- 댓글수 재계산 -->
<update id="updateCmmtCnt" parameterType="map">
UPDATE SX_GW0020
SET CMMT_CNT = (
SELECT COUNT(1) FROM SX_GW0030
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
),
MODID = #{usrId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<!-- 게시물 삭제 -->
<delete id="deletePost" parameterType="map">
DELETE FROM SX_GW0020
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</delete>
<!-- 댓글 목록 -->
<select id="getCommentList" parameterType="map" resultType="map">
SELECT A.CMMT_SNO,
A.CMMT_CN,
A.CMMT_CTUSR_ID,
B.USR_NM AS CMMT_CTUSR_NM,
CONVERT(VARCHAR, A.RGST_DT, 120) AS RGST_DATE
FROM SX_GW0030 A
LEFT JOIN SX_GW0010 B ON B.CORP_NO = A.CORP_NO AND B.USR_ID = A.CMMT_CTUSR_ID
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND A.UNTY_BBS_SNO = #{untyBbsSno}
ORDER BY A.RGST_DT, A.CMMT_SNO
</select>
<!-- 댓글 상세 -->
<select id="getCommentInfo" parameterType="map" resultType="map">
SELECT A.CMMT_SNO, A.CMMT_CN, A.CMMT_CTUSR_ID
FROM SX_GW0030 A
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND A.UNTY_BBS_SNO = #{untyBbsSno}
AND A.CMMT_SNO = #{cmmtSno}
</select>
<!-- 댓글 등록 -->
<insert id="insertComment" parameterType="map">
INSERT INTO SX_GW0030 (
CORP_NO, UNTY_BBS_CD, UNTY_BBS_SNO, CMMT_SNO,
CMMT_CN, CMMT_CTUSR_ID, RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{untyBbsCd}, #{untyBbsSno}, #{cmmtSno},
#{cmmtCn}, #{usrId}, #{usrId}, GETDATE(), #{usrId}, GETDATE()
)
</insert>
<!-- 댓글 삭제 -->
<delete id="deleteComment" parameterType="map">
DELETE FROM SX_GW0030
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
AND CMMT_SNO = #{cmmtSno}
</delete>
<!-- 게시물 전체 댓글 삭제 (게시물 삭제시) -->
<delete id="deleteCommentsByPost" parameterType="map">
DELETE FROM SX_GW0030
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</delete>
<!-- ===================== MariaDB ===================== -->
<select id="getPostList" parameterType="map" resultType="map" databaseId="mariadb">
SELECT A.UNTY_BBS_SNO,
A.BBS_TITLE_NM,
A.INQR_CNT,
A.CMMT_CNT,
A.CTUSR_ID,
B.USR_NM AS CTUSR_NM,
DATE_FORMAT(A.RGST_DT, '%Y%m%d') AS RGST_DT
FROM SX_GW0020 A
LEFT JOIN SX_GW0010 B ON B.CORP_NO = A.CORP_NO AND B.USR_ID = A.CTUSR_ID
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND (#{searchText} IS NULL OR #{searchText} = ''
OR A.BBS_TITLE_NM LIKE CONCAT('%', #{searchText}, '%')
OR CAST(A.BBS_CN AS CHAR) LIKE CONCAT('%', #{searchText}, '%'))
ORDER BY A.RGST_DT DESC, A.UNTY_BBS_SNO DESC
LIMIT #{pageSize} OFFSET (#{pageNo} - 1) * #{pageSize}
</select>
<select id="getPostListCount" parameterType="map" resultType="long" databaseId="mariadb">
SELECT COUNT(1)
FROM SX_GW0020 A
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND (#{searchText} IS NULL OR #{searchText} = ''
OR A.BBS_TITLE_NM LIKE CONCAT('%', #{searchText}, '%')
OR CAST(A.BBS_CN AS CHAR) LIKE CONCAT('%', #{searchText}, '%'))
</select>
<select id="getPostInfo" parameterType="map" resultType="map" databaseId="mariadb">
SELECT A.UNTY_BBS_CD,
A.UNTY_BBS_SNO,
A.BBS_TITLE_NM,
A.BBS_CN,
A.INQR_CNT,
A.CMMT_CNT,
A.CTUSR_ID,
A.ETC_ATCH_NO,
A.PHOTO_ATCH_NO,
A.RGSTR_ID,
DATE_FORMAT(A.RGST_DT, '%Y-%m-%d %H:%i:%s') AS RGST_DATE,
DATE_FORMAT(A.UPD_DT, '%Y-%m-%d %H:%i:%s') AS UPD_DATE,
B.USR_NM AS CTUSR_NM
FROM SX_GW0020 A
LEFT JOIN SX_GW0010 B ON B.CORP_NO = A.CORP_NO AND B.USR_ID = A.CTUSR_ID
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND A.UNTY_BBS_SNO = #{untyBbsSno}
</select>
<insert id="insertPost" parameterType="map" databaseId="mariadb">
INSERT INTO SX_GW0020 (
CORP_NO, UNTY_BBS_CD, UNTY_BBS_SNO,
BBS_TITLE_NM, BBS_CN, INQR_CNT,
CTUSR_ID, RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{untyBbsCd}, #{untyBbsSno},
#{bbsTitleNm}, #{bbsCn}, 0,
#{usrId}, #{usrId}, NOW(), #{usrId}, NOW()
)
</insert>
<update id="updatePost" parameterType="map" databaseId="mariadb">
UPDATE SX_GW0020
SET BBS_TITLE_NM = #{bbsTitleNm},
BBS_CN = #{bbsCn},
MODID = #{usrId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<update id="increaseInqrCnt" parameterType="map" databaseId="mariadb">
UPDATE SX_GW0020
SET INQR_CNT = IFNULL(INQR_CNT, 0) + 1
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<update id="updateEtcAtchNo" parameterType="map" databaseId="mariadb">
UPDATE SX_GW0020
SET ETC_ATCH_NO = #{etcAtchNo},
MODID = #{usrId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<update id="updatePhotoAtchNo" parameterType="map" databaseId="mariadb">
UPDATE SX_GW0020
SET PHOTO_ATCH_NO = #{photoAtchNo},
MODID = #{usrId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<update id="updateCmmtCnt" parameterType="map" databaseId="mariadb">
UPDATE SX_GW0020
SET CMMT_CNT = (
SELECT COUNT(1) FROM SX_GW0030
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
),
MODID = #{usrId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND UNTY_BBS_CD = #{untyBbsCd}
AND UNTY_BBS_SNO = #{untyBbsSno}
</update>
<insert id="insertComment" parameterType="map" databaseId="mariadb">
INSERT INTO SX_GW0030 (
CORP_NO, UNTY_BBS_CD, UNTY_BBS_SNO, CMMT_SNO,
CMMT_CN, CMMT_CTUSR_ID, RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{untyBbsCd}, #{untyBbsSno}, #{cmmtSno},
#{cmmtCn}, #{usrId}, #{usrId}, NOW(), #{usrId}, NOW()
)
</insert>
<select id="getCommentList" parameterType="map" resultType="map" databaseId="mariadb">
SELECT A.CMMT_SNO,
A.CMMT_CN,
A.CMMT_CTUSR_ID,
B.USR_NM AS CMMT_CTUSR_NM,
DATE_FORMAT(A.RGST_DT, '%Y-%m-%d %H:%i:%s') AS RGST_DATE
FROM SX_GW0030 A
LEFT JOIN SX_GW0010 B ON B.CORP_NO = A.CORP_NO AND B.USR_ID = A.CMMT_CTUSR_ID
WHERE A.CORP_NO = #{corpNo}
AND A.UNTY_BBS_CD = #{untyBbsCd}
AND A.UNTY_BBS_SNO = #{untyBbsSno}
ORDER BY A.RGST_DT, A.CMMT_SNO
</select>
</mapper>

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.company.gw.common.mapper.AttachMapper">
<!-- ===================== MSSQL (default) ===================== -->
<select id="getFileInfo" parameterType="string" resultType="map">
SELECT ATCHFILE_NO, ATCH_NO, ATCH_FILE_PATH_NM, ATCH_FILE_NM,
ATCH_TYPE_NM, ATCH_FILE_MG, TBL_COL_NM, RGSTR_ID
FROM SX_CO0050
WHERE ATCHFILE_NO = #{atchfileNo}
</select>
<select id="getFileInfoListByAtchNo" parameterType="string" resultType="map">
SELECT ATCHFILE_NO, ATCH_NO, ATCH_FILE_PATH_NM, ATCH_FILE_NM,
ATCH_TYPE_NM, ATCH_FILE_MG, TBL_COL_NM
FROM SX_CO0050
WHERE ATCH_NO = #{atchNo}
AND DEL_DT IS NULL
ORDER BY RGST_DT
</select>
<insert id="insertFileInfo" parameterType="map">
INSERT INTO SX_CO0050 (
ATCHFILE_NO, ATCH_NO, ATCH_FILE_PATH_NM, ATCH_FILE_NM,
ATCH_TYPE_NM, ATCH_FILE_MG, DEL_DT, TBL_COL_NM,
RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{atchfileNo}, #{atchNo}, #{atchFilePathNm}, #{atchFileNm},
#{atchTypeNm}, #{atchFileMg}, NULL, #{tblColNm},
#{rgstrId}, GETDATE(), #{rgstrId}, GETDATE()
)
</insert>
<delete id="deleteFileInfo" parameterType="string">
DELETE FROM SX_CO0050 WHERE ATCHFILE_NO = #{atchfileNo}
</delete>
<update id="updateTblColNmByAtchNo" parameterType="map">
UPDATE SX_CO0050
SET TBL_COL_NM = #{tblColNm}
WHERE ATCH_NO = #{atchNo}
</update>
<update id="updateTblColNmByAtchfileNo" parameterType="map">
UPDATE SX_CO0050
SET TBL_COL_NM = #{tblColNm}
WHERE ATCHFILE_NO = #{atchfileNo}
</update>
<select id="getNeedDeleteFileList" resultType="map">
SELECT ATCHFILE_NO, ATCH_FILE_PATH_NM
FROM SX_CO0050
WHERE NULLIF(TBL_COL_NM, '') IS NULL
AND RGST_DT &lt; DATEADD(HOUR, -6, GETDATE())
</select>
<!-- ===================== MariaDB ===================== -->
<select id="getFileInfo" parameterType="string" resultType="map" databaseId="mariadb">
SELECT ATCHFILE_NO, ATCH_NO, ATCH_FILE_PATH_NM, ATCH_FILE_NM,
ATCH_TYPE_NM, ATCH_FILE_MG, TBL_COL_NM, RGSTR_ID
FROM SX_CO0050
WHERE ATCHFILE_NO = #{atchfileNo}
</select>
<select id="getFileInfoListByAtchNo" parameterType="string" resultType="map" databaseId="mariadb">
SELECT ATCHFILE_NO, ATCH_NO, ATCH_FILE_PATH_NM, ATCH_FILE_NM,
ATCH_TYPE_NM, ATCH_FILE_MG, TBL_COL_NM
FROM SX_CO0050
WHERE ATCH_NO = #{atchNo}
AND DEL_DT IS NULL
ORDER BY RGST_DT
</select>
<insert id="insertFileInfo" parameterType="map" databaseId="mariadb">
INSERT INTO SX_CO0050 (
ATCHFILE_NO, ATCH_NO, ATCH_FILE_PATH_NM, ATCH_FILE_NM,
ATCH_TYPE_NM, ATCH_FILE_MG, DEL_DT, TBL_COL_NM,
RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{atchfileNo}, #{atchNo}, #{atchFilePathNm}, #{atchFileNm},
#{atchTypeNm}, #{atchFileMg}, NULL, #{tblColNm},
#{rgstrId}, NOW(), #{rgstrId}, NOW()
)
</insert>
<delete id="deleteFileInfo" parameterType="string" databaseId="mariadb">
DELETE FROM SX_CO0050 WHERE ATCHFILE_NO = #{atchfileNo}
</delete>
<update id="updateTblColNmByAtchNo" parameterType="map" databaseId="mariadb">
UPDATE SX_CO0050
SET TBL_COL_NM = #{tblColNm}
WHERE ATCH_NO = #{atchNo}
</update>
<update id="updateTblColNmByAtchfileNo" parameterType="map" databaseId="mariadb">
UPDATE SX_CO0050
SET TBL_COL_NM = #{tblColNm}
WHERE ATCHFILE_NO = #{atchfileNo}
</update>
<select id="getNeedDeleteFileList" resultType="map" databaseId="mariadb">
SELECT ATCHFILE_NO, ATCH_FILE_PATH_NM
FROM SX_CO0050
WHERE (TBL_COL_NM IS NULL OR TBL_COL_NM = '')
AND RGST_DT &lt; DATE_SUB(NOW(), INTERVAL 6 HOUR)
</select>
</mapper>

View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
공통코드 조회 SQL
테이블: SX_CO0040 (공통코드), SX_GW0010 (직원), SX_CO0070 (근무코드)
-->
<mapper namespace="com.company.gw.common.mapper.CodeMapper">
<!-- ──────────────────────────────────────────────
공통코드 목록 (드롭다운용 간략버전)
────────────────────────────────────────────── -->
<select id="getCodeList" parameterType="map" resultType="map"><![CDATA[
SELECT A.COMM_CD AS code,
A.COMM_CD_NM AS name,
A.COMM_CD_USE_YN AS useYn
FROM SX_CO0040 A
WHERE A.CORP_NO = #{corpNo}
AND A.COMM_CL_CD = #{commClCd}
AND (ISNULL(#{useYn}, '') = '' OR A.COMM_CD_USE_YN = #{useYn})
ORDER BY A.COMM_CD_DSPLY_ORDR, A.COMM_CD
]]></select>
<select id="getCodeList" databaseId="mariadb" parameterType="map" resultType="map"><![CDATA[
SELECT A.COMM_CD AS code,
A.COMM_CD_NM AS name,
A.COMM_CD_USE_YN AS useYn
FROM SX_CO0040 A
WHERE A.CORP_NO = #{corpNo}
AND A.COMM_CL_CD = #{commClCd}
AND (IFNULL(#{useYn}, '') = '' OR A.COMM_CD_USE_YN = #{useYn})
ORDER BY A.COMM_CD_DSPLY_ORDR, A.COMM_CD
]]></select>
<!-- ──────────────────────────────────────────────
공통코드 전체 정보
────────────────────────────────────────────── -->
<select id="getCodeListFull" parameterType="map" resultType="map"><![CDATA[
SELECT A.COMM_CD AS code,
A.COMM_CD_NM AS name,
A.COMM_CL_CD,
A.COMM_CD_DSPLY_ORDR,
A.COMM_CD_USE_YN,
A.COMM_CD_TYPE_VAL,
A.COMM_CD_DSCRPT,
A.PROP_CD1,
A.PROP_CD2,
A.PROP_CD3,
A.PROP_CD4,
A.PROP_CD5
FROM SX_CO0040 A
WHERE A.CORP_NO = #{corpNo}
AND A.COMM_CL_CD = #{commClCd}
AND (ISNULL(#{useYn}, '') = '' OR A.COMM_CD_USE_YN = #{useYn})
ORDER BY A.COMM_CD_DSPLY_ORDR, A.COMM_CD
]]></select>
<select id="getCodeListFull" databaseId="mariadb" parameterType="map" resultType="map"><![CDATA[
SELECT A.COMM_CD AS code,
A.COMM_CD_NM AS name,
A.COMM_CL_CD,
A.COMM_CD_DSPLY_ORDR,
A.COMM_CD_USE_YN,
A.COMM_CD_TYPE_VAL,
A.COMM_CD_DSCRPT,
A.PROP_CD1,
A.PROP_CD2,
A.PROP_CD3,
A.PROP_CD4,
A.PROP_CD5
FROM SX_CO0040 A
WHERE A.CORP_NO = #{corpNo}
AND A.COMM_CL_CD = #{commClCd}
AND (IFNULL(#{useYn}, '') = '' OR A.COMM_CD_USE_YN = #{useYn})
ORDER BY A.COMM_CD_DSPLY_ORDR, A.COMM_CD
]]></select>
<!-- ──────────────────────────────────────────────
직원 목록 (결재자 선택 등 팝업용)
────────────────────────────────────────────── -->
<select id="searchUser" parameterType="map" resultType="map"><![CDATA[
SELECT TOP 100
A.USR_ID,
A.USR_NM,
A.DUTY_CD,
A.TEAM_CD,
A.APPR_YN
FROM SX_GW0010 A
WHERE A.CORP_NO = #{corpNo}
AND ISNULL(A.RETIREMENT_DATE, '') = ''
AND (ISNULL(#{searchText}, '') = '' OR (
A.USR_ID LIKE CONCAT('%', #{searchText}, '%') OR
A.USR_NM LIKE CONCAT('%', #{searchText}, '%')))
AND (#{apprOnly} != 'Y' OR A.APPR_YN = 'Y')
ORDER BY A.USR_NM
]]></select>
<select id="searchUser" databaseId="mariadb" parameterType="map" resultType="map"><![CDATA[
SELECT A.USR_ID,
A.USR_NM,
A.DUTY_CD,
A.TEAM_CD,
A.APPR_YN
FROM SX_GW0010 A
WHERE A.CORP_NO = #{corpNo}
AND IFNULL(A.RETIREMENT_DATE, '') = ''
AND (IFNULL(#{searchText}, '') = '' OR (
A.USR_ID LIKE CONCAT('%', #{searchText}, '%') OR
A.USR_NM LIKE CONCAT('%', #{searchText}, '%')))
AND (#{apprOnly} != 'Y' OR A.APPR_YN = 'Y')
ORDER BY A.USR_NM
LIMIT 100
]]></select>
<!-- ──────────────────────────────────────────────
근무코드 목록
────────────────────────────────────────────── -->
<select id="getWorkCdList" parameterType="map" resultType="map"><![CDATA[
SELECT A.WORK_CD AS code,
A.WORK_CD_TITLE_NM AS name,
A.WORK_CD,
A.WORK_CD_TITLE_NM,
A.GOTOWORK_TM_NM,
A.GETOFFWORK_TM_NM,
A.WORK_CD_USE_YN
FROM SX_CO0070 A
WHERE A.CORP_NO = #{corpNo}
AND (ISNULL(#{useYn}, '') = '' OR A.WORK_CD_USE_YN = #{useYn})
ORDER BY A.WORK_CD
]]></select>
<select id="getWorkCdList" databaseId="mariadb" parameterType="map" resultType="map"><![CDATA[
SELECT A.WORK_CD AS code,
A.WORK_CD_TITLE_NM AS name,
A.WORK_CD,
A.WORK_CD_TITLE_NM,
A.GOTOWORK_TM_NM,
A.GETOFFWORK_TM_NM,
A.WORK_CD_USE_YN
FROM SX_CO0070 A
WHERE A.CORP_NO = #{corpNo}
AND (IFNULL(#{useYn}, '') = '' OR A.WORK_CD_USE_YN = #{useYn})
ORDER BY A.WORK_CD
]]></select>
</mapper>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 원본: src/java/sql/common/common_sql.xml -->
<mapper namespace="com.company.gw.common.mapper.CommonMapper">
<!-- 시퀀스 증가 (MERGE = SQL Server / MariaDB INSERT ON DUPLICATE KEY) -->
<update id="increaseSequence" parameterType="String">
MERGE INTO SX_CO0060 T
USING (SELECT 1 AS DUMMY) S
ON (T.SEQUENCE_NM = #{sequenceNm})
WHEN NOT MATCHED THEN
INSERT (SEQUENCE_NM, SEQUENCE_VAL) VALUES (#{sequenceNm}, 1)
WHEN MATCHED THEN
UPDATE SET SEQUENCE_VAL = SEQUENCE_VAL + 1;
</update>
<update id="increaseSequence" parameterType="String" databaseId="mariadb">
INSERT INTO SX_CO0060 (SEQUENCE_NM, SEQUENCE_VAL)
VALUES (#{sequenceNm}, 1)
ON DUPLICATE KEY UPDATE SEQUENCE_VAL = SEQUENCE_VAL + 1
</update>
<select id="getSequenceVal" parameterType="String" resultType="Long">
SELECT SEQUENCE_VAL FROM SX_CO0060 WHERE SEQUENCE_NM = #{sequenceNm}
</select>
</mapper>

View File

@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
원본: src/java/sql/common/menu_sql.xml (SX_CO0080, SX_CO0090)
-->
<mapper namespace="com.company.gw.common.mapper.MenuMapper">
<!-- 사용자 권한 기반 메뉴 트리 조회 (재귀 CTE) -->
<select id="getMenuList" parameterType="map" resultType="com.company.gw.common.dto.MenuDto">
WITH MENU_TREE AS (
SELECT A.MENU_NO AS upMenuNo,
A.CORP_NO,
A.MENU_NO,
A.UPPER_MENU_NO,
A.MENU_NM,
A.URL,
A.RM,
A.MENU_PROP,
A.MENU_ORDR,
A.MENU_USE_YN,
1 AS lvl,
CAST('/' + RIGHT('00000' + CONVERT(VARCHAR, A.MENU_ORDR), 5) AS VARCHAR(MAX)) AS sortPath
FROM SX_CO0080 A
JOIN SX_CO0090 B
ON B.CORP_NO = A.CORP_NO
AND B.MENU_NO = A.MENU_NO
AND B.MENU_AUTH_CD = #{menuAuthCd}
WHERE A.CORP_NO = #{corpNo}
AND A.MENU_USE_YN = 'Y'
AND NULLIF(A.UPPER_MENU_NO, '') IS NULL
UNION ALL
SELECT PARENT.upMenuNo,
A.CORP_NO,
A.MENU_NO,
A.UPPER_MENU_NO,
A.MENU_NM,
A.URL,
A.RM,
A.MENU_PROP,
A.MENU_ORDR,
A.MENU_USE_YN,
PARENT.lvl + 1 AS lvl,
PARENT.sortPath + CAST('/' + RIGHT('00000' + CONVERT(VARCHAR, A.MENU_ORDR), 5) AS VARCHAR(MAX)) AS sortPath
FROM SX_CO0080 A
JOIN SX_CO0090 B
ON B.CORP_NO = A.CORP_NO
AND B.MENU_NO = A.MENU_NO
AND B.MENU_AUTH_CD = #{menuAuthCd}
JOIN MENU_TREE PARENT
ON PARENT.CORP_NO = A.CORP_NO
AND PARENT.MENU_NO = A.UPPER_MENU_NO
WHERE A.CORP_NO = #{corpNo}
AND A.MENU_USE_YN = 'Y'
)
SELECT MENU_NO AS menuNo,
UPPER_MENU_NO AS upperMenuNo,
MENU_NM AS menuNm,
URL,
RM,
MENU_PROP AS menuProp,
MENU_ORDR AS menuOrdr,
MENU_USE_YN AS menuUseYn,
lvl
FROM MENU_TREE
WHERE CORP_NO = #{corpNo}
ORDER BY sortPath
</select>
<select id="getMenuList" parameterType="map" resultType="com.company.gw.common.dto.MenuDto" databaseId="mariadb">
WITH MENU_TREE AS (
SELECT A.MENU_NO AS upMenuNo,
A.CORP_NO,
A.MENU_NO,
A.UPPER_MENU_NO,
A.MENU_NM,
A.URL,
A.RM,
A.MENU_PROP,
A.MENU_ORDR,
A.MENU_USE_YN,
1 AS lvl,
CONCAT('/', LPAD(A.MENU_ORDR, 5, '0')) AS sortPath
FROM SX_CO0080 A
JOIN SX_CO0090 B
ON B.CORP_NO = A.CORP_NO
AND B.MENU_NO = A.MENU_NO
AND B.MENU_AUTH_CD = #{menuAuthCd}
WHERE A.CORP_NO = #{corpNo}
AND A.MENU_USE_YN = 'Y'
AND NULLIF(A.UPPER_MENU_NO, '') IS NULL
UNION ALL
SELECT PARENT.upMenuNo,
A.CORP_NO,
A.MENU_NO,
A.UPPER_MENU_NO,
A.MENU_NM,
A.URL,
A.RM,
A.MENU_PROP,
A.MENU_ORDR,
A.MENU_USE_YN,
PARENT.lvl + 1 AS lvl,
CONCAT(PARENT.sortPath, '/', LPAD(A.MENU_ORDR, 5, '0')) AS sortPath
FROM SX_CO0080 A
JOIN SX_CO0090 B
ON B.CORP_NO = A.CORP_NO
AND B.MENU_NO = A.MENU_NO
AND B.MENU_AUTH_CD = #{menuAuthCd}
JOIN MENU_TREE PARENT
ON PARENT.CORP_NO = A.CORP_NO
AND PARENT.MENU_NO = A.UPPER_MENU_NO
WHERE A.CORP_NO = #{corpNo}
AND A.MENU_USE_YN = 'Y'
)
SELECT MENU_NO AS menuNo,
UPPER_MENU_NO AS upperMenuNo,
MENU_NM AS menuNm,
URL,
RM,
MENU_PROP AS menuProp,
MENU_ORDR AS menuOrdr,
MENU_USE_YN AS menuUseYn,
lvl
FROM MENU_TREE
WHERE CORP_NO = #{corpNo}
ORDER BY sortPath
</select>
<!-- 컨트롤러-롤 매핑 목록 (접근 권한 체크용) -->
<select id="getControllerRoleList" parameterType="map" resultType="com.company.gw.common.dto.ControllerRoleDto">
SELECT DISTINCT
A.CONTROLLER AS controller,
B.MENU_AUTH_CD AS role,
B.FUNC_AUTH_CN AS funcAuthCn
FROM SX_CO0080 A
JOIN SX_CO0090 B
ON B.CORP_NO = A.CORP_NO
AND B.MENU_NO = A.MENU_NO
WHERE A.CORP_NO = #{corpNo}
AND A.MENU_USE_YN = 'Y'
AND NULLIF(A.CONTROLLER, '') IS NOT NULL
ORDER BY A.CONTROLLER
</select>
</mapper>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
원본: src/java/sql/common/security_sql.xml (SX_GW0130)
-->
<mapper namespace="com.company.gw.common.mapper.SecurityMapper">
<!-- 사용자 보유 ROLE 목록 -->
<select id="getUserRoleList" parameterType="map" resultType="String">
SELECT MENU_AUTH_CD AS roleCode
FROM SX_GW0130
WHERE CORP_NO = #{corpNo}
AND USR_ID = #{usrId}
ORDER BY MENU_AUTH_CD
</select>
</mapper>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
원본: src/java/sql/common/user_sql.xml (SX_GW0010)
-->
<mapper namespace="com.company.gw.common.mapper.UserMapper">
<!-- 사용자 정보 by USR_ID -->
<select id="getUserInfo" parameterType="String" resultType="com.company.gw.common.dto.UserDto">
SELECT CORP_NO,
USR_ID,
USR_NM,
LOGIN_ID,
DUTY_CD
FROM SX_GW0010
WHERE USR_ID = #{usrId}
</select>
<!-- 사용자 정보 by LOGIN_ID (비밀번호 포함) - 로그인용 -->
<select id="getUserInfoWithPwd" parameterType="String" resultType="com.company.gw.common.dto.UserDto">
SELECT CORP_NO,
USR_ID,
USR_NM,
LOGIN_ID,
PW,
DUTY_CD,
RETIREMENT_DATE
FROM SX_GW0010
WHERE LOGIN_ID = #{loginId}
</select>
<!-- 비밀번호 변경 -->
<update id="updatePassword">
UPDATE SX_GW0010
SET PW = #{newPw}
WHERE USR_ID = #{usrId}
</update>
</mapper>

View File

@@ -0,0 +1,246 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 원본: src/java/sql/biz/envset/envset0010_sql.xml (SX_GW0010) -->
<mapper namespace="com.company.gw.envset.mapper.UserManageMapper">
<!-- 직원 목록 (페이징) -->
<select id="getUserList" parameterType="map" resultType="com.company.gw.envset.dto.UserListDto">
SELECT A.USR_ID,
A.USR_NM,
A.TEAM_CD,
A.DUTY_CD,
A.USR_TELNO,
A.MTEL_NO,
A.RETIREMENT_DATE
FROM SX_GW0010 A
WHERE A.CORP_NO = #{corpNo}
<if test="usrNm != null and usrNm != ''">AND A.USR_NM LIKE CONCAT('%', #{usrNm}, '%')</if>
<if test="teamCd != null and teamCd != ''">AND A.TEAM_CD = #{teamCd}</if>
<if test="dutyCd != null and dutyCd != ''">AND A.DUTY_CD = #{dutyCd}</if>
<if test="apprYn != null and apprYn != ''">AND A.APPR_YN = #{apprYn}</if>
AND A.USR_ID != '00001'
AND (#{includeRetireYn} = 'Y' OR NULLIF(A.RETIREMENT_DATE, '') IS NULL)
ORDER BY A.${userOrderBy}, A.USR_ID
OFFSET (#{page} - 1) * #{size} ROWS
FETCH NEXT #{size} ROWS ONLY
</select>
<select id="getUserList" parameterType="map" resultType="com.company.gw.envset.dto.UserListDto" databaseId="mariadb">
SELECT A.USR_ID,
A.USR_NM,
A.TEAM_CD,
A.DUTY_CD,
A.USR_TELNO,
A.MTEL_NO,
A.RETIREMENT_DATE
FROM SX_GW0010 A
WHERE A.CORP_NO = #{corpNo}
<if test="usrNm != null and usrNm != ''">AND A.USR_NM LIKE CONCAT('%', #{usrNm}, '%')</if>
<if test="teamCd != null and teamCd != ''">AND A.TEAM_CD = #{teamCd}</if>
<if test="dutyCd != null and dutyCd != ''">AND A.DUTY_CD = #{dutyCd}</if>
<if test="apprYn != null and apprYn != ''">AND A.APPR_YN = #{apprYn}</if>
AND A.USR_ID != '00001'
AND (#{includeRetireYn} = 'Y' OR NULLIF(A.RETIREMENT_DATE, '') IS NULL)
ORDER BY A.${userOrderBy}, A.USR_ID
LIMIT #{size} OFFSET (#{page} - 1) * #{size}
</select>
<!-- 직원 목록 건수 -->
<select id="getUserListCount" parameterType="map" resultType="long">
SELECT COUNT(1)
FROM SX_GW0010 A
WHERE A.CORP_NO = #{corpNo}
<if test="usrNm != null and usrNm != ''">AND A.USR_NM LIKE CONCAT('%', #{usrNm}, '%')</if>
<if test="teamCd != null and teamCd != ''">AND A.TEAM_CD = #{teamCd}</if>
<if test="dutyCd != null and dutyCd != ''">AND A.DUTY_CD = #{dutyCd}</if>
<if test="apprYn != null and apprYn != ''">AND A.APPR_YN = #{apprYn}</if>
AND A.USR_ID != '00001'
AND (#{includeRetireYn} = 'Y' OR NULLIF(A.RETIREMENT_DATE, '') IS NULL)
</select>
<!-- 직원 상세 -->
<select id="getUserDetail" parameterType="map" resultType="com.company.gw.envset.dto.UserDetailDto">
SELECT A.CORP_NO,
A.USR_ID,
A.USR_SORT_ORDR,
A.USR_NM,
A.LOGIN_ID,
A.DUTY_CD,
A.BRTHDY_DATE,
A.USR_TELNO,
A.MTEL_NO,
A.EMAIL,
A.BASE_ADRS,
A.GUSO_ADRS,
A.GUSO_TELNO,
A.JOINCP_DATE,
A.TEAM_CD,
A.DISML_DATE,
A.RETIREMENT_DATE,
A.SPCLT_ARTC_CN,
A.PHOTO_ATCHFILE_NO,
A.SPMT_EMAIL,
A.SPMT_MTEL_NO,
A.APPR_YN,
A.FINAL_SCHSP_NM
FROM SX_GW0010 A
WHERE A.CORP_NO = #{corpNo}
AND A.USR_ID = #{usrId}
</select>
<!-- 로그인ID 중복 체크 -->
<select id="getLoginIdCount" parameterType="map" resultType="int">
SELECT COUNT(1)
FROM SX_GW0010
WHERE LOGIN_ID = #{loginId}
AND CORP_NO != #{corpNo}
AND USR_ID != #{usrId}
</select>
<!-- 직원 등록 -->
<insert id="insertUser" parameterType="com.company.gw.envset.dto.UserSaveDto">
INSERT INTO SX_GW0010 (
CORP_NO, USR_ID, USR_SORT_ORDR, USR_NM, PW,
DUTY_CD, BRTHDY_DATE, USR_TELNO, MTEL_NO, EMAIL,
BASE_ADRS, GUSO_ADRS, GUSO_TELNO, JOINCP_DATE, TEAM_CD,
DISML_DATE, RETIREMENT_DATE, SPCLT_ARTC_CN,
RGSTR_ID, RGST_DT, MODID, UPD_DT,
LOGIN_ID, SPMT_EMAIL, SPMT_MTEL_NO, RRNO,
APPR_YN, FINAL_SCHSP_NM
) VALUES (
#{corpNo}, #{usrId}, #{usrSortOrdr}, #{usrNm}, #{pw},
#{dutyCd}, #{brthDyDate}, #{usrTelno}, #{mtelNo}, #{email},
#{baseAdrs}, #{gusoAdrs}, #{gusoTelno}, #{joinCpDate}, #{teamCd},
#{dismlDate}, #{retirementDate}, #{spkltArtcCn},
#{rgstrId}, GETDATE(), #{rgstrId}, GETDATE(),
#{loginId}, #{spmtEmail}, #{spmtMtelNo}, #{rrno},
ISNULL(NULLIF(#{apprYn}, ''), 'N'), #{finalSchspNm}
)
</insert>
<insert id="insertUser" parameterType="com.company.gw.envset.dto.UserSaveDto" databaseId="mariadb">
INSERT INTO SX_GW0010 (
CORP_NO, USR_ID, USR_SORT_ORDR, USR_NM, PW,
DUTY_CD, BRTHDY_DATE, USR_TELNO, MTEL_NO, EMAIL,
BASE_ADRS, GUSO_ADRS, GUSO_TELNO, JOINCP_DATE, TEAM_CD,
DISML_DATE, RETIREMENT_DATE, SPCLT_ARTC_CN,
RGSTR_ID, RGST_DT, MODID, UPD_DT,
LOGIN_ID, SPMT_EMAIL, SPMT_MTEL_NO, RRNO,
APPR_YN, FINAL_SCHSP_NM
) VALUES (
#{corpNo}, #{usrId}, #{usrSortOrdr}, #{usrNm}, #{pw},
#{dutyCd}, #{brthDyDate}, #{usrTelno}, #{mtelNo}, #{email},
#{baseAdrs}, #{gusoAdrs}, #{gusoTelno}, #{joinCpDate}, #{teamCd},
#{dismlDate}, #{retirementDate}, #{spkltArtcCn},
#{rgstrId}, NOW(), #{rgstrId}, NOW(),
#{loginId}, #{spmtEmail}, #{spmtMtelNo}, #{rrno},
IFNULL(NULLIF(#{apprYn}, ''), 'N'), #{finalSchspNm}
)
</insert>
<!-- 직원 수정 -->
<update id="updateUser" parameterType="com.company.gw.envset.dto.UserSaveDto">
UPDATE SX_GW0010
SET USR_SORT_ORDR = #{usrSortOrdr},
USR_NM = #{usrNm},
DUTY_CD = #{dutyCd},
BRTHDY_DATE = #{brthDyDate},
USR_TELNO = #{usrTelno},
MTEL_NO = #{mtelNo},
EMAIL = #{email},
BASE_ADRS = #{baseAdrs},
GUSO_ADRS = #{gusoAdrs},
GUSO_TELNO = #{gusoTelno},
JOINCP_DATE = #{joinCpDate},
TEAM_CD = #{teamCd},
DISML_DATE = #{dismlDate},
RETIREMENT_DATE = #{retirementDate},
SPCLT_ARTC_CN = #{spkltArtcCn},
LOGIN_ID = #{loginId},
SPMT_EMAIL = #{spmtEmail},
SPMT_MTEL_NO = #{spmtMtelNo},
RRNO = #{rrno},
APPR_YN = ISNULL(NULLIF(#{apprYn}, ''), 'N'),
FINAL_SCHSP_NM = #{finalSchspNm},
MODID = #{modId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND USR_ID = #{usrId}
</update>
<update id="updateUser" parameterType="com.company.gw.envset.dto.UserSaveDto" databaseId="mariadb">
UPDATE SX_GW0010
SET USR_SORT_ORDR = #{usrSortOrdr},
USR_NM = #{usrNm},
DUTY_CD = #{dutyCd},
BRTHDY_DATE = #{brthDyDate},
USR_TELNO = #{usrTelno},
MTEL_NO = #{mtelNo},
EMAIL = #{email},
BASE_ADRS = #{baseAdrs},
GUSO_ADRS = #{gusoAdrs},
GUSO_TELNO = #{gusoTelno},
JOINCP_DATE = #{joinCpDate},
TEAM_CD = #{teamCd},
DISML_DATE = #{dismlDate},
RETIREMENT_DATE = #{retirementDate},
SPCLT_ARTC_CN = #{spkltArtcCn},
LOGIN_ID = #{loginId},
SPMT_EMAIL = #{spmtEmail},
SPMT_MTEL_NO = #{spmtMtelNo},
RRNO = #{rrno},
APPR_YN = IFNULL(NULLIF(#{apprYn}, ''), 'N'),
FINAL_SCHSP_NM = #{finalSchspNm},
MODID = #{modId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND USR_ID = #{usrId}
</update>
<!-- 비밀번호 변경 -->
<update id="updateUserPw" parameterType="map">
UPDATE SX_GW0010
SET PW = #{pw},
MODID = #{modId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND USR_ID = #{usrId}
</update>
<update id="updateUserPw" parameterType="map" databaseId="mariadb">
UPDATE SX_GW0010
SET PW = #{pw},
MODID = #{modId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND USR_ID = #{usrId}
</update>
<!-- 직원 삭제 -->
<delete id="deleteUser" parameterType="map">
DELETE FROM SX_GW0010
WHERE CORP_NO = #{corpNo}
AND USR_ID = #{usrId}
</delete>
<!-- 사진 파일번호 업데이트 -->
<update id="updateUserPhoto" parameterType="map">
UPDATE SX_GW0010
SET PHOTO_ATCHFILE_NO = #{photoAtchfileNo},
MODID = #{modId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND USR_ID = #{usrId}
</update>
<update id="updateUserPhoto" parameterType="map" databaseId="mariadb">
UPDATE SX_GW0010
SET PHOTO_ATCHFILE_NO = #{photoAtchfileNo},
MODID = #{modId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND USR_ID = #{usrId}
</update>
</mapper>

View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 원본: src/java/sql/biz/envset/envset0020_sql.xml (SX_CO0030, SX_CO0040) -->
<mapper namespace="com.company.gw.envset.mapper.CodeManageMapper">
<!-- ===== 코드인덱스 (SX_CO0030) ===== -->
<select id="getCdidxList" parameterType="map" resultType="com.company.gw.envset.dto.CodeIndexDto">
SELECT COMM_CL_CD,
COMM_CL_CD_NM,
COMM_CL_CD_USE_YN,
COMM_CL_CD_DSCRPT,
DSPLY_ORDR
FROM SX_CO0030
WHERE CORP_NO = #{corpNo}
AND (#{searchText} IS NULL
OR COMM_CL_CD LIKE CONCAT('%', #{searchText}, '%')
OR COMM_CL_CD_NM LIKE CONCAT('%', #{searchText}, '%'))
ORDER BY DSPLY_ORDR, COMM_CL_CD
</select>
<select id="getCdidxInfo" parameterType="map" resultType="com.company.gw.envset.dto.CodeIndexDto">
SELECT COMM_CL_CD, COMM_CL_CD_NM, COMM_CL_CD_USE_YN, COMM_CL_CD_DSCRPT, DSPLY_ORDR
FROM SX_CO0030
WHERE CORP_NO = #{corpNo}
AND COMM_CL_CD = #{commClCd}
</select>
<insert id="insertCdidx" parameterType="com.company.gw.envset.dto.CodeIndexDto">
INSERT INTO SX_CO0030 (
CORP_NO, COMM_CL_CD, COMM_CL_CD_NM, COMM_CL_CD_USE_YN,
COMM_CL_CD_DSCRPT, DSPLY_ORDR, PROP_YN,
RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{commClCd}, #{commClCdNm}, #{commClCdUseYn},
#{commClCdDscrpt}, #{dsplyOrdr}, 'N',
#{rgstrId}, GETDATE(), #{rgstrId}, GETDATE()
)
</insert>
<insert id="insertCdidx" parameterType="com.company.gw.envset.dto.CodeIndexDto" databaseId="mariadb">
INSERT INTO SX_CO0030 (
CORP_NO, COMM_CL_CD, COMM_CL_CD_NM, COMM_CL_CD_USE_YN,
COMM_CL_CD_DSCRPT, DSPLY_ORDR, PROP_YN,
RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{commClCd}, #{commClCdNm}, #{commClCdUseYn},
#{commClCdDscrpt}, #{dsplyOrdr}, 'N',
#{rgstrId}, NOW(), #{rgstrId}, NOW()
)
</insert>
<update id="updateCdidx" parameterType="com.company.gw.envset.dto.CodeIndexDto">
UPDATE SX_CO0030
SET COMM_CL_CD_NM = #{commClCdNm},
COMM_CL_CD_USE_YN = #{commClCdUseYn},
COMM_CL_CD_DSCRPT = #{commClCdDscrpt},
DSPLY_ORDR = #{dsplyOrdr},
MODID = #{modId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND COMM_CL_CD = #{commClCd}
</update>
<update id="updateCdidx" parameterType="com.company.gw.envset.dto.CodeIndexDto" databaseId="mariadb">
UPDATE SX_CO0030
SET COMM_CL_CD_NM = #{commClCdNm},
COMM_CL_CD_USE_YN = #{commClCdUseYn},
COMM_CL_CD_DSCRPT = #{commClCdDscrpt},
DSPLY_ORDR = #{dsplyOrdr},
MODID = #{modId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND COMM_CL_CD = #{commClCd}
</update>
<delete id="deleteCdidx" parameterType="map">
DELETE FROM SX_CO0030
WHERE CORP_NO = #{corpNo}
AND COMM_CL_CD = #{commClCd}
</delete>
<!-- ===== 코드 (SX_CO0040) ===== -->
<select id="getCodeList" parameterType="map" resultType="com.company.gw.envset.dto.CodeDto">
SELECT COMM_CD,
COMM_CD_NM,
COMM_CD_USE_YN,
COMM_CD_DSCRPT,
COMM_CD_DSPLY_ORDR
FROM SX_CO0040
WHERE CORP_NO = #{corpNo}
AND COMM_CL_CD = #{commClCd}
AND (#{searchText} IS NULL
OR COMM_CD LIKE CONCAT('%', #{searchText}, '%')
OR COMM_CD_NM LIKE CONCAT('%', #{searchText}, '%'))
ORDER BY COMM_CD_DSPLY_ORDR, COMM_CD
</select>
<insert id="insertCode" parameterType="com.company.gw.envset.dto.CodeDto">
INSERT INTO SX_CO0040 (
CORP_NO, COMM_CL_CD, COMM_CD, COMM_CD_NM,
COMM_CD_USE_YN, COMM_CD_DSCRPT, COMM_CD_DSPLY_ORDR,
RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{commClCd}, #{commCd}, #{commCdNm},
#{commCdUseYn}, #{commCdDscrpt}, #{commCdDsplyOrdr},
#{rgstrId}, GETDATE(), #{rgstrId}, GETDATE()
)
</insert>
<insert id="insertCode" parameterType="com.company.gw.envset.dto.CodeDto" databaseId="mariadb">
INSERT INTO SX_CO0040 (
CORP_NO, COMM_CL_CD, COMM_CD, COMM_CD_NM,
COMM_CD_USE_YN, COMM_CD_DSCRPT, COMM_CD_DSPLY_ORDR,
RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{commClCd}, #{commCd}, #{commCdNm},
#{commCdUseYn}, #{commCdDscrpt}, #{commCdDsplyOrdr},
#{rgstrId}, NOW(), #{rgstrId}, NOW()
)
</insert>
<update id="updateCode" parameterType="com.company.gw.envset.dto.CodeDto">
UPDATE SX_CO0040
SET COMM_CD_NM = #{commCdNm},
COMM_CD_USE_YN = #{commCdUseYn},
COMM_CD_DSCRPT = #{commCdDscrpt},
COMM_CD_DSPLY_ORDR = #{commCdDsplyOrdr},
MODID = #{modId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND COMM_CL_CD = #{commClCd}
AND COMM_CD = #{commCd}
</update>
<update id="updateCode" parameterType="com.company.gw.envset.dto.CodeDto" databaseId="mariadb">
UPDATE SX_CO0040
SET COMM_CD_NM = #{commCdNm},
COMM_CD_USE_YN = #{commCdUseYn},
COMM_CD_DSCRPT = #{commCdDscrpt},
COMM_CD_DSPLY_ORDR = #{commCdDsplyOrdr},
MODID = #{modId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND COMM_CL_CD = #{commClCd}
AND COMM_CD = #{commCd}
</update>
<delete id="deleteCode" parameterType="map">
DELETE FROM SX_CO0040
WHERE CORP_NO = #{corpNo}
AND COMM_CL_CD = #{commClCd}
AND COMM_CD = #{commCd}
</delete>
</mapper>

View File

@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
환경설정 > 근무코드관리 (SX_CO0070)
-->
<mapper namespace="com.company.gw.envset.mapper.WorkCdMapper">
<!-- 근무코드 목록 -->
<select id="getWorkCdList" parameterType="map" resultType="map">
SELECT
A.WORK_CD,
A.WORK_CD_TITLE_NM,
A.GOTOWORK_TM_NM,
A.GETOFFWORK_TM_NM,
A.PAY_DIV_NM,
A.REGULAR_WORK_TM,
A.REST_TM,
A.INCLU_WORK_NM,
A.INCLU_WORK_START_TM,
A.INCLU_WORK_END_TM,
A.INCLU_WORK_TM,
A.CALC_LOCA_NM,
A.OT_START_TM,
A.NGT_ALLW_NM,
A.NGT_START_TM,
A.NGT_END_TM,
A.WORK_ALLW_NM,
A.WORK_CD_USE_YN,
A.WORK_CN,
A.OT_BSCRT,
A.NGT_BSCRT,
A.APLY_WORK_DAY,
A.YYCT_DEDU_DAY,
A.MEAL_START_TM,
A.MEAL_END_TM,
A.HOLIDAY_YN
FROM SX_CO0070 A
WHERE A.CORP_NO = #{corpNo}
<if test="searchText != null and searchText != ''">
AND (A.WORK_CD LIKE CONCAT('%', #{searchText}, '%')
OR A.WORK_CD_TITLE_NM LIKE CONCAT('%', #{searchText}, '%'))
</if>
ORDER BY A.WORK_CD
</select>
<!-- 근무코드 등록 MSSQL -->
<insert id="insertWorkCd" parameterType="map">
INSERT INTO SX_CO0070 (
CORP_NO, WORK_CD, WORK_CD_TITLE_NM, GOTOWORK_TM_NM, GETOFFWORK_TM_NM,
PAY_DIV_NM, REGULAR_WORK_TM, REST_TM,
INCLU_WORK_NM, INCLU_WORK_START_TM, INCLU_WORK_END_TM, INCLU_WORK_TM,
CALC_LOCA_NM, OT_START_TM, NGT_ALLW_NM, NGT_START_TM, NGT_END_TM,
WORK_ALLW_NM, WORK_CD_USE_YN, WORK_CN,
OT_BSCRT, NGT_BSCRT, APLY_WORK_DAY, YYCT_DEDU_DAY,
MEAL_START_TM, MEAL_END_TM, HOLIDAY_YN,
RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{workCd}, #{workCdTitleNm}, #{gotoworkTmNm}, #{getoffworkTmNm},
#{payDivNm}, #{regularWorkTm}, #{restTm},
#{incluWorkNm}, #{incluWorkStartTm}, #{incluWorkEndTm}, #{incluWorkTm},
#{calcLocaNm}, #{otStartTm}, #{ngtAllwNm}, #{ngtStartTm}, #{ngtEndTm},
#{workAllwNm}, #{workCdUseYn}, #{workCn},
#{otBscrt}, #{ngtBscrt}, #{aplyWorkDay}, #{yyctDeduDay},
#{mealStartTm}, #{mealEndTm}, #{holidayYn},
#{regUsrId}, GETDATE(), #{regUsrId}, GETDATE()
)
</insert>
<!-- 근무코드 등록 MariaDB -->
<insert id="insertWorkCd" databaseId="mariadb" parameterType="map">
INSERT INTO SX_CO0070 (
CORP_NO, WORK_CD, WORK_CD_TITLE_NM, GOTOWORK_TM_NM, GETOFFWORK_TM_NM,
PAY_DIV_NM, REGULAR_WORK_TM, REST_TM,
INCLU_WORK_NM, INCLU_WORK_START_TM, INCLU_WORK_END_TM, INCLU_WORK_TM,
CALC_LOCA_NM, OT_START_TM, NGT_ALLW_NM, NGT_START_TM, NGT_END_TM,
WORK_ALLW_NM, WORK_CD_USE_YN, WORK_CN,
OT_BSCRT, NGT_BSCRT, APLY_WORK_DAY, YYCT_DEDU_DAY,
MEAL_START_TM, MEAL_END_TM, HOLIDAY_YN,
RGSTR_ID, RGST_DT, MODID, UPD_DT
) VALUES (
#{corpNo}, #{workCd}, #{workCdTitleNm}, #{gotoworkTmNm}, #{getoffworkTmNm},
#{payDivNm}, #{regularWorkTm}, #{restTm},
#{incluWorkNm}, #{incluWorkStartTm}, #{incluWorkEndTm}, #{incluWorkTm},
#{calcLocaNm}, #{otStartTm}, #{ngtAllwNm}, #{ngtStartTm}, #{ngtEndTm},
#{workAllwNm}, #{workCdUseYn}, #{workCn},
#{otBscrt}, #{ngtBscrt}, #{aplyWorkDay}, #{yyctDeduDay},
#{mealStartTm}, #{mealEndTm}, #{holidayYn},
#{regUsrId}, NOW(), #{regUsrId}, NOW()
)
</insert>
<!-- 근무코드 수정 MSSQL -->
<update id="updateWorkCd" parameterType="map">
UPDATE SX_CO0070 SET
WORK_CD_TITLE_NM = #{workCdTitleNm},
GOTOWORK_TM_NM = #{gotoworkTmNm},
GETOFFWORK_TM_NM = #{getoffworkTmNm},
PAY_DIV_NM = #{payDivNm},
REGULAR_WORK_TM = #{regularWorkTm},
REST_TM = #{restTm},
INCLU_WORK_NM = #{incluWorkNm},
INCLU_WORK_START_TM = #{incluWorkStartTm},
INCLU_WORK_END_TM = #{incluWorkEndTm},
INCLU_WORK_TM = #{incluWorkTm},
CALC_LOCA_NM = #{calcLocaNm},
OT_START_TM = #{otStartTm},
NGT_ALLW_NM = #{ngtAllwNm},
NGT_START_TM = #{ngtStartTm},
NGT_END_TM = #{ngtEndTm},
WORK_ALLW_NM = #{workAllwNm},
WORK_CD_USE_YN = #{workCdUseYn},
WORK_CN = #{workCn},
OT_BSCRT = #{otBscrt},
NGT_BSCRT = #{ngtBscrt},
APLY_WORK_DAY = #{aplyWorkDay},
YYCT_DEDU_DAY = #{yyctDeduDay},
MEAL_START_TM = #{mealStartTm},
MEAL_END_TM = #{mealEndTm},
HOLIDAY_YN = #{holidayYn},
MODID = #{regUsrId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND WORK_CD = #{workCd}
</update>
<!-- 근무코드 수정 MariaDB -->
<update id="updateWorkCd" databaseId="mariadb" parameterType="map">
UPDATE SX_CO0070 SET
WORK_CD_TITLE_NM = #{workCdTitleNm},
GOTOWORK_TM_NM = #{gotoworkTmNm},
GETOFFWORK_TM_NM = #{getoffworkTmNm},
PAY_DIV_NM = #{payDivNm},
REGULAR_WORK_TM = #{regularWorkTm},
REST_TM = #{restTm},
INCLU_WORK_NM = #{incluWorkNm},
INCLU_WORK_START_TM = #{incluWorkStartTm},
INCLU_WORK_END_TM = #{incluWorkEndTm},
INCLU_WORK_TM = #{incluWorkTm},
CALC_LOCA_NM = #{calcLocaNm},
OT_START_TM = #{otStartTm},
NGT_ALLW_NM = #{ngtAllwNm},
NGT_START_TM = #{ngtStartTm},
NGT_END_TM = #{ngtEndTm},
WORK_ALLW_NM = #{workAllwNm},
WORK_CD_USE_YN = #{workCdUseYn},
WORK_CN = #{workCn},
OT_BSCRT = #{otBscrt},
NGT_BSCRT = #{ngtBscrt},
APLY_WORK_DAY = #{aplyWorkDay},
YYCT_DEDU_DAY = #{yyctDeduDay},
MEAL_START_TM = #{mealStartTm},
MEAL_END_TM = #{mealEndTm},
HOLIDAY_YN = #{holidayYn},
MODID = #{regUsrId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND WORK_CD = #{workCd}
</update>
<!-- 근무코드 삭제 -->
<delete id="deleteWorkCd" parameterType="map">
DELETE FROM SX_CO0070
WHERE CORP_NO = #{corpNo}
AND WORK_CD = #{workCd}
</delete>
</mapper>

View File

@@ -0,0 +1,213 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 원본: src/java/sql/biz/envset/envset0050_sql.xml (SX_CO0080, SX_CO0090, SX_GW0130) -->
<mapper namespace="com.company.gw.envset.mapper.MenuManageMapper">
<!-- ===== 메뉴 (SX_CO0080) ===== -->
<select id="getMenuList" parameterType="String" resultType="com.company.gw.envset.dto.MenuManageDto">
SELECT MENU_NO, UPPER_MENU_NO, MENU_NM, URL, RM,
MENU_PROP, MENU_ORDR, MENU_USE_YN, CONTROLLER, MENU_LVL
FROM SX_CO0080
WHERE CORP_NO = #{corpNo}
ORDER BY MENU_ORDR
</select>
<insert id="insertMenu" parameterType="com.company.gw.envset.dto.MenuManageDto">
INSERT INTO SX_CO0080 (
CORP_NO, MENU_NO, UPPER_MENU_NO, MENU_NM, URL, RM,
MENU_PROP, MENU_ORDR, MENU_USE_YN,
RGSTR_ID, RGST_DT, MODID, UPD_DT, CONTROLLER, MENU_LVL
) VALUES (
#{corpNo}, #{menuNo}, NULLIF(#{upperMenuNo}, ''), #{menuNm}, #{url}, #{rm},
#{menuProp}, #{menuOrdr}, #{menuUseYn},
#{rgstrId}, GETDATE(), #{rgstrId}, GETDATE(), #{controller}, #{menuLvl}
)
</insert>
<insert id="insertMenu" parameterType="com.company.gw.envset.dto.MenuManageDto" databaseId="mariadb">
INSERT INTO SX_CO0080 (
CORP_NO, MENU_NO, UPPER_MENU_NO, MENU_NM, URL, RM,
MENU_PROP, MENU_ORDR, MENU_USE_YN,
RGSTR_ID, RGST_DT, MODID, UPD_DT, CONTROLLER, MENU_LVL
) VALUES (
#{corpNo}, #{menuNo}, NULLIF(#{upperMenuNo}, ''), #{menuNm}, #{url}, #{rm},
#{menuProp}, #{menuOrdr}, #{menuUseYn},
#{rgstrId}, NOW(), #{rgstrId}, NOW(), #{controller}, #{menuLvl}
)
</insert>
<update id="updateMenu" parameterType="com.company.gw.envset.dto.MenuManageDto">
UPDATE SX_CO0080
SET UPPER_MENU_NO = NULLIF(#{upperMenuNo}, ''),
MENU_NM = #{menuNm},
URL = #{url},
RM = #{rm},
MENU_PROP = #{menuProp},
MENU_ORDR = #{menuOrdr},
MENU_USE_YN = #{menuUseYn},
CONTROLLER = #{controller},
MENU_LVL = #{menuLvl},
MODID = #{modId},
UPD_DT = GETDATE()
WHERE CORP_NO = #{corpNo}
AND MENU_NO = #{menuNo}
</update>
<update id="updateMenu" parameterType="com.company.gw.envset.dto.MenuManageDto" databaseId="mariadb">
UPDATE SX_CO0080
SET UPPER_MENU_NO = NULLIF(#{upperMenuNo}, ''),
MENU_NM = #{menuNm},
URL = #{url},
RM = #{rm},
MENU_PROP = #{menuProp},
MENU_ORDR = #{menuOrdr},
MENU_USE_YN = #{menuUseYn},
CONTROLLER = #{controller},
MENU_LVL = #{menuLvl},
MODID = #{modId},
UPD_DT = NOW()
WHERE CORP_NO = #{corpNo}
AND MENU_NO = #{menuNo}
</update>
<delete id="deleteMauthByMenu" parameterType="map">
DELETE FROM SX_CO0090 WHERE CORP_NO = #{corpNo} AND MENU_NO = #{menuNo}
</delete>
<delete id="deleteMenu" parameterType="map">
DELETE FROM SX_CO0080 WHERE CORP_NO = #{corpNo} AND MENU_NO = #{menuNo}
</delete>
<!-- ===== 권한-메뉴 (SX_CO0090) ===== -->
<select id="getAuthMenuList" parameterType="map" resultType="com.company.gw.envset.dto.AuthMenuDto">
WITH MENU_TREE AS (
SELECT A.MENU_NO AS UP_MENU_NO, A.CORP_NO, A.MENU_NO, A.UPPER_MENU_NO,
A.MENU_NM, A.URL, A.MENU_PROP, A.MENU_ORDR, A.MENU_USE_YN,
1 AS LVL,
CAST('/' + RIGHT('00000' + CONVERT(VARCHAR, A.MENU_ORDR), 5) AS VARCHAR(MAX)) AS SORT_PATH
FROM SX_CO0080 A
WHERE A.CORP_NO = #{corpNo} AND NULLIF(A.UPPER_MENU_NO, '') IS NULL
UNION ALL
SELECT PARENT.UP_MENU_NO, A.CORP_NO, A.MENU_NO, A.UPPER_MENU_NO,
A.MENU_NM, A.URL, A.MENU_PROP, A.MENU_ORDR, A.MENU_USE_YN,
PARENT.LVL + 1,
PARENT.SORT_PATH + CAST('/' + RIGHT('00000' + CONVERT(VARCHAR, A.MENU_ORDR), 5) AS VARCHAR(MAX))
FROM SX_CO0080 A
JOIN MENU_TREE PARENT ON PARENT.CORP_NO = A.CORP_NO AND PARENT.MENU_NO = A.UPPER_MENU_NO
WHERE A.CORP_NO = #{corpNo}
)
SELECT A.MENU_NO, A.UPPER_MENU_NO, A.MENU_NM, A.URL,
A.MENU_PROP, A.MENU_ORDR, A.MENU_USE_YN, A.LVL,
CASE WHEN B.MENU_NO IS NOT NULL THEN 'Y' ELSE 'N' END AS MENU_AUTH_YN,
B.FUNC_AUTH_CN,
#{menuAuthCd} AS MENU_AUTH_CD
FROM MENU_TREE A
LEFT JOIN SX_CO0090 B
ON B.CORP_NO = A.CORP_NO AND B.MENU_NO = A.MENU_NO AND B.MENU_AUTH_CD = #{menuAuthCd}
WHERE A.CORP_NO = #{corpNo}
ORDER BY A.SORT_PATH
</select>
<select id="getAuthMenuList" parameterType="map" resultType="com.company.gw.envset.dto.AuthMenuDto" databaseId="mariadb">
WITH MENU_TREE AS (
SELECT A.MENU_NO AS UP_MENU_NO, A.CORP_NO, A.MENU_NO, A.UPPER_MENU_NO,
A.MENU_NM, A.URL, A.MENU_PROP, A.MENU_ORDR, A.MENU_USE_YN,
1 AS LVL,
CONCAT('/', LPAD(A.MENU_ORDR, 5, '0')) AS SORT_PATH
FROM SX_CO0080 A
WHERE A.CORP_NO = #{corpNo} AND NULLIF(A.UPPER_MENU_NO, '') IS NULL
UNION ALL
SELECT PARENT.UP_MENU_NO, A.CORP_NO, A.MENU_NO, A.UPPER_MENU_NO,
A.MENU_NM, A.URL, A.MENU_PROP, A.MENU_ORDR, A.MENU_USE_YN,
PARENT.LVL + 1,
CONCAT(PARENT.SORT_PATH, '/', LPAD(A.MENU_ORDR, 5, '0'))
FROM SX_CO0080 A
JOIN MENU_TREE PARENT ON PARENT.CORP_NO = A.CORP_NO AND PARENT.MENU_NO = A.UPPER_MENU_NO
WHERE A.CORP_NO = #{corpNo}
)
SELECT A.MENU_NO, A.UPPER_MENU_NO, A.MENU_NM, A.URL,
A.MENU_PROP, A.MENU_ORDR, A.MENU_USE_YN, A.LVL,
CASE WHEN B.MENU_NO IS NOT NULL THEN 'Y' ELSE 'N' END AS MENU_AUTH_YN,
B.FUNC_AUTH_CN,
#{menuAuthCd} AS MENU_AUTH_CD
FROM MENU_TREE A
LEFT JOIN SX_CO0090 B
ON B.CORP_NO = A.CORP_NO AND B.MENU_NO = A.MENU_NO AND B.MENU_AUTH_CD = #{menuAuthCd}
WHERE A.CORP_NO = #{corpNo}
ORDER BY A.SORT_PATH
</select>
<insert id="mergeMenuAuth" parameterType="com.company.gw.envset.dto.AuthMenuDto">
MERGE INTO SX_CO0090 T
USING (SELECT 1 X) S
ON (T.CORP_NO = #{corpNo} AND T.MENU_NO = #{menuNo} AND T.MENU_AUTH_CD = #{menuAuthCd})
WHEN NOT MATCHED THEN
INSERT (CORP_NO, MENU_NO, MENU_AUTH_CD, FUNC_AUTH_CN, RGSTR_ID, RGST_DT, MODID, UPD_DT)
VALUES (#{corpNo}, #{menuNo}, #{menuAuthCd}, #{funcAuthCn}, #{rgstrId}, GETDATE(), #{rgstrId}, GETDATE())
WHEN MATCHED THEN
UPDATE SET FUNC_AUTH_CN = #{funcAuthCn}, MODID = #{modId}, UPD_DT = GETDATE();
</insert>
<insert id="mergeMenuAuth" parameterType="com.company.gw.envset.dto.AuthMenuDto" databaseId="mariadb">
INSERT INTO SX_CO0090 (CORP_NO, MENU_NO, MENU_AUTH_CD, FUNC_AUTH_CN, RGSTR_ID, RGST_DT, MODID, UPD_DT)
VALUES (#{corpNo}, #{menuNo}, #{menuAuthCd}, #{funcAuthCn}, #{rgstrId}, NOW(), #{rgstrId}, NOW())
ON DUPLICATE KEY UPDATE FUNC_AUTH_CN = #{funcAuthCn}, MODID = #{modId}, UPD_DT = NOW()
</insert>
<delete id="deleteMenuAuth" parameterType="map">
DELETE FROM SX_CO0090
WHERE CORP_NO = #{corpNo} AND MENU_NO = #{menuNo} AND MENU_AUTH_CD = #{menuAuthCd}
</delete>
<!-- ===== 사용자-메뉴권한 (SX_GW0130) ===== -->
<select id="getUserMauthList" parameterType="map" resultType="com.company.gw.envset.dto.UserAuthDto">
SELECT A.USR_ID, A.MENU_AUTH_CD,
U.USR_NM, U.TEAM_CD, U.DUTY_CD, U.RETIREMENT_DATE
FROM SX_GW0130 A
JOIN SX_GW0010 U ON U.CORP_NO = A.CORP_NO AND U.USR_ID = A.USR_ID
WHERE A.CORP_NO = #{corpNo}
AND (#{menuAuthCd} IS NULL OR A.MENU_AUTH_CD = #{menuAuthCd})
AND (#{usrNm} IS NULL OR U.USR_NM LIKE CONCAT('%', #{usrNm}, '%'))
ORDER BY A.MENU_AUTH_CD, U.USR_SORT_ORDR, A.USR_ID
</select>
<insert id="insertUserMauth" parameterType="com.company.gw.envset.dto.UserAuthDto">
INSERT INTO SX_GW0130 (CORP_NO, USR_ID, MENU_AUTH_CD, RGSTR_ID, RGST_DT, MODID, UPD_DT)
VALUES (#{corpNo}, #{usrId}, #{menuAuthCd}, #{rgstrId}, GETDATE(), #{rgstrId}, GETDATE())
</insert>
<insert id="insertUserMauth" parameterType="com.company.gw.envset.dto.UserAuthDto" databaseId="mariadb">
INSERT INTO SX_GW0130 (CORP_NO, USR_ID, MENU_AUTH_CD, RGSTR_ID, RGST_DT, MODID, UPD_DT)
VALUES (#{corpNo}, #{usrId}, #{menuAuthCd}, #{rgstrId}, NOW(), #{rgstrId}, NOW())
</insert>
<delete id="deleteUserMauth" parameterType="map">
DELETE FROM SX_GW0130
WHERE CORP_NO = #{corpNo} AND USR_ID = #{usrId} AND MENU_AUTH_CD = #{menuAuthCd}
</delete>
<!-- 사용자 검색 (autocomplete) -->
<select id="searchUser" parameterType="map" resultType="map">
SELECT TOP 20 USR_ID, USR_NM, TEAM_CD, DUTY_CD
FROM SX_GW0010
WHERE CORP_NO = #{corpNo}
AND USR_NM LIKE CONCAT('%', #{keyword}, '%')
AND NULLIF(RETIREMENT_DATE, '') IS NULL
ORDER BY USR_SORT_ORDR, USR_ID
</select>
<select id="searchUser" parameterType="map" resultType="map" databaseId="mariadb">
SELECT USR_ID, USR_NM, TEAM_CD, DUTY_CD
FROM SX_GW0010
WHERE CORP_NO = #{corpNo}
AND USR_NM LIKE CONCAT('%', #{keyword}, '%')
AND NULLIF(RETIREMENT_DATE, '') IS NULL
ORDER BY USR_SORT_ORDR, USR_ID
LIMIT 20
</select>
</mapper>

Some files were not shown because too many files have changed in this diff Show More