From ba4693ec65898069121c57826528f0c2a618c907 Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Mon, 27 Oct 2025 17:22:43 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[REFAC]=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0,=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=92?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/PartController.java | 99 -- .../api/domain/PartGroupRepository.java | 15 - .../com/sampoom/backend/api/package-info.java | 1 - .../api/part/controller/PartController.java | 99 ++ .../{ => part}/dto/CategoryResponseDTO.java | 4 +- .../{ => part}/dto/PartCreateRequestDTO.java | 2 +- .../{ => part}/dto/PartGroupResponseDTO.java | 6 +- .../api/{ => part}/dto/PartResponseDTO.java | 5 +- .../{ => part}/dto/PartUpdateRequestDTO.java | 4 +- .../api/{domain => part/entity}/Category.java | 4 +- .../PartGroup.java => part/entity/Group.java} | 6 +- .../api/{domain => part/entity}/Part.java | 14 +- .../{domain => part/entity}/PartStatus.java | 2 +- .../repository}/CategoryRepository.java | 3 +- .../part/repository/PartGroupRepository.java | 17 + .../repository}/PartRepository.java | 4 +- .../api/{ => part}/service/PartService.java | 18 +- .../backend/common/config/CsvDataLoader.java | 13 +- src/main/resources/application.properties | 9 +- src/main/resources/data/parts.csv | 1402 ++++++++--------- 20 files changed, 874 insertions(+), 853 deletions(-) delete mode 100644 src/main/java/com/sampoom/backend/api/controller/PartController.java delete mode 100644 src/main/java/com/sampoom/backend/api/domain/PartGroupRepository.java delete mode 100644 src/main/java/com/sampoom/backend/api/package-info.java create mode 100644 src/main/java/com/sampoom/backend/api/part/controller/PartController.java rename src/main/java/com/sampoom/backend/api/{ => part}/dto/CategoryResponseDTO.java (73%) rename src/main/java/com/sampoom/backend/api/{ => part}/dto/PartCreateRequestDTO.java (92%) rename src/main/java/com/sampoom/backend/api/{ => part}/dto/PartGroupResponseDTO.java (67%) rename src/main/java/com/sampoom/backend/api/{ => part}/dto/PartResponseDTO.java (74%) rename src/main/java/com/sampoom/backend/api/{ => part}/dto/PartUpdateRequestDTO.java (80%) rename src/main/java/com/sampoom/backend/api/{domain => part/entity}/Category.java (87%) rename src/main/java/com/sampoom/backend/api/{domain/PartGroup.java => part/entity/Group.java} (80%) rename src/main/java/com/sampoom/backend/api/{domain => part/entity}/Part.java (81%) rename src/main/java/com/sampoom/backend/api/{domain => part/entity}/PartStatus.java (64%) rename src/main/java/com/sampoom/backend/api/{domain => part/repository}/CategoryRepository.java (73%) create mode 100644 src/main/java/com/sampoom/backend/api/part/repository/PartGroupRepository.java rename src/main/java/com/sampoom/backend/api/{domain => part/repository}/PartRepository.java (80%) rename src/main/java/com/sampoom/backend/api/{ => part}/service/PartService.java (83%) diff --git a/src/main/java/com/sampoom/backend/api/controller/PartController.java b/src/main/java/com/sampoom/backend/api/controller/PartController.java deleted file mode 100644 index 6cef376..0000000 --- a/src/main/java/com/sampoom/backend/api/controller/PartController.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.sampoom.backend.api.controller; - -import com.sampoom.backend.api.dto.*; -import com.sampoom.backend.api.service.PartService; -import com.sampoom.backend.common.response.ApiResponse; -import com.sampoom.backend.common.response.SuccessStatus; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@Tag(name = "Part", description = "부품 API") -@Slf4j -@RestController -@RequestMapping() -@RequiredArgsConstructor -public class PartController { - - private final PartService partService; - - @Operation(summary = "카테고리 목록 조회", description = "카테고리 목록 조회") - @GetMapping("/categories") - public ResponseEntity>> getCategories() { - - List categoryList = partService.findAllCategories(); - - return ApiResponse.success(SuccessStatus.CATEGORY_LIST_SUCCESS, categoryList); - } - - @Operation(summary = "그룹 목록 조회", description = "카테고리에 속한 그룹 목록 조회") - @GetMapping("/categories/{categoryId}/groups") - public ResponseEntity>> getGroups(@PathVariable Long categoryId) { - - List groupList = partService.findGroupsByCategoryId(categoryId); - - return ApiResponse.success(SuccessStatus.GROUP_LIST_SUCCESS, groupList); - } - - @Operation(summary = "부품 목록 조회", description = "특정 그룹에 속한 부품 목록 조회") - @GetMapping - public ResponseEntity>> getPartsByGroup(@RequestParam Long groupId) { - - List partList = partService.findPartsByGroupId(groupId); - - return ApiResponse.success(SuccessStatus.PART_LIST_SUCCESS, partList); - } - - @Operation(summary = "부품 등록", description = "새로운 부품을 등록") - @PostMapping - public ResponseEntity> createPart( - @Valid @RequestBody PartCreateRequestDTO partCreateRequestDTO - ) { - PartResponseDTO partResponse = partService.createPart(partCreateRequestDTO); - - return ApiResponse.success(SuccessStatus.PART_CREATE_SUCCESS, partResponse); - } - - @Operation(summary = "부품 수정", description = "부품을 수정") - @PutMapping("/{partId}") - public ResponseEntity> updatePart( - @PathVariable Long partId, - @Valid @RequestBody PartUpdateRequestDTO partUpdateRequestDTO - ) { - PartResponseDTO partResponse = partService.updatePart(partId, partUpdateRequestDTO); - - return ApiResponse.success(SuccessStatus.PART_UPDATE_SUCCESS, partResponse); - } - - @Operation(summary = "부품 삭제", description = "부품 삭제") - @DeleteMapping("/{partId}") - public ResponseEntity> deletePart(@PathVariable Long partId) { - - partService.deletePart(partId); - - return ApiResponse.success(SuccessStatus.PART_DELETE_SUCCESS, null); - } - - @Operation(summary = "부품 검색", description = "부품 검색 (부품코드, 부품명)") - @GetMapping("/search") - public ResponseEntity>> searchParts(@RequestParam String keyword) { - - List partList = partService.searchParts(keyword); - - return ApiResponse.success(SuccessStatus.PART_SEARCH_SUCCESS, partList); - } - - @Operation(summary = "단일 부품 조회", description = "부품 ID로 부품 상세 정보를 조회합니다.") - @GetMapping("/{partId}") - public ResponseEntity> getPartById(@PathVariable Long partId) { - PartResponseDTO partResponse = partService.findPartById(partId); - return ApiResponse.success(SuccessStatus.PART_DETAIL_SUCCESS, partResponse); - } - -} diff --git a/src/main/java/com/sampoom/backend/api/domain/PartGroupRepository.java b/src/main/java/com/sampoom/backend/api/domain/PartGroupRepository.java deleted file mode 100644 index 40116f9..0000000 --- a/src/main/java/com/sampoom/backend/api/domain/PartGroupRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.sampoom.backend.api.domain; - -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; -import java.util.Optional; - -public interface PartGroupRepository extends JpaRepository { - - // 카테고리 id로 모든 그룹 찾는 메서드 - List findByCategoryId(Long categoryId); - - // 카테고리와 코드로 그룹을 찾기 위한 메서드 - Optional findByCodeAndCategory(String code, Category category); -} diff --git a/src/main/java/com/sampoom/backend/api/package-info.java b/src/main/java/com/sampoom/backend/api/package-info.java deleted file mode 100644 index 8727ea2..0000000 --- a/src/main/java/com/sampoom/backend/api/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package com.sampoom.backend.api; \ No newline at end of file diff --git a/src/main/java/com/sampoom/backend/api/part/controller/PartController.java b/src/main/java/com/sampoom/backend/api/part/controller/PartController.java new file mode 100644 index 0000000..3bbc536 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/part/controller/PartController.java @@ -0,0 +1,99 @@ +//package com.sampoom.backend.api.part.controller; +// +//import com.sampoom.backend.api.part.dto.*; +//import com.sampoom.backend.api.part.service.PartService; +//import com.sampoom.backend.common.response.ApiResponse; +//import com.sampoom.backend.common.response.SuccessStatus; +//import io.swagger.v3.oas.annotations.Operation; +//import io.swagger.v3.oas.annotations.tags.Tag; +//import jakarta.validation.Valid; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.*; +// +//import java.util.List; +// +//@Tag(name = "Part", description = "부품 API") +//@Slf4j +//@RestController +//@RequestMapping() +//@RequiredArgsConstructor +//public class PartController { +// +// private final PartService partService; +// +// @Operation(summary = "카테고리 목록 조회", description = "카테고리 목록 조회") +// @GetMapping("/categories") +// public ResponseEntity>> getCategories() { +// +// List categoryList = partService.findAllCategories(); +// +// return ApiResponse.success(SuccessStatus.CATEGORY_LIST_SUCCESS, categoryList); +// } +// +// @Operation(summary = "그룹 목록 조회", description = "카테고리에 속한 그룹 목록 조회") +// @GetMapping("/categories/{categoryId}/groups") +// public ResponseEntity>> getGroups(@PathVariable Long categoryId) { +// +// List groupList = partService.findGroupsByCategoryId(categoryId); +// +// return ApiResponse.success(SuccessStatus.GROUP_LIST_SUCCESS, groupList); +// } +// +// @Operation(summary = "부품 목록 조회", description = "특정 그룹에 속한 부품 목록 조회") +// @GetMapping +// public ResponseEntity>> getPartsByGroup(@RequestParam Long groupId) { +// +// List partList = partService.findPartsByGroupId(groupId); +// +// return ApiResponse.success(SuccessStatus.PART_LIST_SUCCESS, partList); +// } +// +// @Operation(summary = "부품 등록", description = "새로운 부품을 등록") +// @PostMapping +// public ResponseEntity> createPart( +// @Valid @RequestBody PartCreateRequestDTO partCreateRequestDTO +// ) { +// PartResponseDTO partResponse = partService.createPart(partCreateRequestDTO); +// +// return ApiResponse.success(SuccessStatus.PART_CREATE_SUCCESS, partResponse); +// } +// +// @Operation(summary = "부품 수정", description = "부품을 수정") +// @PutMapping("/{partId}") +// public ResponseEntity> updatePart( +// @PathVariable Long partId, +// @Valid @RequestBody PartUpdateRequestDTO partUpdateRequestDTO +// ) { +// PartResponseDTO partResponse = partService.updatePart(partId, partUpdateRequestDTO); +// +// return ApiResponse.success(SuccessStatus.PART_UPDATE_SUCCESS, partResponse); +// } +// +// @Operation(summary = "부품 삭제", description = "부품 삭제") +// @DeleteMapping("/{partId}") +// public ResponseEntity> deletePart(@PathVariable Long partId) { +// +// partService.deletePart(partId); +// +// return ApiResponse.success(SuccessStatus.PART_DELETE_SUCCESS, null); +// } +// +// @Operation(summary = "부품 검색", description = "부품 검색 (부품코드, 부품명)") +// @GetMapping("/search") +// public ResponseEntity>> searchParts(@RequestParam String keyword) { +// +// List partList = partService.searchParts(keyword); +// +// return ApiResponse.success(SuccessStatus.PART_SEARCH_SUCCESS, partList); +// } +// +// @Operation(summary = "단일 부품 조회", description = "부품 ID로 부품 상세 정보를 조회합니다.") +// @GetMapping("/{partId}") +// public ResponseEntity> getPartById(@PathVariable Long partId) { +// PartResponseDTO partResponse = partService.findPartById(partId); +// return ApiResponse.success(SuccessStatus.PART_DETAIL_SUCCESS, partResponse); +// } +// +//} diff --git a/src/main/java/com/sampoom/backend/api/dto/CategoryResponseDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/CategoryResponseDTO.java similarity index 73% rename from src/main/java/com/sampoom/backend/api/dto/CategoryResponseDTO.java rename to src/main/java/com/sampoom/backend/api/part/dto/CategoryResponseDTO.java index 7d379be..7f30d68 100644 --- a/src/main/java/com/sampoom/backend/api/dto/CategoryResponseDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/CategoryResponseDTO.java @@ -1,6 +1,6 @@ -package com.sampoom.backend.api.dto; +package com.sampoom.backend.api.part.dto; -import com.sampoom.backend.api.domain.Category; +import com.sampoom.backend.api.part.entity.Category; import lombok.Getter; @Getter diff --git a/src/main/java/com/sampoom/backend/api/dto/PartCreateRequestDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartCreateRequestDTO.java similarity index 92% rename from src/main/java/com/sampoom/backend/api/dto/PartCreateRequestDTO.java rename to src/main/java/com/sampoom/backend/api/part/dto/PartCreateRequestDTO.java index 5ef323a..96c5ed4 100644 --- a/src/main/java/com/sampoom/backend/api/dto/PartCreateRequestDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartCreateRequestDTO.java @@ -1,4 +1,4 @@ -package com.sampoom.backend.api.dto; +package com.sampoom.backend.api.part.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/sampoom/backend/api/dto/PartGroupResponseDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartGroupResponseDTO.java similarity index 67% rename from src/main/java/com/sampoom/backend/api/dto/PartGroupResponseDTO.java rename to src/main/java/com/sampoom/backend/api/part/dto/PartGroupResponseDTO.java index 606dfa7..ace2763 100644 --- a/src/main/java/com/sampoom/backend/api/dto/PartGroupResponseDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartGroupResponseDTO.java @@ -1,6 +1,6 @@ -package com.sampoom.backend.api.dto; +package com.sampoom.backend.api.part.dto; -import com.sampoom.backend.api.domain.PartGroup; +import com.sampoom.backend.api.part.entity.Group; import lombok.Getter; @Getter @@ -10,7 +10,7 @@ public class PartGroupResponseDTO { private String name; private Long categoryId; - public PartGroupResponseDTO(PartGroup partGroup) { + public PartGroupResponseDTO(Group partGroup) { this.groupId = partGroup.getId(); this.name = partGroup.getName(); this.categoryId = partGroup.getCategory().getId(); diff --git a/src/main/java/com/sampoom/backend/api/dto/PartResponseDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartResponseDTO.java similarity index 74% rename from src/main/java/com/sampoom/backend/api/dto/PartResponseDTO.java rename to src/main/java/com/sampoom/backend/api/part/dto/PartResponseDTO.java index 61fadf0..da77906 100644 --- a/src/main/java/com/sampoom/backend/api/dto/PartResponseDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartResponseDTO.java @@ -1,7 +1,6 @@ -package com.sampoom.backend.api.dto; +package com.sampoom.backend.api.part.dto; -import com.sampoom.backend.api.domain.Part; -import com.sampoom.backend.api.domain.PartGroup; +import com.sampoom.backend.api.part.entity.Part; import lombok.Getter; @Getter diff --git a/src/main/java/com/sampoom/backend/api/dto/PartUpdateRequestDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartUpdateRequestDTO.java similarity index 80% rename from src/main/java/com/sampoom/backend/api/dto/PartUpdateRequestDTO.java rename to src/main/java/com/sampoom/backend/api/part/dto/PartUpdateRequestDTO.java index 81038ec..32a837f 100644 --- a/src/main/java/com/sampoom/backend/api/dto/PartUpdateRequestDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartUpdateRequestDTO.java @@ -1,6 +1,6 @@ -package com.sampoom.backend.api.dto; +package com.sampoom.backend.api.part.dto; -import com.sampoom.backend.api.domain.PartStatus; +import com.sampoom.backend.api.part.entity.PartStatus; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/src/main/java/com/sampoom/backend/api/domain/Category.java b/src/main/java/com/sampoom/backend/api/part/entity/Category.java similarity index 87% rename from src/main/java/com/sampoom/backend/api/domain/Category.java rename to src/main/java/com/sampoom/backend/api/part/entity/Category.java index 00f1bc6..7702389 100644 --- a/src/main/java/com/sampoom/backend/api/domain/Category.java +++ b/src/main/java/com/sampoom/backend/api/part/entity/Category.java @@ -1,4 +1,4 @@ -package com.sampoom.backend.api.domain; +package com.sampoom.backend.api.part.entity; import com.sampoom.backend.common.entitiy.BaseTimeEntity; import jakarta.persistence.*; @@ -8,7 +8,7 @@ @Entity @Getter @NoArgsConstructor -@Table(name = "category") +@Table(name = "part_category") public class Category extends BaseTimeEntity { @Id diff --git a/src/main/java/com/sampoom/backend/api/domain/PartGroup.java b/src/main/java/com/sampoom/backend/api/part/entity/Group.java similarity index 80% rename from src/main/java/com/sampoom/backend/api/domain/PartGroup.java rename to src/main/java/com/sampoom/backend/api/part/entity/Group.java index 7a84082..b7fcba4 100644 --- a/src/main/java/com/sampoom/backend/api/domain/PartGroup.java +++ b/src/main/java/com/sampoom/backend/api/part/entity/Group.java @@ -1,4 +1,4 @@ -package com.sampoom.backend.api.domain; +package com.sampoom.backend.api.part.entity; import com.sampoom.backend.common.entitiy.BaseTimeEntity; import jakarta.persistence.*; @@ -9,7 +9,7 @@ @Getter @NoArgsConstructor @Table(name = "part_group") -public class PartGroup extends BaseTimeEntity { +public class Group extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -24,7 +24,7 @@ public class PartGroup extends BaseTimeEntity { private Category category; // Category 엔티티와 N:1 관계 // CSV 로더가 사용할 생성자 - public PartGroup(String code, String name, Category category) { + public Group(String code, String name, Category category) { this.code = code; this.name = name; this.category = category; diff --git a/src/main/java/com/sampoom/backend/api/domain/Part.java b/src/main/java/com/sampoom/backend/api/part/entity/Part.java similarity index 81% rename from src/main/java/com/sampoom/backend/api/domain/Part.java rename to src/main/java/com/sampoom/backend/api/part/entity/Part.java index 4d12ff9..05c6330 100644 --- a/src/main/java/com/sampoom/backend/api/domain/Part.java +++ b/src/main/java/com/sampoom/backend/api/part/entity/Part.java @@ -1,7 +1,7 @@ -package com.sampoom.backend.api.domain; +package com.sampoom.backend.api.part.entity; -import com.sampoom.backend.api.dto.PartCreateRequestDTO; -import com.sampoom.backend.api.dto.PartUpdateRequestDTO; +import com.sampoom.backend.api.part.dto.PartCreateRequestDTO; +import com.sampoom.backend.api.part.dto.PartUpdateRequestDTO; import com.sampoom.backend.common.entitiy.BaseTimeEntity; import jakarta.persistence.*; import lombok.Getter; @@ -10,7 +10,7 @@ @Entity @Getter @NoArgsConstructor -@Table(name = "part") +@Table(name = "part_master") public class Part extends BaseTimeEntity { @Id @@ -28,10 +28,10 @@ public class Part extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "group_id", nullable = false) - private PartGroup partGroup; // PartGroup 엔티티와 N:1 관계 + private Group partGroup; // PartGroup 엔티티와 N:1 관계 // CSV 로더가 사용할 생성자 - public Part(String code, String name, PartGroup partGroup) { + public Part(String code, String name, Group partGroup) { this.code = code; this.name = name; this.partGroup = partGroup; @@ -39,7 +39,7 @@ public Part(String code, String name, PartGroup partGroup) { } // 생성 메서드 - public static Part create(PartCreateRequestDTO partCreateRequestDTO, PartGroup partGroup) { + public static Part create(PartCreateRequestDTO partCreateRequestDTO, Group partGroup) { Part part = new Part(); part.code = partCreateRequestDTO.getCode(); part.name = partCreateRequestDTO.getName(); diff --git a/src/main/java/com/sampoom/backend/api/domain/PartStatus.java b/src/main/java/com/sampoom/backend/api/part/entity/PartStatus.java similarity index 64% rename from src/main/java/com/sampoom/backend/api/domain/PartStatus.java rename to src/main/java/com/sampoom/backend/api/part/entity/PartStatus.java index 5d66ceb..46a54d3 100644 --- a/src/main/java/com/sampoom/backend/api/domain/PartStatus.java +++ b/src/main/java/com/sampoom/backend/api/part/entity/PartStatus.java @@ -1,4 +1,4 @@ -package com.sampoom.backend.api.domain; +package com.sampoom.backend.api.part.entity; public enum PartStatus { ACTIVE, // 판매중 diff --git a/src/main/java/com/sampoom/backend/api/domain/CategoryRepository.java b/src/main/java/com/sampoom/backend/api/part/repository/CategoryRepository.java similarity index 73% rename from src/main/java/com/sampoom/backend/api/domain/CategoryRepository.java rename to src/main/java/com/sampoom/backend/api/part/repository/CategoryRepository.java index a8756f5..42c369f 100644 --- a/src/main/java/com/sampoom/backend/api/domain/CategoryRepository.java +++ b/src/main/java/com/sampoom/backend/api/part/repository/CategoryRepository.java @@ -1,5 +1,6 @@ -package com.sampoom.backend.api.domain; +package com.sampoom.backend.api.part.repository; +import com.sampoom.backend.api.part.entity.Category; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/src/main/java/com/sampoom/backend/api/part/repository/PartGroupRepository.java b/src/main/java/com/sampoom/backend/api/part/repository/PartGroupRepository.java new file mode 100644 index 0000000..4c78ede --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/part/repository/PartGroupRepository.java @@ -0,0 +1,17 @@ +package com.sampoom.backend.api.part.repository; + +import com.sampoom.backend.api.part.entity.Category; +import com.sampoom.backend.api.part.entity.Group; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface PartGroupRepository extends JpaRepository { + + // 카테고리 id로 모든 그룹 찾는 메서드 + List findByCategoryId(Long categoryId); + + // 카테고리와 코드로 그룹을 찾기 위한 메서드 + Optional findByCodeAndCategory(String code, Category category); +} diff --git a/src/main/java/com/sampoom/backend/api/domain/PartRepository.java b/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java similarity index 80% rename from src/main/java/com/sampoom/backend/api/domain/PartRepository.java rename to src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java index dc27055..655d87e 100644 --- a/src/main/java/com/sampoom/backend/api/domain/PartRepository.java +++ b/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java @@ -1,5 +1,7 @@ -package com.sampoom.backend.api.domain; +package com.sampoom.backend.api.part.repository; +import com.sampoom.backend.api.part.entity.PartStatus; +import com.sampoom.backend.api.part.entity.Part; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/com/sampoom/backend/api/service/PartService.java b/src/main/java/com/sampoom/backend/api/part/service/PartService.java similarity index 83% rename from src/main/java/com/sampoom/backend/api/service/PartService.java rename to src/main/java/com/sampoom/backend/api/part/service/PartService.java index 26f8d61..64583fb 100644 --- a/src/main/java/com/sampoom/backend/api/service/PartService.java +++ b/src/main/java/com/sampoom/backend/api/part/service/PartService.java @@ -1,7 +1,13 @@ -package com.sampoom.backend.api.service; - -import com.sampoom.backend.api.domain.*; -import com.sampoom.backend.api.dto.*; +package com.sampoom.backend.api.part.service; + +import com.sampoom.backend.api.part.dto.*; +import com.sampoom.backend.api.part.entity.Category; +import com.sampoom.backend.api.part.entity.Part; +import com.sampoom.backend.api.part.entity.Group; +import com.sampoom.backend.api.part.entity.PartStatus; +import com.sampoom.backend.api.part.repository.CategoryRepository; +import com.sampoom.backend.api.part.repository.PartGroupRepository; +import com.sampoom.backend.api.part.repository.PartRepository; import com.sampoom.backend.common.exception.NotFoundException; import com.sampoom.backend.common.response.ErrorStatus; import lombok.RequiredArgsConstructor; @@ -34,7 +40,7 @@ public List findAllCategories() { @Transactional public List findGroupsByCategoryId(Long categoryId) { - List partGroups = partGroupRepository.findByCategoryId(categoryId); + List partGroups = partGroupRepository.findByCategoryId(categoryId); return partGroups.stream() .map(PartGroupResponseDTO::new) @@ -58,7 +64,7 @@ public List findPartsByGroupId(Long groupId) { public PartResponseDTO createPart(PartCreateRequestDTO partCreateRequestDTO) { // DTO에 담겨온 groupId로 PartGroup 엔티티 조회 - PartGroup partGroup = partGroupRepository.findById(partCreateRequestDTO.getGroupId()) + Group partGroup = partGroupRepository.findById(partCreateRequestDTO.getGroupId()) .orElseThrow(() -> new NotFoundException(ErrorStatus.GROUP_NOT_FOUND.getMessage())); Part newPart = Part.create(partCreateRequestDTO, partGroup); diff --git a/src/main/java/com/sampoom/backend/common/config/CsvDataLoader.java b/src/main/java/com/sampoom/backend/common/config/CsvDataLoader.java index e0c0c5b..b32693a 100644 --- a/src/main/java/com/sampoom/backend/common/config/CsvDataLoader.java +++ b/src/main/java/com/sampoom/backend/common/config/CsvDataLoader.java @@ -1,7 +1,12 @@ package com.sampoom.backend.common.config; -import com.sampoom.backend.api.domain.*; import com.opencsv.CSVReader; +import com.sampoom.backend.api.part.entity.Category; +import com.sampoom.backend.api.part.entity.Part; +import com.sampoom.backend.api.part.entity.Group; +import com.sampoom.backend.api.part.repository.CategoryRepository; +import com.sampoom.backend.api.part.repository.PartGroupRepository; +import com.sampoom.backend.api.part.repository.PartRepository; import lombok.RequiredArgsConstructor; import org.springframework.boot.CommandLineRunner; import org.springframework.core.io.ClassPathResource; @@ -45,7 +50,7 @@ public void run(String... args) throws Exception { String[] line; Map categoryCache = new HashMap<>(); - Map groupCache = new HashMap<>(); + Map groupCache = new HashMap<>(); while ((line = reader.readNext()) != null) { final String[] currentLine = line; @@ -61,9 +66,9 @@ public void run(String... args) throws Exception { String compositeGroupKey = categoryCode + "-" + groupCode; // 그룹코드만 하면 중복되니깐 '카테고리코드-그룹코드' - PartGroup partGroup = groupCache.computeIfAbsent(compositeGroupKey, key -> + Group partGroup = groupCache.computeIfAbsent(compositeGroupKey, key -> partGroupRepository.findByCodeAndCategory(groupCode, category).orElseGet(() -> - partGroupRepository.save(new PartGroup(groupCode, currentLine[GROUP_NAME], category)) + partGroupRepository.save(new Group(groupCode, currentLine[GROUP_NAME], category)) ) ); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index be9dbc5..3634d07 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,8 @@ -spring.application.name=backend-part +spring.datasource.url=jdbc:postgresql://3.38.218.173:5432/mydb +spring.datasource.username=myuser +spring.datasource.password=mypassword +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.jpa.hibernate.ddl-auto=create +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true diff --git a/src/main/resources/data/parts.csv b/src/main/resources/data/parts.csv index 540d23f..e38c17a 100644 --- a/src/main/resources/data/parts.csv +++ b/src/main/resources/data/parts.csv @@ -1,701 +1,701 @@ -id,category_code,category_name,group_code,group_name,part_no,name -1,ENG,엔진,1,흡기,ENG-01-001,인테이크 매니폴드 -2,ENG,엔진,2,배기,ENG-02-001,배기관 -3,ENG,엔진,3,연료,ENG-03-001,연료레일 -4,ENG,엔진,4,냉각,ENG-04-001,리저버탱크 -5,ENG,엔진,5,윤활,ENG-05-001,오일펌프 -6,ENG,엔진,6,점화,ENG-06-001,점화코일 -7,ENG,엔진,1,흡기,ENG-01-002,매스에어플로우센서 -8,ENG,엔진,2,배기,ENG-02-002,머플러 -9,ENG,엔진,3,연료,ENG-03-002,인젝터 -10,ENG,엔진,4,냉각,ENG-04-002,라디에이터호스 -11,ENG,엔진,5,윤활,ENG-05-002,오일펌프 -12,ENG,엔진,6,점화,ENG-06-002,이그니션스위치 -13,ENG,엔진,1,흡기,ENG-01-003,스로틀바디 -14,ENG,엔진,2,배기,ENG-02-003,머플러 -15,ENG,엔진,3,연료,ENG-03-003,연료펌프 -16,ENG,엔진,4,냉각,ENG-04-003,냉각팬모터 -17,ENG,엔진,5,윤활,ENG-05-003,오일게이지 -18,ENG,엔진,6,점화,ENG-06-003,점화코일 -19,ENG,엔진,1,흡기,ENG-01-004,스로틀바디 -20,ENG,엔진,2,배기,ENG-02-004,머플러 -21,ENG,엔진,3,연료,ENG-03-004,연료차단밸브 -22,ENG,엔진,4,냉각,ENG-04-004,냉각팬모터 -23,ENG,엔진,5,윤활,ENG-05-004,오일펌프 -24,ENG,엔진,6,점화,ENG-06-004,이그니션스위치 -25,ENG,엔진,1,흡기,ENG-01-005,에어필터 -26,ENG,엔진,2,배기,ENG-02-005,배기관 -27,ENG,엔진,3,연료,ENG-03-005,고압펌프 -28,ENG,엔진,4,냉각,ENG-04-005,리저버탱크 -29,ENG,엔진,5,윤활,ENG-05-005,오일쿨러 -30,ENG,엔진,6,점화,ENG-06-005,점화코일 -31,ENG,엔진,1,흡기,ENG-01-006,매스에어플로우센서 -32,ENG,엔진,2,배기,ENG-02-006,플렉시블조인트 -33,ENG,엔진,3,연료,ENG-03-006,연료레일 -34,ENG,엔진,4,냉각,ENG-04-006,라디에이터 -35,ENG,엔진,5,윤활,ENG-05-006,오일필터 -36,ENG,엔진,6,점화,ENG-06-006,점화코일 -37,ENG,엔진,1,흡기,ENG-01-007,매스에어플로우센서 -38,ENG,엔진,2,배기,ENG-02-007,배기관 -39,ENG,엔진,3,연료,ENG-03-007,인젝터 -40,ENG,엔진,4,냉각,ENG-04-007,냉각팬모터 -41,ENG,엔진,5,윤활,ENG-05-007,오일필터 -42,ENG,엔진,6,점화,ENG-06-007,이그니션스위치 -43,ENG,엔진,1,흡기,ENG-01-008,에어필터 -44,ENG,엔진,2,배기,ENG-02-008,플렉시블조인트 -45,ENG,엔진,3,연료,ENG-03-008,인젝터 -46,ENG,엔진,4,냉각,ENG-04-008,라디에이터호스 -47,ENG,엔진,5,윤활,ENG-05-008,오일필터 -48,ENG,엔진,6,점화,ENG-06-008,점화코일 -49,ENG,엔진,1,흡기,ENG-01-009,매스에어플로우센서 -50,ENG,엔진,2,배기,ENG-02-009,플렉시블조인트 -51,ENG,엔진,3,연료,ENG-03-009,고압펌프 -52,ENG,엔진,4,냉각,ENG-04-009,워터펌프 -53,ENG,엔진,5,윤활,ENG-05-009,오일팬 -54,ENG,엔진,6,점화,ENG-06-009,점화코일 -55,ENG,엔진,1,흡기,ENG-01-010,매스에어플로우센서 -56,ENG,엔진,2,배기,ENG-02-010,산소센서 -57,ENG,엔진,3,연료,ENG-03-010,연료펌프 -58,ENG,엔진,4,냉각,ENG-04-010,라디에이터호스 -59,ENG,엔진,5,윤활,ENG-05-010,오일펌프 -60,ENG,엔진,6,점화,ENG-06-010,이그니션스위치 -61,ENG,엔진,1,흡기,ENG-01-011,스로틀바디 -62,ENG,엔진,2,배기,ENG-02-011,가스켓 -63,ENG,엔진,3,연료,ENG-03-011,고압펌프 -64,ENG,엔진,4,냉각,ENG-04-011,라디에이터호스 -65,ENG,엔진,5,윤활,ENG-05-011,오일게이지 -66,ENG,엔진,6,점화,ENG-06-011,플러그와이어 -67,ENG,엔진,1,흡기,ENG-01-012,흡기호스 -68,ENG,엔진,2,배기,ENG-02-012,플렉시블조인트 -69,ENG,엔진,3,연료,ENG-03-012,연료레일 -70,ENG,엔진,4,냉각,ENG-04-012,써모스탯 -71,ENG,엔진,5,윤활,ENG-05-012,오일팬 -72,ENG,엔진,6,점화,ENG-06-012,스파크플러그 -73,ENG,엔진,1,흡기,ENG-01-013,스로틀바디 -74,ENG,엔진,2,배기,ENG-02-013,산소센서 -75,ENG,엔진,3,연료,ENG-03-013,연료필터 -76,ENG,엔진,4,냉각,ENG-04-013,라디에이터 -77,ENG,엔진,5,윤활,ENG-05-013,오일쿨러 -78,ENG,엔진,6,점화,ENG-06-013,플러그와이어 -79,ENG,엔진,1,흡기,ENG-01-014,매스에어플로우센서 -80,ENG,엔진,2,배기,ENG-02-014,가스켓 -81,ENG,엔진,3,연료,ENG-03-014,인젝터 -82,ENG,엔진,4,냉각,ENG-04-014,리저버탱크 -83,ENG,엔진,5,윤활,ENG-05-014,오일게이지 -84,ENG,엔진,6,점화,ENG-06-014,플러그와이어 -85,ENG,엔진,1,흡기,ENG-01-015,매스에어플로우센서 -86,ENG,엔진,2,배기,ENG-02-015,머플러 -87,ENG,엔진,3,연료,ENG-03-015,연료펌프 -88,ENG,엔진,4,냉각,ENG-04-015,라디에이터호스 -89,ENG,엔진,5,윤활,ENG-05-015,오일게이지 -90,ENG,엔진,6,점화,ENG-06-015,스파크플러그 -91,ENG,엔진,1,흡기,ENG-01-016,인테이크 매니폴드 -92,ENG,엔진,2,배기,ENG-02-016,배기관 -93,ENG,엔진,3,연료,ENG-03-016,연료레일 -94,ENG,엔진,4,냉각,ENG-04-016,냉각팬모터 -95,ENG,엔진,5,윤활,ENG-05-016,오일펌프 -96,ENG,엔진,6,점화,ENG-06-016,점화코일 -97,ENG,엔진,1,흡기,ENG-01-017,매스에어플로우센서 -98,ENG,엔진,2,배기,ENG-02-017,플렉시블조인트 -99,ENG,엔진,3,연료,ENG-03-017,인젝터 -100,ENG,엔진,4,냉각,ENG-04-017,써모스탯 -101,ENG,엔진,5,윤활,ENG-05-017,오일팬 -102,ENG,엔진,6,점화,ENG-06-017,이그니션스위치 -103,ENG,엔진,1,흡기,ENG-01-018,흡기호스 -104,ENG,엔진,2,배기,ENG-02-018,플렉시블조인트 -105,ENG,엔진,3,연료,ENG-03-018,연료레일 -106,ENG,엔진,4,냉각,ENG-04-018,라디에이터 -107,ENG,엔진,5,윤활,ENG-05-018,오일펌프 -108,ENG,엔진,6,점화,ENG-06-018,플러그와이어 -109,ENG,엔진,1,흡기,ENG-01-019,흡기호스 -110,ENG,엔진,2,배기,ENG-02-019,산소센서 -111,ENG,엔진,3,연료,ENG-03-019,고압펌프 -112,ENG,엔진,4,냉각,ENG-04-019,라디에이터 -113,ENG,엔진,5,윤활,ENG-05-019,오일펌프 -114,ENG,엔진,6,점화,ENG-06-019,플러그와이어 -115,ENG,엔진,1,흡기,ENG-01-020,인터쿨러 -116,ENG,엔진,2,배기,ENG-02-020,플렉시블조인트 -117,ENG,엔진,3,연료,ENG-03-020,고압펌프 -118,ENG,엔진,4,냉각,ENG-04-020,냉각팬모터 -119,ENG,엔진,5,윤활,ENG-05-020,오일팬 -120,ENG,엔진,6,점화,ENG-06-020,점화모듈 -121,ENG,엔진,1,흡기,ENG-01-021,인터쿨러 -122,ENG,엔진,2,배기,ENG-02-021,촉매변환기 -123,ENG,엔진,3,연료,ENG-03-021,연료펌프 -124,ENG,엔진,4,냉각,ENG-04-021,냉각팬모터 -125,ENG,엔진,5,윤활,ENG-05-021,오일팬 -126,ENG,엔진,6,점화,ENG-06-021,스파크플러그 -127,ENG,엔진,1,흡기,ENG-01-022,매스에어플로우센서 -128,ENG,엔진,2,배기,ENG-02-022,머플러 -129,ENG,엔진,3,연료,ENG-03-022,연료레일 -130,ENG,엔진,4,냉각,ENG-04-022,라디에이터 -131,ENG,엔진,5,윤활,ENG-05-022,오일필터 -132,ENG,엔진,6,점화,ENG-06-022,플러그와이어 -133,ENG,엔진,1,흡기,ENG-01-023,스로틀바디 -134,ENG,엔진,2,배기,ENG-02-023,산소센서 -135,ENG,엔진,3,연료,ENG-03-023,연료필터 -136,ENG,엔진,4,냉각,ENG-04-023,냉각팬모터 -137,ENG,엔진,5,윤활,ENG-05-023,오일게이지 -138,ENG,엔진,6,점화,ENG-06-023,점화모듈 -139,ENG,엔진,1,흡기,ENG-01-024,에어필터 -140,ENG,엔진,2,배기,ENG-02-024,배기관 -141,TRN,트랜스미션,1,기어셋,TRN-01-001,베어링세트 -142,TRN,트랜스미션,2,클러치,TRN-02-001,클러치마스터 -143,TRN,트랜스미션,3,윤활,TRN-03-001,오일씰 -144,TRN,트랜스미션,4,변속제어,TRN-04-001,셀렉터레버 -145,TRN,트랜스미션,1,기어셋,TRN-01-002,메인샤프트 -146,TRN,트랜스미션,2,클러치,TRN-02-002,클러치마스터 -147,TRN,트랜스미션,3,윤활,TRN-03-002,오일씰 -148,TRN,트랜스미션,4,변속제어,TRN-04-002,셀렉터레버 -149,TRN,트랜스미션,1,기어셋,TRN-01-003,피니언기어 -150,TRN,트랜스미션,2,클러치,TRN-02-003,클러치마스터 -151,TRN,트랜스미션,3,윤활,TRN-03-003,가스켓세트 -152,TRN,트랜스미션,4,변속제어,TRN-04-003,시프트케이블 -153,TRN,트랜스미션,1,기어셋,TRN-01-004,메인샤프트 -154,TRN,트랜스미션,2,클러치,TRN-02-004,클러치커버 -155,TRN,트랜스미션,3,윤활,TRN-03-004,ATF오일쿨러 -156,TRN,트랜스미션,4,변속제어,TRN-04-004,밸브바디 -157,TRN,트랜스미션,1,기어셋,TRN-01-005,메인샤프트 -158,TRN,트랜스미션,2,클러치,TRN-02-005,클러치커버 -159,TRN,트랜스미션,3,윤활,TRN-03-005,오일펌프(미션) -160,TRN,트랜스미션,4,변속제어,TRN-04-005,변속솔레노이드 -161,TRN,트랜스미션,1,기어셋,TRN-01-006,베어링세트 -162,TRN,트랜스미션,2,클러치,TRN-02-006,클러치슬레이브 -163,TRN,트랜스미션,3,윤활,TRN-03-006,오일펌프(미션) -164,TRN,트랜스미션,4,변속제어,TRN-04-006,셀렉터레버 -165,TRN,트랜스미션,1,기어셋,TRN-01-007,카운터샤프트 -166,TRN,트랜스미션,2,클러치,TRN-02-007,클러치디스크 -167,TRN,트랜스미션,3,윤활,TRN-03-007,오일펌프(미션) -168,TRN,트랜스미션,4,변속제어,TRN-04-007,시프트케이블 -169,TRN,트랜스미션,1,기어셋,TRN-01-008,동기링 -170,TRN,트랜스미션,2,클러치,TRN-02-008,릴리스베어링 -171,TRN,트랜스미션,3,윤활,TRN-03-008,오일씰 -172,TRN,트랜스미션,4,변속제어,TRN-04-008,포지션센서 -173,TRN,트랜스미션,1,기어셋,TRN-01-009,카운터샤프트 -174,TRN,트랜스미션,2,클러치,TRN-02-009,클러치커버 -175,TRN,트랜스미션,3,윤활,TRN-03-009,오일씰 -176,TRN,트랜스미션,4,변속제어,TRN-04-009,포지션센서 -177,TRN,트랜스미션,1,기어셋,TRN-01-010,피니언기어 -178,TRN,트랜스미션,2,클러치,TRN-02-010,클러치디스크 -179,TRN,트랜스미션,3,윤활,TRN-03-010,오일필터(미션) -180,TRN,트랜스미션,4,변속제어,TRN-04-010,포지션센서 -181,TRN,트랜스미션,1,기어셋,TRN-01-011,베어링세트 -182,TRN,트랜스미션,2,클러치,TRN-02-011,클러치마스터 -183,TRN,트랜스미션,3,윤활,TRN-03-011,오일필터(미션) -184,TRN,트랜스미션,4,변속제어,TRN-04-011,시프트케이블 -185,TRN,트랜스미션,1,기어셋,TRN-01-012,기어세트 -186,TRN,트랜스미션,2,클러치,TRN-02-012,클러치마스터 -187,TRN,트랜스미션,3,윤활,TRN-03-012,오일필터(미션) -188,TRN,트랜스미션,4,변속제어,TRN-04-012,변속솔레노이드 -189,TRN,트랜스미션,1,기어셋,TRN-01-013,메인샤프트 -190,TRN,트랜스미션,2,클러치,TRN-02-013,클러치디스크 -191,TRN,트랜스미션,3,윤활,TRN-03-013,오일펌프(미션) -192,TRN,트랜스미션,4,변속제어,TRN-04-013,시프트케이블 -193,TRN,트랜스미션,1,기어셋,TRN-01-014,메인샤프트 -194,TRN,트랜스미션,2,클러치,TRN-02-014,클러치디스크 -195,TRN,트랜스미션,3,윤활,TRN-03-014,가스켓세트 -196,TRN,트랜스미션,4,변속제어,TRN-04-014,포지션센서 -197,TRN,트랜스미션,1,기어셋,TRN-01-015,기어세트 -198,TRN,트랜스미션,2,클러치,TRN-02-015,클러치디스크 -199,TRN,트랜스미션,3,윤활,TRN-03-015,ATF오일쿨러 -200,TRN,트랜스미션,4,변속제어,TRN-04-015,포지션센서 -201,TRN,트랜스미션,1,기어셋,TRN-01-016,메인샤프트 -202,TRN,트랜스미션,2,클러치,TRN-02-016,클러치슬레이브 -203,TRN,트랜스미션,3,윤활,TRN-03-016,ATF오일쿨러 -204,TRN,트랜스미션,4,변속제어,TRN-04-016,셀렉터레버 -205,TRN,트랜스미션,1,기어셋,TRN-01-017,동기링 -206,TRN,트랜스미션,2,클러치,TRN-02-017,클러치디스크 -207,TRN,트랜스미션,3,윤활,TRN-03-017,ATF오일쿨러 -208,TRN,트랜스미션,4,변속제어,TRN-04-017,밸브바디 -209,TRN,트랜스미션,1,기어셋,TRN-01-018,동기링 -210,TRN,트랜스미션,2,클러치,TRN-02-018,클러치마스터 -211,TRN,트랜스미션,3,윤활,TRN-03-018,오일펌프(미션) -212,TRN,트랜스미션,4,변속제어,TRN-04-018,셀렉터레버 -213,TRN,트랜스미션,1,기어셋,TRN-01-019,카운터샤프트 -214,TRN,트랜스미션,2,클러치,TRN-02-019,클러치슬레이브 -215,TRN,트랜스미션,3,윤활,TRN-03-019,가스켓세트 -216,TRN,트랜스미션,4,변속제어,TRN-04-019,시프트케이블 -217,TRN,트랜스미션,1,기어셋,TRN-01-020,기어세트 -218,TRN,트랜스미션,2,클러치,TRN-02-020,클러치디스크 -219,TRN,트랜스미션,3,윤활,TRN-03-020,오일필터(미션) -220,TRN,트랜스미션,4,변속제어,TRN-04-020,시프트케이블 -221,TRN,트랜스미션,1,기어셋,TRN-01-021,베어링세트 -222,TRN,트랜스미션,2,클러치,TRN-02-021,클러치마스터 -223,TRN,트랜스미션,3,윤활,TRN-03-021,가스켓세트 -224,TRN,트랜스미션,4,변속제어,TRN-04-021,변속솔레노이드 -225,TRN,트랜스미션,1,기어셋,TRN-01-022,메인샤프트 -226,TRN,트랜스미션,2,클러치,TRN-02-022,클러치디스크 -227,TRN,트랜스미션,3,윤활,TRN-03-022,가스켓세트 -228,TRN,트랜스미션,4,변속제어,TRN-04-022,셀렉터레버 -229,TRN,트랜스미션,1,기어셋,TRN-01-023,베어링세트 -230,TRN,트랜스미션,2,클러치,TRN-02-023,클러치커버 -231,TRN,트랜스미션,3,윤활,TRN-03-023,오일씰 -232,TRN,트랜스미션,4,변속제어,TRN-04-023,변속솔레노이드 -233,TRN,트랜스미션,1,기어셋,TRN-01-024,메인샤프트 -234,TRN,트랜스미션,2,클러치,TRN-02-024,클러치슬레이브 -235,TRN,트랜스미션,3,윤활,TRN-03-024,가스켓세트 -236,TRN,트랜스미션,4,변속제어,TRN-04-024,밸브바디 -237,TRN,트랜스미션,1,기어셋,TRN-01-025,피니언기어 -238,TRN,트랜스미션,2,클러치,TRN-02-025,클러치슬레이브 -239,TRN,트랜스미션,3,윤활,TRN-03-025,ATF오일쿨러 -240,TRN,트랜스미션,4,변속제어,TRN-04-025,포지션센서 -241,CHS,샤시,1,서스펜션,CHS-01-001,로워암 -242,CHS,샤시,2,제동,CHS-02-001,브레이크패드 -243,CHS,샤시,3,조향,CHS-03-001,스티어링기어 -244,CHS,샤시,4,휠/허브,CHS-04-001,스페이서 -245,CHS,샤시,1,서스펜션,CHS-01-002,로워암 -246,CHS,샤시,2,제동,CHS-02-002,브레이크디스크 -247,CHS,샤시,3,조향,CHS-03-002,스티어링기어 -248,CHS,샤시,4,휠/허브,CHS-04-002,허브베어링 -249,CHS,샤시,1,서스펜션,CHS-01-003,부싱세트 -250,CHS,샤시,2,제동,CHS-02-003,브레이크마스터 -251,CHS,샤시,3,조향,CHS-03-003,아이들러암 -252,CHS,샤시,4,휠/허브,CHS-04-003,휠너트 -253,CHS,샤시,1,서스펜션,CHS-01-004,어퍼암 -254,CHS,샤시,2,제동,CHS-02-004,브레이크디스크 -255,CHS,샤시,3,조향,CHS-03-004,아이들러암 -256,CHS,샤시,4,휠/허브,CHS-04-004,허브베어링 -257,CHS,샤시,1,서스펜션,CHS-01-005,스프링 -258,CHS,샤시,2,제동,CHS-02-005,브레이크호스 -259,CHS,샤시,3,조향,CHS-03-005,랙앤피니언 -260,CHS,샤시,4,휠/허브,CHS-04-005,허브베어링 -261,CHS,샤시,1,서스펜션,CHS-01-006,부싱세트 -262,CHS,샤시,2,제동,CHS-02-006,브레이크호스 -263,CHS,샤시,3,조향,CHS-03-006,스티어링기어 -264,CHS,샤시,4,휠/허브,CHS-04-006,휠허브 -265,CHS,샤시,1,서스펜션,CHS-01-007,쇽업소버 -266,CHS,샤시,2,제동,CHS-02-007,브레이크캘리퍼 -267,CHS,샤시,3,조향,CHS-03-007,파워스티어링펌프 -268,CHS,샤시,4,휠/허브,CHS-04-007,휠너트 -269,CHS,샤시,1,서스펜션,CHS-01-008,스프링 -270,CHS,샤시,2,제동,CHS-02-008,브레이크마스터 -271,CHS,샤시,3,조향,CHS-03-008,스티어링기어 -272,CHS,샤시,4,휠/허브,CHS-04-008,허브캡 -273,CHS,샤시,1,서스펜션,CHS-01-009,어퍼암 -274,CHS,샤시,2,제동,CHS-02-009,브레이크캘리퍼 -275,CHS,샤시,3,조향,CHS-03-009,스티어링기어 -276,CHS,샤시,4,휠/허브,CHS-04-009,휠허브 -277,CHS,샤시,1,서스펜션,CHS-01-010,스프링 -278,CHS,샤시,2,제동,CHS-02-010,브레이크패드 -279,CHS,샤시,3,조향,CHS-03-010,랙앤피니언 -280,CHS,샤시,4,휠/허브,CHS-04-010,허브캡 -281,CHS,샤시,1,서스펜션,CHS-01-011,스프링 -282,CHS,샤시,2,제동,CHS-02-011,브레이크캘리퍼 -283,CHS,샤시,3,조향,CHS-03-011,랙앤피니언 -284,CHS,샤시,4,휠/허브,CHS-04-011,허브캡 -285,CHS,샤시,1,서스펜션,CHS-01-012,부싱세트 -286,CHS,샤시,2,제동,CHS-02-012,브레이크마스터 -287,CHS,샤시,3,조향,CHS-03-012,타이로드엔드 -288,CHS,샤시,4,휠/허브,CHS-04-012,허브캡 -289,CHS,샤시,1,서스펜션,CHS-01-013,어퍼암 -290,CHS,샤시,2,제동,CHS-02-013,브레이크캘리퍼 -291,CHS,샤시,3,조향,CHS-03-013,타이로드엔드 -292,CHS,샤시,4,휠/허브,CHS-04-013,휠허브 -293,CHS,샤시,1,서스펜션,CHS-01-014,스태빌라이저링크 -294,CHS,샤시,2,제동,CHS-02-014,브레이크디스크 -295,CHS,샤시,3,조향,CHS-03-014,파워스티어링펌프 -296,CHS,샤시,4,휠/허브,CHS-04-014,허브베어링 -297,CHS,샤시,1,서스펜션,CHS-01-015,스태빌라이저링크 -298,CHS,샤시,2,제동,CHS-02-015,브레이크캘리퍼 -299,CHS,샤시,3,조향,CHS-03-015,타이로드엔드 -300,CHS,샤시,4,휠/허브,CHS-04-015,허브캡 -301,CHS,샤시,1,서스펜션,CHS-01-016,스태빌라이저링크 -302,CHS,샤시,2,제동,CHS-02-016,브레이크호스 -303,CHS,샤시,3,조향,CHS-03-016,타이로드엔드 -304,CHS,샤시,4,휠/허브,CHS-04-016,허브베어링 -305,CHS,샤시,1,서스펜션,CHS-01-017,스프링 -306,CHS,샤시,2,제동,CHS-02-017,브레이크디스크 -307,CHS,샤시,3,조향,CHS-03-017,타이로드엔드 -308,CHS,샤시,4,휠/허브,CHS-04-017,허브베어링 -309,CHS,샤시,1,서스펜션,CHS-01-018,부싱세트 -310,CHS,샤시,2,제동,CHS-02-018,브레이크호스 -311,CHS,샤시,3,조향,CHS-03-018,랙앤피니언 -312,CHS,샤시,4,휠/허브,CHS-04-018,스페이서 -313,CHS,샤시,1,서스펜션,CHS-01-019,부싱세트 -314,CHS,샤시,2,제동,CHS-02-019,브레이크호스 -315,CHS,샤시,3,조향,CHS-03-019,스티어링기어 -316,CHS,샤시,4,휠/허브,CHS-04-019,허브베어링 -317,CHS,샤시,1,서스펜션,CHS-01-020,부싱세트 -318,CHS,샤시,2,제동,CHS-02-020,브레이크마스터 -319,CHS,샤시,3,조향,CHS-03-020,랙앤피니언 -320,CHS,샤시,4,휠/허브,CHS-04-020,휠허브 -321,CHS,샤시,1,서스펜션,CHS-01-021,쇽업소버 -322,CHS,샤시,2,제동,CHS-02-021,브레이크패드 -323,CHS,샤시,3,조향,CHS-03-021,아이들러암 -324,CHS,샤시,4,휠/허브,CHS-04-021,허브베어링 -325,CHS,샤시,1,서스펜션,CHS-01-022,스태빌라이저링크 -326,CHS,샤시,2,제동,CHS-02-022,브레이크디스크 -327,CHS,샤시,3,조향,CHS-03-022,랙앤피니언 -328,CHS,샤시,4,휠/허브,CHS-04-022,휠허브 -329,CHS,샤시,1,서스펜션,CHS-01-023,로워암 -330,CHS,샤시,2,제동,CHS-02-023,브레이크디스크 -331,CHS,샤시,3,조향,CHS-03-023,스티어링기어 -332,CHS,샤시,4,휠/허브,CHS-04-023,스페이서 -333,CHS,샤시,1,서스펜션,CHS-01-024,스프링 -334,CHS,샤시,2,제동,CHS-02-024,브레이크마스터 -335,CHS,샤시,3,조향,CHS-03-024,스티어링기어 -336,CHS,샤시,4,휠/허브,CHS-04-024,휠너트 -337,CHS,샤시,1,서스펜션,CHS-01-025,부싱세트 -338,CHS,샤시,2,제동,CHS-02-025,브레이크호스 -339,CHS,샤시,3,조향,CHS-03-025,랙앤피니언 -340,CHS,샤시,4,휠/허브,CHS-04-025,휠허브 -341,CHS,샤시,1,서스펜션,CHS-01-026,어퍼암 -342,CHS,샤시,2,제동,CHS-02-026,브레이크캘리퍼 -343,CHS,샤시,3,조향,CHS-03-026,파워스티어링펌프 -344,CHS,샤시,4,휠/허브,CHS-04-026,스페이서 -345,CHS,샤시,1,서스펜션,CHS-01-027,부싱세트 -346,CHS,샤시,2,제동,CHS-02-027,브레이크호스 -347,CHS,샤시,3,조향,CHS-03-027,아이들러암 -348,CHS,샤시,4,휠/허브,CHS-04-027,허브베어링 -349,CHS,샤시,1,서스펜션,CHS-01-028,부싱세트 -350,CHS,샤시,2,제동,CHS-02-028,브레이크디스크 -351,CHS,샤시,3,조향,CHS-03-028,아이들러암 -352,CHS,샤시,4,휠/허브,CHS-04-028,스페이서 -353,CHS,샤시,1,서스펜션,CHS-01-029,쇽업소버 -354,CHS,샤시,2,제동,CHS-02-029,브레이크호스 -355,CHS,샤시,3,조향,CHS-03-029,랙앤피니언 -356,CHS,샤시,4,휠/허브,CHS-04-029,스페이서 -357,CHS,샤시,1,서스펜션,CHS-01-030,쇽업소버 -358,CHS,샤시,2,제동,CHS-02-030,브레이크디스크 -359,CHS,샤시,3,조향,CHS-03-030,랙앤피니언 -360,CHS,샤시,4,휠/허브,CHS-04-030,허브베어링 -361,BDY,바디,1,외판,BDY-01-001,트렁크리드 -362,BDY,바디,2,범퍼/그릴,BDY-02-001,토우아이커버 -363,BDY,바디,3,도어/윈도우,BDY-03-001,도어핸들 -364,BDY,바디,4,유리,BDY-04-001,와이퍼암 -365,BDY,바디,1,외판,BDY-01-002,후드패널 -366,BDY,바디,2,범퍼/그릴,BDY-02-002,라디에이터그릴 -367,BDY,바디,3,도어/윈도우,BDY-03-002,도어힌지 -368,BDY,바디,4,유리,BDY-04-002,와이퍼암 -369,BDY,바디,1,외판,BDY-01-003,도어패널 -370,BDY,바디,2,범퍼/그릴,BDY-02-003,범퍼보강빔 -371,BDY,바디,3,도어/윈도우,BDY-03-003,도어핸들 -372,BDY,바디,4,유리,BDY-04-003,와이퍼암 -373,BDY,바디,1,외판,BDY-01-004,후드패널 -374,BDY,바디,2,범퍼/그릴,BDY-02-004,리어범퍼 -375,BDY,바디,3,도어/윈도우,BDY-03-004,윈도우레귤레이터 -376,BDY,바디,4,유리,BDY-04-004,리어글래스 -377,BDY,바디,1,외판,BDY-01-005,후드패널 -378,BDY,바디,2,범퍼/그릴,BDY-02-005,프론트범퍼 -379,BDY,바디,3,도어/윈도우,BDY-03-005,도어힌지 -380,BDY,바디,4,유리,BDY-04-005,글래스몰딩 -381,BDY,바디,1,외판,BDY-01-006,도어패널 -382,BDY,바디,2,범퍼/그릴,BDY-02-006,프론트범퍼 -383,BDY,바디,3,도어/윈도우,BDY-03-006,도어핸들 -384,BDY,바디,4,유리,BDY-04-006,글래스몰딩 -385,BDY,바디,1,외판,BDY-01-007,리어펜더 -386,BDY,바디,2,범퍼/그릴,BDY-02-007,토우아이커버 -387,BDY,바디,3,도어/윈도우,BDY-03-007,도어힌지 -388,BDY,바디,4,유리,BDY-04-007,와이퍼암 -389,BDY,바디,1,외판,BDY-01-008,도어패널 -390,BDY,바디,2,범퍼/그릴,BDY-02-008,리어범퍼 -391,BDY,바디,3,도어/윈도우,BDY-03-008,윈도우모터 -392,BDY,바디,4,유리,BDY-04-008,글래스몰딩 -393,BDY,바디,1,외판,BDY-01-009,도어패널 -394,BDY,바디,2,범퍼/그릴,BDY-02-009,토우아이커버 -395,BDY,바디,3,도어/윈도우,BDY-03-009,웨더스트립 -396,BDY,바디,4,유리,BDY-04-009,와이퍼암 -397,BDY,바디,1,외판,BDY-01-010,프론트펜더 -398,BDY,바디,2,범퍼/그릴,BDY-02-010,포그램프커버 -399,BDY,바디,3,도어/윈도우,BDY-03-010,도어힌지 -400,BDY,바디,4,유리,BDY-04-010,리어글래스 -401,BDY,바디,1,외판,BDY-01-011,도어패널 -402,BDY,바디,2,범퍼/그릴,BDY-02-011,리어범퍼 -403,BDY,바디,3,도어/윈도우,BDY-03-011,웨더스트립 -404,BDY,바디,4,유리,BDY-04-011,사이드글래스 -405,BDY,바디,1,외판,BDY-01-012,트렁크리드 -406,BDY,바디,2,범퍼/그릴,BDY-02-012,프론트범퍼 -407,BDY,바디,3,도어/윈도우,BDY-03-012,웨더스트립 -408,BDY,바디,4,유리,BDY-04-012,글래스몰딩 -409,BDY,바디,1,외판,BDY-01-013,리어펜더 -410,BDY,바디,2,범퍼/그릴,BDY-02-013,프론트범퍼 -411,BDY,바디,3,도어/윈도우,BDY-03-013,윈도우레귤레이터 -412,BDY,바디,4,유리,BDY-04-013,글래스몰딩 -413,BDY,바디,1,외판,BDY-01-014,후드패널 -414,BDY,바디,2,범퍼/그릴,BDY-02-014,리어범퍼 -415,BDY,바디,3,도어/윈도우,BDY-03-014,윈도우모터 -416,BDY,바디,4,유리,BDY-04-014,윈드실드글래스 -417,BDY,바디,1,외판,BDY-01-015,프론트펜더 -418,BDY,바디,2,범퍼/그릴,BDY-02-015,포그램프커버 -419,BDY,바디,3,도어/윈도우,BDY-03-015,윈도우모터 -420,BDY,바디,4,유리,BDY-04-015,사이드글래스 -421,BDY,바디,1,외판,BDY-01-016,리어펜더 -422,BDY,바디,2,범퍼/그릴,BDY-02-016,리어범퍼 -423,BDY,바디,3,도어/윈도우,BDY-03-016,웨더스트립 -424,BDY,바디,4,유리,BDY-04-016,사이드글래스 -425,BDY,바디,1,외판,BDY-01-017,사이드실패널 -426,BDY,바디,2,범퍼/그릴,BDY-02-017,프론트범퍼 -427,BDY,바디,3,도어/윈도우,BDY-03-017,웨더스트립 -428,BDY,바디,4,유리,BDY-04-017,글래스몰딩 -429,BDY,바디,1,외판,BDY-01-018,프론트펜더 -430,BDY,바디,2,범퍼/그릴,BDY-02-018,포그램프커버 -431,BDY,바디,3,도어/윈도우,BDY-03-018,윈도우레귤레이터 -432,BDY,바디,4,유리,BDY-04-018,사이드글래스 -433,BDY,바디,1,외판,BDY-01-019,사이드실패널 -434,BDY,바디,2,범퍼/그릴,BDY-02-019,범퍼보강빔 -435,BDY,바디,3,도어/윈도우,BDY-03-019,도어힌지 -436,BDY,바디,4,유리,BDY-04-019,글래스몰딩 -437,BDY,바디,1,외판,BDY-01-020,리어펜더 -438,BDY,바디,2,범퍼/그릴,BDY-02-020,범퍼보강빔 -439,BDY,바디,3,도어/윈도우,BDY-03-020,윈도우레귤레이터 -440,BDY,바디,4,유리,BDY-04-020,리어글래스 -441,BDY,바디,1,외판,BDY-01-021,리어펜더 -442,BDY,바디,2,범퍼/그릴,BDY-02-021,프론트범퍼 -443,BDY,바디,3,도어/윈도우,BDY-03-021,윈도우모터 -444,BDY,바디,4,유리,BDY-04-021,윈드실드글래스 -445,BDY,바디,1,외판,BDY-01-022,리어펜더 -446,BDY,바디,2,범퍼/그릴,BDY-02-022,토우아이커버 -447,BDY,바디,3,도어/윈도우,BDY-03-022,웨더스트립 -448,BDY,바디,4,유리,BDY-04-022,글래스몰딩 -449,BDY,바디,1,외판,BDY-01-023,사이드실패널 -450,BDY,바디,2,범퍼/그릴,BDY-02-023,프론트범퍼 -451,BDY,바디,3,도어/윈도우,BDY-03-023,웨더스트립 -452,BDY,바디,4,유리,BDY-04-023,리어글래스 -453,BDY,바디,1,외판,BDY-01-024,도어패널 -454,BDY,바디,2,범퍼/그릴,BDY-02-024,토우아이커버 -455,BDY,바디,3,도어/윈도우,BDY-03-024,윈도우모터 -456,BDY,바디,4,유리,BDY-04-024,와이퍼암 -457,BDY,바디,1,외판,BDY-01-025,후드패널 -458,BDY,바디,2,범퍼/그릴,BDY-02-025,프론트범퍼 -459,BDY,바디,3,도어/윈도우,BDY-03-025,윈도우레귤레이터 -460,BDY,바디,4,유리,BDY-04-025,윈드실드글래스 -461,TRM,트림,1,시트,TRM-01-001,시트프레임 -462,TRM,트림,2,내장재,TRM-02-001,도어트림 -463,TRM,트림,3,소음진동,TRM-03-001,플로어매트 -464,TRM,트림,4,익스테리어트림,TRM-04-001,사이드몰딩 -465,TRM,트림,1,시트,TRM-01-002,시트쿠션 -466,TRM,트림,2,내장재,TRM-02-002,도어트림 -467,TRM,트림,3,소음진동,TRM-03-002,방진패드 -468,TRM,트림,4,익스테리어트림,TRM-04-002,엠블럼 -469,TRM,트림,1,시트,TRM-01-003,시트트랙 -470,TRM,트림,2,내장재,TRM-02-003,필러트림 -471,TRM,트림,3,소음진동,TRM-03-003,방진패드 -472,TRM,트림,4,익스테리어트림,TRM-04-003,머드가드 -473,TRM,트림,1,시트,TRM-01-004,헤드레스트 -474,TRM,트림,2,내장재,TRM-02-004,루프라이너 -475,TRM,트림,3,소음진동,TRM-03-004,트렁크매트 -476,TRM,트림,4,익스테리어트림,TRM-04-004,스포일러 -477,TRM,트림,1,시트,TRM-01-005,시트프레임 -478,TRM,트림,2,내장재,TRM-02-005,도어트림 -479,TRM,트림,3,소음진동,TRM-03-005,흡음재 -480,TRM,트림,4,익스테리어트림,TRM-04-005,크롬가니쉬 -481,TRM,트림,1,시트,TRM-01-006,시트레버 -482,TRM,트림,2,내장재,TRM-02-006,대시보드패널 -483,TRM,트림,3,소음진동,TRM-03-006,플로어매트 -484,TRM,트림,4,익스테리어트림,TRM-04-006,사이드몰딩 -485,TRM,트림,1,시트,TRM-01-007,시트프레임 -486,TRM,트림,2,내장재,TRM-02-007,도어트림 -487,TRM,트림,3,소음진동,TRM-03-007,흡음재 -488,TRM,트림,4,익스테리어트림,TRM-04-007,머드가드 -489,TRM,트림,1,시트,TRM-01-008,시트쿠션 -490,TRM,트림,2,내장재,TRM-02-008,대시보드패널 -491,TRM,트림,3,소음진동,TRM-03-008,플로어매트 -492,TRM,트림,4,익스테리어트림,TRM-04-008,사이드몰딩 -493,TRM,트림,1,시트,TRM-01-009,시트레버 -494,TRM,트림,2,내장재,TRM-02-009,대시보드패널 -495,TRM,트림,3,소음진동,TRM-03-009,플로어매트 -496,TRM,트림,4,익스테리어트림,TRM-04-009,머드가드 -497,TRM,트림,1,시트,TRM-01-010,시트레버 -498,TRM,트림,2,내장재,TRM-02-010,도어트림 -499,TRM,트림,3,소음진동,TRM-03-010,방진매트 -500,TRM,트림,4,익스테리어트림,TRM-04-010,크롬가니쉬 -501,TRM,트림,1,시트,TRM-01-011,시트프레임 -502,TRM,트림,2,내장재,TRM-02-011,루프라이너 -503,TRM,트림,3,소음진동,TRM-03-011,방진패드 -504,TRM,트림,4,익스테리어트림,TRM-04-011,사이드몰딩 -505,TRM,트림,1,시트,TRM-01-012,시트쿠션 -506,TRM,트림,2,내장재,TRM-02-012,도어트림 -507,TRM,트림,3,소음진동,TRM-03-012,흡음재 -508,TRM,트림,4,익스테리어트림,TRM-04-012,크롬가니쉬 -509,TRM,트림,1,시트,TRM-01-013,시트쿠션 -510,TRM,트림,2,내장재,TRM-02-013,도어트림 -511,TRM,트림,3,소음진동,TRM-03-013,플로어매트 -512,TRM,트림,4,익스테리어트림,TRM-04-013,머드가드 -513,TRM,트림,1,시트,TRM-01-014,시트쿠션 -514,TRM,트림,2,내장재,TRM-02-014,도어트림 -515,TRM,트림,3,소음진동,TRM-03-014,트렁크매트 -516,TRM,트림,4,익스테리어트림,TRM-04-014,머드가드 -517,TRM,트림,1,시트,TRM-01-015,시트쿠션 -518,TRM,트림,2,내장재,TRM-02-015,도어트림 -519,TRM,트림,3,소음진동,TRM-03-015,플로어매트 -520,TRM,트림,4,익스테리어트림,TRM-04-015,사이드몰딩 -521,TRM,트림,1,시트,TRM-01-016,시트트랙 -522,TRM,트림,2,내장재,TRM-02-016,대시보드패널 -523,TRM,트림,3,소음진동,TRM-03-016,흡음재 -524,TRM,트림,4,익스테리어트림,TRM-04-016,사이드몰딩 -525,TRM,트림,1,시트,TRM-01-017,헤드레스트 -526,TRM,트림,2,내장재,TRM-02-017,루프라이너 -527,TRM,트림,3,소음진동,TRM-03-017,방진패드 -528,TRM,트림,4,익스테리어트림,TRM-04-017,머드가드 -529,TRM,트림,1,시트,TRM-01-018,시트레버 -530,TRM,트림,2,내장재,TRM-02-018,콘솔박스 -531,TRM,트림,3,소음진동,TRM-03-018,트렁크매트 -532,TRM,트림,4,익스테리어트림,TRM-04-018,사이드몰딩 -533,TRM,트림,1,시트,TRM-01-019,시트레버 -534,TRM,트림,2,내장재,TRM-02-019,카펫트림 -535,TRM,트림,3,소음진동,TRM-03-019,트렁크매트 -536,TRM,트림,4,익스테리어트림,TRM-04-019,머드가드 -537,TRM,트림,1,시트,TRM-01-020,시트레버 -538,TRM,트림,2,내장재,TRM-02-020,루프라이너 -539,TRM,트림,3,소음진동,TRM-03-020,플로어매트 -540,TRM,트림,4,익스테리어트림,TRM-04-020,크롬가니쉬 -541,ELE,일렉트릭,1,전원,ELE-01-001,알터네이터 -542,ELE,일렉트릭,2,제어,ELE-02-001,퓨즈세트 -543,ELE,일렉트릭,3,조명,ELE-03-001,테일램프 -544,ELE,일렉트릭,4,배선,ELE-04-001,배선클립 -545,ELE,일렉트릭,1,전원,ELE-01-002,퓨즈박스 -546,ELE,일렉트릭,2,제어,ELE-02-002,퓨즈세트 -547,ELE,일렉트릭,3,조명,ELE-03-002,헤드램프 -548,ELE,일렉트릭,4,배선,ELE-04-002,배선클립 -549,ELE,일렉트릭,1,전원,ELE-01-003,배터리 -550,ELE,일렉트릭,2,제어,ELE-02-003,ECU -551,ELE,일렉트릭,3,조명,ELE-03-003,턴시그널램프 -552,ELE,일렉트릭,4,배선,ELE-04-003,케이블타이 -553,ELE,일렉트릭,1,전원,ELE-01-004,퓨즈박스 -554,ELE,일렉트릭,2,제어,ELE-02-004,릴레이 -555,ELE,일렉트릭,3,조명,ELE-03-004,헤드램프 -556,ELE,일렉트릭,4,배선,ELE-04-004,와이어하네스 -557,ELE,일렉트릭,1,전원,ELE-01-005,전압레귤레이터 -558,ELE,일렉트릭,2,제어,ELE-02-005,스로틀포지션센서 -559,ELE,일렉트릭,3,조명,ELE-03-005,실내등 -560,ELE,일렉트릭,4,배선,ELE-04-005,분배커넥터 -561,ELE,일렉트릭,1,전원,ELE-01-006,전압레귤레이터 -562,ELE,일렉트릭,2,제어,ELE-02-006,퓨즈세트 -563,ELE,일렉트릭,3,조명,ELE-03-006,브레이크등 -564,ELE,일렉트릭,4,배선,ELE-04-006,배선클립 -565,ELE,일렉트릭,1,전원,ELE-01-007,전압레귤레이터 -566,ELE,일렉트릭,2,제어,ELE-02-007,퓨즈세트 -567,ELE,일렉트릭,3,조명,ELE-03-007,헤드램프 -568,ELE,일렉트릭,4,배선,ELE-04-007,전선가이드 -569,ELE,일렉트릭,1,전원,ELE-01-008,알터네이터 -570,ELE,일렉트릭,2,제어,ELE-02-008,릴레이 -571,ELE,일렉트릭,3,조명,ELE-03-008,안개등 -572,ELE,일렉트릭,4,배선,ELE-04-008,전선가이드 -573,ELE,일렉트릭,1,전원,ELE-01-009,배터리 -574,ELE,일렉트릭,2,제어,ELE-02-009,퓨즈세트 -575,ELE,일렉트릭,3,조명,ELE-03-009,안개등 -576,ELE,일렉트릭,4,배선,ELE-04-009,케이블타이 -577,ELE,일렉트릭,1,전원,ELE-01-010,접지스트랩 -578,ELE,일렉트릭,2,제어,ELE-02-010,퓨즈세트 -579,ELE,일렉트릭,3,조명,ELE-03-010,테일램프 -580,ELE,일렉트릭,4,배선,ELE-04-010,와이어하네스 -581,ELE,일렉트릭,1,전원,ELE-01-011,시동모터 -582,ELE,일렉트릭,2,제어,ELE-02-011,릴레이 -583,ELE,일렉트릭,3,조명,ELE-03-011,안개등 -584,ELE,일렉트릭,4,배선,ELE-04-011,배선클립 -585,ELE,일렉트릭,1,전원,ELE-01-012,배터리 -586,ELE,일렉트릭,2,제어,ELE-02-012,퓨즈세트 -587,ELE,일렉트릭,3,조명,ELE-03-012,실내등 -588,ELE,일렉트릭,4,배선,ELE-04-012,와이어하네스 -589,ELE,일렉트릭,1,전원,ELE-01-013,퓨즈박스 -590,ELE,일렉트릭,2,제어,ELE-02-013,퓨즈세트 -591,ELE,일렉트릭,3,조명,ELE-03-013,브레이크등 -592,ELE,일렉트릭,4,배선,ELE-04-013,배선클립 -593,ELE,일렉트릭,1,전원,ELE-01-014,알터네이터 -594,ELE,일렉트릭,2,제어,ELE-02-014,노크센서 -595,ELE,일렉트릭,3,조명,ELE-03-014,헤드램프 -596,ELE,일렉트릭,4,배선,ELE-04-014,와이어하네스 -597,ELE,일렉트릭,1,전원,ELE-01-015,시동모터 -598,ELE,일렉트릭,2,제어,ELE-02-015,스로틀포지션센서 -599,ELE,일렉트릭,3,조명,ELE-03-015,헤드램프 -600,ELE,일렉트릭,4,배선,ELE-04-015,배선클립 -601,ELE,일렉트릭,1,전원,ELE-01-016,퓨즈박스 -602,ELE,일렉트릭,2,제어,ELE-02-016,노크센서 -603,ELE,일렉트릭,3,조명,ELE-03-016,헤드램프 -604,ELE,일렉트릭,4,배선,ELE-04-016,전선가이드 -605,ELE,일렉트릭,1,전원,ELE-01-017,배터리 -606,ELE,일렉트릭,2,제어,ELE-02-017,퓨즈세트 -607,ELE,일렉트릭,3,조명,ELE-03-017,안개등 -608,ELE,일렉트릭,4,배선,ELE-04-017,배선클립 -609,ELE,일렉트릭,1,전원,ELE-01-018,배터리 -610,ELE,일렉트릭,2,제어,ELE-02-018,노크센서 -611,ELE,일렉트릭,3,조명,ELE-03-018,브레이크등 -612,ELE,일렉트릭,4,배선,ELE-04-018,배선클립 -613,ELE,일렉트릭,1,전원,ELE-01-019,전압레귤레이터 -614,ELE,일렉트릭,2,제어,ELE-02-019,맵센서 -615,ELE,일렉트릭,3,조명,ELE-03-019,브레이크등 -616,ELE,일렉트릭,4,배선,ELE-04-019,전선가이드 -617,ELE,일렉트릭,1,전원,ELE-01-020,시동모터 -618,ELE,일렉트릭,2,제어,ELE-02-020,맵센서 -619,ELE,일렉트릭,3,조명,ELE-03-020,실내등 -620,ELE,일렉트릭,4,배선,ELE-04-020,배선클립 -621,ELE,일렉트릭,1,전원,ELE-01-021,시동모터 -622,ELE,일렉트릭,2,제어,ELE-02-021,맵센서 -623,ELE,일렉트릭,3,조명,ELE-03-021,브레이크등 -624,ELE,일렉트릭,4,배선,ELE-04-021,배선클립 -625,ELE,일렉트릭,1,전원,ELE-01-022,배터리 -626,ELE,일렉트릭,2,제어,ELE-02-022,스로틀포지션센서 -627,ELE,일렉트릭,3,조명,ELE-03-022,턴시그널램프 -628,ELE,일렉트릭,4,배선,ELE-04-022,분배커넥터 -629,ELE,일렉트릭,1,전원,ELE-01-023,전압레귤레이터 -630,ELE,일렉트릭,2,제어,ELE-02-023,온도센서 -631,ELE,일렉트릭,3,조명,ELE-03-023,턴시그널램프 -632,ELE,일렉트릭,4,배선,ELE-04-023,분배커넥터 -633,ELE,일렉트릭,1,전원,ELE-01-024,알터네이터 -634,ELE,일렉트릭,2,제어,ELE-02-024,노크센서 -635,ELE,일렉트릭,3,조명,ELE-03-024,브레이크등 -636,ELE,일렉트릭,4,배선,ELE-04-024,분배커넥터 -637,ELE,일렉트릭,1,전원,ELE-01-025,배터리 -638,ELE,일렉트릭,2,제어,ELE-02-025,스로틀포지션센서 -639,ELE,일렉트릭,3,조명,ELE-03-025,턴시그널램프 -640,ELE,일렉트릭,4,배선,ELE-04-025,분배커넥터 -641,ELE,일렉트릭,1,전원,ELE-01-026,전압레귤레이터 -642,ELE,일렉트릭,2,제어,ELE-02-026,맵센서 -643,ELE,일렉트릭,3,조명,ELE-03-026,턴시그널램프 -644,ELE,일렉트릭,4,배선,ELE-04-026,배선클립 -645,ELE,일렉트릭,1,전원,ELE-01-027,배터리 -646,ELE,일렉트릭,2,제어,ELE-02-027,ECU -647,ELE,일렉트릭,3,조명,ELE-03-027,헤드램프 -648,ELE,일렉트릭,4,배선,ELE-04-027,배선클립 -649,ELE,일렉트릭,1,전원,ELE-01-028,전압레귤레이터 -650,ELE,일렉트릭,2,제어,ELE-02-028,퓨즈세트 -651,ELE,일렉트릭,3,조명,ELE-03-028,헤드램프 -652,ELE,일렉트릭,4,배선,ELE-04-028,전선가이드 -653,ELE,일렉트릭,1,전원,ELE-01-029,퓨즈박스 -654,ELE,일렉트릭,2,제어,ELE-02-029,노크센서 -655,ELE,일렉트릭,3,조명,ELE-03-029,헤드램프 -656,ELE,일렉트릭,4,배선,ELE-04-029,와이어하네스 -657,ELE,일렉트릭,1,전원,ELE-01-030,전압레귤레이터 -658,ELE,일렉트릭,2,제어,ELE-02-030,노크센서 -659,ELE,일렉트릭,3,조명,ELE-03-030,턴시그널램프 -660,ELE,일렉트릭,4,배선,ELE-04-030,배선클립 -661,CON,커넥터,1,단자,CON-01-001,러그단자 -662,CON,커넥터,2,하우징,CON-02-001,락레버 -663,CON,커넥터,3,실링/고정,CON-03-001,실링플러그 -664,CON,커넥터,4,케이블/클립,CON-04-001,하네스클립 -665,CON,커넥터,1,단자,CON-01-002,핀터미널 -666,CON,커넥터,2,하우징,CON-02-002,커넥터하우징 -667,CON,커넥터,3,실링/고정,CON-03-002,실링플러그 -668,CON,커넥터,4,케이블/클립,CON-04-002,하네스클립 -669,CON,커넥터,1,단자,CON-01-003,링단자 -670,CON,커넥터,2,하우징,CON-02-003,커넥터하우징 -671,CON,커넥터,3,실링/고정,CON-03-003,리테이너 -672,CON,커넥터,4,케이블/클립,CON-04-003,와이어 -673,CON,커넥터,1,단자,CON-01-004,소켓터미널 -674,CON,커넥터,2,하우징,CON-02-004,하우징캡 -675,CON,커넥터,3,실링/고정,CON-03-004,방수캡 -676,CON,커넥터,4,케이블/클립,CON-04-004,하네스클립 -677,CON,커넥터,1,단자,CON-01-005,러그단자 -678,CON,커넥터,2,하우징,CON-02-005,커넥터하우징 -679,CON,커넥터,3,실링/고정,CON-03-005,방수캡 -680,CON,커넥터,4,케이블/클립,CON-04-005,케이블클립 -681,CON,커넥터,1,단자,CON-01-006,핀터미널 -682,CON,커넥터,2,하우징,CON-02-006,하우징캡 -683,CON,커넥터,3,실링/고정,CON-03-006,실링플러그 -684,CON,커넥터,4,케이블/클립,CON-04-006,와이어 -685,CON,커넥터,1,단자,CON-01-007,링단자 -686,CON,커넥터,2,하우징,CON-02-007,락레버 -687,CON,커넥터,3,실링/고정,CON-03-007,리테이너 -688,CON,커넥터,4,케이블/클립,CON-04-007,와이어 -689,CON,커넥터,1,단자,CON-01-008,핀터미널 -690,CON,커넥터,2,하우징,CON-02-008,터미널블록 -691,CON,커넥터,3,실링/고정,CON-03-008,실링플러그 -692,CON,커넥터,4,케이블/클립,CON-04-008,하네스클립 -693,CON,커넥터,1,단자,CON-01-009,링단자 -694,CON,커넥터,2,하우징,CON-02-009,커넥터하우징 -695,CON,커넥터,3,실링/고정,CON-03-009,고무씰 -696,CON,커넥터,4,케이블/클립,CON-04-009,하네스클립 -697,CON,커넥터,1,단자,CON-01-010,링단자 -698,CON,커넥터,2,하우징,CON-02-010,락레버 -699,CON,커넥터,3,실링/고정,CON-03-010,방수캡 -700,CON,커넥터,4,케이블/클립,CON-04-010,하네스클립 +id,category_code,category_name,group_code,group_name,part_no,name +1,ENG,엔진,1,흡기,ENG-01-001,인테이크 매니폴드 +2,ENG,엔진,2,배기,ENG-02-001,배기관 +3,ENG,엔진,3,연료,ENG-03-001,연료레일 +4,ENG,엔진,4,냉각,ENG-04-001,리저버탱크 +5,ENG,엔진,5,윤활,ENG-05-001,오일펌프 +6,ENG,엔진,6,점화,ENG-06-001,점화코일 +7,ENG,엔진,1,흡기,ENG-01-002,매스에어플로우센서 +8,ENG,엔진,2,배기,ENG-02-002,머플러 +9,ENG,엔진,3,연료,ENG-03-002,인젝터 +10,ENG,엔진,4,냉각,ENG-04-002,라디에이터호스 +11,ENG,엔진,5,윤활,ENG-05-002,오일펌프 +12,ENG,엔진,6,점화,ENG-06-002,이그니션스위치 +13,ENG,엔진,1,흡기,ENG-01-003,스로틀바디 +14,ENG,엔진,2,배기,ENG-02-003,머플러 +15,ENG,엔진,3,연료,ENG-03-003,연료펌프 +16,ENG,엔진,4,냉각,ENG-04-003,냉각팬모터 +17,ENG,엔진,5,윤활,ENG-05-003,오일게이지 +18,ENG,엔진,6,점화,ENG-06-003,점화코일 +19,ENG,엔진,1,흡기,ENG-01-004,스로틀바디 +20,ENG,엔진,2,배기,ENG-02-004,머플러 +21,ENG,엔진,3,연료,ENG-03-004,연료차단밸브 +22,ENG,엔진,4,냉각,ENG-04-004,냉각팬모터 +23,ENG,엔진,5,윤활,ENG-05-004,오일펌프 +24,ENG,엔진,6,점화,ENG-06-004,이그니션스위치 +25,ENG,엔진,1,흡기,ENG-01-005,에어필터 +26,ENG,엔진,2,배기,ENG-02-005,배기관 +27,ENG,엔진,3,연료,ENG-03-005,고압펌프 +28,ENG,엔진,4,냉각,ENG-04-005,리저버탱크 +29,ENG,엔진,5,윤활,ENG-05-005,오일쿨러 +30,ENG,엔진,6,점화,ENG-06-005,점화코일 +31,ENG,엔진,1,흡기,ENG-01-006,매스에어플로우센서 +32,ENG,엔진,2,배기,ENG-02-006,플렉시블조인트 +33,ENG,엔진,3,연료,ENG-03-006,연료레일 +34,ENG,엔진,4,냉각,ENG-04-006,라디에이터 +35,ENG,엔진,5,윤활,ENG-05-006,오일필터 +36,ENG,엔진,6,점화,ENG-06-006,점화코일 +37,ENG,엔진,1,흡기,ENG-01-007,매스에어플로우센서 +38,ENG,엔진,2,배기,ENG-02-007,배기관 +39,ENG,엔진,3,연료,ENG-03-007,인젝터 +40,ENG,엔진,4,냉각,ENG-04-007,냉각팬모터 +41,ENG,엔진,5,윤활,ENG-05-007,오일필터 +42,ENG,엔진,6,점화,ENG-06-007,이그니션스위치 +43,ENG,엔진,1,흡기,ENG-01-008,에어필터 +44,ENG,엔진,2,배기,ENG-02-008,플렉시블조인트 +45,ENG,엔진,3,연료,ENG-03-008,인젝터 +46,ENG,엔진,4,냉각,ENG-04-008,라디에이터호스 +47,ENG,엔진,5,윤활,ENG-05-008,오일필터 +48,ENG,엔진,6,점화,ENG-06-008,점화코일 +49,ENG,엔진,1,흡기,ENG-01-009,매스에어플로우센서 +50,ENG,엔진,2,배기,ENG-02-009,플렉시블조인트 +51,ENG,엔진,3,연료,ENG-03-009,고압펌프 +52,ENG,엔진,4,냉각,ENG-04-009,워터펌프 +53,ENG,엔진,5,윤활,ENG-05-009,오일팬 +54,ENG,엔진,6,점화,ENG-06-009,점화코일 +55,ENG,엔진,1,흡기,ENG-01-010,매스에어플로우센서 +56,ENG,엔진,2,배기,ENG-02-010,산소센서 +57,ENG,엔진,3,연료,ENG-03-010,연료펌프 +58,ENG,엔진,4,냉각,ENG-04-010,라디에이터호스 +59,ENG,엔진,5,윤활,ENG-05-010,오일펌프 +60,ENG,엔진,6,점화,ENG-06-010,이그니션스위치 +61,ENG,엔진,1,흡기,ENG-01-011,스로틀바디 +62,ENG,엔진,2,배기,ENG-02-011,가스켓 +63,ENG,엔진,3,연료,ENG-03-011,고압펌프 +64,ENG,엔진,4,냉각,ENG-04-011,라디에이터호스 +65,ENG,엔진,5,윤활,ENG-05-011,오일게이지 +66,ENG,엔진,6,점화,ENG-06-011,플러그와이어 +67,ENG,엔진,1,흡기,ENG-01-012,흡기호스 +68,ENG,엔진,2,배기,ENG-02-012,플렉시블조인트 +69,ENG,엔진,3,연료,ENG-03-012,연료레일 +70,ENG,엔진,4,냉각,ENG-04-012,써모스탯 +71,ENG,엔진,5,윤활,ENG-05-012,오일팬 +72,ENG,엔진,6,점화,ENG-06-012,스파크플러그 +73,ENG,엔진,1,흡기,ENG-01-013,스로틀바디 +74,ENG,엔진,2,배기,ENG-02-013,산소센서 +75,ENG,엔진,3,연료,ENG-03-013,연료필터 +76,ENG,엔진,4,냉각,ENG-04-013,라디에이터 +77,ENG,엔진,5,윤활,ENG-05-013,오일쿨러 +78,ENG,엔진,6,점화,ENG-06-013,플러그와이어 +79,ENG,엔진,1,흡기,ENG-01-014,매스에어플로우센서 +80,ENG,엔진,2,배기,ENG-02-014,가스켓 +81,ENG,엔진,3,연료,ENG-03-014,인젝터 +82,ENG,엔진,4,냉각,ENG-04-014,리저버탱크 +83,ENG,엔진,5,윤활,ENG-05-014,오일게이지 +84,ENG,엔진,6,점화,ENG-06-014,플러그와이어 +85,ENG,엔진,1,흡기,ENG-01-015,매스에어플로우센서 +86,ENG,엔진,2,배기,ENG-02-015,머플러 +87,ENG,엔진,3,연료,ENG-03-015,연료펌프 +88,ENG,엔진,4,냉각,ENG-04-015,라디에이터호스 +89,ENG,엔진,5,윤활,ENG-05-015,오일게이지 +90,ENG,엔진,6,점화,ENG-06-015,스파크플러그 +91,ENG,엔진,1,흡기,ENG-01-016,인테이크 매니폴드 +92,ENG,엔진,2,배기,ENG-02-016,배기관 +93,ENG,엔진,3,연료,ENG-03-016,연료레일 +94,ENG,엔진,4,냉각,ENG-04-016,냉각팬모터 +95,ENG,엔진,5,윤활,ENG-05-016,오일펌프 +96,ENG,엔진,6,점화,ENG-06-016,점화코일 +97,ENG,엔진,1,흡기,ENG-01-017,매스에어플로우센서 +98,ENG,엔진,2,배기,ENG-02-017,플렉시블조인트 +99,ENG,엔진,3,연료,ENG-03-017,인젝터 +100,ENG,엔진,4,냉각,ENG-04-017,써모스탯 +101,ENG,엔진,5,윤활,ENG-05-017,오일팬 +102,ENG,엔진,6,점화,ENG-06-017,이그니션스위치 +103,ENG,엔진,1,흡기,ENG-01-018,흡기호스 +104,ENG,엔진,2,배기,ENG-02-018,플렉시블조인트 +105,ENG,엔진,3,연료,ENG-03-018,연료레일 +106,ENG,엔진,4,냉각,ENG-04-018,라디에이터 +107,ENG,엔진,5,윤활,ENG-05-018,오일펌프 +108,ENG,엔진,6,점화,ENG-06-018,플러그와이어 +109,ENG,엔진,1,흡기,ENG-01-019,흡기호스 +110,ENG,엔진,2,배기,ENG-02-019,산소센서 +111,ENG,엔진,3,연료,ENG-03-019,고압펌프 +112,ENG,엔진,4,냉각,ENG-04-019,라디에이터 +113,ENG,엔진,5,윤활,ENG-05-019,오일펌프 +114,ENG,엔진,6,점화,ENG-06-019,플러그와이어 +115,ENG,엔진,1,흡기,ENG-01-020,인터쿨러 +116,ENG,엔진,2,배기,ENG-02-020,플렉시블조인트 +117,ENG,엔진,3,연료,ENG-03-020,고압펌프 +118,ENG,엔진,4,냉각,ENG-04-020,냉각팬모터 +119,ENG,엔진,5,윤활,ENG-05-020,오일팬 +120,ENG,엔진,6,점화,ENG-06-020,점화모듈 +121,ENG,엔진,1,흡기,ENG-01-021,인터쿨러 +122,ENG,엔진,2,배기,ENG-02-021,촉매변환기 +123,ENG,엔진,3,연료,ENG-03-021,연료펌프 +124,ENG,엔진,4,냉각,ENG-04-021,냉각팬모터 +125,ENG,엔진,5,윤활,ENG-05-021,오일팬 +126,ENG,엔진,6,점화,ENG-06-021,스파크플러그 +127,ENG,엔진,1,흡기,ENG-01-022,매스에어플로우센서 +128,ENG,엔진,2,배기,ENG-02-022,머플러 +129,ENG,엔진,3,연료,ENG-03-022,연료레일 +130,ENG,엔진,4,냉각,ENG-04-022,라디에이터 +131,ENG,엔진,5,윤활,ENG-05-022,오일필터 +132,ENG,엔진,6,점화,ENG-06-022,플러그와이어 +133,ENG,엔진,1,흡기,ENG-01-023,스로틀바디 +134,ENG,엔진,2,배기,ENG-02-023,산소센서 +135,ENG,엔진,3,연료,ENG-03-023,연료필터 +136,ENG,엔진,4,냉각,ENG-04-023,냉각팬모터 +137,ENG,엔진,5,윤활,ENG-05-023,오일게이지 +138,ENG,엔진,6,점화,ENG-06-023,점화모듈 +139,ENG,엔진,1,흡기,ENG-01-024,에어필터 +140,ENG,엔진,2,배기,ENG-02-024,배기관 +141,TRN,트랜스미션,7,기어셋,TRN-07-001,베어링세트 +142,TRN,트랜스미션,8,클러치,TRN-08-001,클러치마스터 +143,TRN,트랜스미션,9,윤활,TRN-09-001,오일씰 +144,TRN,트랜스미션,10,변속제어,TRN-10-001,셀렉터레버 +145,TRN,트랜스미션,7,기어셋,TRN-07-002,메인샤프트 +146,TRN,트랜스미션,8,클러치,TRN-08-002,클러치마스터 +147,TRN,트랜스미션,9,윤활,TRN-09-002,오일씰 +148,TRN,트랜스미션,10,변속제어,TRN-10-002,셀렉터레버 +149,TRN,트랜스미션,7,기어셋,TRN-07-003,피니언기어 +150,TRN,트랜스미션,8,클러치,TRN-08-003,클러치마스터 +151,TRN,트랜스미션,9,윤활,TRN-09-003,가스켓세트 +152,TRN,트랜스미션,10,변속제어,TRN-10-003,시프트케이블 +153,TRN,트랜스미션,7,기어셋,TRN-07-004,메인샤프트 +154,TRN,트랜스미션,8,클러치,TRN-08-004,클러치커버 +155,TRN,트랜스미션,9,윤활,TRN-09-004,ATF오일쿨러 +156,TRN,트랜스미션,10,변속제어,TRN-10-004,밸브바디 +157,TRN,트랜스미션,7,기어셋,TRN-07-005,메인샤프트 +158,TRN,트랜스미션,8,클러치,TRN-08-005,클러치커버 +159,TRN,트랜스미션,9,윤활,TRN-09-005,오일펌프(미션) +160,TRN,트랜스미션,10,변속제어,TRN-10-005,변속솔레노이드 +161,TRN,트랜스미션,7,기어셋,TRN-07-006,베어링세트 +162,TRN,트랜스미션,8,클러치,TRN-08-006,클러치슬레이브 +163,TRN,트랜스미션,9,윤활,TRN-09-006,오일펌프(미션) +164,TRN,트랜스미션,10,변속제어,TRN-10-006,셀렉터레버 +165,TRN,트랜스미션,7,기어셋,TRN-07-007,카운터샤프트 +166,TRN,트랜스미션,8,클러치,TRN-08-007,클러치디스크 +167,TRN,트랜스미션,9,윤활,TRN-09-007,오일펌프(미션) +168,TRN,트랜스미션,10,변속제어,TRN-10-007,시프트케이블 +169,TRN,트랜스미션,7,기어셋,TRN-07-008,동기링 +170,TRN,트랜스미션,8,클러치,TRN-08-008,릴리스베어링 +171,TRN,트랜스미션,9,윤활,TRN-09-008,오일씰 +172,TRN,트랜스미션,10,변속제어,TRN-10-008,포지션센서 +173,TRN,트랜스미션,7,기어셋,TRN-07-009,카운터샤프트 +174,TRN,트랜스미션,8,클러치,TRN-08-009,클러치커버 +175,TRN,트랜스미션,9,윤활,TRN-09-009,오일씰 +176,TRN,트랜스미션,10,변속제어,TRN-10-009,포지션센서 +177,TRN,트랜스미션,7,기어셋,TRN-07-010,피니언기어 +178,TRN,트랜스미션,8,클러치,TRN-08-010,클러치디스크 +179,TRN,트랜스미션,9,윤활,TRN-09-010,오일필터(미션) +180,TRN,트랜스미션,10,변속제어,TRN-10-010,포지션센서 +181,TRN,트랜스미션,7,기어셋,TRN-07-011,베어링세트 +182,TRN,트랜스미션,8,클러치,TRN-08-011,클러치마스터 +183,TRN,트랜스미션,9,윤활,TRN-09-011,오일필터(미션) +184,TRN,트랜스미션,10,변속제어,TRN-10-011,시프트케이블 +185,TRN,트랜스미션,7,기어셋,TRN-07-012,기어세트 +186,TRN,트랜스미션,8,클러치,TRN-08-012,클러치마스터 +187,TRN,트랜스미션,9,윤활,TRN-09-012,오일필터(미션) +188,TRN,트랜스미션,10,변속제어,TRN-10-012,변속솔레노이드 +189,TRN,트랜스미션,7,기어셋,TRN-07-013,메인샤프트 +190,TRN,트랜스미션,8,클러치,TRN-08-013,클러치디스크 +191,TRN,트랜스미션,9,윤활,TRN-09-013,오일펌프(미션) +192,TRN,트랜스미션,10,변속제어,TRN-10-013,시프트케이블 +193,TRN,트랜스미션,7,기어셋,TRN-07-014,메인샤프트 +194,TRN,트랜스미션,8,클러치,TRN-08-014,클러치디스크 +195,TRN,트랜스미션,9,윤활,TRN-09-014,가스켓세트 +196,TRN,트랜스미션,10,변속제어,TRN-10-014,포지션센서 +197,TRN,트랜스미션,7,기어셋,TRN-07-015,기어세트 +198,TRN,트랜스미션,8,클러치,TRN-08-015,클러치디스크 +199,TRN,트랜스미션,9,윤활,TRN-09-015,ATF오일쿨러 +200,TRN,트랜스미션,10,변속제어,TRN-10-015,포지션센서 +201,TRN,트랜스미션,7,기어셋,TRN-07-016,메인샤프트 +202,TRN,트랜스미션,8,클러치,TRN-08-016,클러치슬레이브 +203,TRN,트랜스미션,9,윤활,TRN-09-016,ATF오일쿨러 +204,TRN,트랜스미션,10,변속제어,TRN-10-016,셀렉터레버 +205,TRN,트랜스미션,7,기어셋,TRN-07-017,동기링 +206,TRN,트랜스미션,8,클러치,TRN-08-017,클러치디스크 +207,TRN,트랜스미션,9,윤활,TRN-09-017,ATF오일쿨러 +208,TRN,트랜스미션,10,변속제어,TRN-10-017,밸브바디 +209,TRN,트랜스미션,7,기어셋,TRN-07-018,동기링 +210,TRN,트랜스미션,8,클러치,TRN-08-018,클러치마스터 +211,TRN,트랜스미션,9,윤활,TRN-09-018,오일펌프(미션) +212,TRN,트랜스미션,10,변속제어,TRN-10-018,셀렉터레버 +213,TRN,트랜스미션,7,기어셋,TRN-07-019,카운터샤프트 +214,TRN,트랜스미션,8,클러치,TRN-08-019,클러치슬레이브 +215,TRN,트랜스미션,9,윤활,TRN-09-019,가스켓세트 +216,TRN,트랜스미션,10,변속제어,TRN-10-019,시프트케이블 +217,TRN,트랜스미션,7,기어셋,TRN-07-020,기어세트 +218,TRN,트랜스미션,8,클러치,TRN-08-020,클러치디스크 +219,TRN,트랜스미션,9,윤활,TRN-09-020,오일필터(미션) +220,TRN,트랜스미션,10,변속제어,TRN-10-020,시프트케이블 +221,TRN,트랜스미션,7,기어셋,TRN-07-021,베어링세트 +222,TRN,트랜스미션,8,클러치,TRN-08-021,클러치마스터 +223,TRN,트랜스미션,9,윤활,TRN-09-021,가스켓세트 +224,TRN,트랜스미션,10,변속제어,TRN-10-021,변속솔레노이드 +225,TRN,트랜스미션,7,기어셋,TRN-07-022,메인샤프트 +226,TRN,트랜스미션,8,클러치,TRN-08-022,클러치디스크 +227,TRN,트랜스미션,9,윤활,TRN-09-022,가스켓세트 +228,TRN,트랜스미션,10,변속제어,TRN-10-022,셀렉터레버 +229,TRN,트랜스미션,7,기어셋,TRN-07-023,베어링세트 +230,TRN,트랜스미션,8,클러치,TRN-08-023,클러치커버 +231,TRN,트랜스미션,9,윤활,TRN-09-023,오일씰 +232,TRN,트랜스미션,10,변속제어,TRN-10-023,변속솔레노이드 +233,TRN,트랜스미션,7,기어셋,TRN-07-024,메인샤프트 +234,TRN,트랜스미션,8,클러치,TRN-08-024,클러치슬레이브 +235,TRN,트랜스미션,9,윤활,TRN-09-024,가스켓세트 +236,TRN,트랜스미션,10,변속제어,TRN-10-024,밸브바디 +237,TRN,트랜스미션,7,기어셋,TRN-07-025,피니언기어 +238,TRN,트랜스미션,8,클러치,TRN-08-025,클러치슬레이브 +239,TRN,트랜스미션,9,윤활,TRN-09-025,ATF오일쿨러 +240,TRN,트랜스미션,10,변속제어,TRN-10-025,포지션센서 +241,CHS,샤시,11,서스펜션,CHS-11-001,로워암 +242,CHS,샤시,12,제동,CHS-12-001,브레이크패드 +243,CHS,샤시,13,조향,CHS-13-001,스티어링기어 +244,CHS,샤시,14,휠/허브,CHS-14-001,스페이서 +245,CHS,샤시,11,서스펜션,CHS-11-002,로워암 +246,CHS,샤시,12,제동,CHS-12-002,브레이크디스크 +247,CHS,샤시,13,조향,CHS-13-002,스티어링기어 +248,CHS,샤시,14,휠/허브,CHS-14-002,허브베어링 +249,CHS,샤시,11,서스펜션,CHS-11-003,부싱세트 +250,CHS,샤시,12,제동,CHS-12-003,브레이크마스터 +251,CHS,샤시,13,조향,CHS-13-003,아이들러암 +252,CHS,샤시,14,휠/허브,CHS-14-003,휠너트 +253,CHS,샤시,11,서스펜션,CHS-11-004,어퍼암 +254,CHS,샤시,12,제동,CHS-12-004,브레이크디스크 +255,CHS,샤시,13,조향,CHS-13-004,아이들러암 +256,CHS,샤시,14,휠/허브,CHS-14-004,허브베어링 +257,CHS,샤시,11,서스펜션,CHS-11-005,스프링 +258,CHS,샤시,12,제동,CHS-12-005,브레이크호스 +259,CHS,샤시,13,조향,CHS-13-005,랙앤피니언 +260,CHS,샤시,14,휠/허브,CHS-14-005,허브베어링 +261,CHS,샤시,11,서스펜션,CHS-11-006,부싱세트 +262,CHS,샤시,12,제동,CHS-12-006,브레이크호스 +263,CHS,샤시,13,조향,CHS-13-006,스티어링기어 +264,CHS,샤시,14,휠/허브,CHS-14-006,휠허브 +265,CHS,샤시,11,서스펜션,CHS-11-007,쇽업소버 +266,CHS,샤시,12,제동,CHS-12-007,브레이크캘리퍼 +267,CHS,샤시,13,조향,CHS-13-007,파워스티어링펌프 +268,CHS,샤시,14,휠/허브,CHS-14-007,휠너트 +269,CHS,샤시,11,서스펜션,CHS-11-008,스프링 +270,CHS,샤시,12,제동,CHS-12-008,브레이크마스터 +271,CHS,샤시,13,조향,CHS-13-008,스티어링기어 +272,CHS,샤시,14,휠/허브,CHS-14-008,허브캡 +273,CHS,샤시,11,서스펜션,CHS-11-009,어퍼암 +274,CHS,샤시,12,제동,CHS-12-009,브레이크캘리퍼 +275,CHS,샤시,13,조향,CHS-13-009,스티어링기어 +276,CHS,샤시,14,휠/허브,CHS-14-009,휠허브 +277,CHS,샤시,11,서스펜션,CHS-11-010,스프링 +278,CHS,샤시,12,제동,CHS-12-010,브레이크패드 +279,CHS,샤시,13,조향,CHS-13-010,랙앤피니언 +280,CHS,샤시,14,휠/허브,CHS-14-010,허브캡 +281,CHS,샤시,11,서스펜션,CHS-11-011,스프링 +282,CHS,샤시,12,제동,CHS-12-011,브레이크캘리퍼 +283,CHS,샤시,13,조향,CHS-13-011,랙앤피니언 +284,CHS,샤시,14,휠/허브,CHS-14-011,허브캡 +285,CHS,샤시,11,서스펜션,CHS-11-012,부싱세트 +286,CHS,샤시,12,제동,CHS-12-012,브레이크마스터 +287,CHS,샤시,13,조향,CHS-13-012,타이로드엔드 +288,CHS,샤시,14,휠/허브,CHS-14-012,허브캡 +289,CHS,샤시,11,서스펜션,CHS-11-013,어퍼암 +290,CHS,샤시,12,제동,CHS-12-013,브레이크캘리퍼 +291,CHS,샤시,13,조향,CHS-13-013,타이로드엔드 +292,CHS,샤시,14,휠/허브,CHS-14-013,휠허브 +293,CHS,샤시,11,서스펜션,CHS-11-014,스태빌라이저링크 +294,CHS,샤시,12,제동,CHS-12-014,브레이크디스크 +295,CHS,샤시,13,조향,CHS-13-014,파워스티어링펌프 +296,CHS,샤시,14,휠/허브,CHS-14-014,허브베어링 +297,CHS,샤시,11,서스펜션,CHS-11-015,스태빌라이저링크 +298,CHS,샤시,12,제동,CHS-12-015,브레이크캘리퍼 +299,CHS,샤시,13,조향,CHS-13-015,타이로드엔드 +300,CHS,샤시,14,휠/허브,CHS-14-015,허브캡 +301,CHS,샤시,11,서스펜션,CHS-11-016,스태빌라이저링크 +302,CHS,샤시,12,제동,CHS-12-016,브레이크호스 +303,CHS,샤시,13,조향,CHS-13-016,타이로드엔드 +304,CHS,샤시,14,휠/허브,CHS-14-016,허브베어링 +305,CHS,샤시,11,서스펜션,CHS-11-017,스프링 +306,CHS,샤시,12,제동,CHS-12-017,브레이크디스크 +307,CHS,샤시,13,조향,CHS-13-017,타이로드엔드 +308,CHS,샤시,14,휠/허브,CHS-14-017,허브베어링 +309,CHS,샤시,11,서스펜션,CHS-11-018,부싱세트 +310,CHS,샤시,12,제동,CHS-12-018,브레이크호스 +311,CHS,샤시,13,조향,CHS-13-018,랙앤피니언 +312,CHS,샤시,14,휠/허브,CHS-14-018,스페이서 +313,CHS,샤시,11,서스펜션,CHS-11-019,부싱세트 +314,CHS,샤시,12,제동,CHS-12-019,브레이크호스 +315,CHS,샤시,13,조향,CHS-13-019,스티어링기어 +316,CHS,샤시,14,휠/허브,CHS-14-019,허브베어링 +317,CHS,샤시,11,서스펜션,CHS-11-020,부싱세트 +318,CHS,샤시,12,제동,CHS-12-020,브레이크마스터 +319,CHS,샤시,13,조향,CHS-13-020,랙앤피니언 +320,CHS,샤시,14,휠/허브,CHS-14-020,휠허브 +321,CHS,샤시,11,서스펜션,CHS-11-021,쇽업소버 +322,CHS,샤시,12,제동,CHS-12-021,브레이크패드 +323,CHS,샤시,13,조향,CHS-13-021,아이들러암 +324,CHS,샤시,14,휠/허브,CHS-14-021,허브베어링 +325,CHS,샤시,11,서스펜션,CHS-11-022,스태빌라이저링크 +326,CHS,샤시,12,제동,CHS-12-022,브레이크디스크 +327,CHS,샤시,13,조향,CHS-13-022,랙앤피니언 +328,CHS,샤시,14,휠/허브,CHS-14-022,휠허브 +329,CHS,샤시,11,서스펜션,CHS-11-023,로워암 +330,CHS,샤시,12,제동,CHS-12-023,브레이크디스크 +331,CHS,샤시,13,조향,CHS-13-023,스티어링기어 +332,CHS,샤시,14,휠/허브,CHS-14-023,스페이서 +333,CHS,샤시,11,서스펜션,CHS-11-024,스프링 +334,CHS,샤시,12,제동,CHS-12-024,브레이크마스터 +335,CHS,샤시,13,조향,CHS-13-024,스티어링기어 +336,CHS,샤시,14,휠/허브,CHS-14-024,휠너트 +337,CHS,샤시,11,서스펜션,CHS-11-025,부싱세트 +338,CHS,샤시,12,제동,CHS-12-025,브레이크호스 +339,CHS,샤시,13,조향,CHS-13-025,랙앤피니언 +340,CHS,샤시,14,휠/허브,CHS-14-025,휠허브 +341,CHS,샤시,11,서스펜션,CHS-11-026,어퍼암 +342,CHS,샤시,12,제동,CHS-12-026,브레이크캘리퍼 +343,CHS,샤시,13,조향,CHS-13-026,파워스티어링펌프 +344,CHS,샤시,14,휠/허브,CHS-14-026,스페이서 +345,CHS,샤시,11,서스펜션,CHS-11-027,부싱세트 +346,CHS,샤시,12,제동,CHS-12-027,브레이크호스 +347,CHS,샤시,13,조향,CHS-13-027,아이들러암 +348,CHS,샤시,14,휠/허브,CHS-14-027,허브베어링 +349,CHS,샤시,11,서스펜션,CHS-11-028,부싱세트 +350,CHS,샤시,12,제동,CHS-12-028,브레이크디스크 +351,CHS,샤시,13,조향,CHS-13-028,아이들러암 +352,CHS,샤시,14,휠/허브,CHS-14-028,스페이서 +353,CHS,샤시,11,서스펜션,CHS-11-029,쇽업소버 +354,CHS,샤시,12,제동,CHS-12-029,브레이크호스 +355,CHS,샤시,13,조향,CHS-13-029,랙앤피니언 +356,CHS,샤시,14,휠/허브,CHS-14-029,스페이서 +357,CHS,샤시,11,서스펜션,CHS-11-030,쇽업소버 +358,CHS,샤시,12,제동,CHS-12-030,브레이크디스크 +359,CHS,샤시,13,조향,CHS-13-030,랙앤피니언 +360,CHS,샤시,14,휠/허브,CHS-14-030,허브베어링 +361,BDY,바디,15,외판,BDY-15-001,트렁크리드 +362,BDY,바디,16,범퍼/그릴,BDY-16-001,토우아이커버 +363,BDY,바디,17,도어/윈도우,BDY-17-001,도어핸들 +364,BDY,바디,18,유리,BDY-18-001,와이퍼암 +365,BDY,바디,15,외판,BDY-15-002,후드패널 +366,BDY,바디,16,범퍼/그릴,BDY-16-002,라디에이터그릴 +367,BDY,바디,17,도어/윈도우,BDY-17-002,도어힌지 +368,BDY,바디,18,유리,BDY-18-002,와이퍼암 +369,BDY,바디,15,외판,BDY-15-003,도어패널 +370,BDY,바디,16,범퍼/그릴,BDY-16-003,범퍼보강빔 +371,BDY,바디,17,도어/윈도우,BDY-17-003,도어핸들 +372,BDY,바디,18,유리,BDY-18-003,와이퍼암 +373,BDY,바디,15,외판,BDY-15-004,후드패널 +374,BDY,바디,16,범퍼/그릴,BDY-16-004,리어범퍼 +375,BDY,바디,17,도어/윈도우,BDY-17-004,윈도우레귤레이터 +376,BDY,바디,18,유리,BDY-18-004,리어글래스 +377,BDY,바디,15,외판,BDY-15-005,후드패널 +378,BDY,바디,16,범퍼/그릴,BDY-16-005,프론트범퍼 +379,BDY,바디,17,도어/윈도우,BDY-17-005,도어힌지 +380,BDY,바디,18,유리,BDY-18-005,글래스몰딩 +381,BDY,바디,15,외판,BDY-15-006,도어패널 +382,BDY,바디,16,범퍼/그릴,BDY-16-006,프론트범퍼 +383,BDY,바디,17,도어/윈도우,BDY-17-006,도어핸들 +384,BDY,바디,18,유리,BDY-18-006,글래스몰딩 +385,BDY,바디,15,외판,BDY-15-007,리어펜더 +386,BDY,바디,16,범퍼/그릴,BDY-16-007,토우아이커버 +387,BDY,바디,17,도어/윈도우,BDY-17-007,도어힌지 +388,BDY,바디,18,유리,BDY-18-007,와이퍼암 +389,BDY,바디,15,외판,BDY-15-008,도어패널 +390,BDY,바디,16,범퍼/그릴,BDY-16-008,리어범퍼 +391,BDY,바디,17,도어/윈도우,BDY-17-008,윈도우모터 +392,BDY,바디,18,유리,BDY-18-008,글래스몰딩 +393,BDY,바디,15,외판,BDY-15-009,도어패널 +394,BDY,바디,16,범퍼/그릴,BDY-16-009,토우아이커버 +395,BDY,바디,17,도어/윈도우,BDY-17-009,웨더스트립 +396,BDY,바디,18,유리,BDY-18-009,와이퍼암 +397,BDY,바디,15,외판,BDY-15-010,프론트펜더 +398,BDY,바디,16,범퍼/그릴,BDY-16-010,포그램프커버 +399,BDY,바디,17,도어/윈도우,BDY-17-010,도어힌지 +400,BDY,바디,18,유리,BDY-18-010,리어글래스 +401,BDY,바디,15,외판,BDY-15-011,도어패널 +402,BDY,바디,16,범퍼/그릴,BDY-16-011,리어범퍼 +403,BDY,바디,17,도어/윈도우,BDY-17-011,웨더스트립 +404,BDY,바디,18,유리,BDY-18-011,사이드글래스 +405,BDY,바디,15,외판,BDY-15-012,트렁크리드 +406,BDY,바디,16,범퍼/그릴,BDY-16-012,프론트범퍼 +407,BDY,바디,17,도어/윈도우,BDY-17-012,웨더스트립 +408,BDY,바디,18,유리,BDY-18-012,글래스몰딩 +409,BDY,바디,15,외판,BDY-15-013,리어펜더 +410,BDY,바디,16,범퍼/그릴,BDY-16-013,프론트범퍼 +411,BDY,바디,17,도어/윈도우,BDY-17-013,윈도우레귤레이터 +412,BDY,바디,18,유리,BDY-18-013,글래스몰딩 +413,BDY,바디,15,외판,BDY-15-014,후드패널 +414,BDY,바디,16,범퍼/그릴,BDY-16-014,리어범퍼 +415,BDY,바디,17,도어/윈도우,BDY-17-014,윈도우모터 +416,BDY,바디,18,유리,BDY-18-014,윈드실드글래스 +417,BDY,바디,15,외판,BDY-15-015,프론트펜더 +418,BDY,바디,16,범퍼/그릴,BDY-16-015,포그램프커버 +419,BDY,바디,17,도어/윈도우,BDY-17-015,윈도우모터 +420,BDY,바디,18,유리,BDY-18-015,사이드글래스 +421,BDY,바디,15,외판,BDY-15-016,리어펜더 +422,BDY,바디,16,범퍼/그릴,BDY-16-016,리어범퍼 +423,BDY,바디,17,도어/윈도우,BDY-17-016,웨더스트립 +424,BDY,바디,18,유리,BDY-18-016,사이드글래스 +425,BDY,바디,15,외판,BDY-15-017,사이드실패널 +426,BDY,바디,16,범퍼/그릴,BDY-16-017,프론트범퍼 +427,BDY,바디,17,도어/윈도우,BDY-17-017,웨더스트립 +428,BDY,바디,18,유리,BDY-18-017,글래스몰딩 +429,BDY,바디,15,외판,BDY-15-018,프론트펜더 +430,BDY,바디,16,범퍼/그릴,BDY-16-018,포그램프커버 +431,BDY,바디,17,도어/윈도우,BDY-17-018,윈도우레귤레이터 +432,BDY,바디,18,유리,BDY-18-018,사이드글래스 +433,BDY,바디,15,외판,BDY-15-019,사이드실패널 +434,BDY,바디,16,범퍼/그릴,BDY-16-019,범퍼보강빔 +435,BDY,바디,17,도어/윈도우,BDY-17-019,도어힌지 +436,BDY,바디,18,유리,BDY-18-019,글래스몰딩 +437,BDY,바디,15,외판,BDY-15-020,리어펜더 +438,BDY,바디,16,범퍼/그릴,BDY-16-020,범퍼보강빔 +439,BDY,바디,17,도어/윈도우,BDY-17-020,윈도우레귤레이터 +440,BDY,바디,18,유리,BDY-18-020,리어글래스 +441,BDY,바디,15,외판,BDY-15-021,리어펜더 +442,BDY,바디,16,범퍼/그릴,BDY-16-021,프론트범퍼 +443,BDY,바디,17,도어/윈도우,BDY-17-021,윈도우모터 +444,BDY,바디,18,유리,BDY-18-021,윈드실드글래스 +445,BDY,바디,15,외판,BDY-15-022,리어펜더 +446,BDY,바디,16,범퍼/그릴,BDY-16-022,토우아이커버 +447,BDY,바디,17,도어/윈도우,BDY-17-022,웨더스트립 +448,BDY,바디,18,유리,BDY-18-022,글래스몰딩 +449,BDY,바디,15,외판,BDY-15-023,사이드실패널 +450,BDY,바디,16,범퍼/그릴,BDY-16-023,프론트범퍼 +451,BDY,바디,17,도어/윈도우,BDY-17-023,웨더스트립 +452,BDY,바디,18,유리,BDY-18-023,리어글래스 +453,BDY,바디,15,외판,BDY-15-024,도어패널 +454,BDY,바디,16,범퍼/그릴,BDY-16-024,토우아이커버 +455,BDY,바디,17,도어/윈도우,BDY-17-024,윈도우모터 +456,BDY,바디,18,유리,BDY-18-024,와이퍼암 +457,BDY,바디,15,외판,BDY-15-025,후드패널 +458,BDY,바디,16,범퍼/그릴,BDY-16-025,프론트범퍼 +459,BDY,바디,17,도어/윈도우,BDY-17-025,윈도우레귤레이터 +460,BDY,바디,18,유리,BDY-18-025,윈드실드글래스 +461,TRM,트림,19,시트,TRM-19-001,시트프레임 +462,TRM,트림,20,내장재,TRM-20-001,도어트림 +463,TRM,트림,21,소음진동,TRM-21-001,플로어매트 +464,TRM,트림,22,익스테리어트림,TRM-22-001,사이드몰딩 +465,TRM,트림,19,시트,TRM-19-002,시트쿠션 +466,TRM,트림,20,내장재,TRM-20-002,도어트림 +467,TRM,트림,21,소음진동,TRM-21-002,방진패드 +468,TRM,트림,22,익스테리어트림,TRM-22-002,엠블럼 +469,TRM,트림,19,시트,TRM-19-003,시트트랙 +470,TRM,트림,20,내장재,TRM-20-003,필러트림 +471,TRM,트림,21,소음진동,TRM-21-003,방진패드 +472,TRM,트림,22,익스테리어트림,TRM-22-003,머드가드 +473,TRM,트림,19,시트,TRM-19-004,헤드레스트 +474,TRM,트림,20,내장재,TRM-20-004,루프라이너 +475,TRM,트림,21,소음진동,TRM-21-004,트렁크매트 +476,TRM,트림,22,익스테리어트림,TRM-22-004,스포일러 +477,TRM,트림,19,시트,TRM-19-005,시트프레임 +478,TRM,트림,20,내장재,TRM-20-005,도어트림 +479,TRM,트림,21,소음진동,TRM-21-005,흡음재 +480,TRM,트림,22,익스테리어트림,TRM-22-005,크롬가니쉬 +481,TRM,트림,19,시트,TRM-19-006,시트레버 +482,TRM,트림,20,내장재,TRM-20-006,대시보드패널 +483,TRM,트림,21,소음진동,TRM-21-006,플로어매트 +484,TRM,트림,22,익스테리어트림,TRM-22-006,사이드몰딩 +485,TRM,트림,19,시트,TRM-19-007,시트프레임 +486,TRM,트림,20,내장재,TRM-20-007,도어트림 +487,TRM,트림,21,소음진동,TRM-21-007,흡음재 +488,TRM,트림,22,익스테리어트림,TRM-22-007,머드가드 +489,TRM,트림,19,시트,TRM-19-008,시트쿠션 +490,TRM,트림,20,내장재,TRM-20-008,대시보드패널 +491,TRM,트림,21,소음진동,TRM-21-008,플로어매트 +492,TRM,트림,22,익스테리어트림,TRM-22-008,사이드몰딩 +493,TRM,트림,19,시트,TRM-19-009,시트레버 +494,TRM,트림,20,내장재,TRM-20-009,대시보드패널 +495,TRM,트림,21,소음진동,TRM-21-009,플로어매트 +496,TRM,트림,22,익스테리어트림,TRM-22-009,머드가드 +497,TRM,트림,19,시트,TRM-19-010,시트레버 +498,TRM,트림,20,내장재,TRM-20-010,도어트림 +499,TRM,트림,21,소음진동,TRM-21-010,방진매트 +500,TRM,트림,22,익스테리어트림,TRM-22-010,크롬가니쉬 +501,TRM,트림,19,시트,TRM-19-011,시트프레임 +502,TRM,트림,20,내장재,TRM-20-011,루프라이너 +503,TRM,트림,21,소음진동,TRM-21-011,방진패드 +504,TRM,트림,22,익스테리어트림,TRM-22-011,사이드몰딩 +505,TRM,트림,19,시트,TRM-19-012,시트쿠션 +506,TRM,트림,20,내장재,TRM-20-012,도어트림 +507,TRM,트림,21,소음진동,TRM-21-012,흡음재 +508,TRM,트림,22,익스테리어트림,TRM-22-012,크롬가니쉬 +509,TRM,트림,19,시트,TRM-19-013,시트쿠션 +510,TRM,트림,20,내장재,TRM-20-013,도어트림 +511,TRM,트림,21,소음진동,TRM-21-013,플로어매트 +512,TRM,트림,22,익스테리어트림,TRM-22-013,머드가드 +513,TRM,트림,19,시트,TRM-19-014,시트쿠션 +514,TRM,트림,20,내장재,TRM-20-014,도어트림 +515,TRM,트림,21,소음진동,TRM-21-014,트렁크매트 +516,TRM,트림,22,익스테리어트림,TRM-22-014,머드가드 +517,TRM,트림,19,시트,TRM-19-015,시트쿠션 +518,TRM,트림,20,내장재,TRM-20-015,도어트림 +519,TRM,트림,21,소음진동,TRM-21-015,플로어매트 +520,TRM,트림,22,익스테리어트림,TRM-22-015,사이드몰딩 +521,TRM,트림,19,시트,TRM-19-016,시트트랙 +522,TRM,트림,20,내장재,TRM-20-016,대시보드패널 +523,TRM,트림,21,소음진동,TRM-21-016,흡음재 +524,TRM,트림,22,익스테리어트림,TRM-22-016,사이드몰딩 +525,TRM,트림,19,시트,TRM-19-017,헤드레스트 +526,TRM,트림,20,내장재,TRM-20-017,루프라이너 +527,TRM,트림,21,소음진동,TRM-21-017,방진패드 +528,TRM,트림,22,익스테리어트림,TRM-22-017,머드가드 +529,TRM,트림,19,시트,TRM-19-018,시트레버 +530,TRM,트림,20,내장재,TRM-20-018,콘솔박스 +531,TRM,트림,21,소음진동,TRM-21-018,트렁크매트 +532,TRM,트림,22,익스테리어트림,TRM-22-018,사이드몰딩 +533,TRM,트림,19,시트,TRM-19-019,시트레버 +534,TRM,트림,20,내장재,TRM-20-019,카펫트림 +535,TRM,트림,21,소음진동,TRM-21-019,트렁크매트 +536,TRM,트림,22,익스테리어트림,TRM-22-019,머드가드 +537,TRM,트림,19,시트,TRM-19-020,시트레버 +538,TRM,트림,20,내장재,TRM-20-020,루프라이너 +539,TRM,트림,21,소음진동,TRM-21-020,플로어매트 +540,TRM,트림,22,익스테리어트림,TRM-22-020,크롬가니쉬 +541,ELE,일렉트릭,23,전원,ELE-23-001,알터네이터 +542,ELE,일렉트릭,24,제어,ELE-24-001,퓨즈세트 +543,ELE,일렉트릭,25,조명,ELE-25-001,테일램프 +544,ELE,일렉트릭,26,배선,ELE-26-001,배선클립 +545,ELE,일렉트릭,23,전원,ELE-23-002,퓨즈박스 +546,ELE,일렉트릭,24,제어,ELE-24-002,퓨즈세트 +547,ELE,일렉트릭,25,조명,ELE-25-002,헤드램프 +548,ELE,일렉트릭,26,배선,ELE-26-002,배선클립 +549,ELE,일렉트릭,23,전원,ELE-23-003,배터리 +550,ELE,일렉트릭,24,제어,ELE-24-003,ECU +551,ELE,일렉트릭,25,조명,ELE-25-003,턴시그널램프 +552,ELE,일렉트릭,26,배선,ELE-26-003,케이블타이 +553,ELE,일렉트릭,23,전원,ELE-23-004,퓨즈박스 +554,ELE,일렉트릭,24,제어,ELE-24-004,릴레이 +555,ELE,일렉트릭,25,조명,ELE-25-004,헤드램프 +556,ELE,일렉트릭,26,배선,ELE-26-004,와이어하네스 +557,ELE,일렉트릭,23,전원,ELE-23-005,전압레귤레이터 +558,ELE,일렉트릭,24,제어,ELE-24-005,스로틀포지션센서 +559,ELE,일렉트릭,25,조명,ELE-25-005,실내등 +560,ELE,일렉트릭,26,배선,ELE-26-005,분배커넥터 +561,ELE,일렉트릭,23,전원,ELE-23-006,전압레귤레이터 +562,ELE,일렉트릭,24,제어,ELE-24-006,퓨즈세트 +563,ELE,일렉트릭,25,조명,ELE-25-006,브레이크등 +564,ELE,일렉트릭,26,배선,ELE-26-006,배선클립 +565,ELE,일렉트릭,23,전원,ELE-23-007,전압레귤레이터 +566,ELE,일렉트릭,24,제어,ELE-24-007,퓨즈세트 +567,ELE,일렉트릭,25,조명,ELE-25-007,헤드램프 +568,ELE,일렉트릭,26,배선,ELE-26-007,전선가이드 +569,ELE,일렉트릭,23,전원,ELE-23-008,알터네이터 +570,ELE,일렉트릭,24,제어,ELE-24-008,릴레이 +571,ELE,일렉트릭,25,조명,ELE-25-008,안개등 +572,ELE,일렉트릭,26,배선,ELE-26-008,전선가이드 +573,ELE,일렉트릭,23,전원,ELE-23-009,배터리 +574,ELE,일렉트릭,24,제어,ELE-24-009,퓨즈세트 +575,ELE,일렉트릭,25,조명,ELE-25-009,안개등 +576,ELE,일렉트릭,26,배선,ELE-26-009,케이블타이 +577,ELE,일렉트릭,23,전원,ELE-23-010,접지스트랩 +578,ELE,일렉트릭,24,제어,ELE-24-010,퓨즈세트 +579,ELE,일렉트릭,25,조명,ELE-25-010,테일램프 +580,ELE,일렉트릭,26,배선,ELE-26-010,와이어하네스 +581,ELE,일렉트릭,23,전원,ELE-23-011,시동모터 +582,ELE,일렉트릭,24,제어,ELE-24-011,릴레이 +583,ELE,일렉트릭,25,조명,ELE-25-011,안개등 +584,ELE,일렉트릭,26,배선,ELE-26-011,배선클립 +585,ELE,일렉트릭,23,전원,ELE-23-012,배터리 +586,ELE,일렉트릭,24,제어,ELE-24-012,퓨즈세트 +587,ELE,일렉트릭,25,조명,ELE-25-012,실내등 +588,ELE,일렉트릭,26,배선,ELE-26-012,와이어하네스 +589,ELE,일렉트릭,23,전원,ELE-23-013,퓨즈박스 +590,ELE,일렉트릭,24,제어,ELE-24-013,퓨즈세트 +591,ELE,일렉트릭,25,조명,ELE-25-013,브레이크등 +592,ELE,일렉트릭,26,배선,ELE-26-013,배선클립 +593,ELE,일렉트릭,23,전원,ELE-23-014,알터네이터 +594,ELE,일렉트릭,24,제어,ELE-24-014,노크센서 +595,ELE,일렉트릭,25,조명,ELE-25-014,헤드램프 +596,ELE,일렉트릭,26,배선,ELE-26-014,와이어하네스 +597,ELE,일렉트릭,23,전원,ELE-23-015,시동모터 +598,ELE,일렉트릭,24,제어,ELE-24-015,스로틀포지션센서 +599,ELE,일렉트릭,25,조명,ELE-25-015,헤드램프 +600,ELE,일렉트릭,26,배선,ELE-26-015,배선클립 +601,ELE,일렉트릭,23,전원,ELE-23-016,퓨즈박스 +602,ELE,일렉트릭,24,제어,ELE-24-016,노크센서 +603,ELE,일렉트릭,25,조명,ELE-25-016,헤드램프 +604,ELE,일렉트릭,26,배선,ELE-26-016,전선가이드 +605,ELE,일렉트릭,23,전원,ELE-23-017,배터리 +606,ELE,일렉트릭,24,제어,ELE-24-017,퓨즈세트 +607,ELE,일렉트릭,25,조명,ELE-25-017,안개등 +608,ELE,일렉트릭,26,배선,ELE-26-017,배선클립 +609,ELE,일렉트릭,23,전원,ELE-23-018,배터리 +610,ELE,일렉트릭,24,제어,ELE-24-018,노크센서 +611,ELE,일렉트릭,25,조명,ELE-25-018,브레이크등 +612,ELE,일렉트릭,26,배선,ELE-26-018,배선클립 +613,ELE,일렉트릭,23,전원,ELE-23-019,전압레귤레이터 +614,ELE,일렉트릭,24,제어,ELE-24-019,맵센서 +615,ELE,일렉트릭,25,조명,ELE-25-019,브레이크등 +616,ELE,일렉트릭,26,배선,ELE-26-019,전선가이드 +617,ELE,일렉트릭,23,전원,ELE-23-020,시동모터 +618,ELE,일렉트릭,24,제어,ELE-24-020,맵센서 +619,ELE,일렉트릭,25,조명,ELE-25-020,실내등 +620,ELE,일렉트릭,26,배선,ELE-26-020,배선클립 +621,ELE,일렉트릭,23,전원,ELE-23-021,시동모터 +622,ELE,일렉트릭,24,제어,ELE-24-021,맵센서 +623,ELE,일렉트릭,25,조명,ELE-25-021,브레이크등 +624,ELE,일렉트릭,26,배선,ELE-26-021,배선클립 +625,ELE,일렉트릭,23,전원,ELE-23-022,배터리 +626,ELE,일렉트릭,24,제어,ELE-24-022,스로틀포지션센서 +627,ELE,일렉트릭,25,조명,ELE-25-022,턴시그널램프 +628,ELE,일렉트릭,26,배선,ELE-26-022,분배커넥터 +629,ELE,일렉트릭,23,전원,ELE-23-023,전압레귤레이터 +630,ELE,일렉트릭,24,제어,ELE-24-023,온도센서 +631,ELE,일렉트릭,25,조명,ELE-25-023,턴시그널램프 +632,ELE,일렉트릭,26,배선,ELE-26-023,분배커넥터 +633,ELE,일렉트릭,23,전원,ELE-23-024,알터네이터 +634,ELE,일렉트릭,24,제어,ELE-24-024,노크센서 +635,ELE,일렉트릭,25,조명,ELE-25-024,브레이크등 +636,ELE,일렉트릭,26,배선,ELE-26-024,분배커넥터 +637,ELE,일렉트릭,23,전원,ELE-23-025,배터리 +638,ELE,일렉트릭,24,제어,ELE-24-025,스로틀포지션센서 +639,ELE,일렉트릭,25,조명,ELE-25-025,턴시그널램프 +640,ELE,일렉트릭,26,배선,ELE-26-025,분배커넥터 +641,ELE,일렉트릭,23,전원,ELE-23-026,전압레귤레이터 +642,ELE,일렉트릭,24,제어,ELE-24-026,맵센서 +643,ELE,일렉트릭,25,조명,ELE-25-026,턴시그널램프 +644,ELE,일렉트릭,26,배선,ELE-26-026,배선클립 +645,ELE,일렉트릭,23,전원,ELE-23-027,배터리 +646,ELE,일렉트릭,24,제어,ELE-24-027,ECU +647,ELE,일렉트릭,25,조명,ELE-25-027,헤드램프 +648,ELE,일렉트릭,26,배선,ELE-26-027,배선클립 +649,ELE,일렉트릭,23,전원,ELE-23-028,전압레귤레이터 +650,ELE,일렉트릭,24,제어,ELE-24-028,퓨즈세트 +651,ELE,일렉트릭,25,조명,ELE-25-028,헤드램프 +652,ELE,일렉트릭,26,배선,ELE-26-028,전선가이드 +653,ELE,일렉트릭,23,전원,ELE-23-029,퓨즈박스 +654,ELE,일렉트릭,24,제어,ELE-24-029,노크센서 +655,ELE,일렉트릭,25,조명,ELE-25-029,헤드램프 +656,ELE,일렉트릭,26,배선,ELE-26-029,와이어하네스 +657,ELE,일렉트릭,23,전원,ELE-23-030,전압레귤레이터 +658,ELE,일렉트릭,24,제어,ELE-24-030,노크센서 +659,ELE,일렉트릭,25,조명,ELE-25-030,턴시그널램프 +660,ELE,일렉트릭,26,배선,ELE-26-030,배선클립 +661,CON,커넥터,27,단자,CON-27-001,러그단자 +662,CON,커넥터,28,하우징,CON-28-001,락레버 +663,CON,커넥터,29,실링/고정,CON-29-001,실링플러그 +664,CON,커넥터,30,케이블/클립,CON-30-001,하네스클립 +665,CON,커넥터,27,단자,CON-27-002,핀터미널 +666,CON,커넥터,28,하우징,CON-28-002,커넥터하우징 +667,CON,커넥터,29,실링/고정,CON-29-002,실링플러그 +668,CON,커넥터,30,케이블/클립,CON-30-002,하네스클립 +669,CON,커넥터,27,단자,CON-27-003,링단자 +670,CON,커넥터,28,하우징,CON-28-003,커넥터하우징 +671,CON,커넥터,29,실링/고정,CON-29-003,리테이너 +672,CON,커넥터,30,케이블/클립,CON-30-003,와이어 +673,CON,커넥터,27,단자,CON-27-004,소켓터미널 +674,CON,커넥터,28,하우징,CON-28-004,하우징캡 +675,CON,커넥터,29,실링/고정,CON-29-004,방수캡 +676,CON,커넥터,30,케이블/클립,CON-30-004,하네스클립 +677,CON,커넥터,27,단자,CON-27-005,러그단자 +678,CON,커넥터,28,하우징,CON-28-005,커넥터하우징 +679,CON,커넥터,29,실링/고정,CON-29-005,방수캡 +680,CON,커넥터,30,케이블/클립,CON-30-005,케이블클립 +681,CON,커넥터,27,단자,CON-27-006,핀터미널 +682,CON,커넥터,28,하우징,CON-28-006,하우징캡 +683,CON,커넥터,29,실링/고정,CON-29-006,실링플러그 +684,CON,커넥터,30,케이블/클립,CON-30-006,와이어 +685,CON,커넥터,27,단자,CON-27-007,링단자 +686,CON,커넥터,28,하우징,CON-28-007,락레버 +687,CON,커넥터,29,실링/고정,CON-29-007,리테이너 +688,CON,커넥터,30,케이블/클립,CON-30-007,와이어 +689,CON,커넥터,27,단자,CON-27-008,핀터미널 +690,CON,커넥터,28,하우징,CON-28-008,터미널블록 +691,CON,커넥터,29,실링/고정,CON-29-008,실링플러그 +692,CON,커넥터,30,케이블/클립,CON-30-008,하네스클립 +693,CON,커넥터,27,단자,CON-27-009,링단자 +694,CON,커넥터,28,하우징,CON-28-009,커넥터하우징 +695,CON,커넥터,29,실링/고정,CON-29-009,고무씰 +696,CON,커넥터,30,케이블/클립,CON-30-009,하네스클립 +697,CON,커넥터,27,단자,CON-27-010,링단자 +698,CON,커넥터,28,하우징,CON-28-010,락레버 +699,CON,커넥터,29,실링/고정,CON-29-010,방수캡 +700,CON,커넥터,30,케이블/클립,CON-30-010,하네스클립 From 48a3af79f78ccb29ea74714d266cce3a5bd94596 Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Tue, 28 Oct 2025 02:35:12 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[FEAT]=20=EC=9E=90=EC=9E=AC=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MaterialController.java | 74 +++++++ .../dto/MaterialCategoryResponseDTO.java | 23 +++ .../api/material/dto/MaterialRequestDTO.java | 16 ++ .../api/material/dto/MaterialResponseDTO.java | 31 +++ .../backend/api/material/entity/Material.java | 41 ++++ .../api/material/entity/MaterialCategory.java | 23 +++ .../MaterialCategoryRepository.java | 11 ++ .../repository/MaterialRepository.java | 18 ++ .../api/material/service/MaterialService.java | 185 ++++++++++++++++++ .../common/config/DataInitializer.java | 100 ++++++++++ .../backend/common/dto/PageResponseDTO.java | 21 ++ .../backend/common/response/ErrorStatus.java | 1 + src/main/resources/data/materials_master.csv | 41 ++++ 13 files changed, 585 insertions(+) create mode 100644 src/main/java/com/sampoom/backend/api/material/controller/MaterialController.java create mode 100644 src/main/java/com/sampoom/backend/api/material/dto/MaterialCategoryResponseDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/material/dto/MaterialRequestDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/material/dto/MaterialResponseDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/material/entity/Material.java create mode 100644 src/main/java/com/sampoom/backend/api/material/entity/MaterialCategory.java create mode 100644 src/main/java/com/sampoom/backend/api/material/repository/MaterialCategoryRepository.java create mode 100644 src/main/java/com/sampoom/backend/api/material/repository/MaterialRepository.java create mode 100644 src/main/java/com/sampoom/backend/api/material/service/MaterialService.java create mode 100644 src/main/java/com/sampoom/backend/common/config/DataInitializer.java create mode 100644 src/main/java/com/sampoom/backend/common/dto/PageResponseDTO.java create mode 100644 src/main/resources/data/materials_master.csv diff --git a/src/main/java/com/sampoom/backend/api/material/controller/MaterialController.java b/src/main/java/com/sampoom/backend/api/material/controller/MaterialController.java new file mode 100644 index 0000000..7b315f3 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/controller/MaterialController.java @@ -0,0 +1,74 @@ +package com.sampoom.backend.api.material.controller; + +import com.sampoom.backend.api.material.dto.MaterialCategoryResponseDTO; +import com.sampoom.backend.api.material.dto.MaterialRequestDTO; +import com.sampoom.backend.api.material.dto.MaterialResponseDTO; +import com.sampoom.backend.api.material.service.MaterialService; +import com.sampoom.backend.common.dto.PageResponseDTO; +import com.sampoom.backend.common.response.ApiResponse; +import com.sampoom.backend.common.response.SuccessStatus; +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.*; + +import java.util.List; + +@Tag(name = "Material", description = "Material 관련 API 입니다.") +@RestController +@RequestMapping("/api/materials") +@RequiredArgsConstructor +public class MaterialController { + + private final MaterialService materialService; + + @Operation(summary = "카테고리 조회", description = "모든 자재 카테고리를 조회합니다.") + @GetMapping("/category") + public ResponseEntity>> getAllCategories() { + return ApiResponse.success(SuccessStatus.OK, materialService.findAllCategories()); + } + + @Operation(summary = "카테고리별 자재 목록 조회", description = "특정 카테고리에 속한 자재 목록을 조회합니다.") + @GetMapping("/category/{categoryId}") + public ResponseEntity>> getMaterialsByCategory( + @PathVariable Long categoryId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + return ApiResponse.success(SuccessStatus.OK, materialService.findMaterialsByCategory(categoryId, page, size)); + } + + @Operation(summary = "자재 목록 전체 조회", description = "모든 자재 정보를 조회합니다.") + @GetMapping + public ResponseEntity>> getAllMaterials( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return ApiResponse.success(SuccessStatus.OK, materialService.findAllMaterials(page, size)); + } + + @Operation(summary = "자재 등록", description = "새로운 자재를 등록합니다.") + @PostMapping + public ResponseEntity> createMaterial(@Valid @RequestBody MaterialRequestDTO materialRequestDTO) { + + MaterialResponseDTO created = materialService.createMaterial(materialRequestDTO); + + return ApiResponse.success(SuccessStatus.CREATED,created); + } + + @Operation(summary = "자재 수정", description = "기존 자재 정보를 수정합니다.") + @PutMapping("/{materialId}") + public ResponseEntity> updateMaterial( + @PathVariable("materialId") Long id, + @Valid @RequestBody MaterialRequestDTO materialRequestDTO) { + + return ApiResponse.success(SuccessStatus.OK,materialService.updateMaterial(id, materialRequestDTO)); + } + + @Operation(summary = "자재 삭제", description = "자재를 삭제합니다.") + @DeleteMapping("/{materialId}") + public ResponseEntity> deleteMaterial(@PathVariable("materialId") Long id) { + materialService.deleteMaterial(id); + return ApiResponse.success_only(SuccessStatus.OK); + } +} diff --git a/src/main/java/com/sampoom/backend/api/material/dto/MaterialCategoryResponseDTO.java b/src/main/java/com/sampoom/backend/api/material/dto/MaterialCategoryResponseDTO.java new file mode 100644 index 0000000..48e5b66 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/dto/MaterialCategoryResponseDTO.java @@ -0,0 +1,23 @@ +package com.sampoom.backend.api.material.dto; + +import com.sampoom.backend.api.material.entity.MaterialCategory; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MaterialCategoryResponseDTO { + private Long id; + private String name; + private String code; + + public MaterialCategoryResponseDTO(MaterialCategory category) { + this.id = category.getId(); + this.name = category.getName(); + this.code = category.getCode(); + } +} diff --git a/src/main/java/com/sampoom/backend/api/material/dto/MaterialRequestDTO.java b/src/main/java/com/sampoom/backend/api/material/dto/MaterialRequestDTO.java new file mode 100644 index 0000000..7516948 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/dto/MaterialRequestDTO.java @@ -0,0 +1,16 @@ +package com.sampoom.backend.api.material.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MaterialRequestDTO { + private String name; + private Long materialCategoryId; + private String materialUnit; +} diff --git a/src/main/java/com/sampoom/backend/api/material/dto/MaterialResponseDTO.java b/src/main/java/com/sampoom/backend/api/material/dto/MaterialResponseDTO.java new file mode 100644 index 0000000..5f1fb0e --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/dto/MaterialResponseDTO.java @@ -0,0 +1,31 @@ +package com.sampoom.backend.api.material.dto; + +import com.sampoom.backend.api.material.entity.Material; +import lombok.*; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MaterialResponseDTO { + + private Long id; + private String name; + private String materialCode; + private String materialUnit; + + private Long materialCategoryId; + private String materialCategoryName; + + public MaterialResponseDTO(Material material) { + this.id = material.getId(); + this.name = material.getName(); + this.materialCode = material.getMaterialCode(); + this.materialUnit = material.getMaterialUnit(); + + if (material.getMaterialCategory() != null) { + this.materialCategoryId = material.getMaterialCategory().getId(); + this.materialCategoryName = material.getMaterialCategory().getName(); + } + } +} diff --git a/src/main/java/com/sampoom/backend/api/material/entity/Material.java b/src/main/java/com/sampoom/backend/api/material/entity/Material.java new file mode 100644 index 0000000..f1e189f --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/entity/Material.java @@ -0,0 +1,41 @@ +package com.sampoom.backend.api.material.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "material_master") +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Material { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + private String materialCode; + + private String materialUnit; + + @ManyToOne(fetch = FetchType.LAZY) + private MaterialCategory materialCategory; + + /** 이름/단위 수정 */ + public void updateBasicInfo(String name, String unit) { + this.name = name; + this.materialUnit = unit; + } + + /** 카테고리 변경 + 코드 재발급 */ + public void changeCategory(MaterialCategory newCategory, String newCode) { + this.materialCategory = newCategory; + this.materialCode = newCode; + } +} diff --git a/src/main/java/com/sampoom/backend/api/material/entity/MaterialCategory.java b/src/main/java/com/sampoom/backend/api/material/entity/MaterialCategory.java new file mode 100644 index 0000000..953f3aa --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/entity/MaterialCategory.java @@ -0,0 +1,23 @@ +package com.sampoom.backend.api.material.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "material_category") +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MaterialCategory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private String code; +} diff --git a/src/main/java/com/sampoom/backend/api/material/repository/MaterialCategoryRepository.java b/src/main/java/com/sampoom/backend/api/material/repository/MaterialCategoryRepository.java new file mode 100644 index 0000000..5bd0022 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/repository/MaterialCategoryRepository.java @@ -0,0 +1,11 @@ +package com.sampoom.backend.api.material.repository; + +import com.sampoom.backend.api.material.entity.MaterialCategory; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MaterialCategoryRepository extends JpaRepository { + + Optional findByCode(String code); +} diff --git a/src/main/java/com/sampoom/backend/api/material/repository/MaterialRepository.java b/src/main/java/com/sampoom/backend/api/material/repository/MaterialRepository.java new file mode 100644 index 0000000..7efabd4 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/repository/MaterialRepository.java @@ -0,0 +1,18 @@ +package com.sampoom.backend.api.material.repository; + +import com.sampoom.backend.api.material.entity.Material; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MaterialRepository extends JpaRepository { + + // 카테고리별 자재 조회 (페이지네이션) + Page findByMaterialCategoryId(Long categoryId, Pageable pageable); + + // 이름 또는 코드 검색 (부분 일치, 대소문자 무시) + Page findByNameContainingIgnoreCaseOrMaterialCodeContainingIgnoreCase(String name, String code, Pageable pageable); + + // 카테고리 내에서 가장 최근 등록된 자재 찾기 (코드 자동 생성용) + Material findTopByMaterialCategoryIdOrderByIdDesc(Long categoryId); +} \ No newline at end of file diff --git a/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java b/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java new file mode 100644 index 0000000..6ea2915 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java @@ -0,0 +1,185 @@ +package com.sampoom.backend.api.material.service; + +import com.sampoom.backend.api.material.dto.MaterialCategoryResponseDTO; +import com.sampoom.backend.api.material.dto.MaterialRequestDTO; +import com.sampoom.backend.api.material.dto.MaterialResponseDTO; +import com.sampoom.backend.api.material.entity.Material; +import com.sampoom.backend.api.material.entity.MaterialCategory; +import com.sampoom.backend.api.material.repository.MaterialCategoryRepository; +import com.sampoom.backend.api.material.repository.MaterialRepository; +import com.sampoom.backend.common.dto.PageResponseDTO; +import com.sampoom.backend.common.exception.NotFoundException; +import com.sampoom.backend.common.response.ErrorStatus; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class MaterialService { + + private final MaterialRepository materialRepository; + private final MaterialCategoryRepository categoryRepository; + + // 카테고리 목록 조회 + @Transactional(readOnly = true) + public List findAllCategories() { + + List categories = categoryRepository.findAll(); + + return categories.stream() + .map(MaterialCategoryResponseDTO::new) + .collect(Collectors.toList()); + } + + // 카테고리별 자재 목록 조회 + @Transactional + public PageResponseDTO findMaterialsByCategory(Long categoryId, int page, int size) { + + categoryRepository.findById(categoryId) + .orElseThrow(() -> new NotFoundException(ErrorStatus.CATEGORY_NOT_FOUND)); + + PageRequest pageRequest = PageRequest.of(page, size); + Page materials = materialRepository.findByMaterialCategoryId(categoryId, pageRequest); + + List dtoList = materials.stream() + .map(MaterialResponseDTO::new) + .collect(Collectors.toList()); + + return PageResponseDTO.builder() + .content(dtoList) + .totalElements(materials.getTotalElements()) + .totalPages(materials.getTotalPages()) + .currentPage(page) + .pageSize(size) + .build(); + } + + // 전체 자재 목록 조회 + @Transactional(readOnly = true) + public PageResponseDTO findAllMaterials(int page, int size) { + + PageRequest pageRequest = PageRequest.of(page, size); + Page materials = materialRepository.findAll(pageRequest); + + List dtoList = materials.stream() + .map(MaterialResponseDTO::new) + .collect(Collectors.toList()); + + return PageResponseDTO.builder() + .content(dtoList) + .totalElements(materials.getTotalElements()) + .totalPages(materials.getTotalPages()) + .currentPage(page) + .pageSize(size) + .build(); + } + + // 자재 생성 + @Transactional + public MaterialResponseDTO createMaterial(MaterialRequestDTO requestDTO) { + MaterialCategory category = categoryRepository.findById(requestDTO.getMaterialCategoryId()) + .orElseThrow(() -> new NotFoundException(ErrorStatus.CATEGORY_NOT_FOUND)); + + String materialCode = generateNextMaterialCode(category.getId()); + + Material material = Material.builder() + .name(requestDTO.getName()) + .materialCode(materialCode) + .materialUnit(requestDTO.getMaterialUnit()) + .materialCategory(category) + .build(); + + materialRepository.save(material); + + return new MaterialResponseDTO(material); + } + + // 자재 수정 + @Transactional + public MaterialResponseDTO updateMaterial(Long id, MaterialRequestDTO requestDTO) { + + // 자재 조회 + Material material = materialRepository.findById(id) + .orElseThrow(() -> new NotFoundException(ErrorStatus.MATERIAL_NOT_FOUND)); + + // 새 카테고리 조회 + MaterialCategory newCategory = categoryRepository.findById(requestDTO.getMaterialCategoryId()) + .orElseThrow(() -> new NotFoundException(ErrorStatus.CATEGORY_NOT_FOUND)); + + // 카테고리가 변경 시 코드 재발급 + if (!material.getMaterialCategory().getId().equals(newCategory.getId())) { + String nextCode = generateNextMaterialCode(newCategory.getId()); + material.changeCategory(newCategory, nextCode); + } + + // 나머지 필드 수정 + material.updateBasicInfo(requestDTO.getName(), requestDTO.getMaterialUnit()); + + return new MaterialResponseDTO(material); + } + + // 자재 삭제 + @Transactional + public void deleteMaterial(Long id) { + Material material = materialRepository.findById(id) + .orElseThrow(() -> new NotFoundException(ErrorStatus.MATERIAL_NOT_FOUND)); + materialRepository.delete(material); + } + + // 자재 검색 + @Transactional(readOnly = true) + public PageResponseDTO searchMaterials(String keyword, int page, int size) { + + PageRequest pageRequest = PageRequest.of(page, size); + Page materials; + + if (keyword == null || keyword.trim().isEmpty()) { + materials = materialRepository.findAll(pageRequest); + } else { + materials = materialRepository.findByNameContainingIgnoreCaseOrMaterialCodeContainingIgnoreCase( + keyword, keyword, pageRequest + ); + } + + List dtoList = materials.getContent().stream() + .map(MaterialResponseDTO::new) + .toList(); + + return PageResponseDTO.builder() + .content(dtoList) + .totalElements(materials.getTotalElements()) + .totalPages(materials.getTotalPages()) + .currentPage(page) + .pageSize(size) + .build(); + } + + @Transactional(readOnly = true) + public String generateNextMaterialCode(Long categoryId) { + + MaterialCategory category = categoryRepository.findById(categoryId) + .orElseThrow(() -> new NotFoundException(ErrorStatus.CATEGORY_NOT_FOUND)); + + // 최신 자재 찾기 + Material latest = materialRepository.findTopByMaterialCategoryIdOrderByIdDesc(categoryId); + String latestCode = (latest != null) ? latest.getMaterialCode() : null; + + int nextSeq = 1; + if (latest != null && latest.getMaterialCode() != null) { + String[] parts = latest.getMaterialCode().split("-"); + if (parts.length >= 2) { + try { + nextSeq = Integer.parseInt(parts[1]) + 1; + } catch (NumberFormatException ignored) { } + } + } + + return String.format("%s-%04d", category.getCode(), nextSeq); + } +} diff --git a/src/main/java/com/sampoom/backend/common/config/DataInitializer.java b/src/main/java/com/sampoom/backend/common/config/DataInitializer.java new file mode 100644 index 0000000..087d343 --- /dev/null +++ b/src/main/java/com/sampoom/backend/common/config/DataInitializer.java @@ -0,0 +1,100 @@ +package com.sampoom.backend.common.config; + +import com.opencsv.CSVReader; +import com.sampoom.backend.api.material.entity.Material; +import com.sampoom.backend.api.material.entity.MaterialCategory; +import com.sampoom.backend.api.material.repository.MaterialCategoryRepository; +import com.sampoom.backend.api.material.repository.MaterialRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.*; + +@Slf4j +@Component +@RequiredArgsConstructor +@Transactional +public class DataInitializer implements CommandLineRunner { + + private final MaterialRepository materialRepository; + private final MaterialCategoryRepository categoryRepository; + + // CSV의 카테고리명 → 카테고리 코드(prefix) 매핑 + private static final Map CATEGORY_PREFIX = Map.of( + "금속", "MTL", + "플라스틱/고무", "PLS", + "전기전자", "ELC", + "화학/소모품", "CHM" + ); + + @Override + public void run(String... args) throws Exception { + if (materialRepository.count() > 0) { + log.info("Material data already exists, skipping import."); + return; + } + + log.info("Importing CSV data into PostgreSQL..."); + + try (Reader reader = new InputStreamReader( + Objects.requireNonNull(getClass().getResourceAsStream("/data/materials_master.csv")), + StandardCharsets.UTF_8 + ); + CSVReader csvReader = new CSVReader(reader)) { + + List rows = csvReader.readAll(); + + if (!rows.isEmpty()) rows.remove(0); // 헤더 제거 + + Map categoryCache = new HashMap<>(); + + List toSave = new ArrayList<>(rows.size()); + + for (String[] row : rows) { + // CSV 스키마: id, category_id, category, code, name, unit + // String csvId = row[0]; // 사용 안 함 + // String csvCategoryId = row[1]; // 사용 안 함 (DB PK랑 불일치 가능) + String categoryName = row[2]; + String code = row[3]; + String name = row[4]; + String unitStr = row[5]; // "kg", "m" 등 + + // 1) 카테고리 upsert (code 기준) + String prefix = CATEGORY_PREFIX.getOrDefault(categoryName, "CAT"); + MaterialCategory category = categoryCache.get(prefix); + if (category == null) { + category = categoryRepository.findByCode(prefix).orElseGet(() -> + categoryRepository.save( + MaterialCategory.builder() + .name(categoryName) + .code(prefix) + .build() + ) + ); + categoryCache.put(prefix, category); + } + + // 2) 자재 엔티티 생성 + Material material = Material.builder() + .name(name) + .materialCode(code) + .materialUnit(unitStr) + .materialCategory(category) + .build(); + + toSave.add(material); + + } + + materialRepository.saveAll(toSave); + + log.info("CSV import completed. Inserted materials: " + materialRepository.count()); + } + } +} diff --git a/src/main/java/com/sampoom/backend/common/dto/PageResponseDTO.java b/src/main/java/com/sampoom/backend/common/dto/PageResponseDTO.java new file mode 100644 index 0000000..7393bd8 --- /dev/null +++ b/src/main/java/com/sampoom/backend/common/dto/PageResponseDTO.java @@ -0,0 +1,21 @@ +package com.sampoom.backend.common.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PageResponseDTO { + private List content; // 실제 데이터 + private long totalElements; // 전체 데이터 개수 + private int totalPages; // 전체 페이지 수 + private int currentPage; // 현재 페이지 번호 + private int pageSize; // 한 페이지당 데이터 수 +} + diff --git a/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java b/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java index e17884a..de53e94 100644 --- a/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java +++ b/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java @@ -23,6 +23,7 @@ public enum ErrorStatus { CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "카테고리를 찾을 수 없습니다.", 30401), GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "그룹을 찾을 수 없습니다.", 30402), PART_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 부품을 찾을 수 없습니다.", 30403), + MATERIAL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 자재를 찾을 수 없습니다.", 30403), // 409 CONFLICT CONFLICT(HttpStatus.CONFLICT, "충돌이 발생했습니다.",30900), diff --git a/src/main/resources/data/materials_master.csv b/src/main/resources/data/materials_master.csv new file mode 100644 index 0000000..915c833 --- /dev/null +++ b/src/main/resources/data/materials_master.csv @@ -0,0 +1,41 @@ +id,category_id,category,code,name,unit +1,1,금속,MTL-0001,냉간압연강판,kg +2,1,금속,MTL-0002,열간압연강판,kg +3,1,금속,MTL-0003,스테인리스강,kg +4,1,금속,MTL-0004,알루미늄 합금봉,kg +5,1,금속,MTL-0005,구리판,kg +6,1,금속,MTL-0006,마그네슘 합금,kg +7,1,금속,MTL-0007,아연 도금강판,kg +8,1,금속,MTL-0008,철선(강선),m +9,1,금속,MTL-0009,베어링강,kg +10,1,금속,MTL-0010,주철,kg +11,2,플라스틱/고무,PLS-0001,ABS 수지,kg +12,2,플라스틱/고무,PLS-0002,폴리프로필렌,kg +13,2,플라스틱/고무,PLS-0003,폴리우레탄,kg +14,2,플라스틱/고무,PLS-0004,고무 원자재,kg +15,2,플라스틱/고무,PLS-0005,실리콘,kg +16,2,플라스틱/고무,PLS-0006,나일론,kg +17,2,플라스틱/고무,PLS-0007,폴리에틸렌,kg +18,2,플라스틱/고무,PLS-0008,폴리카보네이트,kg +19,2,플라스틱/고무,PLS-0009,열가소성 엘라스토머,kg +20,2,플라스틱/고무,PLS-0010,EPDM 고무,kg +21,3,전기전자,ELC-0001,구리 전선,m +22,3,전기전자,ELC-0002,알루미늄 전선,m +23,3,전기전자,ELC-0003,커넥터 핀,EA +24,3,전기전자,ELC-0004,PCB 원판,EA +25,3,전기전자,ELC-0005,반도체 칩,EA +26,3,전기전자,ELC-0006,저항기,EA +27,3,전기전자,ELC-0007,콘덴서,EA +28,3,전기전자,ELC-0008,다이오드,EA +29,3,전기전자,ELC-0009,릴레이,EA +30,3,전기전자,ELC-0010,퓨즈,EA +31,4,화학/소모품,CHM-0001,도료(블랙),L +32,4,화학/소모품,CHM-0002,도료(화이트),L +33,4,화학/소모품,CHM-0003,접착제,L +34,4,화학/소모품,CHM-0004,실리콘실란트,L +35,4,화학/소모품,CHM-0005,윤활유,L +36,4,화학/소모품,CHM-0006,그리스,kg +37,4,화학/소모품,CHM-0007,냉각수,L +38,4,화학/소모품,CHM-0008,브레이크액,L +39,4,화학/소모품,CHM-0009,세정제,L +40,4,화학/소모품,CHM-0010,프라이머,L From 2e8363dfe0aba74ab2e4c3dd7dcdfbae199f52ed Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Tue, 28 Oct 2025 03:56:26 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[FIX]=20=EB=B6=80=ED=92=88=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/part/controller/PartController.java | 198 +++++++++--------- .../api/part/dto/CategoryResponseDTO.java | 16 -- .../api/part/dto/PartCategoryResponseDTO.java | 18 ++ .../api/part/dto/PartCreateRequestDTO.java | 3 - .../api/part/dto/PartGroupResponseDTO.java | 10 +- .../api/part/dto/PartListResponseDTO.java | 37 ++++ .../backend/api/part/dto/PartResponseDTO.java | 2 + .../api/part/dto/PartUpdateRequestDTO.java | 7 - .../sampoom/backend/api/part/entity/Part.java | 15 +- .../{Category.java => PartCategory.java} | 4 +- .../entity/{Group.java => PartGroup.java} | 6 +- ...itory.java => PartCategoryRepository.java} | 6 +- .../part/repository/PartGroupRepository.java | 10 +- .../api/part/repository/PartRepository.java | 23 +- .../backend/api/part/service/PartService.java | 137 ++++++++---- .../backend/common/config/CsvDataLoader.java | 20 +- 16 files changed, 304 insertions(+), 208 deletions(-) delete mode 100644 src/main/java/com/sampoom/backend/api/part/dto/CategoryResponseDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/part/dto/PartCategoryResponseDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/part/dto/PartListResponseDTO.java rename src/main/java/com/sampoom/backend/api/part/entity/{Category.java => PartCategory.java} (82%) rename src/main/java/com/sampoom/backend/api/part/entity/{Group.java => PartGroup.java} (76%) rename src/main/java/com/sampoom/backend/api/part/repository/{CategoryRepository.java => PartCategoryRepository.java} (51%) diff --git a/src/main/java/com/sampoom/backend/api/part/controller/PartController.java b/src/main/java/com/sampoom/backend/api/part/controller/PartController.java index 3bbc536..f9d9b99 100644 --- a/src/main/java/com/sampoom/backend/api/part/controller/PartController.java +++ b/src/main/java/com/sampoom/backend/api/part/controller/PartController.java @@ -1,99 +1,99 @@ -//package com.sampoom.backend.api.part.controller; -// -//import com.sampoom.backend.api.part.dto.*; -//import com.sampoom.backend.api.part.service.PartService; -//import com.sampoom.backend.common.response.ApiResponse; -//import com.sampoom.backend.common.response.SuccessStatus; -//import io.swagger.v3.oas.annotations.Operation; -//import io.swagger.v3.oas.annotations.tags.Tag; -//import jakarta.validation.Valid; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.http.ResponseEntity; -//import org.springframework.web.bind.annotation.*; -// -//import java.util.List; -// -//@Tag(name = "Part", description = "부품 API") -//@Slf4j -//@RestController -//@RequestMapping() -//@RequiredArgsConstructor -//public class PartController { -// -// private final PartService partService; -// -// @Operation(summary = "카테고리 목록 조회", description = "카테고리 목록 조회") -// @GetMapping("/categories") -// public ResponseEntity>> getCategories() { -// -// List categoryList = partService.findAllCategories(); -// -// return ApiResponse.success(SuccessStatus.CATEGORY_LIST_SUCCESS, categoryList); -// } -// -// @Operation(summary = "그룹 목록 조회", description = "카테고리에 속한 그룹 목록 조회") -// @GetMapping("/categories/{categoryId}/groups") -// public ResponseEntity>> getGroups(@PathVariable Long categoryId) { -// -// List groupList = partService.findGroupsByCategoryId(categoryId); -// -// return ApiResponse.success(SuccessStatus.GROUP_LIST_SUCCESS, groupList); -// } -// -// @Operation(summary = "부품 목록 조회", description = "특정 그룹에 속한 부품 목록 조회") -// @GetMapping -// public ResponseEntity>> getPartsByGroup(@RequestParam Long groupId) { -// -// List partList = partService.findPartsByGroupId(groupId); -// -// return ApiResponse.success(SuccessStatus.PART_LIST_SUCCESS, partList); -// } -// -// @Operation(summary = "부품 등록", description = "새로운 부품을 등록") -// @PostMapping -// public ResponseEntity> createPart( -// @Valid @RequestBody PartCreateRequestDTO partCreateRequestDTO -// ) { -// PartResponseDTO partResponse = partService.createPart(partCreateRequestDTO); -// -// return ApiResponse.success(SuccessStatus.PART_CREATE_SUCCESS, partResponse); -// } -// -// @Operation(summary = "부품 수정", description = "부품을 수정") -// @PutMapping("/{partId}") -// public ResponseEntity> updatePart( -// @PathVariable Long partId, -// @Valid @RequestBody PartUpdateRequestDTO partUpdateRequestDTO -// ) { -// PartResponseDTO partResponse = partService.updatePart(partId, partUpdateRequestDTO); -// -// return ApiResponse.success(SuccessStatus.PART_UPDATE_SUCCESS, partResponse); -// } -// -// @Operation(summary = "부품 삭제", description = "부품 삭제") -// @DeleteMapping("/{partId}") -// public ResponseEntity> deletePart(@PathVariable Long partId) { -// -// partService.deletePart(partId); -// -// return ApiResponse.success(SuccessStatus.PART_DELETE_SUCCESS, null); -// } -// -// @Operation(summary = "부품 검색", description = "부품 검색 (부품코드, 부품명)") -// @GetMapping("/search") -// public ResponseEntity>> searchParts(@RequestParam String keyword) { -// -// List partList = partService.searchParts(keyword); -// -// return ApiResponse.success(SuccessStatus.PART_SEARCH_SUCCESS, partList); -// } -// -// @Operation(summary = "단일 부품 조회", description = "부품 ID로 부품 상세 정보를 조회합니다.") -// @GetMapping("/{partId}") -// public ResponseEntity> getPartById(@PathVariable Long partId) { -// PartResponseDTO partResponse = partService.findPartById(partId); -// return ApiResponse.success(SuccessStatus.PART_DETAIL_SUCCESS, partResponse); -// } -// -//} +package com.sampoom.backend.api.part.controller; + +import com.sampoom.backend.api.part.dto.*; +import com.sampoom.backend.api.part.service.PartService; +import com.sampoom.backend.common.dto.PageResponseDTO; +import com.sampoom.backend.common.response.ApiResponse; +import com.sampoom.backend.common.response.SuccessStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Part", description = "부품 API") +@Slf4j +@RestController +@RequestMapping("/api/parts") +@RequiredArgsConstructor +public class PartController { + + private final PartService partService; + + @Operation(summary = "카테고리 목록 조회", description = "카테고리 목록 조회") + @GetMapping("/categories") + public ResponseEntity>> getCategories() { + + List categoryList = partService.findAllCategories(); + + return ApiResponse.success(SuccessStatus.CATEGORY_LIST_SUCCESS, categoryList); + } + + @Operation(summary = "카테고리별 그룹 목록 조회", description = "카테고리에 속한 그룹 목록 조회") + @GetMapping("/categories/{categoryId}/groups") + public ResponseEntity>> getGroups(@PathVariable Long categoryId) { + + List groupList = partService.findGroupsByCategoryId(categoryId); + + return ApiResponse.success(SuccessStatus.GROUP_LIST_SUCCESS, groupList); + } + + @Operation(summary = "카테고리별 부품 목록 조회 (그룹 안 정했을 때)", description = "카테고리 내 모든 그룹의 부품을 한 번에 조회합니다.") + @GetMapping("/categories/{categoryId}/parts") + public ResponseEntity>> getPartsByCategory( + @PathVariable Long categoryId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size + ) { + PageResponseDTO result = partService.findAllPartsByCategory(categoryId, page, size); + + return ApiResponse.success(SuccessStatus.PART_LIST_SUCCESS, result); + } + + @Operation(summary = "그룹별 부품 목록 조회", description = "특정 그룹에 속한 부품 목록 조회") + @GetMapping + public ResponseEntity>> getPartsByGroup( + @RequestParam Long groupId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + PageResponseDTO partList = partService.findPartsByGroup(groupId, page, size); + + return ApiResponse.success(SuccessStatus.PART_LIST_SUCCESS, partList); + } + + @Operation(summary = "부품 등록", description = "새로운 부품을 등록") + @PostMapping + public ResponseEntity> createPart( + @Valid @RequestBody PartCreateRequestDTO partCreateRequestDTO + ) { + PartListResponseDTO partResponse = partService.createPart(partCreateRequestDTO); + + return ApiResponse.success(SuccessStatus.PART_CREATE_SUCCESS, partResponse); + } + + @Operation(summary = "부품 수정", description = "부품을 수정") + @PutMapping("/{partId}") + public ResponseEntity> updatePart( + @PathVariable Long partId, + @Valid @RequestBody PartUpdateRequestDTO partUpdateRequestDTO + ) { + PartListResponseDTO partResponse = partService.updatePart(partId, partUpdateRequestDTO); + + return ApiResponse.success(SuccessStatus.PART_UPDATE_SUCCESS, partResponse); + } + + @Operation(summary = "부품 삭제", description = "부품 삭제") + @DeleteMapping("/{partId}") + public ResponseEntity> deletePart(@PathVariable Long partId) { + + partService.deletePart(partId); + + return ApiResponse.success(SuccessStatus.PART_DELETE_SUCCESS, null); + } + +} diff --git a/src/main/java/com/sampoom/backend/api/part/dto/CategoryResponseDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/CategoryResponseDTO.java deleted file mode 100644 index 7f30d68..0000000 --- a/src/main/java/com/sampoom/backend/api/part/dto/CategoryResponseDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.sampoom.backend.api.part.dto; - -import com.sampoom.backend.api.part.entity.Category; -import lombok.Getter; - -@Getter -public class CategoryResponseDTO { - - private Long categoryId; - private String name; - - public CategoryResponseDTO(Category category) { - this.categoryId = category.getId(); - this.name = category.getName(); - } -} diff --git a/src/main/java/com/sampoom/backend/api/part/dto/PartCategoryResponseDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartCategoryResponseDTO.java new file mode 100644 index 0000000..4d295d7 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartCategoryResponseDTO.java @@ -0,0 +1,18 @@ +package com.sampoom.backend.api.part.dto; + +import com.sampoom.backend.api.part.entity.PartCategory; +import lombok.Getter; + +@Getter +public class PartCategoryResponseDTO { + + private Long categoryId; + private String categoryName; + private String name; + + public PartCategoryResponseDTO(PartCategory category) { + this.categoryId = category.getId(); + this.categoryName = category.getName(); + this.name = category.getName(); + } +} diff --git a/src/main/java/com/sampoom/backend/api/part/dto/PartCreateRequestDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartCreateRequestDTO.java index 96c5ed4..1f255ed 100644 --- a/src/main/java/com/sampoom/backend/api/part/dto/PartCreateRequestDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartCreateRequestDTO.java @@ -14,9 +14,6 @@ public class PartCreateRequestDTO { @NotNull(message = "소속될 그룹 ID는 필수입니다.") private Long groupId; - @NotBlank(message = "부품 코드는 필수입니다.") - private String code; - @NotBlank(message = "부품 이름은 필수입니다.") private String name; } diff --git a/src/main/java/com/sampoom/backend/api/part/dto/PartGroupResponseDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartGroupResponseDTO.java index ace2763..37cd1eb 100644 --- a/src/main/java/com/sampoom/backend/api/part/dto/PartGroupResponseDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartGroupResponseDTO.java @@ -1,18 +1,20 @@ package com.sampoom.backend.api.part.dto; -import com.sampoom.backend.api.part.entity.Group; +import com.sampoom.backend.api.part.entity.PartGroup; import lombok.Getter; @Getter public class PartGroupResponseDTO { private Long groupId; - private String name; + private String groupName; private Long categoryId; + private String categoryName; - public PartGroupResponseDTO(Group partGroup) { + public PartGroupResponseDTO(PartGroup partGroup) { this.groupId = partGroup.getId(); - this.name = partGroup.getName(); + this.groupName = partGroup.getName(); this.categoryId = partGroup.getCategory().getId(); + this.categoryName = partGroup.getCategory().getName(); } } diff --git a/src/main/java/com/sampoom/backend/api/part/dto/PartListResponseDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartListResponseDTO.java new file mode 100644 index 0000000..e2c7158 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartListResponseDTO.java @@ -0,0 +1,37 @@ +package com.sampoom.backend.api.part.dto; + +import com.sampoom.backend.api.part.entity.Part; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class PartListResponseDTO { + + private Long partId; + private String code; + private String name; + private String status; + + private Long groupId; + private String groupName; + private Long categoryId; + private String categoryName; + + public PartListResponseDTO(Part part) { + this.partId = part.getId(); + this.code = part.getCode(); + this.name = part.getName(); + this.status = part.getStatus().name(); + + this.groupId = part.getPartGroup().getId(); + this.groupName = part.getPartGroup().getName(); + + this.categoryId = part.getPartGroup().getCategory().getId(); + this.categoryName = part.getPartGroup().getCategory().getName(); + } +} diff --git a/src/main/java/com/sampoom/backend/api/part/dto/PartResponseDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartResponseDTO.java index da77906..b7a1562 100644 --- a/src/main/java/com/sampoom/backend/api/part/dto/PartResponseDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartResponseDTO.java @@ -10,11 +10,13 @@ public class PartResponseDTO { private String name; private String code; private Long groupId; + private String groupName; public PartResponseDTO(Part part) { this.partId = part.getId(); this.name = part.getName(); this.code = part.getCode(); this.groupId = part.getPartGroup().getId(); + this.groupName = part.getPartGroup().getName(); } } diff --git a/src/main/java/com/sampoom/backend/api/part/dto/PartUpdateRequestDTO.java b/src/main/java/com/sampoom/backend/api/part/dto/PartUpdateRequestDTO.java index 32a837f..a3c2a0d 100644 --- a/src/main/java/com/sampoom/backend/api/part/dto/PartUpdateRequestDTO.java +++ b/src/main/java/com/sampoom/backend/api/part/dto/PartUpdateRequestDTO.java @@ -12,11 +12,4 @@ public class PartUpdateRequestDTO { private String name; private PartStatus status; - - public void update(PartUpdateRequestDTO partUpdateRequestDTO) { - - if (partUpdateRequestDTO.getStatus() != null) { - this.status = partUpdateRequestDTO.getStatus(); - } - } } diff --git a/src/main/java/com/sampoom/backend/api/part/entity/Part.java b/src/main/java/com/sampoom/backend/api/part/entity/Part.java index 05c6330..f133160 100644 --- a/src/main/java/com/sampoom/backend/api/part/entity/Part.java +++ b/src/main/java/com/sampoom/backend/api/part/entity/Part.java @@ -28,27 +28,16 @@ public class Part extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "group_id", nullable = false) - private Group partGroup; // PartGroup 엔티티와 N:1 관계 + private PartGroup partGroup; // PartGroup 엔티티와 N:1 관계 // CSV 로더가 사용할 생성자 - public Part(String code, String name, Group partGroup) { + public Part(String code, String name, PartGroup partGroup) { this.code = code; this.name = name; this.partGroup = partGroup; this.status = PartStatus.ACTIVE; } - // 생성 메서드 - public static Part create(PartCreateRequestDTO partCreateRequestDTO, Group partGroup) { - Part part = new Part(); - part.code = partCreateRequestDTO.getCode(); - part.name = partCreateRequestDTO.getName(); - part.partGroup = partGroup; // 연관관계 설정 - part.status = PartStatus.ACTIVE; - - return part; - } - // 수정 메서드 public void update(PartUpdateRequestDTO partUpdateRequestDTO) { // 이름이 null이 아닐 경우에만 수정 diff --git a/src/main/java/com/sampoom/backend/api/part/entity/Category.java b/src/main/java/com/sampoom/backend/api/part/entity/PartCategory.java similarity index 82% rename from src/main/java/com/sampoom/backend/api/part/entity/Category.java rename to src/main/java/com/sampoom/backend/api/part/entity/PartCategory.java index 7702389..7f7839d 100644 --- a/src/main/java/com/sampoom/backend/api/part/entity/Category.java +++ b/src/main/java/com/sampoom/backend/api/part/entity/PartCategory.java @@ -9,7 +9,7 @@ @Getter @NoArgsConstructor @Table(name = "part_category") -public class Category extends BaseTimeEntity { +public class PartCategory extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -20,7 +20,7 @@ public class Category extends BaseTimeEntity { private String code; // CSV 로더가 사용할 생성자 - public Category(String code, String name) { + public PartCategory(String code, String name) { this.code = code; this.name = name; } diff --git a/src/main/java/com/sampoom/backend/api/part/entity/Group.java b/src/main/java/com/sampoom/backend/api/part/entity/PartGroup.java similarity index 76% rename from src/main/java/com/sampoom/backend/api/part/entity/Group.java rename to src/main/java/com/sampoom/backend/api/part/entity/PartGroup.java index b7fcba4..c13d713 100644 --- a/src/main/java/com/sampoom/backend/api/part/entity/Group.java +++ b/src/main/java/com/sampoom/backend/api/part/entity/PartGroup.java @@ -9,7 +9,7 @@ @Getter @NoArgsConstructor @Table(name = "part_group") -public class Group extends BaseTimeEntity { +public class PartGroup extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -21,10 +21,10 @@ public class Group extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "category_id", nullable = false) - private Category category; // Category 엔티티와 N:1 관계 + private PartCategory category; // Category 엔티티와 N:1 관계 // CSV 로더가 사용할 생성자 - public Group(String code, String name, Category category) { + public PartGroup(String code, String name, PartCategory category) { this.code = code; this.name = name; this.category = category; diff --git a/src/main/java/com/sampoom/backend/api/part/repository/CategoryRepository.java b/src/main/java/com/sampoom/backend/api/part/repository/PartCategoryRepository.java similarity index 51% rename from src/main/java/com/sampoom/backend/api/part/repository/CategoryRepository.java rename to src/main/java/com/sampoom/backend/api/part/repository/PartCategoryRepository.java index 42c369f..d954dc1 100644 --- a/src/main/java/com/sampoom/backend/api/part/repository/CategoryRepository.java +++ b/src/main/java/com/sampoom/backend/api/part/repository/PartCategoryRepository.java @@ -1,12 +1,12 @@ package com.sampoom.backend.api.part.repository; -import com.sampoom.backend.api.part.entity.Category; +import com.sampoom.backend.api.part.entity.PartCategory; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; -public interface CategoryRepository extends JpaRepository { +public interface PartCategoryRepository extends JpaRepository { // 코드로 카테고리를 찾기 위한 메서드 - Optional findByCode(String code); + Optional findByCode(String code); } diff --git a/src/main/java/com/sampoom/backend/api/part/repository/PartGroupRepository.java b/src/main/java/com/sampoom/backend/api/part/repository/PartGroupRepository.java index 4c78ede..896faee 100644 --- a/src/main/java/com/sampoom/backend/api/part/repository/PartGroupRepository.java +++ b/src/main/java/com/sampoom/backend/api/part/repository/PartGroupRepository.java @@ -1,17 +1,17 @@ package com.sampoom.backend.api.part.repository; -import com.sampoom.backend.api.part.entity.Category; -import com.sampoom.backend.api.part.entity.Group; +import com.sampoom.backend.api.part.entity.PartCategory; +import com.sampoom.backend.api.part.entity.PartGroup; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; import java.util.Optional; -public interface PartGroupRepository extends JpaRepository { +public interface PartGroupRepository extends JpaRepository { // 카테고리 id로 모든 그룹 찾는 메서드 - List findByCategoryId(Long categoryId); + List findByCategoryId(Long categoryId); // 카테고리와 코드로 그룹을 찾기 위한 메서드 - Optional findByCodeAndCategory(String code, Category category); + Optional findByCodeAndCategory(String code, PartCategory category); } diff --git a/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java b/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java index 655d87e..6d66791 100644 --- a/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java +++ b/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java @@ -2,6 +2,8 @@ import com.sampoom.backend.api.part.entity.PartStatus; import com.sampoom.backend.api.part.entity.Part; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -10,11 +12,22 @@ public interface PartRepository extends JpaRepository { - // 그룹 ID로 모든 부품을 찾는 메서드 - List findByPartGroupId(Long groupId); + // 카테고리별 부품 조회 + Page findByPartGroupCategoryIdAndStatus(Long categoryId, PartStatus status, Pageable pageable); - List findByPartGroupIdAndStatus(Long groupId, PartStatus status); + // 이름 or 코드로 검색 + ACTIVE 상태만 + Page findByNameContainingIgnoreCaseOrCodeContainingIgnoreCaseAndStatus( + String nameKeyword, + String codeKeyword, + PartStatus status, + Pageable pageable + ); - @Query("SELECT p FROM Part p WHERE (p.code LIKE %:keyword% OR p.name LIKE %:keyword%) AND p.status = 'ACTIVE'") - List searchByKeyword(@Param("keyword") String keyword); + // 그룹별 부품 조회 + Page findByPartGroupId(Long groupId, Pageable pageable); + + // 가장 최근 부품 (코드 자동 생성용) + Part findTopByPartGroupIdOrderByIdDesc(Long groupId); + + Part findTopByPartGroupIdOrderByCodeDesc(Long groupId); } diff --git a/src/main/java/com/sampoom/backend/api/part/service/PartService.java b/src/main/java/com/sampoom/backend/api/part/service/PartService.java index 64583fb..dc1f506 100644 --- a/src/main/java/com/sampoom/backend/api/part/service/PartService.java +++ b/src/main/java/com/sampoom/backend/api/part/service/PartService.java @@ -1,16 +1,19 @@ package com.sampoom.backend.api.part.service; import com.sampoom.backend.api.part.dto.*; -import com.sampoom.backend.api.part.entity.Category; +import com.sampoom.backend.api.part.entity.PartCategory; import com.sampoom.backend.api.part.entity.Part; -import com.sampoom.backend.api.part.entity.Group; +import com.sampoom.backend.api.part.entity.PartGroup; import com.sampoom.backend.api.part.entity.PartStatus; -import com.sampoom.backend.api.part.repository.CategoryRepository; +import com.sampoom.backend.api.part.repository.PartCategoryRepository; import com.sampoom.backend.api.part.repository.PartGroupRepository; import com.sampoom.backend.api.part.repository.PartRepository; +import com.sampoom.backend.common.dto.PageResponseDTO; import com.sampoom.backend.common.exception.NotFoundException; import com.sampoom.backend.common.response.ErrorStatus; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,66 +26,95 @@ public class PartService { private final PartRepository partRepository; private final PartGroupRepository partGroupRepository; - private final CategoryRepository categoryRepository; + private final PartCategoryRepository categoryRepository; // 카테고리 목록 조회 @Transactional - public List findAllCategories() { + public List findAllCategories() { - List categories = categoryRepository.findAll(); + List categories = categoryRepository.findAll(); return categories.stream() - .map(CategoryResponseDTO::new) + .map(PartCategoryResponseDTO::new) .collect(Collectors.toList()); } - // 카테고리에 속한 그룹 목록 조회 + // 카테고리별 그룹 목록 조회 @Transactional public List findGroupsByCategoryId(Long categoryId) { - List partGroups = partGroupRepository.findByCategoryId(categoryId); + List partGroups = partGroupRepository.findByCategoryId(categoryId); return partGroups.stream() .map(PartGroupResponseDTO::new) .collect(Collectors.toList()); } + // 카테고리에 속한 모든 그룹 부품 목록 조회 + @Transactional(readOnly = true) + public PageResponseDTO findAllPartsByCategory(Long categoryId, int page, int size) { - // 특정 그룹에 속한 부품 목록 조회 - @Transactional - public List findPartsByGroupId(Long groupId) { + PageRequest pageRequest = PageRequest.of(page, size); + Page partsPage = partRepository.findByPartGroupCategoryIdAndStatus(categoryId, PartStatus.ACTIVE, pageRequest); - List parts = partRepository.findByPartGroupIdAndStatus(groupId, PartStatus.ACTIVE); + List dtoList = partsPage.getContent().stream() + .map(PartListResponseDTO::new) + .toList(); - return parts.stream() - .map(PartResponseDTO::new) - .collect(Collectors.toList()); + return PageResponseDTO.builder() + .content(dtoList) + .totalElements(partsPage.getTotalElements()) + .totalPages(partsPage.getTotalPages()) + .currentPage(page) + .pageSize(size) + .build(); + } + + // 그룹별 부품 목록 조회 + @Transactional + public PageResponseDTO findPartsByGroup(Long groupId, int page, int size) { + + PageRequest pageRequest = PageRequest.of(page, size); + Page parts = partRepository.findByPartGroupId(groupId, pageRequest); + + List dtoList = parts.stream().map(PartListResponseDTO::new).toList(); + return PageResponseDTO.builder() + .content(dtoList) + .totalElements(parts.getTotalElements()) + .totalPages(parts.getTotalPages()) + .currentPage(page) + .pageSize(size) + .build(); } - // 신규 부품 생성 + // 신규 부품 등록 @Transactional - public PartResponseDTO createPart(PartCreateRequestDTO partCreateRequestDTO) { + public PartListResponseDTO createPart(PartCreateRequestDTO partCreateRequestDTO) { // DTO에 담겨온 groupId로 PartGroup 엔티티 조회 - Group partGroup = partGroupRepository.findById(partCreateRequestDTO.getGroupId()) - .orElseThrow(() -> new NotFoundException(ErrorStatus.GROUP_NOT_FOUND.getMessage())); + PartGroup partGroup = partGroupRepository.findById(partCreateRequestDTO.getGroupId()) + .orElseThrow(() -> new NotFoundException(ErrorStatus.GROUP_NOT_FOUND)); + + // 코드 자동 생성 + String nextCode = generateNextPartCode(partGroup.getId()); + + Part newPart = new Part(nextCode, partCreateRequestDTO.getName(), partGroup); - Part newPart = Part.create(partCreateRequestDTO, partGroup); partRepository.save(newPart); - return new PartResponseDTO(newPart); + return new PartListResponseDTO(newPart); } // 부품 수정 @Transactional - public PartResponseDTO updatePart(Long partId, PartUpdateRequestDTO partUpdateRequestDTO) { + public PartListResponseDTO updatePart(Long partId, PartUpdateRequestDTO partUpdateRequestDTO) { // 수정할 부품을 조회 Part part = partRepository.findById(partId) .orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND.getMessage())); part.update(partUpdateRequestDTO); - return new PartResponseDTO(part); + return new PartListResponseDTO(part); } // 부품 삭제 @@ -91,28 +123,57 @@ public void deletePart(Long partId) { // 삭제할 부품 조회 Part part = partRepository.findById(partId) - .orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND.getMessage())); + .orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND)); - part.delete(); + partRepository.delete(part); } // 부품 검색 @Transactional - public List searchParts(String keyword) { + public PageResponseDTO searchParts(String keyword, int page, int size) { + PageRequest pageRequest = PageRequest.of(page, size); + + Page parts = partRepository.findByNameContainingIgnoreCaseOrCodeContainingIgnoreCaseAndStatus( + keyword, keyword, PartStatus.ACTIVE, pageRequest + ); + + List dtoList = parts.getContent().stream() + .map(PartListResponseDTO::new) + .toList(); + + return PageResponseDTO.builder() + .content(dtoList) + .totalElements(parts.getTotalElements()) + .totalPages(parts.getTotalPages()) + .currentPage(page) + .pageSize(size) + .build(); + } - List parts = partRepository.searchByKeyword(keyword); + // 코드 생성 + @Transactional(readOnly = true) + public String generateNextPartCode(Long groupId) { - return parts.stream() - .map(PartResponseDTO::new) - .collect(Collectors.toList()); - } + PartGroup group = partGroupRepository.findById(groupId) + .orElseThrow(() -> new NotFoundException(ErrorStatus.GROUP_NOT_FOUND)); - // 단일 부품 조회 - @Transactional - public PartResponseDTO findPartById(Long partId) { - Part part = partRepository.findById(partId) - .orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND.getMessage())); + String categoryCode = group.getCategory().getCode(); // ENG, TRM 등 + String groupCode = String.format("%02d", group.getId()); // 그룹 ID 두 자리로 포맷 + + // 최신 부품 코드 조회 (code 순 정렬 기준) + Part latest = partRepository.findTopByPartGroupIdOrderByCodeDesc(groupId); - return new PartResponseDTO(part); + int nextSeq = 1; + if (latest != null && latest.getCode() != null) { + String[] parts = latest.getCode().split("-"); + if (parts.length == 3) { + try { + nextSeq = Integer.parseInt(parts[2]) + 1; + } catch (NumberFormatException ignored) {} + } + } + + return String.format("%s-%s-%03d", categoryCode, groupCode, nextSeq); } + } diff --git a/src/main/java/com/sampoom/backend/common/config/CsvDataLoader.java b/src/main/java/com/sampoom/backend/common/config/CsvDataLoader.java index b32693a..4a39367 100644 --- a/src/main/java/com/sampoom/backend/common/config/CsvDataLoader.java +++ b/src/main/java/com/sampoom/backend/common/config/CsvDataLoader.java @@ -1,10 +1,10 @@ package com.sampoom.backend.common.config; import com.opencsv.CSVReader; -import com.sampoom.backend.api.part.entity.Category; +import com.sampoom.backend.api.part.entity.PartCategory; import com.sampoom.backend.api.part.entity.Part; -import com.sampoom.backend.api.part.entity.Group; -import com.sampoom.backend.api.part.repository.CategoryRepository; +import com.sampoom.backend.api.part.entity.PartGroup; +import com.sampoom.backend.api.part.repository.PartCategoryRepository; import com.sampoom.backend.api.part.repository.PartGroupRepository; import com.sampoom.backend.api.part.repository.PartRepository; import lombok.RequiredArgsConstructor; @@ -23,7 +23,7 @@ @RequiredArgsConstructor public class CsvDataLoader implements CommandLineRunner { - private final CategoryRepository categoryRepository; + private final PartCategoryRepository categoryRepository; private final PartGroupRepository partGroupRepository; private final PartRepository partRepository; @@ -49,16 +49,16 @@ public void run(String... args) throws Exception { reader.readNext(); // 헤더 행 건너뛰기 String[] line; - Map categoryCache = new HashMap<>(); - Map groupCache = new HashMap<>(); + Map categoryCache = new HashMap<>(); + Map groupCache = new HashMap<>(); while ((line = reader.readNext()) != null) { final String[] currentLine = line; String categoryCode = currentLine[CATEGORY_CODE]; - Category category = categoryCache.computeIfAbsent(categoryCode, code -> + PartCategory category = categoryCache.computeIfAbsent(categoryCode, code -> categoryRepository.findByCode(code).orElseGet(() -> - categoryRepository.save(new Category(code, currentLine[CATEGORY_NAME])) + categoryRepository.save(new PartCategory(code, currentLine[CATEGORY_NAME])) ) ); @@ -66,9 +66,9 @@ public void run(String... args) throws Exception { String compositeGroupKey = categoryCode + "-" + groupCode; // 그룹코드만 하면 중복되니깐 '카테고리코드-그룹코드' - Group partGroup = groupCache.computeIfAbsent(compositeGroupKey, key -> + PartGroup partGroup = groupCache.computeIfAbsent(compositeGroupKey, key -> partGroupRepository.findByCodeAndCategory(groupCode, category).orElseGet(() -> - partGroupRepository.save(new Group(groupCode, currentLine[GROUP_NAME], category)) + partGroupRepository.save(new PartGroup(groupCode, currentLine[GROUP_NAME], category)) ) ); From 7539260c304d5eab007f89fec6207f07140d6af1 Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Tue, 28 Oct 2025 03:57:38 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[FEAT]=20=EB=B6=80=ED=92=88,=20=EC=9E=90?= =?UTF-8?q?=EC=9E=AC=20=ED=86=B5=ED=95=A9=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/controller/ItemSearchController.java | 35 ++++++++++ .../backend/api/item/dto/ItemResponseDTO.java | 42 ++++++++++++ .../backend/api/item/enums/ItemType.java | 5 ++ .../api/item/service/ItemSearchService.java | 65 +++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 src/main/java/com/sampoom/backend/api/item/controller/ItemSearchController.java create mode 100644 src/main/java/com/sampoom/backend/api/item/dto/ItemResponseDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/item/enums/ItemType.java create mode 100644 src/main/java/com/sampoom/backend/api/item/service/ItemSearchService.java diff --git a/src/main/java/com/sampoom/backend/api/item/controller/ItemSearchController.java b/src/main/java/com/sampoom/backend/api/item/controller/ItemSearchController.java new file mode 100644 index 0000000..717a57a --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/item/controller/ItemSearchController.java @@ -0,0 +1,35 @@ +package com.sampoom.backend.api.item.controller; + +import com.sampoom.backend.api.item.dto.ItemResponseDTO; +import com.sampoom.backend.api.item.enums.ItemType; +import com.sampoom.backend.api.item.service.ItemSearchService; +import com.sampoom.backend.common.dto.PageResponseDTO; +import com.sampoom.backend.common.response.ApiResponse; +import com.sampoom.backend.common.response.SuccessStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Item", description = "자재/부품 통합 검색 API") +@RestController +@RequestMapping("/api/items") +@RequiredArgsConstructor +public class ItemSearchController { + + private final ItemSearchService itemSearchService; + + @Operation(summary = "통합 검색", description = "자재와 부품을 품목명 또는 코드 기준으로 통합 검색합니다.") + @GetMapping("/search") + public ResponseEntity>> searchItems( + @RequestParam(required = false) String keyword, + @RequestParam(defaultValue = "ALL") ItemType type, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + PageResponseDTO result = itemSearchService.searchItems(keyword, type, page, size); + + return ApiResponse.success(SuccessStatus.OK, result); + } +} diff --git a/src/main/java/com/sampoom/backend/api/item/dto/ItemResponseDTO.java b/src/main/java/com/sampoom/backend/api/item/dto/ItemResponseDTO.java new file mode 100644 index 0000000..c382ef3 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/item/dto/ItemResponseDTO.java @@ -0,0 +1,42 @@ +package com.sampoom.backend.api.item.dto; + +import com.sampoom.backend.api.material.dto.MaterialResponseDTO; +import com.sampoom.backend.api.part.dto.PartListResponseDTO; +import lombok.*; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ItemResponseDTO { + private String type; // 원자재 / 부품 + private String code; + private String name; + private String categoryName; + private String groupName; + private String unit; + private String status; + + public static ItemResponseDTO ofMaterial(MaterialResponseDTO m) { + return ItemResponseDTO.builder() + .type("원자재") + .code(m.getMaterialCode()) + .name(m.getName()) + .categoryName(m.getMaterialCategoryName()) + .groupName(null) // 자재는 그룹 없음 + .unit(m.getMaterialUnit()) + .build(); + } + + public static ItemResponseDTO ofPart(PartListResponseDTO p) { + return ItemResponseDTO.builder() + .type("부품") + .code(p.getCode()) + .name(p.getName()) + .categoryName(p.getCategoryName()) + .groupName(p.getGroupName()) + .unit(null) + .status(p.getStatus()) + .build(); + } +} diff --git a/src/main/java/com/sampoom/backend/api/item/enums/ItemType.java b/src/main/java/com/sampoom/backend/api/item/enums/ItemType.java new file mode 100644 index 0000000..72ef930 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/item/enums/ItemType.java @@ -0,0 +1,5 @@ +package com.sampoom.backend.api.item.enums; + +public enum ItemType { + ALL, MATERIAL, PART +} diff --git a/src/main/java/com/sampoom/backend/api/item/service/ItemSearchService.java b/src/main/java/com/sampoom/backend/api/item/service/ItemSearchService.java new file mode 100644 index 0000000..aa77ecc --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/item/service/ItemSearchService.java @@ -0,0 +1,65 @@ +package com.sampoom.backend.api.item.service; + +import com.sampoom.backend.api.item.enums.ItemType; +import com.sampoom.backend.api.material.service.MaterialService; +import com.sampoom.backend.api.part.service.PartService; +import com.sampoom.backend.api.item.dto.ItemResponseDTO; +import com.sampoom.backend.common.dto.PageResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class ItemSearchService { + + private final MaterialService materialService; + private final PartService partService; + + public PageResponseDTO searchItems(String keyword, ItemType type, int page, int size) { + + List allResults = new ArrayList<>(); + + switch (type) { + case MATERIAL -> // 자재만 + materialService.searchMaterials(keyword, page, size) + .getContent() + .forEach(m -> allResults.add(ItemResponseDTO.ofMaterial(m))); + + case PART -> // 부품만 + partService.searchParts(keyword, page, size) + .getContent() + .forEach(p -> allResults.add(ItemResponseDTO.ofPart(p))); + + case ALL -> { // 전체 + materialService.searchMaterials(keyword, page, size) + .getContent() + .forEach(m -> allResults.add(ItemResponseDTO.ofMaterial(m))); + partService.searchParts(keyword, page, size) + .getContent() + .forEach(p -> allResults.add(ItemResponseDTO.ofPart(p))); + } + } + + // 병합 결과 정렬 (코드순 or 이름순) + allResults.sort(Comparator.comparing(ItemResponseDTO::getCode)); + + // 페이지네이션 수동 처리 + int from = page * size; + int to = Math.min(from + size, allResults.size()); + List pageList = from < allResults.size() ? allResults.subList(from, to) : List.of(); + + return PageResponseDTO.builder() + .content(pageList) + .totalElements(allResults.size()) + .totalPages((int) Math.ceil((double) allResults.size() / size)) + .currentPage(page) + .pageSize(size) + .build(); + } +} From 61ebbea8992d5e25de99070ad406e0f71fa9a365 Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Tue, 28 Oct 2025 10:38:46 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[FEAT]=20BOM=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/bom/controller/BomController.java | 72 ++++++++ .../api/bom/dto/BomDetailResponseDTO.java | 61 +++++++ .../backend/api/bom/dto/BomRequestDTO.java | 26 +++ .../backend/api/bom/dto/BomResponseDTO.java | 65 +++++++ .../sampoom/backend/api/bom/entity/Bom.java | 42 +++++ .../backend/api/bom/entity/BomMaterial.java | 43 +++++ .../bom/repository/BomMaterialRepository.java | 7 + .../api/bom/repository/BomRepository.java | 36 ++++ .../backend/api/bom/service/BomService.java | 167 ++++++++++++++++++ .../backend/common/response/ErrorStatus.java | 1 + 10 files changed, 520 insertions(+) create mode 100644 src/main/java/com/sampoom/backend/api/bom/controller/BomController.java create mode 100644 src/main/java/com/sampoom/backend/api/bom/dto/BomDetailResponseDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/bom/dto/BomRequestDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/bom/dto/BomResponseDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/bom/entity/Bom.java create mode 100644 src/main/java/com/sampoom/backend/api/bom/entity/BomMaterial.java create mode 100644 src/main/java/com/sampoom/backend/api/bom/repository/BomMaterialRepository.java create mode 100644 src/main/java/com/sampoom/backend/api/bom/repository/BomRepository.java create mode 100644 src/main/java/com/sampoom/backend/api/bom/service/BomService.java diff --git a/src/main/java/com/sampoom/backend/api/bom/controller/BomController.java b/src/main/java/com/sampoom/backend/api/bom/controller/BomController.java new file mode 100644 index 0000000..93dcc2a --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/controller/BomController.java @@ -0,0 +1,72 @@ +package com.sampoom.backend.api.bom.controller; + +import com.sampoom.backend.api.bom.dto.BomDetailResponseDTO; +import com.sampoom.backend.api.bom.dto.BomRequestDTO; +import com.sampoom.backend.api.bom.dto.BomResponseDTO; +import com.sampoom.backend.api.bom.service.BomService; +import com.sampoom.backend.common.dto.PageResponseDTO; +import com.sampoom.backend.common.response.ApiResponse; +import com.sampoom.backend.common.response.SuccessStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "BOM", description = "BOM API") +@RestController +@RequestMapping("/api/boms") +@RequiredArgsConstructor +public class BomController { + + private final BomService bomService; + + @Operation(summary = "BOM 추가", description = "새로운 BOM을 등록합니다.") + @PostMapping + public ResponseEntity> createOrUpdateBom(@RequestBody BomRequestDTO bomRequestDTO) { + return ApiResponse.success(SuccessStatus.CREATED, bomService.createOrUpdateBom(bomRequestDTO)); + } + + @Operation(summary = "BOM 목록 조회", description = "페이징 처리된 BOM 목록을 조회하고, 카테고리나 그룹으로 필터링합니다.") + @GetMapping + public ResponseEntity>> getBoms( + @RequestParam(required = false) Long categoryId, + @RequestParam(required = false) Long groupId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return ApiResponse.success(SuccessStatus.OK, bomService.searchBoms(null, categoryId, groupId, page, size)); + } + + @Operation(summary = "BOM 상세 조회", description = "특정 BOM의 상세 정보를 조회합니다.") + @GetMapping("/{bomId}") + public ResponseEntity> getBomDetail(@PathVariable Long bomId) { + return ApiResponse.success(SuccessStatus.OK, bomService.getBomDetail(bomId)); + } + + @Operation(summary = "BOM 수정", description = "특정 BOM 정보를 수정합니다.") + @PutMapping("/{bomId}") + public ResponseEntity> updateBom( + @PathVariable Long bomId, + @RequestBody BomRequestDTO bomRequestDTO) { + return ApiResponse.success(SuccessStatus.OK, bomService.updateBom(bomId, bomRequestDTO)); + } + + @Operation(summary = "BOM 삭제", description = "특정 BOM을 삭제합니다.") + @DeleteMapping("/{bomId}") + public ResponseEntity> deleteBom(@PathVariable Long bomId) { + bomService.deleteBom(bomId); + return ApiResponse.success_only(SuccessStatus.OK); + } + + @Operation(summary = "BOM 검색", description = "부품 이름 또는 부품 코드로 BOM을 검색하고, 카테고리나 그룹으로 필터링합니다.") + @GetMapping("/search") + public ResponseEntity>> searchBoms( + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Long categoryId, + @RequestParam(required = false) Long groupId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + return ApiResponse.success(SuccessStatus.OK, + bomService.searchBoms(keyword, categoryId, groupId, page, size)); + } +} diff --git a/src/main/java/com/sampoom/backend/api/bom/dto/BomDetailResponseDTO.java b/src/main/java/com/sampoom/backend/api/bom/dto/BomDetailResponseDTO.java new file mode 100644 index 0000000..ba92b91 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/dto/BomDetailResponseDTO.java @@ -0,0 +1,61 @@ +package com.sampoom.backend.api.bom.dto; + +import com.sampoom.backend.api.bom.entity.Bom; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BomDetailResponseDTO { + private Long id; + private String partName; + private String partCode; + private Long partId; + private List materials; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class BomMaterialDTO { + private Long id; + private Long materialId; + private String materialName; + private String materialCode; + private String unit; + private Long quantity; + } + + public static BomDetailResponseDTO from(Bom bom) { + List materialDtos = bom.getMaterials().stream() + .map(material -> BomMaterialDTO.builder() + .id(material.getId()) + .materialId(material.getMaterial().getId()) + .materialName(material.getMaterial().getName()) + .materialCode(material.getMaterial().getMaterialCode()) + .unit(material.getMaterial().getMaterialUnit()) + .quantity(material.getQuantity()) + .build()) + .collect(Collectors.toList()); + + return BomDetailResponseDTO.builder() + .id(bom.getId()) + .partId(bom.getPart().getId()) + .partName(bom.getPart().getName()) + .partCode(bom.getPart().getCode()) + .materials(materialDtos) + .createdAt(bom.getCreatedAt()) + .updatedAt(bom.getUpdatedAt()) + .build(); + } +} diff --git a/src/main/java/com/sampoom/backend/api/bom/dto/BomRequestDTO.java b/src/main/java/com/sampoom/backend/api/bom/dto/BomRequestDTO.java new file mode 100644 index 0000000..2046496 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/dto/BomRequestDTO.java @@ -0,0 +1,26 @@ +package com.sampoom.backend.api.bom.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BomRequestDTO { + private Long partId; + private List materials; // 자재 목록 + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class BomMaterialDTO { + private Long materialId; + private Long quantity; + } +} diff --git a/src/main/java/com/sampoom/backend/api/bom/dto/BomResponseDTO.java b/src/main/java/com/sampoom/backend/api/bom/dto/BomResponseDTO.java new file mode 100644 index 0000000..f85aa00 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/dto/BomResponseDTO.java @@ -0,0 +1,65 @@ +package com.sampoom.backend.api.bom.dto; + +import com.sampoom.backend.api.bom.entity.Bom; +import com.sampoom.backend.api.bom.entity.BomMaterial; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BomResponseDTO { + private Long id; + private String partName; + private String partCode; + private Long partId; + private List materials; // 자재 구성 목록 + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public static BomResponseDTO from(Bom bom) { + return BomResponseDTO.builder() + .id(bom.getId()) + .partId(bom.getPart().getId()) + .partName(bom.getPart().getName()) + .partCode(bom.getPart().getCode()) + .materials(bom.getMaterials().stream() + .sorted(Comparator.comparing(bm -> bm.getMaterial().getName())) // 이름순 정렬 (선택사항) + .map(BomMaterialResponse::from) + .collect(Collectors.toList())) + .createdAt(bom.getCreatedAt()) + .updatedAt(bom.getUpdatedAt()) + .build(); + } + + // 자재 응답 DTO 내부 클래스 + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class BomMaterialResponse { + private Long materialId; + private String materialName; + private String materialCode; + private String unit; + private Long quantity; + + public static BomMaterialResponse from(BomMaterial bm) { + return BomMaterialResponse.builder() + .materialId(bm.getMaterial().getId()) + .materialName(bm.getMaterial().getName()) + .materialCode(bm.getMaterial().getMaterialCode()) + .unit(bm.getMaterial().getMaterialUnit()) + .quantity(bm.getQuantity()) + .build(); + } + } +} diff --git a/src/main/java/com/sampoom/backend/api/bom/entity/Bom.java b/src/main/java/com/sampoom/backend/api/bom/entity/Bom.java new file mode 100644 index 0000000..524a91b --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/entity/Bom.java @@ -0,0 +1,42 @@ +package com.sampoom.backend.api.bom.entity; + +import com.sampoom.backend.api.part.entity.Part; +import com.sampoom.backend.common.entitiy.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "bom") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class Bom extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "bom_id") + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "part_id", unique = true) + private Part part; + + @OneToMany(mappedBy = "bom", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default + private List materials = new ArrayList<>(); + + public void addMaterial(BomMaterial bomMaterial) { + this.materials.add(bomMaterial); + + if (bomMaterial.getBom() != this) { + bomMaterial.updateBom(this); + } + } + + public void touchNow() { this.updatedAt = LocalDateTime.now(); } +} diff --git a/src/main/java/com/sampoom/backend/api/bom/entity/BomMaterial.java b/src/main/java/com/sampoom/backend/api/bom/entity/BomMaterial.java new file mode 100644 index 0000000..fdba030 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/entity/BomMaterial.java @@ -0,0 +1,43 @@ +package com.sampoom.backend.api.bom.entity; + +import com.sampoom.backend.api.material.entity.Material; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "bom_material") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class BomMaterial { + + @Id + @Column(name = "bom_material_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "bom_id", nullable = false) + private Bom bom; + + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "material_id", nullable = false) + private Material material; + + private Long quantity; + + public void updateBom(Bom bom) { + this.bom = bom; + } + + public void updateQuantity(Long quantity) { + this.quantity = quantity; + } + + + + +} diff --git a/src/main/java/com/sampoom/backend/api/bom/repository/BomMaterialRepository.java b/src/main/java/com/sampoom/backend/api/bom/repository/BomMaterialRepository.java new file mode 100644 index 0000000..22b580e --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/repository/BomMaterialRepository.java @@ -0,0 +1,7 @@ +package com.sampoom.backend.api.bom.repository; + +import com.sampoom.backend.api.bom.entity.BomMaterial; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BomMaterialRepository extends JpaRepository { +} diff --git a/src/main/java/com/sampoom/backend/api/bom/repository/BomRepository.java b/src/main/java/com/sampoom/backend/api/bom/repository/BomRepository.java new file mode 100644 index 0000000..a7bc8df --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/repository/BomRepository.java @@ -0,0 +1,36 @@ +package com.sampoom.backend.api.bom.repository; + +import com.sampoom.backend.api.bom.entity.Bom; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface BomRepository extends JpaRepository { + + @Query(""" +SELECT b FROM Bom b +JOIN b.part p +JOIN p.partGroup g +JOIN g.category c +WHERE ( + COALESCE(:keyword, '') = '' + OR p.name ILIKE CONCAT('%', :keyword, '%') + OR p.code ILIKE CONCAT('%', :keyword, '%') +) +AND (:categoryId IS NULL OR c.id = :categoryId) +AND (:groupId IS NULL OR g.id = :groupId) +ORDER BY b.createdAt DESC +""") + Page findByFilters( + @Param("keyword") String keyword, + @Param("categoryId") Long categoryId, + @Param("groupId") Long groupId, + Pageable pageable); + + Optional findByPart_Id(Long partId); + +} diff --git a/src/main/java/com/sampoom/backend/api/bom/service/BomService.java b/src/main/java/com/sampoom/backend/api/bom/service/BomService.java new file mode 100644 index 0000000..e8c6828 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/service/BomService.java @@ -0,0 +1,167 @@ +package com.sampoom.backend.api.bom.service; + +import com.sampoom.backend.api.bom.dto.BomDetailResponseDTO; +import com.sampoom.backend.api.bom.dto.BomRequestDTO; +import com.sampoom.backend.api.bom.dto.BomResponseDTO; +import com.sampoom.backend.api.bom.entity.Bom; +import com.sampoom.backend.api.bom.entity.BomMaterial; +import com.sampoom.backend.api.bom.repository.BomRepository; +import com.sampoom.backend.api.material.entity.Material; +import com.sampoom.backend.api.material.repository.MaterialRepository; +import com.sampoom.backend.api.part.entity.Part; +import com.sampoom.backend.api.part.repository.PartRepository; +import com.sampoom.backend.common.dto.PageResponseDTO; +import com.sampoom.backend.common.exception.NotFoundException; +import com.sampoom.backend.common.response.ErrorStatus; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class BomService { + private final BomRepository bomRepository; + private final PartRepository partRepository; + private final MaterialRepository materialRepository; + + + // BOM 생성 + @Transactional + public BomResponseDTO createOrUpdateBom(BomRequestDTO requestDTO) { + Part part = partRepository.findById(requestDTO.getPartId()) + .orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND)); + + Bom bom = bomRepository.findByPart_Id(part.getId()) + .orElseGet(() -> Bom.builder() + .part(part) + .materials(new ArrayList<>()) + .build()); + + // 기존 자재를 Map으로 변환 (id 기준) + Map existingMaterials = bom.getMaterials().stream() + .collect(Collectors.toMap(m -> m.getMaterial().getId(), m -> m)); + + List newMaterialList = new ArrayList<>(); + + for (BomRequestDTO.BomMaterialDTO dto : requestDTO.getMaterials()) { + Material material = materialRepository.findById(dto.getMaterialId()) + .orElseThrow(() -> new NotFoundException(ErrorStatus.MATERIAL_NOT_FOUND)); + + BomMaterial existing = existingMaterials.get(material.getId()); + + if (existing != null) { + // 이미 있는 자재 → 수량만 업데이트 + existing.updateQuantity(dto.getQuantity()); + newMaterialList.add(existing); + } else { + // 새로 추가된 자재 + BomMaterial newMat = BomMaterial.builder() + .bom(bom) + .material(material) + .quantity(dto.getQuantity()) + .build(); + newMaterialList.add(newMat); + } + } + + // 기존에 있었는데 요청에서 빠진 자재는 제거 + bom.getMaterials().clear(); + bom.getMaterials().addAll(newMaterialList); + + bom.touchNow(); + + return BomResponseDTO.from(bomRepository.save(bom)); + } + + + // BOM 전체 목록 조회 + @Transactional(readOnly = true) + public PageResponseDTO getBoms(int page, int size) { + + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + Page bomPage = bomRepository.findAll(pageable); + + return PageResponseDTO.builder() + .content(bomPage.getContent().stream() + .map(BomResponseDTO::from) + .collect(Collectors.toList())) + .totalElements(bomPage.getTotalElements()) + .totalPages(bomPage.getTotalPages()) + .currentPage(bomPage.getNumber()) + .pageSize(bomPage.getSize()) + .build(); + } + + + // BOM 상세 조회 + @Transactional(readOnly = true) + public BomDetailResponseDTO getBomDetail(Long bomId) { + Bom bom = bomRepository.findById(bomId) + .orElseThrow(() -> new NotFoundException(ErrorStatus.BOM_NOT_FOUND)); + + return BomDetailResponseDTO.from(bom); + } + + + // BOM 수정 + @Transactional + public BomResponseDTO updateBom(Long bomId, BomRequestDTO bomRequestDTO) { + Bom bom = bomRepository.findById(bomId) + .orElseThrow(() -> new NotFoundException(ErrorStatus.BOM_NOT_FOUND)); + + // 기존 자재 삭제 + bom.getMaterials().clear(); + + // 새 자재 추가 + for (BomRequestDTO.BomMaterialDTO materialDTO : bomRequestDTO.getMaterials()) { + Material material = materialRepository.findById(materialDTO.getMaterialId()) + .orElseThrow(() -> new NotFoundException(ErrorStatus.MATERIAL_NOT_FOUND)); + + BomMaterial bomMaterial = BomMaterial.builder() + .bom(bom) + .material(material) + .quantity(materialDTO.getQuantity()) + .build(); + + bom.addMaterial(bomMaterial); + } + + bom.touchNow(); + + return BomResponseDTO.from(bom); + } + + // BOM 삭제 + @Transactional + public void deleteBom(Long bomId) { + Bom bom = bomRepository.findById(bomId) + .orElseThrow(() -> new NotFoundException(ErrorStatus.BOM_NOT_FOUND)); + + bomRepository.delete(bom); + } + + // BOM 검색 + public PageResponseDTO searchBoms(String keyword, Long categoryId, Long groupId, int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + Page bomPage = bomRepository.findByFilters(keyword, categoryId, groupId, pageable); + + return PageResponseDTO.builder() + .content(bomPage.getContent().stream() + .map(BomResponseDTO::from) + .collect(Collectors.toList())) + .totalElements(bomPage.getTotalElements()) + .totalPages(bomPage.getTotalPages()) + .currentPage(bomPage.getNumber()) + .pageSize(bomPage.getSize()) + .build(); + } +} diff --git a/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java b/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java index de53e94..e90cdb3 100644 --- a/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java +++ b/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java @@ -24,6 +24,7 @@ public enum ErrorStatus { GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "그룹을 찾을 수 없습니다.", 30402), PART_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 부품을 찾을 수 없습니다.", 30403), MATERIAL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 자재를 찾을 수 없습니다.", 30403), + BOM_NOT_FOUND(HttpStatus.NOT_FOUND, "BOM을 찾을 수 없습니다.", 30403), // 409 CONFLICT CONFLICT(HttpStatus.CONFLICT, "충돌이 발생했습니다.",30900), From 0ee73156a725a42618781a151757fbc010f5f3f1 Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Tue, 28 Oct 2025 10:53:12 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[SETTING]=20CI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18dcd38..2f6b97a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,7 @@ jobs: - name: Login to Docker Hub + if: github.event_name == 'push' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} From 13618ae742eda2cecc43b24028b0855478b71de4 Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Tue, 28 Oct 2025 11:32:51 +0900 Subject: [PATCH 07/10] [FIX] stop tracking application config files --- .gitignore | 3 + .gitignore.txt | 684 ---------------------- src/main/resources/application.properties | 8 - 3 files changed, 3 insertions(+), 692 deletions(-) delete mode 100644 .gitignore.txt delete mode 100644 src/main/resources/application.properties diff --git a/.gitignore b/.gitignore index ae96a77..4b53b9b 100644 --- a/.gitignore +++ b/.gitignore @@ -682,3 +682,6 @@ gradle-app.setting *.hprof # End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,jetbrains,intellij,node,java,gradle,maven,python,go + +src/main/resources/application*.yml +src/main/resources/application*.properties \ No newline at end of file diff --git a/.gitignore.txt b/.gitignore.txt deleted file mode 100644 index ae96a77..0000000 --- a/.gitignore.txt +++ /dev/null @@ -1,684 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,jetbrains,intellij,node,java,gradle,maven,python,go -# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,linux,visualstudiocode,jetbrains,intellij,node,java,gradle,maven,python,go - -### Go ### -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Intellij Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -# https://plugins.jetbrains.com/plugin/7973-sonarlint -.idea/**/sonarlint/ - -# SonarQube Plugin -# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin -.idea/**/sonarIssues.xml - -# Markdown Navigator plugin -# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced -.idea/**/markdown-navigator.xml -.idea/**/markdown-navigator-enh.xml -.idea/**/markdown-navigator/ - -# Cache file creation bug -# See https://youtrack.jetbrains.com/issue/JBR-2257 -.idea/$CACHE_FILE$ - -# CodeStream plugin -# https://plugins.jetbrains.com/plugin/12206-codestream -.idea/codestream.xml - -# Azure Toolkit for IntelliJ plugin -# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij -.idea/**/azureSettings.xml - -### Java ### -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* - -### JetBrains ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff - -# AWS User-specific - -# Generated files - -# Sensitive or high-churn files - -# Gradle - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake - -# Mongo Explorer plugin - -# File-based project format - -# IntelliJ - -# mpeltonen/sbt-idea plugin - -# JIRA plugin - -# Cursive Clojure plugin - -# SonarLint plugin - -# Crashlytics plugin (for Android Studio and IntelliJ) - -# Editor-based Rest Client - -# Android studio 3.1+ serialized cache file - -### JetBrains Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -# https://plugins.jetbrains.com/plugin/7973-sonarlint - -# SonarQube Plugin -# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin - -# Markdown Navigator plugin -# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced - -# Cache file creation bug -# See https://youtrack.jetbrains.com/issue/JBR-2257 - -# CodeStream plugin -# https://plugins.jetbrains.com/plugin/12206-codestream - -# Azure Toolkit for IntelliJ plugin -# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### macOS Patch ### -# iCloud generated files -*.icloud - -### Maven ### -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar -.mvn/wrapper/maven-wrapper.jar - -# Eclipse m2e generated files -# Eclipse Core -.project -# JDT-specific (Eclipse Java Development Tools) -.classpath - -### Node ### -# Logs -logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -### Node Patch ### -# Serverless Webpack directories -.webpack/ - -# Optional stylelint cache - -# SvelteKit build / generate output -.svelte-kit - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -### Python Patch ### -# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration -poetry.toml - -# ruff -.ruff_cache/ - -# LSP config files -pyrightconfig.json - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -### Gradle ### -.gradle -**/build/ -!src/**/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Avoid ignore Gradle wrappper properties -!gradle-wrapper.properties - -# Cache of project -.gradletasknamecache - -# Eclipse Gradle plugin generated files -# Eclipse Core -# JDT-specific (Eclipse Java Development Tools) - -### Gradle Patch ### -# Java heap dump -*.hprof - -# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,jetbrains,intellij,node,java,gradle,maven,python,go diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 3634d07..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,8 +0,0 @@ -spring.datasource.url=jdbc:postgresql://3.38.218.173:5432/mydb -spring.datasource.username=myuser -spring.datasource.password=mypassword -spring.datasource.driver-class-name=org.postgresql.Driver - -spring.jpa.hibernate.ddl-auto=create -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true From 9466e52acbb92c8942570bd5c2a0d006424d603a Mon Sep 17 00:00:00 2001 From: Choosla Date: Tue, 28 Oct 2025 14:54:16 +0900 Subject: [PATCH 08/10] chore: Update .gitignore --- .../backend/common/config/web/WebConfig.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/com/sampoom/backend/common/config/web/WebConfig.java diff --git a/src/main/java/com/sampoom/backend/common/config/web/WebConfig.java b/src/main/java/com/sampoom/backend/common/config/web/WebConfig.java new file mode 100644 index 0000000..554bba5 --- /dev/null +++ b/src/main/java/com/sampoom/backend/common/config/web/WebConfig.java @@ -0,0 +1,20 @@ +//package com.sampoom.backend.common.config.web; +// +// +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.servlet.config.annotation.CorsRegistry; +//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +// +//@Configuration +//public class WebConfig implements WebMvcConfigurer { +// +// @Override +// public void addCorsMappings(CorsRegistry registry) { +// registry.addMapping("/**") // 모든 경로 허용 +// .allowedOrigins("https://sampoom.store", "http://localhost:3000") +// .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") +// .allowedHeaders("*") +// .allowCredentials(true) // 쿠키 허용 시 필요 +// .maxAge(3600); // preflight 캐싱 시간 (초) +// } +//} From 7c193230ccdadef9dc340bd705b788c59a97ed81 Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Tue, 28 Oct 2025 14:56:45 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[FIX]=20=EC=BD=94=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EB=B9=97=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/bom/controller/BomController.java | 5 +- .../backend/api/bom/dto/BomResultDTO.java | 6 ++ .../backend/api/bom/service/BomService.java | 39 ++++++--- .../item/controller/ItemSearchController.java | 9 +-- .../backend/api/item/dto/ItemProjection.java | 8 ++ .../backend/api/item/dto/ItemResponseDTO.java | 11 +++ .../item/repository/ItemJdbcRepository.java | 64 +++++++++++++++ .../api/item/service/ItemSearchService.java | 80 +++++++++++-------- .../controller/MaterialController.java | 12 +++ .../repository/MaterialRepository.java | 11 +++ .../api/material/service/MaterialService.java | 10 +-- .../api/part/controller/PartController.java | 11 +++ .../api/part/repository/PartRepository.java | 9 ++- .../backend/api/part/service/PartService.java | 6 +- .../backend/common/response/ErrorStatus.java | 4 +- .../common/response/SuccessStatus.java | 3 + 16 files changed, 222 insertions(+), 66 deletions(-) create mode 100644 src/main/java/com/sampoom/backend/api/bom/dto/BomResultDTO.java create mode 100644 src/main/java/com/sampoom/backend/api/item/dto/ItemProjection.java create mode 100644 src/main/java/com/sampoom/backend/api/item/repository/ItemJdbcRepository.java diff --git a/src/main/java/com/sampoom/backend/api/bom/controller/BomController.java b/src/main/java/com/sampoom/backend/api/bom/controller/BomController.java index 93dcc2a..50bf537 100644 --- a/src/main/java/com/sampoom/backend/api/bom/controller/BomController.java +++ b/src/main/java/com/sampoom/backend/api/bom/controller/BomController.java @@ -9,6 +9,7 @@ import com.sampoom.backend.common.response.SuccessStatus; 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.*; @@ -23,7 +24,7 @@ public class BomController { @Operation(summary = "BOM 추가", description = "새로운 BOM을 등록합니다.") @PostMapping - public ResponseEntity> createOrUpdateBom(@RequestBody BomRequestDTO bomRequestDTO) { + public ResponseEntity> createOrUpdateBom(@Valid @RequestBody BomRequestDTO bomRequestDTO) { return ApiResponse.success(SuccessStatus.CREATED, bomService.createOrUpdateBom(bomRequestDTO)); } @@ -47,7 +48,7 @@ public ResponseEntity> getBomDetail(@PathVaria @PutMapping("/{bomId}") public ResponseEntity> updateBom( @PathVariable Long bomId, - @RequestBody BomRequestDTO bomRequestDTO) { + @Valid @RequestBody BomRequestDTO bomRequestDTO) { return ApiResponse.success(SuccessStatus.OK, bomService.updateBom(bomId, bomRequestDTO)); } diff --git a/src/main/java/com/sampoom/backend/api/bom/dto/BomResultDTO.java b/src/main/java/com/sampoom/backend/api/bom/dto/BomResultDTO.java new file mode 100644 index 0000000..67ed9f0 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/bom/dto/BomResultDTO.java @@ -0,0 +1,6 @@ +package com.sampoom.backend.api.bom.dto; + +public class BomResultDTO { + private boolean created; + private BomResponseDTO bom; +} diff --git a/src/main/java/com/sampoom/backend/api/bom/service/BomService.java b/src/main/java/com/sampoom/backend/api/bom/service/BomService.java index e8c6828..f8302af 100644 --- a/src/main/java/com/sampoom/backend/api/bom/service/BomService.java +++ b/src/main/java/com/sampoom/backend/api/bom/service/BomService.java @@ -40,6 +40,7 @@ public BomResponseDTO createOrUpdateBom(BomRequestDTO requestDTO) { Part part = partRepository.findById(requestDTO.getPartId()) .orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND)); + // 기존 BOM 가져오거나 새로 생성 Bom bom = bomRepository.findByPart_Id(part.getId()) .orElseGet(() -> Bom.builder() .part(part) @@ -50,33 +51,51 @@ public BomResponseDTO createOrUpdateBom(BomRequestDTO requestDTO) { Map existingMaterials = bom.getMaterials().stream() .collect(Collectors.toMap(m -> m.getMaterial().getId(), m -> m)); - List newMaterialList = new ArrayList<>(); + // 요청 자재 중복 제거 및 수량 합산 + Map idToQty = requestDTO.getMaterials().stream() + .collect(Collectors.toMap( + BomRequestDTO.BomMaterialDTO::getMaterialId, + BomRequestDTO.BomMaterialDTO::getQuantity, + Long::sum // 중복 materialId 수량 합산 + )); - for (BomRequestDTO.BomMaterialDTO dto : requestDTO.getMaterials()) { - Material material = materialRepository.findById(dto.getMaterialId()) - .orElseThrow(() -> new NotFoundException(ErrorStatus.MATERIAL_NOT_FOUND)); + // 한 번의 쿼리로 모든 자재 조회 (N+1 방지) + List materials = materialRepository.findAllById(idToQty.keySet()); + + if (materials.size() != idToQty.size()) { + throw new NotFoundException(ErrorStatus.MATERIAL_NOT_FOUND); + } + + Map matMap = materials.stream() + .collect(Collectors.toMap(Material::getId, m -> m)); + + // BOM 자재 리스트 구성 + List newMaterialList = new ArrayList<>(); - BomMaterial existing = existingMaterials.get(material.getId()); + for (Map.Entry entry : idToQty.entrySet()) { + Long materialId = entry.getKey(); + Long quantity = entry.getValue(); + Material material = matMap.get(materialId); + BomMaterial existing = existingMaterials.get(materialId); if (existing != null) { - // 이미 있는 자재 → 수량만 업데이트 - existing.updateQuantity(dto.getQuantity()); + existing.updateQuantity(quantity); newMaterialList.add(existing); } else { - // 새로 추가된 자재 BomMaterial newMat = BomMaterial.builder() .bom(bom) .material(material) - .quantity(dto.getQuantity()) + .quantity(quantity) .build(); newMaterialList.add(newMat); } } - // 기존에 있었는데 요청에서 빠진 자재는 제거 + // 요청에서 빠진 자재 제거 bom.getMaterials().clear(); bom.getMaterials().addAll(newMaterialList); + // 수정일 갱신 후 저장 bom.touchNow(); return BomResponseDTO.from(bomRepository.save(bom)); diff --git a/src/main/java/com/sampoom/backend/api/item/controller/ItemSearchController.java b/src/main/java/com/sampoom/backend/api/item/controller/ItemSearchController.java index 717a57a..3bc79ff 100644 --- a/src/main/java/com/sampoom/backend/api/item/controller/ItemSearchController.java +++ b/src/main/java/com/sampoom/backend/api/item/controller/ItemSearchController.java @@ -12,7 +12,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -@Tag(name = "Item", description = "자재/부품 통합 검색 API") +@Tag(name = "Item", description = "통합 검색 API (자재 + 부품)") @RestController @RequestMapping("/api/items") @RequiredArgsConstructor @@ -20,16 +20,15 @@ public class ItemSearchController { private final ItemSearchService itemSearchService; - @Operation(summary = "통합 검색", description = "자재와 부품을 품목명 또는 코드 기준으로 통합 검색합니다.") + @Operation(summary = "통합 검색", description = "자재, 부품, 또는 전체 항목을 검색합니다.") @GetMapping("/search") public ResponseEntity>> searchItems( - @RequestParam(required = false) String keyword, + @RequestParam(defaultValue = "") String keyword, @RequestParam(defaultValue = "ALL") ItemType type, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { PageResponseDTO result = itemSearchService.searchItems(keyword, type, page, size); - - return ApiResponse.success(SuccessStatus.OK, result); + return ApiResponse.success(SuccessStatus.ITEM_LIST_SUCCESS, result); } } diff --git a/src/main/java/com/sampoom/backend/api/item/dto/ItemProjection.java b/src/main/java/com/sampoom/backend/api/item/dto/ItemProjection.java new file mode 100644 index 0000000..addc961 --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/item/dto/ItemProjection.java @@ -0,0 +1,8 @@ +package com.sampoom.backend.api.item.dto; + +public interface ItemProjection { + Long getId(); + String getCode(); + String getName(); + String getType(); +} \ No newline at end of file diff --git a/src/main/java/com/sampoom/backend/api/item/dto/ItemResponseDTO.java b/src/main/java/com/sampoom/backend/api/item/dto/ItemResponseDTO.java index c382ef3..cd9b6cb 100644 --- a/src/main/java/com/sampoom/backend/api/item/dto/ItemResponseDTO.java +++ b/src/main/java/com/sampoom/backend/api/item/dto/ItemResponseDTO.java @@ -9,19 +9,27 @@ @AllArgsConstructor @Builder public class ItemResponseDTO { + private Long id; private String type; // 원자재 / 부품 private String code; private String name; + + private Long categoryId; private String categoryName; + + private Long groupId; private String groupName; + private String unit; private String status; public static ItemResponseDTO ofMaterial(MaterialResponseDTO m) { return ItemResponseDTO.builder() + .id(m.getId()) .type("원자재") .code(m.getMaterialCode()) .name(m.getName()) + .categoryId(m.getMaterialCategoryId()) .categoryName(m.getMaterialCategoryName()) .groupName(null) // 자재는 그룹 없음 .unit(m.getMaterialUnit()) @@ -30,10 +38,13 @@ public static ItemResponseDTO ofMaterial(MaterialResponseDTO m) { public static ItemResponseDTO ofPart(PartListResponseDTO p) { return ItemResponseDTO.builder() + .id(p.getPartId()) .type("부품") .code(p.getCode()) .name(p.getName()) + .categoryId(p.getCategoryId()) .categoryName(p.getCategoryName()) + .groupId(p.getGroupId()) .groupName(p.getGroupName()) .unit(null) .status(p.getStatus()) diff --git a/src/main/java/com/sampoom/backend/api/item/repository/ItemJdbcRepository.java b/src/main/java/com/sampoom/backend/api/item/repository/ItemJdbcRepository.java new file mode 100644 index 0000000..9b32eea --- /dev/null +++ b/src/main/java/com/sampoom/backend/api/item/repository/ItemJdbcRepository.java @@ -0,0 +1,64 @@ +package com.sampoom.backend.api.item.repository; + +import com.sampoom.backend.api.item.dto.ItemResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class ItemJdbcRepository { + + private final JdbcTemplate jdbcTemplate; + + public List searchAll(String keyword, int offset, int size) { + String sql = """ + SELECT id, code, name, 'MATERIAL' AS type, NULL AS category_id, NULL AS group_id + FROM material + WHERE LOWER(name) LIKE LOWER(?) OR LOWER(code) LIKE LOWER(?) + UNION ALL + SELECT id, code, name, 'PART' AS type, + category_id AS category_id, + group_id AS group_id + FROM part + WHERE LOWER(name) LIKE LOWER(?) OR LOWER(code) LIKE LOWER(?) + ORDER BY code + LIMIT ? OFFSET ? + """; + + return jdbcTemplate.query( + sql, + (rs, rowNum) -> ItemResponseDTO.builder() + .type(rs.getString("type").equals("MATERIAL") ? "원자재" : "부품") + .code(rs.getString("code")) + .name(rs.getString("name")) + .categoryId(rs.getLong("category_id") == 0 ? null : rs.getLong("category_id")) + .groupId(rs.getLong("group_id") == 0 ? null : rs.getLong("group_id")) + .build(), + "%" + keyword + "%", "%" + keyword + "%", + "%" + keyword + "%", "%" + keyword + "%", + size, offset + ); + } + + public int countAll(String keyword) { + String countSql = """ + SELECT COUNT(*) FROM ( + SELECT id FROM material + WHERE LOWER(name) LIKE LOWER(?) OR LOWER(code) LIKE LOWER(?) + UNION ALL + SELECT id FROM part + WHERE LOWER(name) LIKE LOWER(?) OR LOWER(code) LIKE LOWER(?) + ) AS total + """; + + return jdbcTemplate.queryForObject( + countSql, + Integer.class, + "%" + keyword + "%", "%" + keyword + "%", + "%" + keyword + "%", "%" + keyword + "%" + ); + } +} diff --git a/src/main/java/com/sampoom/backend/api/item/service/ItemSearchService.java b/src/main/java/com/sampoom/backend/api/item/service/ItemSearchService.java index aa77ecc..eb93ee9 100644 --- a/src/main/java/com/sampoom/backend/api/item/service/ItemSearchService.java +++ b/src/main/java/com/sampoom/backend/api/item/service/ItemSearchService.java @@ -1,9 +1,9 @@ package com.sampoom.backend.api.item.service; +import com.sampoom.backend.api.item.dto.ItemResponseDTO; import com.sampoom.backend.api.item.enums.ItemType; import com.sampoom.backend.api.material.service.MaterialService; import com.sampoom.backend.api.part.service.PartService; -import com.sampoom.backend.api.item.dto.ItemResponseDTO; import com.sampoom.backend.common.dto.PageResponseDTO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -15,7 +15,7 @@ @Service @RequiredArgsConstructor -@Transactional +@Transactional(readOnly = true) public class ItemSearchService { private final MaterialService materialService; @@ -23,43 +23,57 @@ public class ItemSearchService { public PageResponseDTO searchItems(String keyword, ItemType type, int page, int size) { - List allResults = new ArrayList<>(); + List mergedList = new ArrayList<>(); switch (type) { - case MATERIAL -> // 자재만 - materialService.searchMaterials(keyword, page, size) - .getContent() - .forEach(m -> allResults.add(ItemResponseDTO.ofMaterial(m))); - - case PART -> // 부품만 - partService.searchParts(keyword, page, size) - .getContent() - .forEach(p -> allResults.add(ItemResponseDTO.ofPart(p))); + case MATERIAL -> { + var materials = materialService.searchMaterials(keyword, page, size); + return PageResponseDTO.builder() + .content(materials.getContent().stream() + .map(ItemResponseDTO::ofMaterial) + .toList()) + .totalElements(materials.getTotalElements()) + .totalPages(materials.getTotalPages()) + .currentPage(materials.getCurrentPage()) + .pageSize(materials.getPageSize()) + .build(); + } - case ALL -> { // 전체 - materialService.searchMaterials(keyword, page, size) - .getContent() - .forEach(m -> allResults.add(ItemResponseDTO.ofMaterial(m))); - partService.searchParts(keyword, page, size) - .getContent() - .forEach(p -> allResults.add(ItemResponseDTO.ofPart(p))); + case PART -> { + var parts = partService.searchParts(keyword, page, size); + return PageResponseDTO.builder() + .content(parts.getContent().stream() + .map(ItemResponseDTO::ofPart) + .toList()) + .totalElements(parts.getTotalElements()) + .totalPages(parts.getTotalPages()) + .currentPage(parts.getCurrentPage()) + .pageSize(parts.getPageSize()) + .build(); } - } - // 병합 결과 정렬 (코드순 or 이름순) - allResults.sort(Comparator.comparing(ItemResponseDTO::getCode)); + case ALL -> { + var materials = materialService.searchMaterials(keyword, 0, Integer.MAX_VALUE); + var parts = partService.searchParts(keyword, 0, Integer.MAX_VALUE); - // 페이지네이션 수동 처리 - int from = page * size; - int to = Math.min(from + size, allResults.size()); - List pageList = from < allResults.size() ? allResults.subList(from, to) : List.of(); + mergedList.addAll(materials.getContent().stream().map(ItemResponseDTO::ofMaterial).toList()); + mergedList.addAll(parts.getContent().stream().map(ItemResponseDTO::ofPart).toList()); - return PageResponseDTO.builder() - .content(pageList) - .totalElements(allResults.size()) - .totalPages((int) Math.ceil((double) allResults.size() / size)) - .currentPage(page) - .pageSize(size) - .build(); + mergedList.sort(Comparator.comparing(ItemResponseDTO::getCode)); + + int from = page * size; + int to = Math.min(from + size, mergedList.size()); + List pagedList = from < mergedList.size() ? mergedList.subList(from, to) : List.of(); + + return PageResponseDTO.builder() + .content(pagedList) + .totalElements(mergedList.size()) + .totalPages((int) Math.ceil((double) mergedList.size() / size)) + .currentPage(page) + .pageSize(size) + .build(); + } + } + throw new IllegalArgumentException("Invalid item type: " + type); } } diff --git a/src/main/java/com/sampoom/backend/api/material/controller/MaterialController.java b/src/main/java/com/sampoom/backend/api/material/controller/MaterialController.java index 7b315f3..9dfcf0a 100644 --- a/src/main/java/com/sampoom/backend/api/material/controller/MaterialController.java +++ b/src/main/java/com/sampoom/backend/api/material/controller/MaterialController.java @@ -71,4 +71,16 @@ public ResponseEntity> deleteMaterial(@PathVariable("materialI materialService.deleteMaterial(id); return ApiResponse.success_only(SuccessStatus.OK); } + + @Operation(summary = "자재 검색", description = "자재 이름 또는 코드로 검색합니다.") + @GetMapping("/search") + public ResponseEntity>> searchMaterials( + @RequestParam(defaultValue = "") String keyword, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + PageResponseDTO result = materialService.searchMaterials(keyword, page, size); + + return ApiResponse.success(SuccessStatus.MATERIAL_LIST_SUCCESS, result); + } } diff --git a/src/main/java/com/sampoom/backend/api/material/repository/MaterialRepository.java b/src/main/java/com/sampoom/backend/api/material/repository/MaterialRepository.java index 7efabd4..00ad6a5 100644 --- a/src/main/java/com/sampoom/backend/api/material/repository/MaterialRepository.java +++ b/src/main/java/com/sampoom/backend/api/material/repository/MaterialRepository.java @@ -4,6 +4,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface MaterialRepository extends JpaRepository { @@ -13,6 +15,15 @@ public interface MaterialRepository extends JpaRepository { // 이름 또는 코드 검색 (부분 일치, 대소문자 무시) Page findByNameContainingIgnoreCaseOrMaterialCodeContainingIgnoreCase(String name, String code, Pageable pageable); + @Query(""" + select m from Material m + where (:kw is null or :kw = '' + or lower(m.name) like lower(concat('%', :kw, '%')) + or lower(m.materialCode) like lower(concat('%', :kw, '%'))) + """) + Page search(@Param("kw") String keyword, Pageable pageable); + + // 카테고리 내에서 가장 최근 등록된 자재 찾기 (코드 자동 생성용) Material findTopByMaterialCategoryIdOrderByIdDesc(Long categoryId); } \ No newline at end of file diff --git a/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java b/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java index 6ea2915..915ec42 100644 --- a/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java +++ b/src/main/java/com/sampoom/backend/api/material/service/MaterialService.java @@ -137,15 +137,7 @@ public void deleteMaterial(Long id) { public PageResponseDTO searchMaterials(String keyword, int page, int size) { PageRequest pageRequest = PageRequest.of(page, size); - Page materials; - - if (keyword == null || keyword.trim().isEmpty()) { - materials = materialRepository.findAll(pageRequest); - } else { - materials = materialRepository.findByNameContainingIgnoreCaseOrMaterialCodeContainingIgnoreCase( - keyword, keyword, pageRequest - ); - } + Page materials = materialRepository.search(keyword, pageRequest); List dtoList = materials.getContent().stream() .map(MaterialResponseDTO::new) diff --git a/src/main/java/com/sampoom/backend/api/part/controller/PartController.java b/src/main/java/com/sampoom/backend/api/part/controller/PartController.java index f9d9b99..f7e11d3 100644 --- a/src/main/java/com/sampoom/backend/api/part/controller/PartController.java +++ b/src/main/java/com/sampoom/backend/api/part/controller/PartController.java @@ -96,4 +96,15 @@ public ResponseEntity> deletePart(@PathVariable Long partId) { return ApiResponse.success(SuccessStatus.PART_DELETE_SUCCESS, null); } + @Operation(summary = "부품 검색", description = "부품 이름 또는 코드로 검색") + @GetMapping("/search") + public ResponseEntity>> searchParts( + @RequestParam(defaultValue = "") String keyword, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + PageResponseDTO partPage = partService.searchParts(keyword, page, size); + + return ApiResponse.success(SuccessStatus.PART_LIST_SUCCESS, partPage); + } } diff --git a/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java b/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java index 6d66791..abbae3f 100644 --- a/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java +++ b/src/main/java/com/sampoom/backend/api/part/repository/PartRepository.java @@ -8,7 +8,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; public interface PartRepository extends JpaRepository { @@ -23,6 +22,14 @@ Page findByNameContainingIgnoreCaseOrCodeContainingIgnoreCaseAndStatus( Pageable pageable ); + @Query(""" +select p from Part p +where p.status = :status + and (lower(p.name) like lower(concat('%', :kw, '%')) + or lower(p.code) like lower(concat('%', :kw, '%'))) +""") + Page searchActive(@Param("kw") String kw, @Param("status") PartStatus status, Pageable pageable); + // 그룹별 부품 조회 Page findByPartGroupId(Long groupId, Pageable pageable); diff --git a/src/main/java/com/sampoom/backend/api/part/service/PartService.java b/src/main/java/com/sampoom/backend/api/part/service/PartService.java index dc1f506..0942b60 100644 --- a/src/main/java/com/sampoom/backend/api/part/service/PartService.java +++ b/src/main/java/com/sampoom/backend/api/part/service/PartService.java @@ -110,7 +110,7 @@ public PartListResponseDTO createPart(PartCreateRequestDTO partCreateRequestDTO) public PartListResponseDTO updatePart(Long partId, PartUpdateRequestDTO partUpdateRequestDTO) { // 수정할 부품을 조회 Part part = partRepository.findById(partId) - .orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND.getMessage())); + .orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND)); part.update(partUpdateRequestDTO); @@ -133,9 +133,7 @@ public void deletePart(Long partId) { public PageResponseDTO searchParts(String keyword, int page, int size) { PageRequest pageRequest = PageRequest.of(page, size); - Page parts = partRepository.findByNameContainingIgnoreCaseOrCodeContainingIgnoreCaseAndStatus( - keyword, keyword, PartStatus.ACTIVE, pageRequest - ); + Page parts = partRepository.searchActive(keyword, PartStatus.ACTIVE, pageRequest); List dtoList = parts.getContent().stream() .map(PartListResponseDTO::new) diff --git a/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java b/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java index e90cdb3..b84a489 100644 --- a/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java +++ b/src/main/java/com/sampoom/backend/common/response/ErrorStatus.java @@ -23,8 +23,8 @@ public enum ErrorStatus { CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "카테고리를 찾을 수 없습니다.", 30401), GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "그룹을 찾을 수 없습니다.", 30402), PART_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 부품을 찾을 수 없습니다.", 30403), - MATERIAL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 자재를 찾을 수 없습니다.", 30403), - BOM_NOT_FOUND(HttpStatus.NOT_FOUND, "BOM을 찾을 수 없습니다.", 30403), + MATERIAL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 자재를 찾을 수 없습니다.", 30404), + BOM_NOT_FOUND(HttpStatus.NOT_FOUND, "BOM을 찾을 수 없습니다.", 30405), // 409 CONFLICT CONFLICT(HttpStatus.CONFLICT, "충돌이 발생했습니다.",30900), diff --git a/src/main/java/com/sampoom/backend/common/response/SuccessStatus.java b/src/main/java/com/sampoom/backend/common/response/SuccessStatus.java index 286ac4c..b1af254 100644 --- a/src/main/java/com/sampoom/backend/common/response/SuccessStatus.java +++ b/src/main/java/com/sampoom/backend/common/response/SuccessStatus.java @@ -25,6 +25,9 @@ public enum SuccessStatus { PART_SEARCH_SUCCESS(HttpStatus.OK, "부품 검색 성공"), PART_DETAIL_SUCCESS(HttpStatus.OK, "부품 상세 조회 성공"), + MATERIAL_LIST_SUCCESS(HttpStatus.OK, "자재 목록 조회 성공"), + ITEM_LIST_SUCCESS(HttpStatus.OK, "자재/부품 전체 조회 성공"), + ; From c90f7abb4d32e5817e1e2030604238ca6431abd5 Mon Sep 17 00:00:00 2001 From: yangjiseonn Date: Tue, 28 Oct 2025 15:09:43 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[SETTING]=20CI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f6b97a..6070aaf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Part CI (use Gradle version) +name: CI (use Gradle version) on: push: @@ -34,8 +34,13 @@ jobs: perl -pi -e 's/\r\n/\n/g' Dockerfile fi + - name: Create application.properties from secret + run: | + mkdir -p src/main/resources + echo "${{ secrets.APP_PROPS_PART }}" > src/main/resources/application.yml + - name: Build part jar - run: ./gradlew clean test bootJar -Dspring.profiles.active=ci + run: ./gradlew clean bootJar -x test -Dspring.profiles.active=ci # Gradle의 version 값을 읽어옴 (예: 1.0.0 또는 1.0.0-SNAPSHOT) - name: Read version from Gradle @@ -51,7 +56,6 @@ jobs: echo "TAG_VERSION=part-${VER}" >> $GITHUB_OUTPUT echo "TAG_LATEST=part-latest" >> $GITHUB_OUTPUT - - name: Login to Docker Hub if: github.event_name == 'push' uses: docker/login-action@v3 @@ -67,6 +71,7 @@ jobs: network=host - name: Build & Push (part) + if: github.event_name == 'push' uses: docker/build-push-action@v6 env: BUILDKIT_PROGRESS: plain @@ -79,25 +84,3 @@ jobs: tags: | ${{ steps.vars.outputs.IMAGE }}:${{ steps.vars.outputs.TAG_VERSION }} ${{ steps.vars.outputs.IMAGE }}:${{ steps.vars.outputs.TAG_LATEST }} - - - # EC2 자동 배포 - - name: Deploy to EC2 (Part) - uses: appleboy/ssh-action@master - with: - host: 3.38.218.173 - username: ubuntu - key: ${{ secrets.SAMPOOM_KEY }} - script: | - echo "배포 시작..." - sudo docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_PASSWORD }} - sudo docker pull hysungzzang/sampoom:part-latest - sudo docker stop part-service || true - sudo docker rm part-service || true - sudo docker run -d --name part-service -p 8080:8080 \ - -e SPRING_APPLICATION_JSON='{"server":{"address":"0.0.0.0","port":8080}}' \ - -e SPRING_DATASOURCE_URL=jdbc:postgresql://3.38.218.173:5432/mydb \ - -e SPRING_DATASOURCE_USERNAME=myuser \ - -e SPRING_DATASOURCE_PASSWORD=mypassword \ - hysungzzang/sampoom:part-latest - echo "배포 완료!" \ No newline at end of file