Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,18 @@ public ResponseEntity<ApiResponse<PageResponseDTO<BomResponseDTO>>> searchBoms(
return ApiResponse.success(SuccessStatus.OK,
bomService.searchBoms(keyword, categoryId, groupId, status, complexity, page, size));
}
//
// @Operation(summary = "모든 BOM totalCost 재계산", description = "quantity가 Double로 변경된 후 모든 BOM의 totalCost를 재계산합니다.")
// @PostMapping("/recalculate-all-costs")
// public ResponseEntity<ApiResponse<Void>> recalculateAllBomTotalCosts() {
// bomService.recalculateAllBomTotalCosts();
// return ApiResponse.success_only(SuccessStatus.OK);
// }
//
// @Operation(summary = "특정 BOM totalCost 재계산", description = "특정 BOM의 totalCost를 재계산합니다.")
// @PostMapping("/{bomId}/recalculate-cost")
// public ResponseEntity<ApiResponse<Void>> recalculateBomTotalCost(@PathVariable Long bomId) {
// bomService.recalculateBomTotalCost(bomId);
// return ApiResponse.success_only(SuccessStatus.OK);
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ public class BomDetailResponseDTO {
@AllArgsConstructor
@Builder
public static class BomMaterialDTO {
private Long id;
private Long materialId;
private String materialName;
private String materialCode;
private String unit;
private Long quantity;
private Double quantity;
private Long standardCost; // 단가
private Long total; // 단가 * 수량
}
Expand All @@ -46,10 +45,9 @@ public static BomDetailResponseDTO from(Bom bom) {
List<BomMaterialDTO> materialDtos = bom.getMaterials().stream()
.map(material -> {
Long cost = Optional.ofNullable(material.getMaterial().getStandardCost()).orElse(0L);
Long total = cost * Optional.ofNullable(material.getQuantity()).orElse(0L);
Long total = cost * Optional.ofNullable(material.getQuantity()).orElse(0.0).longValue();

return BomMaterialDTO.builder()
.id(material.getId())
.materialId(material.getMaterial().getId())
.materialName(material.getMaterial().getName())
.materialCode(material.getMaterial().getMaterialCode())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ public class BomRequestDTO {
@Builder
public static class BomMaterialDTO {
private Long materialId;
private Long quantity;
private Double quantity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public static BomResponseDTO from(Bom bom) {

// 실시간 계산
int componentCount = materials.size();
long totalQuantity = materials.stream()
.mapToLong(BomMaterialResponse::getQuantity)
long totalQuantity = (long) materials.stream()
.mapToDouble(BomMaterialResponse::getQuantity)
.sum();
Comment thread
taemin3 marked this conversation as resolved.

return BomResponseDTO.builder()
Expand Down Expand Up @@ -93,24 +93,21 @@ public static class BomMaterialResponse {
private String materialName;
private String materialCode;
private String unit;
private Long quantity;
private Double quantity;
private Long standardCost;
private Long total; // 단가 * 수량

public static BomMaterialResponse from(BomMaterial bm) {
Long cost = bm.getMaterial().getStandardCost() != null
? bm.getMaterial().getStandardCost()
: 0L;
Long total = cost * bm.getQuantity();

? bm.getMaterial().getStandardCost() : 0L;
return BomMaterialResponse.builder()
.materialId(bm.getMaterial().getId())
.materialName(bm.getMaterial().getName())
.materialCode(bm.getMaterial().getMaterialCode())
.unit(bm.getMaterial().getMaterialUnit())
.quantity(bm.getQuantity())
.standardCost(cost)
.total(total)
.total(cost * bm.getQuantity().longValue()) // Double을 Long으로 변환
.build();
Comment thread
taemin3 marked this conversation as resolved.
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/sampoom/backend/api/bom/entity/Bom.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ public void addMaterial(BomMaterial bomMaterial) {
public void calculateTotalCost() {
this.totalCost = this.materials.stream()
.mapToLong(m -> {
if (m.getMaterial().getStandardCost() == null) return 0L;
return m.getMaterial().getStandardCost() * m.getQuantity();
if (m.getMaterial().getStandardCost() == null || m.getQuantity() == null) return 0L;
return (long) (m.getMaterial().getStandardCost() * m.getQuantity());
Comment thread
taemin3 marked this conversation as resolved.
})
.sum();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ public class BomMaterial {
@JoinColumn(name = "material_id", nullable = false)
private Material material;

private Long quantity;
private Double quantity;

public void updateBom(Bom bom) {
this.bom = bom;
}

public void updateQuantity(Long quantity) {
public void updateQuantity(Double quantity) {
this.quantity = quantity;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static class MaterialInfo {
private String materialName;
private String materialCode;
private String unit;
private Long quantity;
private Double quantity;
}
}
}
29 changes: 26 additions & 3 deletions src/main/java/com/sampoom/backend/api/bom/service/BomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ public BomResponseDTO createBom(BomRequestDTO requestDTO) {
}

// 요청 자재 중복 제거 및 수량 합산
Map<Long, Long> idToQty = requestDTO.getMaterials().stream()
Map<Long, Double> idToQty = requestDTO.getMaterials().stream()
.collect(Collectors.toMap(
BomRequestDTO.BomMaterialDTO::getMaterialId,
BomRequestDTO.BomMaterialDTO::getQuantity,
Long::sum // 중복 materialId 수량 합산
Double::sum // 중복 materialId 수량 합산
));

// 한 번의 쿼리로 모든 자재 조회 (N+1 방지)
Expand Down Expand Up @@ -105,7 +105,7 @@ public BomResponseDTO createBom(BomRequestDTO requestDTO) {

// 5️⃣ 자재 매핑
for (Material material : materials) {
Long quantity = idToQty.get(material.getId());
Double quantity = idToQty.get(material.getId());
BomMaterial bm = BomMaterial.builder()
.bom(bom)
.material(material)
Expand Down Expand Up @@ -347,4 +347,27 @@ public void updateBomStatus(Long bomId, BomStatus newStatus) {
bom.updateStatus(newStatus);
bomRepository.save(bom);
}

/**
* 모든 BOM의 totalCost 재계산 (quantity가 Double로 변경된 후 실행)
*/
@Transactional
public void recalculateAllBomTotalCosts() {
List<Bom> boms = bomRepository.findAll();
for (Bom bom : boms) {
bom.calculateTotalCost();
}
bomRepository.saveAll(boms);
}

/**
* 특정 BOM의 totalCost 재계산
*/
@Transactional
public void recalculateBomTotalCost(Long bomId) {
Bom bom = bomRepository.findById(bomId)
.orElseThrow(() -> new NotFoundException(ErrorStatus.BOM_NOT_FOUND));
bom.calculateTotalCost();
bomRepository.save(bom);
}
Comment thread
taemin3 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,18 @@ public ResponseEntity<ApiResponse<PartListResponseDTO>> updatePart(
//
// return ApiResponse.success(SuccessStatus.PART_LIST_SUCCESS, partPage);
// }
//
// @Operation(summary = "모든 Part standard_total_cost 재계산", description = "standard_total_cost 컬럼 추가 후 모든 Part의 비용을 재계산합니다.")
// @PostMapping("/recalculate-all-costs")
// public ResponseEntity<ApiResponse<Void>> recalculateAllPartStandardCosts() {
// partService.recalculateAllPartStandardCosts();
// return ApiResponse.success_only(SuccessStatus.OK);
// }
//
// @Operation(summary = "특정 Part standard_total_cost 재계산", description = "특정 Part의 standard_total_cost를 재계산합니다.")
// @PostMapping("/{partId}/recalculate-cost")
// public ResponseEntity<ApiResponse<Void>> recalculatePartStandardCost(@PathVariable Long partId) {
// partService.recalculatePartStandardCost(partId);
// return ApiResponse.success_only(SuccessStatus.OK);
// }
}
62 changes: 60 additions & 2 deletions src/main/java/com/sampoom/backend/api/part/entity/Part.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class Part extends BaseTimeEntity {

private Long standardCost; // 표준 단가 (자동 계산, 입력 X)

@Column(name = "standard_total_cost")
private Long standardTotalCost; // 표준 총비용 (BOM 비용 * 기준수량 + 공정비)

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_id", nullable = false)
Expand Down Expand Up @@ -99,11 +101,67 @@ public void changeCode(String newCode) {

// Part의 총 비용을 BOM 비용과 Process 비용을 합쳐서 계산하는 메서드
public void calculateStandardCost(Long bomCost, Long processCost) {
this.standardCost = (bomCost != null ? bomCost : 0L) + (processCost != null ? processCost : 0L);
// standard_total_cost = bom 비용 * standard_quantity + 공정비
Long bomTotalCost = (bomCost != null ? bomCost : 0L) * (standardQuantity != null ? standardQuantity : 1);
Long totalProcessCost = processCost != null ? processCost : 0L;
this.standardTotalCost = bomTotalCost + totalProcessCost;

// standard_cost = standard_total_cost / standard_quantity (1000원 단위로 반올림)
Long rawStandardCost = this.standardQuantity != null && this.standardQuantity > 0
? this.standardTotalCost / this.standardQuantity
: this.standardTotalCost;
this.standardCost = roundToThousand(rawStandardCost);
}

// BOM 비용과 공정비를 분리해서 계산하는 새로운 메서드
public void calculateStandardCostAndTotal(Long bomCost, Long processCost) {
Long bomUnitCost = bomCost != null ? bomCost : 0L;
Long processUnitCost = processCost != null ? processCost : 0L;
Integer qty = standardQuantity != null ? standardQuantity : 1;

// standard_total_cost = bom 비용 * standard_quantity + 공정비
this.standardTotalCost = (bomUnitCost * qty) + processUnitCost;

// standard_cost = standard_total_cost / standard_quantity (1000원 단위로 반올림)
Long rawStandardCost = qty > 0 ? this.standardTotalCost / qty : this.standardTotalCost;
this.standardCost = roundToThousand(rawStandardCost);
}

// standardCost 직접 설정 메서드 (필요시)
public void setStandardCost(Long standardCost) {
this.standardCost = standardCost;
this.standardCost = roundToThousand(standardCost);
}

// standardTotalCost 직접 설정 메서드
public void setStandardTotalCost(Long standardTotalCost) {
this.standardTotalCost = standardTotalCost;
// standard_cost 재계산 (1000원 단위로 반올림)
Long rawStandardCost = this.standardQuantity != null && this.standardQuantity > 0
? this.standardTotalCost / this.standardQuantity
: this.standardTotalCost;
this.standardCost = roundToThousand(rawStandardCost);
}

// standardQuantity 변경 시 비용 재계산
public void updateStandardQuantity(Integer standardQuantity) {
this.standardQuantity = standardQuantity;
// 기존 standardTotalCost가 있다면 standardCost 재계산 (1000원 단위로 반올림)
if (this.standardTotalCost != null) {
Long rawStandardCost = standardQuantity != null && standardQuantity > 0
? this.standardTotalCost / standardQuantity
: this.standardTotalCost;
this.standardCost = roundToThousand(rawStandardCost);
}
}

/**
* 1000원 단위로 반올림하는 헬퍼 메서드
*/
private Long roundToThousand(Long amount) {
if (amount == null) {
return null;
}
// 1000으로 나누고 반올림한 후 다시 1000을 곱함
return Math.round(amount / 1000.0) * 1000L;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ public static class Payload {
private Long categoryId;

private Long standardCost;
private Long standardTotalCost; // 표준 총비용 추가
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public void publishAllPartEvents() {
.groupId(group != null ? group.getId() : null)
.categoryId(category != null ? category.getId() : null)
.standardCost(part.getStandardCost())
.standardTotalCost(part.getStandardTotalCost())
.build())
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public PartListResponseDTO createPart(PartCreateRequestDTO partCreateRequestDTO)
.groupId(partGroup.getId())
.categoryId(partGroup.getCategory().getId())
.standardCost(savedPart.getStandardCost())
.standardTotalCost(savedPart.getStandardTotalCost())
.build())
.build();

Expand Down Expand Up @@ -231,6 +232,7 @@ public PartListResponseDTO updatePart(Long partId, PartUpdateRequestDTO partUpda
.groupId(part.getPartGroup().getId())
.categoryId(part.getPartGroup().getCategory().getId())
.standardCost(part.getStandardCost())
.standardTotalCost(part.getStandardTotalCost())
.build())
.build();

Expand Down Expand Up @@ -283,6 +285,7 @@ public void deletePart(Long partId) {
.groupId(part.getPartGroup().getId())
.categoryId(part.getPartGroup().getCategory().getId())
.standardCost(part.getStandardCost())
.standardTotalCost(part.getStandardTotalCost())
.build())
.build();

Expand Down Expand Up @@ -430,6 +433,7 @@ public void publishPartUpdatedEvent(Part part) {
.groupId(part.getPartGroup().getId())
.categoryId(part.getPartGroup().getCategory().getId())
.standardCost(part.getStandardCost())
.standardTotalCost(part.getStandardTotalCost())
.build())
.build();

Expand Down Expand Up @@ -481,4 +485,33 @@ public Long getProcessCostByPartId(Long partId) {
Process process = processRepository.findByPartId(partId).orElse(null);
return process != null ? process.getTotalProcessCost() : 0L;
}

/**
* 모든 Part의 standard_total_cost 재계산
*/
@Transactional
public void recalculateAllPartStandardCosts() {
List<Part> parts = partRepository.findAll();
for (Part part : parts) {
Long bomCost = getBomCostByPartId(part.getId());
Long processCost = getProcessCostByPartId(part.getId());
part.calculateStandardCost(bomCost, processCost);
}
partRepository.saveAll(parts);
}

/**
* 특정 Part의 standard_total_cost 재계산
*/
@Transactional
public void recalculatePartStandardCost(Long partId) {
Part part = partRepository.findById(partId)
.orElseThrow(() -> new NotFoundException(ErrorStatus.PART_NOT_FOUND));

Long bomCost = getBomCostByPartId(partId);
Long processCost = getProcessCostByPartId(partId);
part.calculateStandardCost(bomCost, processCost);

partRepository.save(part);
}
Comment thread
taemin3 marked this conversation as resolved.
}