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
11 changes: 10 additions & 1 deletion src/main/java/com/almang/inventory/receipt/domain/Receipt.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@
import com.almang.inventory.store.domain.Store;
import jakarta.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

@Entity
@Table(name = "receipts")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE receipts SET deleted_at = NOW() WHERE receipt_id = ?")
@Where(clause = "deleted_at IS NULL")
Comment on lines +23 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

@SQLDelete 어노테이션이 실제로 사용되지 않습니다.

@SQLDelete는 JPA의 EntityManager.remove() 또는 CrudRepository.delete(entity) 호출 시에만 트리거됩니다. 현재 ReceiptService.deleteReceipt()receipt.delete() 도메인 메서드를 호출하여 필드만 설정하므로, @SQLDelete가 실행되지 않습니다.

두 가지 접근 방식 중 하나를 선택하세요:

  1. 도메인 메서드 방식 유지: @SQLDelete 어노테이션 제거 (현재 로직에서 불필요)
  2. JPA 방식 사용: receiptRepository.delete(receipt) 호출로 변경하고, 도메인 delete() 메서드에서 deletedAt 설정 제거

@Where 어노테이션은 조회 시 삭제된 레코드를 자동 필터링하므로 유지하면 좋습니다.

참고: Hibernate Soft Delete 공식 문서

🤖 Prompt for AI Agents
In src/main/java/com/almang/inventory/receipt/domain/Receipt.java around lines
23-24, the @SQLDelete annotation is unused because delete logic calls the domain
method receipt.delete() instead of EntityManager/Repository.delete(), so remove
the @SQLDelete line, keep the @Where(clause = "deleted_at IS NULL") to continue
filtering soft-deleted rows, and ensure any imports related only to @SQLDelete
are removed; alternatively, if you prefer the JPA approach, change
ReceiptService.deleteReceipt() to call receiptRepository.delete(receipt) and
remove the domain delete() setter, but do not keep both patterns.

public class Receipt extends BaseTimeEntity {

@Id
Expand All @@ -42,6 +47,9 @@ public class Receipt extends BaseTimeEntity {
@Column(name = "is_activate", nullable = false)
private boolean activated;

@Column(name = "deleted_at")
private LocalDateTime deletedAt;

@OneToMany(mappedBy = "receipt", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<ReceiptItem> items = new ArrayList<>();
Expand All @@ -62,13 +70,14 @@ public void update(
}
}

public void deactivate() {
public void delete() {
if (this.status == ReceiptStatus.CONFIRMED) {
throw new BaseException(ErrorCode.RECEIPT_ALREADY_CONFIRMED);
}
if (this.status == ReceiptStatus.CANCELED) {
throw new BaseException(ErrorCode.RECEIPT_ALREADY_CANCELED);
}
this.deletedAt = LocalDateTime.now();
this.activated = false;
this.status = ReceiptStatus.CANCELED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public DeleteReceiptResponse deleteReceipt(Long receiptId, Long userId) {

log.info("[ReceiptService] 입고 삭제 요청 - userId: {}, storeId: {}", userId, store.getId());
Receipt receipt = findReceiptByIdAndValidateAccess(receiptId, store);
receipt.deactivate();
receipt.delete();

// 입고 취소 후 재고 상태 변경
for (OrderItem orderItem : receipt.getOrder().getItems()) {
Expand Down Expand Up @@ -248,6 +248,7 @@ private Receipt toReceiptEntity(Order order, Store store) {
.receiptDate(LocalDate.now())
.status(ReceiptStatus.PENDING)
.activated(true)
.deletedAt(null)
.build();
}

Expand Down Expand Up @@ -287,6 +288,9 @@ private Receipt findReceiptByIdAndValidateAccess(Long receiptId, Store store) {
Receipt receipt = receiptRepository.findById(receiptId)
.orElseThrow(() -> new BaseException(ErrorCode.RECEIPT_NOT_FOUND));

if (receipt.getDeletedAt() != null) {
throw new BaseException(ErrorCode.RECEIPT_NOT_FOUND);
}
if (!receipt.getStore().getId().equals(store.getId())) {
throw new BaseException(ErrorCode.RECEIPT_ACCESS_DENIED);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.almang.inventory.receipt.dto.request.UpdateReceiptRequest;
import com.almang.inventory.receipt.dto.response.ConfirmReceiptResponse;
import com.almang.inventory.receipt.dto.response.DeleteReceiptItemResponse;
import com.almang.inventory.receipt.dto.response.DeleteReceiptResponse;
import com.almang.inventory.receipt.dto.response.ReceiptItemResponse;
import com.almang.inventory.receipt.dto.response.ReceiptResponse;
import com.almang.inventory.receipt.repository.ReceiptItemRepository;
Expand Down Expand Up @@ -878,18 +879,14 @@ private Order newOrderWithItems(Store store, Vendor vendor) {
receiptRepository.save(receipt);

// when
receiptService.deleteReceipt(receipt.getId(), user.getId());
DeleteReceiptResponse response = receiptService.deleteReceipt(receipt.getId(), user.getId());

// then
Receipt deleted = receiptRepository.findById(receipt.getId()).orElseThrow();
assertThat(deleted.isActivated()).isFalse();
assertThat(deleted.getStatus()).isEqualTo(ReceiptStatus.CANCELED);
assertThat(response.success()).isTrue();

for (OrderItem orderItem : order.getItems()) {
Inventory updated = inventoryRepository.findByProduct_Id(orderItem.getProduct().getId())
.orElseThrow();
assertThat(updated.getIncomingReserved()).isEqualByComparingTo(BigDecimal.ZERO);
}
assertThatThrownBy(() -> receiptService.getReceipt(receipt.getId(), user.getId()))
.isInstanceOf(BaseException.class)
.hasMessageContaining(ErrorCode.RECEIPT_NOT_FOUND.getMessage());
}

@Test
Expand Down