init: SR System — Helpdesk & ITAM Portal
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.config.jwt.JwtTokenProvider;
|
||||
import com.srsystem.dto.EmployeeSummary;
|
||||
import com.srsystem.dto.LoginRequest;
|
||||
import com.srsystem.dto.LoginResponse;
|
||||
import com.srsystem.entity.Employee;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AuthService {
|
||||
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Transactional
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
Employee employee = employeeRepository.findByLoginId(request.loginId())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.INVALID_CREDENTIALS));
|
||||
|
||||
if (!passwordEncoder.matches(request.password(), employee.getPassword())) {
|
||||
throw new BusinessException(ErrorCode.INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
String accessToken = jwtTokenProvider.generateAccessToken(employee.getLoginId(), employee.getRoleCode().name());
|
||||
String refreshToken = jwtTokenProvider.generateRefreshToken(employee.getLoginId());
|
||||
|
||||
EmployeeSummary summary = new EmployeeSummary(
|
||||
employee.getEmpId(),
|
||||
employee.getName(),
|
||||
employee.getEmail(),
|
||||
employee.getPhone(),
|
||||
employee.getRoleCode(),
|
||||
employee.getEmpStatus(),
|
||||
employee.getDispatchStatus(),
|
||||
employee.getJoinedDate(),
|
||||
employee.getLeftDate()
|
||||
);
|
||||
|
||||
return new LoginResponse(accessToken, refreshToken, summary);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LoginResponse refresh(String refreshToken) {
|
||||
if (!jwtTokenProvider.validateToken(refreshToken)) {
|
||||
throw new BusinessException(ErrorCode.INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
String loginId = jwtTokenProvider.getLoginId(refreshToken);
|
||||
Employee employee = employeeRepository.findByLoginId(loginId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
String newAccessToken = jwtTokenProvider.generateAccessToken(employee.getLoginId(), employee.getRoleCode().name());
|
||||
String newRefreshToken = jwtTokenProvider.generateRefreshToken(employee.getLoginId());
|
||||
|
||||
EmployeeSummary summary = new EmployeeSummary(
|
||||
employee.getEmpId(),
|
||||
employee.getName(),
|
||||
employee.getEmail(),
|
||||
employee.getPhone(),
|
||||
employee.getRoleCode(),
|
||||
employee.getEmpStatus(),
|
||||
employee.getDispatchStatus(),
|
||||
employee.getJoinedDate(),
|
||||
employee.getLeftDate()
|
||||
);
|
||||
|
||||
return new LoginResponse(newAccessToken, newRefreshToken, summary);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Employee getEmployeeByLoginId(String loginId) {
|
||||
return employeeRepository.findByLoginId(loginId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.CalendarEvent;
|
||||
import com.srsystem.entity.DispatchHistory;
|
||||
import com.srsystem.entity.Report;
|
||||
import com.srsystem.entity.Ticket;
|
||||
import com.srsystem.entity.enums.RoleCode;
|
||||
import com.srsystem.repository.DispatchHistoryRepository;
|
||||
import com.srsystem.repository.ReportRepository;
|
||||
import com.srsystem.repository.TicketRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class CalendarService {
|
||||
|
||||
private final TicketRepository ticketRepository;
|
||||
private final ReportRepository reportRepository;
|
||||
private final DispatchHistoryRepository dispatchHistoryRepository;
|
||||
|
||||
public List<CalendarEvent> getEvents(Long userId, RoleCode role, LocalDate start, LocalDate end) {
|
||||
List<CalendarEvent> events = new ArrayList<>();
|
||||
|
||||
LocalDateTime startDT = start.atStartOfDay();
|
||||
LocalDateTime endDT = end.atTime(23, 59, 59);
|
||||
|
||||
// Tickets with due dates in range
|
||||
List<Ticket> tickets;
|
||||
if (role == RoleCode.EMPLOYEE) {
|
||||
tickets = ticketRepository.findByRequester_EmpIdAndDueDateBetween(userId, startDT, endDT);
|
||||
} else {
|
||||
tickets = ticketRepository.findByDueDateBetween(startDT, endDT);
|
||||
}
|
||||
for (Ticket t : tickets) {
|
||||
events.add(new CalendarEvent(
|
||||
"ticket-" + t.getTicketId(),
|
||||
t.getTitle(),
|
||||
t.getDueDate() != null ? t.getDueDate().toLocalDate().toString() : "",
|
||||
"ticket",
|
||||
t.getPriority() != null ? t.getPriority().name() : "NORMAL",
|
||||
t.getStatus() != null ? t.getStatus().name() : "OPEN"
|
||||
));
|
||||
}
|
||||
|
||||
// Reports with target dates in range
|
||||
List<Report> reports;
|
||||
if (role == RoleCode.EMPLOYEE) {
|
||||
reports = reportRepository.findByUser_EmpIdAndTargetDateBetween(userId, start, end);
|
||||
} else {
|
||||
reports = reportRepository.findByTargetDateBetween(start, end);
|
||||
}
|
||||
for (Report r : reports) {
|
||||
events.add(new CalendarEvent(
|
||||
"report-" + r.getReportId(),
|
||||
r.getReportType() + " Report",
|
||||
r.getTargetDate().toString(),
|
||||
"report",
|
||||
r.getStatus().name(),
|
||||
""
|
||||
));
|
||||
}
|
||||
|
||||
// Dispatch history for this user
|
||||
List<DispatchHistory> dispatches = dispatchHistoryRepository.findByEmployee_EmpId(userId);
|
||||
for (DispatchHistory d : dispatches) {
|
||||
if (d.getStartDate() != null && !d.getStartDate().isAfter(end) && (d.getEndDate() == null || !d.getEndDate().isBefore(start))) {
|
||||
events.add(new CalendarEvent(
|
||||
"dispatch-" + d.getDispatchId(),
|
||||
d.getProject().getProjectName() + " (" + d.getRoleTitle() + ")",
|
||||
d.getStartDate().toString(),
|
||||
"dispatch",
|
||||
d.getEndDate() != null ? "ended" : "active",
|
||||
d.getEndDate() != null ? d.getEndDate().toString() : ""
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.CommonCodeRequest;
|
||||
import com.srsystem.entity.CommonCode;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.CommonCodeRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class CommonCodeService {
|
||||
|
||||
private final CommonCodeRepository commonCodeRepository;
|
||||
|
||||
public List<CommonCode> getCodes(String groupCode) {
|
||||
if (groupCode != null) {
|
||||
return commonCodeRepository.findByGroupCodeAndIsActiveOrderBySortOrder(groupCode, 'Y');
|
||||
}
|
||||
return commonCodeRepository.findAll().stream()
|
||||
.filter(c -> c.getIsActive() == 'Y')
|
||||
.sorted(java.util.Comparator.comparing(CommonCode::getGroupCode)
|
||||
.thenComparing(CommonCode::getSortOrder))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
public List<String> getGroupCodes() {
|
||||
return commonCodeRepository.findAll().stream()
|
||||
.filter(c -> c.getIsActive() == 'Y')
|
||||
.map(CommonCode::getGroupCode)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CommonCode createCode(CommonCodeRequest request) {
|
||||
CommonCode code = CommonCode.builder()
|
||||
.groupCode(request.groupCode())
|
||||
.codeValue(request.codeValue())
|
||||
.codeName(request.codeName())
|
||||
.optionVal1(request.optionVal1())
|
||||
.optionVal2(request.optionVal2())
|
||||
.sortOrder(request.sortOrder())
|
||||
.isActive(request.isActive())
|
||||
.build();
|
||||
return commonCodeRepository.save(code);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CommonCode updateCode(Long id, CommonCodeRequest request) {
|
||||
CommonCode code = commonCodeRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.CODE_NOT_FOUND));
|
||||
code.setGroupCode(request.groupCode());
|
||||
code.setCodeValue(request.codeValue());
|
||||
code.setCodeName(request.codeName());
|
||||
code.setOptionVal1(request.optionVal1());
|
||||
code.setOptionVal2(request.optionVal2());
|
||||
code.setSortOrder(request.sortOrder());
|
||||
code.setIsActive(request.isActive());
|
||||
return commonCodeRepository.save(code);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteCode(Long id) {
|
||||
CommonCode code = commonCodeRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.CODE_NOT_FOUND));
|
||||
code.setIsDeleted('Y');
|
||||
commonCodeRepository.save(code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.CreateDispatchRequest;
|
||||
import com.srsystem.entity.DispatchHistory;
|
||||
import com.srsystem.entity.Employee;
|
||||
import com.srsystem.entity.Project;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.DispatchHistoryRepository;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import com.srsystem.repository.ProjectRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class DispatchHistoryService {
|
||||
|
||||
private final DispatchHistoryRepository dispatchHistoryRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final ProjectRepository projectRepository;
|
||||
|
||||
public List<DispatchHistory> findByEmployeeId(Long empId) {
|
||||
employeeRepository.findById(empId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
return dispatchHistoryRepository.findByEmployee_EmpId(empId);
|
||||
}
|
||||
|
||||
public List<DispatchHistory> getActiveDispatches() {
|
||||
return dispatchHistoryRepository.findByEndDateIsNull();
|
||||
}
|
||||
|
||||
public Page<DispatchHistory> findAll(Pageable pageable, Long empId, Long projectId) {
|
||||
if (empId != null && projectId != null) {
|
||||
return dispatchHistoryRepository.findByEmployee_EmpIdAndProject_ProjectId(empId, projectId, pageable);
|
||||
}
|
||||
if (empId != null) {
|
||||
return dispatchHistoryRepository.findByEmployee_EmpId(empId, pageable);
|
||||
}
|
||||
if (projectId != null) {
|
||||
return dispatchHistoryRepository.findByProject_ProjectId(projectId, pageable);
|
||||
}
|
||||
return dispatchHistoryRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
public DispatchHistory findById(Long dispatchId) {
|
||||
return dispatchHistoryRepository.findById(dispatchId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.DISPATCH_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long dispatchId) {
|
||||
DispatchHistory dh = findById(dispatchId);
|
||||
dispatchHistoryRepository.delete(dh);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DispatchHistory createDispatch(CreateDispatchRequest request) {
|
||||
Employee employee = employeeRepository.findById(request.employeeId())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
Project project = projectRepository.findById(request.projectId())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_NOT_FOUND));
|
||||
|
||||
DispatchHistory history = DispatchHistory.builder()
|
||||
.employee(employee)
|
||||
.project(project)
|
||||
.roleTitle(request.roleTitle())
|
||||
.contributionRate(request.contributionRate())
|
||||
.individualTech(request.individualTech())
|
||||
.performedTasks(request.performedTasks())
|
||||
.startDate(request.startDate() != null ? request.startDate() : LocalDate.now())
|
||||
.build();
|
||||
|
||||
return dispatchHistoryRepository.save(history);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DispatchHistory endDispatch(Long dispatchId) {
|
||||
DispatchHistory history = dispatchHistoryRepository.findById(dispatchId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.DISPATCH_NOT_FOUND));
|
||||
history.setEndDate(LocalDate.now());
|
||||
return dispatchHistoryRepository.save(history);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.ChangePasswordRequest;
|
||||
import com.srsystem.dto.EmployeeSearchResult;
|
||||
import com.srsystem.dto.EmployeeSummary;
|
||||
import com.srsystem.entity.Employee;
|
||||
import com.srsystem.entity.enums.EmpStatus;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.DispatchHistoryRepository;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class EmployeeService {
|
||||
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final DispatchHistoryRepository dispatchHistoryRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public Page<Employee> findAll(EmpStatus status, Pageable pageable) {
|
||||
if (status != null) {
|
||||
return employeeRepository.findByEmpStatus(status, pageable);
|
||||
}
|
||||
return employeeRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
public Employee findById(Long empId) {
|
||||
return employeeRepository.findById(empId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
}
|
||||
|
||||
public Employee findByLoginId(String loginId) {
|
||||
return employeeRepository.findByLoginId(loginId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
}
|
||||
|
||||
public List<EmployeeSearchResult> searchByName(String keyword) {
|
||||
List<Employee> byName = employeeRepository.findByNameContainingIgnoreCase(keyword);
|
||||
List<EmployeeSearchResult> results = new ArrayList<>();
|
||||
Set<Long> seen = new HashSet<>();
|
||||
for (Employee e : byName) {
|
||||
if (seen.add(e.getEmpId())) {
|
||||
results.add(toSearchResult(e));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private EmployeeSearchResult toSearchResult(Employee e) {
|
||||
return new EmployeeSearchResult(
|
||||
e.getEmpId(),
|
||||
e.getName(),
|
||||
e.getEmail(),
|
||||
e.getDispatchStatus() != null ? e.getDispatchStatus().name() : null
|
||||
);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Employee create(Employee employee) {
|
||||
if (employeeRepository.findByLoginId(employee.getLoginId()).isPresent()) {
|
||||
throw new BusinessException(ErrorCode.DUPLICATE_LOGIN_ID);
|
||||
}
|
||||
|
||||
employee.setPassword(passwordEncoder.encode(employee.getPassword()));
|
||||
return employeeRepository.save(employee);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Employee update(Long empId, Employee updateRequest) {
|
||||
Employee employee = findById(empId);
|
||||
|
||||
if (employee.getEmpStatus() == EmpStatus.RESIGNED) {
|
||||
throw new BusinessException(ErrorCode.RESIGNED_EMPLOYEE);
|
||||
}
|
||||
|
||||
if (!employee.getLoginId().equals(updateRequest.getLoginId())) {
|
||||
if (employeeRepository.findByLoginId(updateRequest.getLoginId()).isPresent()) {
|
||||
throw new BusinessException(ErrorCode.DUPLICATE_LOGIN_ID);
|
||||
}
|
||||
employee.setLoginId(updateRequest.getLoginId());
|
||||
}
|
||||
|
||||
employee.setName(updateRequest.getName());
|
||||
employee.setEmail(updateRequest.getEmail());
|
||||
employee.setPhone(updateRequest.getPhone());
|
||||
employee.setRoleCode(updateRequest.getRoleCode());
|
||||
employee.setEmpStatus(updateRequest.getEmpStatus());
|
||||
employee.setDispatchStatus(updateRequest.getDispatchStatus());
|
||||
employee.setJoinedDate(updateRequest.getJoinedDate());
|
||||
employee.setLeftDate(updateRequest.getLeftDate());
|
||||
|
||||
if (updateRequest.getPassword() != null && !updateRequest.getPassword().isBlank()) {
|
||||
employee.setPassword(passwordEncoder.encode(updateRequest.getPassword()));
|
||||
}
|
||||
|
||||
return employee;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long empId) {
|
||||
Employee employee = findById(empId);
|
||||
|
||||
if (employee.getEmpStatus() == EmpStatus.RESIGNED) {
|
||||
throw new BusinessException(ErrorCode.RESIGNED_EMPLOYEE);
|
||||
}
|
||||
|
||||
employeeRepository.delete(employee);
|
||||
}
|
||||
|
||||
public EmployeeSummary toSummary(Employee employee) {
|
||||
return new EmployeeSummary(
|
||||
employee.getEmpId(),
|
||||
employee.getName(),
|
||||
employee.getEmail(),
|
||||
employee.getPhone(),
|
||||
employee.getRoleCode(),
|
||||
employee.getEmpStatus(),
|
||||
employee.getDispatchStatus(),
|
||||
employee.getJoinedDate(),
|
||||
employee.getLeftDate()
|
||||
);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void changePassword(Long empId, ChangePasswordRequest request) {
|
||||
Employee employee = findById(empId);
|
||||
if (!passwordEncoder.matches(request.currentPassword(), employee.getPassword())) {
|
||||
throw new BusinessException(ErrorCode.AUTH_INVALID_CREDENTIALS);
|
||||
}
|
||||
employee.setPassword(passwordEncoder.encode(request.newPassword()));
|
||||
employeeRepository.save(employee);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.CreateEquipmentRequest;
|
||||
import com.srsystem.dto.CreateMaintenanceRequest;
|
||||
import com.srsystem.dto.EquipmentSummary;
|
||||
import com.srsystem.dto.UpdateEquipmentRequest;
|
||||
import com.srsystem.entity.Employee;
|
||||
import com.srsystem.entity.Equipment;
|
||||
import com.srsystem.entity.EquipmentAssignment;
|
||||
import com.srsystem.entity.EquipmentMaintenance;
|
||||
import com.srsystem.entity.enums.EmpStatus;
|
||||
import com.srsystem.entity.enums.EquipmentStatus;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import com.srsystem.repository.EquipmentAssignmentRepository;
|
||||
import com.srsystem.repository.EquipmentMaintenanceRepository;
|
||||
import com.srsystem.repository.EquipmentRepository;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.persistence.OptimisticLockException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class EquipmentService {
|
||||
|
||||
private final EquipmentRepository equipmentRepository;
|
||||
private final EquipmentAssignmentRepository equipmentAssignmentRepository;
|
||||
private final EquipmentMaintenanceRepository equipmentMaintenanceRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
|
||||
@Value("${file.upload-dir:./uploads}")
|
||||
private String uploadDir;
|
||||
|
||||
@PostConstruct
|
||||
public void initUploadDir() {
|
||||
File dir = new File(uploadDir);
|
||||
if (!dir.exists()) dir.mkdirs();
|
||||
}
|
||||
|
||||
public Page<Equipment> getEquipment(String type, EquipmentStatus status, String keyword, Pageable pageable) {
|
||||
if (status != null) {
|
||||
return equipmentRepository.findByStatus(status, pageable);
|
||||
}
|
||||
return equipmentRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
public Equipment getEquipment(Long equipId) {
|
||||
return equipmentRepository.findById(equipId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EQUIPMENT_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Equipment createEquipment(CreateEquipmentRequest request) {
|
||||
Equipment equipment = Equipment.builder()
|
||||
.equipmentType(request.equipmentType())
|
||||
.manufacturer(request.manufacturer())
|
||||
.modelName(request.modelName())
|
||||
.serialNumber(request.serialNumber())
|
||||
.assetCode(request.assetCode())
|
||||
.specifications(request.specifications())
|
||||
.status(request.status() != null ? request.status() : EquipmentStatus.AVAILABLE)
|
||||
.purchaseDate(request.purchaseDate())
|
||||
.warrantyExpDate(request.warrantyExpDate())
|
||||
.build();
|
||||
|
||||
return equipmentRepository.save(equipment);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Equipment updateEquipment(Long equipId, UpdateEquipmentRequest request) {
|
||||
Equipment equipment = getEquipment(equipId);
|
||||
|
||||
if (request.equipmentType() != null) {
|
||||
equipment.setEquipmentType(request.equipmentType());
|
||||
}
|
||||
if (request.manufacturer() != null) {
|
||||
equipment.setManufacturer(request.manufacturer());
|
||||
}
|
||||
if (request.modelName() != null) {
|
||||
equipment.setModelName(request.modelName());
|
||||
}
|
||||
if (request.serialNumber() != null) {
|
||||
equipment.setSerialNumber(request.serialNumber());
|
||||
}
|
||||
if (request.assetCode() != null) {
|
||||
equipment.setAssetCode(request.assetCode());
|
||||
}
|
||||
if (request.specifications() != null) {
|
||||
equipment.setSpecifications(request.specifications());
|
||||
}
|
||||
if (request.status() != null) {
|
||||
equipment.setStatus(request.status());
|
||||
}
|
||||
if (request.purchaseDate() != null) {
|
||||
equipment.setPurchaseDate(request.purchaseDate());
|
||||
}
|
||||
if (request.warrantyExpDate() != null) {
|
||||
equipment.setWarrantyExpDate(request.warrantyExpDate());
|
||||
}
|
||||
|
||||
return equipment;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteEquipment(Long equipId) {
|
||||
Equipment equipment = getEquipment(equipId);
|
||||
equipmentRepository.delete(equipment);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public EquipmentAssignment assignEquipment(Long equipId, Long userId, String condition) {
|
||||
Equipment equipment = equipmentRepository.findById(equipId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EQUIPMENT_NOT_FOUND));
|
||||
|
||||
if (equipment.getStatus() != EquipmentStatus.AVAILABLE) {
|
||||
throw new BusinessException(ErrorCode.EQUIP_NOT_AVAILABLE);
|
||||
}
|
||||
|
||||
Employee user = employeeRepository.findById(userId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
if (user.getEmpStatus() != EmpStatus.ACTIVE) {
|
||||
throw new BusinessException(ErrorCode.INVALID_ASSIGNEE);
|
||||
}
|
||||
|
||||
try {
|
||||
equipment.setStatus(EquipmentStatus.IN_USE);
|
||||
equipmentRepository.save(equipment);
|
||||
|
||||
EquipmentAssignment assignment = EquipmentAssignment.builder()
|
||||
.equipment(equipment)
|
||||
.user(user)
|
||||
.assignedDate(LocalDateTime.now())
|
||||
.condOnAssign(condition)
|
||||
.build();
|
||||
|
||||
return equipmentAssignmentRepository.save(assignment);
|
||||
} catch (OptimisticLockException e) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_CONCURRENCY);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public EquipmentAssignment returnEquipment(Long equipId, String condition) {
|
||||
Equipment equipment = equipmentRepository.findById(equipId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EQUIPMENT_NOT_FOUND));
|
||||
|
||||
EquipmentAssignment activeAssignment = equipmentAssignmentRepository
|
||||
.findByEquipment_EquipmentIdAndReturnedDateIsNull(equipId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EQUIPMENT_NOT_FOUND));
|
||||
|
||||
activeAssignment.setReturnedDate(LocalDateTime.now());
|
||||
activeAssignment.setCondOnReturn(condition);
|
||||
|
||||
equipment.setStatus(EquipmentStatus.AVAILABLE);
|
||||
equipmentRepository.save(equipment);
|
||||
|
||||
return equipmentAssignmentRepository.save(activeAssignment);
|
||||
}
|
||||
|
||||
public List<EquipmentAssignment> getAssignments(Long equipId) {
|
||||
return equipmentAssignmentRepository.findByEquipment_EquipmentId(equipId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public EquipmentMaintenance addMaintenance(Long equipId, CreateMaintenanceRequest request) {
|
||||
Equipment equipment = getEquipment(equipId);
|
||||
|
||||
Employee reportedBy = employeeRepository.findByLoginId(request.reportedBy())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
EquipmentMaintenance maintenance = EquipmentMaintenance.builder()
|
||||
.equipment(equipment)
|
||||
.reportedBy(reportedBy)
|
||||
.maintenanceType(request.maintenanceType())
|
||||
.issueDescription(request.issueDescription())
|
||||
.actionTaken(request.actionTaken())
|
||||
.cost(request.cost())
|
||||
.reportedDate(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
return equipmentMaintenanceRepository.save(maintenance);
|
||||
}
|
||||
|
||||
public List<EquipmentMaintenance> getMaintenanceHistory(Long equipId) {
|
||||
return equipmentMaintenanceRepository.findByEquipment_EquipmentId(equipId);
|
||||
}
|
||||
|
||||
public Map<String, Long> getSummary() {
|
||||
List<Equipment> all = equipmentRepository.findAll();
|
||||
Map<String, Long> counts = new HashMap<>();
|
||||
|
||||
for (EquipmentStatus status : EquipmentStatus.values()) {
|
||||
long count = all.stream()
|
||||
.filter(e -> e.getStatus() == status)
|
||||
.count();
|
||||
counts.put(status.name(), count);
|
||||
}
|
||||
|
||||
return counts;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Equipment addImage(Long equipmentId, MultipartFile file) {
|
||||
Equipment eq = equipmentRepository.findById(equipmentId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EQUIPMENT_NOT_FOUND));
|
||||
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;
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
List<Map<String, String>> images = new ArrayList<>();
|
||||
if (eq.getImages() != null && !eq.getImages().isEmpty() && !eq.getImages().equals("[]")) {
|
||||
images = mapper.readValue(eq.getImages(), new TypeReference<List<Map<String, String>>>() {});
|
||||
}
|
||||
Map<String, String> newImage = new HashMap<>();
|
||||
newImage.put("url", url);
|
||||
newImage.put("name", originalName != null ? originalName : "unknown");
|
||||
newImage.put("uploadedAt", LocalDateTime.now().toString());
|
||||
images.add(newImage);
|
||||
eq.setImages(mapper.writeValueAsString(images));
|
||||
return equipmentRepository.save(eq);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to upload image", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Equipment removeImage(Long equipmentId, int imageIndex) {
|
||||
Equipment eq = equipmentRepository.findById(equipmentId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EQUIPMENT_NOT_FOUND));
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
List<Map<String, String>> images = new ArrayList<>();
|
||||
if (eq.getImages() != null && !eq.getImages().isEmpty()) {
|
||||
images = mapper.readValue(eq.getImages(), new TypeReference<List<Map<String, String>>>() {});
|
||||
}
|
||||
if (imageIndex >= 0 && imageIndex < images.size()) {
|
||||
images.remove(imageIndex);
|
||||
}
|
||||
eq.setImages(mapper.writeValueAsString(images));
|
||||
return equipmentRepository.save(eq);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Failed to parse images", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class NotificationService {
|
||||
|
||||
private final NotificationRepository notificationRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
|
||||
public List<Notification> getMyNotifications(Long userId) {
|
||||
return notificationRepository.findByUser_EmpIdOrderByCreatedAtDesc(userId);
|
||||
}
|
||||
|
||||
public long getUnreadCount(Long userId) {
|
||||
return notificationRepository.countByUser_EmpIdAndIsRead(userId, 'N');
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void markAsRead(Long notificationId) {
|
||||
Notification notification = notificationRepository.findById(notificationId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.REPORT_NOT_FOUND));
|
||||
notification.setIsRead('Y');
|
||||
notificationRepository.save(notification);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Notification createNotification(Long userId, String type, String title, String message, String linkUrl) {
|
||||
Employee user = employeeRepository.findById(userId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
Notification notification = Notification.builder()
|
||||
.user(user)
|
||||
.type(type)
|
||||
.title(title)
|
||||
.message(message)
|
||||
.linkUrl(linkUrl)
|
||||
.isRead('N')
|
||||
.build();
|
||||
return notificationRepository.save(notification);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.HealthResponse;
|
||||
import com.srsystem.dto.WakeRequest;
|
||||
import com.srsystem.dto.WakeResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Service
|
||||
public class ParkingService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ParkingService.class);
|
||||
|
||||
@Value("${parking.enabled:true}")
|
||||
private boolean parkingEnabled;
|
||||
|
||||
@Value("${parking.schedule.weekday-start:20:00}")
|
||||
private String weekdayStart;
|
||||
|
||||
@Value("${parking.schedule.weekday-end:08:00}")
|
||||
private String weekdayEnd;
|
||||
|
||||
@Value("${parking.schedule.weekends:true}")
|
||||
private boolean parkingOnWeekends;
|
||||
|
||||
public HealthResponse checkStatus() {
|
||||
if (!parkingEnabled) {
|
||||
return new HealthResponse(false, "Server is running (parking disabled)", null, "Parking disabled");
|
||||
}
|
||||
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalTime now = LocalTime.now();
|
||||
DayOfWeek dayOfWeek = today.getDayOfWeek();
|
||||
|
||||
boolean isWeekend = dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
|
||||
LocalTime startTime = LocalTime.parse(weekdayStart, DateTimeFormatter.ofPattern("HH:mm"));
|
||||
LocalTime endTime = LocalTime.parse(weekdayEnd, DateTimeFormatter.ofPattern("HH:mm"));
|
||||
|
||||
boolean isParkingTime;
|
||||
if (isWeekend) {
|
||||
isParkingTime = parkingOnWeekends;
|
||||
} else {
|
||||
if (startTime.isBefore(endTime)) {
|
||||
isParkingTime = !now.isBefore(startTime) && now.isBefore(endTime);
|
||||
} else {
|
||||
isParkingTime = !now.isBefore(startTime) || now.isBefore(endTime);
|
||||
}
|
||||
}
|
||||
|
||||
String message = isParkingTime ? "Server is currently parked" : "Server is running";
|
||||
String resumeTime = isParkingTime ? calculateResumeTime(today, now) : null;
|
||||
String schedule = String.format("Weekdays: %s-%s, Weekends: %s", weekdayStart, weekdayEnd, parkingOnWeekends ? "Parked" : "Running");
|
||||
|
||||
return new HealthResponse(isParkingTime, message, resumeTime, schedule);
|
||||
}
|
||||
|
||||
private String calculateResumeTime(LocalDate today, LocalTime now) {
|
||||
DayOfWeek dayOfWeek = today.getDayOfWeek();
|
||||
LocalTime endTime = LocalTime.parse(weekdayEnd, DateTimeFormatter.ofPattern("HH:mm"));
|
||||
|
||||
if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {
|
||||
if (!parkingOnWeekends) {
|
||||
return today.atTime(endTime).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
|
||||
}
|
||||
LocalDate nextWeekday = today.with(DayOfWeek.MONDAY);
|
||||
return nextWeekday.atTime(endTime).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
|
||||
} else {
|
||||
return today.atTime(endTime).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
|
||||
}
|
||||
}
|
||||
|
||||
public WakeResponse requestWake(WakeRequest request) {
|
||||
log.info("Wake request received - requestedBy: {}, reason: {}, requestedAt: {}",
|
||||
request.requestedBy(), request.reason(), request.requestedAt());
|
||||
|
||||
String estimatedTime = LocalTime.now().plusMinutes(5)
|
||||
.format(DateTimeFormatter.ofPattern("HH:mm"));
|
||||
|
||||
return new WakeResponse(
|
||||
true,
|
||||
"iLO4 remote boot initiated. Server will be available shortly.",
|
||||
estimatedTime
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.PortfolioResponse;
|
||||
import com.srsystem.entity.*;
|
||||
import com.srsystem.entity.enums.TicketStatus;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class PortfolioService {
|
||||
|
||||
private final DispatchHistoryRepository dispatchHistoryRepository;
|
||||
private final ReportRepository reportRepository;
|
||||
private final TicketRepository ticketRepository;
|
||||
|
||||
public List<PortfolioResponse> getPortfolio(Long empId) {
|
||||
List<DispatchHistory> histories = dispatchHistoryRepository.findByEmployee_EmpId(empId);
|
||||
|
||||
return histories.stream().map(dh -> {
|
||||
PortfolioResponse resp = new PortfolioResponse();
|
||||
resp.setDispatchId(dh.getDispatchId());
|
||||
resp.setProjectName(dh.getProject().getProjectName());
|
||||
resp.setRoleTitle(dh.getRoleTitle());
|
||||
resp.setContributionRate(dh.getContributionRate());
|
||||
resp.setIndividualTech(dh.getIndividualTech());
|
||||
resp.setPerformedTasks(dh.getPerformedTasks());
|
||||
resp.setAchievements(dh.getAchievements());
|
||||
resp.setStartDate(dh.getStartDate());
|
||||
resp.setEndDate(dh.getEndDate());
|
||||
resp.setInternalRating(dh.getInternalRating());
|
||||
|
||||
// Bind reports for this dispatch
|
||||
List<PortfolioResponse.BoundReport> reports = reportRepository
|
||||
.findByDispatch_DispatchId(dh.getDispatchId())
|
||||
.stream().map(r -> {
|
||||
PortfolioResponse.BoundReport br = new PortfolioResponse.BoundReport();
|
||||
br.targetDate = r.getTargetDate() != null ? r.getTargetDate().toString() : "";
|
||||
br.reportType = r.getReportType().name();
|
||||
br.completedTasks = r.getCompletedTasks();
|
||||
return br;
|
||||
}).collect(Collectors.toList());
|
||||
resp.setReports(reports);
|
||||
|
||||
// Bind resolved + closed tickets for this project, by this employee
|
||||
List<PortfolioResponse.BoundTicket> tickets = new ArrayList<>();
|
||||
|
||||
ticketRepository
|
||||
.findByProject_ProjectIdAndRequester_EmpIdAndStatus(
|
||||
dh.getProject().getProjectId(), empId, TicketStatus.RESOLVED)
|
||||
.forEach(t -> {
|
||||
PortfolioResponse.BoundTicket bt = new PortfolioResponse.BoundTicket();
|
||||
bt.ticketId = t.getTicketId();
|
||||
bt.title = t.getTitle();
|
||||
bt.category = t.getCategory().name();
|
||||
bt.resolutionNote = t.getResolutionNote();
|
||||
tickets.add(bt);
|
||||
});
|
||||
|
||||
ticketRepository
|
||||
.findByProject_ProjectIdAndRequester_EmpIdAndStatus(
|
||||
dh.getProject().getProjectId(), empId, TicketStatus.CLOSED)
|
||||
.forEach(t -> {
|
||||
PortfolioResponse.BoundTicket bt = new PortfolioResponse.BoundTicket();
|
||||
bt.ticketId = t.getTicketId();
|
||||
bt.title = t.getTitle();
|
||||
bt.category = t.getCategory().name();
|
||||
bt.resolutionNote = t.getResolutionNote();
|
||||
tickets.add(bt);
|
||||
});
|
||||
|
||||
resp.setResolvedTickets(tickets);
|
||||
|
||||
return resp;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateAchievements(Long dispatchId, String achievements) {
|
||||
DispatchHistory dh = dispatchHistoryRepository.findById(dispatchId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.DISPATCH_NOT_FOUND));
|
||||
dh.setAchievements(achievements);
|
||||
dispatchHistoryRepository.save(dh);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.CreateProjectRequest;
|
||||
import com.srsystem.entity.DispatchHistory;
|
||||
import com.srsystem.entity.Employee;
|
||||
import com.srsystem.entity.Project;
|
||||
import com.srsystem.entity.enums.ProjectStatus;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.DispatchHistoryRepository;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import com.srsystem.repository.ProjectRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class ProjectService {
|
||||
|
||||
private final ProjectRepository projectRepository;
|
||||
private final DispatchHistoryRepository dispatchHistoryRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
|
||||
public List<Project> getMyProjects(Long empId) {
|
||||
List<DispatchHistory> histories = dispatchHistoryRepository.findByEmployee_EmpId(empId);
|
||||
Set<Long> projectIds = histories.stream()
|
||||
.map(h -> h.getProject().getProjectId())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return projectRepository.findAllById(projectIds);
|
||||
}
|
||||
|
||||
public Project getProject(Long projectId) {
|
||||
return projectRepository.findById(projectId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Project> findAll(ProjectStatus status) {
|
||||
if (status != null) {
|
||||
return projectRepository.findByStatus(status);
|
||||
}
|
||||
return projectRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Project create(CreateProjectRequest req) {
|
||||
Employee internalManager = null;
|
||||
if (req.internalManagerId() != null) {
|
||||
internalManager = employeeRepository.findById(req.internalManagerId())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
}
|
||||
|
||||
Project project = Project.builder()
|
||||
.projectName(req.projectName())
|
||||
.projectType(req.projectType())
|
||||
.contractCompany(req.contractCompany())
|
||||
.endUserCompany(req.endUserCompany())
|
||||
.contractType(req.contractType())
|
||||
.status(ProjectStatus.PREPARING)
|
||||
.startDate(req.startDate())
|
||||
.endDate(req.endDate())
|
||||
.workLocationType(req.workLocationType())
|
||||
.workAddress(req.workAddress())
|
||||
.techEnvironment(req.techEnvironment())
|
||||
.mainTaskDesc(req.mainTaskDesc())
|
||||
.internalManager(internalManager)
|
||||
.build();
|
||||
|
||||
return projectRepository.save(project);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Project update(Long projectId, CreateProjectRequest req) {
|
||||
Project project = projectRepository.findById(projectId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_NOT_FOUND));
|
||||
|
||||
Employee internalManager = null;
|
||||
if (req.internalManagerId() != null) {
|
||||
internalManager = employeeRepository.findById(req.internalManagerId())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
}
|
||||
|
||||
project.setProjectName(req.projectName());
|
||||
project.setProjectType(req.projectType());
|
||||
project.setContractCompany(req.contractCompany());
|
||||
project.setEndUserCompany(req.endUserCompany());
|
||||
project.setContractType(req.contractType());
|
||||
project.setStartDate(req.startDate());
|
||||
project.setEndDate(req.endDate());
|
||||
project.setWorkLocationType(req.workLocationType());
|
||||
project.setWorkAddress(req.workAddress());
|
||||
project.setTechEnvironment(req.techEnvironment());
|
||||
project.setMainTaskDesc(req.mainTaskDesc());
|
||||
project.setInternalManager(internalManager);
|
||||
|
||||
return projectRepository.save(project);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long projectId) {
|
||||
Project project = projectRepository.findById(projectId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_NOT_FOUND));
|
||||
projectRepository.delete(project);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
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.DispatchHistory;
|
||||
import com.srsystem.entity.Employee;
|
||||
import com.srsystem.entity.Notification;
|
||||
import com.srsystem.entity.Report;
|
||||
import com.srsystem.entity.Ticket;
|
||||
import com.srsystem.entity.WeeklyPlan;
|
||||
import com.srsystem.entity.enums.ReportStatus;
|
||||
import com.srsystem.entity.enums.ReportType;
|
||||
import com.srsystem.entity.enums.TicketStatus;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.DispatchHistoryRepository;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import com.srsystem.repository.NotificationRepository;
|
||||
import com.srsystem.repository.ReportRepository;
|
||||
import com.srsystem.repository.TicketRepository;
|
||||
import com.srsystem.repository.WeeklyPlanRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class ReportService {
|
||||
|
||||
private final ReportRepository reportRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final DispatchHistoryRepository dispatchHistoryRepository;
|
||||
private final TicketRepository ticketRepository;
|
||||
private final NotificationRepository notificationRepository;
|
||||
private final WeeklyPlanRepository weeklyPlanRepository;
|
||||
|
||||
public Page<WorkReportResponse> getReports(ReportStatus status, String type, LocalDate targetDate, Long userId, Pageable pageable) {
|
||||
Page<Report> reports;
|
||||
|
||||
if (userId != null) {
|
||||
reports = reportRepository.findByUser_EmpId(userId, pageable);
|
||||
} else if (status != null) {
|
||||
reports = reportRepository.findByStatus(status, pageable);
|
||||
} else {
|
||||
reports = reportRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
return reports.map(this::toWorkReportResponse);
|
||||
}
|
||||
|
||||
public Page<WorkReportResponse> findByUserId(Long userId, ReportStatus status, String type, LocalDate targetDate, Pageable pageable) {
|
||||
ReportType reportType = type != null ? ReportType.valueOf(type) : null;
|
||||
Page<Report> reports = reportRepository.findByUser_EmpIdAndStatusAndReportType(userId, status, reportType, targetDate, pageable);
|
||||
return reports.map(this::toWorkReportResponse);
|
||||
}
|
||||
|
||||
public WorkReportResponse getReport(Long reportId) {
|
||||
Report report = reportRepository.findById(reportId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.REPORT_NOT_FOUND));
|
||||
return toWorkReportResponse(report);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WorkReportResponse createReport(Long userId, CreateReportRequest request) {
|
||||
if (request.targetDate().isAfter(LocalDate.now())) {
|
||||
throw new BusinessException(ErrorCode.FUTURE_REPORT_DATE);
|
||||
}
|
||||
|
||||
Employee user = employeeRepository.findById(userId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
DispatchHistory dispatch = null;
|
||||
if (request.dispatchId() != null) {
|
||||
dispatch = dispatchHistoryRepository.findById(request.dispatchId())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_NOT_FOUND));
|
||||
}
|
||||
|
||||
Report report = Report.builder()
|
||||
.dispatch(dispatch)
|
||||
.user(user)
|
||||
.reportType(request.reportType())
|
||||
.targetDate(request.targetDate())
|
||||
.completedTasks(request.completedTasks())
|
||||
.plannedTasks(request.plannedTasks())
|
||||
.issuesAndRisks(request.issuesAndRisks())
|
||||
.supportRequested(request.supportRequested())
|
||||
.status(ReportStatus.DRAFT)
|
||||
.build();
|
||||
|
||||
if (request.workHours() != null) report.setWorkHours(request.workHours());
|
||||
if (request.blockerFlag() != null) report.setBlockerFlag(request.blockerFlag().charAt(0));
|
||||
if (request.artifacts() != null) report.setArtifacts(request.artifacts());
|
||||
if (request.weeklyPlanId() != null) {
|
||||
weeklyPlanRepository.findById(request.weeklyPlanId())
|
||||
.ifPresent(report::setWeeklyPlan);
|
||||
}
|
||||
|
||||
Report saved = reportRepository.save(report);
|
||||
return toWorkReportResponse(saved);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WorkReportResponse updateStatus(Long reportId, Long userId, ReportStatus newStatus) {
|
||||
Report report = reportRepository.findById(reportId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.REPORT_NOT_FOUND));
|
||||
|
||||
if (report.getStatus() == ReportStatus.APPROVED || report.getStatus() == ReportStatus.REJECTED) {
|
||||
throw new BusinessException(ErrorCode.LOCKED_REPORT);
|
||||
}
|
||||
|
||||
if (newStatus == ReportStatus.SUBMITTED) {
|
||||
report.setStatus(newStatus);
|
||||
}
|
||||
|
||||
Report updated = reportRepository.save(report);
|
||||
return toWorkReportResponse(updated);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WorkReportResponse reviewReport(Long reportId, Long reviewerId, ReviewReportRequest request) {
|
||||
Report report = reportRepository.findById(reportId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.REPORT_NOT_FOUND));
|
||||
|
||||
if (report.getStatus() == ReportStatus.APPROVED || report.getStatus() == ReportStatus.REJECTED) {
|
||||
throw new BusinessException(ErrorCode.LOCKED_REPORT);
|
||||
}
|
||||
|
||||
if (request.status() == ReportStatus.REJECTED &&
|
||||
(request.reviewComment() == null || request.reviewComment().isBlank())) {
|
||||
throw new BusinessException(ErrorCode.LOCKED_REPORT);
|
||||
}
|
||||
|
||||
Employee reviewer = employeeRepository.findById(reviewerId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
report.setStatus(request.status());
|
||||
report.setReviewer(reviewer);
|
||||
report.setReviewComment(request.reviewComment());
|
||||
report.setReviewedAt(LocalDateTime.now());
|
||||
|
||||
Report updated = reportRepository.save(report);
|
||||
|
||||
// Send notification if rejected
|
||||
if (request.status() == ReportStatus.REJECTED) {
|
||||
Notification notification = Notification.builder()
|
||||
.user(report.getUser())
|
||||
.type("REPORT_REJECTED")
|
||||
.title("Daily report rejected")
|
||||
.message(String.format("Your report for %s was rejected. Reason: %s", report.getTargetDate(), request.reviewComment()))
|
||||
.isRead('N')
|
||||
.build();
|
||||
notificationRepository.save(notification);
|
||||
}
|
||||
|
||||
return toWorkReportResponse(updated);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteReport(Long reportId) {
|
||||
Report report = reportRepository.findById(reportId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.REPORT_NOT_FOUND));
|
||||
|
||||
if (report.getStatus() == ReportStatus.APPROVED || report.getStatus() == ReportStatus.REJECTED) {
|
||||
throw new BusinessException(ErrorCode.LOCKED_REPORT);
|
||||
}
|
||||
|
||||
reportRepository.delete(report);
|
||||
}
|
||||
|
||||
private WorkReportResponse toWorkReportResponse(Report report) {
|
||||
String projectName = report.getDispatch() != null
|
||||
? report.getDispatch().getProject().getProjectName()
|
||||
: null;
|
||||
|
||||
String reviewerName = report.getReviewer() != null
|
||||
? report.getReviewer().getName()
|
||||
: null;
|
||||
|
||||
return new WorkReportResponse(
|
||||
report.getReportId(),
|
||||
report.getUser().getName(),
|
||||
projectName,
|
||||
report.getReportType(),
|
||||
report.getTargetDate(),
|
||||
report.getCompletedTasks(),
|
||||
report.getPlannedTasks(),
|
||||
report.getIssuesAndRisks(),
|
||||
report.getSupportRequested(),
|
||||
report.getStatus(),
|
||||
reviewerName,
|
||||
report.getReviewComment(),
|
||||
report.getReviewedAt(),
|
||||
report.getCreatedAt(),
|
||||
report.getWorkHours(),
|
||||
String.valueOf(report.getBlockerFlag()),
|
||||
report.getArtifacts()
|
||||
);
|
||||
}
|
||||
|
||||
public ReportSummary getSummary() {
|
||||
return new ReportSummary(
|
||||
reportRepository.countByStatus(ReportStatus.SUBMITTED),
|
||||
reportRepository.countByStatus(ReportStatus.APPROVED),
|
||||
reportRepository.countByStatus(ReportStatus.REJECTED),
|
||||
reportRepository.countByStatus(ReportStatus.DRAFT)
|
||||
);
|
||||
}
|
||||
|
||||
public List<TicketSummary> getResolvedTicketsForDate(Long userId, String date) {
|
||||
LocalDate targetDate = LocalDate.parse(date);
|
||||
LocalDateTime startOfDay = targetDate.atStartOfDay();
|
||||
LocalDateTime endOfDay = targetDate.atTime(LocalTime.MAX);
|
||||
|
||||
List<Ticket> resolvedTickets = ticketRepository
|
||||
.findByRequester_EmpIdAndStatusAndResolvedAtBetween(userId, TicketStatus.RESOLVED, startOfDay, endOfDay);
|
||||
List<Ticket> closedTickets = ticketRepository
|
||||
.findByRequester_EmpIdAndStatusAndResolvedAtBetween(userId, TicketStatus.CLOSED, startOfDay, endOfDay);
|
||||
|
||||
List<Ticket> all = new ArrayList<>();
|
||||
all.addAll(resolvedTickets);
|
||||
all.addAll(closedTickets);
|
||||
|
||||
return all.stream().map(t -> new TicketSummary(
|
||||
t.getTicketId(),
|
||||
t.getTitle(),
|
||||
t.getCategory().name(),
|
||||
t.getPriority().name(),
|
||||
t.getResolutionNote(),
|
||||
t.getResolvedAt() != null ? t.getResolvedAt().toString() : null
|
||||
)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
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.Project;
|
||||
import com.srsystem.entity.Ticket;
|
||||
import com.srsystem.entity.TicketComment;
|
||||
import com.srsystem.entity.enums.TicketStatus;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import com.srsystem.repository.ProjectRepository;
|
||||
import com.srsystem.repository.TicketCommentRepository;
|
||||
import com.srsystem.repository.TicketRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class TicketService {
|
||||
|
||||
private final TicketRepository ticketRepository;
|
||||
private final TicketCommentRepository ticketCommentRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final ProjectRepository projectRepository;
|
||||
|
||||
public Page<IssueTicket> getTickets(TicketStatus status, String category, String priority, String keyword, Pageable pageable, Long requesterId) {
|
||||
Page<Ticket> tickets;
|
||||
|
||||
if (requesterId != null) {
|
||||
tickets = ticketRepository.findByRequester_EmpId(requesterId, pageable);
|
||||
} else if (status != null) {
|
||||
tickets = ticketRepository.findByStatus(status, pageable);
|
||||
} else {
|
||||
tickets = ticketRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
return tickets.map(this::toIssueTicket);
|
||||
}
|
||||
|
||||
public IssueTicket getTicket(Long ticketId) {
|
||||
Ticket ticket = ticketRepository.findById(ticketId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.TICKET_NOT_FOUND));
|
||||
return toIssueTicket(ticket);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public IssueTicket createTicket(Long requesterId, CreateTicketRequest request) {
|
||||
Employee requester = employeeRepository.findById(requesterId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
Project project = projectRepository.findById(request.projectId())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_NOT_FOUND));
|
||||
|
||||
Ticket ticket = Ticket.builder()
|
||||
.project(project)
|
||||
.requester(requester)
|
||||
.category(request.category())
|
||||
.priority(request.priority())
|
||||
.title(request.title())
|
||||
.description(request.description())
|
||||
.status(TicketStatus.OPEN)
|
||||
.slaWarnFlag('N')
|
||||
.build();
|
||||
|
||||
Ticket saved = ticketRepository.save(ticket);
|
||||
return toIssueTicket(saved);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public IssueTicket updateStatus(Long ticketId, Long userId, TicketStatus newStatus) {
|
||||
Ticket ticket = ticketRepository.findById(ticketId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.TICKET_NOT_FOUND));
|
||||
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||
throw new BusinessException(ErrorCode.CLOSED_TICKET_MOD);
|
||||
}
|
||||
|
||||
if (newStatus == TicketStatus.IN_PROGRESS && ticket.getAssignee() == null) {
|
||||
throw new BusinessException(ErrorCode.ASSIGNEE_REQUIRED);
|
||||
}
|
||||
|
||||
if (newStatus == TicketStatus.RESOLVED && ticket.getResolvedAt() == null) {
|
||||
ticket.setResolvedAt(LocalDateTime.now());
|
||||
}
|
||||
|
||||
ticket.setStatus(newStatus);
|
||||
Ticket updated = ticketRepository.save(ticket);
|
||||
return toIssueTicket(updated);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public IssueTicket assignTicket(Long ticketId, Long assigneeId) {
|
||||
Ticket ticket = ticketRepository.findById(ticketId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.TICKET_NOT_FOUND));
|
||||
|
||||
Employee assignee = employeeRepository.findById(assigneeId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
ticket.setAssignee(assignee);
|
||||
if (ticket.getStatus() == TicketStatus.OPEN) {
|
||||
ticket.setStatus(TicketStatus.ASSIGNED);
|
||||
}
|
||||
|
||||
Ticket updated = ticketRepository.save(ticket);
|
||||
return toIssueTicket(updated);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public IssueTicket resolveTicket(Long ticketId, String resolutionNote) {
|
||||
Ticket ticket = ticketRepository.findById(ticketId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.TICKET_NOT_FOUND));
|
||||
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||
throw new BusinessException(ErrorCode.CLOSED_TICKET_MOD);
|
||||
}
|
||||
|
||||
ticket.setStatus(TicketStatus.RESOLVED);
|
||||
ticket.setResolvedAt(LocalDateTime.now());
|
||||
ticket.setResolutionNote(resolutionNote);
|
||||
|
||||
Ticket updated = ticketRepository.save(ticket);
|
||||
return toIssueTicket(updated);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public TicketCommentResponse addComment(Long ticketId, Long userId, CreateCommentRequest request) {
|
||||
Ticket ticket = ticketRepository.findById(ticketId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.TICKET_NOT_FOUND));
|
||||
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||
throw new BusinessException(ErrorCode.CLOSED_TICKET_MOD);
|
||||
}
|
||||
|
||||
Employee user = employeeRepository.findById(userId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
|
||||
TicketComment comment = TicketComment.builder()
|
||||
.ticket(ticket)
|
||||
.user(user)
|
||||
.commentText(request.commentText())
|
||||
.attachedFileUrl(request.attachedFileUrl())
|
||||
.build();
|
||||
|
||||
TicketComment saved = ticketCommentRepository.save(comment);
|
||||
return new TicketCommentResponse(
|
||||
saved.getCommentId(),
|
||||
saved.getUser().getName(),
|
||||
saved.getCommentText(),
|
||||
saved.getAttachedFileUrl(),
|
||||
saved.getCreatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
public List<TicketCommentResponse> getComments(Long ticketId) {
|
||||
List<TicketComment> comments = ticketCommentRepository.findByTicket_TicketIdOrderByCreatedAtAsc(ticketId);
|
||||
return comments.stream()
|
||||
.map(c -> new TicketCommentResponse(
|
||||
c.getCommentId(),
|
||||
c.getUser().getName(),
|
||||
c.getCommentText(),
|
||||
c.getAttachedFileUrl(),
|
||||
c.getCreatedAt()
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteTicket(Long ticketId) {
|
||||
Ticket ticket = ticketRepository.findById(ticketId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.TICKET_NOT_FOUND));
|
||||
ticketRepository.delete(ticket);
|
||||
}
|
||||
|
||||
private IssueTicket toIssueTicket(Ticket ticket) {
|
||||
return new IssueTicket(
|
||||
ticket.getTicketId(),
|
||||
ticket.getProject().getProjectName(),
|
||||
ticket.getRequester().getName(),
|
||||
ticket.getAssignee() != null ? ticket.getAssignee().getName() : null,
|
||||
ticket.getCategory(),
|
||||
ticket.getPriority(),
|
||||
ticket.getTitle(),
|
||||
ticket.getDescription(),
|
||||
ticket.getStatus(),
|
||||
ticket.getDueDate(),
|
||||
ticket.getResolvedAt(),
|
||||
ticket.getResolutionNote(),
|
||||
ticket.getSlaWarnFlag() == 'Y',
|
||||
ticket.getCreatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.dto.TrainingSummary;
|
||||
import com.srsystem.dto.UpdateProgressRequest;
|
||||
import com.srsystem.entity.Employee;
|
||||
import com.srsystem.entity.TrainingProgress;
|
||||
import com.srsystem.entity.enums.TrainingStatus;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import com.srsystem.repository.TrainingProgressRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class TrainingService {
|
||||
|
||||
private final TrainingProgressRepository trainingProgressRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
|
||||
public List<TrainingProgress> getMyProgress(Long userId) {
|
||||
return trainingProgressRepository.findByUser_EmpId(userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public TrainingProgress updateProgress(Long progressId, UpdateProgressRequest request) {
|
||||
TrainingProgress progress = trainingProgressRepository.findById(progressId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.REPORT_NOT_FOUND));
|
||||
|
||||
if (request.progressRate() != null) {
|
||||
progress.setProgressRate(request.progressRate());
|
||||
}
|
||||
if (request.status() != null) {
|
||||
progress.setStatus(request.status());
|
||||
}
|
||||
|
||||
if (progress.getProgressRate() == 100) {
|
||||
progress.setStatus(TrainingStatus.COMPLETED);
|
||||
}
|
||||
|
||||
return trainingProgressRepository.save(progress);
|
||||
}
|
||||
|
||||
public List<TrainingSummary> getSummary() {
|
||||
List<Employee> employees = employeeRepository.findAll();
|
||||
|
||||
return employees.stream()
|
||||
.map(emp -> {
|
||||
List<TrainingProgress> progresses = trainingProgressRepository.findByUser_EmpId(emp.getEmpId());
|
||||
long totalCourses = progresses.size();
|
||||
long completedCount = progresses.stream()
|
||||
.filter(p -> p.getStatus() == TrainingStatus.COMPLETED)
|
||||
.count();
|
||||
double avgProgressRate = progresses.stream()
|
||||
.mapToInt(TrainingProgress::getProgressRate)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
|
||||
return new TrainingSummary(
|
||||
emp.getEmpId(),
|
||||
emp.getName(),
|
||||
totalCourses,
|
||||
completedCount,
|
||||
avgProgressRate
|
||||
);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.srsystem.service;
|
||||
|
||||
import com.srsystem.entity.Employee;
|
||||
import com.srsystem.entity.WeeklyPlan;
|
||||
import com.srsystem.entity.enums.WeeklyPlanStatus;
|
||||
import com.srsystem.exception.BusinessException;
|
||||
import com.srsystem.exception.ErrorCode;
|
||||
import com.srsystem.repository.EmployeeRepository;
|
||||
import com.srsystem.repository.WeeklyPlanRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class WeeklyPlanService {
|
||||
|
||||
private final WeeklyPlanRepository weeklyPlanRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
|
||||
public List<WeeklyPlan> getMyPlans(Long userId) {
|
||||
return weeklyPlanRepository.findByUser_EmpIdOrderByWeekStartDesc(userId);
|
||||
}
|
||||
|
||||
public WeeklyPlan getPlan(Long planId) {
|
||||
return weeklyPlanRepository.findById(planId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.PLAN_NOT_FOUND));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WeeklyPlan createPlan(Long userId, String tasksJson) {
|
||||
Employee user = employeeRepository.findById(userId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||
LocalDate weekStart = LocalDate.now().with(java.time.DayOfWeek.MONDAY);
|
||||
|
||||
WeeklyPlan plan = WeeklyPlan.builder()
|
||||
.user(user)
|
||||
.weekStart(weekStart)
|
||||
.status(WeeklyPlanStatus.DRAFT)
|
||||
.tasks(tasksJson)
|
||||
.build();
|
||||
return weeklyPlanRepository.save(plan);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WeeklyPlan updatePlan(Long planId, String tasksJson, WeeklyPlanStatus status) {
|
||||
WeeklyPlan plan = getPlan(planId);
|
||||
if (tasksJson != null) plan.setTasks(tasksJson);
|
||||
if (status != null) plan.setStatus(status);
|
||||
return weeklyPlanRepository.save(plan);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deletePlan(Long planId) {
|
||||
weeklyPlanRepository.deleteById(planId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.srsystem.service.notification;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class EmailSender {
|
||||
|
||||
@Value("${notification.email.enabled:false}")
|
||||
private boolean emailEnabled;
|
||||
|
||||
public void send(String to, String subject, String body) {
|
||||
if (!emailEnabled) {
|
||||
log.debug("Email notification disabled, skipping");
|
||||
return;
|
||||
}
|
||||
// TODO: Implement JavaMailSender or external email API
|
||||
log.info("Email notification sent: to={}, subject={}", to, subject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.srsystem.service.notification;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SlackSender {
|
||||
|
||||
@Value("${notification.slack.webhook-url:}")
|
||||
private String webhookUrl;
|
||||
|
||||
public void send(String title, String message) {
|
||||
if (webhookUrl.isBlank()) {
|
||||
log.debug("Slack webhook URL not configured, skipping Slack notification");
|
||||
return;
|
||||
}
|
||||
// TODO: Implement Slack Webhook API call
|
||||
log.info("Slack notification sent: title={}", title);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user