diff --git a/src/main/java/com/mulmi/backend/domain/equipment/controller/AdminEquipmentModelController.java b/src/main/java/com/mulmi/backend/domain/equipment/controller/AdminEquipmentModelController.java new file mode 100644 index 0000000..a115823 --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/controller/AdminEquipmentModelController.java @@ -0,0 +1,38 @@ +package com.mulmi.backend.domain.equipment.controller; + +import com.mulmi.backend.domain.equipment.dto.request.EquipmentModelCreateRequestDTO; +import com.mulmi.backend.domain.equipment.dto.response.EquipmentModelCreateResponseDTO; +import com.mulmi.backend.domain.equipment.exception.code.EquipmentModelSuccessCode; +import com.mulmi.backend.domain.equipment.service.EquipmentModelService; +import com.mulmi.backend.global.apiPayload.ApiResponse; +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.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin/equipment-models") +@Tag(name = "모델 관리", description = "조교용 모델 관리 API") +public class AdminEquipmentModelController { + + private final EquipmentModelService equipmentModelService; + + // 모델 등록 + @Operation(summary = "모델 등록") + @PostMapping + @PreAuthorize("hasRole('ASSISTANT')") + public ApiResponse createEquipmentModel( + @RequestBody @Valid EquipmentModelCreateRequestDTO dto + ) { + return ApiResponse.onSuccess( + EquipmentModelSuccessCode.MODEL_CREATED, + equipmentModelService.createEquipmentModel(dto) + ); + } +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/converter/CategoryConverter.java b/src/main/java/com/mulmi/backend/domain/equipment/converter/CategoryConverter.java index b872eb4..469bf85 100644 --- a/src/main/java/com/mulmi/backend/domain/equipment/converter/CategoryConverter.java +++ b/src/main/java/com/mulmi/backend/domain/equipment/converter/CategoryConverter.java @@ -2,6 +2,7 @@ import com.mulmi.backend.domain.equipment.dto.response.CategoryResponseDTO; import com.mulmi.backend.domain.equipment.entity.Category; +import com.mulmi.backend.domain.equipment.enums.LoanPeriodType; public class CategoryConverter { @@ -9,6 +10,11 @@ public class CategoryConverter { public static Category toCategory(String name) { return Category.builder() .name(name) + .regularRentalEnabled(false) + .loanPeriodType(LoanPeriodType.FIXED_DAYS) + .loanDurationDays(1) + .pickupPhotoRequired(false) + .pledgeRequired(false) .build(); } diff --git a/src/main/java/com/mulmi/backend/domain/equipment/converter/EquipmentModelConverter.java b/src/main/java/com/mulmi/backend/domain/equipment/converter/EquipmentModelConverter.java new file mode 100644 index 0000000..4e6c44d --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/converter/EquipmentModelConverter.java @@ -0,0 +1,46 @@ +package com.mulmi.backend.domain.equipment.converter; + +import com.mulmi.backend.domain.equipment.dto.request.EquipmentModelCreateRequestDTO; +import com.mulmi.backend.domain.equipment.dto.response.EquipmentModelCreateResponseDTO; +import com.mulmi.backend.domain.equipment.entity.Category; +import com.mulmi.backend.domain.equipment.entity.EquipmentItem; +import com.mulmi.backend.domain.equipment.entity.EquipmentModel; +import com.mulmi.backend.domain.equipment.entity.LabelPrefix; +import com.mulmi.backend.domain.equipment.enums.EquipmentItemStatus; + +public class EquipmentModelConverter { + + // 요청 DTO + 조회해 온 카테고리,접두사 -> EquipmentModel + public static EquipmentModel toEquipmentModel( + EquipmentModelCreateRequestDTO dto, Category category, LabelPrefix labelPrefix) { + return EquipmentModel.builder() + .category(category) + .labelPrefix(labelPrefix) + .name(dto.modelName().trim()) + .components(dto.components()) + .specification(dto.specification()) + .description(dto.description()) + .rentalStatus(dto.rentalStatus()) + .disposalDate(dto.disposalDate()) + .build(); + } + + // 모델 + 발급 번호 -> EquipmentItem 한 대 + public static EquipmentItem toEquipmentItem(EquipmentModel model, int labelNumber) { + String labelCode = String.format("%s-%03d", model.getLabelPrefix().getCode(), labelNumber); + return EquipmentItem.builder() + .equipmentModel(model) + .labelNumber(labelNumber) + .labelCode(labelCode) + .status(EquipmentItemStatus.AVAILABLE) + .build(); + } + + // 저장된 모델 + 생성된 아이템 목록 -> 응답 DTO + public static EquipmentModelCreateResponseDTO toEquipmentModelCreateResponseDTO( + EquipmentModel model) { + return new EquipmentModelCreateResponseDTO( + model.getId() + ); + } +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/dto/request/EquipmentModelCreateRequestDTO.java b/src/main/java/com/mulmi/backend/domain/equipment/dto/request/EquipmentModelCreateRequestDTO.java new file mode 100644 index 0000000..dbe6d6e --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/dto/request/EquipmentModelCreateRequestDTO.java @@ -0,0 +1,33 @@ +package com.mulmi.backend.domain.equipment.dto.request; + +import com.mulmi.backend.domain.equipment.enums.EquipmentModelStatus; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import java.time.LocalDate; + +public record EquipmentModelCreateRequestDTO( + @NotNull(message = "카테고리는 필수입니다.") + Long categoryId, + + @Size(max = 10) + String prefixCode, + + @Size(max = 100, message = "모델명은 100자를 넘을 수 없습니다.") + @NotBlank(message = "모델명은 필수입니다.") + String modelName, + + @NotBlank(message = "구성품은 필수입니다.") + String components, + + String description, + String specification, + + @NotNull(message = "상태는 필수입니다.") + EquipmentModelStatus rentalStatus, + + @NotNull(message = "폐기일은 필수입니다.") + LocalDate disposalDate + ) { +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/dto/response/EquipmentModelCreateResponseDTO.java b/src/main/java/com/mulmi/backend/domain/equipment/dto/response/EquipmentModelCreateResponseDTO.java new file mode 100644 index 0000000..9cae638 --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/dto/response/EquipmentModelCreateResponseDTO.java @@ -0,0 +1,6 @@ +package com.mulmi.backend.domain.equipment.dto.response; + +public record EquipmentModelCreateResponseDTO( + Long modelId +) { +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/entity/Category.java b/src/main/java/com/mulmi/backend/domain/equipment/entity/Category.java index 60ca1cd..3606ca0 100644 --- a/src/main/java/com/mulmi/backend/domain/equipment/entity/Category.java +++ b/src/main/java/com/mulmi/backend/domain/equipment/entity/Category.java @@ -1,8 +1,11 @@ package com.mulmi.backend.domain.equipment.entity; +import com.mulmi.backend.domain.equipment.enums.LoanPeriodType; import com.mulmi.backend.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Entity @Getter @@ -19,6 +22,31 @@ public class Category extends BaseEntity { @Column(name= "category_name", nullable = false, unique = true, length = 50) private String name; + @Column(name = "label_required", nullable = false) + private boolean labelRequired; + + // 정기대여 가능 여부 + @Column(name = "regular_rental_enabled", nullable = false) + private boolean regularRentalEnabled; + + // 기간 유형 + @JdbcTypeCode(SqlTypes.VARCHAR) + @Column(name = "loan_period_type", nullable = false, length = 30) + @Enumerated(EnumType.STRING) + private LoanPeriodType loanPeriodType; + + // 대여 일수 (SEMESTER_END면 null) + @Column(name = "loan_duration_days", nullable = true) + private Integer loanDurationDays; + + // 수령 사진 필수 여부 + @Column(name = "pickup_photo_required", nullable = false) + private boolean pickupPhotoRequired; + + // 서약서 필수 여부 + @Column(name = "pledge_required", nullable = false) + private boolean pledgeRequired; + public void updateCategoryName(String name) { this.name = name; } diff --git a/src/main/java/com/mulmi/backend/domain/equipment/entity/EquipmentItem.java b/src/main/java/com/mulmi/backend/domain/equipment/entity/EquipmentItem.java new file mode 100644 index 0000000..78689b0 --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/entity/EquipmentItem.java @@ -0,0 +1,53 @@ +package com.mulmi.backend.domain.equipment.entity; + +import com.mulmi.backend.domain.equipment.enums.EquipmentItemStatus; +import com.mulmi.backend.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "equipment_item") +public class EquipmentItem extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "model_id", nullable = false) + private EquipmentModel equipmentModel; + + @Column(name="label_number") + private Integer labelNumber; + + @Column(name="label_code", length = 30, unique = true) + private String labelCode; + + @JdbcTypeCode(SqlTypes.VARCHAR) + @Enumerated(EnumType.STRING) + @Column(name="status", length = 30, nullable = false) + private EquipmentItemStatus status; + + @Column(name = "memo", nullable = true, columnDefinition = "TEXT") + private String memo; +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/entity/EquipmentModel.java b/src/main/java/com/mulmi/backend/domain/equipment/entity/EquipmentModel.java index 9bee557..741f744 100644 --- a/src/main/java/com/mulmi/backend/domain/equipment/entity/EquipmentModel.java +++ b/src/main/java/com/mulmi/backend/domain/equipment/entity/EquipmentModel.java @@ -1,8 +1,6 @@ package com.mulmi.backend.domain.equipment.entity; -import com.mulmi.backend.domain.equipment.enums.ApplicationPolicy; import com.mulmi.backend.domain.equipment.enums.EquipmentModelStatus; -import com.mulmi.backend.domain.equipment.enums.LoanPeriodType; import com.mulmi.backend.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -21,6 +19,10 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.time.LocalDate; @Entity @Getter @@ -39,13 +41,13 @@ public class EquipmentModel extends BaseEntity { private Category category; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "prefix_id", nullable = false) + @JoinColumn(name = "prefix_id") private LabelPrefix labelPrefix; @Column(name = "model_name", nullable = false, length = 100) private String name; - @Column(nullable = true, columnDefinition = "TEXT") + @Column(nullable = false, columnDefinition = "TEXT") private String components; @Column(nullable = true, columnDefinition = "TEXT") @@ -57,23 +59,11 @@ public class EquipmentModel extends BaseEntity { @Column(name = "model_image_url", nullable = true, length = 1000) private String modelImageUrl; - @Column(name = "application_policy", nullable = false, length = 30) - @Enumerated(EnumType.STRING) - private ApplicationPolicy applicationPolicy; - - @Column(name = "loan_period_type", nullable = false, length = 30) - @Enumerated(EnumType.STRING) - private LoanPeriodType loanPeriodType; - - @Column(name = "loan_duration_days", nullable = true) - private Integer loanDurationDays; - - @Column(name = "pledge_required", nullable = false) - private boolean pledgeRequired; - + @JdbcTypeCode(SqlTypes.VARCHAR) @Column(name = "rental_status", nullable = false, length = 30) @Enumerated(EnumType.STRING) private EquipmentModelStatus rentalStatus; - + @Column(name = "disposal_date", nullable = false) + private LocalDate disposalDate; } diff --git a/src/main/java/com/mulmi/backend/domain/equipment/enums/ApplicationPolicy.java b/src/main/java/com/mulmi/backend/domain/equipment/enums/ApplicationPolicy.java index d9eb37a..509e0ca 100644 --- a/src/main/java/com/mulmi/backend/domain/equipment/enums/ApplicationPolicy.java +++ b/src/main/java/com/mulmi/backend/domain/equipment/enums/ApplicationPolicy.java @@ -1,6 +1,6 @@ package com.mulmi.backend.domain.equipment.enums; public enum ApplicationPolicy { - REGULAR_AND_ON_DEMAND, - ON_DEMAND_ONLY + REGULAR, + ON_DEMAND } diff --git a/src/main/java/com/mulmi/backend/domain/equipment/enums/EquipmentItemStatus.java b/src/main/java/com/mulmi/backend/domain/equipment/enums/EquipmentItemStatus.java new file mode 100644 index 0000000..6c2df08 --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/enums/EquipmentItemStatus.java @@ -0,0 +1,10 @@ +package com.mulmi.backend.domain.equipment.enums; + +public enum EquipmentItemStatus { + AVAILABLE, + RENTED, + BROKEN, + REPAIRING, + DISPOSAL_PENDING, + DISPOSED +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/enums/EquipmentModelStatus.java b/src/main/java/com/mulmi/backend/domain/equipment/enums/EquipmentModelStatus.java index d872bdd..6e88264 100644 --- a/src/main/java/com/mulmi/backend/domain/equipment/enums/EquipmentModelStatus.java +++ b/src/main/java/com/mulmi/backend/domain/equipment/enums/EquipmentModelStatus.java @@ -4,6 +4,5 @@ public enum EquipmentModelStatus { // 모델 대여 '가능여부'의 상태 // 대여신청에 나올 rental status와 헷갈리지 말 것 AVAILABLE, - UNAVAILABLE, - DISCONTINUED + UNAVAILABLE } diff --git a/src/main/java/com/mulmi/backend/domain/equipment/exception/EquipmentModelException.java b/src/main/java/com/mulmi/backend/domain/equipment/exception/EquipmentModelException.java new file mode 100644 index 0000000..b2cbceb --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/exception/EquipmentModelException.java @@ -0,0 +1,10 @@ +package com.mulmi.backend.domain.equipment.exception; + +import com.mulmi.backend.global.apiPayload.code.BaseErrorCode; +import com.mulmi.backend.global.apiPayload.exception.GeneralException; + +public class EquipmentModelException extends GeneralException { + public EquipmentModelException(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/exception/LabelPrefixException.java b/src/main/java/com/mulmi/backend/domain/equipment/exception/LabelPrefixException.java new file mode 100644 index 0000000..4a45aee --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/exception/LabelPrefixException.java @@ -0,0 +1,10 @@ +package com.mulmi.backend.domain.equipment.exception; + +import com.mulmi.backend.global.apiPayload.code.BaseErrorCode; +import com.mulmi.backend.global.apiPayload.exception.GeneralException; + +public class LabelPrefixException extends GeneralException { + public LabelPrefixException(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/exception/code/EquipmentModelErrorCode.java b/src/main/java/com/mulmi/backend/domain/equipment/exception/code/EquipmentModelErrorCode.java new file mode 100644 index 0000000..568034c --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/exception/code/EquipmentModelErrorCode.java @@ -0,0 +1,33 @@ +package com.mulmi.backend.domain.equipment.exception.code; + +import com.mulmi.backend.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum EquipmentModelErrorCode implements BaseErrorCode { + + LABEL_PREFIX_REQUIRED( + HttpStatus.BAD_REQUEST, + "EQUIPMENTMODEL400_2", + "라벨을 쓰는 카테고리는 접두사가 필요합니다." + ), + + LABEL_PREFIX_NOT_ALLOWED( + HttpStatus.BAD_REQUEST, + "EQUIPMENTMODEL400_3", + "라벨을 쓰지 않는 카테고리에는 접두사를 입력할 수 없습니다." + ), + + DUPLICATE_MODEL( + HttpStatus.CONFLICT, + "EQUIPMENTMODEL409_1", + "같은 카테고리에 동일 이름의 모델이 이미 있습니다." + ); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/exception/code/EquipmentModelSuccessCode.java b/src/main/java/com/mulmi/backend/domain/equipment/exception/code/EquipmentModelSuccessCode.java new file mode 100644 index 0000000..8b63dd0 --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/exception/code/EquipmentModelSuccessCode.java @@ -0,0 +1,21 @@ +package com.mulmi.backend.domain.equipment.exception.code; + +import com.mulmi.backend.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum EquipmentModelSuccessCode implements BaseSuccessCode { + MODEL_CREATED( + HttpStatus.CREATED, + "EQUIPMENTMODEL201_1", + "기자재 모델을 등록했습니다." + ); + + private final HttpStatus status; + private final String code; + private final String message; + +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/exception/code/LabelPrefixErrorCode.java b/src/main/java/com/mulmi/backend/domain/equipment/exception/code/LabelPrefixErrorCode.java new file mode 100644 index 0000000..d63e26c --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/exception/code/LabelPrefixErrorCode.java @@ -0,0 +1,20 @@ +package com.mulmi.backend.domain.equipment.exception.code; + +import com.mulmi.backend.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum LabelPrefixErrorCode implements BaseErrorCode { + LABEL_PREFIX_NOT_FOUND( + HttpStatus.NOT_FOUND, + "LABELPREFIX404_1", + "라벨 접두사를 찾을 수 없습니다." + ); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/repository/EquipmentItemRepository.java b/src/main/java/com/mulmi/backend/domain/equipment/repository/EquipmentItemRepository.java new file mode 100644 index 0000000..b1f215c --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/repository/EquipmentItemRepository.java @@ -0,0 +1,7 @@ +package com.mulmi.backend.domain.equipment.repository; + +import com.mulmi.backend.domain.equipment.entity.EquipmentItem; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EquipmentItemRepository extends JpaRepository { +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/repository/EquipmentModelRepository.java b/src/main/java/com/mulmi/backend/domain/equipment/repository/EquipmentModelRepository.java index 73d6cd7..cbf8da1 100644 --- a/src/main/java/com/mulmi/backend/domain/equipment/repository/EquipmentModelRepository.java +++ b/src/main/java/com/mulmi/backend/domain/equipment/repository/EquipmentModelRepository.java @@ -7,4 +7,5 @@ public interface EquipmentModelRepository extends JpaRepository { + + @Lock(LockModeType.PESSIMISTIC_WRITE) + Optional findWithLockById(Long labelPrefixId); + + Optional findByCode(String code); +} diff --git a/src/main/java/com/mulmi/backend/domain/equipment/service/EquipmentModelService.java b/src/main/java/com/mulmi/backend/domain/equipment/service/EquipmentModelService.java new file mode 100644 index 0000000..c1314e8 --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/service/EquipmentModelService.java @@ -0,0 +1,12 @@ +package com.mulmi.backend.domain.equipment.service; + +import com.mulmi.backend.domain.equipment.dto.request.EquipmentModelCreateRequestDTO; +import com.mulmi.backend.domain.equipment.dto.response.EquipmentModelCreateResponseDTO; + +public interface EquipmentModelService { + + // 모델 등록 + EquipmentModelCreateResponseDTO createEquipmentModel(EquipmentModelCreateRequestDTO dto); + +} + diff --git a/src/main/java/com/mulmi/backend/domain/equipment/service/EquipmentModelServiceImpl.java b/src/main/java/com/mulmi/backend/domain/equipment/service/EquipmentModelServiceImpl.java new file mode 100644 index 0000000..b179e71 --- /dev/null +++ b/src/main/java/com/mulmi/backend/domain/equipment/service/EquipmentModelServiceImpl.java @@ -0,0 +1,82 @@ +package com.mulmi.backend.domain.equipment.service; + +import com.mulmi.backend.domain.equipment.dto.request.EquipmentModelCreateRequestDTO; +import com.mulmi.backend.domain.equipment.dto.response.EquipmentModelCreateResponseDTO; +import com.mulmi.backend.domain.equipment.converter.EquipmentModelConverter; +import com.mulmi.backend.domain.equipment.entity.Category; +import com.mulmi.backend.domain.equipment.entity.EquipmentModel; +import com.mulmi.backend.domain.equipment.entity.LabelPrefix; +import com.mulmi.backend.domain.equipment.exception.CategoryException; +import com.mulmi.backend.domain.equipment.exception.EquipmentModelException; +import com.mulmi.backend.domain.equipment.exception.LabelPrefixException; +import com.mulmi.backend.domain.equipment.exception.code.CategoryErrorCode; +import com.mulmi.backend.domain.equipment.exception.code.EquipmentModelErrorCode; +import com.mulmi.backend.domain.equipment.exception.code.LabelPrefixErrorCode; +import com.mulmi.backend.domain.equipment.repository.CategoryRepository; +import com.mulmi.backend.domain.equipment.repository.EquipmentModelRepository; +import com.mulmi.backend.domain.equipment.repository.LabelPrefixRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class EquipmentModelServiceImpl implements EquipmentModelService { + + private final EquipmentModelRepository equipmentModelRepository; + private final CategoryRepository categoryRepository; + private final LabelPrefixRepository labelPrefixRepository; + + // 모델 등록 + @Override + @Transactional + public EquipmentModelCreateResponseDTO createEquipmentModel(EquipmentModelCreateRequestDTO dto) { + // 0. 접두사 입력 정리 + String prefix = normalizePrefixCode(dto.prefixCode()); + String modelName = dto.modelName().trim(); + + // 1. 카테고리 조회 + Category category = categoryRepository.findByIdAndDeletedAtIsNull(dto.categoryId()) + .orElseThrow(() -> new CategoryException(CategoryErrorCode.CATEGORY_NOT_FOUND)); + + // 2. 접두사 규칙 및 라벨 검사 + // 노트북/태블릿은 라벨(접두사)가 있어야함 + // 기타는 라벨(접두사) 모두 없음 + // 따라서 라벨이 필요한데 없는 경우와, 라벨이 필요없는데 있는 경우를 막아야 함 + if (category.isLabelRequired() && prefix == null) { + throw new EquipmentModelException(EquipmentModelErrorCode.LABEL_PREFIX_REQUIRED); + } + if (!category.isLabelRequired() && prefix != null) { + throw new EquipmentModelException(EquipmentModelErrorCode.LABEL_PREFIX_NOT_ALLOWED); + } + + // 3. 중복 검사 (삭제된 모델 포함, DB UNIQUE와 같은 기준) + if (equipmentModelRepository.existsByCategoryIdAndName(category.getId(), modelName)) { + throw new EquipmentModelException(EquipmentModelErrorCode.DUPLICATE_MODEL); + } + + // 4. 접두사가 있다면 조회 (기타 카테고리는 null) + LabelPrefix labelPrefix = null; + if (prefix != null) { + labelPrefix = labelPrefixRepository.findByCode(prefix) + .orElseThrow(() -> new LabelPrefixException(LabelPrefixErrorCode.LABEL_PREFIX_NOT_FOUND)); + } + + // 5. 엔티티 생성 후 저장 + EquipmentModel model = EquipmentModelConverter.toEquipmentModel(dto, category, labelPrefix); + equipmentModelRepository.save(model); + + // 6. 응답 DTO return + return EquipmentModelConverter.toEquipmentModelCreateResponseDTO(model); + } + + // 접두사 입력 정리 : 널, 빈칸이 들어오면 널로, 그 외에는 trim + private String normalizePrefixCode(String prefixCode) { + if (prefixCode == null || prefixCode.isBlank()) { + return null; + } + return prefixCode.trim(); + } + +}