init: SR System — Helpdesk & ITAM Portal

This commit is contained in:
pray4skylark
2026-06-02 13:50:39 +09:00
commit 832c9f096f
262 changed files with 30470 additions and 0 deletions
@@ -0,0 +1,12 @@
package com.srsystem;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SrsystemApplication {
public static void main(String[] args) {
SpringApplication.run(SrsystemApplication.class, args);
}
}
@@ -0,0 +1,67 @@
package com.srsystem.batch;
import com.srsystem.dto.NotificationEvent;
import com.srsystem.entity.Ticket;
import com.srsystem.entity.enums.TicketStatus;
import com.srsystem.listener.NotificationPublisher;
import com.srsystem.repository.TicketRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class SlaExpirationJob {
private final TicketRepository ticketRepository;
private final NotificationPublisher notificationPublisher;
private static final List<TicketStatus> EXCLUDED_STATUSES = List.of(TicketStatus.RESOLVED, TicketStatus.CLOSED);
@Transactional
public int processExpiredTickets() {
List<Ticket> expiredTickets = ticketRepository
.findBySlaWarnFlagAndStatusNotIn('N', EXCLUDED_STATUSES);
int count = 0;
for (Ticket ticket : expiredTickets) {
if (ticket.getDueDate() != null && ticket.getDueDate().isBefore(LocalDateTime.now())) {
ticket.setSlaWarnFlag('Y');
ticketRepository.save(ticket);
if (ticket.getRequester() != null) {
notificationPublisher.publish(new NotificationEvent(
"SLA_WARNING",
"SLA Expired: Ticket #" + ticket.getTicketId(),
"Ticket \"" + ticket.getTitle() + "\" has exceeded its SLA deadline.",
"/issues/" + ticket.getTicketId(),
ticket.getRequester().getEmpId()
));
}
if (ticket.getAssignee() != null) {
notificationPublisher.publish(new NotificationEvent(
"SLA_WARNING",
"SLA Expired: Ticket #" + ticket.getTicketId(),
"Assigned ticket \"" + ticket.getTitle() + "\" has exceeded its SLA deadline.",
"/issues/" + ticket.getTicketId(),
ticket.getAssignee().getEmpId()
));
}
count++;
}
}
if (count > 0) {
log.info("SLA Expiration Job: {} tickets marked as overdue", count);
}
return count;
}
}
@@ -0,0 +1,25 @@
package com.srsystem.batch;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Slf4j
public class SlaScheduler {
private final SlaExpirationJob slaExpirationJob;
@Scheduled(cron = "0 0 * * * *")
public void runSlaCheck() {
log.info("SLA Scheduler: Starting hourly SLA expiration check");
try {
int processed = slaExpirationJob.processExpiredTickets();
log.info("SLA Scheduler: Completed. {} tickets processed.", processed);
} catch (Exception e) {
log.error("SLA Scheduler: Error during SLA check", e);
}
}
}
@@ -0,0 +1,9 @@
package com.srsystem.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AsyncConfig {
}
@@ -0,0 +1,9 @@
package com.srsystem.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableScheduling
public class BatchConfig {
}
@@ -0,0 +1,10 @@
package com.srsystem.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}
@@ -0,0 +1,75 @@
package com.srsystem.config;
import com.srsystem.config.jwt.JwtAuthenticationFilter;
import com.srsystem.config.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**", "/api/v1/health", "/api/v1/wake").permitAll()
.requestMatchers("/api/v1/**").authenticated()
.anyRequest().permitAll()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
return new ProviderManager(provider);
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
@@ -0,0 +1,34 @@
package com.srsystem.config;
import com.srsystem.config.jwt.CurrentUserArgumentResolver;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final CurrentUserArgumentResolver currentUserArgumentResolver;
public WebConfig(CurrentUserArgumentResolver currentUserArgumentResolver) {
this.currentUserArgumentResolver = currentUserArgumentResolver;
}
@Value("${file.upload-dir:./uploads}")
private String uploadDir;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(currentUserArgumentResolver);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:" + uploadDir + "/");
}
}
@@ -0,0 +1,11 @@
package com.srsystem.config.jwt;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}
@@ -0,0 +1,47 @@
package com.srsystem.config.jwt;
import com.srsystem.entity.Employee;
import com.srsystem.service.AuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
@Slf4j
@Component
@RequiredArgsConstructor
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
private final AuthService authService;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class) &&
parameter.getParameterType().equals(Employee.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
log.warn("No authentication found");
return null;
}
String loginId = authentication.getName();
log.info("Resolving current user for loginId: {}", loginId);
Employee emp = authService.getEmployeeByLoginId(loginId);
if (emp == null) {
log.warn("Employee not found for loginId: {}", loginId);
return null;
}
log.info("Found employee: empId={}, name={}", emp.getEmpId(), emp.getName());
return emp;
}
}
@@ -0,0 +1,56 @@
package com.srsystem.config.jwt;
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.Collections;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = resolveToken(request);
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
String loginId = jwtTokenProvider.getLoginId(token);
String role = jwtTokenProvider.getRole(token);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
loginId,
null,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role))
);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("Security context set for user: {}", loginId);
}
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;
}
}
@@ -0,0 +1,90 @@
package com.srsystem.config.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
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 JwtTokenProvider {
private final SecretKey secretKey;
private final long accessTokenExpiration;
private final long refreshTokenExpiration;
public JwtTokenProvider(
@Value("${jwt.secret}") String secret,
@Value("${jwt.access-token-expiration}") long accessTokenExpiration,
@Value("${jwt.refresh-token-expiration}") long refreshTokenExpiration) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.accessTokenExpiration = accessTokenExpiration;
this.refreshTokenExpiration = refreshTokenExpiration;
}
public String generateAccessToken(String loginId, String role) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + accessTokenExpiration);
return Jwts.builder()
.subject(loginId)
.claim("role", role)
.issuedAt(now)
.expiration(expiryDate)
.signWith(secretKey)
.compact();
}
public String generateRefreshToken(String loginId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + refreshTokenExpiration);
return Jwts.builder()
.subject(loginId)
.issuedAt(now)
.expiration(expiryDate)
.signWith(secretKey)
.compact();
}
public String getLoginId(String token) {
return getClaims(token).getSubject();
}
public String getRole(String token) {
return getClaims(token).get("role", String.class);
}
public boolean validateToken(String token) {
try {
getClaims(token);
return true;
} catch (ExpiredJwtException e) {
log.warn("Expired JWT token: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
log.warn("Unsupported JWT token: {}", e.getMessage());
} catch (MalformedJwtException e) {
log.warn("Malformed JWT token: {}", e.getMessage());
} catch (IllegalArgumentException e) {
log.warn("Invalid JWT token: {}", e.getMessage());
}
return false;
}
private Claims getClaims(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
}
@@ -0,0 +1,39 @@
package com.srsystem.controller;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.LoginRequest;
import com.srsystem.dto.LoginResponse;
import com.srsystem.dto.RefreshTokenRequest;
import com.srsystem.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "Authentication", description = "인증 관련 API")
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@Operation(summary = "로그인", description = "아이디와 비밀번호로 로그인하여 액세스 토큰과 리프레시 토큰을 발급받습니다")
@PostMapping("/login")
public ResponseEntity<ApiResponse<LoginResponse>> login(@Valid @RequestBody LoginRequest request) {
LoginResponse response = authService.login(request);
return ResponseEntity.ok(ApiResponse.ok(response));
}
@Operation(summary = "토큰 갱신", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급받습니다")
@PostMapping("/refresh")
public ResponseEntity<ApiResponse<LoginResponse>> refresh(@RequestBody RefreshTokenRequest request) {
LoginResponse response = authService.refresh(request.refreshToken());
return ResponseEntity.ok(ApiResponse.ok(response));
}
}
@@ -0,0 +1,40 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.CalendarEvent;
import com.srsystem.entity.Employee;
import com.srsystem.service.CalendarService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
@Tag(name = "Calendar", description = "통합 일정 조회 API")
@RestController
@RequestMapping("/api/v1/calendar")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class CalendarController {
private final CalendarService calendarService;
@Operation(summary = "통합 일정 조회", description = "기간 내 모든 일정 (티켓 마감, 보고서 제출, 파견 기간) 을 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/events")
public ResponseEntity<ApiResponse<List<CalendarEvent>>> getEvents(
@RequestParam String start,
@RequestParam String end,
@CurrentUser Employee currentUser) {
LocalDate startDate = LocalDate.parse(start);
LocalDate endDate = LocalDate.parse(end);
List<CalendarEvent> events = calendarService.getEvents(currentUser.getEmpId(), currentUser.getRoleCode(), startDate, endDate);
return ResponseEntity.ok(ApiResponse.ok(events));
}
}
@@ -0,0 +1,64 @@
package com.srsystem.controller;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.CommonCodeRequest;
import com.srsystem.entity.CommonCode;
import com.srsystem.service.CommonCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "Common Code", description = "공통 코드 관리 API")
@RestController
@RequestMapping("/api/v1/common-codes")
@RequiredArgsConstructor
public class CommonCodeController {
private final CommonCodeService commonCodeService;
@Operation(summary = "공통 코드 목록 조회", description = "그룹 코드에 해당하는 공통 코드 목록을 조회합니다")
@GetMapping
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
public ResponseEntity<ApiResponse<List<CommonCode>>> getCodes(@RequestParam(required = false) String groupCode) {
List<CommonCode> codes = commonCodeService.getCodes(groupCode);
return ResponseEntity.ok(ApiResponse.ok(codes));
}
@Operation(summary = "공통 코드 그룹 목록 조회", description = "모든 공통 코드 그룹 목록을 조회합니다")
@GetMapping("/groups")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
public ResponseEntity<ApiResponse<List<String>>> getGroups() {
List<String> groups = commonCodeService.getGroupCodes();
return ResponseEntity.ok(ApiResponse.ok(groups));
}
@Operation(summary = "공통 코드 생성", description = "새로운 공통 코드를 생성합니다 (ADMIN 전용)")
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public ResponseEntity<ApiResponse<CommonCode>> createCode(@Valid @RequestBody CommonCodeRequest request) {
CommonCode code = commonCodeService.createCode(request);
return ResponseEntity.ok(ApiResponse.ok("공통 코드가 생성되었습니다.", code));
}
@Operation(summary = "공통 코드 수정", description = "기존 공통 코드를 수정합니다 (ADMIN 전용)")
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<CommonCode>> updateCode(@PathVariable Long id, @Valid @RequestBody CommonCodeRequest request) {
CommonCode code = commonCodeService.updateCode(id, request);
return ResponseEntity.ok(ApiResponse.ok("공통 코드가 수정되었습니다.", code));
}
@Operation(summary = "공통 코드 삭제", description = "공통 코드를 삭제합니다 (ADMIN 전용)")
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteCode(@PathVariable Long id) {
commonCodeService.deleteCode(id);
return ResponseEntity.ok(ApiResponse.ok("공통 코드가 삭제되었습니다.", null));
}
}
@@ -0,0 +1,82 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.CreateDispatchRequest;
import com.srsystem.entity.DispatchHistory;
import com.srsystem.entity.Employee;
import com.srsystem.service.DispatchHistoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "Dispatch History", description = "파견 이력 관리 API")
@RestController
@RequestMapping("/api/v1/dispatch-histories")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class DispatchHistoryController {
private final DispatchHistoryService dispatchHistoryService;
@Operation(summary = "파견 이력 생성", description = "직원을 프로젝트에 배정합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PostMapping
public ResponseEntity<ApiResponse<DispatchHistory>> createDispatch(
@Valid @RequestBody CreateDispatchRequest request) {
DispatchHistory history = dispatchHistoryService.createDispatch(request);
return ResponseEntity.ok(ApiResponse.ok("파견이 등록되었습니다", history));
}
@Operation(summary = "파견 이력 종료", description = "파견을 종료합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{dispatchId}/end")
public ResponseEntity<ApiResponse<DispatchHistory>> endDispatch(
@PathVariable Long dispatchId) {
DispatchHistory history = dispatchHistoryService.endDispatch(dispatchId);
return ResponseEntity.ok(ApiResponse.ok("파견이 종료되었습니다", history));
}
@Operation(summary = "현재 파견 중인 직원 목록", description = "파견중인 직원 목록을 조회합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping("/active")
public ResponseEntity<ApiResponse<List<DispatchHistory>>> getActiveDispatches() {
List<DispatchHistory> list = dispatchHistoryService.getActiveDispatches();
return ResponseEntity.ok(ApiResponse.ok(list));
}
@Operation(summary = "파견 이력 전체 목록", description = "페이징 및 필터 조건으로 파견 이력 목록을 조회합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<Page<DispatchHistory>>> findAll(
@PageableDefault(size = 20) Pageable pageable,
@RequestParam(required = false) Long empId,
@RequestParam(required = false) Long projectId) {
return ResponseEntity.ok(ApiResponse.ok(dispatchHistoryService.findAll(pageable, empId, projectId)));
}
@Operation(summary = "파견 이력 단건 조회", description = "파견 이력 ID로 단건 조회합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping("/{dispatchId}")
public ResponseEntity<ApiResponse<DispatchHistory>> findById(@PathVariable Long dispatchId) {
return ResponseEntity.ok(ApiResponse.ok(dispatchHistoryService.findById(dispatchId)));
}
@Operation(summary = "파견 이력 삭제", description = "파견 이력을 삭제합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@DeleteMapping("/{dispatchId}")
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long dispatchId) {
dispatchHistoryService.delete(dispatchId);
return ResponseEntity.ok(ApiResponse.ok("파견 이력이 삭제되었습니다", null));
}
}
@@ -0,0 +1,164 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.ChangePasswordRequest;
import com.srsystem.dto.EmployeeSearchResult;
import com.srsystem.dto.EmployeeSummary;
import com.srsystem.dto.PortfolioResponse;
import com.srsystem.dto.UpdateAchievementsRequest;
import com.srsystem.entity.DispatchHistory;
import com.srsystem.entity.Employee;
import com.srsystem.entity.enums.EmpStatus;
import com.srsystem.service.DispatchHistoryService;
import com.srsystem.service.EmployeeService;
import com.srsystem.service.PortfolioService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.transaction.annotation.Transactional;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import java.util.List;
@Tag(name = "Employee", description = "직원 관리 API")
@RestController
@RequestMapping("/api/v1/employees")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class EmployeeController {
private final EmployeeService employeeService;
private final DispatchHistoryService dispatchHistoryService;
private final PortfolioService portfolioService;
@Operation(summary = "직원 목록 조회", description = "페이징된 직원 목록을 조회합니다. 상태 필터 옵션 포함")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<Page<Employee>>> findAll(
@RequestParam(required = false) EmpStatus status,
@PageableDefault(size = 20) Pageable pageable) {
Page<Employee> employees = employeeService.findAll(status, pageable);
return ResponseEntity.ok(ApiResponse.ok(employees));
}
@Operation(summary = "직원 단건 조회", description = "직원 ID 로 단일 직원 정보를 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/{empId}")
public ResponseEntity<ApiResponse<Employee>> findById(@PathVariable Long empId) {
Employee employee = employeeService.findById(empId);
return ResponseEntity.ok(ApiResponse.ok(employee));
}
@Operation(summary = "현재 사용자 프로필 조회", description = "로그인한 사용자의 프로필 정보를 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/me")
public ResponseEntity<ApiResponse<EmployeeSummary>> getMe(@CurrentUser Employee employee) {
EmployeeSummary summary = employeeService.toSummary(employee);
return ResponseEntity.ok(ApiResponse.ok(summary));
}
@Operation(summary = "직원 검색 자동완성", description = "이름 키워드로 직원을 검색합니다 (디바운싱 300ms 권장)")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping("/search")
public ResponseEntity<ApiResponse<List<EmployeeSearchResult>>> search(
@RequestParam String keyword) {
List<EmployeeSearchResult> results = employeeService.searchByName(keyword);
return ResponseEntity.ok(ApiResponse.ok(results));
}
@Operation(summary = "직원 생성", description = "새로운 직원 정보를 생성합니다")
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public ResponseEntity<ApiResponse<Employee>> create(@RequestBody Employee employee) {
Employee created = employeeService.create(employee);
return ResponseEntity.ok(ApiResponse.ok("직원이 등록되었습니다", created));
}
@Operation(summary = "직원 정보 수정", description = "직원 정보를 수정합니다 (퇴사자는 수정 불가)")
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/{empId}")
public ResponseEntity<ApiResponse<Employee>> update(
@PathVariable Long empId,
@RequestBody Employee employee) {
Employee updated = employeeService.update(empId, employee);
return ResponseEntity.ok(ApiResponse.ok("직원 정보가 수정되었습니다", updated));
}
@Operation(summary = "직원 삭제", description = "직원을 삭제합니다 (Soft Delete, 퇴사자는 삭제 불가)")
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{empId}")
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long empId) {
employeeService.delete(empId);
return ResponseEntity.ok(ApiResponse.ok("직원이 삭제되었습니다", null));
}
@Operation(summary = "포트폴리오 조회", description = "직원의 파견 이력 (포트폴리오) 을 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@Transactional(readOnly = true)
@GetMapping("/{empId}/dispatch-histories")
public ResponseEntity<ApiResponse<List<DispatchHistory>>> getDispatchHistories(
@PathVariable Long empId,
@Parameter(hidden = true) @CurrentUser Employee currentUser) {
if (!currentUser.getEmpId().equals(empId) &&
!currentUser.getRoleCode().name().equals("ADMIN") &&
!currentUser.getRoleCode().name().equals("MANAGER")) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다"));
}
List<DispatchHistory> histories = dispatchHistoryService.findByEmployeeId(empId);
return ResponseEntity.ok(ApiResponse.ok(histories));
}
@Operation(summary = "포트폴리오 조회", description = "직원의 포트폴리오를 조회합니다. 파견 이력별로 보고서 요약과 해결 SR 티켓이 자동 바인딩됩니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/{empId}/portfolio")
public ResponseEntity<ApiResponse<List<PortfolioResponse>>> getPortfolio(
@PathVariable Long empId,
@Parameter(hidden = true) @CurrentUser Employee currentUser) {
if (!currentUser.getEmpId().equals(empId) &&
!currentUser.getRoleCode().name().equals("ADMIN") &&
!currentUser.getRoleCode().name().equals("MANAGER")) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다"));
}
List<PortfolioResponse> portfolio = portfolioService.getPortfolio(empId);
return ResponseEntity.ok(ApiResponse.ok(portfolio));
}
@Operation(summary = "포트폴리오 성과 저장", description = "파견 이력의 Achievements 필드를 업데이트합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@PutMapping("/{empId}/dispatch-histories/{dispatchId}/achievements")
public ResponseEntity<ApiResponse<Void>> updateAchievements(
@PathVariable Long empId,
@PathVariable Long dispatchId,
@RequestBody UpdateAchievementsRequest request,
@Parameter(hidden = true) @CurrentUser Employee currentUser) {
if (!currentUser.getEmpId().equals(empId) &&
!currentUser.getRoleCode().name().equals("ADMIN") &&
!currentUser.getRoleCode().name().equals("MANAGER")) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다"));
}
portfolioService.updateAchievements(dispatchId, request.getAchievements());
return ResponseEntity.ok(ApiResponse.ok("성과가 저장되었습니다", null));
}
@Operation(summary = "비밀번호 변경", description = "현재 사용자의 비밀번호를 변경합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@PutMapping("/me/password")
public ResponseEntity<ApiResponse<Void>> changePassword(
@RequestBody @Valid ChangePasswordRequest request,
@CurrentUser Employee currentUser) {
employeeService.changePassword(currentUser.getEmpId(), request);
return ResponseEntity.ok(ApiResponse.ok("비밀번호가 변경되었습니다", null));
}
}
@@ -0,0 +1,161 @@
package com.srsystem.controller;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.CreateEquipmentRequest;
import com.srsystem.dto.CreateMaintenanceRequest;
import com.srsystem.dto.EquipmentSummary;
import com.srsystem.dto.UpdateEquipmentRequest;
import com.srsystem.entity.Equipment;
import com.srsystem.entity.EquipmentAssignment;
import com.srsystem.entity.EquipmentMaintenance;
import com.srsystem.entity.enums.EquipmentStatus;
import com.srsystem.entity.enums.EquipmentType;
import com.srsystem.service.EquipmentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
@Tag(name = "Equipment", description = "IT 자산 관리 API")
@RestController
@RequestMapping("/api/v1/equipment")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class EquipmentController {
private final EquipmentService equipmentService;
@Operation(summary = "장비 목록 조회", description = "필터 조건으로 장비 목록을 조회합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<Page<Equipment>>> getEquipment(
@RequestParam(required = false) String type,
@RequestParam(required = false) EquipmentStatus status,
@RequestParam(required = false) String keyword,
@PageableDefault(size = 20) Pageable pageable) {
Page<Equipment> equipment = equipmentService.getEquipment(type, status, keyword, pageable);
return ResponseEntity.ok(ApiResponse.ok(equipment));
}
@Operation(summary = "장비 상태별 카운트", description = "장비 상태별 개수를 조회합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping("/summary")
public ResponseEntity<ApiResponse<EquipmentSummary>> getSummary() {
Map<String, Long> counts = equipmentService.getSummary();
EquipmentSummary summary = new EquipmentSummary(counts);
return ResponseEntity.ok(ApiResponse.ok(summary));
}
@Operation(summary = "장비 단건 조회", description = "장비 ID 로 단일 장비 정보를 조회합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping("/{equipId}")
public ResponseEntity<ApiResponse<Equipment>> getEquipment(@PathVariable Long equipId) {
Equipment equipment = equipmentService.getEquipment(equipId);
return ResponseEntity.ok(ApiResponse.ok(equipment));
}
@Operation(summary = "장비 생성", description = "새로운 장비를 생성합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PostMapping
public ResponseEntity<ApiResponse<Equipment>> createEquipment(@Valid @RequestBody CreateEquipmentRequest request) {
Equipment equipment = equipmentService.createEquipment(request);
return ResponseEntity.ok(ApiResponse.ok("장비가 등록되었습니다", equipment));
}
@Operation(summary = "장비 정보 수정", description = "장비 정보를 수정합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{equipId}")
public ResponseEntity<ApiResponse<Equipment>> updateEquipment(
@PathVariable Long equipId,
@RequestBody UpdateEquipmentRequest request) {
Equipment equipment = equipmentService.updateEquipment(equipId, request);
return ResponseEntity.ok(ApiResponse.ok("장비 정보가 수정되었습니다", equipment));
}
@Operation(summary = "장비 삭제", description = "장비를 삭제합니다 (Soft Delete)")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@DeleteMapping("/{equipId}")
public ResponseEntity<ApiResponse<Void>> deleteEquipment(@PathVariable Long equipId) {
equipmentService.deleteEquipment(equipId);
return ResponseEntity.ok(ApiResponse.ok("장비가 삭제되었습니다", null));
}
@Operation(summary = "장비 지급", description = "사용자에게 장비를 지급합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PostMapping("/{equipId}/assign")
public ResponseEntity<ApiResponse<EquipmentAssignment>> assignEquipment(
@PathVariable Long equipId,
@RequestBody Map<String, Object> body) {
Long userId = ((Number) body.get("userId")).longValue();
String condition = (String) body.get("condition");
EquipmentAssignment assignment = equipmentService.assignEquipment(equipId, userId, condition);
return ResponseEntity.ok(ApiResponse.ok("장비가 지급되었습니다", assignment));
}
@Operation(summary = "장비 반납", description = "장비를 반납합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{equipId}/return")
public ResponseEntity<ApiResponse<EquipmentAssignment>> returnEquipment(
@PathVariable Long equipId,
@RequestBody Map<String, String> body) {
EquipmentAssignment assignment = equipmentService.returnEquipment(equipId, body.get("condition"));
return ResponseEntity.ok(ApiResponse.ok("장비가 반납되었습니다", assignment));
}
@Operation(summary = "장비 지급 이력 조회", description = "장비의 지급/반납 이력을 조회합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping("/{equipId}/assignments")
public ResponseEntity<ApiResponse<List<EquipmentAssignment>>> getAssignments(@PathVariable Long equipId) {
List<EquipmentAssignment> assignments = equipmentService.getAssignments(equipId);
return ResponseEntity.ok(ApiResponse.ok(assignments));
}
@Operation(summary = "장비 유지보수 이력 조회", description = "장비의 유지보수 이력을 조회합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping("/{equipId}/maintenances")
public ResponseEntity<ApiResponse<List<EquipmentMaintenance>>> getMaintenanceHistory(@PathVariable Long equipId) {
List<EquipmentMaintenance> history = equipmentService.getMaintenanceHistory(equipId);
return ResponseEntity.ok(ApiResponse.ok(history));
}
@Operation(summary = "장비 유지보수 기록 추가", description = "장비의 유지보수 기록을 추가합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PostMapping("/{equipId}/maintenance")
public ResponseEntity<ApiResponse<EquipmentMaintenance>> addMaintenance(
@PathVariable Long equipId,
@Valid @RequestBody CreateMaintenanceRequest request) {
EquipmentMaintenance maintenance = equipmentService.addMaintenance(equipId, request);
return ResponseEntity.ok(ApiResponse.ok("유지보수 기록이 등록되었습니다", maintenance));
}
@Operation(summary = "장비 이미지 추가", description = "장비에 이미지를 추가합니다 (JSONB 배열)")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PostMapping("/{equipmentId}/images")
public ResponseEntity<ApiResponse<Equipment>> addImage(
@PathVariable Long equipmentId,
@RequestParam("file") MultipartFile file) {
Equipment equipment = equipmentService.addImage(equipmentId, file);
return ResponseEntity.ok(ApiResponse.ok("이미지가 추가되었습니다", equipment));
}
@Operation(summary = "장비 이미지 삭제", description = "장비에서 특정 이미지를 삭제합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@DeleteMapping("/{equipmentId}/images/{imageIndex}")
public ResponseEntity<ApiResponse<Equipment>> removeImage(
@PathVariable Long equipmentId,
@PathVariable int imageIndex) {
Equipment equipment = equipmentService.removeImage(equipmentId, imageIndex);
return ResponseEntity.ok(ApiResponse.ok("이미지가 삭제되었습니다", equipment));
}
}
@@ -0,0 +1,61 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.entity.Employee;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
@Tag(name = "File", description = "파일 업로드 API")
@RestController
@RequestMapping("/api/v1/files")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class FileUploadController {
@Value("${file.upload-dir:./uploads}")
private String uploadDir;
@PostConstruct
public void init() {
File dir = new File(uploadDir);
if (!dir.exists()) dir.mkdirs();
}
@Operation(summary = "파일 업로드", description = "이미지 파일을 업로드하고 URL 을 반환합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@PostMapping("/upload")
public ResponseEntity<ApiResponse<String>> uploadFile(
@RequestParam("file") MultipartFile file,
@CurrentUser Employee currentUser) {
try {
String originalName = file.getOriginalFilename();
String ext = "";
if (originalName != null && originalName.contains(".")) {
ext = originalName.substring(originalName.lastIndexOf("."));
}
String storedName = UUID.randomUUID().toString() + ext;
Path targetPath = Paths.get(uploadDir, storedName);
Files.copy(file.getInputStream(), targetPath);
String url = "/uploads/" + storedName;
return ResponseEntity.ok(ApiResponse.ok("파일 업로드 성공", url));
} catch (IOException e) {
return ResponseEntity.internalServerError().body(ApiResponse.error("파일 업로드 실패: " + e.getMessage()));
}
}
}
@@ -0,0 +1,51 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.entity.Employee;
import com.srsystem.entity.Notification;
import com.srsystem.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "Notification", description = "알림 관리 API")
@RestController
@RequestMapping("/api/v1/notifications")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class NotificationController {
private final NotificationService notificationService;
@Operation(summary = "내 알림 목록 조회", description = "로그인한 사용자의 알림 목록을 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<List<Notification>>> getMyNotifications(@CurrentUser Employee currentUser) {
List<Notification> notifications = notificationService.getMyNotifications(currentUser.getEmpId());
return ResponseEntity.ok(ApiResponse.ok(notifications));
}
@Operation(summary = "안읽은 알림 카운트", description = "안읽은 알림 개수를 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/unread")
public ResponseEntity<ApiResponse<Map<String, Long>>> getUnreadCount(@CurrentUser Employee currentUser) {
long count = notificationService.getUnreadCount(currentUser.getEmpId());
return ResponseEntity.ok(ApiResponse.ok(Map.of("count", count)));
}
@Operation(summary = "알림 읽음 처리", description = "알림을 읽음으로 표시합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@PutMapping("/{notificationId}/read")
public ResponseEntity<ApiResponse<Void>> markAsRead(@PathVariable Long notificationId) {
notificationService.markAsRead(notificationId);
return ResponseEntity.ok(ApiResponse.ok("알림이 읽음 처리되었습니다", null));
}
}
@@ -0,0 +1,39 @@
package com.srsystem.controller;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.HealthResponse;
import com.srsystem.dto.WakeRequest;
import com.srsystem.dto.WakeResponse;
import com.srsystem.service.ParkingService;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1")
@Tag(name = "Parking")
public class ParkingController {
private final ParkingService parkingService;
public ParkingController(ParkingService parkingService) {
this.parkingService = parkingService;
}
@GetMapping("/health")
public ResponseEntity<ApiResponse<HealthResponse>> checkHealth() {
HealthResponse response = parkingService.checkStatus();
return ResponseEntity.ok(ApiResponse.ok(response));
}
@PostMapping("/wake")
public ResponseEntity<ApiResponse<WakeResponse>> requestWake(@Valid @RequestBody WakeRequest request) {
WakeResponse response = parkingService.requestWake(request);
return ResponseEntity.ok(ApiResponse.ok("Wake request processed successfully", response));
}
}
@@ -0,0 +1,84 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.CreateProjectRequest;
import com.srsystem.entity.Employee;
import com.srsystem.entity.Project;
import com.srsystem.entity.enums.ProjectStatus;
import com.srsystem.service.ProjectService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "Project", description = "프로젝트 관리 API")
@RestController
@RequestMapping("/api/v1/projects")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class ProjectController {
private final ProjectService projectService;
@Operation(summary = "내 프로젝트 목록 조회", description = "로그인한 사용자가 참여 중인 프로젝트 목록을 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/my")
public ResponseEntity<ApiResponse<List<Project>>> getMyProjects(@CurrentUser Employee currentUser) {
List<Project> projects = projectService.getMyProjects(currentUser.getEmpId());
return ResponseEntity.ok(ApiResponse.ok(projects));
}
@Operation(summary = "프로젝트 단건 조회", description = "프로젝트 ID 로 단일 프로젝트 정보를 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/{projectId}")
public ResponseEntity<ApiResponse<Project>> getProject(@PathVariable Long projectId) {
Project project = projectService.getProject(projectId);
return ResponseEntity.ok(ApiResponse.ok(project));
}
@Operation(summary = "프로젝트 목록 조회", description = "전체 프로젝트 목록을 조회합니다. 상태 필터 옵션 포함")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@Transactional(readOnly = true)
@GetMapping
public ResponseEntity<ApiResponse<List<Project>>> findAll(
@RequestParam(required = false) ProjectStatus status) {
List<Project> projects = projectService.findAll(status);
return ResponseEntity.ok(ApiResponse.ok(projects));
}
@Operation(summary = "프로젝트 생성", description = "새로운 프로젝트를 등록합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PostMapping
public ResponseEntity<ApiResponse<Project>> create(
@RequestBody @Valid CreateProjectRequest request) {
Project project = projectService.create(request);
return ResponseEntity.ok(ApiResponse.ok("프로젝트가 등록되었습니다", project));
}
@Operation(summary = "프로젝트 수정", description = "프로젝트 정보를 수정합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{projectId}")
public ResponseEntity<ApiResponse<Project>> update(
@PathVariable Long projectId,
@RequestBody @Valid CreateProjectRequest request) {
Project project = projectService.update(projectId, request);
return ResponseEntity.ok(ApiResponse.ok("프로젝트가 수정되었습니다", project));
}
@Operation(summary = "프로젝트 삭제", description = "프로젝트를 삭제합니다 (Soft Delete)")
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{projectId}")
public ResponseEntity<ApiResponse<Void>> delete(
@PathVariable Long projectId) {
projectService.delete(projectId);
return ResponseEntity.ok(ApiResponse.ok("프로젝트가 삭제되었습니다", null));
}
}
@@ -0,0 +1,129 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.CreateReportRequest;
import com.srsystem.dto.ReportSummary;
import com.srsystem.dto.ReviewReportRequest;
import com.srsystem.dto.TicketSummary;
import com.srsystem.dto.WorkReportResponse;
import com.srsystem.entity.Employee;
import com.srsystem.entity.enums.RoleCode;
import com.srsystem.entity.enums.ReportStatus;
import com.srsystem.entity.enums.ReportType;
import com.srsystem.service.ReportService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
@Tag(name = "Report", description = "업무 보고 관리 API")
@RestController
@RequestMapping("/api/v1/reports")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class ReportController {
private final ReportService reportService;
@Operation(summary = "보고서 목록 조회", description = "필터 조건으로 보고서 목록을 조회합니다. EMPLOYEE 는 본인만, MANAGER+ 는 전체")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<Page<WorkReportResponse>>> getReports(
@RequestParam(required = false) ReportStatus status,
@RequestParam(required = false) String type,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate targetDate,
@PageableDefault(size = 20) Pageable pageable,
@CurrentUser Employee currentUser) {
Page<WorkReportResponse> reports;
if (currentUser.getRoleCode() == RoleCode.EMPLOYEE) {
// Employee sees only their own reports
reports = reportService.findByUserId(currentUser.getEmpId(), status, type, targetDate, pageable);
} else {
// Manager/Admin see all reports, with SUBMITTED first
reports = reportService.getReports(status, type, targetDate, null, pageable);
}
return ResponseEntity.ok(ApiResponse.ok(reports));
}
@Operation(summary = "보고서 단건 조회", description = "보고서 ID 로 단일 보고서 정보를 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/{reportId}")
public ResponseEntity<ApiResponse<WorkReportResponse>> getReport(@PathVariable Long reportId) {
WorkReportResponse report = reportService.getReport(reportId);
return ResponseEntity.ok(ApiResponse.ok(report));
}
@Operation(summary = "보고서 작성", description = "새로운 업무 보고서를 작성합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@PostMapping
public ResponseEntity<ApiResponse<WorkReportResponse>> createReport(
@Valid @RequestBody CreateReportRequest request,
@CurrentUser Employee currentUser) {
WorkReportResponse report = reportService.createReport(currentUser.getEmpId(), request);
return ResponseEntity.ok(ApiResponse.ok("보고서가 작성되었습니다", report));
}
@Operation(summary = "보고서 상태 변경", description = "보고서 상태를 변경합니다 (DRAFT→SUBMITTED)")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@PutMapping("/{reportId}/status")
public ResponseEntity<ApiResponse<WorkReportResponse>> updateStatus(
@PathVariable Long reportId,
@RequestBody Map<String, String> body,
@CurrentUser Employee currentUser) {
ReportStatus newStatus = ReportStatus.valueOf(body.get("status"));
WorkReportResponse report = reportService.updateStatus(reportId, currentUser.getEmpId(), newStatus);
return ResponseEntity.ok(ApiResponse.ok("보고서 상태가 변경되었습니다", report));
}
@Operation(summary = "보고서 결재", description = "관리자가 보고서를 승인/반려합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{reportId}/review")
public ResponseEntity<ApiResponse<WorkReportResponse>> reviewReport(
@PathVariable Long reportId,
@Valid @RequestBody ReviewReportRequest request,
@CurrentUser Employee currentUser) {
WorkReportResponse report = reportService.reviewReport(reportId, currentUser.getEmpId(), request);
return ResponseEntity.ok(ApiResponse.ok("보고서가 결재되었습니다", report));
}
@Operation(summary = "보고서 삭제", description = "보고서를 삭제합니다 (LOCKED 상태는 불가)")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@DeleteMapping("/{reportId}")
public ResponseEntity<ApiResponse<Void>> deleteReport(@PathVariable Long reportId) {
reportService.deleteReport(reportId);
return ResponseEntity.ok(ApiResponse.ok("보고서가 삭제되었습니다", null));
}
@Operation(summary = "보고서 결재 요약 조회", description = "상태별 보고서 개수를 반환합니다 (MANAGER/ADMIN)")
@PreAuthorize("hasAnyRole('MANAGER','ADMIN')")
@GetMapping("/summary")
public ResponseEntity<ApiResponse<ReportSummary>> getSummary() {
return ResponseEntity.ok(ApiResponse.ok(reportService.getSummary()));
}
@Operation(summary = "SR 자동 완성", description = "해당 일자의 해결/종료된 SR 내역을 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/sr-auto-complete")
public ResponseEntity<ApiResponse<List<TicketSummary>>> getSrAutoComplete(
@RequestParam String date,
@CurrentUser Employee currentUser) {
List<TicketSummary> tickets = reportService.getResolvedTicketsForDate(currentUser.getEmpId(), date);
return ResponseEntity.ok(ApiResponse.ok(tickets));
}
}
@@ -0,0 +1,131 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.CreateCommentRequest;
import com.srsystem.dto.CreateTicketRequest;
import com.srsystem.dto.IssueTicket;
import com.srsystem.dto.TicketCommentResponse;
import com.srsystem.entity.Employee;
import com.srsystem.entity.enums.RoleCode;
import com.srsystem.entity.enums.TicketCategory;
import com.srsystem.entity.enums.TicketPriority;
import com.srsystem.entity.enums.TicketStatus;
import com.srsystem.service.TicketService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "Ticket", description = "SR 티켓 관리 API")
@RestController
@RequestMapping("/api/v1/tickets")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class TicketController {
private final TicketService ticketService;
@Operation(summary = "티켓 목록 조회", description = "필터 조건으로 티켓 목록을 조회합니다. EMPLOYEE 는 본인 티켓만, MANAGER/ADMIN 은 전체 조회")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<Page<IssueTicket>>> getTickets(
@RequestParam(required = false) TicketStatus status,
@RequestParam(required = false) String category,
@RequestParam(required = false) String priority,
@RequestParam(required = false) String keyword,
@PageableDefault(size = 20) Pageable pageable,
@CurrentUser Employee currentUser) {
Long requesterId = currentUser.getRoleCode() == RoleCode.EMPLOYEE ? currentUser.getEmpId() : null;
Page<IssueTicket> tickets = ticketService.getTickets(status, category, priority, keyword, pageable, requesterId);
return ResponseEntity.ok(ApiResponse.ok(tickets));
}
@Operation(summary = "티켓 단건 조회", description = "티켓 ID 로 단일 티켓 정보를 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/{ticketId}")
public ResponseEntity<ApiResponse<IssueTicket>> getTicket(@PathVariable Long ticketId) {
IssueTicket ticket = ticketService.getTicket(ticketId);
return ResponseEntity.ok(ApiResponse.ok(ticket));
}
@Operation(summary = "티켓 생성", description = "새로운 SR 티켓을 생성합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@PostMapping
public ResponseEntity<ApiResponse<IssueTicket>> createTicket(
@Valid @RequestBody CreateTicketRequest request,
@CurrentUser Employee currentUser) {
IssueTicket ticket = ticketService.createTicket(currentUser.getEmpId(), request);
return ResponseEntity.ok(ApiResponse.ok("티켓이 등록되었습니다", ticket));
}
@Operation(summary = "티켓 상태 변경", description = "티켓 상태를 변경합니다 (OPEN→ASSIGNED→IN_PROGRESS→RESOLVED→CLOSED)")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{ticketId}/status")
public ResponseEntity<ApiResponse<IssueTicket>> updateStatus(
@PathVariable Long ticketId,
@RequestBody Map<String, String> body,
@CurrentUser Employee currentUser) {
TicketStatus newStatus = TicketStatus.valueOf(body.get("status"));
IssueTicket ticket = ticketService.updateStatus(ticketId, currentUser.getEmpId(), newStatus);
return ResponseEntity.ok(ApiResponse.ok("티켓 상태가 변경되었습니다", ticket));
}
@Operation(summary = "티켓 담당자 배정", description = "티켓에 담당자를 배정합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{ticketId}/assign")
public ResponseEntity<ApiResponse<IssueTicket>> assignTicket(
@PathVariable Long ticketId,
@RequestBody Map<String, Long> body) {
IssueTicket ticket = ticketService.assignTicket(ticketId, body.get("assigneeId"));
return ResponseEntity.ok(ApiResponse.ok("티켓이 배정되었습니다", ticket));
}
@Operation(summary = "티켓 해결 처리", description = "티켓을 해결 처리하고 해결 노트를 기록합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{ticketId}/resolve")
public ResponseEntity<ApiResponse<IssueTicket>> resolveTicket(
@PathVariable Long ticketId,
@RequestBody Map<String, String> body) {
IssueTicket ticket = ticketService.resolveTicket(ticketId, body.get("resolutionNote"));
return ResponseEntity.ok(ApiResponse.ok("티켓이 해결되었습니다", ticket));
}
@Operation(summary = "티켓 삭제", description = "티켓을 삭제합니다 (Soft Delete)")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@DeleteMapping("/{ticketId}")
public ResponseEntity<ApiResponse<Void>> deleteTicket(@PathVariable Long ticketId) {
ticketService.deleteTicket(ticketId);
return ResponseEntity.ok(ApiResponse.ok("티켓이 삭제되었습니다", null));
}
@Operation(summary = "댓글 목록 조회", description = "티켓의 댓글 목록을 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping("/{ticketId}/comments")
public ResponseEntity<ApiResponse<List<TicketCommentResponse>>> getComments(@PathVariable Long ticketId) {
List<TicketCommentResponse> comments = ticketService.getComments(ticketId);
return ResponseEntity.ok(ApiResponse.ok(comments));
}
@Operation(summary = "댓글 등록", description = "티켓에 댓글을 등록합니다 (CLOSED 티켓은 불가)")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@PostMapping("/{ticketId}/comments")
public ResponseEntity<ApiResponse<TicketCommentResponse>> addComment(
@PathVariable Long ticketId,
@Valid @RequestBody CreateCommentRequest request,
@CurrentUser Employee currentUser) {
TicketCommentResponse response = ticketService.addComment(ticketId, currentUser.getEmpId(), request);
return ResponseEntity.ok(ApiResponse.ok("댓글이 등록되었습니다", response));
}
}
@@ -0,0 +1,55 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.dto.TrainingSummary;
import com.srsystem.dto.UpdateProgressRequest;
import com.srsystem.entity.Employee;
import com.srsystem.entity.TrainingProgress;
import com.srsystem.service.TrainingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "Training", description = "교육 관리 API")
@RestController
@RequestMapping("/api/v1/training")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class TrainingController {
private final TrainingService trainingService;
@Operation(summary = "내 교육 진도 조회", description = "로그인한 사용자의 교육 진도 목록을 조회합니다")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<List<TrainingProgress>>> getMyProgress(@CurrentUser Employee currentUser) {
List<TrainingProgress> progress = trainingService.getMyProgress(currentUser.getEmpId());
return ResponseEntity.ok(ApiResponse.ok(progress));
}
@Operation(summary = "교육 진도 업데이트", description = "교육 진도율을 업데이트합니다")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@PutMapping("/{progressId}")
public ResponseEntity<ApiResponse<TrainingProgress>> updateProgress(
@PathVariable Long progressId,
@Valid @RequestBody UpdateProgressRequest request) {
TrainingProgress progress = trainingService.updateProgress(progressId, request);
return ResponseEntity.ok(ApiResponse.ok("교육 진도가 업데이트되었습니다", progress));
}
@Operation(summary = "전체 교육 현황 조회", description = "모든 사용자의 교육 이수 현황을 조회합니다 (관리자용)")
@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")
@GetMapping("/summary")
public ResponseEntity<ApiResponse<List<TrainingSummary>>> getSummary() {
List<TrainingSummary> summary = trainingService.getSummary();
return ResponseEntity.ok(ApiResponse.ok(summary));
}
}
@@ -0,0 +1,72 @@
package com.srsystem.controller;
import com.srsystem.config.jwt.CurrentUser;
import com.srsystem.dto.ApiResponse;
import com.srsystem.entity.Employee;
import com.srsystem.entity.WeeklyPlan;
import com.srsystem.service.WeeklyPlanService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "Weekly Plan", description = "주간 계획 관리 API")
@RestController
@RequestMapping("/api/v1/weekly-plans")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearerAuth")
public class WeeklyPlanController {
private final WeeklyPlanService weeklyPlanService;
@GetMapping
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@Operation(summary = "내 주간 계획 목록 조회", description = "현재 사용자의 모든 주간 계획 목록을 조회합니다.")
public ResponseEntity<ApiResponse<List<WeeklyPlan>>> getMyPlans(@CurrentUser Employee currentUser) {
return ResponseEntity.ok(ApiResponse.ok(weeklyPlanService.getMyPlans(currentUser.getEmpId())));
}
@GetMapping("/{planId}")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@Operation(summary = "주간 계획 상세 조회", description = "특정 주간 계획의 상세 정보를 조회합니다.")
public ResponseEntity<ApiResponse<WeeklyPlan>> getPlan(@PathVariable Long planId) {
return ResponseEntity.ok(ApiResponse.ok(weeklyPlanService.getPlan(planId)));
}
@PostMapping
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@Operation(summary = "주간 계획 생성", description = "새로운 주간 계획을 생성합니다.")
public ResponseEntity<ApiResponse<WeeklyPlan>> createPlan(
@RequestBody Map<String, String> body,
@CurrentUser Employee currentUser) {
WeeklyPlan plan = weeklyPlanService.createPlan(currentUser.getEmpId(), body.get("tasks"));
return ResponseEntity.ok(ApiResponse.ok("주간 계획이 생성되었습니다", plan));
}
@PutMapping("/{planId}")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@Operation(summary = "주간 계획 수정", description = "기존 주간 계획의 태스크와 상태를 수정합니다.")
public ResponseEntity<ApiResponse<WeeklyPlan>> updatePlan(
@PathVariable Long planId,
@RequestBody Map<String, String> body) {
String statusStr = body.get("status");
com.srsystem.entity.enums.WeeklyPlanStatus status = statusStr != null ?
com.srsystem.entity.enums.WeeklyPlanStatus.valueOf(statusStr) : null;
WeeklyPlan plan = weeklyPlanService.updatePlan(planId, body.get("tasks"), status);
return ResponseEntity.ok(ApiResponse.ok("주간 계획이 수정되었습니다", plan));
}
@DeleteMapping("/{planId}")
@PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')")
@Operation(summary = "주간 계획 삭제", description = "주간 계획을 삭제합니다 (Soft Delete).")
public ResponseEntity<ApiResponse<Void>> deletePlan(@PathVariable Long planId) {
weeklyPlanService.deletePlan(planId);
return ResponseEntity.ok(ApiResponse.ok("주간 계획이 삭제되었습니다", null));
}
}
@@ -0,0 +1,16 @@
package com.srsystem.dto;
public record ApiResponse<T>(boolean success, String message, T data) {
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(true, "OK", data);
}
public static <T> ApiResponse<T> ok(String message, T data) {
return new ApiResponse<>(true, message, data);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(false, message, null);
}
}
@@ -0,0 +1,10 @@
package com.srsystem.dto;
public record CalendarEvent(
String id,
String title,
String date,
String type,
String status,
String endDate
) {}
@@ -0,0 +1,8 @@
package com.srsystem.dto;
import jakarta.validation.constraints.NotBlank;
public record ChangePasswordRequest(
@NotBlank String currentPassword,
@NotBlank String newPassword
) {}
@@ -0,0 +1,14 @@
package com.srsystem.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record CommonCodeRequest(
@NotBlank String groupCode,
@NotBlank String codeValue,
@NotBlank String codeName,
String optionVal1,
String optionVal2,
@NotNull Integer sortOrder,
@NotNull Character isActive
) {}
@@ -0,0 +1,9 @@
package com.srsystem.dto;
import jakarta.validation.constraints.NotBlank;
public record CreateCommentRequest(
@NotBlank String commentText,
String attachedFileUrl
) {
}
@@ -0,0 +1,16 @@
package com.srsystem.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
public record CreateDispatchRequest(
@NotNull Long employeeId,
@NotNull Long projectId,
@NotBlank String roleTitle,
@NotNull Integer contributionRate,
@NotBlank String individualTech,
@NotBlank String performedTasks,
LocalDate startDate
) {}
@@ -0,0 +1,21 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.EquipmentStatus;
import com.srsystem.entity.enums.EquipmentType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
public record CreateEquipmentRequest(
@NotNull EquipmentType equipmentType,
@NotBlank String manufacturer,
@NotBlank String modelName,
@NotBlank String serialNumber,
@NotBlank String assetCode,
String specifications,
@NotNull EquipmentStatus status,
@NotNull LocalDate purchaseDate,
LocalDate warrantyExpDate
) {
}
@@ -0,0 +1,16 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.MaintenanceType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
public record CreateMaintenanceRequest(
@NotNull MaintenanceType maintenanceType,
@NotBlank String issueDescription,
String actionTaken,
@NotNull BigDecimal cost,
@NotBlank String reportedBy
) {
}
@@ -0,0 +1,44 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.ContractType;
import com.srsystem.entity.enums.ProjectType;
import com.srsystem.entity.enums.WorkLocationType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
public record CreateProjectRequest(
@NotBlank @Size(max = 255)
String projectName,
@NotNull
ProjectType projectType,
@NotBlank @Size(max = 255)
String contractCompany,
@Size(max = 255)
String endUserCompany,
@NotNull
ContractType contractType,
@NotNull
LocalDate startDate,
LocalDate endDate,
@NotNull
WorkLocationType workLocationType,
@Size(max = 500)
String workAddress,
String techEnvironment,
String mainTaskDesc,
Long internalManagerId
) {}
@@ -0,0 +1,23 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.ReportType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDate;
public record CreateReportRequest(
Long dispatchId,
@NotNull ReportType reportType,
@NotNull LocalDate targetDate,
@NotBlank String completedTasks,
@NotBlank String plannedTasks,
String issuesAndRisks,
String supportRequested,
BigDecimal workHours,
String blockerFlag,
String artifacts,
Long weeklyPlanId
) {
}
@@ -0,0 +1,15 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.TicketCategory;
import com.srsystem.entity.enums.TicketPriority;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record CreateTicketRequest(
@NotNull Long projectId,
@NotNull TicketCategory category,
@NotNull TicketPriority priority,
@NotBlank String title,
@NotBlank String description
) {
}
@@ -0,0 +1,9 @@
package com.srsystem.dto;
public record EmployeeSearchResult(
Long empId,
String name,
String email,
String department
) {
}
@@ -0,0 +1,19 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.DispatchStatus;
import com.srsystem.entity.enums.EmpStatus;
import com.srsystem.entity.enums.RoleCode;
import java.time.LocalDate;
public record EmployeeSummary(
Long empId,
String name,
String email,
String phone,
RoleCode roleCode,
EmpStatus empStatus,
DispatchStatus dispatchStatus,
LocalDate joinedDate,
LocalDate leftDate
) {}
@@ -0,0 +1,8 @@
package com.srsystem.dto;
import java.util.Map;
public record EquipmentSummary(
Map<String, Long> counts
) {
}
@@ -0,0 +1,4 @@
package com.srsystem.dto;
public record HealthResponse(boolean parked, String message, String resumeTime, String parkingSchedule) {
}
@@ -0,0 +1,25 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.TicketCategory;
import com.srsystem.entity.enums.TicketPriority;
import com.srsystem.entity.enums.TicketStatus;
import java.time.LocalDateTime;
public record IssueTicket(
Long ticketId,
String projectName,
String requesterName,
String assigneeName,
TicketCategory category,
TicketPriority priority,
String title,
String description,
TicketStatus status,
LocalDateTime dueDate,
LocalDateTime resolvedAt,
String resolutionNote,
boolean slaWarnFlag,
LocalDateTime createdAt
) {
}
@@ -0,0 +1,13 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.DispatchStatus;
import com.srsystem.entity.enums.RoleCode;
import jakarta.validation.constraints.NotBlank;
public record LoginRequest(
@NotBlank(message = "로그인 ID 는 필수입니다")
String loginId,
@NotBlank(message = "비밀번호는 필수입니다")
String password
) {
}
@@ -0,0 +1,11 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.DispatchStatus;
import com.srsystem.entity.enums.RoleCode;
public record LoginResponse(
String accessToken,
String refreshToken,
EmployeeSummary employee
) {
}
@@ -0,0 +1,20 @@
package com.srsystem.dto;
import lombok.Getter;
@Getter
public class NotificationEvent {
private final String type;
private final String title;
private final String message;
private final String linkUrl;
private final Long recipientId;
public NotificationEvent(String type, String title, String message, String linkUrl, Long recipientId) {
this.type = type;
this.title = title;
this.message = message;
this.linkUrl = linkUrl;
this.recipientId = recipientId;
}
}
@@ -0,0 +1,74 @@
package com.srsystem.dto;
import java.time.LocalDate;
import java.util.List;
public class PortfolioResponse {
private Long dispatchId;
private String projectName;
private String roleTitle;
private Integer contributionRate;
private String individualTech;
private String performedTasks;
private String achievements;
private LocalDate startDate;
private LocalDate endDate;
private Integer internalRating;
private List<BoundReport> reports;
private List<BoundTicket> resolvedTickets;
// Inner classes
public static class BoundReport {
public String targetDate;
public String reportType;
public String completedTasks;
}
public static class BoundTicket {
public Long ticketId;
public String title;
public String category;
public String resolutionNote;
}
public PortfolioResponse() {}
// --- Getters & Setters ---
public Long getDispatchId() { return dispatchId; }
public void setDispatchId(Long dispatchId) { this.dispatchId = dispatchId; }
public String getProjectName() { return projectName; }
public void setProjectName(String projectName) { this.projectName = projectName; }
public String getRoleTitle() { return roleTitle; }
public void setRoleTitle(String roleTitle) { this.roleTitle = roleTitle; }
public Integer getContributionRate() { return contributionRate; }
public void setContributionRate(Integer contributionRate) { this.contributionRate = contributionRate; }
public String getIndividualTech() { return individualTech; }
public void setIndividualTech(String individualTech) { this.individualTech = individualTech; }
public String getPerformedTasks() { return performedTasks; }
public void setPerformedTasks(String performedTasks) { this.performedTasks = performedTasks; }
public String getAchievements() { return achievements; }
public void setAchievements(String achievements) { this.achievements = achievements; }
public LocalDate getStartDate() { return startDate; }
public void setStartDate(LocalDate startDate) { this.startDate = startDate; }
public LocalDate getEndDate() { return endDate; }
public void setEndDate(LocalDate endDate) { this.endDate = endDate; }
public Integer getInternalRating() { return internalRating; }
public void setInternalRating(Integer internalRating) { this.internalRating = internalRating; }
public List<BoundReport> getReports() { return reports; }
public void setReports(List<BoundReport> reports) { this.reports = reports; }
public List<BoundTicket> getResolvedTickets() { return resolvedTickets; }
public void setResolvedTickets(List<BoundTicket> resolvedTickets) { this.resolvedTickets = resolvedTickets; }
}
@@ -0,0 +1,9 @@
package com.srsystem.dto;
import jakarta.validation.constraints.NotBlank;
public record RefreshTokenRequest(
@NotBlank(message = "리프레시 토큰은 필수입니다")
String refreshToken
) {
}
@@ -0,0 +1,8 @@
package com.srsystem.dto;
public record ReportSummary(
long submitted,
long approved,
long rejected,
long draft
) {}
@@ -0,0 +1,11 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.ReportStatus;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record ReviewReportRequest(
@NotNull ReportStatus status,
String reviewComment
) {
}
@@ -0,0 +1,12 @@
package com.srsystem.dto;
import java.time.LocalDateTime;
public record TicketCommentResponse(
Long commentId,
String userName,
String commentText,
String attachedFileUrl,
LocalDateTime createdAt
) {
}
@@ -0,0 +1,10 @@
package com.srsystem.dto;
public record TicketSummary(
Long ticketId,
String title,
String category,
String priority,
String resolutionNote,
String resolvedAt
) {}
@@ -0,0 +1,10 @@
package com.srsystem.dto;
public record TrainingSummary(
Long userId,
String userName,
long totalCourses,
long completedCount,
double avgProgressRate
) {
}
@@ -0,0 +1,12 @@
package com.srsystem.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UpdateAchievementsRequest {
private String achievements;
}
@@ -0,0 +1,19 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.EquipmentStatus;
import com.srsystem.entity.enums.EquipmentType;
import java.time.LocalDate;
public record UpdateEquipmentRequest(
EquipmentType equipmentType,
String manufacturer,
String modelName,
String serialNumber,
String assetCode,
String specifications,
EquipmentStatus status,
LocalDate purchaseDate,
LocalDate warrantyExpDate
) {
}
@@ -0,0 +1,11 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.TrainingStatus;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
public record UpdateProgressRequest(
@Min(0) @Max(100) Integer progressRate,
TrainingStatus status
) {
}
@@ -0,0 +1,11 @@
package com.srsystem.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record WakeRequest(
@NotBlank String requestedBy,
@NotBlank String reason,
@NotNull String requestedAt
) {
}
@@ -0,0 +1,4 @@
package com.srsystem.dto;
public record WakeResponse(boolean success, String message, String estimatedWakeTime) {
}
@@ -0,0 +1,29 @@
package com.srsystem.dto;
import com.srsystem.entity.enums.ReportStatus;
import com.srsystem.entity.enums.ReportType;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
public record WorkReportResponse(
Long reportId,
String userName,
String projectName,
ReportType reportType,
LocalDate targetDate,
String completedTasks,
String plannedTasks,
String issuesAndRisks,
String supportRequested,
ReportStatus status,
String reviewerName,
String reviewComment,
LocalDateTime reviewedAt,
LocalDateTime createdAt,
BigDecimal workHours,
String blockerFlag,
String artifacts
) {
}
@@ -0,0 +1,38 @@
package com.srsystem.entity;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@SQLDelete(sql = "UPDATE #{#entityName} SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
@Getter
@Setter
public abstract class BaseEntity {
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@Column(name = "is_deleted", nullable = false, length = 1)
private char isDeleted = 'N';
public void setIsDeleted(char isDeleted) {
this.isDeleted = isDeleted;
}
}
@@ -0,0 +1,45 @@
package com.srsystem.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
@Entity
@Table(name = "tb_common_code")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_common_code SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class CommonCode extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_common_codes")
@SequenceGenerator(name = "sq_common_codes", sequenceName = "sq_common_codes", allocationSize = 50)
@Column(name = "id")
private Long codeId;
@Column(name = "group_code", nullable = false, length = 50)
private String groupCode;
@Column(name = "code_value", nullable = false, length = 100)
private String codeValue;
@Column(name = "code_name", nullable = false, length = 255)
private String codeName;
@Column(name = "option_val1", length = 255)
private String optionVal1;
@Column(name = "option_val2", length = 255)
private String optionVal2;
@Column(name = "sort_order", nullable = false)
private Integer sortOrder;
@Column(name = "is_active", nullable = false, length = 1)
private char isActive = 'Y';
}
@@ -0,0 +1,43 @@
package com.srsystem.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.math.BigDecimal;
@Entity
@Table(name = "tb_daily_task_progress")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_daily_task_progress SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class DailyTaskProgress extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_daily_task_progress")
@SequenceGenerator(name = "sq_daily_task_progress", sequenceName = "sq_daily_task_progress", allocationSize = 50)
@Column(name = "id")
private Long progressId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "report_id", nullable = false)
private Report report;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "weekly_plan_id", nullable = false)
private WeeklyPlan weeklyPlan;
@Column(name = "task_name", nullable = false, length = 500)
private String taskName;
@Column(name = "progress_rate", nullable = false)
private Integer progressRate;
@Column(name = "work_hours", nullable = false)
private BigDecimal workHours;
}
@@ -0,0 +1,64 @@
package com.srsystem.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.time.LocalDate;
@Entity
@Table(name = "tb_dispatch_history")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_dispatch_history SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class DispatchHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_dispatch_histories")
@SequenceGenerator(name = "sq_dispatch_histories", sequenceName = "sq_dispatch_histories", allocationSize = 50)
@Column(name = "id")
private Long dispatchId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "employee_id", nullable = false)
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Employee employee;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id", nullable = false)
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Project project;
@Column(name = "start_date", nullable = false)
private LocalDate startDate;
@Column(name = "end_date")
private LocalDate endDate;
@Column(name = "role_title", nullable = false, length = 255)
private String roleTitle;
@Column(name = "contribution_rate", nullable = false)
private Integer contributionRate;
@Column(name = "individual_tech", columnDefinition = "TEXT", nullable = false)
private String individualTech;
@Column(name = "performed_tasks", columnDefinition = "TEXT", nullable = false)
private String performedTasks;
@Column(name = "achievements", columnDefinition = "TEXT")
private String achievements;
@Column(name = "client_feedback", columnDefinition = "TEXT")
private String clientFeedback;
@Column(name = "internal_rating")
private Integer internalRating;
}
@@ -0,0 +1,63 @@
package com.srsystem.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.srsystem.entity.enums.DispatchStatus;
import com.srsystem.entity.enums.EmpStatus;
import com.srsystem.entity.enums.RoleCode;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.time.LocalDate;
@Entity
@Table(name = "tb_employee")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_employee SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class Employee extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_employees")
@SequenceGenerator(name = "sq_employees", sequenceName = "sq_employees", allocationSize = 50)
@Column(name = "id")
private Long empId;
@Column(name = "login_id", nullable = false, unique = true, length = 50)
private String loginId;
@Column(name = "password", nullable = false)
private String password;
@Column(name = "name", nullable = false, length = 100)
private String name;
@Column(name = "email", length = 255)
private String email;
@Column(name = "phone", length = 20)
private String phone;
@Enumerated(EnumType.STRING)
@Column(name = "role_code", nullable = false, length = 20)
private RoleCode roleCode;
@Enumerated(EnumType.STRING)
@Column(name = "emp_status", nullable = false, length = 20)
private EmpStatus empStatus;
@Enumerated(EnumType.STRING)
@Column(name = "dispatch_status", nullable = false, length = 20)
private DispatchStatus dispatchStatus;
@Column(name = "joined_date")
private LocalDate joinedDate;
@Column(name = "left_date")
private LocalDate leftDate;
}
@@ -0,0 +1,64 @@
package com.srsystem.entity;
import com.srsystem.entity.enums.EquipmentStatus;
import com.srsystem.entity.enums.EquipmentType;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.time.LocalDate;
@Entity
@Table(name = "tb_equipment")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_equipment SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class Equipment extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_equipment")
@SequenceGenerator(name = "sq_equipment", sequenceName = "sq_equipment", allocationSize = 50)
@Column(name = "id")
private Long equipmentId;
@Enumerated(EnumType.STRING)
@Column(name = "equipment_type", nullable = false, length = 20)
private EquipmentType equipmentType;
@Column(name = "manufacturer", nullable = false, length = 255)
private String manufacturer;
@Column(name = "model_name", nullable = false, length = 255)
private String modelName;
@Column(name = "serial_number", nullable = false, unique = true, length = 100)
private String serialNumber;
@Column(name = "asset_code", nullable = false, unique = true, length = 100)
private String assetCode;
@Column(name = "specifications", columnDefinition = "JSONB")
private String specifications;
@Column(name = "images", columnDefinition = "JSONB")
private String images;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private EquipmentStatus status;
@Column(name = "purchase_date", nullable = false)
private LocalDate purchaseDate;
@Column(name = "warranty_exp_date")
private LocalDate warrantyExpDate;
@Version
@Column(name = "version", nullable = false)
private Long version;
}
@@ -0,0 +1,46 @@
package com.srsystem.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.time.LocalDateTime;
@Entity
@Table(name = "tb_equipment_assignment")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_equipment_assignment SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class EquipmentAssignment extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_equipment_assignments")
@SequenceGenerator(name = "sq_equipment_assignments", sequenceName = "sq_equipment_assignments", allocationSize = 50)
@Column(name = "id")
private Long assignmentId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "equipment_id", nullable = false)
private Equipment equipment;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private Employee user;
@Column(name = "assigned_date", nullable = false)
private LocalDateTime assignedDate;
@Column(name = "returned_date")
private LocalDateTime returnedDate;
@Column(name = "cond_on_assign", columnDefinition = "TEXT", nullable = false)
private String condOnAssign;
@Column(name = "cond_on_return", columnDefinition = "TEXT")
private String condOnReturn;
}
@@ -0,0 +1,55 @@
package com.srsystem.entity;
import com.srsystem.entity.enums.MaintenanceType;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "tb_equipment_maintenance")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_equipment_maintenance SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class EquipmentMaintenance extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_equipment_maintenance")
@SequenceGenerator(name = "sq_equipment_maintenance", sequenceName = "sq_equipment_maintenance", allocationSize = 50)
@Column(name = "id")
private Long maintenanceId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "equipment_id", nullable = false)
private Equipment equipment;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reported_by", nullable = false)
private Employee reportedBy;
@Enumerated(EnumType.STRING)
@Column(name = "maintenance_type", nullable = false, length = 20)
private MaintenanceType maintenanceType;
@Column(name = "issue_description", columnDefinition = "TEXT", nullable = false)
private String issueDescription;
@Column(name = "action_taken", columnDefinition = "TEXT")
private String actionTaken;
@Column(name = "cost", nullable = false, precision = 12, scale = 2)
private BigDecimal cost;
@Column(name = "reported_date", nullable = false)
private LocalDateTime reportedDate;
@Column(name = "resolved_date")
private LocalDateTime resolvedDate;
}
@@ -0,0 +1,43 @@
package com.srsystem.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
@Entity
@Table(name = "tb_notification")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_notification SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class Notification extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_notifications")
@SequenceGenerator(name = "sq_notifications", sequenceName = "sq_notifications", allocationSize = 50)
@Column(name = "id")
private Long notificationId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private Employee user;
@Column(name = "type", nullable = false, length = 50)
private String type;
@Column(name = "title", nullable = false, length = 255)
private String title;
@Column(name = "message", columnDefinition = "TEXT", nullable = false)
private String message;
@Column(name = "is_read", nullable = false, length = 1)
private char isRead = 'N';
@Column(name = "link_url", length = 500)
private String linkUrl;
}
@@ -0,0 +1,76 @@
package com.srsystem.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.srsystem.entity.enums.ContractType;
import com.srsystem.entity.enums.ProjectStatus;
import com.srsystem.entity.enums.ProjectType;
import com.srsystem.entity.enums.WorkLocationType;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.time.LocalDate;
@Entity
@Table(name = "tb_project")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_project SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class Project extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_projects")
@SequenceGenerator(name = "sq_projects", sequenceName = "sq_projects", allocationSize = 50)
@Column(name = "id")
private Long projectId;
@Enumerated(EnumType.STRING)
@Column(name = "project_type", nullable = false, length = 20)
private ProjectType projectType;
@Column(name = "project_name", nullable = false, length = 255)
private String projectName;
@Column(name = "contract_company", nullable = false, length = 255)
private String contractCompany;
@Column(name = "end_user_company", length = 255)
private String endUserCompany;
@Enumerated(EnumType.STRING)
@Column(name = "contract_type", nullable = false, length = 20)
private ContractType contractType;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private ProjectStatus status;
@Column(name = "start_date", nullable = false)
private LocalDate startDate;
@Column(name = "end_date")
private LocalDate endDate;
@Enumerated(EnumType.STRING)
@Column(name = "work_location_type", nullable = false, length = 20)
private WorkLocationType workLocationType;
@Column(name = "work_address", length = 500)
private String workAddress;
@Column(name = "tech_environment", columnDefinition = "TEXT")
private String techEnvironment;
@Column(name = "main_task_desc", columnDefinition = "TEXT")
private String mainTaskDesc;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "internal_manager_id")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Employee internalManager;
}
@@ -0,0 +1,85 @@
package com.srsystem.entity;
import com.srsystem.entity.enums.ReportStatus;
import com.srsystem.entity.enums.ReportType;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Entity
@Table(name = "tb_report")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_report SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class Report extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_reports")
@SequenceGenerator(name = "sq_reports", sequenceName = "sq_reports", allocationSize = 50)
@Column(name = "id")
private Long reportId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dispatch_id")
private DispatchHistory dispatch;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private Employee user;
@Enumerated(EnumType.STRING)
@Column(name = "report_type", nullable = false, length = 20)
private ReportType reportType;
@Column(name = "target_date", nullable = false)
private LocalDate targetDate;
@Column(name = "completed_tasks", columnDefinition = "TEXT", nullable = false)
private String completedTasks;
@Column(name = "planned_tasks", columnDefinition = "TEXT", nullable = false)
private String plannedTasks;
@Column(name = "issues_and_risks", columnDefinition = "TEXT")
private String issuesAndRisks;
@Column(name = "support_requested", columnDefinition = "TEXT")
private String supportRequested;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private ReportStatus status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reviewer_id")
private Employee reviewer;
@Column(name = "review_comment", columnDefinition = "TEXT")
private String reviewComment;
@Column(name = "reviewed_at")
private LocalDateTime reviewedAt;
@Column(name = "work_hours")
private BigDecimal workHours;
@Column(name = "blocker_flag", length = 1)
@Builder.Default
private char blockerFlag = 'N';
@Column(name = "artifacts", columnDefinition = "JSONB")
private String artifacts;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "weekly_plan_id")
private WeeklyPlan weeklyPlan;
}
@@ -0,0 +1,71 @@
package com.srsystem.entity;
import com.srsystem.entity.enums.TicketCategory;
import com.srsystem.entity.enums.TicketPriority;
import com.srsystem.entity.enums.TicketStatus;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.time.LocalDateTime;
@Entity
@Table(name = "tb_ticket")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_ticket SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class Ticket extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_tickets")
@SequenceGenerator(name = "sq_tickets", sequenceName = "sq_tickets", allocationSize = 50)
@Column(name = "id")
private Long ticketId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id", nullable = false)
private Project project;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "requester_id", nullable = false)
private Employee requester;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "assignee_id")
private Employee assignee;
@Enumerated(EnumType.STRING)
@Column(name = "category", nullable = false, length = 20)
private TicketCategory category;
@Enumerated(EnumType.STRING)
@Column(name = "priority", nullable = false, length = 20)
private TicketPriority priority;
@Column(name = "title", nullable = false, length = 500)
private String title;
@Column(name = "description", columnDefinition = "TEXT", nullable = false)
private String description;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private TicketStatus status;
@Column(name = "due_date")
private LocalDateTime dueDate;
@Column(name = "resolved_at")
private LocalDateTime resolvedAt;
@Column(name = "resolution_note", columnDefinition = "TEXT")
private String resolutionNote;
@Column(name = "sla_warn_flag", nullable = false, length = 1)
private char slaWarnFlag = 'N';
}
@@ -0,0 +1,38 @@
package com.srsystem.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
@Entity
@Table(name = "tb_ticket_comment")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_ticket_comment SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class TicketComment extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_ticket_comments")
@SequenceGenerator(name = "sq_ticket_comments", sequenceName = "sq_ticket_comments", allocationSize = 50)
@Column(name = "id")
private Long commentId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ticket_id", nullable = false)
private Ticket ticket;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private Employee user;
@Column(name = "comment_text", columnDefinition = "TEXT", nullable = false)
private String commentText;
@Column(name = "attached_file_url", length = 1000)
private String attachedFileUrl;
}
@@ -0,0 +1,39 @@
package com.srsystem.entity;
import com.srsystem.entity.enums.TrainingStatus;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
@Entity
@Table(name = "tb_training_progress")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_training_progress SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class TrainingProgress extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_training_progress")
@SequenceGenerator(name = "sq_training_progress", sequenceName = "sq_training_progress", allocationSize = 50)
@Column(name = "id")
private Long progressId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private Employee user;
@Column(name = "course_name", nullable = false, length = 255)
private String courseName;
@Column(name = "progress_rate", nullable = false)
private Integer progressRate;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private TrainingStatus status;
}
@@ -0,0 +1,41 @@
package com.srsystem.entity;
import com.srsystem.entity.enums.WeeklyPlanStatus;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import java.time.LocalDate;
@Entity
@Table(name = "tb_weekly_plan")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE tb_weekly_plan SET is_deleted = 'Y' WHERE id = ?")
@SQLRestriction("is_deleted = 'N'")
public class WeeklyPlan extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_weekly_plans")
@SequenceGenerator(name = "sq_weekly_plans", sequenceName = "sq_weekly_plans", allocationSize = 50)
@Column(name = "id")
private Long planId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private Employee user;
@Column(name = "week_start", nullable = false)
private LocalDate weekStart;
@Column(name = "status", nullable = false, length = 20)
@Enumerated(EnumType.STRING)
private WeeklyPlanStatus status;
@Column(name = "tasks", columnDefinition = "JSONB")
private String tasks;
}
@@ -0,0 +1,6 @@
package com.srsystem.entity.enums;
public enum ContractType {
TURN_KEY,
MM
}
@@ -0,0 +1,6 @@
package com.srsystem.entity.enums;
public enum DispatchStatus {
WAITING,
DISPATCHED
}
@@ -0,0 +1,7 @@
package com.srsystem.entity.enums;
public enum EmpStatus {
ACTIVE,
ON_LEAVE,
RESIGNED
}
@@ -0,0 +1,9 @@
package com.srsystem.entity.enums;
public enum EquipmentStatus {
AVAILABLE,
IN_USE,
REPAIRING,
RETIRED,
LOST
}
@@ -0,0 +1,8 @@
package com.srsystem.entity.enums;
public enum EquipmentType {
LAPTOP,
MONITOR,
TEST_DEVICE,
ACCESSORY
}
@@ -0,0 +1,8 @@
package com.srsystem.entity.enums;
public enum MaintenanceType {
REPAIR,
UPGRADE,
INSPECTION,
DISPOSAL
}
@@ -0,0 +1,8 @@
package com.srsystem.entity.enums;
public enum ProjectStatus {
PREPARING,
ONGOING,
COMPLETED,
HOLD
}
@@ -0,0 +1,7 @@
package com.srsystem.entity.enums;
public enum ProjectType {
SI,
SM,
MIGRATION
}
@@ -0,0 +1,8 @@
package com.srsystem.entity.enums;
public enum ReportStatus {
DRAFT,
SUBMITTED,
APPROVED,
REJECTED
}
@@ -0,0 +1,8 @@
package com.srsystem.entity.enums;
public enum ReportType {
DAILY,
WEEKLY,
MONTHLY,
RETURN
}
@@ -0,0 +1,7 @@
package com.srsystem.entity.enums;
public enum RoleCode {
ADMIN,
MANAGER,
EMPLOYEE
}
@@ -0,0 +1,8 @@
package com.srsystem.entity.enums;
public enum TicketCategory {
BUG,
FEATURE,
SUPPORT,
QA
}
@@ -0,0 +1,8 @@
package com.srsystem.entity.enums;
public enum TicketPriority {
LOW,
NORMAL,
HIGH,
CRITICAL
}
@@ -0,0 +1,9 @@
package com.srsystem.entity.enums;
public enum TicketStatus {
OPEN,
ASSIGNED,
IN_PROGRESS,
RESOLVED,
CLOSED
}
@@ -0,0 +1,7 @@
package com.srsystem.entity.enums;
public enum TrainingStatus {
NOT_STARTED,
ONGOING,
COMPLETED
}
@@ -0,0 +1,5 @@
package com.srsystem.entity.enums;
public enum WeeklyPlanStatus {
DRAFT, ACTIVE, CLOSED
}
@@ -0,0 +1,7 @@
package com.srsystem.entity.enums;
public enum WorkLocationType {
ON_SITE,
REMOTE,
HYBRID
}
@@ -0,0 +1,13 @@
package com.srsystem.exception;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
@@ -0,0 +1,38 @@
package com.srsystem.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
public enum ErrorCode {
RESIGNED_EMPLOYEE(HttpStatus.BAD_REQUEST, "EMP_ERR_001", "퇴사 처리된 직원 정보는 수정할 수 없습니다"),
LOCKED_REPORT(HttpStatus.BAD_REQUEST, "RPT_ERR_001", "결재 완료 보고서는 수정/삭제할 수 없습니다"),
FUTURE_REPORT_DATE(HttpStatus.BAD_REQUEST, "RPT_ERR_002", "미래 날짜의 업무 보고는 작성할 수 없습니다"),
CLOSED_TICKET_MOD(HttpStatus.BAD_REQUEST, "TCK_ERR_001", "종료된 티켓은 댓글/수정이 불가능합니다"),
ASSIGNEE_REQUIRED(HttpStatus.BAD_REQUEST, "TCK_ERR_002", "담당자 미배정 티켓은 IN_PROGRESS 전환 불가"),
EQUIP_NOT_AVAILABLE(HttpStatus.BAD_REQUEST, "EQP_ERR_001", "지급 가능한 재고 상태가 아닙니다"),
INVALID_ASSIGNEE(HttpStatus.BAD_REQUEST, "EQP_ERR_002", "재직 중인 직원에게만 장비 지급 가능"),
SYSTEM_CONCURRENCY(HttpStatus.CONFLICT, "SYS_ERR_001", "동시성 충돌 - 다시 시도해 주세요"),
DUPLICATE_LOGIN_ID(HttpStatus.CONFLICT, "EMP_ERR_002", "이미 사용 중인 로그인 ID 입니다"),
INVALID_CREDENTIALS(HttpStatus.UNAUTHORIZED, "AUTH_ERR_001", "아이디 또는 비밀번호가 일치하지 않습니다"),
EMPLOYEE_NOT_FOUND(HttpStatus.NOT_FOUND, "EMP_ERR_003", "직원을 찾을 수 없습니다"),
TICKET_NOT_FOUND(HttpStatus.NOT_FOUND, "TCK_ERR_003", "티켓을 찾을 수 없습니다"),
PROJECT_NOT_FOUND(HttpStatus.NOT_FOUND, "PRJ_ERR_001", "프로젝트를 찾을 수 없습니다"),
EQUIPMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "EQP_ERR_003", "장비를 찾을 수 없습니다"),
REPORT_NOT_FOUND(HttpStatus.NOT_FOUND, "RPT_ERR_003", "보고서를 찾을 수 없습니다"),
INVALID_ROLE(HttpStatus.FORBIDDEN, "AUTH_ERR_002", "권한이 없습니다"),
DISPATCH_NOT_FOUND(HttpStatus.NOT_FOUND, "DSP_ERR_001", "파견 이력을 찾을 수 없습니다"),
AUTH_INVALID_CREDENTIALS(HttpStatus.BAD_REQUEST, "AUTH_ERR_003", "현재 비밀번호가 일치하지 않습니다"),
CODE_NOT_FOUND(HttpStatus.NOT_FOUND, "COD_ERR_001", "공통코드를 찾을 수 없습니다"),
PLAN_NOT_FOUND(HttpStatus.NOT_FOUND, "PLN_ERR_001", "주간 계획을 찾을 수 없습니다");
private final HttpStatus httpStatus;
private final String errorCode;
private final String message;
ErrorCode(HttpStatus httpStatus, String errorCode, String message) {
this.httpStatus = httpStatus;
this.errorCode = errorCode;
this.message = message;
}
}
@@ -0,0 +1,50 @@
package com.srsystem.exception;
import com.srsystem.dto.ApiResponse;
import jakarta.persistence.OptimisticLockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException ex) {
ErrorCode errorCode = ex.getErrorCode();
return ResponseEntity
.status(errorCode.getHttpStatus())
.body(ApiResponse.error(errorCode.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationException(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity
.badRequest()
.body(ApiResponse.error(message));
}
@ExceptionHandler(OptimisticLockException.class)
public ResponseEntity<ApiResponse<Void>> handleOptimisticLockException(OptimisticLockException ex) {
log.warn("Optimistic lock conflict detected", ex);
return ResponseEntity
.status(ErrorCode.SYSTEM_CONCURRENCY.getHttpStatus())
.body(ApiResponse.error(ErrorCode.SYSTEM_CONCURRENCY.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleGenericException(Exception ex) {
log.error("Unexpected exception occurred", ex);
return ResponseEntity
.internalServerError()
.body(ApiResponse.error("서버 내부 오류가 발생했습니다."));
}
}
@@ -0,0 +1,57 @@
package com.srsystem.listener;
import com.srsystem.dto.NotificationEvent;
import com.srsystem.entity.Employee;
import com.srsystem.entity.Notification;
import com.srsystem.exception.BusinessException;
import com.srsystem.exception.ErrorCode;
import com.srsystem.repository.EmployeeRepository;
import com.srsystem.repository.NotificationRepository;
import com.srsystem.service.notification.EmailSender;
import com.srsystem.service.notification.SlackSender;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
@Component
@RequiredArgsConstructor
@Slf4j
public class NotificationListener {
private final NotificationRepository notificationRepository;
private final EmployeeRepository employeeRepository;
private final SlackSender slackSender;
private final EmailSender emailSender;
@Async
@TransactionalEventListener
public void handleNotification(NotificationEvent event) {
Employee recipient = employeeRepository.findById(event.getRecipientId())
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
Notification notification = Notification.builder()
.user(recipient)
.type(event.getType())
.title(event.getTitle())
.message(event.getMessage())
.linkUrl(event.getLinkUrl())
.isRead('N')
.build();
notificationRepository.save(notification);
try {
slackSender.send(event.getTitle(), event.getMessage());
} catch (Exception e) {
log.warn("Slack notification failed: {}", e.getMessage());
}
try {
emailSender.send(recipient.getEmail(), event.getTitle(), event.getMessage());
} catch (Exception e) {
log.warn("Email notification failed: {}", e.getMessage());
}
}
}
@@ -0,0 +1,17 @@
package com.srsystem.listener;
import com.srsystem.dto.NotificationEvent;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class NotificationPublisher {
private final ApplicationEventPublisher eventPublisher;
public void publish(NotificationEvent event) {
eventPublisher.publishEvent(event);
}
}
@@ -0,0 +1,12 @@
package com.srsystem.repository;
import com.srsystem.entity.CommonCode;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CommonCodeRepository extends JpaRepository<CommonCode, Long> {
List<CommonCode> findByGroupCodeAndIsActiveOrderBySortOrder(String groupCode, char isActive);
}
@@ -0,0 +1,13 @@
package com.srsystem.repository;
import com.srsystem.entity.DailyTaskProgress;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface DailyTaskProgressRepository extends JpaRepository<DailyTaskProgress, Long> {
List<DailyTaskProgress> findByReport_ReportId(Long reportId);
List<DailyTaskProgress> findByWeeklyPlan_PlanId(Long planId);
}
@@ -0,0 +1,26 @@
package com.srsystem.repository;
import com.srsystem.entity.DispatchHistory;
import com.srsystem.entity.Employee;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface DispatchHistoryRepository extends JpaRepository<DispatchHistory, Long> {
List<DispatchHistory> findByEmployee_EmpId(Long empId);
List<DispatchHistory> findByEndDateIsNull();
@Query("SELECT DISTINCT dh.employee FROM DispatchHistory dh WHERE dh.individualTech LIKE %:keyword%")
List<Employee> findEmployeesByIndividualTechContaining(@Param("keyword") String keyword);
Page<DispatchHistory> findByEmployee_EmpId(Long empId, Pageable pageable);
Page<DispatchHistory> findByProject_ProjectId(Long projectId, Pageable pageable);
Page<DispatchHistory> findByEmployee_EmpIdAndProject_ProjectId(Long empId, Long projectId, Pageable pageable);
}
@@ -0,0 +1,21 @@
package com.srsystem.repository;
import com.srsystem.entity.Employee;
import com.srsystem.entity.enums.EmpStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
@Query("SELECT e FROM Employee e WHERE e.loginId = :loginId")
Optional<Employee> findByLoginId(@Param("loginId") String loginId);
List<Employee> findByNameContainingIgnoreCase(String name);
Page<Employee> findByEmpStatus(EmpStatus status, Pageable pageable);
}
@@ -0,0 +1,14 @@
package com.srsystem.repository;
import com.srsystem.entity.EquipmentAssignment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface EquipmentAssignmentRepository extends JpaRepository<EquipmentAssignment, Long> {
List<EquipmentAssignment> findByEquipment_EquipmentId(Long equipmentId);
Optional<EquipmentAssignment> findByEquipment_EquipmentIdAndReturnedDateIsNull(Long equipmentId);
}

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