Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6fbdaef
[FEAT] 자재 관련 엔티티 구현
Oct 12, 2025
387f900
[FEAT] 공장 자재 관련 api 구현
Oct 12, 2025
f8200c5
Merge pull request #2 from 33-Auto/feat/#1
taemin3 Oct 12, 2025
69340d0
Merge pull request #6 from 33-Auto/main
CHOOSLA Oct 12, 2025
4147fa3
chore: Apply batch updates from central configuration
CHOOSLA Oct 14, 2025
1d6e0b2
[FIX] 공장 서비스 api 경로 변경
Oct 14, 2025
d37c026
Merge pull request #7 from 33-Auto/SPM-74
taemin3 Oct 14, 2025
d78c46b
[FIX] 스웨거 설정 수정
Oct 14, 2025
8f4553e
Merge pull request #8 from 33-Auto/SPM-87
taemin3 Oct 14, 2025
ee38ef3
[FIX] 자재, 자재 카테고리 엔티티 읽기 전용으로 수정
Oct 15, 2025
3295cc2
Merge pull request #9 from 33-Auto/SPM-89
taemin3 Oct 15, 2025
d53fc1a
[FEAT] 공장 생성 api 구현
Oct 15, 2025
15edb2e
[FEAT] 공장 생성 시 입력 검증
Oct 15, 2025
9344d3c
Merge pull request #10 from 33-Auto/SPM-91
taemin3 Oct 15, 2025
3961874
[FIX] 공장 자재 관련 엔티티 수정
Oct 15, 2025
8643ae8
chore: Apply batch updates from central configuration
CHOOSLA Oct 15, 2025
d5dcfdb
[FEAT] 부품 관련 api 구현
Oct 15, 2025
3aacd6f
[FIX] 공장 및 자재 관련 엔티티 수정
Oct 15, 2025
f8443cd
[FEAT] BOM 관련 api 구현
Oct 15, 2025
9a6d584
chore: Apply batch updates from central configuration
CHOOSLA Oct 16, 2025
481233b
chore: Apply batch updates from central configuration
CHOOSLA Oct 16, 2025
fe1462d
chore: Apply batch updates from central configuration
CHOOSLA Oct 16, 2025
fa3cb69
chore: Apply batch updates from central configuration
CHOOSLA Oct 16, 2025
6ed3f84
chore: Apply batch updates from central configuration
CHOOSLA Oct 16, 2025
5eab7da
chore: Apply batch updates from central configuration
CHOOSLA Oct 16, 2025
ea12978
chore: Apply batch updates from central configuration
CHOOSLA Oct 16, 2025
c17461d
chore: Apply batch updates from central configuration
CHOOSLA Oct 16, 2025
90d916b
[FIX] bom 엔티티 수정
Oct 16, 2025
f510f01
Merge pull request #11 from 33-Auto/SPM-36
taemin3 Oct 16, 2025
b37ec3b
[FEAT] 자재 목록/검색 api 구현 및 자재 주문 삭제, 취소 api 구현
Oct 16, 2025
2265e43
[FEAT] 자재 주문 상세 조회 구현
Oct 17, 2025
1f5862b
[FEAT] 자재 주문 관련 테스트 코드 구현
Oct 17, 2025
74d12b0
[FIX] EntityGraph 추가
Oct 17, 2025
bd9a6a7
Merge pull request #12 from 33-Auto/SPM-111
taemin3 Oct 17, 2025
f0f9023
chore: Apply batch updates from central configuration
CHOOSLA Oct 17, 2025
473cc75
chore: Apply batch updates from central configuration
CHOOSLA Oct 17, 2025
b43f3c4
chore: Apply batch updates from central configuration
CHOOSLA Oct 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions .github/workflows/pr-reminder.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
name: PR Reminder
name: PR Reminder

on:
schedule:
- cron: "0 0,5,8 * * *" # 아침 9시, 오후 2시, 오후 5시에 실행 (UTC 기준으로 설정해서 한국 시간에 맞춤)
workflow_dispatch:
on:
schedule:
- cron: "47 23,4,7,8,10 * * *" # 아침 8시 47분, 오후 2시 47분, 오후 4시 47분, 오후 5시 47분, 오후 7시 47분 에 실행 (UTC 기준으로 설정해서 한국 시간에 맞춤)
workflow_dispatch:

jobs:
call-reusable-reminder:
uses: 33-Auto/.github/.github/workflows/reusable-pr-reminder.yml@main
secrets:
# 해당 시크릿은 조직의 시크릿에 저장되어 있음
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
jobs:
call-reusable-reminder:
uses: 33-Auto/.github/.github/workflows/reusable-pr-reminder.yml@main
secrets:
# 해당 시크릿은 조직의 시크릿에 저장되어 있음
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
with:
SLACK_USER_MAP: ${{ vars.SLACK_USER_MAP }}
Comment on lines +10 to +15

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 4 months ago

To fix the problem:

  • Explicitly add a permissions block either at the workflow root level, which applies to all jobs unless they override it, or to the specific job if requirements differ by job.
  • The root-level block is most effective for this workflow, since only a single job exists and there is no indication that more jobs will be added.
  • Set the permissions to the minimum level needed. For most scheduled notification workflows, contents: read is sufficient unless pull request or issues write access is demonstrably required. If, in inspection, we find that SLACK integration is handled outside the repository, contents: read will suffice.
  • Edit .github/workflows/pr-reminder.yml by adding:
    permissions:
      contents: read
    just after the name declaration and before the on block.

Suggested changeset 1
.github/workflows/pr-reminder.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pr-reminder.yml b/.github/workflows/pr-reminder.yml
--- a/.github/workflows/pr-reminder.yml
+++ b/.github/workflows/pr-reminder.yml
@@ -1,4 +1,6 @@
   name: PR Reminder
+  permissions:
+    contents: read
 
   on:
     schedule:
EOF
@@ -1,4 +1,6 @@
name: PR Reminder
permissions:
contents: read

on:
schedule:
Copilot is powered by AI and may make mistakes. Always verify output.
20 changes: 20 additions & 0 deletions .github/workflows/trigger_infra.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Trigger Infra CD

on:
push:
branches:
- main

jobs:
trigger-infra:
runs-on: ubuntu-latest
steps:
- name: Trigger infra repo deploy workflow
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.ORGANIZATION_TOKEN }}
# [중요] 아래 repository 값은 모든 앱이 공유하는 '중앙 인프라 리포지토리' 주소이다.
repository: 33-Auto/Sampoom-Management-Infra
event-type: deploy
# 'Sampoom-Management-Backend-Part'은 스크립트가 동적으로 치환할 자리표시자(placeholder)이다.
client-payload: '{"service":"Sampoom-Management-Backend-Part","branch":"main"}'
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ dependencies {

//Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'

implementation("com.opencsv:opencsv:5.9") // CSV 파싱용
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.sampoom.factory.api.bom.controller;

import com.sampoom.factory.api.bom.dto.BomDetailResponseDto;
import com.sampoom.factory.api.bom.dto.BomRequestDto;
import com.sampoom.factory.api.bom.dto.BomResponseDto;
import com.sampoom.factory.api.bom.service.BomService;
import com.sampoom.factory.common.response.ApiResponse;
import com.sampoom.factory.common.response.PageResponseDto;
import com.sampoom.factory.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(Bill of Materials) 관련 API 입니다.")
@RestController
@RequestMapping("/bom")
@RequiredArgsConstructor
public class BomController {

private final BomService bomService;

@Operation(summary = "BOM 추가", description = "새로운 BOM을 등록합니다.")
@PostMapping
public ResponseEntity<ApiResponse<BomResponseDto>> createBom(@RequestBody BomRequestDto requestDto) {
return ApiResponse.success(SuccessStatus.CREATED, bomService.createBom(requestDto));
}

@Operation(summary = "BOM 목록 조회", description = "페이징 처리된 BOM 목록을 조회하고, 카테고리나 그룹으로 필터링합니다.")
@GetMapping
public ResponseEntity<ApiResponse<PageResponseDto<BomResponseDto>>> 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<ApiResponse<BomDetailResponseDto>> getBomDetail(@PathVariable Long bomId) {
return ApiResponse.success(SuccessStatus.OK, bomService.getBomDetail(bomId));
}

@Operation(summary = "BOM 수정", description = "특정 BOM 정보를 수정합니다.")
@PutMapping("/{bomId}")
public ResponseEntity<ApiResponse<BomResponseDto>> updateBom(
@PathVariable Long bomId,
@RequestBody BomRequestDto requestDto) {
return ApiResponse.success(SuccessStatus.OK, bomService.updateBom(bomId, requestDto));
}

@Operation(summary = "BOM 삭제", description = "특정 BOM을 삭제합니다.")
@DeleteMapping("/{bomId}")
public ResponseEntity<ApiResponse<Void>> deleteBom(@PathVariable Long bomId) {
bomService.deleteBom(bomId);
return ApiResponse.success_only(SuccessStatus.OK);
}

@Operation(summary = "BOM 검색", description = "부품 이름 또는 부품 코드로 BOM을 검색하고, 카테고리나 그룹으로 필터링합니다.")
@GetMapping("/search")
public ResponseEntity<ApiResponse<PageResponseDto<BomResponseDto>>> 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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.sampoom.factory.api.bom.dto;

import com.sampoom.factory.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<BomMaterialDto> 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 Long quantity;
}

public static BomDetailResponseDto from(Bom bom) {
List<BomMaterialDto> materialDtos = bom.getMaterials().stream()
.map(material -> BomMaterialDto.builder()
.id(material.getId())
.materialId(material.getMaterial().getId())
.materialName(material.getMaterial().getName())
.materialCode(material.getMaterial().getCode())
.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();
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/sampoom/factory/api/bom/dto/BomMaterialDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.sampoom.factory.api.bom.dto;

import com.sampoom.factory.api.bom.entity.BomMaterial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BomMaterialDto {
private Long id; // BomMaterial 엔티티의 ID
private Long materialId; // Material 엔티티의 ID
private String materialName; // 자재명
private String materialCode; // 자재 코드
private Long quantity; // 수량

public static BomMaterialDto from(BomMaterial material) {
return BomMaterialDto.builder()
.id(material.getId())
.materialId(material.getMaterial().getId())
.materialName(material.getMaterial().getName())
.materialCode(material.getMaterial().getCode())
.quantity(material.getQuantity())
.build();
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/sampoom/factory/api/bom/dto/BomRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.sampoom.factory.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<BomMaterialDto> materials;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class BomMaterialDto {
private Long materialId;
private Long quantity;
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/sampoom/factory/api/bom/dto/BomResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.sampoom.factory.api.bom.dto;


import com.sampoom.factory.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 BomResponseDto {
private Long id;
private String partName;
private String partCode;
private Long partId;
private List<BomMaterialDto> 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()
.map(BomMaterialDto::from)
.collect(Collectors.toList()))
.createdAt(bom.getCreatedAt())
.updatedAt(bom.getUpdatedAt())
.build();
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/sampoom/factory/api/bom/entity/Bom.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.sampoom.factory.api.bom.entity;

import com.sampoom.factory.api.part.entity.Part;
import com.sampoom.factory.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<BomMaterial> 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(); }



}


38 changes: 38 additions & 0 deletions src/main/java/com/sampoom/factory/api/bom/entity/BomMaterial.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.sampoom.factory.api.bom.entity;

import com.sampoom.factory.api.material.entity.Material;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "bom_material") // 실제 테이블명이 'BOM-자재'가 아니라면 명확히 지정
@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;
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sampoom.factory.api.bom.repository;

import com.sampoom.factory.api.bom.entity.BomMaterial;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BomMaterialRepository extends JpaRepository<BomMaterial,Long> {
}
Loading
Loading