init: SR System — Helpdesk & ITAM Portal
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.next
|
||||||
|
*.md
|
||||||
|
backend/
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# PostgreSQL
|
||||||
|
POSTGRES_DB=sr_system
|
||||||
|
POSTGRES_USER=sr_admin
|
||||||
|
POSTGRES_PASSWORD=<your-db-password>
|
||||||
|
|
||||||
|
# JWT (최소 256bit)
|
||||||
|
JWT_SECRET=<your-256bit-jwt-secret>
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
backend/build/
|
||||||
|
backend/.gradle/
|
||||||
|
backend/out/
|
||||||
|
*.jar
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Agent Tier Routing Guide
|
||||||
|
|
||||||
|
3-tier agent system: **LITE → MID → MAX**
|
||||||
|
|
||||||
|
## Decision Flow
|
||||||
|
|
||||||
|
1. **LITE first** for simple/cheap tasks
|
||||||
|
2. **MID** for normal complexity
|
||||||
|
3. **MAX** only when MID fails 3+ times
|
||||||
|
|
||||||
|
## Per-role routing
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
| Task | Agent |
|
||||||
|
|------|-------|
|
||||||
|
| Simple design sketch, brief review | @architect-lite (LITE) |
|
||||||
|
| Full architecture, domain modeling, DB schema | @architect (MID) |
|
||||||
|
| Unsolvable architecture problem | @escalation (MAX) |
|
||||||
|
|
||||||
|
### Code Analysis
|
||||||
|
| Task | Agent |
|
||||||
|
|------|-------|
|
||||||
|
| Quick file find, simple grep | @context-manager-lite (LITE) |
|
||||||
|
| Deep codebase analysis, refactoring scope | @context-manager (MID) |
|
||||||
|
| Beyond MID capability | @escalation (MAX) |
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
| Task | Agent |
|
||||||
|
|------|-------|
|
||||||
|
| DTO/Entity boilerplate, simple CRUD, basic test | @worker-lite (LITE) |
|
||||||
|
| Business logic, API binding, complex test | @worker (MID) |
|
||||||
|
| Beyond MID capability | @escalation (MAX) |
|
||||||
|
|
||||||
|
### Error Fixing
|
||||||
|
| Task | Agent |
|
||||||
|
|------|-------|
|
||||||
|
| Missing import, syntax fix, format lint | @inspector-lite (LITE) |
|
||||||
|
| Multi-step compile error, runtime bug | @inspector (MID) |
|
||||||
|
| 3+ failures on same bug | @escalation (MAX) |
|
||||||
|
|
||||||
|
## Cost Awareness
|
||||||
|
- LITE: deepseek-v4-flash / mimo-v2.5 — $0.14/$0.28 per 1M tokens
|
||||||
|
- MID: deepseek-v4-pro / mimo-v2.5-pro / qwen3.6-plus — $0.50~$1.74/$3.48
|
||||||
|
- MAX: glm-5.1 — $1.40/$4.40 (880 req/5hr limit, use sparingly)
|
||||||
|
|
||||||
|
## Rule
|
||||||
|
Never use MAX for problems that LITE or MID can handle. Let the failed agent's prompt guide escalation.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Token Efficiency — Caveman Default
|
||||||
|
|
||||||
|
Load skill: `~/.agents/skills/caveman/SKILL.md`
|
||||||
|
|
||||||
|
## Default Mode
|
||||||
|
|
||||||
|
Output in **caveman full** level. Exceptions:
|
||||||
|
|
||||||
|
| Context | Mode | Reason |
|
||||||
|
|---------|------|--------|
|
||||||
|
| Code/blocks | Normal | Accuracy critical |
|
||||||
|
| Commit msg | Normal | Conventional commits |
|
||||||
|
| Security warnings | Normal | Clarity required |
|
||||||
|
| Irreversible confirm | Normal | Safety |
|
||||||
|
| User asks "normal mode" | Normal | User override |
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms. Technical terms exact. Code blocks unchanged. Errors quoted exact.
|
||||||
|
|
||||||
|
Pattern: `[thing] [action] [reason]. [next step].`
|
||||||
|
|
||||||
|
## Intensity Switching
|
||||||
|
- `/caveman lite` — no filler/hedging, keep sentences
|
||||||
|
- `/caveman full` — default, classic caveman
|
||||||
|
- `/caveman ultra` — max compression
|
||||||
|
- `stop caveman` / `normal mode` — revert
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Context Caching Optimization Specification
|
||||||
|
|
||||||
|
## 1. Prompt Structure (Strict)
|
||||||
|
Prompts MUST follow this sequence:
|
||||||
|
|
||||||
|
### [A. STATIC REGION] - Top (Always Cached)
|
||||||
|
1. System Persona & Rules (e.g., .geminirules)
|
||||||
|
2. Background Knowledge (API specs, architecture, DB schema)
|
||||||
|
3. Output Format Definitions
|
||||||
|
|
||||||
|
### [B. DYNAMIC REGION] - Bottom (Cache Boundary)
|
||||||
|
1. Variable Parameters (Current Time, Session Metadata)
|
||||||
|
2. User Request / Query
|
||||||
|
|
||||||
|
## 2. Token Compression
|
||||||
|
- Data Flattening: Use Markdown tables, CSV, or JSON instead of prose
|
||||||
|
- Metadata Sanitization: Remove code comments, redundant empty lines, duplicate spacing, and non-essential tracking properties
|
||||||
|
|
||||||
|
## 3. Session Controls
|
||||||
|
- History Truncation: Do not concatenate raw dialog turns (invalidates cache)
|
||||||
|
- Checkpoint Aggregation: Summarize history at checkpoints; cache summary, flush raw turns
|
||||||
|
- Request Batching: Align multi-agent tasks within same TTL cycles for Cache Hit ratio
|
||||||
|
|
||||||
|
# Agentic Coding Workflow - Model Orchestration
|
||||||
|
|
||||||
|
Role-based model dispatching strategy for rate limit optimization.
|
||||||
|
|
||||||
|
## 1. Agent Architecture
|
||||||
|
4-layer workflow routing by context size, reasoning difficulty, speed, and remaining quota:
|
||||||
|
|
||||||
|
- Layer 1. Architect Agent (Design / Bug Tracking)
|
||||||
|
Models: DeepSeek V4 Pro, Qwen3.6 Plus (Sub: GLM-5.1)
|
||||||
|
Role: Requirements analysis and structural design specification
|
||||||
|
|
||||||
|
- Layer 2. Repository Context Agent (Large-scale Source Analysis)
|
||||||
|
Models: MiMo-V2.5-Pro, Kimi K2.6
|
||||||
|
Role: Long context codebase analysis and task splitting
|
||||||
|
|
||||||
|
- Layer 3. Worker Agent (Code Generation / Boilerplate)
|
||||||
|
Models: Qwen3.5 Plus, MiniMax M2.7
|
||||||
|
Role: Business logic and function-level implementation
|
||||||
|
|
||||||
|
- Layer 4. Inspector Agent (Compile/Lint Error Self-Correction)
|
||||||
|
Models: DeepSeek V4 Flash
|
||||||
|
Role: Build monitoring, syntax error fix, output format validation
|
||||||
|
|
||||||
|
## 2. Layer Routing & Limits
|
||||||
|
|
||||||
|
| Layer | Models | Hourly | Weekly | Primary Tasks |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Architect | DeepSeek V4 Pro, Qwen3.6 Plus, GLM-5.1 | 3450, 3300, 880 | 8550, 8200, 2150 | Domain modeling, DB schema, complex debugging. GLM-5.1: approval only |
|
||||||
|
| Context Manager | MiMo-V2.5-Pro, Kimi K2.6 | 1290, 1150 | 3225, 2880 | Long context analysis, legacy refactoring scope, API doc ingestion |
|
||||||
|
| Worker | Qwen3.5 Plus, MiniMax M2.7 | 10200, 3400 | 25200, 8500 | Unit function impl, API binding, DTO/Entity boilerplate, unit tests |
|
||||||
|
| Inspector | DeepSeek V4 Flash | 31650 | 79050 | Compile/lint error fix, output validation, self-correction loop |
|
||||||
|
|
||||||
|
## 3. Implementation Guide
|
||||||
|
|
||||||
|
```python
|
||||||
|
architect_llm = CustomLLM("deepseek-v4-pro")
|
||||||
|
context_llm = CustomLLM("mimo-v2.5-pro")
|
||||||
|
worker_llm = CustomLLM("qwen-3.5-plus")
|
||||||
|
inspector_llm = CustomLLM("deepseek-v4-flash")
|
||||||
|
|
||||||
|
architect = Agent(role="Software Architect", llm=architect_llm)
|
||||||
|
code_worker = Agent(role="Senior Backend Developer", llm=worker_llm)
|
||||||
|
linter_inspector = Agent(role="QA & Code Inspector", llm=inspector_llm)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Operational Rules
|
||||||
|
|
||||||
|
1. DeepSeek V4 Flash Firewall
|
||||||
|
- Typos, semicolons, import errors: NEVER escalate to higher models
|
||||||
|
- Minimum 3 self-correction loops must resolve within Flash (31650/hr)
|
||||||
|
|
||||||
|
2. GLM-5.1 Escalation Threshold
|
||||||
|
- 880/hr limit is tight. Exclude from default pipeline
|
||||||
|
- Use only as last-resort fallback after 3+ failures from other models
|
||||||
|
|
||||||
|
3. Context Split for Token Savings
|
||||||
|
- After MiMo/Kimi scans full repo, extract only needed snippets for Qwen3.5 Plus
|
||||||
|
|
||||||
|
4. MiniMax Surge Switching
|
||||||
|
- During boilerplate/test code surges, temporarily route Worker from Qwen3.5 Plus to MiniMax M2.5 (6300/hr) or M2.7 to prevent weekly/monthly lock
|
||||||
|
|
||||||
|
## 5. Development Docs
|
||||||
|
1. Base docs: ./docs/
|
||||||
|
2. Real-time dev logs: ./work_logs/
|
||||||
|
3. New session: check ./work_logs/work_log_*.md to resume
|
||||||
|
4. Milestone complete: use /graphify to map milestone relationships
|
||||||
|
5. Design elements: ./premium_enterprise/DESIGN.md
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
FROM node:20-alpine AS build
|
||||||
|
ARG API_BACKEND_URL
|
||||||
|
ENV API_BACKEND_URL=$API_BACKEND_URL
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/.next/standalone ./
|
||||||
|
COPY --from=build /app/.next/static ./.next/static
|
||||||
|
COPY --from=build /app/public ./public
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.gradle
|
||||||
|
build
|
||||||
|
*.md
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
FROM eclipse-temurin:17-jdk AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY gradlew build.gradle settings.gradle ./
|
||||||
|
COPY gradle gradle
|
||||||
|
RUN chmod +x gradlew && ./gradlew dependencies --no-daemon
|
||||||
|
COPY src src
|
||||||
|
RUN ./gradlew bootJar --no-daemon
|
||||||
|
|
||||||
|
FROM eclipse-temurin:17-jre
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/build/libs/*.jar app.jar
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.boot' version '3.2.0'
|
||||||
|
id 'io.spring.dependency-management' version '1.1.4'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com'
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compileOnly {
|
||||||
|
extendsFrom annotationProcessor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
|
||||||
|
implementation 'org.flywaydb:flyway-core'
|
||||||
|
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
|
||||||
|
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
|
||||||
|
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-batch'
|
||||||
|
implementation 'org.postgresql:postgresql'
|
||||||
|
implementation 'org.projectlombok:lombok:1.18.38'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:1.18.38'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1,9 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
+248
@@ -0,0 +1,248 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
Vendored
+82
@@ -0,0 +1,82 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
|
setlocal EnableExtensions
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
|
:exitWithErrorLevel
|
||||||
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'sr-system-backend'
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user